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

Last change on this file since 181 was 181, checked in by Nicholas Riley, 15 years ago

ICeCoffEEAction.c: Replace undocumented _LSCopyApplicationURLsForItemURL
with LSCopyApplicationURLsForURL, which was introduced in 10.3.

ICFindFilesToRemove/UICookieMonster.m: Fix some minor bugs revealed by
new compiler warnings in Apple's GCC 4.0.

ICeCoffEETextEdit.c: Pass an unsigned long instead of a SInt32 to
Delay(), as intended (another GCC 4.0 nit-pick).

English.lproj/InfoPlist.strings: Update version number.

ICeCoffEEShared.h: Enable debugging.

ICeCoffEE APE.xcode: Changes for Xcode 2.0.

ICeCoffEE.m: Casts to satisfy GCC 4.0.

APEMain.m: Fix CFStringCompare third argument: options, not a pointer.
Yet another dumb coding mistake pointed out courtesy of GCC 4.0.

File size: 21.6 KB
Line 
1// ICeCoffEE - Internet Config Cocoa Editor Extension
2// Nicholas Riley <mailto:icecoffee@sabi.net>
3
4/* To do/think about:
5
6- TXNClick - MLTE has its own support in Jaguar and later, but it's lousy
7
8Done:
9
10- TEClick - TextEdit
11- flash on success (like BBEdit)
12- display dialog on failure (decode OSStatus)
13- adjust URL blinking
14- app exclusion list - make a pref pane (see AquaShade config)
15- _LSCopyApplicationURLsForItemURL - list apps
16- Menu on command-option-click: add bookmark, open with other helper, pass to configurable service, ...?
17
18*/
19
20#import "ICeCoffEE.h"
21#import <Carbon/Carbon.h>
22#include <unistd.h>
23#import "ICeCoffEESuper.h"
24
25iccfPrefRec ICCF_prefs;
26
27NSString *ICCF_ErrString(OSStatus err, NSString *context) {   
28    if (err == noErr || err == userCanceledErr) return nil;
29
30    NSString *errNum = [NSString stringWithFormat: @"%ld", err];
31    NSString *errDesc = ICCF_LocalizedString(errNum);
32
33    if (errDesc == NULL || errDesc == errNum)
34        errDesc = [NSString stringWithFormat: ICCF_LocalizedString(@"An unknown error occurred in %@"), context];
35
36    return [NSString stringWithFormat: @"%@ (%d)", errDesc, (int)err];
37}
38
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
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
66BOOL ICCF_EventIsCommandMouseDown(NSEvent *e) {
67    return ([e type] == NSLeftMouseDown && ([e modifierFlags] & NSCommandKeyMask) != 0 && [e clickCount] == 1);
68}
69
70iccfURLAction ICCF_KeyboardAction() {
71    unsigned int modifierFlags = [[NSApp currentEvent] modifierFlags];
72    iccfURLAction action;
73    action.presentMenu = (modifierFlags & NSAlternateKeyMask) != 0;
74    action.launchInBackground = (modifierFlags & NSShiftKeyMask) != 0;
75    return action;
76}
77
78void ICCF_CheckRange(NSRange range) {
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);
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];
93        [set formUnionWithCharacterSet: [[NSCharacterSet characterSetWithRange: NSMakeRange(0, 128)] invertedSet]]; // remove non-ASCII characters
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    }
118    err = ICStart(&ICCF_icInst, kICCFCreator);
119    NSCAssert1(err == noErr, ICCF_LocalizedString(@"Unable to start Internet Config (error %d)"), err);
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
136ConstStringPtr ICCF_GetHint(ICInstance inst, const char *urlData, Size length, long *selStart, long *selEnd, Boolean *needsSlashes) {
137    Handle h = NewHandle(0);
138    OSStatus err;
139
140    if (h == NULL) return NULL;
141
142    // parse the URL providing a bogus protocol, to get rid of escaped forms
143    err = ICParseURL(inst, "\p*", urlData, length, selStart, selEnd, h);
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;
153    if (hSize >= 2 && urlParsed[0] == '*' && urlParsed[1] == ':') {
154        // this is an IC-inserted protocol; skip over it
155        i = 2;
156        *needsSlashes = (hSize < i + 2 || urlParsed[i] != '/' || urlParsed[i + 1] != '/');
157    } else *needsSlashes = false;
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);
169    if (sawAt) {
170        *needsSlashes = false;
171        return "\pmailto";
172    }
173    return "\phttp";
174}
175
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((const char *)&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
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
218        NSCAssert([string canBeConvertedToEncoding: NSASCIIStringEncoding], @"No URL is selected");
219
220        urlData = (char *)malloc( (range->length + 1) * sizeof(char));
221        NSCAssert(urlData != NULL, @"Internal error: can't allocate memory for URL string");
222
223        selStart = 0; selEnd = range->length;
224
225        [string getCString: urlData];
226
227        h = NewHandle(0);
228        NSCAssert(h != NULL, @"Internal error: can't allocate URL handle");
229
230        err = ICParseURL(ICCF_GetInst(), "\pmailto", urlData, range->length, &selStart, &selEnd, h);
231        DisposeHandle(h);
232
233        ICCF_OSErrCAssert(err, @"ICParseURL");
234   
235        range->length = selEnd - selStart;
236        range->location += selStart;
237    NS_HANDLER
238        free(urlData);
239        [localException raise];
240    NS_ENDHANDLER
241   
242    free(urlData);
243}
244
245void ICCF_LaunchURL(NSString *string, iccfURLAction action) {
246    OSStatus err;
247    long selStart, selEnd;
248    unsigned len = [string length];
249
250    Handle h = NULL;
251   
252    NS_DURING
253        h = NewHandle(len);
254        if (h == NULL)
255            ICCF_OSErrCAssert(MemError(), @"NewHandle");
256
257        if (CFStringGetBytes((CFStringRef)string, CFRangeMake(0, len), kCFStringEncodingASCII, '\0', false, (UInt8 *)*h, len, NULL) != len)
258            ICCF_OSErrCAssert(kTECNoConversionPathErr, @"CFStringGetBytes");
259
260        selStart = 0; selEnd = len;
261
262        Boolean needsSlashes;
263        ConstStringPtr hint = ICCF_GetHint(ICCF_GetInst(), *h, len, &selStart, &selEnd, &needsSlashes);
264        NSCAssert(hint != NULL, @"Internal error: can't get protocol hint for URL");
265
266        if (needsSlashes) {
267            ICCF_AddSlashes(h, hint);
268            len = selEnd = GetHandleSize(h);
269        }
270
271        err = ICCF_DoURLAction(ICCF_GetInst(), hint, *h, selStart, selEnd, action);
272        ICCF_OSErrCAssert(err, @"ICCF_DoURLAction");
273       
274    NS_HANDLER
275        DisposeHandle(h);
276        [localException raise];
277    NS_ENDHANDLER
278
279    DisposeHandle(h);   
280}
281
282// XXX not sure what to do if there's already a selection; BBEdit and MLTE extend it, Tex-Edit Plus doesn't.
283// RFC-ordained max URL length, just to avoid passing IC/LS multi-megabyte documents
284#if ICCF_DEBUG
285const long ICCF_MAX_URL_LEN = 60; // XXX change later
286#else
287const long ICCF_MAX_URL_LEN = 1024;
288#endif
289
290Boolean ICCF_enabled = true;
291
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);
300    if (result != NSAlertDefaultReturn) {
301        result = NSRunAlertPanel(ICCF_LocalizedString(@"DisableAlertTitle"), ICCF_LocalizedString(@"DisableAlertMessage%@"), ICCF_LocalizedString(@"DisableAlertDisableButton"), ICCF_LocalizedString(@"DisableAlertDontDisableButton"), nil,
302           [(NSString *)ICCF_CopyAppName() autorelease]);
303        if (result == NSAlertDefaultReturn)
304            ICCF_enabled = NO;
305    }
306    return YES;
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
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"));
321
322        ICCF_StartIC();
323
324        NSCAssert([s length] != 0, ICCF_LocalizedString(@"No text was found"));
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
377        ICCF_LaunchURL([s substringWithRange: range], ICCF_KeyboardAction());
378
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
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;
405    // don't want to use [[NSApp servicesMenu] title] because the Services menu may not have been created yet
406    NSString *servicesTitle = [[NSBundle bundleWithIdentifier: @"com.apple.AppKit"] localizedStringForKey: @"Services" value: nil table: @"ServicesMenu"];
407    if (servicesTitle == nil) {
408        ICLog(@"Can't get localized text for 'Services' in AppKit.framework");
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];
417    return [servicesItem autorelease];
418}
419
420static const unichar UNICHAR_BLACK_RIGHT_POINTING_SMALL_TRIANGLE = 0x25b8;
421
422// returns YES if menu contains useful items, NO otherwise
423BOOL ICCF_ConsolidateServicesMenu(NSMenu *menu, NSDictionary *serviceOptions) {
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;
428    NSDictionary *itemOptions = nil;
429    BOOL shouldKeepItem = NO, shouldKeepMenu = NO;
430
431    while ( (menuItem = [enumerator nextObject]) != nil) {
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]);
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();
464    if (ICCF_ConsolidateServicesMenu([servicesItem submenu], (NSDictionary *)ICCF_prefs.serviceOptions))
465        return servicesItem;
466    else
467        return nil;
468}
469
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];
475}
476
477NSMenu *ICCF_MenuForEvent(NSView *self, NSMenu *contextMenu, NSEvent *e) {
478    if (contextMenu != nil && [e type] == NSRightMouseDown || ([e type] == NSLeftMouseDown && [e modifierFlags] & NSControlKeyMask)) {
479        int servicesItemIndex = [contextMenu indexOfItemWithRepresentedObject: ICCF_SERVICES_ITEM];
480        // always regenerate: make sure menu reflects context
481        if (servicesItemIndex != -1) {
482            [contextMenu removeItemAtIndex: servicesItemIndex];
483            [contextMenu removeItemAtIndex: servicesItemIndex - 1];
484        }
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        }
492    }
493    return contextMenu;
494}
495
496@implementation ICeCoffEE
497
498+ (void)IC_addRemoveServicesMenu;
499{
500    NSMenu *mainMenu = [[NSApplication sharedApplication] mainMenu];
501    static NSMenuItem *servicesItem = nil;
502   
503    if (servicesItem == nil && ICCF_prefs.servicesInMenuBar) {
504        servicesItem = [ICCF_ServicesMenuItem() retain];
505
506        int insertLoc = [mainMenu indexOfItemWithSubmenu: [NSApp windowsMenu]];
507        if (insertLoc == -1)
508            insertLoc = [mainMenu numberOfItems];
509
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    }
516    [[NSApp servicesMenu] update]; // enable keyboard equivalents
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
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
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!");
546        if (abs(downPt.x - upPt.x) <= kICHysteresisPixels && abs(downPt.y - upPt.y) <= kICHysteresisPixels) {
547            ICCF_LaunchURLFromTextView(self);
548        }
549    } else {
550        [super mouseDown: e];
551    }
552#if ICCF_DEBUG
553    down = NO;
554#endif
555}
556
557@end
Note: See TracBrowser for help on using the repository browser.