/* * Copyright (C) 2007 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "WebNodeHighlight.h" #import "WebNodeHighlightView.h" #import "WebNSViewExtras.h" #import #define FADE_ANIMATION_DURATION 0.2 @interface WebNodeHighlightFadeInAnimation : NSAnimation @end @interface WebNodeHighlight (FileInternal) - (NSRect)_computeHighlightWindowFrame; - (void)_repositionHighlightWindow; - (void)_animateFadeIn:(WebNodeHighlightFadeInAnimation *)animation; @end @implementation WebNodeHighlightFadeInAnimation - (void)setCurrentProgress:(NSAnimationProgress)progress { [super setCurrentProgress:progress]; [(WebNodeHighlight *)[self delegate] _animateFadeIn:self]; } @end @implementation WebNodeHighlight - (id)initWithTargetView:(NSView *)targetView { self = [super init]; if (!self) return nil; _targetView = [targetView retain]; int styleMask = NSBorderlessWindowMask; NSRect contentRect = [NSWindow contentRectForFrameRect:[self _computeHighlightWindowFrame] styleMask:styleMask]; _highlightWindow = [[NSWindow alloc] initWithContentRect:contentRect styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; [_highlightWindow setBackgroundColor:[NSColor clearColor]]; [_highlightWindow setOpaque:NO]; [_highlightWindow setIgnoresMouseEvents:YES]; [_highlightWindow setReleasedWhenClosed:NO]; _highlightView = [[WebNodeHighlightView alloc] initWithWebNodeHighlight:self]; [_highlightView setFractionFadedIn:0.0]; [_highlightWindow setContentView:_highlightView]; [_highlightView release]; return self; } - (void)setHighlightedNode:(DOMNode *)node { id old = _highlightNode; _highlightNode = [node retain]; [old release]; } - (DOMNode *)highlightedNode { return _highlightNode; } - (void)dealloc { // FIXME: Bad to do all this work in dealloc. What about under GC? [self detachHighlight]; ASSERT(!_highlightWindow); ASSERT(!_targetView); [_fadeInAnimation setDelegate:nil]; [_fadeInAnimation stopAnimation]; [_fadeInAnimation release]; [_highlightNode release]; [super dealloc]; } - (void)attachHighlight { ASSERT(_targetView); ASSERT([_targetView window]); ASSERT(_highlightWindow); // Disable screen updates so the highlight moves in sync with the view. [[_targetView window] disableScreenUpdatesUntilFlush]; [[_targetView window] addChildWindow:_highlightWindow ordered:NSWindowAbove]; // Observe both frame-changed and bounds-changed notifications because either one could leave // the highlight incorrectly positioned with respect to the target view. We need to do this for // the entire superview hierarchy to handle scrolling, bars coming and going, etc. // (without making concrete assumptions about the view hierarchy). NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; for (NSView *v = _targetView; v; v = [v superview]) { [notificationCenter addObserver:self selector:@selector(_repositionHighlightWindow) name:NSViewFrameDidChangeNotification object:v]; [notificationCenter addObserver:self selector:@selector(_repositionHighlightWindow) name:NSViewBoundsDidChangeNotification object:v]; } if (_delegate && [_delegate respondsToSelector:@selector(didAttachWebNodeHighlight:)]) [_delegate didAttachWebNodeHighlight:self]; } - (id)delegate { return _delegate; } - (void)detachHighlight { if (!_highlightWindow) { ASSERT(!_targetView); return; } if (_delegate && [_delegate respondsToSelector:@selector(willDetachWebNodeHighlight:)]) [_delegate willDetachWebNodeHighlight:self]; // FIXME: is this necessary while detaching? Should test. [[_targetView window] disableScreenUpdatesUntilFlush]; NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter removeObserver:self name:NSViewFrameDidChangeNotification object:nil]; [notificationCenter removeObserver:self name:NSViewBoundsDidChangeNotification object:nil]; [[_highlightWindow parentWindow] removeChildWindow:_highlightWindow]; [_highlightWindow release]; _highlightWindow = nil; [_targetView release]; _targetView = nil; // We didn't retain _highlightView, but we do need to tell it to forget about us, so it doesn't // try to send our delegate messages after we've been dealloc'ed, e.g. [_highlightView detachFromWebNodeHighlight]; _highlightView = nil; } - (void)show { ASSERT(!_fadeInAnimation); if (_fadeInAnimation || [_highlightView fractionFadedIn] == 1.0) return; _fadeInAnimation = [[WebNodeHighlightFadeInAnimation alloc] initWithDuration:FADE_ANIMATION_DURATION animationCurve:NSAnimationEaseInOut]; [_fadeInAnimation setAnimationBlockingMode:NSAnimationNonblocking]; [_fadeInAnimation setDelegate:self]; [_fadeInAnimation startAnimation]; } - (void)hide { [_highlightView setFractionFadedIn:0.0]; } - (void)animationDidEnd:(NSAnimation *)animation { ASSERT(animation == _fadeInAnimation); [_fadeInAnimation release]; _fadeInAnimation = nil; } - (BOOL)ignoresMouseEvents { ASSERT(_highlightWindow); return [_highlightWindow ignoresMouseEvents]; } - (WebNodeHighlightView *)highlightView { return _highlightView; } - (void)setDelegate:(id)delegate { // The delegate is not retained, as usual in Cocoa. _delegate = delegate; } - (void)setHolesNeedUpdateInTargetViewRect:(NSRect)rect { ASSERT(_targetView); [_highlightView setHolesNeedUpdateInRect:[_targetView _web_convertRect:rect toView:_highlightView]]; // Redraw highlight view immediately so it updates in sync with the target view // if we called disableScreenUpdatesUntilFlush on the target view earlier. This // is especially visible when resizing the window. [_highlightView displayIfNeeded]; } - (void)setIgnoresMouseEvents:(BOOL)newValue { ASSERT(_highlightWindow); [_highlightWindow setIgnoresMouseEvents:newValue]; } - (NSView *)targetView { return _targetView; } @end @implementation WebNodeHighlight (FileInternal) - (NSRect)_computeHighlightWindowFrame { ASSERT(_targetView); ASSERT([_targetView window]); NSRect highlightWindowFrame = [_targetView convertRect:[_targetView visibleRect] toView:nil]; highlightWindowFrame.origin = [[_targetView window] convertBaseToScreen:highlightWindowFrame.origin]; return highlightWindowFrame; } - (void)_repositionHighlightWindow { ASSERT([_targetView window]); // Disable screen updates so the highlight moves in sync with the view. [[_targetView window] disableScreenUpdatesUntilFlush]; [_highlightWindow setFrame:[self _computeHighlightWindowFrame] display:YES]; } - (void)_animateFadeIn:(WebNodeHighlightFadeInAnimation *)animation { [_highlightView setFractionFadedIn:[animation currentValue]]; } @end