source: trunk/Cocoa/F-Script Anywhere/Source/FSAAppList.m @ 7

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

F-Script Anywhere 1.1.2a1

File size: 10.1 KB
Line 
1//
2//  FSAAppList.m
3//  F-Script Anywhere
4//
5//  Created by Nicholas Riley on Fri Feb 01 2002.
6//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
7//
8
9/*
10
11 F-Script Anywhere is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
15
16 F-Script Anywhere is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with F-Script Anywhere; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24
25*/
26
27#import "FSAAppList.h"
28#import "FSAApp.h"
29#import "libMatch.h"
30#import "DeVercruesseProcessManager.h"
31#import "NJRLabeledImageCell.h"
32#import "NSTableView-NJRExtensions.h"
33
34NSString * const FSATableColumnIdentifier_appNameAndIcon = @"appNameAndIcon";
35NSString * const FSATableColumnIdentifier_checkMark = @"checkMark";
36
37NSString *FSACheckMarkCharacter;
38
39static const char *FSACocoaFrameworks[] = {
40    "/System/Library/Frameworks/AppKit.framework",
41    "/System/Library/Frameworks/Foundation.framework",
42    "/System/Library/Frameworks/Cocoa.framework",
43    NULL
44};
45
46@implementation FSAAppList
47
48+ (void)load;
49{
50    FSACheckMarkCharacter = [[NSString alloc] initWithCharacters: (const unichar *)"\x27\x13" length: 1];
51}
52
53- (void)awakeFromNib;
54{
55    processManager = [DeVercruesseProcessManager defaultManager];
56    cocoaApps = [[NSMutableArray alloc] init];
57    patchedApps = [[NSMutableSet alloc] init];
58    appsByPID = [[NSMutableDictionary alloc] init];
59
60    [[tableView tableColumnWithIdentifier: FSATableColumnIdentifier_appNameAndIcon]
61        setDataCell: [NJRLabeledImageCell cell]];
62    [[tableView window] setResizeIncrements: NSMakeSize(1, [tableView cellHeight])];
63
64    [self update];
65    [[tableView window] makeFirstResponder: tableView];
66}
67
68- (void)dealloc;
69{
70    // don't release processManager, we don't own it
71    [cocoaApps release];
72    [patchedApps release];
73    [appsByPID release];
74    [super dealloc];
75}
76
77- (pid_t)selectedProcessID;
78{
79    int row = [tableView selectedRow];
80    if (row == -1) return -1;
81
82    return [[cocoaApps objectAtIndex: row] pid];
83}
84
85- (void)didPatchProcessID:(pid_t)pid;
86{
87    [patchedApps addObject: [appsByPID objectForKey: [NSNumber numberWithInt: pid]]];
88    [tableView reloadData];
89    [installButton setEnabled: NO];
90}
91
92- (BOOL)appIsCocoa:(DeVercruesseProcess *)app;
93{
94    NSString *bundleExecutableLoc = [app executableLoc];
95    if (bundleExecutableLoc == NULL)
96        return NO;
97    return appContainsLibMatching([bundleExecutableLoc fileSystemRepresentation], FSACocoaFrameworks);
98}
99
100- (void)addApp:(DeVercruesseProcess *)app;
101{
102    if ( ([app isCarbon] || [app isCocoa]) // XXX OS X 10.1.2 (and earlier?) bug, Cocoa reported as Carbon
103         && ![app isBackgroundOnly]
104         && [self appIsCocoa: app]) {
105        [cocoaApps addObject: app];
106    }
107    [appsByPID setObject: app forKey: [NSNumber numberWithInt: [app pid]]];
108}
109
110// XXX should insert/resort on launch too; this is harder because of synchronization issues
111
112- (void)update;
113{
114    NSEnumerator *e;
115    NSArray *allApps;
116    DeVercruesseProcess *app;
117
118    [cocoaApps removeAllObjects];
119    [appsByPID removeAllObjects];
120    // [processManager update]; // [processManager processes] sends update
121
122    allApps = [processManager processes];
123    e = [allApps objectEnumerator];
124
125    while ( (app = [e nextObject]) != nil) {
126        [self addApp: app];
127    }
128
129    [tableView noteNumberOfRowsChanged];
130    [tableView reloadData];
131    // XXX this is broken, it doesn't update properly
132    [self tableView: tableView shouldSelectRow: [tableView selectedRow]];
133}
134
135- (void)applicationLaunchedWithProcessID:(pid_t)pid;
136{
137    NSNumber *pidNum = [NSNumber numberWithInt: pid];
138    DeVercruesseProcess *app = [appsByPID objectForKey: pidNum];
139
140    if (app == nil) {
141        [self update];
142    }
143}
144
145- (void)applicationQuitWithProcessID:(pid_t)pid;
146{
147    NSNumber *pidNum = [NSNumber numberWithInt: pid];
148    DeVercruesseProcess *app = [appsByPID objectForKey: pidNum];
149
150    if (app != nil) {
151        [cocoaApps removeObject: app];
152        [appsByPID removeObjectForKey: pidNum];
153        [patchedApps removeObject: app];
154    }
155
156    [tableView reloadData];
157}
158
159@end
160
161@implementation FSAAppList (NSTableViewDelegate)
162
163- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row;
164{
165    if ([[tableColumn identifier] isEqualToString: FSATableColumnIdentifier_appNameAndIcon]) {
166        DeVercruesseProcess *app = [cocoaApps objectAtIndex: row];
167
168        NSAssert1([cell isKindOfClass: [NJRLabeledImageCell class]], @"Cell is not what we expected, instead %@", cell);
169        [(NJRLabeledImageCell *)cell setImage: [app img]];
170        [(NJRLabeledImageCell *)cell setImageCacheSource: app];
171    }
172}
173
174- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
175{
176    BOOL canInstall = NO;
177
178    if (row != -1) {
179        canInstall = ![patchedApps containsObject: [cocoaApps objectAtIndex: row]];
180    }
181
182    [installButton setEnabled: canInstall];
183   
184    return YES;
185}
186
187@end
188
189@implementation FSAAppList (NSTableDataSource)
190
191- (int)numberOfRowsInTableView:(NSTableView *)aTableView
192{
193    return [cocoaApps count];
194}
195
196- (id)tableView:(NSTableView *)aTableView
197    objectValueForTableColumn:(NSTableColumn *)aTableColumn
198    row:(int)rowIndex
199{
200    NSString *columnIdentifier = [aTableColumn identifier];
201   
202    if ([columnIdentifier isEqualToString: FSATableColumnIdentifier_appNameAndIcon]) {
203        return [(DeVercruesseProcess *)[cocoaApps objectAtIndex: rowIndex] name];
204    } else if ([columnIdentifier isEqualToString: FSATableColumnIdentifier_checkMark]) {
205        return [patchedApps containsObject: [cocoaApps objectAtIndex: rowIndex]] ? FSACheckMarkCharacter : @"";
206    }
207    return nil;
208}
209
210@end
211
212@implementation FSAAppList (NSWindowDelegate)
213
214- (NSRect)windowWillUseStandardFrame:(NSWindow *)sender defaultFrame:(NSRect)defaultFrame;
215{
216    NSWindow *window = [tableView window];
217    NSRect frame = [window frame];
218    NSScrollView *scrollView = [tableView enclosingScrollView];
219    float displayedHeight = [[scrollView contentView] bounds].size.height;
220    float heightChange = [[scrollView documentView] bounds].size.height - displayedHeight;
221    float heightExcess;
222
223    if (heightChange >= 0 && heightChange <= 1) {
224        // either the window is already optimal size, or it's too big
225        float rowHeight = [tableView cellHeight];
226        heightChange = (rowHeight * [tableView numberOfRows]) - displayedHeight;
227    }
228
229    frame.size.height += heightChange;
230
231    if ( (heightExcess = [window minSize].height - frame.size.height) > 1 ||
232         (heightExcess = [window maxSize].height - frame.size.height) < 1) {
233        heightChange += heightExcess;
234        frame.size.height += heightExcess;
235    }
236
237    frame.origin.y -= heightChange;
238
239    return frame;
240}
241
242- (BOOL)windowShouldClose:(id)sender;
243{
244    if ([patchedApps count] != 0) {
245        NSMutableString *message =  [@"F-Script Anywhere is installed in the following applications:\n\n" mutableCopy];
246        NSEnumerator *e = [patchedApps objectEnumerator];
247        DeVercruesseProcess *app;
248        int retval;
249
250        while ( (app = [e nextObject]) != nil) {
251            [message appendFormat: @"    ¥ %@\n", [app name]];
252        }
253
254        [message appendString: @"\nIf F-Script Anywhere quits now, these applications will be forced to quit, and any changes you have made in them will be lost.\n\nPlease quit these applications before quitting F-Script Anywhere, or click Force Quit to continue."];
255
256        retval = NSRunAlertPanel(@"Force applications to quit?", message, @"DonÕt Quit", @"Force Quit", nil);
257        if (retval != NSAlertAlternateReturn) {
258            return NO;
259        }
260
261        [(FSAApp *)NSApp unloadBundles: self];
262    }
263
264    return YES;
265}
266
267@end
268
269@implementation FSAAppList (NSApplicationDelegate)
270
271- (NSMenu *)applicationDockMenu:(NSApplication *)sender;
272{
273    static NSMenu *dockMenu = nil;
274    NSMenuItem *menuItem;
275    DeVercruesseProcess *frontApp = [processManager frontProcess];
276    NSString *appName = [frontApp name];
277    NSString *status = nil;
278    // XXX workaround for broken dock menu sender
279    NSMethodSignature *sig = [NSApp methodSignatureForSelector: @selector(installBundleInFrontmostApp:)];
280    NSInvocation *inv;
281
282    if (dockMenu != nil) {
283        // XXX release invocation
284        [[[dockMenu itemAtIndex: 0] target] release];
285        [dockMenu removeItemAtIndex: 0];
286    } else {
287        dockMenu = [[NSMenu alloc] init];
288    }
289
290    NSAssert(frontApp != nil && appName != nil, @"CanÕt obtain information on the frontmost application");
291
292    if ([patchedApps containsObject: frontApp]) {
293        status = [NSString stringWithFormat: @"Installed in Ò%@Ó", appName];
294    } else if (![cocoaApps containsObject: frontApp]) {
295        status = [NSString stringWithFormat: @"CanÕt install because Ò%@Ó is not a Cocoa application", appName];
296    }
297
298    if (status == nil) {
299        menuItem = [dockMenu addItemWithTitle: [NSString stringWithFormat: @"Install in Ò%@Ó", appName]
300                                       action: @selector(invoke)
301                                keyEquivalent: @""];
302        inv = [NSInvocation invocationWithMethodSignature: sig];
303        [inv setSelector: @selector(installBundleInFrontmostApp:)];
304        [inv setTarget: NSApp];
305        [inv setArgument: &menuItem atIndex: 2];
306        [menuItem setTag: [frontApp pid]];
307        [menuItem setTarget: [inv retain]];
308    } else {
309        menuItem = [dockMenu addItemWithTitle: status action: nil keyEquivalent: @""];
310        [menuItem setEnabled: NO];
311    }
312
313    return dockMenu;
314}
315
316- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender;
317{
318    return YES;
319}
320
321- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
322{
323    return ([self windowShouldClose: self] ? NSTerminateNow : NSTerminateCancel);
324}
325
326@end
Note: See TracBrowser for help on using the repository browser.