Home | History | Annotate | Download | only in WebView
      1 /*
      2  * Copyright (C) 2009 Apple Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
     14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
     15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
     17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
     23  * THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #if ENABLE(VIDEO)
     27 
     28 #import "WebVideoFullscreenController.h"
     29 
     30 #import "WebTypesInternal.h"
     31 #import "WebVideoFullscreenHUDWindowController.h"
     32 #import "WebWindowAnimation.h"
     33 #import <QTKit/QTKit.h>
     34 #import <WebCore/HTMLMediaElement.h>
     35 #import <WebCore/SoftLinking.h>
     36 #import <objc/objc-runtime.h>
     37 #import <wtf/UnusedParam.h>
     38 
     39 SOFT_LINK_FRAMEWORK(QTKit)
     40 SOFT_LINK_CLASS(QTKit, QTMovieView)
     41 
     42 SOFT_LINK_POINTER(QTKit, QTMovieRateDidChangeNotification, NSString *)
     43 
     44 #define QTMovieRateDidChangeNotification getQTMovieRateDidChangeNotification()
     45 
     46 @interface WebVideoFullscreenWindow : NSWindow
     47 #if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_TIGER)
     48 <NSAnimationDelegate>
     49 #endif
     50 {
     51     SEL _controllerActionOnAnimationEnd;
     52     WebWindowScaleAnimation *_fullscreenAnimation; // (retain)
     53 }
     54 - (void)animateFromRect:(NSRect)startRect toRect:(NSRect)endRect withSubAnimation:(NSAnimation *)subAnimation controllerAction:(SEL)controllerAction;
     55 @end
     56 
     57 @interface WebVideoFullscreenController(HUDWindowControllerDelegate) <WebVideoFullscreenHUDWindowControllerDelegate>
     58 @end
     59 
     60 @implementation WebVideoFullscreenController
     61 - (id)init
     62 {
     63     // Do not defer window creation, to make sure -windowNumber is created (needed by WebWindowScaleAnimation).
     64     NSWindow *window = [[WebVideoFullscreenWindow alloc] initWithContentRect:NSZeroRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
     65     self = [super initWithWindow:window];
     66     [window release];
     67     if (!self)
     68         return nil;
     69     [self windowDidLoad];
     70     return self;
     71 
     72 }
     73 - (void)dealloc
     74 {
     75     ASSERT(!_backgroundFullscreenWindow);
     76     ASSERT(!_fadeAnimation);
     77     [[NSNotificationCenter defaultCenter] removeObserver:self];
     78     [super dealloc];
     79 }
     80 
     81 - (WebVideoFullscreenWindow *)fullscreenWindow
     82 {
     83     return (WebVideoFullscreenWindow *)[super window];
     84 }
     85 
     86 - (void)windowDidLoad
     87 {
     88     WebVideoFullscreenWindow *window = [self fullscreenWindow];
     89     QTMovieView *view = [[getQTMovieViewClass() alloc] init];
     90     [view setFillColor:[NSColor clearColor]];
     91     [window setContentView:view];
     92     [view setControllerVisible:NO];
     93     [view setPreservesAspectRatio:YES];
     94     if (_mediaElement)
     95         [view setMovie:_mediaElement->platformMedia().qtMovie];
     96     [window setHasShadow:YES]; // This is nicer with a shadow.
     97     [window setLevel:NSPopUpMenuWindowLevel-1];
     98     [view release];
     99 }
    100 
    101 - (WebCore::HTMLMediaElement*)mediaElement;
    102 {
    103     return _mediaElement.get();
    104 }
    105 
    106 - (void)setMediaElement:(WebCore::HTMLMediaElement*)mediaElement;
    107 {
    108     _mediaElement = mediaElement;
    109     if ([self isWindowLoaded]) {
    110         QTMovieView *movieView = (QTMovieView *)[[self fullscreenWindow] contentView];
    111         QTMovie *movie = _mediaElement->platformMedia().qtMovie;
    112 
    113         ASSERT(movieView && [movieView isKindOfClass:[getQTMovieViewClass() class]]);
    114         ASSERT(movie);
    115         [movieView setMovie:movie];
    116         [[NSNotificationCenter defaultCenter] addObserver:self
    117                                                  selector:@selector(rateChanged:)
    118                                                      name:QTMovieRateDidChangeNotification
    119                                                    object:movie];
    120     }
    121 }
    122 
    123 - (id <WebVideoFullscreenControllerDelegate>)delegate
    124 {
    125     return _delegate;
    126 }
    127 
    128 - (void)setDelegate:(id <WebVideoFullscreenControllerDelegate>)delegate;
    129 {
    130     _delegate = delegate;
    131 }
    132 
    133 - (CGFloat)clearFadeAnimation
    134 {
    135     [_fadeAnimation stopAnimation];
    136     CGFloat previousAlpha = [_fadeAnimation currentAlpha];
    137     [_fadeAnimation setWindow:nil];
    138     [_fadeAnimation release];
    139     _fadeAnimation = nil;
    140     return previousAlpha;
    141 }
    142 
    143 - (void)windowDidExitFullscreen
    144 {
    145     [self clearFadeAnimation];
    146     [[self window] close];
    147     [self setWindow:nil];
    148     SetSystemUIMode(_savedUIMode, _savedUIOptions);
    149     [_hudController setDelegate:nil];
    150     [_hudController release];
    151     _hudController = nil;
    152     [_backgroundFullscreenWindow close];
    153     [_backgroundFullscreenWindow release];
    154     _backgroundFullscreenWindow = nil;
    155 
    156     [self autorelease]; // Associated -retain is in -exitFullscreen.
    157     _isEndingFullscreen = NO;
    158 }
    159 
    160 - (void)windowDidEnterFullscreen
    161 {
    162     [self clearFadeAnimation];
    163 
    164     ASSERT(!_hudController);
    165     _hudController = [[WebVideoFullscreenHUDWindowController alloc] init];
    166     [_hudController setDelegate:self];
    167 
    168     GetSystemUIMode(&_savedUIMode, &_savedUIOptions);
    169     SetSystemUIMode(kUIModeAllSuppressed , 0);
    170     [NSCursor setHiddenUntilMouseMoves:YES];
    171 
    172     // Give the HUD keyboard focus initially
    173     [_hudController fadeWindowIn];
    174 }
    175 
    176 - (NSRect)mediaElementRect
    177 {
    178     return _mediaElement->screenRect();
    179 }
    180 
    181 #pragma mark -
    182 #pragma mark Exposed Interface
    183 
    184 static void constrainFrameToRatioOfFrame(NSRect *frameToConstrain, const NSRect *frame)
    185 {
    186     // Keep a constrained aspect ratio for the destination window
    187     double originalRatio = frame->size.width / frame->size.height;
    188     double newRatio = frameToConstrain->size.width / frameToConstrain->size.height;
    189     if (newRatio > originalRatio) {
    190         double newWidth = originalRatio * frameToConstrain->size.height;
    191         double diff = frameToConstrain->size.width - newWidth;
    192         frameToConstrain->size.width = newWidth;
    193         frameToConstrain->origin.x += diff / 2;
    194     } else {
    195         double newHeight = frameToConstrain->size.width / originalRatio;
    196         double diff = frameToConstrain->size.height - newHeight;
    197         frameToConstrain->size.height = newHeight;
    198         frameToConstrain->origin.y += diff / 2;
    199     }
    200 }
    201 
    202 static NSWindow *createBackgroundFullscreenWindow(NSRect frame, int level)
    203 {
    204     NSWindow *window = [[NSWindow alloc] initWithContentRect:frame styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
    205     [window setOpaque:YES];
    206     [window setBackgroundColor:[NSColor blackColor]];
    207     [window setLevel:level];
    208     [window setHidesOnDeactivate:YES];
    209     [window setReleasedWhenClosed:NO];
    210     return window;
    211 }
    212 
    213 - (void)setupFadeAnimationIfNeededAndFadeIn:(BOOL)fadeIn
    214 {
    215     CGFloat initialAlpha = fadeIn ? 0 : 1;
    216     if (_fadeAnimation) {
    217         // Make sure we support queuing animation if the previous one isn't over yet
    218         initialAlpha = [self clearFadeAnimation];
    219     }
    220     if (!_forceDisableAnimation)
    221         _fadeAnimation = [[WebWindowFadeAnimation alloc] initWithDuration:0.2 window:_backgroundFullscreenWindow initialAlpha:initialAlpha finalAlpha:fadeIn ? 1 : 0];
    222 }
    223 
    224 - (void)enterFullscreen:(NSScreen *)screen;
    225 {
    226     if (!screen)
    227         screen = [NSScreen mainScreen];
    228 
    229     NSRect frame = [self mediaElementRect];
    230     NSRect endFrame = [screen frame];
    231     constrainFrameToRatioOfFrame(&endFrame, &frame);
    232 
    233     // Create a black window if needed
    234     if (!_backgroundFullscreenWindow)
    235         _backgroundFullscreenWindow = createBackgroundFullscreenWindow([screen frame], [[self window] level]-1);
    236     else
    237         [_backgroundFullscreenWindow setFrame:[screen frame] display:NO];
    238 
    239     [self setupFadeAnimationIfNeededAndFadeIn:YES];
    240     if (_forceDisableAnimation) {
    241         // This will disable scale animation
    242         frame = NSZeroRect;
    243     }
    244     [[self fullscreenWindow] animateFromRect:frame toRect:endFrame withSubAnimation:_fadeAnimation controllerAction:@selector(windowDidEnterFullscreen)];
    245 
    246     [_backgroundFullscreenWindow orderWindow:NSWindowBelow relativeTo:[[self fullscreenWindow] windowNumber]];
    247 }
    248 
    249 - (void)exitFullscreen
    250 {
    251     if (_isEndingFullscreen)
    252         return;
    253     _isEndingFullscreen = YES;
    254     [_hudController closeWindow];
    255 
    256     NSRect endFrame = [self mediaElementRect];
    257 
    258     [self setupFadeAnimationIfNeededAndFadeIn:NO];
    259     if (_forceDisableAnimation) {
    260         // This will disable scale animation
    261         endFrame = NSZeroRect;
    262     }
    263 
    264     // We have to retain ourselves because we want to be alive for the end of the animation.
    265     // If our owner releases us we could crash if this is not the case.
    266     // Balanced in windowDidExitFullscreen
    267     [self retain];
    268 
    269     [[self fullscreenWindow] animateFromRect:[[self window] frame] toRect:endFrame withSubAnimation:_fadeAnimation controllerAction:@selector(windowDidExitFullscreen)];
    270 }
    271 
    272 #pragma mark -
    273 #pragma mark Window callback
    274 
    275 - (void)_requestExit
    276 {
    277     if (_mediaElement)
    278         _mediaElement->exitFullscreen();
    279     _forceDisableAnimation = NO;
    280 }
    281 
    282 - (void)requestExitFullscreenWithAnimation:(BOOL)animation
    283 {
    284     if (_isEndingFullscreen)
    285         return;
    286 
    287     _forceDisableAnimation = !animation;
    288     [self performSelector:@selector(_requestExit) withObject:nil afterDelay:0];
    289 
    290 }
    291 
    292 - (void)requestExitFullscreen
    293 {
    294     [self requestExitFullscreenWithAnimation:YES];
    295 }
    296 
    297 - (void)fadeHUDIn
    298 {
    299     [_hudController fadeWindowIn];
    300 }
    301 
    302 #pragma mark -
    303 #pragma mark QTMovie callbacks
    304 
    305 - (void)rateChanged:(NSNotification *)unusedNotification
    306 {
    307     UNUSED_PARAM(unusedNotification);
    308     [_hudController updateRate];
    309 }
    310 
    311 @end
    312 
    313 @implementation WebVideoFullscreenWindow
    314 
    315 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag
    316 {
    317     UNUSED_PARAM(aStyle);
    318     self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:bufferingType defer:flag];
    319     if (!self)
    320         return nil;
    321     [self setOpaque:NO];
    322     [self setBackgroundColor:[NSColor clearColor]];
    323     [self setHidesOnDeactivate:YES];
    324     [self setIgnoresMouseEvents:NO];
    325     [self setAcceptsMouseMovedEvents:YES];
    326     return self;
    327 }
    328 
    329 - (void)dealloc
    330 {
    331     ASSERT(!_fullscreenAnimation);
    332     [super dealloc];
    333 }
    334 
    335 - (BOOL)resignFirstResponder
    336 {
    337     return NO;
    338 }
    339 
    340 - (BOOL)canBecomeKeyWindow
    341 {
    342     return NO;
    343 }
    344 
    345 - (void)mouseDown:(NSEvent *)theEvent
    346 {
    347     UNUSED_PARAM(theEvent);
    348 }
    349 
    350 - (void)cancelOperation:(id)sender
    351 {
    352     UNUSED_PARAM(sender);
    353     [[self windowController] requestExitFullscreen];
    354 }
    355 
    356 - (void)animatedResizeDidEnd
    357 {
    358     // Call our windowController.
    359     if (_controllerActionOnAnimationEnd)
    360         [[self windowController] performSelector:_controllerActionOnAnimationEnd];
    361     _controllerActionOnAnimationEnd = NULL;
    362 }
    363 
    364 //
    365 // This function will animate a change of frame rectangle
    366 // We support queuing animation, that means that we'll correctly
    367 // interrupt the running animation, and queue the next one.
    368 //
    369 - (void)animateFromRect:(NSRect)startRect toRect:(NSRect)endRect withSubAnimation:(NSAnimation *)subAnimation controllerAction:(SEL)controllerAction
    370 {
    371     _controllerActionOnAnimationEnd = controllerAction;
    372 
    373     BOOL wasAnimating = NO;
    374     if (_fullscreenAnimation) {
    375         wasAnimating = YES;
    376 
    377         // Interrupt any running animation.
    378         [_fullscreenAnimation stopAnimation];
    379 
    380         // Save the current rect to ensure a smooth transition.
    381         startRect = [_fullscreenAnimation currentFrame];
    382         [_fullscreenAnimation release];
    383         _fullscreenAnimation = nil;
    384     }
    385 
    386     if (NSIsEmptyRect(startRect) || NSIsEmptyRect(endRect)) {
    387         // Fakely end the subanimation.
    388         [subAnimation setCurrentProgress:1.0];
    389         // And remove the weak link to the window.
    390         [subAnimation stopAnimation];
    391 
    392         [self setFrame:endRect display:NO];
    393         [self makeKeyAndOrderFront:self];
    394         [self animatedResizeDidEnd];
    395         return;
    396     }
    397 
    398     if (!wasAnimating) {
    399         // We'll downscale the window during the animation based on the higher resolution rect
    400         BOOL higherResolutionIsEndRect = startRect.size.width < endRect.size.width && startRect.size.height < endRect.size.height;
    401         [self setFrame:higherResolutionIsEndRect ? endRect : startRect display:NO];
    402     }
    403 
    404     ASSERT(!_fullscreenAnimation);
    405     _fullscreenAnimation = [[WebWindowScaleAnimation alloc] initWithHintedDuration:0.2 window:self initalFrame:startRect finalFrame:endRect];
    406     [_fullscreenAnimation setSubAnimation:subAnimation];
    407     [_fullscreenAnimation setDelegate:self];
    408 
    409     // Make sure the animation has scaled the window before showing it.
    410     [_fullscreenAnimation setCurrentProgress:0];
    411     [self makeKeyAndOrderFront:self];
    412 
    413     [_fullscreenAnimation startAnimation];
    414 }
    415 
    416 - (void)animationDidEnd:(NSAnimation *)animation
    417 {
    418 #if !defined(BUILDING_ON_TIGER) // Animations are never threaded on Tiger.
    419     if (![NSThread isMainThread]) {
    420         [self performSelectorOnMainThread:@selector(animationDidEnd:) withObject:animation waitUntilDone:NO];
    421         return;
    422     }
    423 #endif
    424     if (animation != _fullscreenAnimation)
    425         return;
    426 
    427     // The animation is not really over and was interrupted
    428     // Don't send completion events.
    429     if ([animation currentProgress] < 1.0)
    430         return;
    431 
    432     // Ensure that animation (and subanimation) don't keep
    433     // the weak reference to the window ivar that may be destroyed from
    434     // now on.
    435     [_fullscreenAnimation setWindow:nil];
    436 
    437     [_fullscreenAnimation autorelease];
    438     _fullscreenAnimation = nil;
    439 
    440     [self animatedResizeDidEnd];
    441 }
    442 
    443 - (void)mouseMoved:(NSEvent *)theEvent
    444 {
    445     [[self windowController] fadeHUDIn];
    446 }
    447 
    448 - (void)resignKeyWindow
    449 {
    450     [super resignKeyWindow];
    451     [[self windowController] requestExitFullscreenWithAnimation:NO];
    452 }
    453 
    454 @end
    455 
    456 #endif /* ENABLE(VIDEO) */
    457