Changed application termination procedure
[MacVim/jjgod.git] / src / MacVim / MMAppController.m
blobe4cbf48648811130354bf0847962da5a8c9725f0
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
11 #import "MMAppController.h"
12 #import "MMVimController.h"
13 #import "MMWindowController.h"
17 // Default timeout intervals on all connections.
18 static NSTimeInterval MMRequestTimeout = 5;
19 static NSTimeInterval MMReplyTimeout = 5;
23 @interface MMAppController (MMServices)
24 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
25                 error:(NSString **)error;
26 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
27            error:(NSString **)error;
28 @end
31 @interface MMAppController (Private)
32 - (MMVimController *)keyVimController;
33 - (MMVimController *)topmostVimController;
34 - (void)launchVimProcessWithArguments:(NSArray *)args;
35 @end
37 @interface NSMenu (MMExtras)
38 - (void)recurseSetAutoenablesItems:(BOOL)on;
39 @end
41 @interface NSNumber (MMExtras)
42 - (int)tag;
43 @end
47 @implementation MMAppController
49 + (void)initialize
51     NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
52         [NSNumber numberWithBool:NO],   MMNoWindowKey,
53         [NSNumber numberWithInt:64],    MMTabMinWidthKey,
54         [NSNumber numberWithInt:6*64],  MMTabMaxWidthKey,
55         [NSNumber numberWithInt:132],   MMTabOptimumWidthKey,
56         [NSNumber numberWithInt:2],     MMTextInsetLeftKey,
57         [NSNumber numberWithInt:1],     MMTextInsetRightKey,
58         [NSNumber numberWithInt:1],     MMTextInsetTopKey,
59         [NSNumber numberWithInt:1],     MMTextInsetBottomKey,
60         [NSNumber numberWithBool:NO],   MMTerminateAfterLastWindowClosedKey,
61         @"MMTypesetter",                MMTypesetterKey,
62         [NSNumber numberWithFloat:1],   MMCellWidthMultiplierKey,
63         [NSNumber numberWithFloat:-1],  MMBaselineOffsetKey,
64         [NSNumber numberWithBool:YES],  MMTranslateCtrlClickKey,
65         [NSNumber numberWithBool:NO],   MMOpenFilesInTabsKey,
66         nil];
68     [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
70     NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
71     [NSApp registerServicesMenuSendTypes:types returnTypes:types];
74 - (id)init
76     if ((self = [super init])) {
77         fontContainerRef = loadFonts();
79         vimControllers = [NSMutableArray new];
81         // NOTE!  If the name of the connection changes here it must also be
82         // updated in MMBackend.m.
83         NSConnection *connection = [NSConnection defaultConnection];
84         NSString *name = [NSString stringWithFormat:@"%@-connection",
85                  [[NSBundle mainBundle] bundleIdentifier]];
86         //NSLog(@"Registering connection with name '%@'", name);
87         if ([connection registerName:name]) {
88             [connection setRequestTimeout:MMRequestTimeout];
89             [connection setReplyTimeout:MMReplyTimeout];
90             [connection setRootObject:self];
92             // NOTE: When the user is resizing the window the AppKit puts the
93             // run loop in event tracking mode.  Unless the connection listens
94             // to request in this mode, live resizing won't work.
95             [connection addRequestMode:NSEventTrackingRunLoopMode];
96         } else {
97             NSLog(@"WARNING: Failed to register connection with name '%@'",
98                     name);
99         }
100     }
102     return self;
105 - (void)dealloc
107     //NSLog(@"MMAppController dealloc");
109     [vimControllers release];
110     [openSelectionString release];
112     [super dealloc];
115 - (void)applicationDidFinishLaunching:(NSNotification *)notification
117     [NSApp setServicesProvider:self];
120 - (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
122     // NOTE!  This way it possible to start the app with the command-line
123     // argument '-nowindow yes' and no window will be opened by default.
124     untitledWindowOpening =
125         ![[NSUserDefaults standardUserDefaults] boolForKey:MMNoWindowKey];
126     return untitledWindowOpening;
129 - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
131     //NSLog(@"%s NSapp=%@ theApp=%@", _cmd, NSApp, sender);
133     [self newWindow:self];
134     return YES;
137 - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
139     // Go trough 'filenames' array and make sure each file exists.
140     NSString *firstMissingFile = nil;
141     NSMutableArray *files = [NSMutableArray array];
142     int i, count = [filenames count];
143     for (i = 0; i < count; ++i) {
144         NSString *name = [filenames objectAtIndex:i];
145         if ([[NSFileManager defaultManager] fileExistsAtPath:name]) {
146             [files addObject:name];
147         } else if (!firstMissingFile) {
148             firstMissingFile = name;
149         }
150     }
152     if (firstMissingFile) {
153         NSAlert *alert = [[NSAlert alloc] init];
154         [alert addButtonWithTitle:@"OK"];
156         NSString *text;
157         if ([files count] >= count-1) {
158             [alert setMessageText:@"File not found"];
159             text = [NSString stringWithFormat:@"Could not open file with "
160                 "name %@.", firstMissingFile];
161         } else {
162             [alert setMessageText:@"Multiple files not found"];
163             text = [NSString stringWithFormat:@"Could not open file with "
164                 "name %@, and %d other files.", firstMissingFile,
165                 count-[files count]-1];
166         }
168         [alert setInformativeText:text];
169         [alert setAlertStyle:NSWarningAlertStyle];
171         [alert runModal];
172         [alert release];
174         [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
175         return;
176     }
178     MMVimController *vc;
179     BOOL openInTabs = [[NSUserDefaults standardUserDefaults]
180         boolForKey:MMOpenFilesInTabsKey];
182     if (openInTabs && (vc = [self topmostVimController])) {
183         [vc dropFiles:files];
184     } else {
185         NSMutableArray *args = [NSMutableArray arrayWithObject:@"-p"];
186         [args addObjectsFromArray:files];
187         [self launchVimProcessWithArguments:args];
188     }
190     [NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
191     // NSApplicationDelegateReplySuccess = 0,
192     // NSApplicationDelegateReplyCancel = 1,
193     // NSApplicationDelegateReplyFailure = 2
196 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
198     return [[NSUserDefaults standardUserDefaults]
199             boolForKey:MMTerminateAfterLastWindowClosedKey];
202 - (NSApplicationTerminateReply)applicationShouldTerminate:
203     (NSApplication *)sender
205     // TODO: Follow Apple's guidelines for 'Graceful Application Termination'
206     // (in particular, allow user to review changes and save).
207     int reply = NSTerminateNow;
208     BOOL modifiedBuffers = NO;
210     // Go through windows, checking for modified buffers.  (Each Vim process
211     // tells MacVim when any buffer has been modified and MacVim sets the
212     // 'documentEdited' flag of the window correspondingly.)
213     NSEnumerator *e = [[NSApp windows] objectEnumerator];
214     id window;
215     while (window = [e nextObject]) {
216         if ([window isDocumentEdited]) {
217             modifiedBuffers = YES;
218             break;
219         }
220     }
222     if (modifiedBuffers) {
223         NSAlert *alert = [[NSAlert alloc] init];
224         [alert addButtonWithTitle:@"Quit"];
225         [alert addButtonWithTitle:@"Cancel"];
226         [alert setMessageText:@"Quit without saving?"];
227         [alert setInformativeText:@"There are modified buffers, "
228             "if you quit now all changes will be lost.  Quit anyway?"];
229         [alert setAlertStyle:NSWarningAlertStyle];
231         if ([alert runModal] != NSAlertFirstButtonReturn)
232             reply = NSTerminateCancel;
234         [alert release];
235     }
237     return reply;
240 - (void)applicationWillTerminate:(NSNotification *)aNotification
242     // This will invalidate all connections (since they were spawned from the
243     // default connection).
244     [[NSConnection defaultConnection] invalidate];
246     // Send a SIGINT to all running Vim processes, so that they are sure to
247     // receive the connectionDidDie: notification (a process has to be checking
248     // the run-loop for this to happen).
249     unsigned i, count = [vimControllers count];
250     for (i = 0; i < count; ++i) {
251         MMVimController *controller = [vimControllers objectAtIndex:i];
252         int pid = [controller pid];
253         if (pid > 0)
254             kill(pid, SIGINT);
255     }
257     if (fontContainerRef) {
258         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
259         fontContainerRef = 0;
260     }
262     // TODO: Is this a correct way of releasing the MMAppController?
263     // (It doesn't seem like dealloc is ever called.)
264     [NSApp setDelegate:nil];
265     [self autorelease];
268 - (void)removeVimController:(id)controller
270     //NSLog(@"%s%@", _cmd, controller);
272     [[controller windowController] close];
274     [vimControllers removeObject:controller];
276     if (![vimControllers count]) {
277         // Turn on autoenabling of menus (because no Vim is open to handle it),
278         // but do not touch the MacVim menu.  Note that the menus must be
279         // enabled first otherwise autoenabling does not work.
280         NSMenu *mainMenu = [NSApp mainMenu];
281         int i, count = [mainMenu numberOfItems];
282         for (i = 1; i < count; ++i) {
283             NSMenuItem *item = [mainMenu itemAtIndex:i];
284             [item setEnabled:YES];
285             [[item submenu] recurseSetAutoenablesItems:YES];
286         }
287     }
290 - (void)windowControllerWillOpen:(MMWindowController *)windowController
292     NSPoint topLeft = NSZeroPoint;
293     NSWindow *keyWin = [NSApp keyWindow];
294     NSWindow *win = [windowController window];
296     if (!win) return;
298     // If there is a key window, cascade from it, otherwise use the autosaved
299     // window position (if any).
300     if (keyWin) {
301         NSRect frame = [keyWin frame];
302         topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
303     } else {
304         NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
305             stringForKey:MMTopLeftPointKey];
306         if (topLeftString)
307             topLeft = NSPointFromString(topLeftString);
308     }
310     if (!NSEqualPoints(topLeft, NSZeroPoint)) {
311         if (keyWin)
312             topLeft = [win cascadeTopLeftFromPoint:topLeft];
314         [win setFrameTopLeftPoint:topLeft];
315     }
317     if (openSelectionString) {
318         // There is some text to paste into this window as a result of the
319         // services menu "Open selection ..." being used.
320         [[windowController vimController] dropString:openSelectionString];
321         [openSelectionString release];
322         openSelectionString = nil;
323     }
326 - (IBAction)newWindow:(id)sender
328     [self launchVimProcessWithArguments:nil];
331 - (IBAction)selectNextWindow:(id)sender
333     unsigned i, count = [vimControllers count];
334     if (!count) return;
336     NSWindow *keyWindow = [NSApp keyWindow];
337     for (i = 0; i < count; ++i) {
338         MMVimController *vc = [vimControllers objectAtIndex:i];
339         if ([[[vc windowController] window] isEqual:keyWindow])
340             break;
341     }
343     if (i < count) {
344         if (++i >= count)
345             i = 0;
346         MMVimController *vc = [vimControllers objectAtIndex:i];
347         [[vc windowController] showWindow:self];
348     }
351 - (IBAction)selectPreviousWindow:(id)sender
353     unsigned i, count = [vimControllers count];
354     if (!count) return;
356     NSWindow *keyWindow = [NSApp keyWindow];
357     for (i = 0; i < count; ++i) {
358         MMVimController *vc = [vimControllers objectAtIndex:i];
359         if ([[[vc windowController] window] isEqual:keyWindow])
360             break;
361     }
363     if (i < count) {
364         if (i > 0) {
365             --i;
366         } else {
367             i = count - 1;
368         }
369         MMVimController *vc = [vimControllers objectAtIndex:i];
370         [[vc windowController] showWindow:self];
371     }
374 - (IBAction)fontSizeUp:(id)sender
376     [[NSFontManager sharedFontManager] modifyFont:
377             [NSNumber numberWithInt:NSSizeUpFontAction]];
380 - (IBAction)fontSizeDown:(id)sender
382     [[NSFontManager sharedFontManager] modifyFont:
383             [NSNumber numberWithInt:NSSizeDownFontAction]];
386 - (byref id <MMFrontendProtocol>)
387     connectBackend:(byref in id <MMBackendProtocol>)backend
388                pid:(int)pid
390     //NSLog(@"Frontend got connection request from backend...adding new "
391     //        "MMVimController");
393     [(NSDistantObject*)backend
394             setProtocolForProxy:@protocol(MMBackendProtocol)];
396     MMVimController *vc = [[[MMVimController alloc]
397             initWithBackend:backend pid:pid] autorelease];
399     if (![vimControllers count]) {
400         // The first window autosaves its position.  (The autosaving features
401         // of Cocoa are not used because we need more control over what is
402         // autosaved and when it is restored.)
403         [[vc windowController] setWindowAutosaveKey:MMTopLeftPointKey];
404     }
406     [vimControllers addObject:vc];
408     // HACK!  MacVim does not get activated if it is launched from the
409     // terminal, so we forcibly activate here unless it is an untitled window
410     // opening (i.e. MacVim was opened from the Finder).  Untitled windows are
411     // treated differently, else MacVim would steal the focus if another app
412     // was activated while the untitled window was loading.
413     if (!untitledWindowOpening)
414         [NSApp activateIgnoringOtherApps:YES];
416     untitledWindowOpening = NO;
418     return vc;
421 - (NSArray *)serverList
423     NSMutableArray *array = [NSMutableArray array];
425     unsigned i, count = [vimControllers count];
426     for (i = 0; i < count; ++i) {
427         MMVimController *controller = [vimControllers objectAtIndex:i];
428         if ([controller serverName])
429             [array addObject:[controller serverName]];
430     }
432     return array;
435 @end // MMAppController
440 @implementation MMAppController (MMServices)
442 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
443                 error:(NSString **)error
445     if (![[pboard types] containsObject:NSStringPboardType]) {
446         NSLog(@"WARNING: Pasteboard contains no object of type "
447                 "NSStringPboardType");
448         return;
449     }
451     MMVimController *vc = [self topmostVimController];
452     if (vc) {
453         // Open a new tab first, since dropString: does not do this.
454         [vc sendMessage:AddNewTabMsgID data:nil];
455         [vc dropString:[pboard stringForType:NSStringPboardType]];
456     } else {
457         // NOTE: There is no window to paste the selection into, so save the
458         // text, open a new window, and paste the text when the next window
459         // opens.  (If this is called several times in a row, then all but the
460         // last call might be ignored.)
461         if (openSelectionString) [openSelectionString release];
462         openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
464         [self newWindow:self];
465     }
468 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
469            error:(NSString **)error
471     if (![[pboard types] containsObject:NSStringPboardType]) {
472         NSLog(@"WARNING: Pasteboard contains no object of type "
473                 "NSStringPboardType");
474         return;
475     }
477     // TODO: Parse multiple filenames and create array with names.
478     NSString *string = [pboard stringForType:NSStringPboardType];
479     string = [string stringByTrimmingCharactersInSet:
480             [NSCharacterSet whitespaceAndNewlineCharacterSet]];
481     string = [string stringByStandardizingPath];
483     MMVimController *vc = nil;
484     if (userData && [userData isEqual:@"Tab"])
485         vc = [self topmostVimController];
487     if (vc) {
488         [vc dropFiles:[NSArray arrayWithObject:string]];
489     } else {
490         [self application:NSApp openFiles:[NSArray arrayWithObject:string]];
491     }
494 @end // MMAppController (MMServices)
499 @implementation MMAppController (Private)
501 - (MMVimController *)keyVimController
503     NSWindow *keyWindow = [NSApp keyWindow];
504     if (keyWindow) {
505         unsigned i, count = [vimControllers count];
506         for (i = 0; i < count; ++i) {
507             MMVimController *vc = [vimControllers objectAtIndex:i];
508             if ([[[vc windowController] window] isEqual:keyWindow])
509                 return vc;
510         }
511     }
513     return nil;
516 - (MMVimController *)topmostVimController
518     NSArray *windows = [NSApp orderedWindows];
519     if ([windows count] > 0) {
520         NSWindow *window = [windows objectAtIndex:0];
521         unsigned i, count = [vimControllers count];
522         for (i = 0; i < count; ++i) {
523             MMVimController *vc = [vimControllers objectAtIndex:i];
524             if ([[[vc windowController] window] isEqual:window])
525                 return vc;
526         }
527     }
529     return nil;
532 - (void)launchVimProcessWithArguments:(NSArray *)args
534     NSString *path = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@"Vim"];
535     if (!path) {
536         NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
537         return;
538     }
540     NSMutableString *execArg = [NSMutableString
541         stringWithFormat:@"exec \"%@\" -g", path];
542     if (args) {
543         // Append all arguments while making sure that arguments containing
544         // spaces are enclosed in quotes.
545         NSCharacterSet *space = [NSCharacterSet whitespaceCharacterSet];
546         unsigned i, count = [args count];
548         for (i = 0; i < count; ++i) {
549             NSString *arg = [args objectAtIndex:i];
550             if (NSNotFound != [arg rangeOfCharacterFromSet:space].location)
551                 [execArg appendFormat:@" \"%@\"", arg];
552             else
553                 [execArg appendFormat:@" %@", arg];
554         }
555     }
557     // Launch the process with a login shell so that users environment settings
558     // get sourced.  This does not always happen when MacVim is started.
559     NSArray *shellArgs = [NSArray arrayWithObjects:@"-l", @"-c", execArg, nil];
560     NSString *shell = [[[NSProcessInfo processInfo] environment]
561         objectForKey:@"SHELL"];
562     if (!shell)
563         shell = @"/bin/sh";
565     //NSLog(@"Launching: %@  args: %@", shell, shellArgs);
566     [NSTask launchedTaskWithLaunchPath:shell arguments:shellArgs];
569 @end // MMAppController (Private)
574 @implementation NSMenu (MMExtras)
576 - (void)recurseSetAutoenablesItems:(BOOL)on
578     [self setAutoenablesItems:on];
580     int i, count = [self numberOfItems];
581     for (i = 0; i < count; ++i) {
582         NSMenuItem *item = [self itemAtIndex:i];
583         [item setEnabled:YES];
584         NSMenu *submenu = [item submenu];
585         if (submenu) {
586             [submenu recurseSetAutoenablesItems:on];
587         }
588     }
591 @end  // NSMenu (MMExtras)
596 @implementation NSNumber (MMExtras)
597 - (int)tag
599     return [self intValue];
601 @end // NSNumber (MMExtras)