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

Last change on this file since 142 was 142, checked in by Nicholas Riley, 17 years ago

ICeCoffEE 1.4a1

File size: 11.8 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(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(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    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        NSString *equivalent = [item keyEquivalent];
259        if (equivalent == nil || [equivalent length] != 1) return nil;
260        // 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.
261        return ICCF_KeyEquivalentAttributedStringWithModifierFlags(equivalent, [item keyEquivalentModifierMask] | ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember: [equivalent characterAtIndex: 0]] ? NSShiftKeyMask : 0));
262    }
263    return nil;
264}
265
266- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item;
267{
268    if ([[tableColumn identifier] isEqualToString: @"show"]) {
269        [item setRepresentedObject: [object boolValue] ? nil : ICCF_SERVICE_HIDDEN];
270       
271        NSMenu *submenu = [item menu];
272        if (submenu == servicesMenu) {
273            ICCF_PropagateServiceState(item, item);
274        } else {
275            NSMenu *supermenu;
276            while ( (supermenu = [submenu supermenu]) != servicesMenu) {
277                submenu = supermenu;
278            }
279            ICCF_PropagateServiceState([supermenu itemAtIndex: [supermenu indexOfItemWithSubmenu: submenu]], item);
280        }
281        [outlineView reloadData];
282    }
283}
284
285@end
286
287@implementation ICeCoffEEServicePrefController (NSWindowDelegate)
288
289- (NSRect)windowWillUseStandardFrame:(NSWindow *)sender defaultFrame:(NSRect)defaultFrame;
290{
291    NSWindow *window = [serviceOutline window];
292    NSRect frame = [window frame];
293    NSScrollView *scrollView = [serviceOutline enclosingScrollView];
294    float displayedHeight = [[scrollView contentView] bounds].size.height;
295    float heightChange = [[scrollView documentView] bounds].size.height - displayedHeight;
296    float heightExcess;
297
298    if (heightChange >= 0 && heightChange <= 1) {
299        // either the window is already optimal size, or it's too big
300        float rowHeight = ICCF_TableViewCellHeight(serviceOutline);
301        heightChange = (rowHeight * [serviceOutline numberOfRows]) - displayedHeight;
302    }
303
304    frame.size.height += heightChange;
305
306    if ( (heightExcess = [window minSize].height - frame.size.height) > 1 ||
307         (heightExcess = [window maxSize].height - frame.size.height) < 1) {
308        heightChange += heightExcess;
309        frame.size.height += heightExcess;
310    }
311
312    frame.origin.y -= heightChange;
313
314    return frame;
315}
316
317@end
318
319@implementation ICeCoffEEServicePrefController (NSWindowNotifications)
320
321- (void)windowWillClose:(NSNotification *)notification;
322{
323    [self autorelease];
324}
325
326@end
Note: See TracBrowser for help on using the repository browser.