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

Last change on this file since 320 was 320, checked in by Nicholas Riley, 15 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: 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 "ICeCoffEEServices.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 objectForKey: (NSString *)kICServiceSubmenu]);
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.