source: trunk/ICeCoffEE/ICeCoffEE/ICeCoffEESetServicesMenu.m@ 319

Last change on this file since 319 was 319, checked in by Nicholas Riley, 17 years ago

VERSION: Starting with 1.5d1.

ICeCoffEEKeyEquivalents.m: Support "collision font" for displaying key
equivalent conflicts.

ICeCoffEE.m: Increase debug ICCF_MAX_URL_LEN to 120 for testing. Set
icons in ICCF_ConsolidateServicesMenu (needs better caching).

ICeCoffEEServicePrefController.m: Display icons, proper key
equivalents (instead of #, what was I thinking?!) and conflicts. Fix
a dumb bug in ICCF_PropagateServiceStateChange. Ellipsize long menu
items rather than chopping them off. Fix key equivalent column
getting moved when expanding disclosure triangles.

ICeCoffEELabeledIconCell.[hm]: An IconRef-displaying text cell.

Info-APE Module.plist: Update version to 1.5d1.

ICeCoffEE.xcodeproj: Added files, no significant changes.

English.lproj/InfoPlist.strings: Update version to 1.5d1.

English.lproj/APEInfo.rtfd/TXT.rtf: Some overdue documentation
updates.

ICeCoffEEShared.[hm]: Enable debugging; we're now using
kICServiceShortcut (though not yet for customizable shortcuts) so
define its data type.

ICeCoffEETerminal.m: Remove some useless code to "extend to beginning
of string" which seems to have been stolen from the NSTextView
implementation and not well understood. Handle common uses of
parentheses in URLs; still need to do this for NSTextView.

ICeCoffEESetServicesMenu.[hm]: Needs renaming; now with icon
extraction functionality and semi-working code to create a service
info dictionary.

Info-APEManagerPrefPane.plist: Update version to 1.5d1.

File size: 5.8 KB
Line 
1//
2// ICeCoffEESetServicesMenu.m
3// ICeCoffEE APE
4//
5// Created by Nicholas Riley on 5/10/05.
6// Copyright 2005 Nicholas Riley. All rights reserved.
7//
8
9#import "ICeCoffEESetServicesMenu.h"
10#import "ICeCoffEEShared.h"
11
12// XXX rename me to something like "ICeCoffEEServicesMenu.m"
13
14// an approximate clone of HIToolbox's CreateServicesLocalizedDictKey
15static CFStringRef preferredLocalization(CFDictionaryRef localizations) {
16 if (localizations == NULL)
17 return NULL;
18
19 CFIndex localizationCount = CFDictionaryGetCount(localizations);
20 if (localizationCount == 0)
21 return NULL;
22
23 const void **locales = malloc(localizationCount * sizeof(void *));
24 if (locales == NULL)
25 return NULL;
26
27 CFDictionaryGetKeysAndValues(localizations, locales, NULL);
28 CFArrayRef availableLocales = CFArrayCreate(NULL, locales, localizationCount, NULL);
29 if (availableLocales == NULL)
30 return NULL;
31
32 // XXX does this actually work in a localized app?
33 CFArrayRef preferredLocales = CFBundleCopyPreferredLocalizationsFromArray(availableLocales);
34 // NSLog(@"%@ => %@", availableLocales, preferredLocales);
35 CFRelease(availableLocales);
36 CFStringRef preferredLocalization;
37 if (preferredLocales != NULL) {
38 if (CFArrayGetCount(preferredLocales) > 0) {
39 CFStringRef preferredLocale = CFArrayGetValueAtIndex(preferredLocales, 0);
40 preferredLocalization = (CFStringRef)CFDictionaryGetValue(localizations, preferredLocale);
41 }
42 CFRelease(preferredLocales);
43 }
44 if (preferredLocalization == NULL) {
45 preferredLocalization = (CFStringRef)CFDictionaryGetValue(localizations, CFSTR("default"));
46 if (preferredLocalization == NULL)
47 preferredLocalization = (CFStringRef)CFDictionaryGetValue(localizations, (CFStringRef)locales[0]);
48 }
49
50 free(locales);
51 // NSLog(@"%@", preferredLocalization);
52 return preferredLocalization;
53}
54
55NSArray *CFServiceControllerCopyServicesEntries(void);
56
57NSDictionary *ICCF_GetServicesInfo(void) {
58 NSArray *services = CFServiceControllerCopyServicesEntries();
59 // NSLog(@"%@", services);
60
61 NSEnumerator *e = [services objectEnumerator];
62 NSDictionary *serviceEntry;
63 NSMutableDictionary *serviceDict = [[NSMutableDictionary alloc] init];
64 while ( (serviceEntry = (NSDictionary *)[e nextObject]) != nil) {
65 // XXX once tested, redo all this with CF, and no autoreleasing
66 // XXX items named the same as submenus (figure out how Cocoa itself does it, too)
67 // XXX use kICServiceSubmenu (recursion?)
68 NSString *itemPath = (NSString *)preferredLocalization((CFDictionaryRef)[serviceEntry objectForKey: @"NSMenuItem"]);
69 if (itemPath == nil) continue;
70
71 NSString *bundlePath = [serviceEntry objectForKey: @"NSBundlePath"];
72 BOOL bubbledUp = (bundlePath == nil);
73 NSArray *itemComponents = [itemPath componentsSeparatedByString: @"/"];
74 NSEnumerator *ce = [itemComponents objectEnumerator];
75 NSString *itemComponent;
76 NSMutableDictionary *levelDict = serviceDict;
77 NSMutableDictionary *itemDict = nil;
78 while ( (itemComponent = (NSString *)[ce nextObject]) != nil) {
79 // itemDict is nil if just created
80 if (itemDict != nil && !bubbledUp) {
81 NSString *oldBundlePath = [levelDict objectForKey: (NSString *)kICServiceBundlePath];
82 if ([oldBundlePath isEqualToString: bundlePath])
83 bubbledUp = YES;
84 else if (oldBundlePath != nil) {
85 [oldBundlePath retain];
86 [levelDict removeObjectForKey: (NSString *)kICServiceBundlePath];
87 NSEnumerator *be = [levelDict objectEnumerator];
88 while ( (itemDict = (NSMutableDictionary *)[be nextObject]) != nil)
89 [itemDict setObject: oldBundlePath forKey: (NSString *)kICServiceBundlePath];
90 [oldBundlePath release];
91 }
92 }
93 itemDict = [levelDict objectForKey: itemComponent];
94 if (itemDict == nil) {
95 itemDict = [[NSMutableDictionary alloc] init];
96 if (!bubbledUp) {
97 [itemDict setObject: bundlePath forKey: (NSString *)kICServiceBundlePath];
98 bubbledUp = YES;
99 }
100 [levelDict setObject: itemDict forKey: itemComponent];
101 levelDict = itemDict;
102 [itemDict release];
103 itemDict = nil;
104 } else {
105 levelDict = itemDict;
106 }
107 }
108
109 if (!bubbledUp)
110 [levelDict setObject: bundlePath forKey: (NSString *)kICServiceBundlePath];
111
112 NSString *keyEquivalent = (NSString *)preferredLocalization((CFDictionaryRef)[serviceEntry objectForKey: @"NSKeyEquivalent"]);
113 if (keyEquivalent == nil) continue;
114
115 [levelDict setObject: keyEquivalent forKey: (NSString *)kICServiceShortcut];
116 }
117 [services release];
118
119 return [serviceDict autorelease];
120}
121
122
123void ICCF_SetServicesMenu(NSMenu *servicesMenu) {
124 // in 10.3, this populates the menu; in 10.4, it attaches a delegate (NSServiceMaster)
125 [[NSApplication sharedApplication] setServicesMenu: servicesMenu];
126 id delegate;
127 if ( (delegate = [servicesMenu delegate]) != nil) {
128 // populate menu so we have something to filter
129 if ([delegate respondsToSelector: @selector(menuNeedsUpdate:)]) {
130 [delegate menuNeedsUpdate: servicesMenu];
131 }
132 if ([delegate respondsToSelector: @selector(menu:updateItem:atIndex:shouldCancel:)]) {
133 int itemCount = [delegate numberOfItemsInMenu: servicesMenu];
134 for (int i = 0 ; i < itemCount ; i++) {
135 [servicesMenu addItemWithTitle: @"" action: NULL keyEquivalent: @""];
136 }
137 for (int i = 0 ; i < itemCount ; i++) {
138 if (![delegate menu: servicesMenu updateItem: (NSMenuItem *)[servicesMenu itemAtIndex: i] atIndex: i shouldCancel: NO])
139 break;
140 }
141 }
142 }
143}
144
145IconRef ICCF_CopyIconRefForPath(NSString *path) {
146 IconRef icon;
147 FSRef fsr;
148 SInt16 label;
149 OSStatus err = noErr;
150
151 err = FSPathMakeRef((const UInt8 *)[path fileSystemRepresentation], &fsr, NULL);
152 if (err != noErr) return NULL;
153
154 err = GetIconRefFromFileInfo(&fsr, 0, NULL, kFSCatInfoNone, NULL, kIconServicesNormalUsageFlag, &icon, &label);
155 if (err != noErr) return NULL;
156
157 return icon;
158}
Note: See TracBrowser for help on using the repository browser.