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

Last change on this file since 182 was 182, checked in by Nicholas Riley, 15 years ago

English.lproj/APEInfo.rtfd: Partial documentation update for 1.4.2;
fixed many instances of outdated and incorrect information.

ICeCoffEE.m: Removed completed "to do" comments - that's what
OmniOutliner? and Trac are for. Fixed delimiters to make more sense.
Redid ICCF_ParseURL() to make more sense and strip invalid characters
from beginning of URL. Added note about deprecated getCString:.
Fixed ICCF_ServicesMenuItem() to work on Tiger; moved menu population
logic (where services menu delegate used) to new
ICCF_SetServicesMenu() in ICeCoffEESetServicesMenu.[hm]. Remove key
equivalents from services in ICCF_ConsolidateServicesMenu(). First
pass at a workaround for discontiguous selection: only trigger if
there is no selection. This will be fixed to use a timer.

ICeCoffEEServicePrefController: Fixed service population to work on
Tiger, though keyboard equivalents are not provided; will need to
switch to parsing output of CFServiceControllerCopyServicesEntries()
for that one.

ICeCoffEEWebKit.m: Removed Safari 1.0-1.2 support. Fixed incorrect
comment about -selectionRect? only being in Safari
1.1-1.2.

ICeCoffEESetServicesMenu.[hm]: Handle getting a usable services menu
for Panther and Tiger.

File size: 11.9 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 <objc/objc.h>
15#import <ApplicationEnhancer/ApplicationEnhancer.h>
16
17static NSNumber *ICCF_SERVICE_SHOWN, *ICCF_SERVICE_HIDDEN, *ICCF_SERVICE_MIXED;
18static NSDictionary *ICCF_SERVICE_OPTION_HIDDEN;
19
20static float ICCF_TableViewCellHeight(NSTableView *tableView) {
21    return ([tableView rowHeight] + [tableView intercellSpacing].height);
22}
23
24static inline NSCellStateValue ICCF_ServiceItemState(id <NSMenuItem> item) {
25    id state = [item representedObject];
26    return (state == nil) ? NSOnState : [state intValue];
27}
28
29static void ICCF_PropagateServiceStateChange(NSMenu *menu, id state) {
30    NSEnumerator *e = [[menu itemArray] objectEnumerator];
31    NSMenuItem *item;
32    NSMenu *submenu;
33
34    while ( (item = [e nextObject]) != nil) {
35        submenu = [item submenu];
36        if (submenu != nil) {
37            ICCF_PropagateServiceStateChange(submenu, state);
38            [item setRepresentedObject: (state == nil) ? ICCF_SERVICE_SHOWN : state];
39        }
40        [item setRepresentedObject: state];
41    }
42}
43
44static NSCellStateValue ICCF_PropagateServiceState(id <NSMenuItem> item, NSMenuItem *changedItem) {
45    NSMenu *submenu = [item submenu];
46    if (submenu == nil) return ICCF_ServiceItemState(item);
47
48    if (item == changedItem) ICCF_PropagateServiceStateChange(submenu, [item representedObject]);
49
50    BOOL areOn = NO, areOff = NO;
51    NSEnumerator *e = [[submenu itemArray] objectEnumerator];
52    NSMenuItem *subItem;
53    while ( (subItem = [e nextObject]) != nil) {
54        switch (ICCF_PropagateServiceState(subItem, changedItem)) {
55            case NSOnState: if (!areOff) { areOn = YES; continue; }
56                break;
57            case NSOffState: if (!areOn) { areOff = YES; continue; }
58                break;
59            case NSMixedState:
60                break;
61        }
62        [item setRepresentedObject: ICCF_SERVICE_MIXED];
63        return NSMixedState;
64    }
65    if (areOn) {
66        [item setRepresentedObject: ICCF_SERVICE_SHOWN];
67        return NSOnState;
68    } else {
69        [item setRepresentedObject: ICCF_SERVICE_HIDDEN];
70        return NSOffState;
71    }
72}
73
74static NSMutableDictionary *ICCF_RetainedServiceOptionsDictionary(NSMenu *menu) {
75    NSEnumerator *e = [[menu itemArray] objectEnumerator];
76    NSMenuItem *item;
77    NSMenu *submenu;
78    NSMutableDictionary *dict = nil, *subDict = nil, *submenuDict = nil;
79
80    while ( (item = [e nextObject]) != nil) {
81        submenu = [item submenu];
82        if (ICCF_ServiceItemState(item) == NSOffState) {
83            subDict = [ICCF_SERVICE_OPTION_HIDDEN retain];
84        } else if (submenu != nil) {
85            submenuDict = ICCF_RetainedServiceOptionsDictionary(submenu);
86            if (submenuDict == nil)
87                continue;
88            subDict = [[NSDictionary alloc] initWithObjectsAndKeys: submenuDict, kICServiceSubmenu, nil];
89            [submenuDict release];
90        } else continue;
91        if (dict == nil) {
92            dict = [[NSMutableDictionary alloc] init];
93        }
94        [dict setObject: subDict forKey: [item title]];
95        [subDict release];
96    }
97    return dict;
98}
99
100static void ICCF_RestoreServiceOptionsDictionary(NSMenu *menu, NSDictionary *dict) {
101    NSEnumerator *e = [dict keyEnumerator];
102    NSString *itemTitle;
103    NSDictionary *subDict, *submenuDict;
104    id <NSMenuItem> item;
105    NSMenu *submenu;
106
107    // XXX handle exceptions
108    while ( (itemTitle = [e nextObject]) != nil) {
109        item = [menu itemWithTitle: itemTitle];
110        if (item == nil) continue;
111        subDict = [dict objectForKey: itemTitle];
112        if ([[subDict objectForKey: (NSString *)kICServiceHidden] boolValue]) {
113            [item setRepresentedObject: ICCF_SERVICE_HIDDEN];
114        }
115        if ( (submenu = [item submenu]) != nil) {
116            submenuDict = [subDict objectForKey: (NSString *)kICServiceSubmenu];
117            if ([submenuDict count] == 0)
118                ICCF_PropagateServiceStateChange(submenu, ICCF_SERVICE_HIDDEN);
119            else
120                ICCF_RestoreServiceOptionsDictionary(submenu, submenuDict);
121        }
122    }
123}
124
125@implementation ICeCoffEEServicePrefController
126
127#pragma mark class initialization
128
129+ (void)initialize;
130{
131    ICCF_SERVICE_SHOWN = [[NSNumber alloc] initWithInt: NSOnState];
132    ICCF_SERVICE_HIDDEN = [[NSNumber alloc] initWithInt: NSOffState];
133    ICCF_SERVICE_MIXED = [[NSNumber alloc] initWithInt: NSMixedState];
134    ICCF_SERVICE_OPTION_HIDDEN = [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithBool: YES], kICServiceHidden, nil];
135}
136
137#pragma mark initialize-release
138
139- (id)initWithParentWindow:(NSWindow *)parent;
140{
141    if ( (self = [self initWithWindowNibName: @"Select services"])) {
142        NSWindow *window = [self window]; // connect outlets
143        NSButtonCell *checkBoxCell = [[ICeCoffEENonHighlightingButtonCell alloc] init];
144        [checkBoxCell setButtonType: NSSwitchButton];
145        [checkBoxCell setImagePosition: NSImageOnly];
146        [checkBoxCell setAllowsMixedState: YES];
147        [[serviceOutline tableColumnWithIdentifier: @"show"] setDataCell: checkBoxCell];
148        [checkBoxCell release];
149
150        NSTextFieldCell *textFieldCell = [[serviceOutline tableColumnWithIdentifier: @"service"] dataCell];
151        ((struct objc_object *)textFieldCell)->isa = [ICeCoffEENonHighlightingTextFieldCell class];
152
153        textFieldCell = [[serviceOutline tableColumnWithIdentifier: @"key"] dataCell];
154        ((struct objc_object *)textFieldCell)->isa = [ICeCoffEENonHighlightingTextFieldCell class];
155
156        [window setResizeIncrements: NSMakeSize(1, ICCF_TableViewCellHeight(serviceOutline))];
157        if (parent != nil) {
158            [NSApp beginSheet: window modalForWindow: parent modalDelegate: self didEndSelector: nil contextInfo: nil];
159        } else {
160            [window center];
161            [window makeKeyAndOrderFront: nil];
162        }
163    }
164    return self;
165}
166
167- (void)dealloc;
168{
169    [servicesMenu release];
170    [super dealloc];
171}
172
173#pragma mark actions
174
175- (IBAction)showAll:(NSButton *)sender;
176{
177    ICCF_PropagateServiceStateChange(servicesMenu, nil);
178    [serviceOutline reloadData];
179}
180
181- (IBAction)hideAll:(NSButton *)sender;
182{
183    ICCF_PropagateServiceStateChange(servicesMenu, ICCF_SERVICE_HIDDEN);
184    [serviceOutline reloadData];
185}
186
187- (void)closeWithReturnCode:(int)returnCode;
188{
189    if ([[self window] isSheet]) {
190        [NSApp endSheet: [self window] returnCode: NSRunAbortedResponse];
191    }
192    [self close];
193}
194
195- (IBAction)cancel:(NSButton *)sender;
196{
197    [self closeWithReturnCode: NSRunAbortedResponse];
198}
199
200- (IBAction)saveChanges:(NSButton *)sender;
201{
202    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
203    NSMutableDictionary *icDefaults = [[defaults persistentDomainForName: (NSString *)kICBundleIdentifier] mutableCopy];
204    NSDictionary *serviceOptions = ICCF_RetainedServiceOptionsDictionary(servicesMenu);
205    if (serviceOptions != nil) {
206        [icDefaults setObject: serviceOptions forKey: (NSString *)kICServiceOptions];
207        [serviceOptions release];
208    } else {
209        [icDefaults removeObjectForKey: (NSString *)kICServiceOptions];
210    }
211    [defaults setPersistentDomain: icDefaults forName: (NSString *)kICBundleIdentifier];
212    [defaults synchronize];
213    APEMessageBroadcast(kICBundleIdentifier, kICPreferencesChanged, NULL);
214    [icDefaults release];
215    [self closeWithReturnCode: NSRunStoppedResponse];
216}
217
218@end
219
220@implementation ICeCoffEEServicePrefController (NSOutlineViewDataSource)
221
222- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item;
223{
224    if (servicesMenu == nil) {
225        servicesMenu = [[NSMenu alloc] initWithTitle: @""];
226        ICCF_SetServicesMenu(servicesMenu);
227        [servicesMenu update]; // XXX necessary on 10.3? or anywhere?
228        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
229        NSDictionary *icDefaults = [defaults persistentDomainForName: (NSString *)kICBundleIdentifier];
230        NSDictionary *serviceOptions = [icDefaults objectForKey: (NSString *)kICServiceOptions];
231        ICCF_RestoreServiceOptionsDictionary(servicesMenu, serviceOptions);
232    }
233    return [(item == nil ? servicesMenu : [item submenu]) numberOfItems];
234}
235
236- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item;
237{
238    return (item == nil ? YES : [item submenu] != nil);
239}
240
241- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item;
242{
243    return [(item == nil ? servicesMenu : [item submenu]) itemAtIndex: index];
244}
245
246NSAttributedString *ICCF_KeyEquivalentAttributedStringWithModifierFlags(NSString *self, unsigned int modifierFlags);
247
248- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item;
249{
250    if ([[tableColumn identifier] isEqualToString: @"service"]) {
251        return [item title];
252    } else if ([[tableColumn identifier] isEqualToString: @"show"]) {
253        id state = [item representedObject];
254        if ([item submenu] != nil && state == nil) {
255            return [NSNumber numberWithInt: ICCF_PropagateServiceState(item, nil)];
256        }
257        return (state == nil) ? ICCF_SERVICE_SHOWN : state;
258    } else if ([[tableColumn identifier] isEqualToString: @"key"]) {
259        [item setKeyEquivalent: @"#"];
260        NSString *equivalent = [item keyEquivalent];
261        if (equivalent == nil || [equivalent length] != 1) return nil;
262        // 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.
263        return ICCF_KeyEquivalentAttributedStringWithModifierFlags(equivalent, [item keyEquivalentModifierMask] | ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember: [equivalent characterAtIndex: 0]] ? NSShiftKeyMask : 0));
264    }
265    return nil;
266}
267
268- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item;
269{
270    if ([[tableColumn identifier] isEqualToString: @"show"]) {
271        [item setRepresentedObject: [object boolValue] ? nil : ICCF_SERVICE_HIDDEN];
272       
273        NSMenu *submenu = [item menu];
274        if (submenu == servicesMenu) {
275            ICCF_PropagateServiceState(item, item);
276        } else {
277            NSMenu *supermenu;
278            while ( (supermenu = [submenu supermenu]) != servicesMenu) {
279                submenu = supermenu;
280            }
281            ICCF_PropagateServiceState([supermenu itemAtIndex: [supermenu indexOfItemWithSubmenu: submenu]], item);
282        }
283        [outlineView reloadData];
284    }
285}
286
287@end
288
289@implementation ICeCoffEEServicePrefController (NSWindowDelegate)
290
291- (NSRect)windowWillUseStandardFrame:(NSWindow *)sender defaultFrame:(NSRect)defaultFrame;
292{
293    NSWindow *window = [serviceOutline window];
294    NSRect frame = [window frame];
295    NSScrollView *scrollView = [serviceOutline enclosingScrollView];
296    float displayedHeight = [[scrollView contentView] bounds].size.height;
297    float heightChange = [[scrollView documentView] bounds].size.height - displayedHeight;
298    float heightExcess;
299
300    if (heightChange >= 0 && heightChange <= 1) {
301        // either the window is already optimal size, or it's too big
302        float rowHeight = ICCF_TableViewCellHeight(serviceOutline);
303        heightChange = (rowHeight * [serviceOutline numberOfRows]) - displayedHeight;
304    }
305
306    frame.size.height += heightChange;
307
308    if ( (heightExcess = [window minSize].height - frame.size.height) > 1 ||
309         (heightExcess = [window maxSize].height - frame.size.height) < 1) {
310        heightChange += heightExcess;
311        frame.size.height += heightExcess;
312    }
313
314    frame.origin.y -= heightChange;
315
316    return frame;
317}
318
319@end
320
321@implementation ICeCoffEEServicePrefController (NSWindowNotifications)
322
323- (void)windowWillClose:(NSNotification *)notification;
324{
325    [self autorelease];
326}
327
328@end
Note: See TracBrowser for help on using the repository browser.