source: trunk/ICeCoffEE/ICeCoffEE/ICeCoffEEServices.m @ 320

Last change on this file since 320 was 320, checked in by Nicholas Riley, 13 years ago

ICeCoffEE.m: I was wrong in [319] about the selection extension stuff
being dumb; explain why in code, even though it's not fixed.
Implement some of the same parens support I did in Terminal. Update
for more robust service info dictionary format.

ICeCoffEEServicePrefController.m: Update for more robust service info
dictionary format.

ICeCoffEEServices.[hm]: Renamed from ICeCoffEESetServicesMenu.[hm],
since we do more now. Service info dictionary-creating code now makes
more sense and no longer has naming collision issues.

ICeCoffEEWebKit.m: Some preliminary Safari 3 compatibility stuff, not
quite working yet. An outstanding question - is it better to rely on
"public" WebCore? API or private WebKit? API? So far it seems the
latter is more stable.

English.lproj/APEInfo.rtfd: The Bored Zo has a name: use it. Remove
now-erroneous reference to SimpleText? since TextEdit? support is gone.

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