[182] | 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"
|
---|
[319] | 10 | #import "ICeCoffEEShared.h"
|
---|
[182] | 11 |
|
---|
[319] | 12 | // XXX rename me to something like "ICeCoffEEServicesMenu.m"
|
---|
[182] | 13 |
|
---|
[319] | 14 | // an approximate clone of HIToolbox's CreateServicesLocalizedDictKey
|
---|
| 15 | static 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 |
|
---|
| 55 | NSArray *CFServiceControllerCopyServicesEntries(void);
|
---|
| 56 |
|
---|
| 57 | NSDictionary *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 |
|
---|
[182] | 123 | void 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 | }
|
---|
[319] | 144 |
|
---|
| 145 | IconRef 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 | }
|
---|