Changed DiffText and Constant highlight groups
[MacVim/jjgod.git] / src / MacVim / MMWindowController.m
blobd3efe1f6fb967419889bdf079ea8de088080bb09
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 "MMWindowController.h"
12 #import <PSMTabBarControl.h>
13 #import "MMTextView.h"
14 #import "MMTextStorage.h"
15 #import "MMVimController.h"
16 #import "MacVim.h"
17 #import "MMAppController.h"
18 #import "MMTypesetter.h"
19 #import "MMFullscreenWindow.h"
20 #import "MMVimView.h"
24 @interface MMWindowController (Private)
25 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize;
26 - (NSRect)textViewRectForContentSize:(NSSize)contentSize;
27 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize;
28 - (void)resizeWindowToFit:(id)sender;
29 - (NSRect)fitWindowToFrame:(NSRect)frame;
30 - (void)updateResizeIncrements;
31 - (NSTabViewItem *)addNewTabViewItem;
32 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi;
33 - (IBAction)vimMenuItemAction:(id)sender;
34 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx;
35 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb;
36 - (void)checkWindowNeedsResizing;
37 @end
41 #if 0
42 NSString *buildMenuItemDescriptor(NSMenu *menu, NSString *tail)
44     return menu ? buildMenuItemDescriptor([menu supermenu], [[menu title]
45                     stringByAppendingString:tail])
46                 : tail;
49 NSMutableArray *buildMenuAddress(NSMenu *menu)
51     NSMutableArray *addr;
52     if (menu) {
53         addr = buildMenuAddress([menu supermenu]);
54         [addr addObject:[menu title]];
55     } else {
56         addr = [NSMutableArray array];
57     }
59     return addr;
61 #endif
63 // Note: This hack allows us to set content shadowing separately from
64 // the window shadow.  This is apparently what webkit and terminal do.
65 @interface NSWindow (NSWindowPrivate) // new Tiger private method
66 - (void)_setContentHasShadow:(BOOL)shadow;
67 @end
70 @implementation MMWindowController
72 - (id)initWithVimController:(MMVimController *)controller
74     if ((self = [super initWithWindowNibName:@"EmptyWindow"])) {
75         fullscreenWindow = nil;
76         vimController = controller;
78         // Window cascading is handled by MMAppController.
79         [self setShouldCascadeWindows:NO];
81         NSWindow *win = [self window];
82         NSView *contentView = [win contentView];
83         vimView = [[MMVimView alloc] initWithFrame:[contentView frame]
84                                      vimController:vimController];
85         [contentView addSubview:vimView];
86         //[vimView translateOriginToPoint:
87         //    NSMakePoint([contentView frame].size.width, 0)];
88         //[vimView rotateByAngle:45.f];
90         // Create the tabline separator (which may be visible when the tabline
91         // is hidden).
92         NSRect tabSepRect = [contentView frame];
93         tabSepRect.origin.y = NSMaxY(tabSepRect)-1;
94         tabSepRect.size.height = 1;
95         tablineSeparator = [[NSBox alloc] initWithFrame:tabSepRect];
96         
97         [tablineSeparator setBoxType:NSBoxSeparator];
98         [tablineSeparator setHidden:NO];
99         [tablineSeparator setAutoresizingMask:NSViewWidthSizable
100             | NSViewMinYMargin];
102         [contentView setAutoresizesSubviews:YES];
103         [contentView addSubview:tablineSeparator];
105         [win setDelegate:self];
106         [win setInitialFirstResponder:[vimView textView]];
107         
108         // Make us safe on pre-tiger OSX
109         if ([win respondsToSelector:@selector(_setContentHasShadow:)])
110             [win _setContentHasShadow:NO];
111     }
113     return self;
116 - (void)dealloc
118     //NSLog(@"%@ %s", [self className], _cmd);
120     [tablineSeparator release];  tablineSeparator = nil;
121     [windowAutosaveKey release];  windowAutosaveKey = nil;
122     [vimView release];  vimView = nil;
124     [super dealloc];
127 - (NSString *)description
129     return [NSString stringWithFormat:@"%@ : setupDone=%d windowAutosaveKey=%@ vimController=%@", [self className], setupDone, windowAutosaveKey, vimController];
132 - (MMVimController *)vimController
134     return vimController;
137 - (MMTextView *)textView
139     return [vimView textView];
142 - (MMTextStorage *)textStorage
144     return [vimView textStorage];
147 - (MMVimView *)vimView
149     return vimView;
152 - (NSString *)windowAutosaveKey
154     return windowAutosaveKey;
157 - (void)setWindowAutosaveKey:(NSString *)key
159     [windowAutosaveKey autorelease];
160     windowAutosaveKey = [key copy];
163 - (void)cleanup
165     //NSLog(@"%@ %s", [self className], _cmd);
167     if (fullscreenWindow != nil) {
168         // if we are closed while still in fullscreen, end fullscreen mode,
169         // release ourselves (because this won't happen in MMWindowController)
170         // and perform close operation on the original window
171         [self leaveFullscreen];
172     }
175     setupDone = NO;
176     vimController = nil;
178     [tablineSeparator removeFromSuperviewWithoutNeedingDisplay];
179     [vimView removeFromSuperviewWithoutNeedingDisplay];
180     [vimView cleanup];  // TODO: is this necessary?
182     [[self window] orderOut:self];
185 - (void)openWindow
187     [[NSApp delegate] windowControllerWillOpen:self];
189     [self addNewTabViewItem];
191     setupDone = YES;
193     [self updateResizeIncrements];
194     [self resizeWindowToFit:self];
195     [[self window] makeKeyAndOrderFront:self];
198 - (void)updateTabsWithData:(NSData *)data
200     [vimView updateTabsWithData:data];
203 - (void)selectTabWithIndex:(int)idx
205     [vimView selectTabWithIndex:idx];
208 - (void)setTextDimensionsWithRows:(int)rows columns:(int)cols
210     //NSLog(@"setTextDimensionsWithRows:%d columns:%d", rows, cols);
212     [[vimView textStorage] setMaxRows:rows columns:cols];
214     if (setupDone && ![vimView inLiveResize])
215         shouldUpdateWindowSize = YES;
218 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
220     [vimView createScrollbarWithIdentifier:ident type:type];
223 - (void)destroyScrollbarWithIdentifier:(long)ident
225     [vimView destroyScrollbarWithIdentifier:ident];   
226     [self checkWindowNeedsResizing];
229 - (void)showScrollbarWithIdentifier:(long)ident state:(BOOL)visible
231     [vimView showScrollbarWithIdentifier:ident state:visible];
232     [self checkWindowNeedsResizing];
235 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
237     MMScroller *scroller = [self scrollbarForIdentifier:ident index:NULL];
238     NSRange range = NSMakeRange(pos, len);
239     if (!NSEqualRanges(range, [scroller range])) {
240         //NSLog(@"Set range %@ for scroller %d",
241         //        NSStringFromRange(range), ident);
242         [scroller setRange:range];
243         // TODO!  Should only do this once per update.
244         
245         if (setupDone) // TODO: probably not necessary
246           [vimView placeScrollbars];
247     }
250 - (void)setScrollbarThumbValue:(float)val proportion:(float)prop
251                     identifier:(long)ident
253     [vimView setScrollbarThumbValue:val proportion:prop identifier:ident];
256 - (void)setDefaultColorsBackground:(NSColor *)back foreground:(NSColor *)fore
258     // NOTE: This is called when the transparency changes so set the opacity
259     // flag on the window here (should be faster if the window is opaque).
260     BOOL isOpaque = [back alphaComponent] == 1.0f;
261     [[self window] setOpaque:isOpaque];
263     [vimView setDefaultColorsBackground:back foreground:fore];
266 - (void)setFont:(NSFont *)font
268     [[NSFontManager sharedFontManager] setSelectedFont:font isMultiple:NO];
269     [[vimView textStorage] setFont:font];
270     [self updateResizeIncrements];
273 - (void)processCommandQueueDidFinish
275     if (shouldUpdateWindowSize) {
276         shouldUpdateWindowSize = NO;
277         [vimView setShouldUpdateWindowSize:NO];
278         [self resizeWindowToFit:self];
279     }
282 - (void)popupMenu:(NSMenu *)menu atRow:(int)row column:(int)col
284     if (!setupDone) return;
286     NSEvent *event;
287     if (row >= 0 && col >= 0) {
288         NSSize cellSize = [[vimView textStorage] cellSize];
289         NSPoint pt = { (col+1)*cellSize.width, (row+1)*cellSize.height };
290         pt = [[vimView textView] convertPoint:pt toView:nil];
292         event = [NSEvent mouseEventWithType:NSRightMouseDown
293                                    location:pt
294                               modifierFlags:0
295                                   timestamp:0
296                                windowNumber:[[self window] windowNumber]
297                                     context:nil
298                                 eventNumber:0
299                                  clickCount:0
300                                    pressure:1.0];
301     } else {
302         event = [[vimView textView] lastMouseDownEvent];
303     }
305     [NSMenu popUpContextMenu:menu withEvent:event forView:[vimView textView]];
308 - (void)showTabBar:(BOOL)on
310     [[vimView tabBarControl] setHidden:!on];
312     if (!on) {
313         NSToolbar *toolbar = [[self window] toolbar]; 
314         [tablineSeparator setHidden:![toolbar isVisible]];
315     } else {
316         [tablineSeparator setHidden:on];
317     }
319     //if (setupDone)
320     //    shouldUpdateWindowSize = YES;
323 - (void)showToolbar:(BOOL)on size:(int)size mode:(int)mode
325     NSToolbar *toolbar = [[self window] toolbar];
326     if (!toolbar) return;
328     [toolbar setSizeMode:size];
329     [toolbar setDisplayMode:mode];
330     [toolbar setVisible:on];
332     if (!on) {
333         [tablineSeparator setHidden:YES];
334     } else {
335         [tablineSeparator setHidden:![[vimView tabBarControl] isHidden]];
336     }
339 - (void)setMouseShape:(int)shape
341     // This switch should match mshape_names[] in misc2.c.
342     //
343     // TODO: Add missing cursor shapes.
344     switch (shape) {
345         case 2: [[NSCursor IBeamCursor] set]; break;
346         case 3: case 4: [[NSCursor resizeUpDownCursor] set]; break;
347         case 5: case 6: [[NSCursor resizeLeftRightCursor] set]; break;
348         case 9: [[NSCursor crosshairCursor] set]; break;
349         case 10: [[NSCursor pointingHandCursor] set]; break;
350         case 11: [[NSCursor openHandCursor] set]; break;
351         default:
352             [[NSCursor arrowCursor] set]; break;
353     }
355     // Shape 1 indicates that the mouse cursor should be hidden.
356     if (1 == shape)
357         [NSCursor setHiddenUntilMouseMoves:YES];
360 - (void)adjustLinespace:(int)linespace
362     if (vimView && [vimView textStorage]) {
363         [[vimView textStorage] setLinespace:(float)linespace];
364         shouldUpdateWindowSize = YES;
365     }
368 - (void)liveResizeDidEnd
370     // TODO: Don't duplicate code from placeViews.
372     if (!setupDone) return;
374     // NOTE!  It is assumed that the window has been resized so that it will
375     // exactly fit the text storage (possibly after resizing it).  If this is
376     // not the case the display might be messed up.
377     BOOL resizeFailed = NO;
378     NSWindow *win = [self window];
379     NSRect contentRect = [win contentRectForFrameRect:[win frame]];
380     NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
381     NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
383     int dim[2], rows, cols;
384     [[vimView textStorage] getMaxRows:&rows columns:&cols];
385     [[vimView textStorage] fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
387     if (dim[0] != rows || dim[1] != cols) {
388         NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
390         // NOTE:  Since we're at the end of a live resize we want to make sure
391         // that the SetTextDimensionsMsgID message reaches Vim, else Vim and
392         // MacVim will have inconsistent states (i.e. the text view will be too
393         // large or too small for the window size).  Thus, add a timeout (this
394         // may have to be tweaked) and take note if the message was sent or
395         // not.
396         resizeFailed = ![vimController sendMessageNow:SetTextDimensionsMsgID
397                                                  data:data
398                                               timeout:.5];
399     }
401     [[vimView textView] setFrame:textViewRect];
403     [vimView placeScrollbars];
405     if (resizeFailed) {
406         // Force the window size to match the text view size otherwise Vim and
407         // MacVim will have inconsistent states.
408         [self resizeWindowToFit:self];
409     }
412 - (void)placeViews
414     if (!setupDone) return;
416     // NOTE!  It is assumed that the window has been resized so that it will
417     // exactly fit the text storage (possibly after resizing it).  If this is
418     // not the case the display might be messed up.
419     NSWindow *win = [self window];
420     NSRect contentRect = [win contentRectForFrameRect:[win frame]];
421     NSRect textViewRect = [self textViewRectForContentSize:contentRect.size];
422     NSSize tsSize = [self textStorageSizeForTextViewSize:textViewRect.size];
424     int dim[2], rows, cols;
425     [[vimView textStorage] getMaxRows:&rows columns:&cols];
426     [[vimView textStorage] fitToSize:tsSize rows:&dim[0] columns:&dim[1]];
428     if (dim[0] != rows || dim[1] != cols) {
429         //NSLog(@"Notify Vim that text storage dimensions changed to %dx%d",
430         //        dim[0], dim[1]);
431         NSData *data = [NSData dataWithBytes:dim length:2*sizeof(int)];
433         [vimController sendMessage:SetTextDimensionsMsgID data:data];
434     }
436     // XXX: put vimView resizing logic in vimView
437     [[vimView textView] setFrame:textViewRect];
438     
439     NSRect vimViewRect = textViewRect;
440     vimViewRect.origin = NSMakePoint(0, 0);
441     if (![[vimView tabBarControl] isHidden])
442         vimViewRect.size.height += [[vimView tabBarControl] frame].size.height;
443     if ([vimView bottomScrollbarVisible])
444         vimViewRect.size.height += [NSScroller scrollerWidth];
445     if ([vimView leftScrollbarVisible])
446         vimViewRect.size.width += [NSScroller scrollerWidth];
447     if ([vimView rightScrollbarVisible])
448         vimViewRect.size.width += [NSScroller scrollerWidth];
452     [vimView setFrame:vimViewRect];
454     [vimView placeScrollbars];
457 - (void)enterFullscreen
459     fullscreenWindow = [[MMFullscreenWindow alloc] initWithWindow:[self window]
460                                                              view:vimView];
461     [fullscreenWindow enterFullscreen];    
462       
463     [fullscreenWindow setDelegate:self];
466 - (void)leaveFullscreen
468     [fullscreenWindow leaveFullscreen];    
469     [fullscreenWindow release];
470     fullscreenWindow = nil;
474 - (IBAction)addNewTab:(id)sender
476     [vimView addNewTab:sender];
479 - (IBAction)toggleToolbar:(id)sender
481     [vimController sendMessage:ToggleToolbarMsgID data:nil];
486 // -- NSWindow delegate ------------------------------------------------------
488 - (void)windowDidBecomeMain:(NSNotification *)notification
490     [vimController sendMessage:GotFocusMsgID data:nil];
492     if ([vimView textStorage]) {
493         NSFontManager *fontManager = [NSFontManager sharedFontManager];
494         [fontManager setSelectedFont:[[vimView textStorage] font]
495                           isMultiple:NO];
496     }
499 - (void)windowDidResignMain:(NSNotification *)notification
501     [vimController sendMessage:LostFocusMsgID data:nil];
503     if ([vimView textView])
504         [[vimView textView] hideMarkedTextField];
507 - (BOOL)windowShouldClose:(id)sender
509     [vimController sendMessage:VimShouldCloseMsgID data:nil];
510     return NO;
513 - (void)windowDidMove:(NSNotification *)notification
515     if (setupDone && windowAutosaveKey) {
516         NSRect frame = [[self window] frame];
517         NSPoint topLeft = { frame.origin.x, NSMaxY(frame) };
518         NSString *topLeftString = NSStringFromPoint(topLeft);
520         [[NSUserDefaults standardUserDefaults]
521             setObject:topLeftString forKey:windowAutosaveKey];
522     }
525 - (void)windowDidResize:(id)sender
527     if (!setupDone) return;
528     [self placeViews];
531 - (NSRect)windowWillUseStandardFrame:(NSWindow *)win
532                         defaultFrame:(NSRect)frame
534     // HACK!  For some reason 'frame' is not always constrained to fit on the
535     // screen (e.g. it may overlap the menu bar), so first constrain it to the
536     // screen; otherwise the new frame we compute may be too large and this
537     // will mess up the display after the window resizes.
538     frame = [win constrainFrameRect:frame toScreen:[win screen]];
540     // HACK!  If the top of 'frame' is lower than the current window frame,
541     // increase 'frame' so that their tops align.  Really, 'frame' should
542     // already have its top at least as high as the current window frame, but
543     // for some reason this is not always the case.
544     // (See resizeWindowToFit: for a similar hack.)
545     NSRect cur = [win frame];
546     if (NSMaxY(cur) > NSMaxY(frame)) {
547         frame.size.height = cur.origin.y - frame.origin.y + cur.size.height;
548     }
550     frame = [self fitWindowToFrame:frame];
552     // Keep old width and horizontal position unless user clicked with the
553     // Command key is held down.
554     NSEvent *event = [NSApp currentEvent];
555     if (!([event type] == NSLeftMouseUp
556             && [event modifierFlags] & NSCommandKeyMask)) {
557         NSRect currentFrame = [win frame];
558         frame.size.width = currentFrame.size.width;
559         frame.origin.x = currentFrame.origin.x;
560     }
562     return frame;
568 // -- Services menu delegate -------------------------------------------------
570 - (id)validRequestorForSendType:(NSString *)sendType
571                      returnType:(NSString *)returnType
573     if ([sendType isEqual:NSStringPboardType]
574             && [self askBackendForStarRegister:nil])
575         return self;
577     return [super validRequestorForSendType:sendType returnType:returnType];
580 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard
581                              types:(NSArray *)types
583     if (![types containsObject:NSStringPboardType])
584         return NO;
586     return [self askBackendForStarRegister:pboard];
589 @end // MMWindowController
593 @implementation MMWindowController (Private)
595 - (NSSize)contentSizeForTextStorageSize:(NSSize)textViewSize
597     NSSize size = [vimView contentSizeForTextStorageSize:textViewSize];
598     if (![tablineSeparator isHidden])
599         ++size.height;
600     return size;
603 - (NSRect)textViewRectForContentSize:(NSSize)contentSize
605     NSSize size = { contentSize.width, contentSize.height };
606     if (![tablineSeparator isHidden])
607         --size.height;
609     return [vimView textViewRectForContentSize:size];
612 - (NSSize)textStorageSizeForTextViewSize:(NSSize)textViewSize
614     return [vimView textStorageSizeForTextViewSize:textViewSize];
617 - (void)resizeWindowToFit:(id)sender
619     // NOTE: Be very careful when you call this method!  Do not call while
620     // processing command queue, instead set 'shouldUpdateWindowSize' to YES.
621     // The only other place it is currently called is when live resize ends.
622     // This is done to ensure that the text view and window sizes match up
623     // (they may become out of sync if a SetTextDimensionsMsgID message to the
624     // backend is dropped).
626     if (!setupDone) return;
628     NSWindow *win = [self window];
629     NSRect frame = [win frame];
630     NSRect contentRect = [win contentRectForFrameRect:frame];
631     NSSize textStorageSize = [[vimView textStorage] size];
632     NSSize newSize = [self contentSizeForTextStorageSize:textStorageSize];
634     // Keep top-left corner of the window fixed when resizing.
635     contentRect.origin.y -= newSize.height - contentRect.size.height;
636     contentRect.size = newSize;
638     frame = [win frameRectForContentRect:contentRect];
639     NSRect maxFrame = [win constrainFrameRect:frame toScreen:[win screen]];
641     // HACK!  Assuming the window frame cannot already be placed too high,
642     // adjust 'maxFrame' so that it at least as high up as the current frame.
643     // The reason for doing this is that constrainFrameRect:toScreen: does not
644     // always seem to utilize as much area as possible.
645     if (NSMaxY(frame) > NSMaxY(maxFrame)) {
646         maxFrame.size.height = frame.origin.y - maxFrame.origin.y
647                 + frame.size.height;
648     }
650     if (!NSEqualRects(maxFrame, frame)) {
651         // The new window frame is too big to fit on the screen, so fit the
652         // text storage to the biggest frame which will fit on the screen.
653         //NSLog(@"Proposed window frame does not fit on the screen!");
654         frame = [self fitWindowToFrame:maxFrame];
655     }
657     //NSLog(@"%s %@", _cmd, NSStringFromRect(frame));
659     // HACK! If the window does resize, then windowDidResize is called which in
660     // turn calls placeViews.  In case the computed new size of the window is
661     // no different from the current size, then we need to call placeViews
662     // manually.
663     if (NSEqualRects(frame, [win frame])) {
664         [self placeViews];
665     } else {
666         [win setFrame:frame display:YES];
667     }
670 - (NSRect)fitWindowToFrame:(NSRect)frame
672     if (!setupDone) return frame;
674     NSWindow *win = [self window];
675     NSRect contentRect = [win contentRectForFrameRect:frame];
676     NSSize size = [self textViewRectForContentSize:contentRect.size].size;
677     size = [self textStorageSizeForTextViewSize:size];
678     size = [[vimView textStorage] fitToSize:size];
679     size = [self contentSizeForTextStorageSize:size];
681     // Keep top-left corner of 'frame' fixed.
682     contentRect.origin.y -= size.height - contentRect.size.height;
683     contentRect.size = size;
685     return [win frameRectForContentRect:contentRect];
688 - (void)updateResizeIncrements
690     if (!setupDone) return;
692     NSSize size = [[vimView textStorage] cellSize];
693     [[self window] setContentResizeIncrements:size];
696 - (NSTabViewItem *)addNewTabViewItem
698     return [vimView addNewTabViewItem];
701 - (int)representedIndexOfTabViewItem:(NSTabViewItem *)tvi
703     return [vimView representedIndexOfTabViewItem:tvi];
706 - (IBAction)vimMenuItemAction:(id)sender
708     int tag = [sender tag];
710     NSMutableData *data = [NSMutableData data];
711     [data appendBytes:&tag length:sizeof(int)];
713     [vimController sendMessage:ExecuteMenuMsgID data:data];
716 - (MMScroller *)scrollbarForIdentifier:(long)ident index:(unsigned *)idx
718     return [vimView scrollbarForIdentifier:ident index:idx];
721 - (BOOL)askBackendForStarRegister:(NSPasteboard *)pb
723     BOOL reply = NO;
724     id backendProxy = [vimController backendProxy];
726     if (backendProxy) {
727         @try {
728             reply = [backendProxy starRegisterToPasteboard:pb];
729         }
730         @catch (NSException *e) {
731             NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
732         }
733     }
735     return reply;
738 - (void)checkWindowNeedsResizing
740     shouldUpdateWindowSize =
741         shouldUpdateWindowSize || [vimView shouldUpdateWindowSize];
744 @end // MMWindowController (Private)