merge changes from upstream
[MacVim/jjgod.git] / src / MacVim / MMWindowController.m
blob29ab3e8bf25486cb2c82877778fa5cd57beab6f7
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  * MMWindowController
12  *
13  * Handles resizing of windows, acts as an mediator between MMVimView and
14  * MMVimController.
15  */
17 #import "MMWindowController.h"
18 #import <PSMTabBarControl.h>
19 #import "MMTextView.h"
20 #import "MMTextStorage.h"
21 #import "MMVimController.h"
22 #import "MacVim.h"
23 #import "MMAppController.h"
24 #import "MMTypesetter.h"
25 #import "MMFullscreenWindow.h"
26 #import "MMVimView.h"
30 @interface MMWindowController (Private)
31 - (NSSize)contentSize;
32 - (NSRect)contentRectForFrameRect:(NSRect)frame;
33 - (NSRect)frameRectForContentRect:(NSRect)contentRect;
34 - (void)resizeWindowToFit:(id)sender;
35 - (NSRect)fitWindowToFrame:(NSRect)frame;
36 - (void)updateResizeIncrements;
37 - (NSTabViewItem *)addNewTabViewItem;
38 - (IBAction)vimMenuItemAction:(id)sender;
39 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb;
40 - (void)checkWindowNeedsResizing;
41 - (NSSize)resizeVimViewToFitSize:(NSSize)size;
42 @end
46 #if 0
47 NSString *buildMenuItemDescriptor(NSMenu *menu, NSString *tail)
49     return menu ? buildMenuItemDescriptor([menu supermenu], [[menu title]
50                     stringByAppendingString:tail])
51                 : tail;
54 NSMutableArray *buildMenuAddress(NSMenu *menu)
56     NSMutableArray *addr;
57     if (menu) {
58         addr = buildMenuAddress([menu supermenu]);
59         [addr addObject:[menu title]];
60     } else {
61         addr = [NSMutableArray array];
62     }
64     return addr;
66 #endif
68 // Note: This hack allows us to set content shadowing separately from
69 // the window shadow.  This is apparently what webkit and terminal do.
70 @interface NSWindow (NSWindowPrivate) // new Tiger private method
71 - (void)_setContentHasShadow:(BOOL)shadow;
72 @end
75 @implementation MMWindowController
77 - (id)initWithVimController:(MMVimController *)controller
79     if ((self = [super initWithWindowNibName:@"EmptyWindow"])) {
80         vimController = controller;
82         // Window cascading is handled by MMAppController.
83         [self setShouldCascadeWindows:NO];
85         NSWindow *win = [self window];
86         NSView *contentView = [win contentView];
87         vimView = [[MMVimView alloc] initWithFrame:[contentView frame]
88                                      vimController:vimController];
89         [contentView addSubview:vimView];
90         // [vimView translateOriginToPoint:
91         //     NSMakePoint([contentView frame].size.width, 0)];
92         // [vimView rotateByAngle:45.f];
94         // Create the tabline separator (which may be visible when the tabline
95         // is hidden).
96         NSRect tabSepRect = [contentView frame];
97         tabSepRect.origin.y = NSMaxY(tabSepRect)-1;
98         tabSepRect.size.height = 1;
99         tablineSeparator = [[NSBox alloc] initWithFrame:tabSepRect];
100         
101         [tablineSeparator setBoxType:NSBoxSeparator];
102         [tablineSeparator setHidden:NO];
103         [tablineSeparator setAutoresizingMask:NSViewWidthSizable
104             | NSViewMinYMargin];
106         [contentView setAutoresizesSubviews:YES];
107         [contentView addSubview:tablineSeparator];
109         [win setDelegate:self];
110         [win setInitialFirstResponder:[vimView textView]];
111         
112         // Make us safe on pre-tiger OSX
113         if ([win respondsToSelector:@selector(_setContentHasShadow:)])
114             [win _setContentHasShadow:NO];
115         
116         NSLog(@"initWithVimController done.");
117     }
119     return self;
122 - (void)dealloc
124     //NSLog(@"%@ %s", [self className], _cmd);
126     [tablineSeparator release];  tablineSeparator = nil;
127     [windowAutosaveKey release];  windowAutosaveKey = nil;
128     [vimView release];  vimView = nil;
130     [super dealloc];
133 - (NSString *)description
135     NSString *format =
136         @"%@ : setupDone=%d windowAutosaveKey=%@ vimController=%@";
137     return [NSString stringWithFormat:format,
138         [self className], setupDone, windowAutosaveKey, vimController];
141 - (MMVimController *)vimController
143     return vimController;
146 - (MMTextView *)textView
148     return [vimView textView];
151 - (MMTextStorage *)textStorage
153     return [vimView textStorage];
156 - (MMVimView *)vimView
158     return vimView;
161 - (NSString *)windowAutosaveKey
163     return windowAutosaveKey;
166 - (void)setWindowAutosaveKey:(NSString *)key
168     [windowAutosaveKey autorelease];
169     windowAutosaveKey = [key copy];
172 - (void)cleanup
174     //NSLog(@"%@ %s", [self className], _cmd);
176     if (fullscreenWindow != nil) {
177         // if we are closed while still in fullscreen, end fullscreen mode,
178         // release ourselves (because this won't happen in MMWindowController)
179         // and perform close operation on the original window
180         [self leaveFullscreen];
181     }
184     setupDone = NO;
185     vimController = nil;
187     [tablineSeparator removeFromSuperviewWithoutNeedingDisplay];
188     [vimView removeFromSuperviewWithoutNeedingDisplay];
189     [vimView cleanup];  // TODO: is this necessary?
191     [[self window] orderOut:self];
194 - (void)openWindow
196     [[NSApp delegate] windowControllerWillOpen:self];
198     [self addNewTabViewItem];
200     setupDone = YES;
202     [self updateResizeIncrements];
203     [self resizeWindowToFit:self];
204     [[self window] makeKeyAndOrderFront:self];
207 - (void)updateTabsWithData:(NSData *)data
209     [vimView updateTabsWithData:data];
212 - (void)selectTabWithIndex:(int)idx
214     [vimView selectTabWithIndex:idx];
217 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols
219     //NSLog(@"setTextDimensionsWithRows:%d columns:%d", rows, cols);
221     [vimView setActualRows:rows columns:cols];
223     if (setupDone && ![vimView inLiveResize])
224         shouldUpdateWindowSize = YES;
227 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
229     [vimView createScrollbarWithIdentifier:ident type:type];
232 - (void)destroyScrollbarWithIdentifier:(long)ident
234     [vimView destroyScrollbarWithIdentifier:ident];   
235     [self checkWindowNeedsResizing];
238 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
240     [vimView showScrollbarWithIdentifier:ident state:visible];
241     [self checkWindowNeedsResizing];
244 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
246     [vimView setScrollbarPosition:pos length:len identifier:ident];
249 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
250                     identifier:(long)ident
252     [vimView setScrollbarThumbValue:val proportion:prop identifier:ident];
255 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
257     // NOTE: This is called when the transparency changes so set the opacity
258     // flag on the window here (should be faster if the window is opaque).
259     BOOL isOpaque = [back alphaComponent] == 1.0f;
260     [[self window] setOpaque:isOpaque];
262     [vimView setDefaultColorsBackground:back foreground:fore];
265 - (void)setFont:(NSFont *)font
267     [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
268     [[vimView textStorage] setFont:font];
269     [self updateResizeIncrements];
272 - (void)setWideFont:(NSFont *)font
274     [[vimView textStorage] setWideFont:font];
277 - (void)processCommandQueueDidFinish
279     // XXX: If not in live resize and vimview's desired size differs from actual
280     // size, resize ourselves
281     if (shouldUpdateWindowSize) {
282         shouldUpdateWindowSize = NO;
283         [vimView setShouldUpdateWindowSize:NO];
284         [self resizeWindowToFit:self];
285     }
288 - (void)popupMenu:(NSMenu *)menu atRow:(int)row column:(int)col
290     if (!setupDone) return;
292     NSEvent *event;
293     if (row >= 0 && col >= 0) {
294         NSSize cellSize = [[vimView textStorage] cellSize];
295         NSPoint pt = { (col+1)*cellSize.width, (row+1)*cellSize.height };
296         pt = [[vimView textView] convertPoint:pt toView:nil];
298         event = [NSEvent mouseEventWithType:NSRightMouseDown
299                                    location:pt
300                               modifierFlags:0
301                                   timestamp:0
302                                windowNumber:[[self window] windowNumber]
303                                     context:nil
304                                 eventNumber:0
305                                  clickCount:0
306                                    pressure:1.0];
307     } else {
308         event = [[vimView textView] lastMouseDownEvent];
309     }
311     [NSMenu popUpContextMenu:menu withEvent:event forView:[vimView textView]];
314 - (void)showTabBar:(BOOL)on
316     [[vimView tabBarControl] setHidden:!on];
318     if (!on) {
319         NSToolbar *toolbar = [[self window] toolbar]; 
320         [tablineSeparator setHidden:![toolbar isVisible]];
321     } else {
322         [tablineSeparator setHidden:on];
323     }
325     //if (setupDone)
326     //    shouldUpdateWindowSize = YES;
329 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
331     NSToolbar *toolbar = [[self window] toolbar];
332     if (!toolbar) return;
334     [toolbar setSizeMode:size];
335     [toolbar setDisplayMode:mode];
336     [toolbar setVisible:on];
338     if (!on) {
339         [tablineSeparator setHidden:YES];
340     } else {
341         [tablineSeparator setHidden:![[vimView tabBarControl] isHidden]];
342     }
345 - (void)setMouseShape:(int)shape
347     // This switch should match mshape_names[] in misc2.c.
348     //
349     // TODO: Add missing cursor shapes.
350     switch (shape) {
351         case 2: [[NSCursor IBeamCursor] set]; break;
352         case 3: case 4: [[NSCursor resizeUpDownCursor] set]; break;
353         case 5: case 6: [[NSCursor resizeLeftRightCursor] set]; break;
354         case 9: [[NSCursor crosshairCursor] set]; break;
355         case 10: [[NSCursor pointingHandCursor] set]; break;
356         case 11: [[NSCursor openHandCursor] set]; break;
357         default:
358             [[NSCursor arrowCursor] set]; break;
359     }
361     // Shape 1 indicates that the mouse cursor should be hidden.
362     if (1 == shape)
363         [NSCursor setHiddenUntilMouseMoves:YES];
366 - (void)adjustLinespace:(int)linespace
368     if (vimView && [vimView textStorage]) {
369         [[vimView textStorage] setLinespace:(float)linespace];
370         shouldUpdateWindowSize = YES;
371     }
374 - (void)liveResizeWillStart
376     // Save the original title, if we haven't already.
377     if (lastSetTitle == nil) {
378         lastSetTitle = [[[self window] title] retain];
379     }
382 - (void)liveResizeDidEnd
384     if (!setupDone) return;
386     // At the end of an resize, check if vim view size and number of
387     // columns / rows agree (the first is set while resizing, the second by
388     // messages sent from vim). If not, send a synchronous (!) message to vim
389     // to set columns / rows to the value belonging to the view size. If the
390     // message couldn't be sent, change the view size to fit columns / rows.
392     // NOTE!  It is assumed that the window has been resized so that it will
393     // exactly fit the text storage (possibly after resizing it).  If this is
394     // not the case the display might be messed up.
395     BOOL resizeFailed = NO;
396     NSSize contentSize = [self contentSize];
398     int desiredSize[2];
399     [vimView getDesiredRows:&desiredSize[0] columns:&desiredSize[1]
400                     forSize:contentSize];
402     int rows, columns;
403     [vimView getActualRows:&rows columns:&columns];
405     if (desiredSize[0] != rows || desiredSize[1] != columns) {
407         NSData *data = [NSData dataWithBytes:desiredSize length:2*sizeof(int)];
409         // NOTE:  Since we're at the end of a live resize we want to make sure
410         // that the SetTextDimensionsMsgID message reaches Vim, else Vim and
411         // MacVim will have inconsistent states (i.e. the text view will be too
412         // large or too small for the window size).  Thus, add a timeout (this
413         // may have to be tweaked) and take note if the message was sent or
414         // not.
415         resizeFailed = ![vimController sendMessageNow:SetTextDimensionsMsgID
416                                                  data:data
417                                               timeout:.5];
418     }
420     if (resizeFailed) {
421         // Force the window size to match the text view size otherwise Vim and
422         // MacVim will have inconsistent states.
423         [self resizeWindowToFit:self];
424     }
426     // If we saved the original title while resizing, restore it.
427     if (lastSetTitle != nil) {
428         [[self window] setTitle:lastSetTitle];
429         [lastSetTitle release];
430         lastSetTitle = nil;
431     }
434 - (void)placeViews
436     if (!setupDone) return;
438     NSRect vimViewRect;
439     vimViewRect.origin = NSMakePoint(0, 0);
440     vimViewRect.size = [vimView getDesiredRows:NULL columns:NULL
441                                        forSize:[self contentSize]];
443      // HACK! If the window does resize, then windowDidResize is called which in
444      // turn calls placeViews.  In case the computed new size of the window is
445      // no different from the current size, then we need to call placeViews
446      // manually.
447      if (NSEqualRects(vimViewRect, [vimView frame])) {
448          [vimView placeViews];
449      } else {
450          [vimView setFrame:vimViewRect];
451      }
454 - (void)enterFullscreen
456     fullscreenWindow = [[MMFullscreenWindow alloc] initWithWindow:[self window]
457                                                              view:vimView];
458     [fullscreenWindow enterFullscreen];    
459       
460     [fullscreenWindow setDelegate:self];
463 - (void)leaveFullscreen
465     [fullscreenWindow leaveFullscreen];    
466     [fullscreenWindow release];
467     fullscreenWindow = nil;
471 - (IBAction)addNewTab:(id)sender
473     [vimView addNewTab:sender];
476 - (IBAction)toggleToolbar:(id)sender
478     [vimController sendMessage:ToggleToolbarMsgID data:nil];
483 // -- NSWindow delegate ------------------------------------------------------
485 - (void)windowDidBecomeMain:(NSNotification *)notification
487     [vimController sendMessage:GotFocusMsgID data:nil];
489     if ([vimView textStorage]) {
490         NSFontManager *fontManager = [NSFontManager sharedFontManager];
491         [fontManager setSelectedFont:[[vimView textStorage] font]
492                           isMultiple:NO];
493     }
496 - (void)windowDidResignMain:(NSNotification *)notification
498     [vimController sendMessage:LostFocusMsgID data:nil];
500     if ([vimView textView])
501         [[vimView textView] hideMarkedTextField];
504 - (BOOL)windowShouldClose:(id)sender
506     [vimController sendMessage:VimShouldCloseMsgID data:nil];
507     return NO;
510 - (void)windowDidMove:(NSNotification *)notification
512     if (setupDone && windowAutosaveKey) {
513         NSRect frame = [[self window] frame];
514         NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
515         NSString *topLeftString = NSStringFromPoint(topLeft);
517         [[NSUserDefaults standardUserDefaults]
518             setObject:topLeftString forKey:windowAutosaveKey];
519     }
522 - (void)windowDidResize:(id)sender
524     if (!setupDone) return;
526     // Live resizing works as follows:
527     // VimView's size is changed immediatly, and a resize message to the
528     // remote vim instance is sent. The remote vim instance sends a
529     // "vim content size changed" right back, but in live resize mode this
530     // doesn't change the VimView (because we assume that it already has the
531     // correct size because we set the resize increments correctly). Afterward,
532     // the remote vim view sends a batch draw for the text visible in the
533     // resized text area.
535     NSSize contentSize = [self contentSize];
536     [self resizeVimViewToFitSize:contentSize];
538     NSRect frame;
539     frame.origin = NSMakePoint(0, 0);
540     frame.size = contentSize;
541     [vimView setFrame:frame];
544 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
545                         defaultFrame:(NSRect)frame
547     // HACK!  For some reason 'frame' is not always constrained to fit on the
548     // screen (e.g. it may overlap the menu bar), so first constrain it to the
549     // screen; otherwise the new frame we compute may be too large and this
550     // will mess up the display after the window resizes.
551     frame = [win constrainFrameRect:frame toScreen:[win screen]];
553     // HACK!  If the top of 'frame' is lower than the current window frame,
554     // increase 'frame' so that their tops align.  Really, 'frame' should
555     // already have its top at least as high as the current window frame, but
556     // for some reason this is not always the case.
557     // (See resizeWindowToFit: for a similar hack.)
558     NSRect cur = [win frame];
559     if (NSMaxY(cur) > NSMaxY(frame)) {
560         frame.size.height = cur.origin.y - frame.origin.y + cur.size.height;
561     }
563     frame = [self fitWindowToFrame:frame];
565     // Keep old width and horizontal position unless user clicked while the
566     // Command key is held down.
567     NSEvent *event = [NSApp currentEvent];
568     if (!([event type] == NSLeftMouseUp
569             && [event modifierFlags] & NSCommandKeyMask)) {
570         NSRect currentFrame = [win frame];
571         frame.size.width = currentFrame.size.width;
572         frame.origin.x = currentFrame.origin.x;
573     }
575     return frame;
581 // -- Services menu delegate -------------------------------------------------
583 - (id)validRequestorForSendType:(NSString *)sendType
584                      returnType:(NSString *)returnType
586     if ([sendType isEqual:NSStringPboardType]
587             && [self askBackendForStarRegister:nil])
588         return self;
590     return [super validRequestorForSendType:sendType returnType:returnType];
593 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
594                              types:(NSArray *)types
596     if (![types containsObject:NSStringPboardType])
597         return NO;
599     return [self askBackendForStarRegister:pboard];
602 @end // MMWindowController
606 @implementation MMWindowController (Private)
608 - (NSRect)contentRectForFrameRect:(NSRect)frame
610     NSRect result = [[self window] contentRectForFrameRect:frame];
611     if (![tablineSeparator isHidden])
612         --result.size.height;
613     return result;
616 - (NSRect)frameRectForContentRect:(NSRect)contentRect
618     if (![tablineSeparator isHidden])
619         ++contentRect.size.height;
620     return [[self window] frameRectForContentRect:contentRect];
623 - (NSSize)contentSize
625     return [self contentRectForFrameRect:[[self window] frame]].size;
628 - (void)resizeWindowToFit:(id)sender
630     // Makes the window large enough to contain the vim view, called after the
631     // vim view's size was changed. If the window had to become to big, the
632     // vim view is made smaller.
634     // NOTE: Be very careful when you call this method!  Do not call while
635     // processing command queue, instead set 'shouldUpdateWindowSize' to YES.
636     // The only other place it is currently called is when live resize ends.
637     // This is done to ensure that the text view and window sizes match up
638     // (they may become out of sync if a SetTextDimensionsMsgID message to the
639     // backend is dropped).
641     if (!setupDone) return;
643     // Get size of text view, adapt window size to it
644     NSWindow *win = [self window];
645     NSRect frame = [win frame];
646     NSRect contentRect = [self contentRectForFrameRect:frame];
647     NSSize newSize = [vimView desiredSizeForActualRowsAndColumns];
649     // Keep top-left corner of the window fixed when resizing.
650     contentRect.origin.y -= newSize.height - contentRect.size.height;
651     contentRect.size = newSize;
653     frame = [self frameRectForContentRect:contentRect];
654     NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]];
656     // HACK!  Assuming the window frame cannot already be placed too high,
657     // adjust 'maxFrame' so that it at least as high up as the current frame.
658     // The reason for doing this is that constrainFrameRect:toScreen: does not
659     // always seem to utilize as much area as possible.
660     if (NSMaxY(frame) > NSMaxY(maxFrame)) {
661         maxFrame.size.height = frame.origin.y - maxFrame.origin.y
662                 + frame.size.height;
663     }
665     if (!NSEqualRects(maxFrame, frame)) {
666         // The new window frame is too big to fit on the screen, so fit the
667         // text storage to the biggest frame which will fit on the screen.
668         //NSLog(@"Proposed window frame does not fit on the screen!");
669         frame = [self fitWindowToFrame:maxFrame];
670         [self resizeVimViewToFitSize:[self contentRectForFrameRect:frame].size];
671     }
673     // NSLog(@"%s %@", _cmd, NSStringFromRect(frame));
675     // HACK! If the window does resize, then windowDidResize is called which in
676     // turn calls placeViews.  In case the computed new size of the window is
677     // no different from the current size, then we need to call placeViews
678     // manually.
679     if (NSEqualRects(frame, [win frame])) {
680         [self placeViews];
681     } else {
682         [win setFrame:frame display:YES];
683     }
686 - (NSRect)fitWindowToFrame:(NSRect)frame
688     if (!setupDone) return frame;
690     NSRect contentRect = [self contentRectForFrameRect:frame];
691     NSSize size = [vimView getDesiredRows:NULL columns:NULL
692                                   forSize:contentRect.size];
694     // Keep top-left corner of 'frame' fixed.
695     contentRect.origin.y -= size.height - contentRect.size.height;
696     contentRect.size = size;
698     return [self frameRectForContentRect:contentRect];
701 - (void)updateResizeIncrements
703     if (!setupDone) return;
705     NSSize size = [[vimView textStorage] cellSize];
706     [[self window] setContentResizeIncrements:size];
709 - (NSTabViewItem *)addNewTabViewItem
711     return [vimView addNewTabViewItem];
714 - (IBAction)vimMenuItemAction:(id)sender
716     int tag = [sender tag];
718     NSMutableData *data = [NSMutableData data];
719     [data appendBytes:&tag length:sizeof(int)];
721     [vimController sendMessage:ExecuteMenuMsgID data:data];
724 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
726     BOOL reply = NO;
727     id backendProxy = [vimController backendProxy];
729     if (backendProxy) {
730         @try {
731             reply = [backendProxy starRegisterToPasteboard:pb];
732         }
733         @catch (NSException *e) {
734             NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
735         }
736     }
738     return reply;
741 - (void)checkWindowNeedsResizing
743     shouldUpdateWindowSize =
744         shouldUpdateWindowSize || [vimView shouldUpdateWindowSize];
747 - (NSSize)resizeVimViewToFitSize:(NSSize)size
749     // If our optimal (rows,cols) do not match our current (rows,cols), resize
750     // ourselves and tell the Vim process to sync up.
751     int desired[2];
752     NSSize newSize = [vimView getDesiredRows:&desired[0] columns:&desired[1]
753                               forSize:size];
755     int rows, columns;
756     [vimView getActualRows:&rows columns:&columns];
758     if (desired[0] != rows || desired[1] != columns) {
759         // NSLog(@"Notify Vim that text storage dimensions changed from %dx%d "
760         //       @"to %dx%d", columns, rows, desired[0], desired[1]);
761         NSData *data = [NSData dataWithBytes:desired length:2*sizeof(int)];
763         [vimController sendMessage:SetTextDimensionsMsgID data:data];
765         // We only want to set the window title if this resize came from
766         // a live-resize, not (for example) setting 'columns' or 'lines'.
767         if ([[self textView] inLiveResize]) {
768             [[self window] setTitle:[NSString stringWithFormat:@"%dx%d",
769                     desired[1], desired[0]]];
770         }
771     }
773     return newSize;
777 @end // MMWindowController (Private)