Add full-screen options
[MacVim.git] / src / MacVim / MMFullscreenWindow.m
blob2ce2d04131f3888345309f508b7a7542affbc0b5
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 "MMVimController.h"
27 #import "MMFullscreenWindow.h"
28 #import <PSMTabBarControl.h>
29 #import "MMVimView.h"
30 #import "MMTextView.h"
31 #import "MMWindowController.h"
32 #import <Carbon/Carbon.h>
34 // These have to be the same as in option.h
35 #define FUOPT_MAXVERT 0x001
36 #define FUOPT_MAXHORZ 0x002
39 static int numFullscreenWindows = 0;
41 @interface MMFullscreenWindow (Private)
42 - (BOOL)isOnPrimaryScreen;
43 - (void)hideDockIfAppropriate;
44 - (void)revealDockIfAppropriate;
45 @end
47 @implementation MMFullscreenWindow
49 - (MMFullscreenWindow *)initWithWindow:(NSWindow *)t view:(MMVimView *)v
51     NSScreen* screen = [t screen];
53     // XXX: what if screen == nil?
55     // you can't change the style of an existing window in cocoa. create a new
56     // window and move the MMTextView into it.
57     // (another way would be to make the existing window large enough that the
58     // title bar is off screen. but that doesn't work with multiple screens).  
59     self = [super initWithContentRect:[screen frame]
60                             styleMask:NSBorderlessWindowMask
61                               backing:NSBackingStoreBuffered
62                                 defer:YES
63                                // since we're passing [screen frame] above,
64                                // we want the content rect to be relative to
65                                // the main screen (ie, pass nil for screen).
66                                screen:nil];
67       
68     if (self == nil)
69         return nil;
71     target = [t retain];
72     view = [v retain];
74     [self setHasShadow:NO];
75     [self setShowsResizeIndicator:NO];
76     [self setBackgroundColor:[NSColor blackColor]];
77     [self setReleasedWhenClosed:NO];
79     return self;
82 - (void)dealloc
84     [target release];  target = nil;
85     [view release];  view = nil;
87     [super dealloc];
90 - (void)enterFullscreen:(int)fuoptions
92     [self hideDockIfAppropriate];
94     // fade to black
95     Boolean didBlend = NO;
96     CGDisplayFadeReservationToken token;
97     if (CGAcquireDisplayFadeReservation(.5, &token) == kCGErrorSuccess) {
98         CGDisplayFade(token, .25, kCGDisplayBlendNormal,
99             kCGDisplayBlendSolidColor, .0, .0, .0, true);
100         didBlend = YES;
101     }
102     
103     // fool delegate
104     id delegate = [target delegate];
105     [target setDelegate:nil];
106     
107     // make target's window controller believe that it's now controlling us
108     [[target windowController] setWindow:self];
110     oldTabBarStyle = [[view tabBarControl] styleName];
111     [[view tabBarControl] setStyleNamed:@"Unified"];
113     // add text view
114     oldPosition = [view frame].origin;
116     [view removeFromSuperviewWithoutNeedingDisplay];
117     [[self contentView] addSubview:view];
118     [self setInitialFirstResponder:[view textView]];
119     
120     // NOTE: Calling setTitle:nil causes an exception to be raised (and it is
121     // possible that 'target' has no title when we get here).
122     if ([target title])
123         [self setTitle:[target title]];
125     [self setOpaque:[target isOpaque]];
127     // don't set this sooner, so we don't get an additional
128     // focus gained message  
129     [self setDelegate:delegate];
131     // resize vim view according to fuoptions
132     int currRows, currColumns;
133     [[view textView] getMaxRows:&currRows columns:&currColumns];
135     int fuRows = currRows, fuColumns = currColumns;
137     int maxRows, maxColumns;
138     NSSize size = [[self screen] frame].size;
139     [view constrainRows:&maxRows columns:&maxColumns toSize:size];
141     // Store current pre-fu vim size
142     nonFuRows = currRows;
143     nonFuColumns = currColumns;
145     // Compute current fu size
146     if (fuoptions & FUOPT_MAXVERT)
147         fuRows = maxRows;
148     if (fuoptions & FUOPT_MAXHORZ)
149         fuColumns = maxColumns;
151     startFuFlags = fuoptions;
153     // if necessary, resize vim to target fu size
154     if (currRows != fuRows || currColumns != fuColumns) {
155         int newSize[2] = { fuRows, fuColumns };
156         NSData *data = [NSData dataWithBytes:newSize length:2*sizeof(int)];
157         MMVimController *vimController =
158             [[self windowController] vimController];
160         [vimController sendMessage:SetTextDimensionsMsgID data:data];
161         [[view textView] setMaxRows:fuRows columns:fuColumns];
162     }
164     startFuRows = fuRows;
165     startFuColumns = fuColumns;
167     // move vim view to the window's center
168     [self centerView];
170     // make us visible and target invisible
171     [target orderOut:self];
172     [self makeKeyAndOrderFront:self];
174     // fade back in
175     if (didBlend) {
176         CGDisplayFade(token, .25, kCGDisplayBlendSolidColor,
177             kCGDisplayBlendNormal, .0, .0, .0, false);
178         CGReleaseDisplayFadeReservation(token);
179     }
182 - (void)leaveFullscreen
184     // fade to black
185     Boolean didBlend = NO;
186     CGDisplayFadeReservationToken token;
187     if (CGAcquireDisplayFadeReservation(.5, &token) == kCGErrorSuccess) {
188         CGDisplayFade(token, .25, kCGDisplayBlendNormal,
189             kCGDisplayBlendSolidColor, .0, .0, .0, true);
190         didBlend = YES;
191     }
193     // restore old vim view size
194     int currRows, currColumns;
195     [[view textView] getMaxRows:&currRows columns:&currColumns];
196     int newRows = currRows, newColumns = currColumns;
198     // compute desired non-fu size.
199     // if current fu size is equal to fu size at fu enter time,
200     // restore the old size
201     //
202     if (startFuFlags & FUOPT_MAXVERT && startFuRows == currRows)
203         newRows = nonFuRows;
205     if (startFuFlags & FUOPT_MAXHORZ && startFuColumns == currColumns)
206         newColumns = nonFuColumns;
208     // resize vim if necessary
209     if (currRows != newRows || currColumns != newColumns) {
210         int newSize[2] = { newRows, newColumns };
211         NSData *data = [NSData dataWithBytes:newSize length:2*sizeof(int)];
212         MMVimController *vimController =
213             [[self windowController] vimController];
215         [vimController sendMessage:SetTextDimensionsMsgID data:data];
216         [[view textView] setMaxRows:newRows columns:newColumns];
217     }
219     // fix up target controller
220     [self retain];  // NSWindowController releases us once
221     [[self windowController] setWindow:target];
223     [[view tabBarControl] setStyleNamed:oldTabBarStyle];
225     // fix delegate
226     id delegate = [self delegate];
227     [self setDelegate:nil];
228     
229     // move text view back to original window, hide fullscreen window,
230     // show original window
231     // do this _after_ resetting delegate and window controller, so the
232     // window controller doesn't get a focus lost message from the fullscreen
233     // window.
234     [view removeFromSuperviewWithoutNeedingDisplay];
235     [[target contentView] addSubview:view];
237     [view setFrameOrigin:oldPosition];
238     [self close];
240     // Set the text view to initial first responder, otherwise the 'plus'
241     // button on the tabline steals the first responder status.
242     [target setInitialFirstResponder:[view textView]];
244     [target makeKeyAndOrderFront:self];
246     // ...but we don't want a focus gained message either, so don't set this
247     // sooner
248     [target setDelegate:delegate];
250     // fade back in  
251     if (didBlend) {
252         CGDisplayFade(token, .25, kCGDisplayBlendSolidColor,
253             kCGDisplayBlendNormal, .0, .0, .0, false);
254         CGReleaseDisplayFadeReservation(token);
255     }
256     
257     [self revealDockIfAppropriate];
259     [self autorelease]; // Balance the above retain
262 // Title-less windows normally don't receive key presses, override this
263 - (BOOL)canBecomeKeyWindow
265     return YES;
268 // Title-less windows normally can't become main which means that another
269 // non-fullscreen window will have the "active" titlebar in expose. Bad, fix it.
270 - (BOOL)canBecomeMainWindow
272     return YES;
275 - (void)centerView
277     NSRect outer = [self frame], inner = [view frame];
278     //NSLog(@"%s %@%@", _cmd, NSStringFromRect(outer), NSStringFromRect(inner));
280     NSPoint origin = NSMakePoint((outer.size.width - inner.size.width)/2,
281                                  (outer.size.height - inner.size.height)/2);
282     [view setFrameOrigin:origin];
285 - (void)scrollWheel:(NSEvent *)theEvent
287     [[view textView] scrollWheel:theEvent];
290 - (void)performClose:(id)sender
292     id wc = [self windowController];
293     if ([wc respondsToSelector:@selector(performClose:)])
294         [wc performClose:sender];
295     else
296         [super performClose:sender];
299 @end // MMFullscreenWindow
304 @implementation MMFullscreenWindow (Private)
306 - (BOOL)isOnPrimaryScreen
308     // The primary screen is the screen the menu bar is on. This is different
309     // from [NSScreen mainScreen] (which returns the screen containing the
310     // key window).
311     NSArray *screens = [NSScreen screens];
312     if (screens == nil || [screens count] < 1)
313         return NO;
315     return [self screen] == [screens objectAtIndex:0];
318 - (void)hideDockIfAppropriate
320     // Hide menu and dock, both appear on demand.
321     //
322     // Don't hide the dock if going fullscreen on a non-primary screen. Also,
323     // if there are several fullscreen windows on the primary screen, only
324     // hide dock and friends for the first fullscreen window (and display
325     // them again after the last fullscreen window has been closed).
326     //
327     // Another way to deal with several fullscreen windows would be to hide/
328     // reveal the dock each time a fullscreen window gets/loses focus, but
329     // this way it's less distracting.
331     // XXX: If you have a fullscreen window on a secondary monitor and unplug
332     // the monitor, this will probably not work right.
334     if ([self isOnPrimaryScreen]) {
335         if (numFullscreenWindows == 0) {
336             SetSystemUIMode(kUIModeAllSuppressed, 0); //requires 10.3
337         }
338         ++numFullscreenWindows;
339     }
342 - (void)revealDockIfAppropriate
344      // order menu and dock back in
345     if ([self isOnPrimaryScreen]) {
346         --numFullscreenWindows;
347         if (numFullscreenWindows == 0) {
348             SetSystemUIMode(kUIModeNormal, 0);
349         }
350     }
353 @end // MMFullscreenWindow (Private)