source: releases/F-Script Anywhere/1.1.2a1/Source/FSAAppList.m@ 662

Last change on this file since 662 was 7, checked in by Nicholas Riley, 22 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.