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

Last change on this file since 319 was 319, checked in by Nicholas Riley, 13 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.