Revert "Modifier key sends Esc" related commits
[MacVim.git] / src / MacVim / MMFullscreenWindow.m
blob8c445e538474f568dd69b0b8dbbb6cad0dfe1ed2
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 static int numFullscreenWindows = 0;
43 @interface MMFullscreenWindow (Private)
44 - (BOOL)isOnPrimaryScreen;
45 - (void)hideDockIfAppropriate;
46 - (void)revealDockIfAppropriate;
47 @end
49 @implementation MMFullscreenWindow
51 - (MMFullscreenWindow *)initWithWindow:(NSWindow *)t view:(MMVimView *)v 
52                                backgroundColor:(NSColor *)back
54     NSScreen* screen = [t screen];
56     // XXX: what if screen == nil?
58     // you can't change the style of an existing window in cocoa. create a new
59     // window and move the MMTextView into it.
60     // (another way would be to make the existing window large enough that the
61     // title bar is off screen. but that doesn't work with multiple screens).  
62     self = [super initWithContentRect:[screen frame]
63                             styleMask:NSBorderlessWindowMask
64                               backing:NSBackingStoreBuffered
65                                 defer:YES
66                                // since we're passing [screen frame] above,
67                                // we want the content rect to be relative to
68                                // the main screen (ie, pass nil for screen).
69                                screen:nil];
70       
71     if (self == nil)
72         return nil;
74     target = [t retain];
75     view = [v retain];
77     [self setHasShadow:NO];
78     [self setShowsResizeIndicator:NO];
79     [self setBackgroundColor:back];
80     [self setReleasedWhenClosed:NO];
82     return self;
85 - (void)dealloc
87     LOG_DEALLOC
89     [target release];  target = nil;
90     [view release];  view = nil;
92     [super dealloc];
95 - (void)enterFullscreen:(int)fuoptions
97     [self hideDockIfAppropriate];
99     // fade to black
100     Boolean didBlend = NO;
101     CGDisplayFadeReservationToken token;
102     if (CGAcquireDisplayFadeReservation(.5, &token) == kCGErrorSuccess) {
103         CGDisplayFade(token, .25, kCGDisplayBlendNormal,
104             kCGDisplayBlendSolidColor, .0, .0, .0, true);
105         didBlend = YES;
106     }
107     
108     // fool delegate
109     id delegate = [target delegate];
110     [target setDelegate:nil];
111     
112     // make target's window controller believe that it's now controlling us
113     [[target windowController] setWindow:self];
115     oldTabBarStyle = [[view tabBarControl] styleName];
116     [[view tabBarControl] setStyleNamed:@"Unified"];
118     // add text view
119     oldPosition = [view frame].origin;
121     [view removeFromSuperviewWithoutNeedingDisplay];
122     [[self contentView] addSubview:view];
123     [self setInitialFirstResponder:[view textView]];
124     
125     // NOTE: Calling setTitle:nil causes an exception to be raised (and it is
126     // possible that 'target' has no title when we get here).
127     if ([target title])
128         [self setTitle:[target title]];
130     [self setOpaque:[target isOpaque]];
132     // don't set this sooner, so we don't get an additional
133     // focus gained message  
134     [self setDelegate:delegate];
136     // resize vim view according to fuoptions
137     int currRows, currColumns;
138     [[view textView] getMaxRows:&currRows columns:&currColumns];
140     int fuRows = currRows, fuColumns = currColumns;
142     int maxRows, maxColumns;
143     NSSize size = [[self screen] visibleFrame].size;
144     [view constrainRows:&maxRows columns:&maxColumns toSize:size];
146     // Store current pre-fu vim size
147     nonFuRows = currRows;
148     nonFuColumns = currColumns;
150     // Compute current fu size
151     if (fuoptions & FUOPT_MAXVERT)
152         fuRows = maxRows;
153     if (fuoptions & FUOPT_MAXHORZ)
154         fuColumns = maxColumns;
156     startFuFlags = fuoptions;
158     // if necessary, resize vim to target fu size
159     if (currRows != fuRows || currColumns != fuColumns) {
161         // The size sent here is queued and sent to vim when it's in
162         // event processing mode again. Make sure to only send the values we
163         // care about, as they override any changes that were made to 'lines'
164         // and 'columns' after 'fu' was set but before the event loop is run.
165         NSData *data = nil;
166         int msgid = 0;
167         if (currRows != fuRows && currColumns != fuColumns) {
168             int newSize[2] = { fuRows, fuColumns };
169             data = [NSData dataWithBytes:newSize length:2*sizeof(int)];
170             msgid = SetTextDimensionsMsgID;
171         } else if (currRows != fuRows) {
172             data = [NSData dataWithBytes:&fuRows length:sizeof(int)];
173             msgid = SetTextRowsMsgID;
174         } else if (currColumns != fuColumns) {
175             data = [NSData dataWithBytes:&fuColumns length:sizeof(int)];
176             msgid = SetTextColumnsMsgID;
177         }
178         NSParameterAssert(data != nil && msgid != 0);
180         MMVimController *vimController =
181             [[self windowController] vimController];
183         [vimController sendMessage:msgid data:data];
184         [[view textView] setMaxRows:fuRows columns:fuColumns];
185     }
187     startFuRows = fuRows;
188     startFuColumns = fuColumns;
190     // move vim view to the window's center
191     [self centerView];
193     // make us visible and target invisible
194     [target orderOut:self];
195     [self makeKeyAndOrderFront:self];
197     // fade back in
198     if (didBlend) {
199         CGDisplayFade(token, .25, kCGDisplayBlendSolidColor,
200             kCGDisplayBlendNormal, .0, .0, .0, false);
201         CGReleaseDisplayFadeReservation(token);
202     }
205 - (void)leaveFullscreen
207     // fade to black
208     Boolean didBlend = NO;
209     CGDisplayFadeReservationToken token;
210     if (CGAcquireDisplayFadeReservation(.5, &token) == kCGErrorSuccess) {
211         CGDisplayFade(token, .25, kCGDisplayBlendNormal,
212             kCGDisplayBlendSolidColor, .0, .0, .0, true);
213         didBlend = YES;
214     }
216     // restore old vim view size
217     int currRows, currColumns;
218     [[view textView] getMaxRows:&currRows columns:&currColumns];
219     int newRows = currRows, newColumns = currColumns;
221     // compute desired non-fu size.
222     // if current fu size is equal to fu size at fu enter time,
223     // restore the old size
224     //
225     if (startFuFlags & FUOPT_MAXVERT && startFuRows == currRows)
226         newRows = nonFuRows;
228     if (startFuFlags & FUOPT_MAXHORZ && startFuColumns == currColumns)
229         newColumns = nonFuColumns;
231     // resize vim if necessary
232     if (currRows != newRows || currColumns != newColumns) {
233         int newSize[2] = { newRows, newColumns };
234         NSData *data = [NSData dataWithBytes:newSize length:2*sizeof(int)];
235         MMVimController *vimController =
236             [[self windowController] vimController];
238         [vimController sendMessage:SetTextDimensionsMsgID data:data];
239         [[view textView] setMaxRows:newRows columns:newColumns];
240     }
242     // fix up target controller
243     [self retain];  // NSWindowController releases us once
244     [[self windowController] setWindow:target];
246     [[view tabBarControl] setStyleNamed:oldTabBarStyle];
248     // fix delegate
249     id delegate = [self delegate];
250     [self setDelegate:nil];
251     
252     // move text view back to original window, hide fullscreen window,
253     // show original window
254     // do this _after_ resetting delegate and window controller, so the
255     // window controller doesn't get a focus lost message from the fullscreen
256     // window.
257     [view removeFromSuperviewWithoutNeedingDisplay];
258     [[target contentView] addSubview:view];
260     [view setFrameOrigin:oldPosition];
261     [self close];
263     // Set the text view to initial first responder, otherwise the 'plus'
264     // button on the tabline steals the first responder status.
265     [target setInitialFirstResponder:[view textView]];
267     [target makeKeyAndOrderFront:self];
269     // ...but we don't want a focus gained message either, so don't set this
270     // sooner
271     [target setDelegate:delegate];
273     // fade back in  
274     if (didBlend) {
275         CGDisplayFade(token, .25, kCGDisplayBlendSolidColor,
276             kCGDisplayBlendNormal, .0, .0, .0, false);
277         CGReleaseDisplayFadeReservation(token);
278     }
279     
280     [self revealDockIfAppropriate];
282     [self autorelease]; // Balance the above retain
285 // Title-less windows normally don't receive key presses, override this
286 - (BOOL)canBecomeKeyWindow
288     return YES;
291 // Title-less windows normally can't become main which means that another
292 // non-fullscreen window will have the "active" titlebar in expose. Bad, fix it.
293 - (BOOL)canBecomeMainWindow
295     return YES;
298 - (void)centerView
300     NSRect outer = [self frame], inner = [view frame];
301     //NSLog(@"%s %@%@", _cmd, NSStringFromRect(outer), NSStringFromRect(inner));
303     NSPoint origin = NSMakePoint((outer.size.width - inner.size.width)/2,
304                                  (outer.size.height - inner.size.height)/2);
305     [view setFrameOrigin:origin];
308 - (void)scrollWheel:(NSEvent *)theEvent
310     [[view textView] scrollWheel:theEvent];
313 - (void)performClose:(id)sender
315     id wc = [self windowController];
316     if ([wc respondsToSelector:@selector(performClose:)])
317         [wc performClose:sender];
318     else
319         [super performClose:sender];
322 - (BOOL)validateMenuItem:(NSMenuItem *)item
324     if ([item action] == @selector(vimMenuItemAction:)
325             || [item action] == @selector(performClose:))
326         return [item tag];
328     return YES;
331 @end // MMFullscreenWindow
336 @implementation MMFullscreenWindow (Private)
338 - (BOOL)isOnPrimaryScreen
340     // The primary screen is the screen the menu bar is on. This is different
341     // from [NSScreen mainScreen] (which returns the screen containing the
342     // key window).
343     NSArray *screens = [NSScreen screens];
344     if (screens == nil || [screens count] < 1)
345         return NO;
347     return [self screen] == [screens objectAtIndex:0];
350 - (void)hideDockIfAppropriate
352     // Hide menu and dock, both appear on demand.
353     //
354     // Don't hide the dock if going fullscreen on a non-primary screen. Also,
355     // if there are several fullscreen windows on the primary screen, only
356     // hide dock and friends for the first fullscreen window (and display
357     // them again after the last fullscreen window has been closed).
358     //
359     // Another way to deal with several fullscreen windows would be to hide/
360     // reveal the dock each time a fullscreen window gets/loses focus, but
361     // this way it's less distracting.
363     // XXX: If you have a fullscreen window on a secondary monitor and unplug
364     // the monitor, this will probably not work right.
366     if ([self isOnPrimaryScreen]) {
367         if (numFullscreenWindows == 0) {
368             SetSystemUIMode(kUIModeAllSuppressed, 0); //requires 10.3
369         }
370         ++numFullscreenWindows;
371     }
374 - (void)revealDockIfAppropriate
376      // order menu and dock back in
377     if ([self isOnPrimaryScreen]) {
378         --numFullscreenWindows;
379         if (numFullscreenWindows == 0) {
380             SetSystemUIMode(kUIModeNormal, 0);
381         }
382     }
385 @end // MMFullscreenWindow (Private)