Ignore:
Timestamp:
01/30/03 05:31:29 (21 years ago)
Author:
Nicholas Riley
Message:

ICeCoffEE 1.3b2 plus some changes for 1.3

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/ICeCoffEE/ICeCoffEE/ICeCoffEE.m

    r66 r74  
    66- Carbon contextual menu plugin which presents Services (yah!)
    77  for both files and text
    8 - Carbon version
    9 - app exclusion list - make a pref pane (see AquaShade config)
    108- if it's not a URL, try using TextExtras' open list
    119- John Hayes' suggestions
    12 - ICeCoffEE 2 functionality (bookmark helper app for cmd-option-click)
    13 - adjust URL blinking
    14 
    15 Carbon support ideas:
    16     TEClick - TextEdit
    17     TXNClick - MLTE
    18     ? ATSUI
    19     WASTE has its own support
     10- _LSCopyApplicationURLsForItemURL - list apps
     11- Menu on command-option-click: add bookmark, open with other helper, pass to configurable service, ...?
     12- TXNClick - MLTE has its own support in Jaguar and later, but it's lousy
    2013
    2114Done:
    2215
     16- TEClick - TextEdit
    2317- flash on success (like BBEdit)
    2418- display dialog on failure (decode OSStatus)
     19- adjust URL blinking
     20- app exclusion list - make a pref pane (see AquaShade config)
    2521
    2622*/
     
    3026#include <unistd.h>
    3127#import "ICeCoffEESuper.h"
    32 
    33 @implementation ICeCoffEE
    34 
    35 typedef struct {
    36     OSStatus status;
    37     NSString * const desc;
    38 } errRec, errList[];
    39 
    40 static errList ERRS = {
    41     // Internet Config errors
    42     { icPrefNotFoundErr, @"No helper application is defined for the selected URLÕs scheme (e.g. http:)" },
    43     { icNoURLErr, @"The selection is not a URL" },
    44     { icInternalErr, @"Internet Config experienced an internal error" },
    45     // Misc. errors
    46     { paramErr, @"The selection is not a complete URL" },
    47     { 0, NULL }
    48 };
    49 
    50 NSString *ICCF_ErrString(OSStatus err, NSString *context) {
    51     errRec *rec;
    52     NSString *errDesc = [NSString stringWithFormat: @"An unknown error occurred in %@", context];
    53     if (err == noErr) return nil;
    54     for (rec = &(ERRS[0]) ; rec->status != 0 ; rec++)
    55         if (rec->status == err) {
    56             errDesc = rec->desc;
    57             break;
    58         }
     28#import "ICeCoffEEAppMenu.h"
     29
     30iccfPrefRec ICCF_prefs;
     31
     32NSString *ICCF_ErrString(OSStatus err, NSString *context) {   
     33    if (err == noErr || err == userCanceledErr) return nil;
     34
     35    NSString *errNum = [NSString stringWithFormat: @"%ld", err];
     36    NSString *errDesc = ICCF_LocalizedString(errNum);
     37
     38    if (errDesc == NULL || errDesc == errNum)
     39        errDesc = [NSString stringWithFormat: ICCF_LocalizedString(@"An unknown error occurred in %@"), context];
     40
    5941    return [NSString stringWithFormat: @"%@ (%d)", errDesc, (int)err];
    6042}
    6143
     44CFStringRef ICCF_CopyErrString(OSStatus err, CFStringRef context) {
     45    if (err == noErr || err == userCanceledErr) return NULL;
     46
     47    CFStringRef errNum = CFStringCreateWithFormat(NULL, NULL, CFSTR("%ld"), err);
     48    CFStringRef errDesc = ICCF_CopyLocalizedString(errNum);
     49
     50    if (errDesc == NULL || errDesc == errNum) {
     51        CFStringRef errDescFormat = ICCF_CopyLocalizedString(CFSTR("An unknown error occurred in %@"));
     52        if (errDesc != NULL) CFRelease(errDesc);
     53        errDesc = CFStringCreateWithFormat(NULL, NULL, errDescFormat, context);
     54    }
     55
     56    CFStringRef errStr = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%d)"), errDesc, (int)err);
     57
     58    if (errNum != NULL) CFRelease(errNum);
     59    if (errDesc != NULL) CFRelease(errDesc);
     60    return errStr;
     61}
     62
    6263BOOL ICCF_EventIsCommandMouseDown(NSEvent *e) {
    63     return ([e type] == NSLeftMouseDown && [e modifierFlags] == NSCommandKeyMask && [e clickCount] == 1);
     64    unsigned int modifierFlags = [e modifierFlags];
     65    return ([e type] == NSLeftMouseDown &&
     66            (modifierFlags == NSCommandKeyMask || modifierFlags == (NSCommandKeyMask | NSAlternateKeyMask))
     67            && [e clickCount] == 1);
     68}
     69
     70BOOL ICCF_OptionKeyIsDown() {
     71    unsigned int modifierFlags = [[NSApp currentEvent] modifierFlags];
     72    return (modifierFlags & NSAlternateKeyMask) != 0;
    6473}
    6574
    6675void ICCF_CheckRange(NSRange range) {
    67     NSCAssert(range.length > 0, @"No URL is selected");
    68     NSCAssert1(range.length <= ICCF_MAX_URL_LEN, @"The potential URL is longer than %ld characters", ICCF_MAX_URL_LEN);
     76    NSCAssert(range.length > 0, ICCF_LocalizedString(@"No URL is selected"));
     77    NSCAssert1(range.length <= ICCF_MAX_URL_LEN, ICCF_LocalizedString(@"The potential URL is longer than %lu characters"), ICCF_MAX_URL_LEN);
    6978}
    7079
     
    103112        ICCF_StopIC();
    104113    }
    105     err = ICStart(&ICCF_icInst, 'ICCF');
    106     NSCAssert1(err == noErr, @"Unable to start Internet Config (error %d)", err);
     114    err = ICStart(&ICCF_icInst, kICCFCreator);
     115    NSCAssert1(err == noErr, ICCF_LocalizedString(@"Unable to start Internet Config (error %d)"), err);
    107116}
    108117
     
    121130}
    122131
     132ConstStringPtr ICCF_GetHint(ICInstance inst, const char *urlData, long startIndex, long endIndex) {
     133    Handle h = NewHandle(0);
     134    OSStatus err;
     135    long tmpStartIndex = startIndex, tmpEndIndex = endIndex;
     136   
     137    if (h == NULL) return NULL;
     138
     139    // parse the URL providing a bogus protocol, to get rid of escaped forms
     140    err = ICParseURL(inst, "\p*", urlData + startIndex, endIndex - startIndex + 1,
     141                     &tmpStartIndex, &tmpEndIndex, h);
     142    if (err != noErr) return NULL;
     143
     144    // scan through the parsed URL looking for characters not found in email addresses
     145    Size hSize = GetHandleSize(h);
     146    if (hSize == 0) return NULL;
     147
     148    const char *urlParsed = *h;
     149    long i = 0;
     150    Boolean sawAt = false;
     151    if (urlParsed[0] == '*' && urlParsed[1] == ':') {
     152        // this is an IC-inserted protocol; skip over it
     153        i = 2;
     154    }
     155    for ( ; i < hSize ; i++) {
     156        char c = urlParsed[i];
     157        if (c == '@') {
     158            sawAt = true;
     159        } else if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
     160                     (c == '+' || c == '-' || c == '_' || c == '!' || c == '.'))) {
     161            DisposeHandle(h);
     162            return "\phttp";
     163        }
     164    }
     165    DisposeHandle(h);
     166    return (sawAt ? "\pmailto" : "\phttp");
     167}
     168
    123169void ICCF_ParseURL(NSString *string, NSRange *range) {
    124170    OSStatus err;
     
    140186
    141187        urlData = (char *)malloc( (range->length + 1) * sizeof(char));
    142         NSCAssert(urlData != NULL, @"Internal error: can't allocate memory for URL string");
     188        NSCAssert(urlData != NULL, @"Internal error: canÕt allocate memory for URL string");
    143189
    144190        selStart = 0; selEnd = range->length;
     
    147193
    148194        h = NewHandle(0);
    149         NSCAssert(h != NULL, @"Internal error: can't allocate URL handle");
    150    
     195        NSCAssert(h != NULL, @"Internal error: canÕt allocate URL handle");
     196
    151197        err = ICParseURL(ICCF_GetInst(), "\pmailto", urlData, range->length, &selStart, &selEnd, h);
     198        DisposeHandle(h);
     199
    152200        ICCF_OSErrCAssert(err, @"ICParseURL");
    153201   
    154         DisposeHandle(h);
    155         range->length = range->length - (range->length - selEnd) + selStart;
     202        range->length = selEnd - selStart;
    156203        range->location += selStart;
    157204    NS_HANDLER
     
    163210}
    164211
    165 void ICCF_LaunchURL(NSString *string) {
     212void ICCF_LaunchURL(NSString *string, BOOL chooseApp) {
    166213    OSStatus err;
    167214    long selStart, selEnd;
     
    172219    NS_DURING
    173220        urlData = (char *)malloc( (len + 1) * sizeof(char));
    174         NSCAssert(urlData != NULL, @"Internal error: can't allocate memory for URL string");
     221        NSCAssert(urlData != NULL, @"Internal error: canÕt allocate memory for URL string");
    175222
    176223        [string getCString: urlData];
     
    178225        selStart = 0; selEnd = len;
    179226
    180         err = ICLaunchURL(ICCF_GetInst(), "\pmailto", urlData, len, &selStart, &selEnd);
    181         ICCF_OSErrCAssert(err, @"ICLaunchURL");
     227        ConstStringPtr hint = ICCF_GetHint(ICCF_GetInst(), urlData, selStart, selEnd);
     228        NSCAssert(hint != NULL, @"Internal error: canÕt get protocol hint for URL");
     229
     230        if (chooseApp) {
     231            err = ICCF_LaunchURLWithApplication(ICCF_GetInst(), hint, urlData, selStart, selEnd);
     232            ICCF_OSErrCAssert(err, @"ICCF_LaunchURLWithApplication");
     233        } else {
     234            err = ICLaunchURL(ICCF_GetInst(), hint, urlData, len, &selStart, &selEnd);
     235            ICCF_OSErrCAssert(err, @"ICLaunchURL");
     236        }
    182237       
    183238    NS_HANDLER
     
    189244}
    190245
    191 // XXX not sure what to do if there's already a selection; BBEdit extends it, Tex-Edit Plus doesn't.
     246// XXX not sure what to do if there's already a selection; BBEdit and MLTE extend it, Tex-Edit Plus doesn't.
    192247// RFC-ordained max URL length, just to avoid passing IC multi-megabyte documents
    193248#if ICCF_DEBUG
     
    197252#endif
    198253
    199 BOOL ICCF_enabled = YES;
    200 
    201 void ICCF_HandleException(NSException *e) {
    202     int result = NSRunAlertPanel(@"Open Internet Location", @"The selected Internet location could not be opened.\n\n%@.", @"OK", nil, @"Disable ICeCoffEEÉ", e);
     254Boolean ICCF_enabled = true;
     255
     256BOOL ICCF_HandleException(NSException *e) {
     257    if ([e reason] == nil || [[e reason] length] == 0)
     258        return NO;
     259   
     260    if (ICCF_prefs.errorSoundEnabled) NSBeep();
     261    if (!ICCF_prefs.errorDialogEnabled) return YES;
     262   
     263    int result = NSRunAlertPanel(ICCF_LocalizedString(@"AlertTitle"), ICCF_LocalizedString(@"AlertMessage%@"), nil, nil, ICCF_LocalizedString(@"AlertDisableButton"), e);
    203264    if (result != NSAlertDefaultReturn) {
    204         result = NSRunAlertPanel(@"Disable ICeCoffEE", @"If you believe ICeCoffEE is interfering with the normal functioning of this application, you can turn it off in this application until the application has quit.\n\nIf this is the first time you are experiencing this problem, please email icecoffee@sabi.net with the details of the conflict.\n\nTo remove ICeCoffEE permanently, drag its icon to the Trash or use the ICeCoffEE Installer.", @"Disable", @"DonÕt Disable", nil);
     265        result = NSRunAlertPanel(ICCF_LocalizedString(@"DisableAlertTitle"), ICCF_LocalizedString(@"DisableAlertMessage"), ICCF_LocalizedString(@"DisableAlertDisableButton"), ICCF_LocalizedString(@"DisableAlertDontDisableButton"), nil);
    205266        if (result == NSAlertDefaultReturn)
    206267            ICCF_enabled = NO;
    207268    }
     269    return YES;
    208270}
    209271
     
    218280    NS_DURING
    219281
    220         NSCAssert(range.location != NSNotFound, @"There is no insertion point or selection in the text field you clicked");
    221         NSCAssert(s != nil, @"Sorry, ICeCoffEE is unable to locate the insertion point or selection");
     282        NSCAssert(range.location != NSNotFound, ICCF_LocalizedString(@"There is no insertion point or selection in the text field where you clicked"));
     283        NSCAssert(s != nil, ICCF_LocalizedString(@"Sorry, ICeCoffEE is unable to locate the insertion point or selection"));
    222284
    223285        ICCF_StartIC();
    224286
    225         NSCAssert([s length] != 0, @"No text was found");
     287        NSCAssert([s length] != 0, ICCF_LocalizedString(@"No text was found"));
    226288
    227289        if (range.location == [s length]) range.location--; // work around bug in selectionRangeForProposedRange (r. 2845418)
     
    273335        ICCF_ParseURL([s substringWithRange: range], &range);
    274336
    275         for (i = 0 ; i < 3 ; i++) {
    276             NSRange emptyRange = {range.location, 0};
    277             [self setInsertionPointColor: [self backgroundColor]];
    278             [self setSelectedRange: range affinity: NSSelectionAffinityDownstream stillSelecting: YES];
    279             [self display];
    280             usleep(60000);
    281             [self setSelectedRange: emptyRange affinity: NSSelectionAffinityDownstream stillSelecting: YES];
    282             [self display];
    283             usleep(60000);
    284         }
    285337        [self setSelectedRange: range affinity: NSSelectionAffinityDownstream stillSelecting: NO];
    286338        [self display];
    287339
    288         ICCF_LaunchURL([s substringWithRange: range]);
     340        ICCF_LaunchURL([s substringWithRange: range], ICCF_OptionKeyIsDown());
     341
     342        if (ICCF_prefs.textBlinkEnabled) {
     343            for (i = 0 ; i < ICCF_prefs.textBlinkCount ; i++) {
     344                NSRange emptyRange = {range.location, 0};
     345                [self setSelectedRange: emptyRange affinity: NSSelectionAffinityDownstream stillSelecting: YES];
     346                [self display];
     347                usleep(kICBlinkDelayUsecs);
     348                [self setInsertionPointColor: [self backgroundColor]];
     349                [self setSelectedRange: range affinity: NSSelectionAffinityDownstream stillSelecting: YES];
     350                [self display];
     351                usleep(kICBlinkDelayUsecs);
     352            }
     353        }
    289354
    290355    NS_HANDLER
     
    316381}
    317382
    318 void ICCF_AddServicesMenu() {
    319     [ICeCoffEE performSelector: @selector(IC_addServicesMenu) withObject: nil afterDelay: 0.0];
     383void ICCF_AddRemoveServicesMenu() {
     384    // needed because:
     385    // (a) we get called before the runloop has properly started and will crash if we don't delay on app startup
     386    // (b) the APE message handler calls us from another thread and nothing happens if we try to add a menu on it
     387    [ICeCoffEE performSelectorOnMainThread: @selector(IC_addRemoveServicesMenu) withObject: nil waitUntilDone: NO];
    320388}
    321389
     
    323391    if (contextMenu != nil && [e type] == NSRightMouseDown || ([e type] == NSLeftMouseDown && [e modifierFlags] & NSControlKeyMask)) {
    324392        int servicesItemIndex = [contextMenu indexOfItemWithRepresentedObject: ICCF_SERVICES_ITEM];
    325         if (servicesItemIndex == -1) {
     393        if (servicesItemIndex == -1 && ICCF_prefs.servicesInContextualMenu) {
    326394            [contextMenu addItem: [NSMenuItem separatorItem]];
    327395            [contextMenu addItem: ICCF_ServicesMenuItem()];
     396        } else if (servicesItemIndex != -1 && !ICCF_prefs.servicesInContextualMenu) {
     397            [contextMenu removeItemAtIndex: servicesItemIndex];
     398            [contextMenu removeItemAtIndex: servicesItemIndex - 1];
    328399        }
    329400    }
    330401    return contextMenu;
    331402}
     403
     404@implementation ICeCoffEE
    332405
    333406+ (NSString *)IC_version;
     
    337410}
    338411
    339 + (void)IC_addServicesMenu;
     412+ (void)IC_addRemoveServicesMenu;
    340413{
    341414    NSMenu *mainMenu = [[NSApplication sharedApplication] mainMenu];
    342     int insertLoc = [mainMenu indexOfItemWithSubmenu: [NSApp windowsMenu]];
    343 
    344     if (insertLoc == -1)
    345         insertLoc = [mainMenu numberOfItems];
    346 
    347     [mainMenu insertItem: ICCF_ServicesMenuItem() atIndex: insertLoc];
     415    static NSMenuItem *servicesItem = nil;
     416   
     417    if (servicesItem == nil && ICCF_prefs.servicesInMenuBar) {
     418        servicesItem = [ICCF_ServicesMenuItem() retain];
     419
     420        int insertLoc = [mainMenu indexOfItemWithSubmenu: [NSApp windowsMenu]];
     421        if (insertLoc == -1)
     422            insertLoc = [mainMenu numberOfItems];
     423
     424        [mainMenu insertItem: servicesItem atIndex: insertLoc];
     425    } else if (servicesItem != nil && !ICCF_prefs.servicesInMenuBar) {
     426        [mainMenu removeItem: servicesItem];
     427        [servicesItem release];
     428        servicesItem = nil;
     429    }
    348430}
    349431
     
    365447    down = YES;
    366448    ICLog(@"ICeCoffEE down: %@", e);
    367     NSLog(@"super is %@", self);
    368449#endif
    369450    // we don't actually get a mouseUp event, just wait for mouseDown to return
    370451    [super mouseDown: e];
    371     if (!ICCF_enabled) return;
    372     // don't want command-option-click, command-shift-click, etc. to trigger
     452    if (!ICCF_enabled || !ICCF_prefs.commandClickEnabled) {
     453#if ICCF_DEBUG
     454        down = NO;
     455#endif
     456        return;
     457    }
     458    // don't want command-shift-click, etc. to trigger
    373459    if (ICCF_EventIsCommandMouseDown(e)) {
    374460        NSEvent *upEvent = [[self window] currentEvent];
     
    377463        ICLog(@"next: %@", upEvent);
    378464        NSAssert([upEvent type] == NSLeftMouseUp, @"NSTextView mouseDown: did not return with current event as mouse up!");
    379         if (abs(downPt.x - upPt.x) <= 4 && abs(downPt.y - upPt.y) <= 4) {
     465        if (abs(downPt.x - upPt.x) <= kICHysteresisPixels && abs(downPt.y - upPt.y) <= kICHysteresisPixels) {
    380466            ICCF_LaunchURLFromTextView(self);
    381467        }
Note: See TracChangeset for help on using the changeset viewer.