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

Last change on this file since 167 was 167, checked in by Nicholas Riley, 15 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
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(&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, *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.