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

Last change on this file since 462 was 462, checked in by Nicholas Riley, 16 years ago

Copy URL to clipboard as link support.

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