source: trunk/ICeCoffEE/ICeCoffEE/ICeCoffEEServicePrefController.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: 16.5 KB
Line 
1//
2// ICeCoffEEServicePrefController.m
3// ICeCoffEE APE
4//
5// Created by Nicholas Riley on Fri Jun 06 2003.
6// Copyright (c) 2003 Nicholas Riley. All rights reserved.
7//
8
9#import "ICeCoffEEShared.h"
10#import "ICeCoffEESetServicesMenu.h"
11#import "ICeCoffEEServicePrefController.h"
12#import "ICeCoffEENonHighlightingButtonCell.h"
13#import "ICeCoffEENonHighlightingTextFieldCell.h"
14#import "ICeCoffEELabeledIconCell.h"
15#import <objc/objc.h>
16#import <ApplicationEnhancer/ApplicationEnhancer.h>
17
18static NSNumber *ICCF_SERVICE_SHOWN, *ICCF_SERVICE_HIDDEN, *ICCF_SERVICE_MIXED;
19static NSDictionary *ICCF_SERVICE_OPTION_HIDDEN;
20
21static float ICCF_TableViewCellHeight(NSTableView *tableView) {
22 return ([tableView rowHeight] + [tableView intercellSpacing].height);
23}
24
25static NSMutableDictionary *keyEquivalents;
26
27static void ICCF_RemoveSingleKeyEquivalents() {
28 NSMutableArray *singleKeys = [[NSMutableArray alloc] init];
29 NSEnumerator *e = [[keyEquivalents allKeys] objectEnumerator];
30 NSString *keyEquivalent;
31 ICLog(@"before ICCF_RemoveSingleKeyEquivalents: %@", keyEquivalents);
32 while ( (keyEquivalent = [e nextObject]) != nil) {
33 if ([[keyEquivalents objectForKey: keyEquivalent] count] == 1)
34 [singleKeys addObject: keyEquivalent];
35 }
36 [keyEquivalents removeObjectsForKeys: singleKeys];
37 [singleKeys release];
38 ICLog(@"after ICCF_RemoveSingleKeyEquivalents: %@", keyEquivalents);
39}
40
41static inline unsigned ICCF_CountForKeyEquivalent(NSString *keyEquivalent) {
42 if (keyEquivalent == nil) return 0;
43 NSMutableSet *setOrNil = (NSMutableSet *)[keyEquivalents objectForKey: keyEquivalent];
44 return (setOrNil == nil) ? nil : [setOrNil count];
45}
46
47static inline void ICCF_AddKeyEquivalentForItem(NSMenuItem *item) {
48 NSString *keyEquivalent = [item toolTip];
49 if (keyEquivalent == nil) return;
50 NSMutableSet *setOrNil = (NSMutableSet *)[keyEquivalents objectForKey: keyEquivalent];
51 if (setOrNil == nil) return;
52 [setOrNil addObject: item];
53}
54
55static inline void ICCF_RemoveKeyEquivalentForItem(NSMenuItem *item) {
56 NSString *keyEquivalent = [item toolTip];
57 if (keyEquivalent == nil) return;
58 NSMutableSet *setOrNil = (NSMutableSet *)[keyEquivalents objectForKey: keyEquivalent];
59 if (setOrNil == nil) return;
60 [setOrNil removeObject: item];
61}
62
63static inline void ICCF_UpdateKeyEquivalentForItem(NSMenuItem *item, id state) {
64 NSNumber *oldState = [item representedObject];
65 if ((oldState == nil || oldState == ICCF_SERVICE_SHOWN) && state == ICCF_SERVICE_HIDDEN)
66 ICCF_RemoveKeyEquivalentForItem(item);
67 else if (oldState == ICCF_SERVICE_HIDDEN && (state == ICCF_SERVICE_SHOWN || state == nil))
68 ICCF_AddKeyEquivalentForItem(item);
69}
70
71static inline NSCellStateValue ICCF_ServiceItemState(NSMenuItem *item) {
72 id state = [item representedObject];
73 return (state == nil) ? NSOnState : [state intValue];
74}
75
76static void ICCF_PropagateServiceStateChange(NSMenu *menu, id state) {
77 NSEnumerator *e = [[menu itemArray] objectEnumerator];
78 NSMenuItem *item;
79 NSMenu *submenu;
80
81 while ( (item = [e nextObject]) != nil) {
82 submenu = [item submenu];
83 if (submenu != nil)
84 ICCF_PropagateServiceStateChange(submenu, state);
85 else
86 ICCF_UpdateKeyEquivalentForItem(item, state);
87
88 [item setRepresentedObject: state];
89 }
90}
91
92static NSCellStateValue ICCF_PropagateServiceState(NSMenuItem *item, NSMenuItem *changedItem) {
93 NSMenu *submenu = [item submenu];
94 if (submenu == nil) return ICCF_ServiceItemState(item);
95
96 if (item == changedItem) ICCF_PropagateServiceStateChange(submenu, [item representedObject]);
97
98 BOOL areOn = NO, areOff = NO;
99 NSEnumerator *e = [[submenu itemArray] objectEnumerator];
100 NSMenuItem *subItem;
101 while ( (subItem = [e nextObject]) != nil) {
102 switch (ICCF_PropagateServiceState(subItem, changedItem)) {
103 case NSOnState: if (!areOff) { areOn = YES; continue; }
104 break;
105 case NSOffState: if (!areOn) { areOff = YES; continue; }
106 break;
107 case NSMixedState:
108 break;
109 }
110 [item setRepresentedObject: ICCF_SERVICE_MIXED];
111 return NSMixedState;
112 }
113 if (areOn) {
114 [item setRepresentedObject: ICCF_SERVICE_SHOWN];
115 return NSOnState;
116 } else {
117 [item setRepresentedObject: ICCF_SERVICE_HIDDEN];
118 return NSOffState;
119 }
120}
121
122static NSMutableDictionary *ICCF_RetainedServiceOptionsDictionary(NSMenu *menu) {
123 NSEnumerator *e = [[menu itemArray] objectEnumerator];
124 NSMenuItem *item;
125 NSMenu *submenu;
126 NSMutableDictionary *dict = nil, *subDict = nil, *submenuDict = nil;
127
128 while ( (item = [e nextObject]) != nil) {
129 submenu = [item submenu];
130 if (ICCF_ServiceItemState(item) == NSOffState) {
131 subDict = [ICCF_SERVICE_OPTION_HIDDEN retain];
132 } else if (submenu != nil) {
133 submenuDict = ICCF_RetainedServiceOptionsDictionary(submenu);
134 if (submenuDict == nil)
135 continue;
136 subDict = [[NSDictionary alloc] initWithObjectsAndKeys: submenuDict, kICServiceSubmenu, nil];
137 [submenuDict release];
138 } else continue;
139 if (dict == nil) {
140 dict = [[NSMutableDictionary alloc] init];
141 }
142 [dict setObject: subDict forKey: [item title]];
143 [subDict release];
144 }
145 return dict;
146}
147
148static void ICCF_RestoreServiceOptionsDictionary(NSMenu *menu, NSDictionary *dict) {
149 NSEnumerator *e = [dict keyEnumerator];
150 NSString *itemTitle;
151 NSDictionary *subDict, *submenuDict;
152 NSMenuItem *item;
153 NSMenu *submenu;
154
155 // XXX handle exceptions
156 while ( (itemTitle = [e nextObject]) != nil) {
157 item = (NSMenuItem *)[menu itemWithTitle: itemTitle];
158 if (item == nil) continue;
159 subDict = [dict objectForKey: itemTitle];
160 if ([[subDict objectForKey: (NSString *)kICServiceHidden] boolValue]) {
161 [item setRepresentedObject: ICCF_SERVICE_HIDDEN];
162 ICCF_RemoveKeyEquivalentForItem(item);
163 }
164 if ( (submenu = [item submenu]) != nil) {
165 submenuDict = [subDict objectForKey: (NSString *)kICServiceSubmenu];
166 if ([submenuDict count] == 0)
167 ICCF_PropagateServiceStateChange(submenu, ICCF_SERVICE_HIDDEN);
168 else
169 ICCF_RestoreServiceOptionsDictionary(submenu, submenuDict);
170 }
171 }
172}
173
174static void ICCF_AddServiceKeyEquivalentsAndIcons(NSMenu *menu, NSDictionary *serviceInfo) {
175 if (serviceInfo == nil) return;
176 NSEnumerator *enumerator = [[menu itemArray] objectEnumerator];
177 NSMenuItem *menuItem;
178 NSMenu *submenu;
179 NSDictionary *itemInfo = nil;
180 while ( (menuItem = [enumerator nextObject]) != nil) {
181 itemInfo = [serviceInfo objectForKey: [menuItem title]];
182 if (itemInfo == nil) continue;
183
184 if ( (submenu = [menuItem submenu]) != nil) {
185 ICCF_AddServiceKeyEquivalentsAndIcons(submenu, itemInfo);
186 } else {
187 NSString *keyEquivalent = (NSString *)[itemInfo objectForKey: (NSString *)kICServiceShortcut];
188 if (keyEquivalent != nil) {
189 [menuItem setToolTip: keyEquivalent];
190 NSMutableSet *equivalentItems = (NSMutableSet *)[keyEquivalents objectForKey: keyEquivalent];
191 if (equivalentItems == nil) {
192 equivalentItems = [[NSMutableSet alloc] initWithObjects: menuItem, nil];
193 [keyEquivalents setObject: equivalentItems forKey: keyEquivalent];
194 [equivalentItems release];
195 } else {
196 [equivalentItems addObject: menuItem];
197 }
198 }
199 }
200
201 NSString *bundlePath = (NSString *)[itemInfo objectForKey: (NSString *)kICServiceBundlePath];
202 if (bundlePath == NULL) continue;
203 IconRef serviceIcon = ICCF_CopyIconRefForPath(bundlePath);
204 if (serviceIcon == NULL) continue;
205 [menuItem _setIconRef: serviceIcon];
206 ReleaseIconRef(serviceIcon);
207 }
208}
209
210@implementation ICeCoffEEServicePrefController
211
212#pragma mark class initialization
213
214+ (void)initialize;
215{
216 ICCF_SERVICE_SHOWN = [[NSNumber alloc] initWithInt: NSOnState];
217 ICCF_SERVICE_HIDDEN = [[NSNumber alloc] initWithInt: NSOffState];
218 ICCF_SERVICE_MIXED = [[NSNumber alloc] initWithInt: NSMixedState];
219 ICCF_SERVICE_OPTION_HIDDEN = [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithBool: YES], kICServiceHidden, nil];
220}
221
222#pragma mark initialize-release
223
224- (id)initWithParentWindow:(NSWindow *)parent;
225{
226 if ( (self = [self initWithWindowNibName: @"Select services"])) {
227 NSWindow *window = [self window]; // connect outlets
228 [serviceOutline setAutoresizesOutlineColumn: NO];
229
230 NSButtonCell *checkBoxCell = [[ICeCoffEENonHighlightingButtonCell alloc] init];
231 [checkBoxCell setButtonType: NSSwitchButton];
232 [checkBoxCell setImagePosition: NSImageOnly];
233 [checkBoxCell setAllowsMixedState: YES];
234 [[serviceOutline tableColumnWithIdentifier: @"show"] setDataCell: checkBoxCell];
235 [checkBoxCell release];
236
237 NSTextFieldCell *textFieldCell = [[serviceOutline tableColumnWithIdentifier: @"service"] dataCell];
238 [textFieldCell setWraps: YES];
239 [[serviceOutline tableColumnWithIdentifier: @"service"] setDataCell:
240 [ICeCoffEELabeledIconCell copyFromTextFieldCell: textFieldCell]];
241
242 textFieldCell = [[serviceOutline tableColumnWithIdentifier: @"key"] dataCell];
243 ((struct objc_object *)textFieldCell)->isa = [ICeCoffEENonHighlightingTextFieldCell class];
244
245 [window setResizeIncrements: NSMakeSize(1, ICCF_TableViewCellHeight(serviceOutline))];
246 if (parent != nil) {
247 [NSApp beginSheet: window modalForWindow: parent modalDelegate: self didEndSelector: nil contextInfo: nil];
248 } else {
249 [window center];
250 [window makeKeyAndOrderFront: nil];
251 }
252 }
253 return self;
254}
255
256- (void)dealloc;
257{
258 [keyEquivalents release];
259 [servicesMenu release];
260 [super dealloc];
261}
262
263#pragma mark actions
264
265- (IBAction)showAll:(NSButton *)sender;
266{
267 ICCF_PropagateServiceStateChange(servicesMenu, nil);
268 [serviceOutline reloadData];
269}
270
271- (IBAction)hideAll:(NSButton *)sender;
272{
273 ICCF_PropagateServiceStateChange(servicesMenu, ICCF_SERVICE_HIDDEN);
274 [serviceOutline reloadData];
275}
276
277- (void)closeWithReturnCode:(int)returnCode;
278{
279 if ([[self window] isSheet]) {
280 [NSApp endSheet: [self window] returnCode: NSRunAbortedResponse];
281 }
282 [self close];
283}
284
285- (IBAction)cancel:(NSButton *)sender;
286{
287 [self closeWithReturnCode: NSRunAbortedResponse];
288}
289
290- (IBAction)saveChanges:(NSButton *)sender;
291{
292 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
293 NSMutableDictionary *icDefaults = [[defaults persistentDomainForName: (NSString *)kICBundleIdentifier] mutableCopy];
294 NSDictionary *serviceOptions = ICCF_RetainedServiceOptionsDictionary(servicesMenu);
295 if (serviceOptions != nil) {
296 [icDefaults setObject: serviceOptions forKey: (NSString *)kICServiceOptions];
297 [serviceOptions release];
298 } else {
299 [icDefaults removeObjectForKey: (NSString *)kICServiceOptions];
300 }
301 [defaults setPersistentDomain: icDefaults forName: (NSString *)kICBundleIdentifier];
302 [defaults synchronize];
303 APEMessageBroadcast(kICBundleIdentifier, kICPreferencesChanged, NULL);
304 [icDefaults release];
305 [self closeWithReturnCode: NSRunStoppedResponse];
306}
307
308@end
309
310@implementation ICeCoffEEServicePrefController (NSOutlineViewDataSource)
311
312- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item;
313{
314 if (servicesMenu == nil) {
315 keyEquivalents = [[NSMutableDictionary alloc] init];
316 servicesMenu = [[NSMenu alloc] initWithTitle: @""];
317 ICCF_SetServicesMenu(servicesMenu);
318 ICCF_AddServiceKeyEquivalentsAndIcons(servicesMenu, ICCF_GetServicesInfo());
319 ICCF_RemoveSingleKeyEquivalents();
320
321 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
322 NSDictionary *icDefaults = [defaults persistentDomainForName: (NSString *)kICBundleIdentifier];
323 NSDictionary *serviceOptions = [icDefaults objectForKey: (NSString *)kICServiceOptions];
324 ICCF_RestoreServiceOptionsDictionary(servicesMenu, serviceOptions);
325 }
326 return [(item == nil ? servicesMenu : [item submenu]) numberOfItems];
327}
328
329- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item;
330{
331 return (item == nil ? YES : [item submenu] != nil);
332}
333
334- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item;
335{
336 return [(item == nil ? servicesMenu : [item submenu]) itemAtIndex: index];
337}
338
339NSAttributedString *ICCF_KeyEquivalentAttributedStringWithModifierFlags(NSString *self, unsigned int modifierFlags, unsigned count);
340
341- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item;
342{
343 if ([[tableColumn identifier] isEqualToString: @"service"]) {
344 static NSDictionary *attrDict = nil;
345 if (attrDict == nil) { // XXX we leak this, but so does the Apple sample code...
346 NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
347 [style setLineBreakMode: NSLineBreakByTruncatingMiddle];
348 attrDict = [[NSDictionary alloc] initWithObjectsAndKeys: style, NSParagraphStyleAttributeName, nil];
349 [style release];
350 }
351 return [[[NSAttributedString alloc] initWithString: [item title] attributes: attrDict] autorelease];
352 } else if ([[tableColumn identifier] isEqualToString: @"show"]) {
353 id state = [item representedObject];
354 if ([item submenu] != nil && state == nil) {
355 return [NSNumber numberWithInt: ICCF_PropagateServiceState(item, nil)];
356 }
357 return (state == nil) ? ICCF_SERVICE_SHOWN : state;
358 } else if ([[tableColumn identifier] isEqualToString: @"key"]) {
359 NSString *equivalent = [item toolTip];
360 if (equivalent == nil || [equivalent length] != 1) return nil;
361 // XXX Inconsistency between Cocoa and Carbon: always command-shift in Carbon, not in Cocoa. Since we only patch Cocoa for the moment, keep as is.
362 return ICCF_KeyEquivalentAttributedStringWithModifierFlags(equivalent, [item keyEquivalentModifierMask] | ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember: [equivalent characterAtIndex: 0]] ? NSShiftKeyMask : 0), ICCF_CountForKeyEquivalent(equivalent));
363 }
364 return nil;
365}
366
367- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item;
368{
369 if ([[tableColumn identifier] isEqualToString: @"show"]) {
370 NSNumber *newState = [object boolValue] ? nil : ICCF_SERVICE_HIDDEN;
371 ICCF_UpdateKeyEquivalentForItem(item, newState);
372 [item setRepresentedObject: newState];
373
374 NSMenu *submenu = [item menu];
375 if (submenu == servicesMenu) {
376 ICCF_PropagateServiceState(item, item);
377 } else {
378 NSMenu *supermenu;
379 while ( (supermenu = [submenu supermenu]) != servicesMenu) {
380 submenu = supermenu;
381 }
382 ICCF_PropagateServiceState((NSMenuItem *)[supermenu itemAtIndex: [supermenu indexOfItemWithSubmenu: submenu]], item);
383 }
384 [outlineView reloadData];
385 }
386}
387
388@end
389
390@implementation ICeCoffEEServicePrefController (NSOutlineViewDelegate)
391
392- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item;
393{
394 if (![[tableColumn identifier] isEqualToString: @"service"])
395 return;
396 [(ICeCoffEELabeledIconCell *)cell setIconRef: [item _iconRef]];
397}
398
399@end
400
401@implementation ICeCoffEEServicePrefController (NSWindowDelegate)
402
403- (NSRect)windowWillUseStandardFrame:(NSWindow *)sender defaultFrame:(NSRect)defaultFrame;
404{
405 NSWindow *window = [serviceOutline window];
406 NSRect frame = [window frame];
407 NSScrollView *scrollView = [serviceOutline enclosingScrollView];
408 float displayedHeight = [[scrollView contentView] bounds].size.height;
409 float heightChange = [[scrollView documentView] bounds].size.height - displayedHeight;
410 float heightExcess;
411
412 if (heightChange >= 0 && heightChange <= 1) {
413 // either the window is already optimal size, or it's too big
414 float rowHeight = ICCF_TableViewCellHeight(serviceOutline);
415 heightChange = (rowHeight * [serviceOutline numberOfRows]) - displayedHeight;
416 }
417
418 frame.size.height += heightChange;
419
420 if ( (heightExcess = [window minSize].height - frame.size.height) > 1 ||
421 (heightExcess = [window maxSize].height - frame.size.height) < 1) {
422 heightChange += heightExcess;
423 frame.size.height += heightExcess;
424 }
425
426 frame.origin.y -= heightChange;
427
428 return frame;
429}
430
431@end
432
433@implementation ICeCoffEEServicePrefController (NSWindowNotifications)
434
435- (void)windowWillClose:(NSNotification *)notification;
436{
437 [self autorelease];
438}
439
440@end
Note: See TracBrowser for help on using the repository browser.