Fix full-screen mode for secondary monitors
[MacVim.git] / src / MacVim / MMFullscreenWindow.m
blobf2f20808364b7195bb90c444ae19dbd611ab7872
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  * MMFullscreen
12  *
13  * Support for full-screen editing.
14  *
15  * Author: Nico Weber
16  */
18 #import "MMFullscreenWindow.h"
19 #import <PSMTabBarControl.h>
20 #import "MMVimView.h"
21 #import "MMTextView.h"
22 #import "MMWindowController.h"
23 #import <Carbon/Carbon.h>
26 static int numFullscreenWindows = 0;
28 @interface MMFullscreenWindow (Private)
29 - (void)centerView;
30 - (BOOL)isOnPrimaryScreen;
31 - (void)hideDockIfAppropriate;
32 - (void)revealDockIfAppropriate;
33 @end
35 @implementation MMFullscreenWindow
37 - (MMFullscreenWindow *)initWithWindow:(NSWindow *)t view:(MMVimView *)v
39     NSScreen* screen = [t screen];
41     // XXX: what if screen == nil?
43     // you can't change the style of an existing window in cocoa. create a new
44     // window and move the MMTextView into it.
45     // (another way would be to make the existing window large enough that the
46     // title bar is off screen. but that doesn't work with multiple screens).  
47     self = [super initWithContentRect:[screen frame]
48                             styleMask:NSBorderlessWindowMask
49                               backing:NSBackingStoreBuffered
50                                 defer:YES
51                                // since we're passing [screen frame] above,
52                                // we want the content rect to be relative to
53                                // the main screen (ie, pass nil for screen).
54                                screen:nil];
55       
56     if (self == nil)
57         return nil;
59     [self setHasShadow:NO];
60     [self setShowsResizeIndicator:NO];
61     [self setBackgroundColor:[NSColor blackColor]];
62     [self setReleasedWhenClosed:NO];
64     target = t;  [target retain];
65     view = v;  [view retain];
67     return self;
70 - (void)dealloc
72     [target release];
73     [view release];
75     [super dealloc];
78 - (void)enterFullscreen
80     [self hideDockIfAppropriate];
82     // fade to black
83     Boolean didBlend = NO;
84     CGDisplayFadeReservationToken token;
85     if (CGAcquireDisplayFadeReservation(.5, &token) == kCGErrorSuccess) {
86         CGDisplayFade(token, .25, kCGDisplayBlendNormal,
87             kCGDisplayBlendSolidColor, .0, .0, .0, true);
88         didBlend = YES;
89     }
90     
91     // fool delegate
92     id delegate = [target delegate];
93     [target setDelegate:nil];
94     
95     // make target's window controller believe that it's now controlling us
96     [target retain];  // NSWindowController will release target once in the
97                       // in the next line
98     [[target windowController] setWindow:self];
101     oldTabBarStyle = [[view tabBarControl] styleName];
102     [[view tabBarControl] setStyleNamed:@"Unified"];
104     // add text view
105     oldPosition = [view frame].origin;
107     [[self contentView] addSubview:view];
108     [self setInitialFirstResponder:[view textView]];
109     
110     [self setTitle:[target title]];
111     [self setOpaque:[target isOpaque]];
113     // don't set this sooner, so we don't get an additional
114     // focus gained message  
115     [self setDelegate:delegate];
117     // update bottom right corner scrollbar (no resize handle in fu mode)
118     [view placeViews];
120     // move vim view to the window's center
121     [self centerView];
123     // make us visible and target invisible
124     [target orderOut:self];
125     [self makeKeyAndOrderFront:self];
127     // fade back in
128     if (didBlend) {
129         CGDisplayFade(token, .25, kCGDisplayBlendSolidColor,
130             kCGDisplayBlendNormal, .0, .0, .0, false);
131         CGReleaseDisplayFadeReservation(token);
132     }
135 - (void)leaveFullscreen
137     // fade to black
138     Boolean didBlend = NO;
139     CGDisplayFadeReservationToken token;
140     if (CGAcquireDisplayFadeReservation(.5, &token) == kCGErrorSuccess) {
141         CGDisplayFade(token, .25, kCGDisplayBlendNormal,
142             kCGDisplayBlendSolidColor, .0, .0, .0, true);
143         didBlend = YES;
144     }
146     // fix up target controller
147     [self retain];  // NSWindowController releases us once
148     [[self windowController] setWindow:target];
150     [[view tabBarControl] setStyleNamed:oldTabBarStyle];
152     // fix delegate
153     id delegate = [self delegate];
154     [self setDelegate:nil];
155     
156     // move text view back to original window, hide fullscreen window,
157     // show original window
158     // do this _after_ resetting delegate and window controller, so the
159     // window controller doesn't get a focus lost message from the fullscreen
160     // window.
161     [[target contentView] addSubview:view];
162     [view setFrameOrigin:oldPosition];
163     [self close];
164     [target makeKeyAndOrderFront:self];
166     // ...but we don't want a focus gained message either, so don't set this
167     // sooner
168     [target setDelegate:delegate];
170     // update bottom right corner scrollbar (resize handle reappears)
171     [view placeViews];
173     // fade back in  
174     if (didBlend) {
175         CGDisplayFade(token, .25, kCGDisplayBlendSolidColor,
176             kCGDisplayBlendNormal, .0, .0, .0, false);
177         CGReleaseDisplayFadeReservation(token);
178     }
179     
180     [self revealDockIfAppropriate];
183 // Title-less windows normally don't receive key presses, override this
184 - (BOOL)canBecomeKeyWindow
186     return YES;
189 // Title-less windows normally can't become main which means that another
190 // non-fullscreen window will have the "active" titlebar in expose. Bad, fix it.
191 - (BOOL)canBecomeMainWindow
193     return YES;
197 #pragma mark Proxy/Decorator/whatever stuff
199 - (void)scrollWheel:(NSEvent *)theEvent
201     [[view textView] scrollWheel:theEvent];
204 // the window controller will send us messages that are meant for the original,
205 // non-fullscreen window. forward those, and interpret the messages that are
206 // interesting for us
208 - (void)setTitle:(NSString *)title
210     [target setTitle:title];
211     [super setTitle:title];
214 // HACK: if the T flag in guioptions is changed in fu mode, the toolbar needs
215 // to be changed when nofu is set. MMWindowController gets the toolbar object,
216 // so we need to return a toolbar from this method, even if none is visible for
217 // the fullscreen window. Seems to work, though.
218 - (NSToolbar *)toolbar
220     return [target toolbar];
223 - (void)setFrame:(NSRect)frame display:(BOOL)display
225     // HACK: if the target window would resize, we have to call our own
226     // windowDidResize method so that placeViews in MMWindowController is called
227     if (!NSEqualRects(frame, [target frame]))
228     {
229         [target setFrame:frame display:NO];
231         // XXX: send this directly to MMVimView
232         if ([[self delegate] respondsToSelector:@selector(windowDidResize:)])
233           [[self delegate] windowDidResize:nil];
235         [self centerView];
236         [self display];
237     }
240 /*- (NSRect)frame
242     return [target frame];  // really? needed by MMWindowController placeViews.
243                             //  but mucks up display
246 - (NSRect)contentRectForFrameRect:(NSRect)rect
248     //return [target contentRectForFrameRect:rect];
249     
250     // EVIL HACK: if this is always called with [[self window] frame] as
251     // argument from MMWindowController, we can't let frame return the frame
252     // of target so "fix" this here.
253     if (NSEqualRects([self frame], rect)) {
254         return [target contentRectForFrameRect:[target frame]];
255     } else {
256         return [target contentRectForFrameRect:rect];
257     }
260 - (NSRect)frameRectForContentRect:(NSRect)contentRect
262     return [target frameRectForContentRect:contentRect];
265 - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen*)screen
267     return [target constrainFrameRect:frameRect toScreen:screen];
270 - (void)setContentResizeIncrements:(NSSize)size
272     [target setContentResizeIncrements:size];
275 - (void)setOpaque:(BOOL)isOpaque
277     // XXX: Do we want transparency even in fullscreen mode?
278     [super setOpaque:isOpaque];
279     [target setOpaque:isOpaque];
282 @end // MMFullscreenWindow
287 @implementation MMFullscreenWindow (Private)
289 - (void)centerView
291     NSRect outer = [self frame], inner = [view frame];
292     //NSLog(@"%s %@%@", _cmd, NSStringFromRect(outer), NSStringFromRect(inner));
294     NSPoint origin = NSMakePoint((outer.size.width - inner.size.width)/2,
295                                  (outer.size.height - inner.size.height)/2);
296     [view setFrameOrigin:origin];
299 - (BOOL)isOnPrimaryScreen
301     // The primary screen is the screen the menu bar is on. This is different
302     // from [NSScreen mainScreen] (which returns the screen containing the
303     // key window).
304     NSArray *screens = [NSScreen screens];
305     if (screens == nil || [screens count] < 1)
306         return NO;
308     return [self screen] == [screens objectAtIndex:0];
311 - (void)hideDockIfAppropriate
313     // Hide menu and dock, both appear on demand.
314     //
315     // Don't hide the dock if going fullscreen on a non-primary screen. Also,
316     // if there are several fullscreen windows on the primary screen, only
317     // hide dock and friends for the first fullscreen window (and display
318     // them again after the last fullscreen window has been closed).
319     //
320     // Another way to deal with several fullscreen windows would be to hide/
321     // reveal the dock each time a fullscreen window gets/loses focus, but
322     // this way it's less distracting.
324     // XXX: If you have a fullscreen window on a secondary monitor and unplug
325     // the monitor, this will probably not work right.
327     if ([self isOnPrimaryScreen]) {
328         if (numFullscreenWindows == 0) {
329             SetSystemUIMode(kUIModeAllSuppressed, 0); //requires 10.3
330         }
331         ++numFullscreenWindows;
332     }
335 - (void)revealDockIfAppropriate
337      // order menu and dock back in
338     if ([self isOnPrimaryScreen]) {
339         --numFullscreenWindows;
340         if (numFullscreenWindows == 0) {
341             SetSystemUIMode(kUIModeNormal, 0);
342         }
343     }
346 @end // MMFullscreenWindow (Private)