Dialogs are always displayed in the default run loop mode
[MacVim.git] / src / MacVim / MMFullscreenWindow.m
blob51a82e312be6eb931ce64b57f4d57aa27ddc6bc3
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 <Carbon/Carbon.h>
32 #import <PSMTabBarControl.h>
34 // These have to be the same as in option.h
35 #define FUOPT_MAXVERT         0x001
36 #define FUOPT_MAXHORZ         0x002
37 #define FUOPT_BGCOLOR_HLGROUP 0x004
40 static int numFullscreenWindows = 0;
42 @interface MMFullscreenWindow (Private)
43 - (BOOL)isOnPrimaryScreen;
44 - (void)hideDockIfAppropriate;
45 - (void)revealDockIfAppropriate;
46 @end
48 @implementation MMFullscreenWindow
50 - (MMFullscreenWindow *)initWithWindow:(NSWindow *)t view:(MMVimView *)v 
51                                backgroundColor:(NSColor *)back
53     NSScreen* screen = [t screen];
55     // XXX: what if screen == nil?
57     // you can't change the style of an existing window in cocoa. create a new
58     // window and move the MMTextView into it.
59     // (another way would be to make the existing window large enough that the
60     // title bar is off screen. but that doesn't work with multiple screens).  
61     self = [super initWithContentRect:[screen frame]
62                             styleMask:NSBorderlessWindowMask
63                               backing:NSBackingStoreBuffered
64                                 defer:YES
65                                // since we're passing [screen frame] above,
66                                // we want the content rect to be relative to
67                                // the main screen (ie, pass nil for screen).
68                                screen:nil];
69       
70     if (self == nil)
71         return nil;
73     target = [t retain];
74     view = [v retain];
76     [self setHasShadow:NO];
77     [self setShowsResizeIndicator:NO];
78     [self setBackgroundColor:back];
79     [self setReleasedWhenClosed:NO];
81     return self;
84 - (void)dealloc
86     [target release];  target = nil;
87     [view release];  view = nil;
89     [super dealloc];
92 - (void)enterFullscreen:(int)fuoptions
94     [self hideDockIfAppropriate];
96     // fade to black
97     Boolean didBlend = NO;
98     CGDisplayFadeReservationToken token;
99     if (CGAcquireDisplayFadeReservation(.5, &token) == kCGErrorSuccess) {
100         CGDisplayFade(token, .25, kCGDisplayBlendNormal,
101             kCGDisplayBlendSolidColor, .0, .0, .0, true);
102         didBlend = YES;
103     }
104     
105     // fool delegate
106     id delegate = [target delegate];
107     [target setDelegate:nil];
108     
109     // make target's window controller believe that it's now controlling us
110     [[target windowController] setWindow:self];
112     oldTabBarStyle = [[view tabBarControl] styleName];
113     [[view tabBarControl] setStyleNamed:@"Unified"];
115     // add text view
116     oldPosition = [view frame].origin;
118     [view removeFromSuperviewWithoutNeedingDisplay];
119     [[self contentView] addSubview:view];
120     [self setInitialFirstResponder:[view textView]];
121     
122     // NOTE: Calling setTitle:nil causes an exception to be raised (and it is
123     // possible that 'target' has no title when we get here).
124     if ([target title])
125         [self setTitle:[target title]];
127     [self setOpaque:[target isOpaque]];
129     // don't set this sooner, so we don't get an additional
130     // focus gained message  
131     [self setDelegate:delegate];
133     // resize vim view according to fuoptions
134     int currRows, currColumns;
135     [[view textView] getMaxRows:&currRows columns:&currColumns];
137     int fuRows = currRows, fuColumns = currColumns;
139     int maxRows, maxColumns;
140     NSSize size = [[self screen] visibleFrame].size;
141     [view constrainRows:&maxRows columns:&maxColumns toSize:size];
143     // Store current pre-fu vim size
144     nonFuRows = currRows;
145     nonFuColumns = currColumns;
147     // Compute current fu size
148     if (fuoptions & FUOPT_MAXVERT)
149         fuRows = maxRows;
150     if (fuoptions & FUOPT_MAXHORZ)
151         fuColumns = maxColumns;
153     startFuFlags = fuoptions;
155     // if necessary, resize vim to target fu size
156     if (currRows != fuRows || currColumns != fuColumns) {
157         int newSize[2] = { fuRows, fuColumns };
158         NSData *data = [NSData dataWithBytes:newSize length:2*sizeof(int)];
159         MMVimController *vimController =
160             [[self windowController] vimController];
162         [vimController sendMessage:SetTextDimensionsMsgID data:data];
163         [[view textView] setMaxRows:fuRows columns:fuColumns];
164     }
166     startFuRows = fuRows;
167     startFuColumns = fuColumns;
169     // move vim view to the window's center
170     [self centerView];
172     // make us visible and target invisible
173     [target orderOut:self];
174     [self makeKeyAndOrderFront:self];
176     // fade back in
177     if (didBlend) {
178         CGDisplayFade(token, .25, kCGDisplayBlendSolidColor,
179             kCGDisplayBlendNormal, .0, .0, .0, false);
180         CGReleaseDisplayFadeReservation(token);
181     }
184 - (void)leaveFullscreen
186     // fade to black
187     Boolean didBlend = NO;
188     CGDisplayFadeReservationToken token;
189     if (CGAcquireDisplayFadeReservation(.5, &token) == kCGErrorSuccess) {
190         CGDisplayFade(token, .25, kCGDisplayBlendNormal,
191             kCGDisplayBlendSolidColor, .0, .0, .0, true);
192         didBlend = YES;
193     }
195     // restore old vim view size
196     int currRows, currColumns;
197     [[view textView] getMaxRows:&currRows columns:&currColumns];
198     int newRows = currRows, newColumns = currColumns;
200     // compute desired non-fu size.
201     // if current fu size is equal to fu size at fu enter time,
202     // restore the old size
203     //
204     if (startFuFlags & FUOPT_MAXVERT && startFuRows == currRows)
205         newRows = nonFuRows;
207     if (startFuFlags & FUOPT_MAXHORZ && startFuColumns == currColumns)
208         newColumns = nonFuColumns;
210     // resize vim if necessary
211     if (currRows != newRows || currColumns != newColumns) {
212         int newSize[2] = { newRows, newColumns };
213         NSData *data = [NSData dataWithBytes:newSize length:2*sizeof(int)];
214         MMVimController *vimController =
215             [[self windowController] vimController];
217         [vimController sendMessage:SetTextDimensionsMsgID data:data];
218         [[view textView] setMaxRows:newRows columns:newColumns];
219     }
221     // fix up target controller
222     [self retain];  // NSWindowController releases us once
223     [[self windowController] setWindow:target];
225     [[view tabBarControl] setStyleNamed:oldTabBarStyle];
227     // fix delegate
228     id delegate = [self delegate];
229     [self setDelegate:nil];
230     
231     // move text view back to original window, hide fullscreen window,
232     // show original window
233     // do this _after_ resetting delegate and window controller, so the
234     // window controller doesn't get a focus lost message from the fullscreen
235     // window.
236     [view removeFromSuperviewWithoutNeedingDisplay];
237     [[target contentView] addSubview:view];
239     [view setFrameOrigin:oldPosition];
240     [self close];
242     // Set the text view to initial first responder, otherwise the 'plus'
243     // button on the tabline steals the first responder status.
244     [target setInitialFirstResponder:[view textView]];
246     [target makeKeyAndOrderFront:self];
248     // ...but we don't want a focus gained message either, so don't set this
249     // sooner
250     [target setDelegate:delegate];
252     // fade back in  
253     if (didBlend) {
254         CGDisplayFade(token, .25, kCGDisplayBlendSolidColor,
255             kCGDisplayBlendNormal, .0, .0, .0, false);
256         CGReleaseDisplayFadeReservation(token);
257     }
258     
259     [self revealDockIfAppropriate];
261     [self autorelease]; // Balance the above retain
264 // Title-less windows normally don't receive key presses, override this
265 - (BOOL)canBecomeKeyWindow
267     return YES;
270 // Title-less windows normally can't become main which means that another
271 // non-fullscreen window will have the "active" titlebar in expose. Bad, fix it.
272 - (BOOL)canBecomeMainWindow
274     return YES;
277 - (void)centerView
279     NSRect outer = [self frame], inner = [view frame];
280     //NSLog(@"%s %@%@", _cmd, NSStringFromRect(outer), NSStringFromRect(inner));
282     NSPoint origin = NSMakePoint((outer.size.width - inner.size.width)/2,
283                                  (outer.size.height - inner.size.height)/2);
284     [view setFrameOrigin:origin];
287 - (void)scrollWheel:(NSEvent *)theEvent
289     [[view textView] scrollWheel:theEvent];
292 - (void)performClose:(id)sender
294     id wc = [self windowController];
295     if ([wc respondsToSelector:@selector(performClose:)])
296         [wc performClose:sender];
297     else
298         [super performClose:sender];
301 - (BOOL)validateMenuItem:(NSMenuItem *)item
303     if ([item action] == @selector(vimMenuItemAction:)
304             || [item action] == @selector(performClose:))
305         return [item tag];
307     return YES;
310 @end // MMFullscreenWindow
315 @implementation MMFullscreenWindow (Private)
317 - (BOOL)isOnPrimaryScreen
319     // The primary screen is the screen the menu bar is on. This is different
320     // from [NSScreen mainScreen] (which returns the screen containing the
321     // key window).
322     NSArray *screens = [NSScreen screens];
323     if (screens == nil || [screens count] < 1)
324         return NO;
326     return [self screen] == [screens objectAtIndex:0];
329 - (void)hideDockIfAppropriate
331     // Hide menu and dock, both appear on demand.
332     //
333     // Don't hide the dock if going fullscreen on a non-primary screen. Also,
334     // if there are several fullscreen windows on the primary screen, only
335     // hide dock and friends for the first fullscreen window (and display
336     // them again after the last fullscreen window has been closed).
337     //
338     // Another way to deal with several fullscreen windows would be to hide/
339     // reveal the dock each time a fullscreen window gets/loses focus, but
340     // this way it's less distracting.
342     // XXX: If you have a fullscreen window on a secondary monitor and unplug
343     // the monitor, this will probably not work right.
345     if ([self isOnPrimaryScreen]) {
346         if (numFullscreenWindows == 0) {
347             SetSystemUIMode(kUIModeAllSuppressed, 0); //requires 10.3
348         }
349         ++numFullscreenWindows;
350     }
353 - (void)revealDockIfAppropriate
355      // order menu and dock back in
356     if ([self isOnPrimaryScreen]) {
357         --numFullscreenWindows;
358         if (numFullscreenWindows == 0) {
359             SetSystemUIMode(kUIModeNormal, 0);
360         }
361     }
364 @end // MMFullscreenWindow (Private)