source: trunk/ICeCoffEE/ICeCoffEE/ICeCoffEEAction.c @ 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: 12.2 KB
Line 
1/*
2 *  ICeCoffEEAction.c
3 *  ICeCoffEE APE
4 *
5 *  Created by Nicholas Riley on Wed Jan 29 2003.
6 *  Copyright (c) 2003 Nicholas Riley. All rights reserved.
7 *
8 */
9
10#include "ICeCoffEEAction.h"
11#include "ICeCoffEEConfig.h"
12#include "ICeCoffEEShared.h"
13#include "ICeCoffEEBookmarks.h"
14
15#define THROW_ERR(e) { err = e; goto END; }
16
17static CFStringRef ICCF_NameWithLocation(CFStringRef name, CFURLRef url) {
18    CFURLRef urlMinus = CFURLCreateCopyDeletingLastPathComponent(NULL, url);
19    CFStringRef urlString = CFURLCopyFileSystemPath(urlMinus, kCFURLPOSIXPathStyle);
20    CFStringRef nameWithLocationTemplate = ICCF_CopyLocalizedString(CFSTR("App%@Location%@"));
21    CFStringRef nameWithLocation = CFStringCreateWithFormat(NULL, NULL, nameWithLocationTemplate, name, urlString);
22   
23    SAFE_RELEASE(nameWithLocationTemplate);
24    SAFE_RELEASE(urlMinus);
25    SAFE_RELEASE(urlString);
26    return nameWithLocation;
27}
28
29static const MenuItemIndex kICAppMenuItemHasPath = 0xfffe;
30
31typedef struct {
32    CFURLRef defaultAppURL; // URL of default app, set to NULL after added to menu
33    CFMutableSetRef appPaths;
34    CFMutableDictionaryRef appItemTitles; // keys: URLs; values: item titles (CFString)
35    CFMutableDictionaryRef appURLs; // keys: app display names (CFString), values: URL of item without path appended, or NULL if already appended (at least 2 items with this name exist)
36    MenuRef menu;
37} icAppMenuContext;
38
39static OSStatus ICCF_AddAppItemTitle(icAppMenuContext *ctx, CFURLRef appURL) {
40    CFStringRef appName = NULL, appItemTitle = NULL;
41    CFBundleRef appBundle = NULL;
42    OSStatus err = noErr;
43
44    CFStringRef appPath = CFURLCopyPath(appURL);
45    // only one entry for each path
46    if (CFSetContainsValue(ctx->appPaths, appPath))
47        return dupFNErr;
48    CFSetAddValue(ctx->appPaths, appPath);
49
50    if ( (err = LSCopyDisplayNameForURL(appURL, &appName)) != noErr)
51        return err;
52
53    // if we encounter multiple applications with the same display name, add locations to the menu item titles to disambiguate them
54    CFURLRef sameAppURL;
55    Boolean shouldAppendLocation;
56    if ( (shouldAppendLocation = CFDictionaryGetValueIfPresent(ctx->appURLs, appName, (const void **)&sameAppURL)) && (CFTypeRef)sameAppURL != kCFNull) {
57        // this app is the second encountered with the same name; go back and fix the menu item title of the first app
58        CFStringRef sameAppItemTitle = CFDictionaryGetValue(ctx->appItemTitles, sameAppURL);
59        CFStringRef appItemTitleWithVersion = ICCF_NameWithLocation(sameAppItemTitle, sameAppURL);
60        CFDictionarySetValue(ctx->appItemTitles, sameAppURL, appItemTitleWithVersion);
61        SAFE_RELEASE(appItemTitleWithVersion);
62        CFDictionarySetValue(ctx->appURLs, appName, kCFNull);
63    }
64
65    CFRetain(appName);
66    appItemTitle = appName;
67
68    if ( (appBundle = CFBundleCreate(NULL, appURL)) != NULL) {
69        // prefer a short version string, e.g. "1.0 Beta" instead of "51" for Safari
70        CFStringRef appVersion = CFBundleGetValueForInfoDictionaryKey(appBundle, CFSTR("CFBundleShortVersionString"));
71        if (appVersion == NULL)
72            appVersion = CFBundleGetValueForInfoDictionaryKey(appBundle, kCFBundleVersionKey);
73        if (appVersion != NULL) {
74            appItemTitle = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)"), appName, appVersion);
75            CFRelease(appName);
76        }
77        CFRelease(appBundle);
78    }
79
80    if (shouldAppendLocation) {
81        CFStringRef appItemTitleWithVersion = ICCF_NameWithLocation(appItemTitle, appURL);
82        CFRelease(appItemTitle);
83        appItemTitle = appItemTitleWithVersion;
84    } else {
85        CFDictionarySetValue(ctx->appURLs, appName, appURL);
86    }
87
88    if (ctx->defaultAppURL != NULL && CFEqual(appURL, ctx->defaultAppURL)) {
89        CFStringRef defaultFormat = ICCF_CopyLocalizedString(CFSTR("DefaultApp%@"));
90        CFStringRef appItemTitleWithDefault = CFStringCreateWithFormat(NULL, NULL, defaultFormat, appItemTitle);
91        CFRelease(appItemTitle);
92        appItemTitle = appItemTitleWithDefault;
93        ctx->defaultAppURL = NULL; // mark as added
94    }
95
96    CFDictionarySetValue(ctx->appItemTitles, appURL, appItemTitle);
97    CFRelease(appItemTitle);
98
99    return noErr;
100}
101
102static OSStatus ICCF_AddTitledAppToMenu(icAppMenuContext *ctx, CFURLRef appURL, MenuCommand menuCommand) {
103    CFStringRef appItemTitle = NULL;
104    IconRef appIcon = NULL;
105    FSRef appFSR;
106    SInt16 label;
107    MenuItemIndex menuItemIndex;
108    OSStatus err = noErr;
109
110    appItemTitle = CFDictionaryGetValue(ctx->appItemTitles, appURL);
111    if (appItemTitle == NULL) return fnfErr;
112   
113    err = AppendMenuItemTextWithCFString(ctx->menu, appItemTitle, 0, 0, &menuItemIndex);
114    if (err != noErr) return err;
115
116    if (!CFURLGetFSRef(appURL, &appFSR)) return paramErr;
117    err = GetIconRefFromFileInfo(&appFSR, 0, NULL, kFSCatInfoNone, NULL, kIconServicesNormalUsageFlag, &appIcon, &label);
118    if (err != noErr) return err;
119
120    SetMenuItemIconHandle(ctx->menu, menuItemIndex, kMenuIconRefType, (Handle)appIcon);
121    SetMenuItemCommandID(ctx->menu, menuItemIndex, menuCommand);
122    SetMenuItemRefCon(ctx->menu, menuItemIndex, (UInt32)appURL);
123    ReleaseIconRef(appIcon);
124
125    return err;
126}
127
128static OSStatus ICCF_AddAppToMenu(icAppMenuContext *ctx, CFURLRef appURL, MenuCommand menuCommand) {
129    OSStatus err = ICCF_AddAppItemTitle(ctx, appURL);
130    switch (err) {
131        case noErr: break;
132        case dupFNErr: return noErr;
133        default: return err;
134    }
135    return ICCF_AddTitledAppToMenu(ctx, appURL, menuCommand);
136}   
137
138CFComparisonResult ICCF_CompareURLsByItemTitle(const void *url1, const void *url2, void *appItemTitles) {
139    CFStringRef appItemTitle1 = CFDictionaryGetValue((CFDictionaryRef)appItemTitles, (CFURLRef)url1);
140    CFStringRef appItemTitle2 = CFDictionaryGetValue((CFDictionaryRef)appItemTitles, (CFURLRef)url2);
141    return CFStringCompareWithOptions(appItemTitle1, appItemTitle2,
142                                      CFRangeMake(0, CFStringGetLength(appItemTitle1)),
143                                      kCFCompareCaseInsensitive | kCFCompareNumerically);
144}
145
146enum {
147    kICURLActionOpenWith = 'OpnW',
148    kICURLActionAddBookmark = 'AddB'
149};
150
151OSStatus ICCF_DoURLActionMenu(ICInstance inst, CFURLRef url, LSLaunchFlags launchFlags) {
152    CFArrayRef appURLsUnsorted = NULL; // matching app URLs
153    CFMutableArrayRef appURLs = NULL; // matching app URLs sorted by item title
154    CFArrayRef urlArray = NULL; // single-URL array
155    icAppMenuContext ctx = {NULL, NULL, NULL, NULL};
156    OSStatus err;
157   
158    appURLsUnsorted = LSCopyApplicationURLsForURL(url, kLSRolesAll);
159
160    CFIndex appCount = 0;
161    if (appURLsUnsorted == NULL || (appCount = CFArrayGetCount(appURLsUnsorted)) == 0)
162        THROW_ERR(kLSApplicationNotFoundErr);
163
164    if ( (appURLs = CFArrayCreateMutableCopy(NULL, appCount, appURLsUnsorted)) == NULL)
165        THROW_ERR(memFullErr);
166
167    if ( (ctx.appPaths = CFSetCreateMutable(NULL, appCount, &kCFCopyStringSetCallBacks)) == NULL)
168        THROW_ERR(memFullErr);
169
170    if ( (ctx.appItemTitles = CFDictionaryCreateMutable(NULL, appCount, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) == NULL)
171        THROW_ERR(memFullErr);
172
173    if ( (ctx.appURLs = CFDictionaryCreateMutable(NULL, appCount, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) == NULL)
174        THROW_ERR(memFullErr);
175
176    LSGetApplicationForURL(url, kLSRolesAll, NULL, &ctx.defaultAppURL);
177
178    CFIndex appIndex;
179    CFURLRef appURL;
180    for (appIndex = 0 ; appIndex < appCount ; appIndex++) {
181        appURL = CFArrayGetValueAtIndex(appURLs, appIndex);
182        err = ICCF_AddAppItemTitle(&ctx, appURL);
183        switch (err) {
184            case noErr: break;
185            case dupFNErr:
186                CFArrayRemoveValueAtIndex(appURLs, appIndex);
187                appIndex--;
188                appCount--;
189                break;
190            default:
191                THROW_ERR(err);
192        } 
193    }
194
195    CFArraySortValues(appURLs, CFRangeMake(0, appCount), ICCF_CompareURLsByItemTitle, ctx.appItemTitles);
196   
197    if ( (err = CreateNewMenu(0, kMenuAttrExcludesMarkColumn, &ctx.menu)) != noErr)
198        THROW_ERR(err);
199   
200    MenuItemIndex menuItemIndex;
201    if ( (err = AppendMenuItemTextWithCFString(ctx.menu, ICCF_CopyLocalizedString(CFSTR("Open Location With")), kMenuItemAttrDisabled, 0, &menuItemIndex)) != noErr)
202        THROW_ERR(err);
203
204    if ( (urlArray = CFArrayCreate(NULL, (const void **)&url, 1, &kCFTypeArrayCallBacks)) == NULL)
205        THROW_ERR(memFullErr);
206
207    for (appIndex = 0 ; appIndex < appCount ; appIndex++) {
208        appURL = CFArrayGetValueAtIndex(appURLs, appIndex);
209
210        if ( (err = ICCF_AddTitledAppToMenu(&ctx, appURL, kICURLActionOpenWith)) != noErr)
211            THROW_ERR(err);
212    }
213    // sometimes the default protocol handler won't be on the list because it doesnÕt claim to handle that protocol; add it anyway
214    if (ctx.defaultAppURL != NULL) {
215        if ( (err = ICCF_AddAppToMenu(&ctx, ctx.defaultAppURL, kICURLActionOpenWith)) != noErr)
216            THROW_ERR(err);
217    }
218
219    appURL = ICCF_GetBookmarkHelperURL(inst);
220    if (appURL != NULL) {
221        if ( (err = AppendMenuItemTextWithCFString(ctx.menu, CFSTR(""), kMenuItemAttrSeparator, 0, NULL)) != noErr)
222            THROW_ERR(err);
223        if ( (err = AppendMenuItemTextWithCFString(ctx.menu, ICCF_CopyLocalizedString(CFSTR("Add Bookmark")), kMenuItemAttrDisabled, 0, &menuItemIndex)) != noErr)
224            THROW_ERR(err);
225        // the app won't show up at the bottom if someone cmd-option-clicks on 'bookmark:', but they'd have to be crazy to do that anyway
226        if ( (err = ICCF_AddAppToMenu(&ctx, appURL, kICURLActionAddBookmark)) != noErr)
227            THROW_ERR(err);
228    }
229   
230    InsertMenu(ctx.menu, -1);
231    Point mousePoint;
232    GetGlobalMouse(&mousePoint);
233    long selectedAppIndex = PopUpMenuSelect(ctx.menu, mousePoint.v + 18, mousePoint.h - 30, 0/*popUpItem*/);
234    if (selectedAppIndex == 0) {
235        err = userCanceledErr;
236    } else {
237        CFURLRef appURL = NULL;
238        MenuCommand menuCommand;
239        err = GetMenuItemRefCon(ctx.menu, selectedAppIndex, (void *)&appURL);
240        if (err == noErr) {
241            err = GetMenuItemCommandID(ctx.menu, selectedAppIndex, &menuCommand);
242            if (err == noErr) {
243                if (menuCommand == kICURLActionOpenWith) {
244                    LSLaunchURLSpec lsSpec = {appURL, urlArray, NULL, launchFlags};
245                    err = LSOpenFromURLSpec(&lsSpec, NULL);
246                } else if (menuCommand == kICURLActionAddBookmark) {
247                    err = ICCF_DoBookmarkDialog(inst, CFURLGetString(url));
248                }
249            }
250        }
251    }
252   
253END:
254    if (ctx.menu != NULL) {
255        DeleteMenu(GetMenuID(ctx.menu));
256        DisposeMenu(ctx.menu);
257    }
258    SAFE_RELEASE(urlArray);
259    SAFE_RELEASE(appURLsUnsorted);
260    SAFE_RELEASE(appURLs);
261    SAFE_RELEASE(ctx.appPaths);
262    SAFE_RELEASE(ctx.appItemTitles);
263    SAFE_RELEASE(ctx.appURLs);
264   
265    return err;
266}
267
268OSStatus ICCF_DoURLActionLaunch(ICInstance inst, CFURLRef url, LSLaunchFlags launchFlags) {
269    CFArrayRef urlArray = CFArrayCreate(NULL, (const void **)&url, 1, &kCFTypeArrayCallBacks);
270    LSLaunchURLSpec lsSpec = {NULL, urlArray, NULL, launchFlags, NULL};
271    OSStatus err;
272
273    if (urlArray == NULL)
274        THROW_ERR(memFullErr);
275
276    err = LSOpenFromURLSpec(&lsSpec, NULL);
277
278END:
279    SAFE_RELEASE(urlArray);
280
281    return err;
282}
283
284OSStatus ICCF_DoURLAction(ICInstance inst, ConstStr255Param hint, const char *urlData, long startIndex, long endIndex, iccfURLAction action) {
285    Handle h = NewHandle(0);
286    CFURLRef url = NULL;
287    LSLaunchFlags launchFlags = kLSLaunchDefaults;
288    OSStatus err;
289
290    if (h == NULL) return MemError();
291
292    if ( (err = ICParseURL(inst, hint, urlData + startIndex, endIndex - startIndex + 1,
293                           &startIndex, &endIndex, h)) != noErr) THROW_ERR(err);
294
295    if ( (url = CFURLCreateWithBytes(NULL, (const UInt8 *)*h, GetHandleSize(h), kCFStringEncodingASCII,
296                                     NULL)) == NULL) THROW_ERR(paramErr);
297
298    if (action.launchInBackground) launchFlags |= kLSLaunchDontSwitch;
299
300    if (action.presentMenu)
301        err = ICCF_DoURLActionMenu(inst, url, launchFlags);
302    else
303        err = ICCF_DoURLActionLaunch(inst, url, launchFlags);
304
305END:
306    if (h != NULL) DisposeHandle(h);
307    SAFE_RELEASE(url);
308
309    return err;
310}
Note: See TracBrowser for help on using the repository browser.