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

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

ICeCoffEE 1.4 and preliminary 1.4.1 changes. Sorry, I forgot to
commit version 1.4 when it was released, so the precise source for
that release has been lost.

See the release notes for details of what changed in these versions.
1.4 was a significant feature release; 1.4.1 is a bug fix for 10.3.9,
incorporating up-to-date Unsanity Installer and APE.

package-ICeCoffEE.sh: use xcodebuild instead of pbxbuild.

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