source: trunk/ICeCoffEE/ICeCoffEE/ICeCoffEEAction.c @ 152

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

ICeCoffEE 1.3.2b1

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