Fix full-screen zooming with Dock visible
[MacVim.git] / src / MacVim / MMFullscreenWindow.m
blobb366658d85bc9c0b07ec10bf1b1599e9a392783d
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  * MMFullscreenWindow
12  *
13  * A window without any decorations which covers an entire screen.
14  *
15  * When entering full-screen mode the window controller is set to control an
16  * instance of this class instead of an MMWindow.  (This seems to work fine
17  * even though the Apple docs state that it is generally a better idea to
18  * create a separate window controller for each window.)
19  *
20  * Most of the full-screen logic is currently in this class although it might
21  * move to the window controller in the future.
22  *
23  * Author: Nico Weber
24  */
26 #import "MMFullscreenWindow.h"
27 #import "MMTextView.h"
28 #import "MMVimController.h"
29 #import "MMVimView.h"
30 #import "MMWindowController.h"
31 #import "Miscellaneous.h"
32 #import <Carbon/Carbon.h>
33 #import <PSMTabBarControl.h>
35 // These have to be the same as in option.h
36 #define FUOPT_MAXVERT         0x001
37 #define FUOPT_MAXHORZ         0x002
38 #define FUOPT_BGCOLOR_HLGROUP 0x004
41 @interface MMFullscreenWindow (Private)
42 - (BOOL)isOnPrimaryScreen;
43 - (void)handleWindowDidBecomeMainNotification:(NSNotification *)notification;
44 - (void)handleWindowDidResignMainNotification:(NSNotification *)notification;
45 @end
47 @implementation MMFullscreenWindow
49 - (MMFullscreenWindow *)initWithWindow:(NSWindow *)t view:(MMVimView *)v 
50                                backgroundColor:(NSColor *)back
52     NSScreen* screen = [t screen];
54     // XXX: what if screen == nil?
56     // you can't change the style of an existing window in cocoa. create a new
57     // window and move the MMTextView into it.
58     // (another way would be to make the existing window large enough that the
59     // title bar is off screen. but that doesn't work with multiple screens).  
60     self = [super initWithContentRect:[screen frame]
61                             styleMask:NSBorderlessWindowMask
62                               backing:NSBackingStoreBuffered
63                                 defer:YES
64                                // since we're passing [screen frame] above,
65                                // we want the content rect to be relative to
66                                // the main screen (ie, pass nil for screen).
67                                screen:nil];
68       
69     if (self == nil)
70         return nil;
72     target = [t retain];
73     view = [v retain];
75     [self setHasShadow:NO];
76     [self setShowsResizeIndicator:NO];
77     [self setBackgroundColor:back];
78     [self setReleasedWhenClosed:NO];
80     [[NSNotificationCenter defaultCenter]
81         addObserver:self
82            selector:@selector(handleWindowDidBecomeMainNotification:)
83                name:NSWindowDidBecomeMainNotification
84              object:self];
86     [[NSNotificationCenter defaultCenter]
87         addObserver:self
88            selector:@selector(handleWindowDidResignMainNotification:)
89                name:NSWindowDidResignMainNotification
90              object:self];
92     return self;
95 - (void)dealloc
97     LOG_DEALLOC
99     [[NSNotificationCenter defaultCenter] removeObserver:self];
101     [target release];  target = nil;
102     [view release];  view = nil;
104     [super dealloc];
107 - (void)enterFullscreen:(int)fuoptions
109     // fade to black
110     Boolean didBlend = NO;
111     CGDisplayFadeReservationToken token;
112     if (CGAcquireDisplayFadeReservation(.5, &token) == kCGErrorSuccess) {
113         CGDisplayFade(token, .25, kCGDisplayBlendNormal,
114             kCGDisplayBlendSolidColor, .0, .0, .0, true);
115         didBlend = YES;
116     }
117     
118     // fool delegate
119     id delegate = [target delegate];
120     [target setDelegate:nil];
121     
122     // make target's window controller believe that it's now controlling us
123     [[target windowController] setWindow:self];
125     oldTabBarStyle = [[view tabBarControl] styleName];
126     [[view tabBarControl] setStyleNamed:@"Unified"];
128     // add text view
129     oldPosition = [view frame].origin;
131     [view removeFromSuperviewWithoutNeedingDisplay];
132     [[self contentView] addSubview:view];
133     [self setInitialFirstResponder:[view textView]];
134     
135     // NOTE: Calling setTitle:nil causes an exception to be raised (and it is
136     // possible that 'target' has no title when we get here).
137     if ([target title])
138         [self setTitle:[target title]];
140     [self setOpaque:[target isOpaque]];
142     // don't set this sooner, so we don't get an additional
143     // focus gained message  
144     [self setDelegate:delegate];
146     // resize vim view according to fuoptions
147     int currRows, currColumns;
148     [[view textView] getMaxRows:&currRows columns:&currColumns];
150     int fuRows = currRows, fuColumns = currColumns;
152     // NOTE: Do not use [NSScreen visibleFrame] when determining the screen
153     // size since it compensates for menu and dock.
154     int maxRows, maxColumns;
155     NSSize size = [[self screen] frame].size;
156     [view constrainRows:&maxRows columns:&maxColumns toSize:size];
158     // Store current pre-fu vim size
159     nonFuRows = currRows;
160     nonFuColumns = currColumns;
162     // Compute current fu size
163     if (fuoptions & FUOPT_MAXVERT)
164         fuRows = maxRows;
165     if (fuoptions & FUOPT_MAXHORZ)
166         fuColumns = maxColumns;
168     startFuFlags = fuoptions;
170     // if necessary, resize vim to target fu size
171     if (currRows != fuRows || currColumns != fuColumns) {
173         // The size sent here is queued and sent to vim when it's in
174         // event processing mode again. Make sure to only send the values we
175         // care about, as they override any changes that were made to 'lines'
176         // and 'columns' after 'fu' was set but before the event loop is run.
177         NSData *data = nil;
178         int msgid = 0;
179         if (currRows != fuRows && currColumns != fuColumns) {
180             int newSize[2] = { fuRows, fuColumns };
181             data = [NSData dataWithBytes:newSize length:2*sizeof(int)];
182             msgid = SetTextDimensionsMsgID;
183         } else if (currRows != fuRows) {
184             data = [NSData dataWithBytes:&fuRows length:sizeof(int)];
185             msgid = SetTextRowsMsgID;
186         } else if (currColumns != fuColumns) {
187             data = [NSData dataWithBytes:&fuColumns length:sizeof(int)];
188             msgid = SetTextColumnsMsgID;
189         }
190         NSParameterAssert(data != nil && msgid != 0);
192         MMVimController *vimController =
193             [[self windowController] vimController];
195         [vimController sendMessage:msgid data:data];
196         [[view textView] setMaxRows:fuRows columns:fuColumns];
197     }
199     startFuRows = fuRows;
200     startFuColumns = fuColumns;
202     // move vim view to the window's center
203     [self centerView];
205     // make us visible and target invisible
206     [target orderOut:self];
207     [self makeKeyAndOrderFront:self];
209     // fade back in
210     if (didBlend) {
211         CGDisplayFade(token, .25, kCGDisplayBlendSolidColor,
212             kCGDisplayBlendNormal, .0, .0, .0, false);
213         CGReleaseDisplayFadeReservation(token);
214     }
217 - (void)leaveFullscreen
219     // fade to black
220     Boolean didBlend = NO;
221     CGDisplayFadeReservationToken token;
222     if (CGAcquireDisplayFadeReservation(.5, &token) == kCGErrorSuccess) {
223         CGDisplayFade(token, .25, kCGDisplayBlendNormal,
224             kCGDisplayBlendSolidColor, .0, .0, .0, true);
225         didBlend = YES;
226     }
228     // restore old vim view size
229     int currRows, currColumns;
230     [[view textView] getMaxRows:&currRows columns:&currColumns];
231     int newRows = currRows, newColumns = currColumns;
233     // compute desired non-fu size.
234     // if current fu size is equal to fu size at fu enter time,
235     // restore the old size
236     //
237     if (startFuFlags & FUOPT_MAXVERT && startFuRows == currRows)
238         newRows = nonFuRows;
240     if (startFuFlags & FUOPT_MAXHORZ && startFuColumns == currColumns)
241         newColumns = nonFuColumns;
243     // resize vim if necessary
244     if (currRows != newRows || currColumns != newColumns) {
245         int newSize[2] = { newRows, newColumns };
246         NSData *data = [NSData dataWithBytes:newSize length:2*sizeof(int)];
247         MMVimController *vimController =
248             [[self windowController] vimController];
250         [vimController sendMessage:SetTextDimensionsMsgID data:data];
251         [[view textView] setMaxRows:newRows columns:newColumns];
252     }
254     // fix up target controller
255     [self retain];  // NSWindowController releases us once
256     [[self windowController] setWindow:target];
258     [[view tabBarControl] setStyleNamed:oldTabBarStyle];
260     // fix delegate
261     id delegate = [self delegate];
262     [self setDelegate:nil];
263     
264     // move text view back to original window, hide fullscreen window,
265     // show original window
266     // do this _after_ resetting delegate and window controller, so the
267     // window controller doesn't get a focus lost message from the fullscreen
268     // window.
269     [view removeFromSuperviewWithoutNeedingDisplay];
270     [[target contentView] addSubview:view];
272     [view setFrameOrigin:oldPosition];
273     [self close];
275     // Set the text view to initial first responder, otherwise the 'plus'
276     // button on the tabline steals the first responder status.
277     [target setInitialFirstResponder:[view textView]];
279     [target makeKeyAndOrderFront:self];
281     // ...but we don't want a focus gained message either, so don't set this
282     // sooner
283     [target setDelegate:delegate];
285     // fade back in  
286     if (didBlend) {
287         CGDisplayFade(token, .25, kCGDisplayBlendSolidColor,
288             kCGDisplayBlendNormal, .0, .0, .0, false);
289         CGReleaseDisplayFadeReservation(token);
290     }
291     
292     [self autorelease]; // Balance the above retain
295 // Title-less windows normally don't receive key presses, override this
296 - (BOOL)canBecomeKeyWindow
298     return YES;
301 // Title-less windows normally can't become main which means that another
302 // non-fullscreen window will have the "active" titlebar in expose. Bad, fix it.
303 - (BOOL)canBecomeMainWindow
305     return YES;
308 - (void)centerView
310     NSRect outer = [self frame], inner = [view frame];
311     //NSLog(@"%s %@%@", _cmd, NSStringFromRect(outer), NSStringFromRect(inner));
313     NSPoint origin = NSMakePoint((outer.size.width - inner.size.width)/2,
314                                  (outer.size.height - inner.size.height)/2);
315     [view setFrameOrigin:origin];
318 - (void)scrollWheel:(NSEvent *)theEvent
320     [[view textView] scrollWheel:theEvent];
323 - (void)performClose:(id)sender
325     id wc = [self windowController];
326     if ([wc respondsToSelector:@selector(performClose:)])
327         [wc performClose:sender];
328     else
329         [super performClose:sender];
332 - (BOOL)validateMenuItem:(NSMenuItem *)item
334     if ([item action] == @selector(vimMenuItemAction:)
335             || [item action] == @selector(performClose:))
336         return [item tag];
338     return YES;
341 @end // MMFullscreenWindow
346 @implementation MMFullscreenWindow (Private)
348 - (BOOL)isOnPrimaryScreen
350     // The primary screen is the screen the menu bar is on. This is different
351     // from [NSScreen mainScreen] (which returns the screen containing the
352     // key window).
353     NSArray *screens = [NSScreen screens];
354     if (screens == nil || [screens count] < 1)
355         return NO;
357     return [self screen] == [screens objectAtIndex:0];
360 - (void)handleWindowDidBecomeMainNotification:(NSNotification *)notification
362     // Hide menu and dock, both appear on demand.
363     //
364     // Another way to deal with several fullscreen windows would be to hide/
365     // reveal the dock only when the first fullscreen window is created and
366     // show it again after the last one has been closed, but toggling on each
367     // focus gain/loss works better with Spaces. The downside is that the
368     // menu bar flashes shortly when switching between two fullscreen windows.
370     // XXX: If you have a fullscreen window on a secondary monitor and unplug
371     // the monitor, this will probably not work right.
373     if ([self isOnPrimaryScreen]) {
374         SetSystemUIMode(kUIModeAllSuppressed, 0); //requires 10.3
375     }
378 - (void)handleWindowDidResignMainNotification:(NSNotification *)notification
380     // order menu and dock back in
381     if ([self isOnPrimaryScreen]) {
382         SetSystemUIMode(kUIModeNormal, 0);
383     }
386 @end // MMFullscreenWindow (Private)