source: trunk/ICeCoffEE/ICeCoffEE/ICeCoffEE.m@ 167

Last change on this file since 167 was 167, checked in by Nicholas Riley, 19 years ago

ICeCoffEE 1.4 and preliminary 1.4.1 changes. Sorry, I forgot to
commit version 1.4 when it was released, so the precise source for
that release has been lost.

See the release notes for details of what changed in these versions.
1.4 was a significant feature release; 1.4.1 is a bug fix for 10.3.9,
incorporating up-to-date Unsanity Installer and APE.

package-ICeCoffEE.sh: use xcodebuild instead of pbxbuild.

File size: 21.6 KB
RevLine 
[66]1// ICeCoffEE - Internet Config Cocoa Editor Extension
2// Nicholas Riley <mailto:icecoffee@sabi.net>
3
4/* To do/think about:
5
[74]6- TXNClick - MLTE has its own support in Jaguar and later, but it's lousy
[66]7
8Done:
9
[74]10- TEClick - TextEdit
[66]11- flash on success (like BBEdit)
12- display dialog on failure (decode OSStatus)
[74]13- adjust URL blinking
14- app exclusion list - make a pref pane (see AquaShade config)
[75]15- _LSCopyApplicationURLsForItemURL - list apps
[79]16- Menu on command-option-click: add bookmark, open with other helper, pass to configurable service, ...?
[66]17
18*/
19
20#import "ICeCoffEE.h"
21#import <Carbon/Carbon.h>
22#include <unistd.h>
23#import "ICeCoffEESuper.h"
24
[74]25iccfPrefRec ICCF_prefs;
[66]26
[74]27NSString *ICCF_ErrString(OSStatus err, NSString *context) {
28 if (err == noErr || err == userCanceledErr) return nil;
[66]29
[74]30 NSString *errNum = [NSString stringWithFormat: @"%ld", err];
31 NSString *errDesc = ICCF_LocalizedString(errNum);
[66]32
[74]33 if (errDesc == NULL || errDesc == errNum)
34 errDesc = [NSString stringWithFormat: ICCF_LocalizedString(@"An unknown error occurred in %@"), context];
35
[66]36 return [NSString stringWithFormat: @"%@ (%d)", errDesc, (int)err];
37}
38
[74]39CFStringRef ICCF_CopyErrString(OSStatus err, CFStringRef context) {
40 if (err == noErr || err == userCanceledErr) return NULL;
41
42 CFStringRef errNum = CFStringCreateWithFormat(NULL, NULL, CFSTR("%ld"), err);
43 CFStringRef errDesc = ICCF_CopyLocalizedString(errNum);
44
45 if (errDesc == NULL || errDesc == errNum) {
46 CFStringRef errDescFormat = ICCF_CopyLocalizedString(CFSTR("An unknown error occurred in %@"));
47 if (errDesc != NULL) CFRelease(errDesc);
48 errDesc = CFStringCreateWithFormat(NULL, NULL, errDescFormat, context);
49 }
50
51 CFStringRef errStr = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%d)"), errDesc, (int)err);
52
53 if (errNum != NULL) CFRelease(errNum);
54 if (errDesc != NULL) CFRelease(errDesc);
55 return errStr;
56}
57
[139]58CFStringRef ICCF_CopyAppName() {
59 ProcessSerialNumber psn = {0, kCurrentProcess};
60 CFStringRef appName = NULL;
61 CopyProcessName(&psn, &appName);
62 if (appName == NULL) return CFSTR("(unknown)");
63 return appName;
64}
65
[66]66BOOL ICCF_EventIsCommandMouseDown(NSEvent *e) {
[106]67 return ([e type] == NSLeftMouseDown && ([e modifierFlags] & NSCommandKeyMask) != 0 && [e clickCount] == 1);
[66]68}
69
[106]70iccfURLAction ICCF_KeyboardAction() {
[74]71 unsigned int modifierFlags = [[NSApp currentEvent] modifierFlags];
[106]72 iccfURLAction action;
73 action.presentMenu = (modifierFlags & NSAlternateKeyMask) != 0;
74 action.launchInBackground = (modifierFlags & NSShiftKeyMask) != 0;
75 return action;
[74]76}
77
[66]78void ICCF_CheckRange(NSRange range) {
[74]79 NSCAssert(range.length > 0, ICCF_LocalizedString(@"No URL is selected"));
80 NSCAssert1(range.length <= ICCF_MAX_URL_LEN, ICCF_LocalizedString(@"The potential URL is longer than %lu characters"), ICCF_MAX_URL_LEN);
[66]81}
82
83void ICCF_Delimiters(NSCharacterSet **leftPtr, NSCharacterSet **rightPtr) {
84 static NSCharacterSet *urlLeftDelimiters = nil, *urlRightDelimiters = nil;
85
86 if (urlLeftDelimiters == nil || urlRightDelimiters == nil) {
87 NSMutableCharacterSet *set = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
88 NSMutableCharacterSet *tmpSet;
89 [urlLeftDelimiters release];
90 [urlRightDelimiters release];
91
92 [set autorelease];
[139]93 [set formUnionWithCharacterSet: [[NSCharacterSet characterSetWithRange: NSMakeRange(0, 128)] invertedSet]]; // remove non-ASCII characters
[66]94 [set formUnionWithCharacterSet: [NSCharacterSet punctuationCharacterSet]];
95 [set removeCharactersInString: @";/?:@&=+$,-_.!~*'()%#"]; // RFC 2396 ¤2.2, 2.3, 2.4, plus #
96
97 tmpSet = [[set mutableCopy] autorelease];
98 [tmpSet formUnionWithCharacterSet: [NSCharacterSet characterSetWithCharactersInString: @"><("]];
99 urlLeftDelimiters = [tmpSet copy]; // make immutable again - for efficiency
100
101 tmpSet = [[set mutableCopy] autorelease];
102 [tmpSet formUnionWithCharacterSet: [NSCharacterSet characterSetWithCharactersInString: @"><)"]];
103 urlRightDelimiters = [tmpSet copy]; // make immutable again - for efficiency
104 }
105
106 *leftPtr = urlLeftDelimiters; *rightPtr = urlRightDelimiters;
107}
108
109static ICInstance ICCF_icInst = NULL;
110
111void ICCF_StartIC() {
112 OSStatus err;
113
114 if (ICCF_icInst != NULL) {
115 ICLog(@"ICCF_StartIC: Internet Config is already running!");
116 ICCF_StopIC();
117 }
[74]118 err = ICStart(&ICCF_icInst, kICCFCreator);
119 NSCAssert1(err == noErr, ICCF_LocalizedString(@"Unable to start Internet Config (error %d)"), err);
[66]120}
121
122void ICCF_StopIC() {
123 if (ICCF_icInst == NULL) {
124 ICLog(@"ICCF_StopIC: Internet Config is not running!");
125 } else {
126 ICStop(ICCF_icInst);
127 ICCF_icInst = NULL;
128 }
129}
130
131ICInstance ICCF_GetInst() {
132 NSCAssert(ICCF_icInst != NULL, @"Internal error: Called ICCF_GetInst without ICCF_StartIC");
133 return ICCF_icInst;
134}
135
[79]136ConstStringPtr ICCF_GetHint(ICInstance inst, const char *urlData, Size length, long *selStart, long *selEnd, Boolean *needsSlashes) {
[74]137 Handle h = NewHandle(0);
138 OSStatus err;
[79]139
[74]140 if (h == NULL) return NULL;
141
142 // parse the URL providing a bogus protocol, to get rid of escaped forms
[79]143 err = ICParseURL(inst, "\p*", urlData, length, selStart, selEnd, h);
[74]144 if (err != noErr) return NULL;
145
146 // scan through the parsed URL looking for characters not found in email addresses
147 Size hSize = GetHandleSize(h);
148 if (hSize == 0) return NULL;
149
150 const char *urlParsed = *h;
151 long i = 0;
152 Boolean sawAt = false;
[79]153 if (hSize >= 2 && urlParsed[0] == '*' && urlParsed[1] == ':') {
[74]154 // this is an IC-inserted protocol; skip over it
155 i = 2;
[79]156 *needsSlashes = (hSize < i + 2 || urlParsed[i] != '/' || urlParsed[i + 1] != '/');
157 } else *needsSlashes = false;
[74]158 for ( ; i < hSize ; i++) {
159 char c = urlParsed[i];
160 if (c == '@') {
161 sawAt = true;
162 } else if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
163 (c == '+' || c == '-' || c == '_' || c == '!' || c == '.'))) {
164 DisposeHandle(h);
165 return "\phttp";
166 }
167 }
168 DisposeHandle(h);
[79]169 if (sawAt) {
170 *needsSlashes = false;
171 return "\pmailto";
172 }
173 return "\phttp";
[74]174}
175
[79]176static const char *kICSlashes = "//";
177
178void ICCF_AddSlashes(Handle h, ConstStringPtr hint) {
179 Size sizeBefore = GetHandleSize(h);
180 unsigned char hintLength = StrLength(hint);
181 char *copy = (char *)malloc(sizeBefore);
182 memcpy(copy, *h, sizeBefore);
183 ICLog(@"ICCF_AddSlashes before: |%s|\n", *h);
184 ReallocateHandle(h, sizeBefore + 2);
185
186 // if *h begins with '<hint>:', then copy the slashes after it
187 if (sizeBefore > hintLength + 1 && strncmp(&hint[1], copy, hintLength) == 0 && copy[hintLength] == ':') {
188 memcpy(*h, copy, hintLength + 1);
189 memcpy(*h + hintLength + 1, kICSlashes, 2);
190 memcpy(*h + hintLength + 3, &copy[hintLength + 1], sizeBefore - hintLength - 1);
191 } else {
192 memcpy(*h, kICSlashes, 2);
193 memcpy(*h + 2, copy, sizeBefore);
194 }
195
196 free(copy);
197 ICLog(@"ICCF_AddSlashes after: |%s|\n", *h);
198}
199
[66]200void ICCF_ParseURL(NSString *string, NSRange *range) {
201 OSStatus err;
202 Handle h;
203 long selStart, selEnd;
204 char *urlData = NULL;
205
206 NSCAssert(range->length == [string length], @"Internal error: URL string is wrong length");
207
208 NS_DURING
209 if ([[NSCharacterSet characterSetWithCharactersInString: @";,."] characterIsMember:
210 [string characterAtIndex: range->length - 1]]) {
211 range->length--;
212 }
213
214 string = [string substringToIndex: range->length];
215
216 ICLog(@"Parsing URL |%@|", string);
217
[139]218 NSCAssert([string canBeConvertedToEncoding: NSASCIIStringEncoding], @"No URL is selected");
219
[66]220 urlData = (char *)malloc( (range->length + 1) * sizeof(char));
[167]221 NSCAssert(urlData != NULL, @"Internal error: can't allocate memory for URL string");
[66]222
223 selStart = 0; selEnd = range->length;
224
225 [string getCString: urlData];
226
227 h = NewHandle(0);
[167]228 NSCAssert(h != NULL, @"Internal error: can't allocate URL handle");
[74]229
[66]230 err = ICParseURL(ICCF_GetInst(), "\pmailto", urlData, range->length, &selStart, &selEnd, h);
[74]231 DisposeHandle(h);
232
[66]233 ICCF_OSErrCAssert(err, @"ICParseURL");
234
[74]235 range->length = selEnd - selStart;
[66]236 range->location += selStart;
237 NS_HANDLER
238 free(urlData);
239 [localException raise];
240 NS_ENDHANDLER
241
242 free(urlData);
243}
244
[106]245void ICCF_LaunchURL(NSString *string, iccfURLAction action) {
[66]246 OSStatus err;
247 long selStart, selEnd;
248 unsigned len = [string length];
249
[79]250 Handle h = NULL;
[66]251
252 NS_DURING
[79]253 h = NewHandle(len);
254 if (h == NULL)
255 ICCF_OSErrCAssert(MemError(), @"NewHandle");
[66]256
[79]257 if (CFStringGetBytes((CFStringRef)string, CFRangeMake(0, len), kCFStringEncodingASCII, '\0', false, *h, len, NULL) != len)
258 ICCF_OSErrCAssert(kTECNoConversionPathErr, @"CFStringGetBytes");
[66]259
260 selStart = 0; selEnd = len;
261
[79]262 Boolean needsSlashes;
263 ConstStringPtr hint = ICCF_GetHint(ICCF_GetInst(), *h, len, &selStart, &selEnd, &needsSlashes);
[167]264 NSCAssert(hint != NULL, @"Internal error: can't get protocol hint for URL");
[74]265
[79]266 if (needsSlashes) {
267 ICCF_AddSlashes(h, hint);
268 len = selEnd = GetHandleSize(h);
269 }
270
[106]271 err = ICCF_DoURLAction(ICCF_GetInst(), hint, *h, selStart, selEnd, action);
272 ICCF_OSErrCAssert(err, @"ICCF_DoURLAction");
[66]273
274 NS_HANDLER
[79]275 DisposeHandle(h);
[66]276 [localException raise];
277 NS_ENDHANDLER
278
[79]279 DisposeHandle(h);
[66]280}
281
[74]282// XXX not sure what to do if there's already a selection; BBEdit and MLTE extend it, Tex-Edit Plus doesn't.
[106]283// RFC-ordained max URL length, just to avoid passing IC/LS multi-megabyte documents
[66]284#if ICCF_DEBUG
[106]285const long ICCF_MAX_URL_LEN = 60; // XXX change later
[66]286#else
287const long ICCF_MAX_URL_LEN = 1024;
288#endif
289
[74]290Boolean ICCF_enabled = true;
[66]291
[74]292BOOL ICCF_HandleException(NSException *e) {
293 if ([e reason] == nil || [[e reason] length] == 0)
294 return NO;
295
296 if (ICCF_prefs.errorSoundEnabled) NSBeep();
297 if (!ICCF_prefs.errorDialogEnabled) return YES;
298
299 int result = NSRunAlertPanel(ICCF_LocalizedString(@"AlertTitle"), ICCF_LocalizedString(@"AlertMessage%@"), nil, nil, ICCF_LocalizedString(@"AlertDisableButton"), e);
[66]300 if (result != NSAlertDefaultReturn) {
[139]301 result = NSRunAlertPanel(ICCF_LocalizedString(@"DisableAlertTitle"), ICCF_LocalizedString(@"DisableAlertMessage%@"), ICCF_LocalizedString(@"DisableAlertDisableButton"), ICCF_LocalizedString(@"DisableAlertDontDisableButton"), nil,
302 [(NSString *)ICCF_CopyAppName() autorelease]);
[66]303 if (result == NSAlertDefaultReturn)
304 ICCF_enabled = NO;
305 }
[74]306 return YES;
[66]307}
308
309void ICCF_LaunchURLFromTextView(NSTextView *self) {
310 NSCharacterSet *urlLeftDelimiters = nil, *urlRightDelimiters = nil;
311 NSRange range = [self selectedRange], delimiterRange;
312 NSColor *insertionPointColor = [self insertionPointColor];
313 NSString *s = [[self textStorage] string]; // according to the class documentation, sending 'string' is guaranteed to be O(1)
314 unsigned extraLen;
315 int i;
316
317 NS_DURING
318
[74]319 NSCAssert(range.location != NSNotFound, ICCF_LocalizedString(@"There is no insertion point or selection in the text field where you clicked"));
320 NSCAssert(s != nil, ICCF_LocalizedString(@"Sorry, ICeCoffEE is unable to locate the insertion point or selection"));
[66]321
322 ICCF_StartIC();
323
[74]324 NSCAssert([s length] != 0, ICCF_LocalizedString(@"No text was found"));
[66]325
326 if (range.location == [s length]) range.location--; // work around bug in selectionRangeForProposedRange (r. 2845418)
327
328 range = [self selectionRangeForProposedRange: range granularity: NSSelectByWord];
329
330 // However, NSSelectByWord does not capture even the approximate boundaries of a URL
331 // (text to a space/line ending character); it'll stop at a period in the middle of a hostname.
332 // So, we expand it as follows:
333
334 ICCF_CheckRange(range);
335
336 ICCF_Delimiters(&urlLeftDelimiters, &urlRightDelimiters);
337
338 // XXX instead of 0, make this stop at the max URL length to prevent protracted searches
339 // add 1 to range to trap delimiters that are on the edge of the selection (i.e., <...)
340 delimiterRange = [s rangeOfCharacterFromSet: urlLeftDelimiters
341 options: NSLiteralSearch | NSBackwardsSearch
342 range: NSMakeRange(0, range.location + (range.location != [s length]))];
343 if (delimiterRange.location == NSNotFound) {
344 // extend to beginning of string
345 range.length += range.location;
346 range.location = 0;
347 } else {
348 NSCAssert(delimiterRange.length == 1, @"Internal error: delimiter matched range is not of length 1");
349 range.length += range.location - delimiterRange.location - 1;
350 range.location = delimiterRange.location + 1;
351 }
352
353 ICCF_CheckRange(range);
354
355 // XXX instead of length of string, make this stop at the max URL length to prevent protracted searches
356 // add 1 to range to trap delimiters that are on the edge of the selection (i.e., ...>)
357 extraLen = [s length] - range.location - range.length;
358 delimiterRange = [s rangeOfCharacterFromSet: urlRightDelimiters
359 options: NSLiteralSearch
360 range: NSMakeRange(range.location + range.length - (range.length != 0),
361 extraLen + (range.length != 0))];
362 if (delimiterRange.location == NSNotFound) {
363 // extend to end of string
364 range.length += extraLen;
365 } else {
366 NSCAssert(delimiterRange.length == 1, @"Internal error: delimiter matched range is not of length 1");
367 range.length += delimiterRange.location - range.location - range.length;
368 }
369
370 ICCF_CheckRange(range);
371
372 ICCF_ParseURL([s substringWithRange: range], &range);
373
374 [self setSelectedRange: range affinity: NSSelectionAffinityDownstream stillSelecting: NO];
375 [self display];
376
[106]377 ICCF_LaunchURL([s substringWithRange: range], ICCF_KeyboardAction());
[66]378
[74]379 if (ICCF_prefs.textBlinkEnabled) {
380 for (i = 0 ; i < ICCF_prefs.textBlinkCount ; i++) {
381 NSRange emptyRange = {range.location, 0};
382 [self setSelectedRange: emptyRange affinity: NSSelectionAffinityDownstream stillSelecting: YES];
383 [self display];
384 usleep(kICBlinkDelayUsecs);
385 [self setInsertionPointColor: [self backgroundColor]];
386 [self setSelectedRange: range affinity: NSSelectionAffinityDownstream stillSelecting: YES];
387 [self display];
388 usleep(kICBlinkDelayUsecs);
389 }
390 }
391
[66]392 NS_HANDLER
393 ICCF_HandleException(localException);
394 NS_ENDHANDLER
395
396 ICCF_StopIC();
397 [self setInsertionPointColor: insertionPointColor];
398}
399
400NSString * const ICCF_SERVICES_ITEM = @"ICeCoffEE Services Item";
401
402NSMenuItem *ICCF_ServicesMenuItem() {
403 NSMenuItem *servicesItem;
404 NSMenu *servicesMenu;
[139]405 // don't want to use [[NSApp servicesMenu] title] because the Services menu may not have been created yet
[66]406 NSString *servicesTitle = [[NSBundle bundleWithIdentifier: @"com.apple.AppKit"] localizedStringForKey: @"Services" value: nil table: @"ServicesMenu"];
407 if (servicesTitle == nil) {
[167]408 ICLog(@"Can't get localized text for 'Services' in AppKit.framework");
[66]409 servicesTitle = @"Services";
410 }
411 servicesMenu = [[NSMenu alloc] initWithTitle: servicesTitle];
412 servicesItem = [[NSMenuItem alloc] initWithTitle: servicesTitle action:nil keyEquivalent:@""];
413 [[NSApplication sharedApplication] setServicesMenu: servicesMenu];
414 [servicesItem setSubmenu: servicesMenu];
415 [servicesItem setRepresentedObject: ICCF_SERVICES_ITEM];
416 [servicesMenu release];
[139]417 return [servicesItem autorelease];
[66]418}
419
[139]420static const unichar UNICHAR_BLACK_RIGHT_POINTING_SMALL_TRIANGLE = 0x25b8;
421
422// returns YES if menu contains useful items, NO otherwise
[142]423BOOL ICCF_ConsolidateServicesMenu(NSMenu *menu, NSDictionary *serviceOptions) {
[139]424 [menu update]; // doesn't propagate to submenus, so we need to do this first
425 NSEnumerator *enumerator = [[menu itemArray] objectEnumerator];
426 NSMenuItem *menuItem;
427 NSMenu *submenu;
[142]428 NSDictionary *itemOptions = nil;
[139]429 BOOL shouldKeepItem = NO, shouldKeepMenu = NO;
430
431 while ( (menuItem = [enumerator nextObject]) != nil) {
[142]432 if (serviceOptions != nil)
433 itemOptions = [serviceOptions objectForKey: [menuItem title]];
434 if ([[itemOptions objectForKey: (NSString *)kICServiceHidden] boolValue]) {
435 shouldKeepItem = NO;
436 } else if ( (submenu = [menuItem submenu]) != nil) {
437 shouldKeepItem = ICCF_ConsolidateServicesMenu(submenu, [itemOptions objectForKey: (NSString *)kICServiceSubmenu]);
[139]438 if (shouldKeepItem && [submenu numberOfItems] == 1) { // consolidate
439 NSMenuItem *serviceItem = [[submenu itemAtIndex: 0] retain];
440 [serviceItem setTitle:
441 [NSString stringWithFormat: @"%@ %@ %@", [menuItem title], [NSString stringWithCharacters: &UNICHAR_BLACK_RIGHT_POINTING_SMALL_TRIANGLE length: 1], [serviceItem title]]];
442
443 int serviceIndex = [menu indexOfItem: menuItem];
444 [submenu removeItemAtIndex: 0]; // can't have item in two menus
445 [menu removeItemAtIndex: serviceIndex];
446 [menu insertItem: serviceItem atIndex: serviceIndex];
447 [serviceItem release];
448 }
449 } else {
450 shouldKeepItem = [menuItem isEnabled];
451 }
452 if (shouldKeepItem) {
453 shouldKeepMenu = YES;
454 } else {
455 [menu removeItem: menuItem];
456 }
457 }
458
459 return shouldKeepMenu;
460}
461
462NSMenuItem *ICCF_ContextualServicesMenuItem() {
463 NSMenuItem *servicesItem = ICCF_ServicesMenuItem();
[142]464 if (ICCF_ConsolidateServicesMenu([servicesItem submenu], (NSDictionary *)ICCF_prefs.serviceOptions))
[139]465 return servicesItem;
466 else
467 return nil;
468}
469
[74]470void ICCF_AddRemoveServicesMenu() {
471 // needed because:
472 // (a) we get called before the runloop has properly started and will crash if we don't delay on app startup
473 // (b) the APE message handler calls us from another thread and nothing happens if we try to add a menu on it
474 [ICeCoffEE performSelectorOnMainThread: @selector(IC_addRemoveServicesMenu) withObject: nil waitUntilDone: NO];
[66]475}
476
[167]477NSMenu *ICCF_MenuForEvent(NSView *self, NSMenu *contextMenu, NSEvent *e) {
[66]478 if (contextMenu != nil && [e type] == NSRightMouseDown || ([e type] == NSLeftMouseDown && [e modifierFlags] & NSControlKeyMask)) {
479 int servicesItemIndex = [contextMenu indexOfItemWithRepresentedObject: ICCF_SERVICES_ITEM];
[139]480 // always regenerate: make sure menu reflects context
481 if (servicesItemIndex != -1) {
[74]482 [contextMenu removeItemAtIndex: servicesItemIndex];
483 [contextMenu removeItemAtIndex: servicesItemIndex - 1];
[66]484 }
[139]485 if (ICCF_prefs.servicesInContextualMenu) {
486 NSMenuItem *contextualServicesItem = ICCF_ContextualServicesMenuItem();
487 if (contextualServicesItem != nil) {
488 [contextMenu addItem: [NSMenuItem separatorItem]];
489 [contextMenu addItem: contextualServicesItem];
490 }
491 }
[66]492 }
493 return contextMenu;
494}
495
[74]496@implementation ICeCoffEE
497
498+ (void)IC_addRemoveServicesMenu;
[66]499{
500 NSMenu *mainMenu = [[NSApplication sharedApplication] mainMenu];
[74]501 static NSMenuItem *servicesItem = nil;
502
503 if (servicesItem == nil && ICCF_prefs.servicesInMenuBar) {
504 servicesItem = [ICCF_ServicesMenuItem() retain];
[66]505
[74]506 int insertLoc = [mainMenu indexOfItemWithSubmenu: [NSApp windowsMenu]];
507 if (insertLoc == -1)
508 insertLoc = [mainMenu numberOfItems];
[66]509
[74]510 [mainMenu insertItem: servicesItem atIndex: insertLoc];
511 } else if (servicesItem != nil && !ICCF_prefs.servicesInMenuBar) {
512 [mainMenu removeItem: servicesItem];
513 [servicesItem release];
514 servicesItem = nil;
515 }
[139]516 [[NSApp servicesMenu] update]; // enable keyboard equivalents
[66]517}
518
519// XXX localization?
520- (NSMenu *)menuForEvent:(NSEvent *)e;
521{
522 NSMenu *myMenu = [super menuForEvent: e];
523 return ICCF_MenuForEvent(self, myMenu, e);
524}
525
526- (void)mouseDown:(NSEvent *)e;
527{
528#if ICCF_DEBUG
529 static BOOL down = NO;
530 if (down) {
531 ICLog(@"recursive invocation!");
532 return;
533 }
534 down = YES;
535 ICLog(@"ICeCoffEE down: %@", e);
536#endif
[106]537 if (ICCF_enabled && ICCF_prefs.commandClickEnabled && ICCF_EventIsCommandMouseDown(e)) {
538 // don't want to trigger selection extension or anything else; pass through as a plain click
539 [super mouseDown: [NSEvent mouseEventWithType: NSLeftMouseDown location: [e locationInWindow] modifierFlags: 0 timestamp: [e timestamp] windowNumber: [e windowNumber] context: [e context] eventNumber: [e eventNumber] clickCount: 1 pressure: 0]];
540 // we don't actually get a mouseUp event, just wait for mouseDown to return
[66]541 NSEvent *upEvent = [[self window] currentEvent];
542 NSPoint downPt = [e locationInWindow];
543 NSPoint upPt = [upEvent locationInWindow];
544 ICLog(@"next: %@", upEvent);
545 NSAssert([upEvent type] == NSLeftMouseUp, @"NSTextView mouseDown: did not return with current event as mouse up!");
[74]546 if (abs(downPt.x - upPt.x) <= kICHysteresisPixels && abs(downPt.y - upPt.y) <= kICHysteresisPixels) {
[66]547 ICCF_LaunchURLFromTextView(self);
548 }
[106]549 } else {
550 [super mouseDown: e];
[66]551 }
552#if ICCF_DEBUG
553 down = NO;
554#endif
555}
556
557@end
Note: See TracBrowser for help on using the repository browser.