ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/SheepShaver/src/MacOSX/Launcher/VMListController.mm
Revision: 1.7
Committed: 2009-08-12T02:18:19Z (14 years, 10 months ago) by asvitkine
Branch: MAIN
Changes since 1.6: +85 -2 lines
Log Message:
re-arrange virtual machines by drag and drop
add a contextual menu that also allows to
reveal the selected virtual machine in finder

File Contents

# Content
1 /*
2 * VMListController.mm - SheepShaver VM manager in Cocoa on Mac OS X
3 *
4 * Copyright (C) 2009 Alexei Svitkine
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 #import "VMListController.h"
22 #import "VMSettingsController.h"
23
24 /*
25
26 TODO:
27
28 Verify if VM exists
29 Drag VM from Finder to import
30 Don't show Preferences menu in spawned SheepShaver instances - or make them
31 use the same nib file as this app!
32 When choosing things like rom file and keycode files - have a checkbox to copy
33 selected file into the bundle.
34
35 */
36
37 @interface NSObject (TableViewContextMenu)
38 - (NSMenu *) tableView: (NSTableView *) tableView menuForEvent: (NSEvent *) event;
39 @end
40
41 @implementation NSTableView (ContextMenu)
42 - (NSMenu *) menuForEvent: (NSEvent *) event
43 {
44 if ([[self delegate] respondsToSelector:@selector(tableView:menuForEvent:)])
45 return [[self delegate] tableView:self menuForEvent:event];
46 return nil;
47 }
48 @end
49
50 #define VM_DRAG_TYPE @"sheepvm"
51
52 @implementation VMListController
53
54 + (id) sharedInstance
55 {
56 static VMListController *_sharedInstance = nil;
57 if (!_sharedInstance) {
58 _sharedInstance = [[VMListController allocWithZone:[self zone]] init];
59 }
60 return _sharedInstance;
61 }
62
63 - (id) init
64 {
65 self = [super initWithWindowNibName:@"VMListWindow"];
66
67 NSArray *vms = [[NSUserDefaults standardUserDefaults] stringArrayForKey:@"vm_list"];
68 vmArray = [[NSMutableArray alloc] initWithCapacity:[vms count]];
69 [vmArray addObjectsFromArray:vms];
70
71 tasks = [[NSMutableDictionary alloc] init];
72
73 [[NSNotificationCenter defaultCenter] addObserver:self
74 selector:@selector(onTaskTerminated:)
75 name:NSTaskDidTerminateNotification
76 object:nil];
77
78 return self;
79 }
80
81 - (void) awakeFromNib
82 {
83 [vmList setDataSource: self];
84 [vmList setDelegate: self];
85 [vmList reloadData];
86 [vmList registerForDraggedTypes:[NSArray arrayWithObjects:VM_DRAG_TYPE, nil]];
87 }
88
89 - (void) keyDown: (NSEvent *) event
90 {
91 if ([event type] == NSKeyDown && [[event characters] length] > 0) {
92 unichar key = [[event characters] characterAtIndex:0];
93 if (key == NSDeleteFunctionKey || key == NSDeleteCharacter) {
94 [self deleteVirtualMachine:self];
95 }
96 }
97 }
98
99 - (int) numberOfRowsInTableView: (NSTableView *) table
100 {
101 return [vmArray count];
102 }
103
104 - (id) tableView: (NSTableView *) table objectValueForTableColumn: (NSTableColumn *) c row: (int) r
105 {
106 return [vmArray objectAtIndex: r]; // [[vmArray objectAtIndex: r] lastPathComponent];
107 }
108
109 - (void) tableViewSelectionDidChange: (NSNotification *) notification
110 {
111 if ([vmList selectedRow] >= 0) {
112 [settingsButton setEnabled:YES];
113 [launchButton setEnabled:YES];
114 } else {
115 [settingsButton setEnabled:NO];
116 [launchButton setEnabled:NO];
117 }
118 }
119
120 - (BOOL) tableView: (NSTableView *) table writeRowsWithIndexes: (NSIndexSet *) rows toPasteboard: (NSPasteboard *) pboard
121 {
122 vmBeingDragged = [vmArray objectAtIndex:[rows firstIndex]];
123 [pboard declareTypes:[NSArray arrayWithObject:VM_DRAG_TYPE] owner:self];
124 [pboard setString:VM_DRAG_TYPE forType:VM_DRAG_TYPE];
125 return YES;
126 }
127
128 - (NSDragOperation) tableView: (NSTableView *) table validateDrop: (id <NSDraggingInfo>) info proposedRow: (int) row proposedDropOperation: (NSTableViewDropOperation) op
129 {
130 if (op == NSTableViewDropAbove && row != -1) {
131 return NSDragOperationPrivate;
132 } else {
133 return NSDragOperationNone;
134 }
135 }
136
137 - (BOOL) tableView: (NSTableView *) table acceptDrop: (id <NSDraggingInfo>) info row: (int) row dropOperation: (NSTableViewDropOperation) op
138 {
139 if ([[[info draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObject:VM_DRAG_TYPE]] isEqualToString:VM_DRAG_TYPE]) {
140 [vmList deselectAll:nil];
141 int index = [vmArray indexOfObject:vmBeingDragged];
142 if (index != row) {
143 [vmArray insertObject:vmBeingDragged atIndex:row];
144 if (row <= index) {
145 index += 1;
146 } else {
147 row -= 1;
148 }
149 [vmArray removeObjectAtIndex: index];
150 }
151 [[NSUserDefaults standardUserDefaults] setObject:vmArray forKey:@"vm_list"];
152 [vmList reloadData];
153 [vmList selectRow:row byExtendingSelection:NO];
154 return YES;
155 }
156
157 return NO;
158 }
159
160 - (NSMenu *) tableView: (NSTableView *) table menuForEvent: (NSEvent *) event
161 {
162 NSMenu *menu = nil;
163 int row = [table rowAtPoint:[table convertPoint:[event locationInWindow] fromView:nil]];
164 if (row >= 0) {
165 [table selectRow:row byExtendingSelection:NO];
166 menu = [[[NSMenu alloc] initWithTitle: @"Contextual Menu"] autorelease];
167 [menu addItemWithTitle: @"Launch Virtual Machine"
168 action: @selector(launchVirtualMachine:) keyEquivalent: @""];
169 [menu addItemWithTitle: @"Edit VM Settings..."
170 action: @selector(editVirtualMachineSettings:) keyEquivalent: @""];
171 [menu addItemWithTitle: @"Reveal VM in Finder"
172 action: @selector(revealVirtualMachineInFinder:) keyEquivalent: @""];
173 [menu addItemWithTitle: @"Remove VM from List"
174 action: @selector(deleteVirtualMachine:) keyEquivalent: @""];
175 }
176 return menu;
177 }
178
179 //- (NSString *) tableView: (NSTableView *) table toolTipForCell: (NSCell *) cell rect: (NSRectPointer) rect
180 // tableColumn: (NSTableColumn *) c row: (int) r mouseLocation: (NSPoint) loc
181 //{
182 // return [vmArray objectAtIndex: r];
183 //}
184
185 - (IBAction) newVirtualMachine: (id) sender
186 {
187 NSSavePanel *save = [NSSavePanel savePanel];
188 [save setMessage: @"New SheepShaver Virtual Machine:"];
189 [save setRequiredFileType: @"sheepvm"];
190 [save setCanSelectHiddenExtension: YES];
191 [save setExtensionHidden: NO];
192 [save beginSheetForDirectory: nil
193 file: @"New.sheepvm"
194 modalForWindow: [self window]
195 modalDelegate: self
196 didEndSelector: @selector(_newVirtualMachineDone: returnCode: contextInfo:)
197 contextInfo: nil];
198 }
199
200 - (IBAction) _newVirtualMachineDone: (NSSavePanel *) save returnCode: (int) returnCode contextInfo: (void *) contextInfo
201 {
202 if (returnCode == NSOKButton) {
203 NSFileManager *manager = [NSFileManager defaultManager];
204 [manager createDirectoryAtPath:[save filename] attributes:nil];
205 [manager createFileAtPath:[[save filename] stringByAppendingPathComponent:@"prefs"] contents:nil attributes:nil];
206 [vmArray addObject:[save filename]];
207 [vmList reloadData];
208 [[NSUserDefaults standardUserDefaults] setObject:vmArray forKey:@"vm_list"];
209 [vmList selectRow:([vmArray count] - 1) byExtendingSelection:NO];
210 [self editVirtualMachineSettings:self];
211 if ([[VMSettingsController sharedInstance] cancelWasClicked]) {
212 [manager removeFileAtPath:[save filename] handler:nil];
213 [vmArray removeObjectAtIndex:([vmArray count] - 1)];
214 [vmList reloadData];
215 }
216 // TODO advanced: show sub-panel in save dialog that says "Create Disk:"
217 }
218 }
219
220 - (IBAction) importVirtualMachine: (id) sender
221 {
222 NSOpenPanel *open = [NSOpenPanel openPanel];
223 [open setMessage:@"Import SheepShaver Virtual Machine:"];
224 [open setResolvesAliases:YES];
225 // Curiously, bundles are treated as "files" not "directories" by NSOpenPanel.
226 [open setCanChooseDirectories:NO];
227 [open setCanChooseFiles:YES];
228 [open setAllowsMultipleSelection:NO];
229 [open beginSheetForDirectory: nil
230 file: nil
231 types: [NSArray arrayWithObject:@"sheepvm"]
232 modalForWindow: [self window]
233 modalDelegate: self
234 didEndSelector: @selector(_importVirtualMachineDone: returnCode: contextInfo:)
235 contextInfo: nil];
236 }
237
238 - (void) _importVirtualMachineDone: (NSOpenPanel *) open returnCode: (int) returnCode contextInfo: (void *) contextInfo
239 {
240 if (returnCode == NSOKButton) {
241 [vmArray addObject:[open filename]];
242 [vmList reloadData];
243 [[NSUserDefaults standardUserDefaults] setObject:vmArray forKey:@"vm_list"];
244 }
245 }
246
247 - (IBAction) editVirtualMachineSettings: (id) sender
248 {
249 int selectedRow = [vmList selectedRow];
250 if (selectedRow >= 0) {
251 NSString *path = [vmArray objectAtIndex:selectedRow];
252 if ([tasks objectForKey:path]) {
253 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
254 [alert setMessageText:@"Cannot edit virtual machine settings while it's running."];
255 [alert setAlertStyle:NSWarningAlertStyle];
256 [alert beginSheetModalForWindow:[self window]
257 modalDelegate:self
258 didEndSelector:nil
259 contextInfo:nil];
260 } else {
261 [[VMSettingsController sharedInstance] editSettingsFor:path sender:sender];
262 }
263 }
264 }
265
266 - (IBAction) launchVirtualMachine: (id) sender
267 {
268 int selectedRow = [vmList selectedRow];
269 if (selectedRow >= 0) {
270 NSString *path = [vmArray objectAtIndex:selectedRow];
271 if ([tasks objectForKey:path]) {
272 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
273 [alert setMessageText:@"The selected virtual machine is already running."];
274 [alert setAlertStyle:NSWarningAlertStyle];
275 [alert beginSheetModalForWindow:[self window]
276 modalDelegate:self
277 didEndSelector:nil
278 contextInfo:nil];
279 } else {
280 NSTask *sheep = [[NSTask alloc] init];
281 [sheep setLaunchPath:[[NSBundle mainBundle] pathForAuxiliaryExecutable:@"SheepShaver"]];
282 [sheep setArguments:[NSArray arrayWithObject:path]];
283 [sheep launch];
284 [tasks setObject:sheep forKey:path];
285 }
286 }
287 }
288
289 - (void) onTaskTerminated: (NSNotification *) notification
290 {
291 NSArray *paths = [tasks allKeys];
292 NSEnumerator *enumerator = [paths objectEnumerator];
293 NSString *path;
294 while ((path = [enumerator nextObject])) {
295 NSTask *task = [tasks objectForKey:path];
296 if (![task isRunning]) {
297 [tasks removeObjectForKey:path];
298 [task release];
299 }
300 }
301 }
302
303 - (IBAction) deleteVirtualMachine: (id) sender
304 {
305 int selectedRow = [vmList selectedRow];
306 if (selectedRow >= 0) {
307 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
308 [alert setMessageText:@"Do you wish to remove the selected virtual machine from the list?"];
309 [alert addButtonWithTitle:@"Remove"];
310 [alert addButtonWithTitle:@"Cancel"];
311 [alert setAlertStyle:NSWarningAlertStyle];
312 [alert beginSheetModalForWindow:[self window]
313 modalDelegate:self
314 didEndSelector:@selector(_deleteVirtualMachineDone: returnCode: contextInfo:)
315 contextInfo:nil];
316
317 }
318 }
319
320 - (void) _deleteVirtualMachineDone: (NSAlert *) alert returnCode: (int) returnCode contextInfo: (void *) contextInfo
321 {
322 if (returnCode == NSAlertFirstButtonReturn) {
323 [vmArray removeObjectAtIndex:[vmList selectedRow]];
324 [vmList deselectAll:self];
325 [vmList reloadData];
326 [[NSUserDefaults standardUserDefaults] setObject:vmArray forKey:@"vm_list"];
327 }
328 }
329
330 - (IBAction) revealVirtualMachineInFinder: (id) sender
331 {
332 int selectedRow = [vmList selectedRow];
333 if (selectedRow >= 0) {
334 [[NSWorkspace sharedWorkspace] selectFile: [vmArray objectAtIndex:selectedRow] inFileViewerRootedAtPath: @""];
335 }
336 }
337
338 @end