mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 13:07:02 +00:00
4464 lines
133 KiB
Plaintext
4464 lines
133 KiB
Plaintext
/*
|
|
* CPWindow.j
|
|
* AppKit
|
|
*
|
|
* Created by Francisco Tolmasky.
|
|
* Copyright 2008, 280 North, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
@import <Foundation/CPCountedSet.j>
|
|
@import <Foundation/CPNotificationCenter.j>
|
|
@import <Foundation/CPUndoManager.j>
|
|
|
|
@import "CGGeometry.j"
|
|
@import "CPAnimation.j"
|
|
@import "CPCursor.j"
|
|
@import "CPDragServer.j"
|
|
@import "CPEvent.j"
|
|
@import "CPPlatformWindow.j"
|
|
#if PLATFORM(BROWSER)
|
|
@import "CPPlatformWindow+DOM.j"
|
|
#endif
|
|
@import "CPResponder.j"
|
|
@import "CPScreen.j"
|
|
@import "CPText.j"
|
|
@import "CPTrackingArea.j"
|
|
@import "CPView.j"
|
|
@import "CPWindow_Constants.j"
|
|
@import "_CPBorderlessBridgeWindowView.j"
|
|
@import "_CPBorderlessWindowView.j"
|
|
@import "_CPDocModalWindowView.j"
|
|
@import "_CPHUDWindowView.j"
|
|
@import "_CPModalWindowView.j"
|
|
@import "_CPPopoverWindowView.j"
|
|
@import "_CPShadowWindowView.j"
|
|
@import "_CPStandardWindowView.j"
|
|
@import "_CPToolTipWindowView.j"
|
|
|
|
@class CPMenu
|
|
@class CPProgressIndicator
|
|
@class CPToolbar
|
|
@class CPWindowController
|
|
@class _CPWindowFrameAnimation
|
|
@class _CPWindowFrameAnimationDelegate
|
|
|
|
@global CPApp
|
|
@global _CPPlatformWindowWillCloseNotification
|
|
|
|
@typedef _CPWindowFullPlatformWindowSession
|
|
|
|
|
|
@protocol CPWindowDelegate <CPObject>
|
|
|
|
@optional
|
|
- (BOOL)windowShouldClose:(CPWindow)aWindow;
|
|
- (CGSize)windowWillResize:(CPWindow)sender toSize:(CGSize)aSize;
|
|
- (CPUndoManager)windowWillReturnUndoManager:(CPWindow)window;
|
|
- (void)windowDidBecomeKey:(CPNotification)aNotification;
|
|
- (void)windowDidBecomeMain:(CPNotification)aNotification;
|
|
- (void)windowDidDeminiaturize:(CPNotification)notification;
|
|
- (void)windowDidEndSheet:(CPNotification)aNotification;
|
|
- (void)windowDidMiniaturize:(CPNotification)notification;
|
|
- (void)windowDidMove:(CPNotification)aNotification;
|
|
- (void)windowDidResignKey:(CPNotification)aNotification;
|
|
- (void)windowDidResignMain:(CPNotification)aNotification;
|
|
- (void)windowDidResize:(CPNotification)aNotification;
|
|
- (void)windowWillMiniaturize:(CPNotification)notification;
|
|
- (void)windowWillBeginSheet:(CPNotification)aNotification;
|
|
- (void)windowWillClose:(CPWindow)aWindow;
|
|
|
|
@end
|
|
|
|
var CPWindowDelegate_windowShouldClose_ = 1 << 1,
|
|
CPWindowDelegate_windowWillReturnUndoManager_ = 1 << 2,
|
|
CPWindowDelegate_windowWillClose_ = 1 << 3,
|
|
CPWindowDelegate_windowWillResize_toSize_ = 1 << 4;
|
|
|
|
|
|
var CPWindowSaveImage = nil,
|
|
|
|
CPWindowResizeTime = 0.2,
|
|
CPWindowResizeStyleGlobalChangeNotification = @"CPWindowResizeStyleGlobalChangeNotification",
|
|
|
|
CPWindowMinVisibleHorizontalMargin = 40,
|
|
CPWindowMinVisibleVerticalMargin = 2;
|
|
|
|
/*
|
|
Keys for which action messages will be sent by default when unhandled, e.g. complete:.
|
|
*/
|
|
var CPWindowActionMessageKeys = [
|
|
CPLeftArrowFunctionKey,
|
|
CPRightArrowFunctionKey,
|
|
CPUpArrowFunctionKey,
|
|
CPDownArrowFunctionKey,
|
|
CPPageUpFunctionKey,
|
|
CPPageDownFunctionKey,
|
|
CPHomeFunctionKey,
|
|
CPEndFunctionKey,
|
|
CPEscapeFunctionKey
|
|
];
|
|
|
|
/*!
|
|
@ingroup appkit
|
|
@class CPWindow
|
|
|
|
An CPWindow instance represents a window, panel or menu on the screen.</p>
|
|
|
|
<p>Each window has a style, which determines how the window is decorated; whether it has a border, a title bar, a resize bar, minimise and close buttons.</p>
|
|
|
|
<p>A window has a frame. This is the frame of the entire window on the screen, including all decorations and borders. The origin of the frame represents its bottom left corner and the frame is expressed in screen coordinates.</p>
|
|
|
|
<p>A window always contains a content view which is the highest level view available for public (application) use. This view fills the area of the window inside any decoration/border. This is the only part of the window that application programmers are allowed to draw in directly.</p>
|
|
|
|
<p>You can convert between view coordinates and window base coordinates using the [CPView -convertPoint:fromView:], [CPView -convertPoint:toView:], [CPView -convertRect:fromView:], and [CPView -convertRect:toView:] methods with a nil view argument.
|
|
|
|
@par Delegate Methods
|
|
|
|
@delegate -(void)windowDidResize:(CPNotification)notification;
|
|
Sent from the notification center when the window has been resized.
|
|
@param notification contains information about the resize event
|
|
|
|
@delegate -(CPUndoManager)windowWillReturnUndoManager:(CPWindow)aWindow;
|
|
Called to obtain the undo manager for a window
|
|
@param aWindow the window for which to return the undo manager
|
|
@return the window's undo manager
|
|
|
|
@delegate -(void)windowDidBecomeMain:(CPNotification)notification;
|
|
Sent from the notification center when the delegate's window becomes
|
|
the main window.
|
|
@param notification contains information about the event
|
|
|
|
@delegate -(void)windowDidResignMain:(CPNotification)notification;
|
|
Sent from the notification center when the delegate's window has
|
|
resigned main window status.
|
|
@param notification contains information about the event
|
|
|
|
@delegate -(void)windowDidResignKey:(CPNotification)notification;
|
|
Sent from the notification center when the delegate's window has
|
|
resigned key window status.
|
|
@param notification contains information about the event
|
|
|
|
@delegate -(BOOL)windowShouldClose:(id)aWindow;
|
|
Called when the user tries to close the window.
|
|
@param aWindow the window to close
|
|
@return \c YES allows the window to close. \c NO
|
|
vetoes the close operation and leaves the window open.
|
|
|
|
@delegate -(BOOL)windowWillBeginSheet:(CPNotification)notification;
|
|
Sent from the notification center before sheet is visible on
|
|
the delegate's window.
|
|
@param notification contains information about the event
|
|
|
|
@delegate -(BOOL)windowDidEndSheet:(CPNotification)notification;
|
|
Sent from the notification center when an attached sheet on the
|
|
delegate's window has been animated out and is no longer visible.
|
|
@param notification contains information about the event
|
|
*/
|
|
@implementation CPWindow : CPResponder
|
|
{
|
|
CPPlatformWindow _platformWindow;
|
|
|
|
int _windowNumber;
|
|
unsigned _styleMask;
|
|
CGRect _frame;
|
|
int _level;
|
|
BOOL _isVisible;
|
|
BOOL _hasBeenOrderedIn @accessors;
|
|
BOOL _isMiniaturized;
|
|
BOOL _isAnimating @accessors(setter=_setAnimating:);
|
|
BOOL _hasShadow;
|
|
BOOL _isMovableByWindowBackground;
|
|
BOOL _isMovable;
|
|
BOOL _constrainsToUsableScreen;
|
|
unsigned _shadowStyle;
|
|
BOOL _showsResizeIndicator;
|
|
|
|
int _positioningMask;
|
|
CGRect _positioningScreenRect;
|
|
|
|
BOOL _isDocumentEdited;
|
|
BOOL _isDocumentSaving;
|
|
|
|
_CPShadowWindowView _shadowView;
|
|
|
|
CPView _windowView;
|
|
CPView _contentView;
|
|
CPView _toolbarView;
|
|
|
|
BOOL _handlingTrackingAreaEvent;
|
|
BOOL _restartHandlingTrackingAreaEvent;
|
|
CPArray _previousMouseEnteredStack;
|
|
CPArray _previousCursorUpdateStack;
|
|
CPArray _mouseEnteredStack;
|
|
CPArray _cursorUpdateStack;
|
|
CPArray _queuedEvents;
|
|
CPArray _trackingAreaViews;
|
|
id _activeCursorTrackingArea;
|
|
CPArray _queuedTrackingEvents;
|
|
CPView _leftMouseDownView;
|
|
CPView _rightMouseDownView;
|
|
|
|
CPToolbar _toolbar;
|
|
CPResponder _firstResponder;
|
|
CPResponder _initialFirstResponder;
|
|
BOOL _hasBecomeKeyWindow;
|
|
id <CPWindowDelegate> _delegate;
|
|
unsigned _implementedDelegateMethods;
|
|
|
|
CPString _title;
|
|
|
|
BOOL _acceptsMouseMovedEvents;
|
|
BOOL _ignoresMouseEvents;
|
|
|
|
CPWindowController _windowController;
|
|
|
|
CGSize _minSize;
|
|
CGSize _maxSize;
|
|
|
|
CPUndoManager _undoManager;
|
|
CPURL _representedURL;
|
|
|
|
CPSet _registeredDraggedTypes;
|
|
CPArray _registeredDraggedTypesArray;
|
|
CPCountedSet _inclusiveRegisteredDraggedTypes;
|
|
|
|
CPButton _defaultButton;
|
|
BOOL _defaultButtonEnabled;
|
|
BOOL _defaultButtonDisabledTemporarily;
|
|
|
|
BOOL _autorecalculatesKeyViewLoop;
|
|
BOOL _keyViewLoopIsDirty;
|
|
|
|
BOOL _sharesChromeWithPlatformWindow;
|
|
|
|
// Bridge Support
|
|
#if PLATFORM(DOM)
|
|
DOMElement _DOMElement;
|
|
#endif
|
|
|
|
unsigned _autoresizingMask;
|
|
|
|
BOOL _isFullPlatformWindow;
|
|
_CPWindowFullPlatformWindowSession _fullPlatformWindowSession;
|
|
|
|
CPWindow _parentWindow;
|
|
CPArray _childWindows;
|
|
CPWindowOrderingMode _childOrdering @accessors(setter=_setChildOrdering);
|
|
|
|
CPDictionary _sheetContext;
|
|
CPWindow _parentView;
|
|
BOOL _isSheet;
|
|
_CPWindowFrameAnimation _frameAnimation;
|
|
_CPWindowFrameAnimationDelegate _frameAnimationDelegate;
|
|
|
|
BOOL _inhibitUpdateTrackingAreas; // Used by the CPView when updating tracking areas
|
|
}
|
|
|
|
+ (Class)_binderClassForBinding:(CPString)aBinding
|
|
{
|
|
if ([aBinding hasPrefix:CPDisplayPatternTitleBinding])
|
|
return [CPTitleWithPatternBinding class];
|
|
|
|
return [super _binderClassForBinding:aBinding];
|
|
}
|
|
|
|
- (id)init
|
|
{
|
|
return [self initWithContentRect:CGRectMakeZero() styleMask:CPTitledWindowMask];
|
|
}
|
|
|
|
/*!
|
|
Initializes the window. The method also takes a style bit mask made up
|
|
of any of the following values:
|
|
<pre>
|
|
CPBorderlessWindowMask
|
|
CPTitledWindowMask
|
|
CPClosableWindowMask
|
|
CPMiniaturizableWindowMask
|
|
CPResizableWindowMask
|
|
CPTexturedBackgroundWindowMask
|
|
</pre>
|
|
@param aContentRect the size and location of the window in screen space
|
|
@param aStyleMask a style mask
|
|
@return the initialized window
|
|
*/
|
|
- (id)initWithContentRect:(CGRect)aContentRect styleMask:(unsigned)aStyleMask
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
var windowViewClass = [[self class] _windowViewClassForStyleMask:aStyleMask];
|
|
|
|
_frame = [windowViewClass frameRectForContentRect:aContentRect];
|
|
_constrainsToUsableScreen = YES;
|
|
|
|
[self _setSharesChromeWithPlatformWindow:![CPPlatform isBrowser]];
|
|
|
|
if ([CPPlatform isBrowser])
|
|
[self setPlatformWindow:[CPPlatformWindow primaryPlatformWindow]];
|
|
else
|
|
{
|
|
// give zero sized borderless bridge windows a default size.
|
|
if ((aStyleMask & CPBorderlessBridgeWindowMask) && aContentRect.size.width === 0 && aContentRect.size.height === 0)
|
|
{
|
|
var visibleFrame = [[[CPScreen alloc] init] visibleFrame];
|
|
_frame.size.height = MIN(768.0, visibleFrame.size.height);
|
|
_frame.size.width = MIN(1024.0, visibleFrame.size.width);
|
|
_frame.origin.x = (visibleFrame.size.width - _frame.size.width) / 2;
|
|
_frame.origin.y = (visibleFrame.size.height - _frame.size.height) / 2;
|
|
}
|
|
|
|
[self setPlatformWindow:[[CPPlatformWindow alloc] initWithContentRect:_frame]];
|
|
[self platformWindow]._only = self;
|
|
}
|
|
|
|
_isFullPlatformWindow = NO;
|
|
_registeredDraggedTypes = [CPSet set];
|
|
_registeredDraggedTypesArray = [];
|
|
_acceptsMouseMovedEvents = YES;
|
|
_isMovable = YES;
|
|
_hasBeenOrderedIn = NO;
|
|
|
|
_parentWindow = nil;
|
|
_childWindows = [];
|
|
_childOrdering = CPWindowOut;
|
|
|
|
_isSheet = NO;
|
|
_sheetContext = nil;
|
|
_parentView = nil;
|
|
_frameAnimation = nil;
|
|
_frameAnimationDelegate = nil;
|
|
|
|
// Set up our window number.
|
|
_windowNumber = [CPApp._windows count];
|
|
CPApp._windows[_windowNumber] = self;
|
|
|
|
_styleMask = aStyleMask;
|
|
|
|
[self setLevel:CPNormalWindowLevel];
|
|
|
|
_handlingTrackingAreaEvent = NO;
|
|
_restartHandlingTrackingAreaEvent = NO;
|
|
_trackingAreaViews = [];
|
|
_previousMouseEnteredStack = [];
|
|
_previousCursorUpdateStack = [];
|
|
_mouseEnteredStack = [];
|
|
_cursorUpdateStack = [];
|
|
_queuedEvents = [];
|
|
_queuedTrackingEvents = [];
|
|
_activeCursorTrackingArea = nil;
|
|
|
|
// Create our border view which is the actual root of our view hierarchy.
|
|
_windowView = [[windowViewClass alloc] initWithFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(_frame), CGRectGetHeight(_frame)) styleMask:aStyleMask];
|
|
|
|
[_windowView _setWindow:self];
|
|
[_windowView setNextResponder:self];
|
|
|
|
// CSS Styling
|
|
#if PLATFORM(DOM)
|
|
var radius;
|
|
|
|
if (radius = [_windowView actualValueForThemeAttribute:@"border-top-left-radius"])
|
|
_windowView._DOMElement.style.borderTopLeftRadius = radius;
|
|
|
|
if (radius = [_windowView actualValueForThemeAttribute:@"border-top-right-radius"])
|
|
_windowView._DOMElement.style.borderTopRightRadius = radius;
|
|
|
|
if (radius = [_windowView actualValueForThemeAttribute:@"border-bottom-left-radius"])
|
|
_windowView._DOMElement.style.borderBottomLeftRadius = radius;
|
|
|
|
if (radius = [_windowView actualValueForThemeAttribute:@"border-bottom-right-radius"])
|
|
_windowView._DOMElement.style.borderBottomRightRadius = radius;
|
|
#endif
|
|
|
|
// Size calculation needs _windowView
|
|
_minSize = [self _calculateMinSizeForProposedSize:CGSizeMake(0.0, 0.0)];
|
|
_maxSize = CGSizeMake(1000000.0, 1000000.0);
|
|
|
|
[self setMovableByWindowBackground:aStyleMask & CPHUDBackgroundWindowMask];
|
|
|
|
// Create a generic content view.
|
|
[self setContentView:[[CPView alloc] initWithFrame:CGRectMakeZero()]];
|
|
|
|
_firstResponder = self;
|
|
|
|
#if PLATFORM(DOM)
|
|
_DOMElement = document.createElement("div");
|
|
|
|
_DOMElement.style.position = "absolute";
|
|
_DOMElement.style.visibility = "visible";
|
|
_DOMElement.style.zIndex = 0;
|
|
|
|
if (![self _sharesChromeWithPlatformWindow])
|
|
{
|
|
CPDOMDisplayServerSetStyleLeftTop(_DOMElement, NULL, CGRectGetMinX(_frame), CGRectGetMinY(_frame));
|
|
}
|
|
|
|
CPDOMDisplayServerSetStyleSize(_DOMElement, 1, 1);
|
|
CPDOMDisplayServerAppendChild(_DOMElement, _windowView._DOMElement);
|
|
#endif
|
|
|
|
[self setNextResponder:CPApp];
|
|
|
|
[self setHasShadow:aStyleMask !== CPBorderlessWindowMask];
|
|
|
|
if (aStyleMask & CPBorderlessBridgeWindowMask)
|
|
[self setFullPlatformWindow:YES];
|
|
|
|
_autorecalculatesKeyViewLoop = NO;
|
|
_defaultButtonEnabled = YES;
|
|
_defaultButtonDisabledTemporarily = NO;
|
|
_keyViewLoopIsDirty = NO;
|
|
_hasBecomeKeyWindow = NO;
|
|
|
|
[self setShowsResizeIndicator:_styleMask & CPResizableWindowMask];
|
|
|
|
[[CPNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(_didReceiveResizeStyleChange:)
|
|
name:CPWindowResizeStyleGlobalChangeNotification
|
|
object:nil];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (CPPlatformWindow)platformWindow
|
|
{
|
|
return _platformWindow;
|
|
}
|
|
|
|
/*!
|
|
Sets the platform window of the reciver.
|
|
This method will first close the reciever,
|
|
change the platform window, then reopen the window (if it was originally open).
|
|
*/
|
|
- (void)setPlatformWindow:(CPPlatformWindow)aPlatformWindow
|
|
{
|
|
var wasVisible = [self isVisible];
|
|
|
|
// we have to close it first, otherwise we get a DOM exception.
|
|
if (wasVisible)
|
|
[self close];
|
|
|
|
_platformWindow = aPlatformWindow;
|
|
[_platformWindow _setTitle:_title window:self];
|
|
|
|
if (wasVisible)
|
|
[self orderFront:self];
|
|
}
|
|
|
|
|
|
/*!
|
|
@ignore
|
|
*/
|
|
+ (Class)_windowViewClassForStyleMask:(unsigned)aStyleMask
|
|
{
|
|
if (aStyleMask & CPHUDBackgroundWindowMask)
|
|
return _CPHUDWindowView;
|
|
|
|
else if (aStyleMask === CPBorderlessWindowMask)
|
|
return _CPBorderlessWindowView;
|
|
|
|
else if (aStyleMask & CPDocModalWindowMask)
|
|
return _CPDocModalWindowView;
|
|
|
|
else if (aStyleMask & _CPModalWindowMask)
|
|
return _CPModalWindowView;
|
|
|
|
return _CPStandardWindowView;
|
|
}
|
|
|
|
+ (Class)_windowViewClassForFullPlatformWindowStyleMask:(unsigned)aStyleMask
|
|
{
|
|
return _CPBorderlessBridgeWindowView;
|
|
}
|
|
|
|
- (void)awakeFromCib
|
|
{
|
|
// At this time we know the final screen (or browser) size
|
|
// and can apply the positioning mask, if any, from the nib.
|
|
if (_positioningScreenRect)
|
|
{
|
|
var actualScreenRect = [CPPlatform isBrowser] ? [_platformWindow contentBounds] : [[self screen] visibleFrame],
|
|
frame = [self frame],
|
|
origin = frame.origin;
|
|
|
|
if (actualScreenRect)
|
|
{
|
|
if ((_positioningMask & CPWindowPositionFlexibleLeft) && (_positioningMask & CPWindowPositionFlexibleRight))
|
|
{
|
|
// Proportional Horizontal.
|
|
origin.x *= (actualScreenRect.size.width / _positioningScreenRect.size.width);
|
|
}
|
|
else if (_positioningMask & CPWindowPositionFlexibleLeft)
|
|
{
|
|
// Fixed from Right
|
|
origin.x += actualScreenRect.size.width - _positioningScreenRect.size.width;
|
|
}
|
|
else if (_positioningMask & CPWindowPositionFlexibleRight)
|
|
{
|
|
// Fixed from Left
|
|
}
|
|
|
|
if ((_positioningMask & CPWindowPositionFlexibleTop) && (_positioningMask & CPWindowPositionFlexibleBottom))
|
|
{
|
|
// Proportional Vertical.
|
|
origin.y *= (actualScreenRect.size.height / _positioningScreenRect.size.height);
|
|
}
|
|
else if (_positioningMask & CPWindowPositionFlexibleTop)
|
|
{
|
|
// Fixed from Bottom
|
|
origin.y += actualScreenRect.size.height - _positioningScreenRect.size.height;
|
|
}
|
|
else if (_positioningMask & CPWindowPositionFlexibleBottom)
|
|
{
|
|
// Fixed from Top
|
|
}
|
|
|
|
[self setFrameOrigin:origin];
|
|
}
|
|
}
|
|
|
|
/*
|
|
Calculate the key view loop if necessary. Note that Cocoa does not call recalculateKeyViewLoop when awaking a nib. If a key view loop was set in the cib, we have to chain it to the content view.
|
|
*/
|
|
if ([self _hasKeyViewLoop:[_contentView subviews]])
|
|
{
|
|
var views = [self _viewsSortedByPosition],
|
|
count = [views count];
|
|
|
|
// The first view is the content view.
|
|
// Find the first subview that has a next key view.
|
|
for (var i = 1; i < count; ++i)
|
|
{
|
|
var view = views[i];
|
|
|
|
if ([view nextKeyView])
|
|
{
|
|
[_contentView setNextKeyView:view];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Cooca does NOT call the public method recalculateKeyViewLoop for nibs,
|
|
// but it does calculate the loop.
|
|
[self _doRecalculateKeyViewLoop];
|
|
}
|
|
}
|
|
|
|
- (void)_setWindowView:(CPView)aWindowView
|
|
{
|
|
if (_windowView === aWindowView)
|
|
return;
|
|
|
|
var oldWindowView = _windowView;
|
|
|
|
_windowView = aWindowView;
|
|
|
|
if (oldWindowView)
|
|
{
|
|
[oldWindowView _setWindow:nil];
|
|
[oldWindowView noteToolbarChanged];
|
|
|
|
#if PLATFORM(DOM)
|
|
CPDOMDisplayServerRemoveChild(_DOMElement, oldWindowView._DOMElement);
|
|
#endif
|
|
}
|
|
|
|
if (_windowView)
|
|
{
|
|
#if PLATFORM(DOM)
|
|
CPDOMDisplayServerAppendChild(_DOMElement, _windowView._DOMElement);
|
|
#endif
|
|
|
|
var contentRect = [_contentView convertRect:[_contentView bounds] toView:nil];
|
|
|
|
contentRect.origin = [self convertBaseToGlobal:contentRect.origin];
|
|
|
|
[_windowView _setWindow:self];
|
|
[_windowView setNextResponder:self];
|
|
[_windowView addSubview:_contentView];
|
|
[_windowView setTitle:_title];
|
|
[_windowView noteToolbarChanged];
|
|
[_windowView setShowsResizeIndicator:[self showsResizeIndicator]];
|
|
|
|
[self setFrame:[self frameRectForContentRect:contentRect]];
|
|
}
|
|
}
|
|
|
|
- (CPView)_windowView
|
|
{
|
|
return _windowView;
|
|
}
|
|
|
|
/*!
|
|
Sets the receiver as a full platform window. If you pass YES the CPWindow instance will fill the entire browser content area,
|
|
otherwise the CPWindow will be a window inside of your browser window which the user can drag around, and resize (if you allow).
|
|
|
|
@param BOOL - YES if the window should fill the browser window, otherwise NO.
|
|
*/
|
|
- (void)setFullPlatformWindow:(BOOL)shouldBeFullPlatformWindow
|
|
{
|
|
if (![_platformWindow supportsFullPlatformWindows])
|
|
return;
|
|
|
|
shouldBeFullPlatformWindow = !!shouldBeFullPlatformWindow;
|
|
|
|
if (_isFullPlatformWindow === shouldBeFullPlatformWindow)
|
|
return;
|
|
|
|
_isFullPlatformWindow = shouldBeFullPlatformWindow;
|
|
|
|
if (_isFullPlatformWindow)
|
|
{
|
|
_fullPlatformWindowSession = _CPWindowFullPlatformWindowSessionMake(_windowView, [self contentRectForFrameRect:[self frame]], [self hasShadow], [self level]);
|
|
|
|
var fullPlatformWindowViewClass = [[self class] _windowViewClassForFullPlatformWindowStyleMask:_styleMask],
|
|
windowView = [[fullPlatformWindowViewClass alloc] initWithFrame:CGRectMakeZero() styleMask:_styleMask];
|
|
|
|
if (_platformWindow != [CPPlatformWindow primaryPlatformWindow] && [_platformWindow _hasInitializeInstanceWithWindow])
|
|
[_platformWindow setContentRect:[self frame]];
|
|
|
|
[self _setWindowView:windowView];
|
|
|
|
[self setLevel:CPBackgroundWindowLevel];
|
|
[self setHasShadow:NO];
|
|
[self setAutoresizingMask:CPWindowWidthSizable | CPWindowHeightSizable];
|
|
[self setFrame:[_platformWindow visibleFrame]];
|
|
}
|
|
else
|
|
{
|
|
var windowView = _fullPlatformWindowSession.windowView;
|
|
|
|
[self _setWindowView:windowView];
|
|
|
|
[self setLevel:_fullPlatformWindowSession.level];
|
|
[self setHasShadow:_fullPlatformWindowSession.hasShadow];
|
|
[self setAutoresizingMask:CPWindowNotSizable];
|
|
|
|
[self setFrame:[windowView frameRectForContentRect:_fullPlatformWindowSession.contentRect]];
|
|
}
|
|
}
|
|
|
|
/*!
|
|
@return BOOL - YES if the CPWindow fills the browser window, otherwise NO.
|
|
*/
|
|
- (BOOL)isFullPlatformWindow
|
|
{
|
|
return _isFullPlatformWindow;
|
|
}
|
|
|
|
/*!
|
|
Returns the window's style mask.
|
|
*/
|
|
- (unsigned)styleMask
|
|
{
|
|
return _styleMask;
|
|
}
|
|
|
|
/*!
|
|
Returns the frame rectangle used by a window.
|
|
Style masks include:
|
|
<pre>
|
|
CPBorderlessWindowMask
|
|
CPTitledWindowMask
|
|
CPClosableWindowMask
|
|
CPResizableWindowMask
|
|
CPTexturedBackgroundWindowMask
|
|
CPBorderlessBridgeWindowMask
|
|
CPHUDBackgroundWindowMask
|
|
</pre>
|
|
|
|
@param aContentRect the content rectangle of the window
|
|
@param aStyleMask the style mask of the window
|
|
@return the matching window's frame rectangle
|
|
*/
|
|
+ (CGRect)frameRectForContentRect:(CGRect)aContentRect styleMask:(unsigned)aStyleMask
|
|
{
|
|
return [[[self class] _windowViewClassForStyleMask:aStyleMask] frameRectForContentRect:aContentRect];
|
|
}
|
|
|
|
/*!
|
|
Returns the receiver's content rectangle. A content rectangle does not include toolbars.
|
|
@param aFrame the window's frame rectangle
|
|
*/
|
|
- (CGRect)contentRectForFrameRect:(CGRect)aFrame
|
|
{
|
|
return [_windowView contentRectForFrameRect:aFrame];
|
|
}
|
|
|
|
/*!
|
|
Retrieves the frame rectangle for this window.
|
|
@param aContentRect the window's content rectangle
|
|
@return the window's frame rectangle
|
|
*/
|
|
- (CGRect)frameRectForContentRect:(CGRect)aContentRect
|
|
{
|
|
return [_windowView frameRectForContentRect:aContentRect];
|
|
}
|
|
|
|
/*!
|
|
Returns the window's frame rectangle
|
|
*/
|
|
- (CGRect)frame
|
|
{
|
|
return CGRectMakeCopy(_frame);
|
|
}
|
|
|
|
/*!
|
|
Sets the frame of the window.
|
|
|
|
@param aFrame - A CGRect of the new frame for the receiver.
|
|
@param shouldDisplay - YES if the window should call setNeedsDisplay otherwise NO.
|
|
@param shouldAnimate - YES if the window should animate to it's new size and position, otherwise NO.
|
|
*/
|
|
- (void)setFrame:(CGRect)aFrame display:(BOOL)shouldDisplay animate:(BOOL)shouldAnimate
|
|
{
|
|
[self _setFrame:aFrame display:shouldDisplay animate:shouldAnimate constrainWidth:NO constrainHeight:YES];
|
|
}
|
|
|
|
- (void)_setFrame:(CGRect)aFrame display:(BOOL)shouldDisplay animate:(BOOL)shouldAnimate constrainWidth:(BOOL)shouldConstrainWidth constrainHeight:(BOOL)shouldConstrainHeight
|
|
{
|
|
var frame = CGRectMakeCopy(aFrame),
|
|
value = frame.origin.x,
|
|
delta = value - FLOOR(value);
|
|
|
|
if (delta)
|
|
frame.origin.x = value > 0.879 ? CEIL(value) : FLOOR(value);
|
|
|
|
value = frame.origin.y;
|
|
delta = value - FLOOR(value);
|
|
|
|
if (delta)
|
|
frame.origin.y = value > 0.879 ? CEIL(value) : FLOOR(value);
|
|
|
|
value = frame.size.width;
|
|
delta = value - FLOOR(value);
|
|
|
|
if (delta)
|
|
frame.size.width = value > 0.15 ? CEIL(value) : FLOOR(value);
|
|
|
|
value = frame.size.height;
|
|
delta = value - FLOOR(value);
|
|
|
|
if (delta)
|
|
frame.size.height = value > 0.15 ? CEIL(value) : FLOOR(value);
|
|
|
|
frame = [self _constrainFrame:frame toUsableScreenWidth:shouldConstrainWidth andHeight:shouldConstrainHeight];
|
|
|
|
if (shouldAnimate)
|
|
{
|
|
[_frameAnimation stopAnimation];
|
|
_frameAnimation = [[_CPWindowFrameAnimation alloc] initWithWindow:self targetFrame:frame];
|
|
[_frameAnimation setDelegate:[self _frameAnimationDelegate]];
|
|
[_frameAnimation startAnimation];
|
|
}
|
|
else
|
|
{
|
|
var origin = _frame.origin,
|
|
newOrigin = frame.origin,
|
|
originMoved = !CGPointEqualToPoint(origin, newOrigin);
|
|
|
|
if (originMoved)
|
|
{
|
|
delta = CGPointMake(newOrigin.x - origin.x, newOrigin.y - origin.y);
|
|
origin.x = newOrigin.x;
|
|
origin.y = newOrigin.y;
|
|
|
|
#if PLATFORM(DOM)
|
|
if (![self _sharesChromeWithPlatformWindow])
|
|
{
|
|
CPDOMDisplayServerSetStyleLeftTop(_DOMElement, NULL, origin.x, origin.y);
|
|
}
|
|
#endif
|
|
|
|
// reposition sheet
|
|
if ([self attachedSheet])
|
|
[self _setAttachedSheetFrameOrigin];
|
|
|
|
[[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidMoveNotification object:self];
|
|
}
|
|
|
|
var size = _frame.size,
|
|
newSize = frame.size;
|
|
|
|
if (!CGSizeEqualToSize(size, newSize))
|
|
{
|
|
size.width = newSize.width;
|
|
size.height = newSize.height;
|
|
|
|
if (!_isAnimating)
|
|
size = [self _sendDelegateWindowWillResizeToSize:size];
|
|
|
|
[_windowView setFrameSize:size];
|
|
|
|
if (_hasShadow)
|
|
[_shadowView setNeedsLayout];
|
|
|
|
if (!_isAnimating)
|
|
[[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidResizeNotification object:self];
|
|
}
|
|
|
|
if ([self _sharesChromeWithPlatformWindow])
|
|
[_platformWindow setContentRect:_frame];
|
|
|
|
if (originMoved)
|
|
[self _moveChildWindows:delta];
|
|
}
|
|
|
|
if ([_platformWindow _canUpdateContentRect] && _isFullPlatformWindow && _platformWindow != [CPPlatformWindow primaryPlatformWindow])
|
|
[_platformWindow setContentRect:aFrame];
|
|
}
|
|
|
|
- (id)_frameAnimationDelegate
|
|
{
|
|
if (_frameAnimationDelegate == nil)
|
|
_frameAnimationDelegate = [[_CPWindowFrameAnimationDelegate alloc] initWithWindow:self];
|
|
|
|
return _frameAnimationDelegate;
|
|
}
|
|
/*
|
|
Constrain a frame so that the window remains at least partially visible on screen,
|
|
moving or resizing the frame as necessary.
|
|
*/
|
|
- (CGRect)_constrainFrame:(CGRect)aFrame toUsableScreenWidth:(BOOL)constrainWidth andHeight:(BOOL)constrainHeight
|
|
{
|
|
var frame = CGRectMakeCopy(aFrame);
|
|
|
|
if (!_constrainsToUsableScreen || !_isVisible)
|
|
return frame;
|
|
|
|
var usableRect = [_platformWindow usableContentFrame];
|
|
|
|
if (constrainWidth)
|
|
{
|
|
// First move the frame right to ensure the left side is within the usable rect.
|
|
frame.origin.x = MAX(frame.origin.x, usableRect.origin.x);
|
|
|
|
// Now move the frame left so that the right side is within the usable rect.
|
|
var maxX = MIN(CGRectGetMaxX(frame), CGRectGetMaxX(usableRect));
|
|
frame.origin.x = maxX - frame.size.width;
|
|
|
|
// Finally, adjust the left + width to ensure the left side is within the usable rect.
|
|
var usableWidth = CGRectGetWidth(usableRect);
|
|
|
|
if (CGRectGetWidth(frame) > usableWidth)
|
|
{
|
|
frame.origin.x = CGRectGetMinX(usableRect);
|
|
frame.size.width = MAX(usableWidth, _minSize.width);
|
|
}
|
|
}
|
|
|
|
if (constrainHeight)
|
|
{
|
|
// First move the frame down to ensure the top is within the usable rect.
|
|
frame.origin.y = MAX(frame.origin.y, usableRect.origin.y);
|
|
|
|
// Now move the frame up so that the bottom is within the usable rect.
|
|
var maxY = MIN(CGRectGetMaxY(frame), CGRectGetMaxY(usableRect));
|
|
frame.origin.y = maxY - frame.size.height;
|
|
|
|
// Finally, adjust the top + height to ensure the top is within the usable rect.
|
|
var usableHeight = CGRectGetHeight(usableRect);
|
|
|
|
if (CGRectGetHeight(frame) > usableHeight)
|
|
{
|
|
frame.origin.y = CGRectGetMinY(usableRect);
|
|
frame.size.height = MAX(usableHeight, _minSize.height);
|
|
}
|
|
}
|
|
|
|
return frame;
|
|
}
|
|
|
|
/*
|
|
Constrain the origin of a frame such that:
|
|
|
|
- The window view's minimum resize width is kept onscreen at the left/right of the window.
|
|
- The top of the window is kept below the top of the usable content.
|
|
- The top of the contentView + CPWindowMinVisibleVerticalMargin is kept above the bottom of the usable content.
|
|
*/
|
|
- (CGRect)_constrainOriginOfFrame:(CGRect)aFrame
|
|
{
|
|
var frame = CGRectMakeCopy(aFrame);
|
|
|
|
if (!_constrainsToUsableScreen || !_isVisible)
|
|
return frame;
|
|
|
|
var usableRect = [_platformWindow usableContentFrame],
|
|
minimumSize = [_windowView _minimumResizeSize];
|
|
|
|
// First constrain x so that at least CPWindowMinVisibleHorizontalMargin is visible on the right
|
|
frame.origin.x = MAX(frame.origin.x, CGRectGetMinX(usableRect) + minimumSize.width - CGRectGetWidth(frame));
|
|
|
|
// Now constrain x so that at least CPWindowMinVisibleHorizontalMargin is visible on the left
|
|
frame.origin.x = MIN(frame.origin.x, CGRectGetMaxX(usableRect) - minimumSize.width);
|
|
|
|
// Now constrain y so that it is below the top of the usable content
|
|
frame.origin.y = MAX(frame.origin.y, CGRectGetMinY(usableRect));
|
|
|
|
// Finally constrain y so that at least CPWindowMinVisibleHorizontalMargin is visible at the bottom
|
|
frame.origin.y = MIN(frame.origin.y, CGRectGetMaxY(usableRect) - CGRectGetMinY([_contentView frame]) - CPWindowMinVisibleVerticalMargin);
|
|
|
|
return frame;
|
|
}
|
|
|
|
- (void)_moveChildWindows:(CGPoint)delta
|
|
{
|
|
[_childWindows enumerateObjectsUsingBlock:function(childWindow)
|
|
{
|
|
var origin = [childWindow frame].origin;
|
|
|
|
[childWindow setFrameOrigin:CGPointMake(origin.x + delta.x, origin.y + delta.y)];
|
|
}
|
|
];
|
|
}
|
|
|
|
/*!
|
|
Sets the window's frame rect.
|
|
@param aFrame - The new CGRect of the window.
|
|
@param shouldDisplay - YES if the window should call setNeedsDisplay: otherwise NO.
|
|
*/
|
|
- (void)setFrame:(CGRect)aFrame display:(BOOL)shouldDisplay
|
|
{
|
|
[self setFrame:aFrame display:shouldDisplay animate:NO];
|
|
}
|
|
|
|
/*!
|
|
Sets the window's frame rectangle
|
|
@param aFrame - The CGRect of the windows new frame
|
|
*/
|
|
- (void)setFrame:(CGRect)aFrame
|
|
{
|
|
[self setFrame:aFrame display:YES animate:NO];
|
|
}
|
|
|
|
/*!
|
|
Sets the window's location.
|
|
@param anOrigin the new location for the window
|
|
*/
|
|
- (void)setFrameOrigin:(CGPoint)anOrigin
|
|
{
|
|
var frame = [self _constrainOriginOfFrame:CGRectMake(anOrigin.x, anOrigin.y, _frame.size.width, _frame.size.height)];
|
|
[self _setFrame:frame display:YES animate:NO constrainWidth:NO constrainHeight:NO];
|
|
}
|
|
|
|
/*!
|
|
Sets the window's size.
|
|
@param aSize the new size for the window
|
|
*/
|
|
- (void)setFrameSize:(CGSize)aSize
|
|
{
|
|
[self setFrame:CGRectMake(CGRectGetMinX(_frame), CGRectGetMinY(_frame), aSize.width, aSize.height) display:YES animate:NO];
|
|
}
|
|
|
|
/*!
|
|
Makes the receiver the front most window in the screen ordering.
|
|
@param aSender the object that requested this
|
|
*/
|
|
- (void)orderFront:(id)aSender
|
|
{
|
|
[self orderWindow:CPWindowAbove relativeTo:0];
|
|
}
|
|
|
|
- (void)_orderFront
|
|
{
|
|
|
|
#if PLATFORM(DOM)
|
|
|
|
if (!_isVisible)
|
|
[_platformWindow _setShouldUpdateContentRect:NO];
|
|
|
|
// -dw- if a sheet is clicked, the parent window should come up too
|
|
if (_isSheet)
|
|
[_parentView orderFront:self];
|
|
|
|
// Save the boolean since it will be updated in the method order:window:relativeTo:
|
|
var wasVisible = _isVisible;
|
|
|
|
[_platformWindow orderFront:self];
|
|
[_platformWindow order:CPWindowAbove window:self relativeTo:nil];
|
|
|
|
// setFrame is set after ordering the window as this method can send some notifications
|
|
if (!wasVisible)
|
|
[self _setFrame:_frame display:YES animate:NO constrainWidth:YES constrainHeight:YES];
|
|
#endif
|
|
|
|
if (!CPApp._keyWindow)
|
|
[self makeKeyWindow];
|
|
|
|
if ([self isKeyWindow] && (_firstResponder === self || !_firstResponder))
|
|
[self makeFirstResponder:_initialFirstResponder];
|
|
|
|
if (!CPApp._mainWindow)
|
|
[self makeMainWindow];
|
|
|
|
[_platformWindow _setShouldUpdateContentRect:YES];
|
|
|
|
if ([self attachedSheet])
|
|
[_platformWindow order:CPWindowAbove window:[self attachedSheet] relativeTo:nil];
|
|
}
|
|
|
|
/*
|
|
Called when a parent window orders in a child window directly.
|
|
without going through the ordering methods in CPWindow.
|
|
*/
|
|
- (void)_parentDidOrderInChild
|
|
{
|
|
}
|
|
|
|
/*
|
|
Called when the window is displayed in the DOM
|
|
*/
|
|
- (void)_windowWillBeAddedToTheDOM
|
|
{
|
|
[[CPNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(_didReceivePlatformWindowWillCloseNotification:)
|
|
name:_CPPlatformWindowWillCloseNotification
|
|
object:_platformWindow];
|
|
|
|
[[self contentView] _addObservers];
|
|
}
|
|
|
|
/*
|
|
Called when the window is removed in the DOM
|
|
*/
|
|
- (void)_windowWillBeRemovedFromTheDOM
|
|
{
|
|
[[CPNotificationCenter defaultCenter] removeObserver:self name:_CPPlatformWindowWillCloseNotification object:nil];
|
|
|
|
[[self contentView] _removeObservers];
|
|
_hasBecomeKeyWindow = NO;
|
|
}
|
|
|
|
|
|
/*
|
|
Makes the receiver the last window in the screen ordering.
|
|
@param aSender the object that requested this
|
|
@ignore
|
|
*/
|
|
- (void)orderBack:(id)aSender
|
|
{
|
|
[self orderWindow:CPWindowBelow relativeTo:0];
|
|
}
|
|
|
|
- (void)_orderBack
|
|
{
|
|
// FIXME: Implement this
|
|
}
|
|
|
|
/*!
|
|
Hides the window.
|
|
@param the object that requested this
|
|
*/
|
|
- (void)orderOut:(id)aSender
|
|
{
|
|
[self orderWindow:CPWindowOut relativeTo:0];
|
|
}
|
|
|
|
- (void)_orderOutRecursively:(BOOL)recursive
|
|
{
|
|
if (!_isVisible)
|
|
return;
|
|
|
|
if ([self isSheet])
|
|
{
|
|
// -dw- as in Cocoa, orderOut: detaches the sheet and animates out
|
|
[self._parentView _detachSheetWindow];
|
|
return;
|
|
}
|
|
|
|
if (recursive)
|
|
[_childWindows makeObjectsPerformSelector:@selector(_orderOutRecursively:) withObject:recursive];
|
|
|
|
#if PLATFORM(DOM)
|
|
if ([self _sharesChromeWithPlatformWindow])
|
|
[_platformWindow orderOut:self];
|
|
|
|
if (_isFullPlatformWindow && _platformWindow != [CPPlatformWindow primaryPlatformWindow])
|
|
[_platformWindow orderOut:self];
|
|
|
|
[_platformWindow order:CPWindowOut window:self relativeTo:nil];
|
|
#endif
|
|
|
|
[self makeFirstResponder:nil];
|
|
[self _updateMainAndKeyWindows];
|
|
}
|
|
|
|
/*!
|
|
Relocates the window in the screen list.
|
|
@param orderingMode the positioning relative to \c otherWindowNumber
|
|
@param otherWindowNumber the window relative to which the receiver should be placed
|
|
*/
|
|
- (void)orderWindow:(CPWindowOrderingMode)orderingMode relativeTo:(int)otherWindowNumber
|
|
{
|
|
if (orderingMode === CPWindowOut)
|
|
{
|
|
// Directly ordering out will detach a child window
|
|
[_parentWindow removeChildWindow:self];
|
|
|
|
// In Cocoa, a window orders out its child windows only if it has no parent
|
|
[self _orderOutRecursively:!_parentWindow];
|
|
}
|
|
else if (orderingMode === CPWindowAbove && otherWindowNumber === 0)
|
|
[self _orderFront];
|
|
else if (orderingMode === CPWindowBelow && otherWindowNumber === 0)
|
|
[self _orderBack];
|
|
#if PLATFORM(DOM)
|
|
else
|
|
[_platformWindow order:orderingMode window:self relativeTo:CPApp._windows[otherWindowNumber]];
|
|
#endif
|
|
}
|
|
|
|
/*!
|
|
Sets the window's level
|
|
@param the window's new level
|
|
*/
|
|
- (void)setLevel:(int)aLevel
|
|
{
|
|
if (aLevel === _level)
|
|
return;
|
|
|
|
[_platformWindow moveWindow:self fromLevel:_level toLevel:aLevel];
|
|
|
|
_level = aLevel;
|
|
[_childWindows makeObjectsPerformSelector:@selector(setLevel:) withObject:_level];
|
|
|
|
if ([self _sharesChromeWithPlatformWindow])
|
|
[_platformWindow setLevel:aLevel];
|
|
}
|
|
|
|
/*!
|
|
Returns the window's current level
|
|
*/
|
|
- (int)level
|
|
{
|
|
return _level;
|
|
}
|
|
|
|
/*!
|
|
Returns \c YES if the window is visible. It does not mean that the window is not obscured by other windows.
|
|
*/
|
|
- (BOOL)isVisible
|
|
{
|
|
return _isVisible;
|
|
}
|
|
|
|
/*!
|
|
Globally sets whether windows resize following the legacy style (using a resize thumb
|
|
in the bottom right corner), or the modern style (no resize thumb, resizing
|
|
can be done on all edges).
|
|
*/
|
|
+ (void)setGlobalResizeStyle:(int)aStyle
|
|
{
|
|
if (CPWindowResizeStyle === aStyle)
|
|
return;
|
|
|
|
CPWindowResizeStyle = aStyle;
|
|
[[CPNotificationCenter defaultCenter] postNotificationName:CPWindowResizeStyleGlobalChangeNotification object:nil];
|
|
}
|
|
|
|
/*!
|
|
If set to NO, platform window (virtual screen) resizes will not attempt to move/resize user windows.
|
|
to the usable area.
|
|
*/
|
|
+ (void)setConstrainWindowsToUsableScreen:(BOOL)shouldConstrain
|
|
{
|
|
CPWindowConstrainToScreen = shouldConstrain;
|
|
}
|
|
|
|
/*!
|
|
Return whether platform window (virtual screen) resizes constrain user windows to the usable area.
|
|
*/
|
|
+ (BOOL)constrainWindowsToUsableScreen
|
|
{
|
|
return CPWindowConstrainToScreen;
|
|
}
|
|
|
|
- (void)_didReceiveResizeStyleChange:(CPNotification)aNotification
|
|
{
|
|
[_windowView setShowsResizeIndicator:_styleMask & CPResizableWindowMask];
|
|
}
|
|
|
|
/*!
|
|
Returns the global window resizing style.
|
|
*/
|
|
+ (int)globalResizeStyle
|
|
{
|
|
return CPWindowResizeStyle;
|
|
}
|
|
|
|
/*!
|
|
Returns \c YES if the window's resize indicator is showing. \c NO otherwise.
|
|
*/
|
|
- (BOOL)showsResizeIndicator
|
|
{
|
|
return _showsResizeIndicator;
|
|
}
|
|
|
|
/*!
|
|
Sets the window's resize indicator.
|
|
@param shouldShowResizeIndicator \c YES sets the window to show its resize indicator.
|
|
*/
|
|
- (void)setShowsResizeIndicator:(BOOL)shouldShowResizeIndicator
|
|
{
|
|
shouldShowResizeIndicator = !!shouldShowResizeIndicator;
|
|
|
|
if (_showsResizeIndicator === shouldShowResizeIndicator)
|
|
return;
|
|
|
|
_showsResizeIndicator = shouldShowResizeIndicator;
|
|
[_windowView setShowsResizeIndicator:[self showsResizeIndicator]];
|
|
}
|
|
|
|
/*!
|
|
Returns the offset of the window's resize indicator.
|
|
*/
|
|
- (CGSize)resizeIndicatorOffset
|
|
{
|
|
return [_windowView resizeIndicatorOffset];
|
|
}
|
|
|
|
/*!
|
|
Sets the offset of the window's resize indicator.
|
|
@param aSize the offset for the resize indicator
|
|
*/
|
|
- (void)setResizeIndicatorOffset:(CGSize)anOffset
|
|
{
|
|
[_windowView setResizeIndicatorOffset:anOffset];
|
|
}
|
|
|
|
/*!
|
|
Sets the window's content view. The new view will be resized to fit
|
|
inside the content rectangle of the window.
|
|
@param aView the new content view for the receiver
|
|
*/
|
|
- (void)setContentView:(CPView)aView
|
|
{
|
|
if (_contentView && _contentView !== aView)
|
|
[_contentView removeFromSuperview];
|
|
|
|
var bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(_frame), CGRectGetHeight(_frame));
|
|
|
|
_contentView = aView;
|
|
[_contentView setFrame:[self contentRectForFrameRect:bounds]];
|
|
|
|
[_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
|
[_windowView addSubview:_contentView];
|
|
|
|
/*
|
|
If the initial first responder has been set to something other than
|
|
the window, set it to the window because it will no longer be valid.
|
|
*/
|
|
if (_initialFirstResponder && _initialFirstResponder !== self)
|
|
_initialFirstResponder = self;
|
|
}
|
|
|
|
/*!
|
|
Returns the window's current content view.
|
|
*/
|
|
- (CPView)contentView
|
|
{
|
|
return _contentView;
|
|
}
|
|
|
|
/*!
|
|
Applies an alpha value to the window.
|
|
@param aValue the alpha value to apply
|
|
*/
|
|
- (void)setAlphaValue:(float)aValue
|
|
{
|
|
[_windowView setAlphaValue:aValue];
|
|
}
|
|
|
|
/*!
|
|
Returns the alpha value of the window.
|
|
*/
|
|
- (float)alphaValue
|
|
{
|
|
return [_windowView alphaValue];
|
|
}
|
|
|
|
/*!
|
|
Sets the window's background color.
|
|
@param aColor the new color for the background
|
|
*/
|
|
- (void)setBackgroundColor:(CPColor)aColor
|
|
{
|
|
[_windowView setBackgroundColor:aColor];
|
|
}
|
|
|
|
/*!
|
|
Returns the window's background color.
|
|
*/
|
|
- (CPColor)backgroundColor
|
|
{
|
|
return [_windowView backgroundColor];
|
|
}
|
|
|
|
/*!
|
|
Sets the window's minimum size. If the provided
|
|
size is the same as the current minimum size, the method simply returns.
|
|
The height is pinned to the difference in height between the frame rect
|
|
and content rect, to account for a title bar + toolbar.
|
|
@param aSize the new minimum size for the window
|
|
*/
|
|
- (void)setMinSize:(CGSize)aSize
|
|
{
|
|
if (CGSizeEqualToSize(_minSize, aSize))
|
|
return;
|
|
|
|
_minSize = [self _calculateMinSizeForProposedSize:aSize];
|
|
|
|
var size = CGSizeMakeCopy([self frame].size),
|
|
needsFrameChange = NO;
|
|
|
|
if (size.width < _minSize.width)
|
|
{
|
|
size.width = _minSize.width;
|
|
needsFrameChange = YES;
|
|
}
|
|
|
|
if (size.height < _minSize.height)
|
|
{
|
|
size.height = _minSize.height;
|
|
needsFrameChange = YES;
|
|
}
|
|
|
|
if (needsFrameChange)
|
|
[self setFrameSize:size];
|
|
}
|
|
|
|
/*!
|
|
Returns the windows minimum size.
|
|
*/
|
|
- (CGSize)minSize
|
|
{
|
|
return _minSize;
|
|
}
|
|
|
|
/*! @ignore */
|
|
- (CGSize)_calculateMinSizeForProposedSize:(CGSize)proposedSize
|
|
{
|
|
var contentFrame = [self contentRectForFrameRect:_frame],
|
|
minHeight = CGRectGetHeight(_frame) - CGRectGetHeight(contentFrame);
|
|
|
|
return CGSizeMake(MAX(proposedSize.width, 0), MAX(proposedSize.height, minHeight));
|
|
}
|
|
|
|
/*!
|
|
Sets the window's maximum size. If the provided
|
|
size is the same as the current maximum size,
|
|
the method simply returns.
|
|
@param aSize the new maximum size
|
|
*/
|
|
- (void)setMaxSize:(CGSize)aSize
|
|
{
|
|
if (CGSizeEqualToSize(_maxSize, aSize))
|
|
return;
|
|
|
|
_maxSize = CGSizeMakeCopy(aSize);
|
|
|
|
var size = CGSizeMakeCopy([self frame].size),
|
|
needsFrameChange = NO;
|
|
|
|
if (size.width > _maxSize.width)
|
|
{
|
|
size.width = _maxSize.width;
|
|
needsFrameChange = YES;
|
|
}
|
|
|
|
if (size.height > _maxSize.height)
|
|
{
|
|
size.height = _maxSize.height;
|
|
needsFrameChange = YES;
|
|
}
|
|
|
|
if (needsFrameChange)
|
|
[self setFrameSize:size];
|
|
}
|
|
|
|
/*!
|
|
Returns the window's maximum size.
|
|
*/
|
|
- (CGSize)maxSize
|
|
{
|
|
return _maxSize;
|
|
}
|
|
|
|
/*!
|
|
Returns \c YES if the window has a drop shadow. \c NO otherwise.
|
|
*/
|
|
- (BOOL)hasShadow
|
|
{
|
|
return _hasShadow;
|
|
}
|
|
|
|
- (void)_updateShadow
|
|
{
|
|
if ([self _sharesChromeWithPlatformWindow])
|
|
{
|
|
if (_shadowView)
|
|
{
|
|
#if PLATFORM(DOM)
|
|
CPDOMDisplayServerRemoveChild(_DOMElement, _shadowView._DOMElement);
|
|
#endif
|
|
_shadowView = nil;
|
|
}
|
|
|
|
[_platformWindow setHasShadow:_hasShadow];
|
|
|
|
return;
|
|
}
|
|
|
|
if ([[self actualTheme] isCSSBased])
|
|
{
|
|
// When using CSS theming, we get rid of _shadowView and add CSS shadowing directly
|
|
// on the _windowView
|
|
|
|
#if PLATFORM(DOM)
|
|
// We must check that _windowView exists as _updateShadow can be called by
|
|
// _setSharesChromeWithPlatformWindow whereas _windowView is not yet created
|
|
if (_windowView && _windowView._DOMElement)
|
|
{
|
|
var currentBoxShadow = _windowView._DOMElement.style.boxShadow;
|
|
|
|
if (_hasShadow && (currentBoxShadow.length == 0))
|
|
{
|
|
_windowView._DOMElement.style.boxShadow = [_windowView actualValueForThemeAttribute:@"window-shadow-color"];
|
|
}
|
|
else if (!_hasShadow && (currentBoxShadow.length > 0))
|
|
{
|
|
_windowView._DOMElement.style.boxShadow = "";
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
if (_hasShadow && !_shadowView)
|
|
{
|
|
_shadowView = [[_CPShadowWindowView alloc] initWithFrame:CGRectMakeZero()];
|
|
|
|
[_shadowView setWindowView:_windowView];
|
|
[_shadowView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
|
[_shadowView setNeedsLayout];
|
|
|
|
#if PLATFORM(DOM)
|
|
CPDOMDisplayServerInsertBefore(_DOMElement, _shadowView._DOMElement, _windowView._DOMElement);
|
|
#endif
|
|
}
|
|
else if (!_hasShadow && _shadowView)
|
|
{
|
|
#if PLATFORM(DOM)
|
|
CPDOMDisplayServerRemoveChild(_DOMElement, _shadowView._DOMElement);
|
|
#endif
|
|
_shadowView = nil;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Sets whether the window should have a drop shadow.
|
|
@param shouldHaveShadow \c YES to have a drop shadow.
|
|
*/
|
|
- (void)setHasShadow:(BOOL)shouldHaveShadow
|
|
{
|
|
if (_hasShadow === shouldHaveShadow)
|
|
return;
|
|
|
|
_hasShadow = shouldHaveShadow;
|
|
|
|
[self _updateShadow];
|
|
}
|
|
|
|
/*!
|
|
Sets the shadow style of the receiver.
|
|
Values are:
|
|
<pre>
|
|
CPWindowShadowStyleStandard
|
|
CPWindowShadowStyleMenu
|
|
CPWindowShadowStylePanel
|
|
</pre>
|
|
|
|
@param aStyle - The new shadow style of the receiver.
|
|
*/
|
|
- (void)setShadowStyle:(unsigned)aStyle
|
|
{
|
|
_shadowStyle = aStyle;
|
|
|
|
[[self platformWindow] setShadowStyle:_shadowStyle];
|
|
}
|
|
|
|
/*!
|
|
Sets the delegate for the window. Passing \c nil will just remove the window's current delegate.
|
|
@param aDelegate an object to respond to the various delegate methods of CPWindow
|
|
*/
|
|
- (void)setDelegate:(id <CPWindowDelegate>)aDelegate
|
|
{
|
|
if (_delegate === aDelegate)
|
|
return;
|
|
|
|
var defaultCenter = [CPNotificationCenter defaultCenter];
|
|
|
|
[defaultCenter removeObserver:_delegate name:CPWindowDidResignKeyNotification object:self];
|
|
[defaultCenter removeObserver:_delegate name:CPWindowDidBecomeKeyNotification object:self];
|
|
[defaultCenter removeObserver:_delegate name:CPWindowDidBecomeMainNotification object:self];
|
|
[defaultCenter removeObserver:_delegate name:CPWindowDidResignMainNotification object:self];
|
|
[defaultCenter removeObserver:_delegate name:CPWindowDidMoveNotification object:self];
|
|
[defaultCenter removeObserver:_delegate name:CPWindowDidResizeNotification object:self];
|
|
[defaultCenter removeObserver:_delegate name:CPWindowWillBeginSheetNotification object:self];
|
|
[defaultCenter removeObserver:_delegate name:CPWindowDidEndSheetNotification object:self];
|
|
[defaultCenter removeObserver:_delegate name:CPWindowDidMiniaturizeNotification object:self];
|
|
[defaultCenter removeObserver:_delegate name:CPWindowDidDeminiaturizeNotification object:self];
|
|
[defaultCenter removeObserver:_delegate name:CPWindowWillMiniaturizeNotification object:self];
|
|
|
|
_delegate = aDelegate;
|
|
_implementedDelegateMethods = 0;
|
|
|
|
if ([_delegate respondsToSelector:@selector(windowShouldClose:)])
|
|
_implementedDelegateMethods |= CPWindowDelegate_windowShouldClose_;
|
|
|
|
if ([_delegate respondsToSelector:@selector(windowWillReturnUndoManager:)])
|
|
_implementedDelegateMethods |= CPWindowDelegate_windowWillReturnUndoManager_;
|
|
|
|
if ([_delegate respondsToSelector:@selector(windowWillClose:)])
|
|
_implementedDelegateMethods |= CPWindowDelegate_windowWillClose_;
|
|
|
|
if ([_delegate respondsToSelector:@selector(windowWillResize:toSize:)])
|
|
_implementedDelegateMethods |= CPWindowDelegate_windowWillResize_toSize_;
|
|
|
|
if ([_delegate respondsToSelector:@selector(windowDidResignKey:)])
|
|
[defaultCenter
|
|
addObserver:_delegate
|
|
selector:@selector(windowDidResignKey:)
|
|
name:CPWindowDidResignKeyNotification
|
|
object:self];
|
|
|
|
if ([_delegate respondsToSelector:@selector(windowDidBecomeKey:)])
|
|
[defaultCenter
|
|
addObserver:_delegate
|
|
selector:@selector(windowDidBecomeKey:)
|
|
name:CPWindowDidBecomeKeyNotification
|
|
object:self];
|
|
|
|
if ([_delegate respondsToSelector:@selector(windowDidBecomeMain:)])
|
|
[defaultCenter
|
|
addObserver:_delegate
|
|
selector:@selector(windowDidBecomeMain:)
|
|
name:CPWindowDidBecomeMainNotification
|
|
object:self];
|
|
|
|
if ([_delegate respondsToSelector:@selector(windowDidResignMain:)])
|
|
[defaultCenter
|
|
addObserver:_delegate
|
|
selector:@selector(windowDidResignMain:)
|
|
name:CPWindowDidResignMainNotification
|
|
object:self];
|
|
|
|
if ([_delegate respondsToSelector:@selector(windowDidMove:)])
|
|
[defaultCenter
|
|
addObserver:_delegate
|
|
selector:@selector(windowDidMove:)
|
|
name:CPWindowDidMoveNotification
|
|
object:self];
|
|
|
|
if ([_delegate respondsToSelector:@selector(windowDidResize:)])
|
|
[defaultCenter
|
|
addObserver:_delegate
|
|
selector:@selector(windowDidResize:)
|
|
name:CPWindowDidResizeNotification
|
|
object:self];
|
|
|
|
if ([_delegate respondsToSelector:@selector(windowWillBeginSheet:)])
|
|
[defaultCenter
|
|
addObserver:_delegate
|
|
selector:@selector(windowWillBeginSheet:)
|
|
name:CPWindowWillBeginSheetNotification
|
|
object:self];
|
|
|
|
if ([_delegate respondsToSelector:@selector(windowDidEndSheet:)])
|
|
[defaultCenter
|
|
addObserver:_delegate
|
|
selector:@selector(windowDidEndSheet:)
|
|
name:CPWindowDidEndSheetNotification
|
|
object:self];
|
|
|
|
if ([_delegate respondsToSelector:@selector(windowDidMiniaturize:)])
|
|
[defaultCenter
|
|
addObserver:_delegate
|
|
selector:@selector(windowDidMiniaturize:)
|
|
name:CPWindowDidMiniaturizeNotification
|
|
object:self];
|
|
|
|
if ([_delegate respondsToSelector:@selector(windowWillMiniaturize:)])
|
|
[defaultCenter
|
|
addObserver:_delegate
|
|
selector:@selector(windowWillMiniaturize:)
|
|
name:CPWindowWillMiniaturizeNotification
|
|
object:self];
|
|
|
|
if ([_delegate respondsToSelector:@selector(windowDidDeminiaturize:)])
|
|
[defaultCenter
|
|
addObserver:_delegate
|
|
selector:@selector(windowDidDeminiaturize:)
|
|
name:CPWindowDidDeminiaturizeNotification
|
|
object:self];
|
|
}
|
|
|
|
/*!
|
|
Returns window's delegate
|
|
*/
|
|
- (id)delegate
|
|
{
|
|
return _delegate;
|
|
}
|
|
|
|
/*!
|
|
Sets the window's controller
|
|
@param aWindowController a window controller
|
|
*/
|
|
- (void)setWindowController:(CPWindowController)aWindowController
|
|
{
|
|
_windowController = aWindowController;
|
|
}
|
|
|
|
/*!
|
|
Returns the window's controller.
|
|
*/
|
|
- (CPWindowController)windowController
|
|
{
|
|
return _windowController;
|
|
}
|
|
|
|
- (void)doCommandBySelector:(SEL)aSelector
|
|
{
|
|
if ([_delegate respondsToSelector:aSelector])
|
|
[_delegate performSelector:aSelector];
|
|
else
|
|
[super doCommandBySelector:aSelector];
|
|
}
|
|
|
|
- (BOOL)acceptsFirstResponder
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
- (CPView)initialFirstResponder
|
|
{
|
|
return _initialFirstResponder;
|
|
}
|
|
|
|
- (void)setInitialFirstResponder:(CPView)aView
|
|
{
|
|
_initialFirstResponder = aView;
|
|
}
|
|
|
|
- (void)_setupFirstResponder
|
|
{
|
|
/*
|
|
When the window is first made the key window, if the first responder is the window, use the initial first responder if there is one. If there is a first responder and it is not the window, ignore the initial first responder.
|
|
*/
|
|
if (!_hasBecomeKeyWindow)
|
|
{
|
|
if (_firstResponder === self)
|
|
{
|
|
if (_initialFirstResponder)
|
|
[self makeFirstResponder:_initialFirstResponder];
|
|
else
|
|
{
|
|
// Make the first valid key view the first responder
|
|
var view = [_contentView nextValidKeyView];
|
|
|
|
if (view)
|
|
[self makeFirstResponder:view];
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (_firstResponder)
|
|
[self makeFirstResponder:_firstResponder];
|
|
}
|
|
|
|
/*!
|
|
Attempts to make the \c aResponder the first responder. Before trying
|
|
to make it the first responder, the receiver will ask the current first responder
|
|
to resign its first responder status. If it resigns, it will ask
|
|
\c aResponder accept first responder, then finally tell it to become first responder.
|
|
@return \c YES if the attempt was successful. \c NO otherwise.
|
|
*/
|
|
- (BOOL)makeFirstResponder:(CPResponder)aResponder
|
|
{
|
|
if (_firstResponder === aResponder)
|
|
return YES;
|
|
|
|
if (![_firstResponder resignFirstResponder])
|
|
return NO;
|
|
|
|
if (!aResponder || ![aResponder acceptsFirstResponder] || ![aResponder becomeFirstResponder])
|
|
{
|
|
_firstResponder = self;
|
|
|
|
return NO;
|
|
}
|
|
|
|
_firstResponder = aResponder;
|
|
|
|
[[CPNotificationCenter defaultCenter] postNotificationName:_CPWindowDidChangeFirstResponderNotification object:self];
|
|
|
|
return YES;
|
|
}
|
|
|
|
/*!
|
|
Returns the window's current first responder.
|
|
*/
|
|
- (CPResponder)firstResponder
|
|
{
|
|
return _firstResponder;
|
|
}
|
|
|
|
- (BOOL)acceptsMouseMovedEvents
|
|
{
|
|
return _acceptsMouseMovedEvents;
|
|
}
|
|
|
|
- (void)setAcceptsMouseMovedEvents:(BOOL)shouldAcceptMouseMovedEvents
|
|
{
|
|
_acceptsMouseMovedEvents = shouldAcceptMouseMovedEvents;
|
|
}
|
|
|
|
- (BOOL)ignoresMouseEvents
|
|
{
|
|
return _ignoresMouseEvents;
|
|
}
|
|
|
|
- (void)setIgnoresMouseEvents:(BOOL)shouldIgnoreMouseEvents
|
|
{
|
|
_ignoresMouseEvents = shouldIgnoreMouseEvents;
|
|
}
|
|
|
|
- (void)_mouseExitedResizeRect
|
|
{
|
|
[[CPCursor arrowCursor] set];
|
|
}
|
|
|
|
// Managing Titles
|
|
|
|
/*!
|
|
Returns the window's title bar string
|
|
*/
|
|
- (CPString)title
|
|
{
|
|
return _title;
|
|
}
|
|
|
|
/*!
|
|
Sets the window's title bar string
|
|
*/
|
|
- (void)setTitle:(CPString)aTitle
|
|
{
|
|
_title = aTitle;
|
|
|
|
[_windowView setTitle:aTitle];
|
|
[_platformWindow _setTitle:_title window:self];
|
|
}
|
|
|
|
/*!
|
|
Sets the title bar to represent a file path
|
|
*/
|
|
- (void)setTitleWithRepresentedFilename:(CPString)aFilePath
|
|
{
|
|
[self setRepresentedFilename:aFilePath];
|
|
[self setTitle:[aFilePath lastPathComponent]];
|
|
}
|
|
|
|
/*!
|
|
Sets the path to the file the receiver represents
|
|
*/
|
|
- (void)setRepresentedFilename:(CPString)aFilePath
|
|
{
|
|
// FIXME: urls vs filepaths and all.
|
|
[self setRepresentedURL:[CPURL URLWithString:aFilePath]];
|
|
}
|
|
|
|
/*!
|
|
Returns the path to the file the receiver represents
|
|
*/
|
|
- (CPString)representedFilename
|
|
{
|
|
return [_representedURL absoluteString];
|
|
}
|
|
|
|
/*!
|
|
Sets the URL that the receiver represents
|
|
*/
|
|
- (void)setRepresentedURL:(CPURL)aURL
|
|
{
|
|
_representedURL = aURL;
|
|
}
|
|
|
|
/*!
|
|
Returns the URL that the receiver represents
|
|
*/
|
|
- (CPURL)representedURL
|
|
{
|
|
return _representedURL;
|
|
}
|
|
|
|
- (CPScreen)screen
|
|
{
|
|
return [[CPScreen alloc] init];
|
|
}
|
|
|
|
// Moving
|
|
|
|
/*!
|
|
Sets whether the window can be moved by dragging its background. The default is based on the window style.
|
|
@param shouldBeMovableByWindowBackground \c YES makes the window move from a background drag.
|
|
*/
|
|
- (void)setMovableByWindowBackground:(BOOL)shouldBeMovableByWindowBackground
|
|
{
|
|
_isMovableByWindowBackground = shouldBeMovableByWindowBackground;
|
|
}
|
|
|
|
/*!
|
|
Returns \c YES if the window can be moved by dragging its background.
|
|
*/
|
|
- (BOOL)isMovableByWindowBackground
|
|
{
|
|
return _isMovableByWindowBackground;
|
|
}
|
|
|
|
/*!
|
|
Sets whether the window can be moved.
|
|
@param shouldBeMovable \c YES makes the window movable.
|
|
*/
|
|
- (void)setMovable:(BOOL)shouldBeMovable
|
|
{
|
|
_isMovable = shouldBeMovable;
|
|
}
|
|
|
|
/*!
|
|
Returns \c YES if the window can be moved.
|
|
*/
|
|
- (BOOL)isMovable
|
|
{
|
|
return _isMovable;
|
|
}
|
|
|
|
/*!
|
|
Sets the window location to be the center of the screen
|
|
*/
|
|
- (void)center
|
|
{
|
|
if (_isFullPlatformWindow)
|
|
return;
|
|
|
|
var size = [self frame].size,
|
|
containerSize = [CPPlatform isBrowser] ? [_platformWindow contentBounds].size : [[self screen] visibleFrame].size;
|
|
|
|
var origin = CGPointMake((containerSize.width - size.width) / 2.0, (containerSize.height - size.height) / 2.0);
|
|
|
|
if (origin.x < 0.0)
|
|
origin.x = 0.0;
|
|
|
|
if (origin.y < 0.0)
|
|
origin.y = 0.0;
|
|
|
|
[self setFrameOrigin:origin];
|
|
}
|
|
|
|
/*!
|
|
Dispatches events that are sent to it from CPApplication.
|
|
@param anEvent the event to be dispatched
|
|
*/
|
|
- (void)sendEvent:(CPEvent)anEvent
|
|
{
|
|
var type = [anEvent type],
|
|
sheet = [self attachedSheet];
|
|
|
|
// If a sheet is attached events get filtered here.
|
|
// It is not clear what events should be passed to the view, perhaps all?
|
|
// CPLeftMouseDown is needed for window moving and resizing to work.
|
|
// CPMouseMoved is needed for rollover effects on title bar buttons.
|
|
|
|
// ignore events that happen during open / close animations
|
|
// not ignoring these would cause the app to freeze thereafter
|
|
if (sheet && (_sheetContext["isClosing"] || _sheetContext["isOpening"]))
|
|
return;
|
|
|
|
if (sheet && _sheetContext["isAttached"])
|
|
{
|
|
switch (type)
|
|
{
|
|
case CPLeftMouseDown:
|
|
|
|
[_windowView mouseDown:anEvent];
|
|
|
|
// -dw- if the window is clicked, the sheet should come to front, and become key,
|
|
// and the window should be immediately behind
|
|
[sheet makeKeyAndOrderFront:self];
|
|
|
|
return;
|
|
|
|
case CPMouseMoved:
|
|
// Allow these through to the parent
|
|
break;
|
|
|
|
default:
|
|
// Everything else is filtered
|
|
return;
|
|
}
|
|
}
|
|
|
|
var point = [anEvent locationInWindow];
|
|
|
|
switch (type)
|
|
{
|
|
case CPAppKitDefined:
|
|
return [CPApp activateIgnoringOtherApps:YES];
|
|
|
|
case CPFlagsChanged:
|
|
return [[self firstResponder] flagsChanged:anEvent];
|
|
|
|
case CPKeyUp:
|
|
return [[self firstResponder] keyUp:anEvent];
|
|
|
|
case CPKeyDown:
|
|
if ([anEvent charactersIgnoringModifiers] === CPTabCharacter)
|
|
{
|
|
if ([anEvent modifierFlags] & CPShiftKeyMask)
|
|
[self selectPreviousKeyView:self];
|
|
else
|
|
[self selectNextKeyView:self];
|
|
#if PLATFORM(DOM)
|
|
// Make sure the browser doesn't try to do its own tab handling.
|
|
// This is important or the browser might blur the shared text field or token field input field,
|
|
// even that we just moved it to a new first responder.
|
|
[[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO];
|
|
#endif
|
|
return;
|
|
}
|
|
else if ([anEvent charactersIgnoringModifiers] === CPBackTabCharacter)
|
|
{
|
|
var didTabBack = [self selectPreviousKeyView:self];
|
|
|
|
if (didTabBack)
|
|
{
|
|
#if PLATFORM(DOM)
|
|
// Make sure the browser doesn't try to do its own tab handling.
|
|
// This is important or the browser might blur the shared text field or token field input field,
|
|
// even that we just moved it to a new first responder.
|
|
[[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO];
|
|
#endif
|
|
|
|
}
|
|
return didTabBack;
|
|
}
|
|
else if ([anEvent charactersIgnoringModifiers] == CPEscapeFunctionKey && [self _processKeyboardUIKey:anEvent])
|
|
{
|
|
return;
|
|
}
|
|
|
|
[[self firstResponder] keyDown:anEvent];
|
|
|
|
// Trigger the default button if needed
|
|
if (_defaultButtonEnabled && !_defaultButtonDisabledTemporarily)
|
|
{
|
|
var defaultButton = [self defaultButton],
|
|
keyEquivalent = [defaultButton keyEquivalent],
|
|
modifierMask = [defaultButton keyEquivalentModifierMask];
|
|
|
|
if ([anEvent _triggersKeyEquivalent:keyEquivalent withModifierMask:modifierMask])
|
|
[[self defaultButton] performClick:self];
|
|
}
|
|
|
|
_defaultButtonDisabledTemporarily = NO;
|
|
|
|
return;
|
|
|
|
case CPScrollWheel:
|
|
return [[_windowView hitTest:point] scrollWheel:anEvent];
|
|
|
|
case CPLeftMouseUp:
|
|
case CPRightMouseUp:
|
|
var hitTestedView = _leftMouseDownView,
|
|
selector = type == CPRightMouseUp ? @selector(rightMouseUp:) : @selector(mouseUp:);
|
|
|
|
if (!hitTestedView)
|
|
hitTestedView = [_windowView hitTest:point];
|
|
|
|
[hitTestedView performSelector:selector withObject:anEvent];
|
|
|
|
_leftMouseDownView = nil;
|
|
|
|
// If mouseUp ends a drag operation, send delayed events for tracking views under the mouse, then flush delayed events
|
|
[self _flushTrackingEventQueueForMouseAt:point];
|
|
|
|
return;
|
|
|
|
case CPLeftMouseDown:
|
|
case CPRightMouseDown:
|
|
// This will return _windowView if it is within a resize region
|
|
_leftMouseDownView = [_windowView hitTest:point];
|
|
|
|
if (_leftMouseDownView !== _firstResponder && [_leftMouseDownView acceptsFirstResponder])
|
|
[self makeFirstResponder:_leftMouseDownView];
|
|
|
|
var keyWindow = [CPApp keyWindow];
|
|
|
|
// This is only when we move from a platform to another one
|
|
if ([keyWindow platformWindow] != [self platformWindow])
|
|
[self makeKeyAndOrderFront:self];
|
|
|
|
[CPApp activateIgnoringOtherApps:YES];
|
|
|
|
var theWindow = [anEvent window],
|
|
selector = type == CPRightMouseDown ? @selector(rightMouseDown:) : @selector(mouseDown:);
|
|
|
|
if (([theWindow _isFrontmostWindow] && [theWindow isKeyWindow]) || ([theWindow becomesKeyOnlyIfNeeded] && ![_leftMouseDownView needsPanelToBecomeKey]))
|
|
return [_leftMouseDownView performSelector:selector withObject:anEvent];
|
|
else
|
|
{
|
|
// FIXME: delayed ordering?
|
|
[self makeKeyAndOrderFront:self];
|
|
|
|
if ([_leftMouseDownView acceptsFirstMouse:anEvent])
|
|
return [_leftMouseDownView performSelector:selector withObject:anEvent];
|
|
}
|
|
break;
|
|
|
|
case CPLeftMouseDragged:
|
|
case CPRightMouseDragged:
|
|
// First, we search for any tracking area requesting CPTrackingEnabledDuringMouseDrag.
|
|
// At the same time, we update the entered stack.
|
|
[self _handleTrackingAreaEvent:anEvent];
|
|
|
|
// Normal mouseDragged workflow
|
|
if (!_leftMouseDownView)
|
|
return [[_windowView hitTest:point] mouseDragged:anEvent];
|
|
|
|
var selector;
|
|
|
|
if (type == CPRightMouseDragged)
|
|
{
|
|
selector = @selector(rightMouseDragged:);
|
|
if (![_leftMouseDownView respondsToSelector:selector])
|
|
selector = nil;
|
|
}
|
|
|
|
if (!selector)
|
|
selector = @selector(mouseDragged:);
|
|
|
|
return [_leftMouseDownView performSelector:selector withObject:anEvent];
|
|
|
|
case CPMouseMoved:
|
|
|
|
// Ignore mouse moves for parents of sheets
|
|
if (!_acceptsMouseMovedEvents || sheet)
|
|
return;
|
|
|
|
[self _handleTrackingAreaEvent:anEvent];
|
|
}
|
|
}
|
|
|
|
- (void)_startLiveResize
|
|
{
|
|
[[CPNotificationCenter defaultCenter]
|
|
postNotificationName:CPWindowWillStartLiveResizeNotification
|
|
object:self];
|
|
}
|
|
|
|
- (void)_endLiveResize
|
|
{
|
|
[[CPNotificationCenter defaultCenter]
|
|
postNotificationName:CPWindowDidEndLiveResizeNotification
|
|
object:self];
|
|
}
|
|
|
|
- (BOOL)_inLiveResize
|
|
{
|
|
return [_windowView _isTracking];
|
|
}
|
|
|
|
/*!
|
|
Returns the window's number in the desktop's screen list
|
|
*/
|
|
- (int)windowNumber
|
|
{
|
|
return _windowNumber;
|
|
}
|
|
|
|
/*!
|
|
Called when the receiver should become the key window. It sends
|
|
the \c -becomeKeyWindow message to the first responder if it responds,
|
|
and posts \c CPWindowDidBecomeKeyNotification.
|
|
*/
|
|
- (void)becomeKeyWindow
|
|
{
|
|
CPApp._keyWindow = self;
|
|
|
|
if (_firstResponder !== self && [_firstResponder respondsToSelector:@selector(becomeKeyWindow)])
|
|
[_firstResponder becomeKeyWindow];
|
|
|
|
if (!_hasBecomeKeyWindow)
|
|
{
|
|
// The first time a window is loaded, if it does not have a key view loop
|
|
// established, calculate it now.
|
|
if (![self _hasKeyViewLoop:[_contentView subviews]])
|
|
[self recalculateKeyViewLoop];
|
|
}
|
|
|
|
[self _setupFirstResponder];
|
|
_hasBecomeKeyWindow = YES;
|
|
_platformWindow._currentKeyWindow = self;
|
|
|
|
[_windowView noteKeyWindowStateChanged];
|
|
[_contentView _notifyWindowDidBecomeKey];
|
|
|
|
[[CPNotificationCenter defaultCenter]
|
|
postNotificationName:CPWindowDidBecomeKeyNotification
|
|
object:self];
|
|
}
|
|
|
|
/*!
|
|
Determines if the window can become the key window.
|
|
@return \c YES means the window can become the key window.
|
|
*/
|
|
- (BOOL)canBecomeKeyWindow
|
|
{
|
|
/*
|
|
In Cocoa only titled windows return YES here by default. But the main browser
|
|
window in Cappuccino doesn't have a title bar even that it's both titled and
|
|
resizable, so we return YES when isFullPlatformWindow too.
|
|
|
|
Note that Cocoa will return NO for a non-titled, resizable window. The Cocoa documention
|
|
says it will return YES if there is a "resize bar", but in practice
|
|
that is not the same as the resizable mask.
|
|
*/
|
|
return (_styleMask & CPTitledWindowMask) || [self isFullPlatformWindow] || _isSheet;
|
|
}
|
|
|
|
/* @ignore */
|
|
- (BOOL)_isFrontmostWindow
|
|
{
|
|
if ([self isFullBridge])
|
|
return YES;
|
|
|
|
var orderedWindows = [CPApp orderedWindows];
|
|
|
|
if ([orderedWindows count] == 0)
|
|
return YES;
|
|
|
|
if ([orderedWindows objectAtIndex:0] === self)
|
|
return YES;
|
|
|
|
// this is necessary, because the CPMainMenuWindow is always the first object in orderedWindows, even if another window is in front of it
|
|
if ([[orderedWindows objectAtIndex:0] level] === CPMainMenuWindowLevel &&
|
|
[orderedWindows count] > 1 && [orderedWindows objectAtIndex:1] === self)
|
|
return YES;
|
|
|
|
return NO;
|
|
}
|
|
|
|
/*!
|
|
Returns \c YES if the window is the key window.
|
|
*/
|
|
- (BOOL)isKeyWindow
|
|
{
|
|
return [CPApp keyWindow] == self;
|
|
}
|
|
|
|
/*!
|
|
Makes the window the key window and brings it to the front of the screen list.
|
|
@param aSender the object requesting this
|
|
*/
|
|
- (void)makeKeyAndOrderFront:(id)aSender
|
|
{
|
|
[self orderFront:self];
|
|
|
|
[self makeKeyWindow];
|
|
[self makeMainWindow];
|
|
}
|
|
|
|
/*!
|
|
Makes this window the key window.
|
|
*/
|
|
- (void)makeKeyWindow
|
|
{
|
|
if ([CPApp keyWindow] === self || ![self canBecomeKeyWindow])
|
|
return;
|
|
|
|
[[CPApp keyWindow] resignKeyWindow];
|
|
[self becomeKeyWindow];
|
|
}
|
|
|
|
/*!
|
|
Causes the window to resign it's key window status.
|
|
*/
|
|
- (void)resignKeyWindow
|
|
{
|
|
if (_firstResponder !== self && [_firstResponder respondsToSelector:@selector(resignKeyWindow)])
|
|
[_firstResponder resignKeyWindow];
|
|
|
|
if (CPApp._keyWindow === self)
|
|
CPApp._keyWindow = nil;
|
|
|
|
_platformWindow._currentKeyWindow = nil;
|
|
[_windowView noteKeyWindowStateChanged];
|
|
[_contentView _notifyWindowDidResignKey];
|
|
|
|
[[CPNotificationCenter defaultCenter]
|
|
postNotificationName:CPWindowDidResignKeyNotification
|
|
object:self];
|
|
}
|
|
|
|
/*!
|
|
Initiates a drag operation from the receiver to another view that accepts dragged data.
|
|
@param anImage the image to be dragged
|
|
@param aLocation the lower-left corner coordinate of \c anImage
|
|
@param mouseOffset the distance from the \c -mouseDown: location and the current location
|
|
@param anEvent the \c -mouseDown: that triggered the drag
|
|
@param aPasteboard the pasteboard that holds the drag data
|
|
@param aSourceObject the drag operation controller
|
|
@param slideBack Whether the image should 'slide back' if the drag is rejected
|
|
*/
|
|
- (void)dragImage:(CPImage)anImage at:(CGPoint)imageLocation offset:(CGSize)mouseOffset event:(CPEvent)anEvent pasteboard:(CPPasteboard)aPasteboard source:(id)aSourceObject slideBack:(BOOL)slideBack
|
|
{
|
|
[[CPDragServer sharedDragServer] dragImage:anImage fromWindow:self at:[self convertBaseToGlobal:imageLocation] offset:mouseOffset event:anEvent pasteboard:aPasteboard source:aSourceObject slideBack:slideBack];
|
|
}
|
|
|
|
- (void)_noteRegisteredDraggedTypes:(CPSet)pasteboardTypes
|
|
{
|
|
if (!pasteboardTypes)
|
|
return;
|
|
|
|
if (!_inclusiveRegisteredDraggedTypes)
|
|
_inclusiveRegisteredDraggedTypes = [CPCountedSet set];
|
|
|
|
[_inclusiveRegisteredDraggedTypes unionSet:pasteboardTypes];
|
|
}
|
|
|
|
- (void)_noteUnregisteredDraggedTypes:(CPSet)pasteboardTypes
|
|
{
|
|
if (!pasteboardTypes)
|
|
return;
|
|
|
|
[_inclusiveRegisteredDraggedTypes minusSet:pasteboardTypes];
|
|
|
|
if ([_inclusiveRegisteredDraggedTypes count] === 0)
|
|
_inclusiveRegisteredDraggedTypes = nil;
|
|
}
|
|
|
|
/*!
|
|
Initiates a drag operation from the receiver to another view that accepts dragged data.
|
|
@param aView the view to be dragged
|
|
@param aLocation the lower-left corner coordinate of \c aView
|
|
@param mouseOffset the distance from the \c -mouseDown: location and the current location
|
|
@param anEvent the \c -mouseDown: that triggered the drag
|
|
@param aPasteboard the pasteboard that holds the drag data
|
|
@param aSourceObject the drag operation controller
|
|
@param slideBack Whether the view should 'slide back' if the drag is rejected
|
|
*/
|
|
- (void)dragView:(CPView)aView at:(CGPoint)viewLocation offset:(CGSize)mouseOffset event:(CPEvent)anEvent pasteboard:(CPPasteboard)aPasteboard source:(id)aSourceObject slideBack:(BOOL)slideBack
|
|
{
|
|
[[CPDragServer sharedDragServer] dragView:aView fromWindow:self at:[self convertBaseToGlobal:viewLocation] offset:mouseOffset event:anEvent pasteboard:aPasteboard source:aSourceObject slideBack:slideBack];
|
|
}
|
|
|
|
/*!
|
|
Sets the receiver's list of acceptable data types for a dragging operation.
|
|
@param pasteboardTypes an array of CPPasteboards
|
|
*/
|
|
- (void)registerForDraggedTypes:(CPArray)pasteboardTypes
|
|
{
|
|
if (!pasteboardTypes)
|
|
return;
|
|
|
|
[self _noteUnregisteredDraggedTypes:_registeredDraggedTypes];
|
|
[_registeredDraggedTypes addObjectsFromArray:pasteboardTypes];
|
|
[self _noteRegisteredDraggedTypes:_registeredDraggedTypes];
|
|
|
|
_registeredDraggedTypesArray = nil;
|
|
}
|
|
|
|
/*!
|
|
Returns an array of all types the receiver accepts for dragging operations.
|
|
@return an array of CPPasteBoards
|
|
*/
|
|
- (CPArray)registeredDraggedTypes
|
|
{
|
|
if (!_registeredDraggedTypesArray)
|
|
_registeredDraggedTypesArray = [_registeredDraggedTypes allObjects];
|
|
|
|
return _registeredDraggedTypesArray;
|
|
}
|
|
|
|
/*!
|
|
Resets the array of acceptable data types for a dragging operation.
|
|
*/
|
|
- (void)unregisterDraggedTypes
|
|
{
|
|
[self _noteUnregisteredDraggedTypes:_registeredDraggedTypes];
|
|
|
|
_registeredDraggedTypes = [CPSet set];
|
|
_registeredDraggedTypesArray = [];
|
|
}
|
|
|
|
// Accessing Editing Status
|
|
|
|
/*!
|
|
Sets whether the document has been edited.
|
|
@param isDocumentEdited \c YES if the document has been edited.
|
|
*/
|
|
- (void)setDocumentEdited:(BOOL)isDocumentEdited
|
|
{
|
|
if (_isDocumentEdited == isDocumentEdited)
|
|
return;
|
|
|
|
_isDocumentEdited = isDocumentEdited;
|
|
|
|
[CPMenu _setMenuBarIconImageAlphaValue:_isDocumentEdited ? 0.5 : 1.0];
|
|
|
|
[_windowView setDocumentEdited:isDocumentEdited];
|
|
}
|
|
|
|
/*!
|
|
Returns \c YES if the document has been edited.
|
|
*/
|
|
- (BOOL)isDocumentEdited
|
|
{
|
|
return _isDocumentEdited;
|
|
}
|
|
|
|
- (void)setDocumentSaving:(BOOL)isDocumentSaving
|
|
{
|
|
if (_isDocumentSaving == isDocumentSaving)
|
|
return;
|
|
|
|
_isDocumentSaving = isDocumentSaving;
|
|
|
|
[self _synchronizeSaveMenuWithDocumentSaving];
|
|
|
|
[_windowView windowDidChangeDocumentSaving];
|
|
}
|
|
|
|
- (BOOL)isDocumentSaving
|
|
{
|
|
return _isDocumentSaving;
|
|
}
|
|
|
|
/* @ignore */
|
|
- (void)_synchronizeSaveMenuWithDocumentSaving
|
|
{
|
|
if (![self isMainWindow])
|
|
return;
|
|
|
|
var mainMenu = [CPApp mainMenu],
|
|
index = [mainMenu indexOfItemWithTitle:_isDocumentSaving ? @"Save" : @"Saving..."];
|
|
|
|
if (index == CPNotFound)
|
|
return;
|
|
|
|
var item = [mainMenu itemAtIndex:index];
|
|
|
|
if (_isDocumentSaving)
|
|
{
|
|
CPWindowSaveImage = [item image];
|
|
|
|
[item setTitle:@"Saving..."];
|
|
[item setImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"spinning-regular-gif" forClass:CPProgressIndicator]];
|
|
[item setEnabled:NO];
|
|
}
|
|
else
|
|
{
|
|
[item setTitle:@"Save"];
|
|
[item setImage:CPWindowSaveImage];
|
|
[item setEnabled:YES];
|
|
}
|
|
}
|
|
|
|
// Minimizing Windows
|
|
|
|
/*!
|
|
Simulates the user minimizing the window, then minimizes the window.
|
|
@param aSender the object making this request
|
|
*/
|
|
- (void)performMiniaturize:(id)aSender
|
|
{
|
|
//FIXME show stuff
|
|
[self miniaturize:aSender];
|
|
}
|
|
|
|
/*!
|
|
Minimizes the window. Posts a \c CPWindowWillMiniaturizeNotification to the
|
|
notification center before minimizing the window.
|
|
*/
|
|
- (void)miniaturize:(id)sender
|
|
{
|
|
[[CPNotificationCenter defaultCenter] postNotificationName:CPWindowWillMiniaturizeNotification object:self];
|
|
|
|
[[self platformWindow] miniaturize:sender];
|
|
|
|
[self _updateMainAndKeyWindows];
|
|
|
|
[[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidMiniaturizeNotification object:self];
|
|
|
|
_isMiniaturized = YES;
|
|
}
|
|
|
|
/*!
|
|
Restores a minimized window to it's original size.
|
|
*/
|
|
- (void)deminiaturize:(id)sender
|
|
{
|
|
[[self platformWindow] deminiaturize:sender];
|
|
|
|
[[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidDeminiaturizeNotification object:self];
|
|
|
|
_isMiniaturized = NO;
|
|
}
|
|
|
|
/*!
|
|
Returns YES if the window is minimized.
|
|
*/
|
|
- (BOOL)isMiniaturized
|
|
{
|
|
return _isMiniaturized;
|
|
}
|
|
|
|
// Closing Windows
|
|
|
|
/*!
|
|
Simulates the user closing the window, then closes the window.
|
|
@param aSender the object making this request
|
|
*/
|
|
- (void)performClose:(id)aSender
|
|
{
|
|
if (!(_styleMask & CPClosableWindowMask))
|
|
return;
|
|
|
|
if ([self isFullPlatformWindow])
|
|
{
|
|
var event = [CPApp currentEvent];
|
|
|
|
if ([event type] === CPKeyDown && [event characters] === "w" && ([event modifierFlags] & CPPlatformActionKeyMask))
|
|
{
|
|
[[self platformWindow] _propagateCurrentDOMEvent:YES];
|
|
return;
|
|
}
|
|
}
|
|
|
|
// The Cocoa docs say that if both the delegate and the window implement
|
|
// windowShouldClose:, only the delegate receives the message.
|
|
if ([self _delegateRespondsToWindowShouldClose])
|
|
{
|
|
if (![self _sendDelegateWindowShouldClose])
|
|
return;
|
|
}
|
|
else if ([self respondsToSelector:@selector(windowShouldClose:)])
|
|
{
|
|
if (![self windowShouldClose:self])
|
|
return;
|
|
}
|
|
|
|
var documents = [_windowController documents];
|
|
|
|
if ([documents count])
|
|
{
|
|
var index = [documents indexOfObject:[_windowController document]];
|
|
|
|
[documents[index] shouldCloseWindowController:_windowController
|
|
delegate:self
|
|
shouldCloseSelector:@selector(_windowControllerContainingDocument:shouldClose:contextInfo:)
|
|
contextInfo:{documents:[documents copy], visited:0, index:index}];
|
|
}
|
|
else
|
|
[self close];
|
|
}
|
|
|
|
- (void)_windowControllerContainingDocument:(CPDocument)document shouldClose:(BOOL)shouldClose contextInfo:(Object)context
|
|
{
|
|
if (shouldClose)
|
|
{
|
|
var windowController = [self windowController],
|
|
documents = context.documents,
|
|
count = [documents count],
|
|
visited = ++context.visited,
|
|
index = ++context.index % count;
|
|
|
|
[document removeWindowController:windowController];
|
|
|
|
if (visited < count)
|
|
{
|
|
[windowController setDocument:documents[index]];
|
|
|
|
[documents[index] shouldCloseWindowController:_windowController
|
|
delegate:self
|
|
shouldCloseSelector:@selector(_windowControllerContainingDocument:shouldClose:contextInfo:)
|
|
contextInfo:context];
|
|
}
|
|
else
|
|
[self close];
|
|
}
|
|
}
|
|
|
|
/*!
|
|
Closes the window. Posts a \c CPWindowWillCloseNotification to the
|
|
notification center before closing the window.
|
|
*/
|
|
- (void)close
|
|
{
|
|
[self _sendDelegateWindowWillClose];
|
|
|
|
[[CPNotificationCenter defaultCenter] postNotificationName:CPWindowWillCloseNotification object:self];
|
|
|
|
[_parentWindow removeChildWindow:self];
|
|
[self _orderOutRecursively:NO];
|
|
[self _detachFromChildrenClosing:!_parentWindow];
|
|
}
|
|
|
|
- (void)_detachFromChildrenClosing:(BOOL)shouldCloseChildren
|
|
{
|
|
// When a window is closed, it must detach itself from all children
|
|
[_childWindows enumerateObjectsUsingBlock:function(child)
|
|
{
|
|
[child setParentWindow:nil];
|
|
}
|
|
];
|
|
|
|
if (shouldCloseChildren)
|
|
{
|
|
[_childWindows enumerateObjectsUsingBlock:function(child)
|
|
{
|
|
// Cocoa does NOT call close or orderOut when closing child windows,
|
|
// they are summarily closed.
|
|
[child _orderOutRecursively:NO];
|
|
[child _detachFromChildrenClosing:![child parentWindow]];
|
|
}
|
|
];
|
|
}
|
|
|
|
_childWindows = [];
|
|
}
|
|
|
|
// Managing Main Status
|
|
/*!
|
|
Returns \c YES if this the main window.
|
|
*/
|
|
- (BOOL)isMainWindow
|
|
{
|
|
return [CPApp mainWindow] === self;
|
|
}
|
|
|
|
/*!
|
|
Returns \c YES if the window can become the main window.
|
|
*/
|
|
- (BOOL)canBecomeMainWindow
|
|
{
|
|
// Note that the Cocoa documentation says that this method returns YES if
|
|
// the window is visible and has a title bar or a "resize mechanism". It turns
|
|
// out a "resize mechanism" is not the same as having the resize mask set.
|
|
// In practice a window must have a title bar to become main, but we make
|
|
// an exception for a full platform window.
|
|
return ([self isVisible] && ((_styleMask & CPTitledWindowMask) || _isFullPlatformWindow));
|
|
}
|
|
|
|
/*!
|
|
Makes the receiver the main window.
|
|
*/
|
|
- (void)makeMainWindow
|
|
{
|
|
// Sheets cannot be main. Their parent window becomes main.
|
|
if (_isSheet)
|
|
{
|
|
[_parentView makeMainWindow];
|
|
return;
|
|
}
|
|
|
|
if ([CPApp mainWindow] === self || ![self canBecomeMainWindow])
|
|
return;
|
|
|
|
[[CPApp mainWindow] resignMainWindow];
|
|
[self becomeMainWindow];
|
|
}
|
|
|
|
/*!
|
|
Called to tell the receiver that it has become the main window.
|
|
*/
|
|
- (void)becomeMainWindow
|
|
{
|
|
CPApp._mainWindow = self;
|
|
_platformWindow._currentMainWindow = self;
|
|
|
|
[self _synchronizeSaveMenuWithDocumentSaving];
|
|
|
|
[_windowView noteMainWindowStateChanged];
|
|
|
|
[[CPNotificationCenter defaultCenter]
|
|
postNotificationName:CPWindowDidBecomeMainNotification
|
|
object:self];
|
|
}
|
|
|
|
/*!
|
|
Called when the window resigns main window status.
|
|
*/
|
|
- (void)resignMainWindow
|
|
{
|
|
[[CPNotificationCenter defaultCenter]
|
|
postNotificationName:CPWindowDidResignMainNotification
|
|
object:self];
|
|
|
|
if (CPApp._mainWindow === self)
|
|
CPApp._mainWindow = nil;
|
|
|
|
_platformWindow._currentMainWindow = nil;
|
|
[_windowView noteMainWindowStateChanged];
|
|
}
|
|
|
|
- (void)_updateMainAndKeyWindows
|
|
{
|
|
var allWindows = [CPApp orderedWindows],
|
|
windowCount = [allWindows count];
|
|
|
|
if ([self isKeyWindow])
|
|
{
|
|
var keyWindow = [CPApp keyWindow];
|
|
[self resignKeyWindow];
|
|
|
|
if (keyWindow && keyWindow !== self && [keyWindow canBecomeKeyWindow])
|
|
[keyWindow makeKeyWindow];
|
|
else
|
|
{
|
|
var mainMenu = [CPApp mainMenu],
|
|
menuBarClass = objj_getClass("_CPMenuBarWindow"),
|
|
menuWindow;
|
|
|
|
for (var i = 0; i < windowCount; i++)
|
|
{
|
|
var currentWindow = allWindows[i];
|
|
|
|
if ([currentWindow isKindOfClass:menuBarClass])
|
|
menuWindow = currentWindow;
|
|
|
|
if (currentWindow === self || currentWindow === menuWindow)
|
|
continue;
|
|
|
|
if ([currentWindow isVisible] && [currentWindow canBecomeKeyWindow] && [currentWindow platformWindow] == [keyWindow platformWindow])
|
|
{
|
|
[currentWindow makeKeyWindow];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (![CPApp keyWindow])
|
|
[menuWindow makeKeyWindow];
|
|
}
|
|
}
|
|
|
|
if ([self isMainWindow])
|
|
{
|
|
var mainWindow = [CPApp mainWindow];
|
|
[self resignMainWindow];
|
|
|
|
if (mainWindow && mainWindow !== self && [mainWindow canBecomeMainWindow])
|
|
[mainWindow makeMainWindow];
|
|
else
|
|
{
|
|
var mainMenu = [CPApp mainMenu],
|
|
menuBarClass = objj_getClass("_CPMenuBarWindow"),
|
|
menuWindow;
|
|
|
|
for (var i = 0; i < windowCount; i++)
|
|
{
|
|
var currentWindow = allWindows[i];
|
|
|
|
if ([currentWindow isKindOfClass:menuBarClass])
|
|
menuWindow = currentWindow;
|
|
|
|
if (currentWindow === self || currentWindow === menuWindow)
|
|
continue;
|
|
|
|
if ([currentWindow isVisible] && [currentWindow canBecomeMainWindow])
|
|
{
|
|
[currentWindow makeMainWindow];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Managing Toolbars
|
|
/*!
|
|
Return's the window's toolbar
|
|
*/
|
|
- (CPToolbar)toolbar
|
|
{
|
|
return _toolbar;
|
|
}
|
|
|
|
/*!
|
|
Sets the window's toolbar.
|
|
@param aToolbar the window's new toolbar
|
|
*/
|
|
- (void)setToolbar:(CPToolbar)aToolbar
|
|
{
|
|
if (_toolbar === aToolbar)
|
|
return;
|
|
|
|
// If this has an owner, dump it!
|
|
[[aToolbar _window] setToolbar:nil];
|
|
|
|
// This is no longer our toolbar.
|
|
[_toolbar _setWindow:nil];
|
|
|
|
_toolbar = aToolbar;
|
|
|
|
// THIS is our toolbar.
|
|
[_toolbar _setWindow:self];
|
|
|
|
[self _noteToolbarChanged];
|
|
}
|
|
|
|
- (void)toggleToolbarShown:(id)aSender
|
|
{
|
|
var toolbar = [self toolbar];
|
|
|
|
[toolbar setVisible:![toolbar isVisible]];
|
|
}
|
|
|
|
- (void)_noteToolbarChanged
|
|
{
|
|
var frame = CGRectMakeCopy([self frame]),
|
|
newFrame;
|
|
|
|
[_windowView noteToolbarChanged];
|
|
|
|
if (_isFullPlatformWindow)
|
|
newFrame = [_platformWindow visibleFrame];
|
|
else
|
|
{
|
|
newFrame = CGRectMakeCopy([self frame]);
|
|
|
|
newFrame.origin = frame.origin;
|
|
}
|
|
|
|
[self setFrame:newFrame];
|
|
/*
|
|
[_windowView setAnimatingToolbar:YES];
|
|
[self setFrame:frame];
|
|
[self setFrame:newFrame display:YES animate:YES];
|
|
[_windowView setAnimatingToolbar:NO];
|
|
*/
|
|
}
|
|
|
|
/*!
|
|
Do NOT modify the array returned by this method!
|
|
*/
|
|
- (CPArray)childWindows
|
|
{
|
|
return _childWindows;
|
|
}
|
|
|
|
- (void)addChildWindow:(CPWindow)childWindow ordered:(CPWindowOrderingMode)orderingMode
|
|
{
|
|
// Don't add the child if it is already in our list
|
|
if ([_childWindows indexOfObject:childWindow] >= 0)
|
|
return;
|
|
|
|
if (orderingMode === CPWindowAbove || orderingMode === CPWindowBelow)
|
|
[_childWindows addObject:childWindow];
|
|
else
|
|
[CPException raise:CPInvalidArgumentException
|
|
reason:_cmd + @" unrecognized ordering mode " + orderingMode];
|
|
|
|
[childWindow setParentWindow:self];
|
|
[childWindow _setChildOrdering:orderingMode];
|
|
[childWindow setLevel:[self level]];
|
|
|
|
if ([self isVisible] && ![childWindow isVisible])
|
|
[childWindow orderWindow:orderingMode relativeTo:_windowNumber];
|
|
}
|
|
|
|
- (void)removeChildWindow:(CPWindow)childWindow
|
|
{
|
|
var index = [_childWindows indexOfObject:childWindow];
|
|
|
|
if (index === CPNotFound)
|
|
return;
|
|
|
|
[_childWindows removeObjectAtIndex:index];
|
|
[childWindow setParentWindow:nil];
|
|
}
|
|
|
|
- (CPWindow)parentWindow
|
|
{
|
|
return _parentWindow;
|
|
}
|
|
|
|
/*!
|
|
Return YES if anAncestor is the parent or a higher ancestor of the receiver.
|
|
|
|
@ignore
|
|
*/
|
|
- (BOOL)_hasAncestorWindow:(CPWindow)anAncestor
|
|
{
|
|
if (!_parentWindow || !anAncestor)
|
|
return NO;
|
|
|
|
if (anAncestor === _parentWindow)
|
|
return YES;
|
|
|
|
return [_parentWindow _hasAncestorWindow:anAncestor];
|
|
}
|
|
|
|
- (CPWindow)setParentWindow:(CPWindow)parentWindow
|
|
{
|
|
_parentWindow = parentWindow;
|
|
}
|
|
|
|
- (void)_setFrame:(CGRect)aFrame delegate:(id)delegate duration:(int)duration curve:(CPAnimationCurve)curve
|
|
{
|
|
[_frameAnimation stopAnimation];
|
|
_frameAnimation = [[_CPWindowFrameAnimation alloc] initWithWindow:self targetFrame:aFrame];
|
|
[_frameAnimation setDelegate:delegate];
|
|
[_frameAnimation setAnimationCurve:curve];
|
|
[_frameAnimation setDuration:duration];
|
|
[_frameAnimation startAnimation];
|
|
}
|
|
|
|
- (CPTimeInterval)animationResizeTime:(CGRect)newWindowFrame
|
|
{
|
|
return CPWindowResizeTime;
|
|
}
|
|
|
|
- (void)_setAttachedSheetFrameOrigin
|
|
{
|
|
// Position the sheet above the contentRect.
|
|
var attachedSheet = [self attachedSheet],
|
|
contentRect = [_contentView frame],
|
|
sheetFrame = CGRectMakeCopy([attachedSheet frame]);
|
|
|
|
sheetFrame.origin.y = [[attachedSheet _windowView] _sheetVerticalOffset]+ CGRectGetMinY(_frame) + CGRectGetMinY(contentRect);
|
|
sheetFrame.origin.x = CGRectGetMinX(_frame) + FLOOR((CGRectGetWidth(_frame) - CGRectGetWidth(sheetFrame)) / 2.0);
|
|
|
|
[attachedSheet setFrame:sheetFrame display:YES animate:NO];
|
|
}
|
|
|
|
- (void)_previousSheetIsClosedNotification:(CPNotification)aNotification
|
|
{
|
|
[[CPNotificationCenter defaultCenter] removeObserver:self name:CPWindowDidEndSheetNotification object:self];
|
|
|
|
var sheet = _sheetContext[@"nextSheet"],
|
|
modalDelegate = _sheetContext[@"nextModalDelegate"],
|
|
endSelector = _sheetContext[@"nextEndSelector"],
|
|
contextInfo = _sheetContext[@"nextContextInfo"];
|
|
|
|
// Needed, because when the notification CPWindowDidEndSheetNotification is sent, the sheetContext is not up to date...
|
|
setTimeout(function()
|
|
{
|
|
[sheet._windowView _enableSheet:YES inWindow:self];
|
|
[self _attachSheet:sheet modalDelegate:modalDelegate didEndSelector:endSelector contextInfo:contextInfo];
|
|
}, 0)
|
|
}
|
|
|
|
/*
|
|
Starting point for sheet session, called from CPApplication beginSheet:
|
|
*/
|
|
- (void)_attachSheet:(CPWindow)aSheet modalDelegate:(id)aModalDelegate
|
|
didEndSelector:(SEL)didEndSelector contextInfo:(id)contextInfo
|
|
{
|
|
if (_sheetContext)
|
|
{
|
|
// Here we wait till the current sheet is closed
|
|
if (_sheetContext[@"isClosing"])
|
|
{
|
|
// Here we save the next sheet to open
|
|
_sheetContext[@"nextSheet"] = aSheet;
|
|
_sheetContext[@"nextModalDelegate"] = aModalDelegate;
|
|
_sheetContext[@"nextEndSelector"] = didEndSelector;
|
|
_sheetContext[@"nextContextInfo"] = contextInfo;
|
|
|
|
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_previousSheetIsClosedNotification:) name:CPWindowDidEndSheetNotification object:self];
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
[CPException raise:CPInternalInconsistencyException
|
|
reason:@"The target window of beginSheet: already has a sheet, did you forget orderOut: ?"];
|
|
return;
|
|
}
|
|
}
|
|
|
|
_sheetContext = {
|
|
"sheet": aSheet,
|
|
"modalDelegate": aModalDelegate,
|
|
"endSelector": didEndSelector,
|
|
"contextInfo": contextInfo,
|
|
"returnCode": -1,
|
|
"opened": NO,
|
|
"isAttached": YES,
|
|
"savedConstrains": aSheet._constrainsToUsableScreen
|
|
};
|
|
|
|
// Sheets are not constrained, they are controlled by their parent
|
|
aSheet._constrainsToUsableScreen = NO;
|
|
|
|
// A timer seems to be necessary for the animation to work correctly
|
|
[CPTimer scheduledTimerWithTimeInterval:0.0
|
|
target:self
|
|
selector:@selector(_sheetShouldAnimateIn:)
|
|
userInfo:nil
|
|
repeats:NO];
|
|
}
|
|
|
|
/*
|
|
Called to end the sheet. Note that orderOut: is needed to animate the sheet out, as in Cocoa.
|
|
The sheet isn't completely gone until _cleanupSheetWindow gets called.
|
|
*/
|
|
- (void)_endSheet
|
|
{
|
|
var delegate = _sheetContext["modalDelegate"],
|
|
endSelector = _sheetContext["endSelector"];
|
|
|
|
// If the sheet has been ordered out, defer didEndSelector until after sheet animates out.
|
|
// This must be done since we cannot block and wait for the animation to complete.
|
|
if (delegate && endSelector)
|
|
{
|
|
if (_sheetContext["isAttached"])
|
|
delegate.isa.objj_msgSend3(delegate, endSelector, _sheetContext["sheet"], _sheetContext["returnCode"],
|
|
_sheetContext["contextInfo"]);
|
|
else
|
|
_sheetContext["deferDidEndSelector"] = YES;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Called to animate the sheet out. If called while animating in, schedules an animate
|
|
out at completion
|
|
*/
|
|
- (void)_detachSheetWindow
|
|
{
|
|
if (_sheetContext["isClosing"])
|
|
return;
|
|
|
|
_sheetContext["isAttached"] = NO;
|
|
_sheetContext["isClosing"] = YES;
|
|
_sheetContext["opened"] = NO;
|
|
|
|
// A timer seems to be necessary for the animation to work correctly.
|
|
// It would be ideal to block here and spin the event loop, until attach is complete.
|
|
[CPTimer scheduledTimerWithTimeInterval:0.0
|
|
target:self
|
|
selector:@selector(_sheetShouldAnimateOut:)
|
|
userInfo:nil
|
|
repeats:NO];
|
|
}
|
|
|
|
/*
|
|
Called to cleanup sheet, when we are definitely done with it
|
|
*/
|
|
- (void)_cleanupSheetWindow
|
|
{
|
|
var sheet = _sheetContext["sheet"],
|
|
deferDidEnd = _sheetContext["deferDidEndSelector"];
|
|
|
|
// If the parent window is modal, the sheet started its own modal session
|
|
if (sheet._isModal)
|
|
[CPApp stopModal];
|
|
|
|
[self _removeClipForSheet:sheet];
|
|
|
|
// Restore the state of window before it was sheetified
|
|
sheet._isSheet = NO;
|
|
[sheet._windowView _enableSheet:NO inWindow:self];
|
|
sheet._constrainsToUsableScreen = _sheetContext["savedConstrains"];
|
|
|
|
// Close it
|
|
[sheet orderOut:self];
|
|
|
|
[[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidEndSheetNotification object:self];
|
|
|
|
if (deferDidEnd)
|
|
{
|
|
var delegate = _sheetContext["modalDelegate"],
|
|
selector = _sheetContext["endSelector"],
|
|
returnCode = _sheetContext["returnCode"],
|
|
contextInfo = _sheetContext["contextInfo"];
|
|
|
|
// Context must be destroyed, since didEnd might want to attach another sheet
|
|
_sheetContext = nil;
|
|
sheet._parentView = nil;
|
|
|
|
if (delegate != null)
|
|
delegate.isa.objj_msgSend3(delegate, selector, sheet, returnCode, contextInfo);
|
|
}
|
|
else
|
|
{
|
|
_sheetContext = nil;
|
|
sheet._parentView = nil;
|
|
}
|
|
}
|
|
|
|
/* @ignore */
|
|
- (void)animationDidEnd:(id)anim
|
|
{
|
|
var sheet = _sheetContext["sheet"];
|
|
|
|
if (anim._window != sheet)
|
|
return;
|
|
|
|
[CPTimer scheduledTimerWithTimeInterval:0.0
|
|
target:self
|
|
selector:@selector(_sheetAnimationDidEnd:)
|
|
userInfo:nil
|
|
repeats:NO];
|
|
}
|
|
|
|
- (void)_sheetShouldAnimateIn:(CPTimer)timer
|
|
{
|
|
// Can't open sheet while opening or closing animation is going on
|
|
if (_sheetContext["isOpening"] || _sheetContext["isClosing"])
|
|
return;
|
|
|
|
var sheet = _sheetContext["sheet"];
|
|
sheet._isSheet = YES;
|
|
sheet._parentView = self;
|
|
|
|
[[CPNotificationCenter defaultCenter] postNotificationName:CPWindowWillBeginSheetNotification object:self];
|
|
|
|
// If sheet is attached to a modal window, the sheet runs
|
|
// as if itself and the parent window are modal
|
|
sheet._isModal = NO;
|
|
|
|
if ([CPApp modalWindow] === self)
|
|
{
|
|
[CPApp runModalForWindow:sheet];
|
|
sheet._isModal = YES;
|
|
}
|
|
|
|
// The sheet starts hidden just above the top of a clip rect
|
|
var sheetFrame = [sheet frame],
|
|
sheetShadowFrame = [sheet _shadowFrame],
|
|
frame = [self frame],
|
|
originX = frame.origin.x + FLOOR((frame.size.width - sheetFrame.size.width) / 2),
|
|
startFrame = CGRectMake(originX, -sheetShadowFrame.size.height, sheetFrame.size.width, sheetFrame.size.height),
|
|
endY = [[sheet _windowView] _sheetVerticalOffset] + [_windowView bodyOffset] - [[self contentView] frame].origin.y,
|
|
endFrame = CGRectMake(originX, endY, sheetFrame.size.width, sheetFrame.size.height);
|
|
|
|
if (_toolbar && [_windowView showsToolbar] && [self isFullPlatformWindow])
|
|
{
|
|
endY += [[_toolbar _toolbarView] frameSize].height;
|
|
endFrame = CGRectMake(originX, endY, sheetFrame.size.width, sheetFrame.size.height);
|
|
}
|
|
|
|
// Move the sheet offscreen before ordering front so it doesn't appear briefly
|
|
[sheet setFrameOrigin:CGPointMake(0, -13000)];
|
|
|
|
// Because clipping does funny thing with the DOM, we have to orderFront before clipping
|
|
[sheet orderFront:self];
|
|
[self _clipSheet:sheet];
|
|
|
|
[sheet setFrame:startFrame display:YES animate:NO];
|
|
|
|
_sheetContext["opened"] = YES;
|
|
_sheetContext["shouldClose"] = NO;
|
|
_sheetContext["isOpening"] = YES;
|
|
|
|
[sheet _setFrame:endFrame delegate:self duration:[self animationResizeTime:endFrame] curve:CPAnimationEaseOut];
|
|
}
|
|
|
|
- (void)_sheetShouldAnimateOut:(CPTimer)timer
|
|
{
|
|
if (_sheetContext["isOpening"])
|
|
{
|
|
// Allow sheet to be closed while opening, it will close when animate in completes
|
|
_sheetContext["shouldClose"] = YES;
|
|
return;
|
|
}
|
|
|
|
// The parent window can be orderedOut to disable the sheet animate out, as in Cocoa
|
|
if ([self isVisible])
|
|
{
|
|
var sheet = _sheetContext["sheet"],
|
|
sheetFrame = [sheet frame],
|
|
fullHeight = [sheet _shadowFrame].size.height,
|
|
endFrame = CGRectMakeCopy(sheetFrame),
|
|
contentOrigin = [self convertBaseToGlobal:[[self contentView] frame].origin];
|
|
|
|
// Don't constrain sheets, they are controlled by the parent
|
|
sheet._constrainsToUsableScreen = NO;
|
|
|
|
[sheet setFrameOrigin:CGPointMake(sheetFrame.origin.x, sheetFrame.origin.y - contentOrigin.y)];
|
|
[self _clipSheet:sheet];
|
|
|
|
endFrame.origin.y = -fullHeight;
|
|
[sheet _setFrame:endFrame delegate:self duration:[self animationResizeTime:endFrame] curve:CPAnimationEaseIn];
|
|
}
|
|
else
|
|
{
|
|
[self _sheetAnimationDidEnd:nil];
|
|
}
|
|
}
|
|
|
|
- (void)_sheetAnimationDidEnd:(CPTimer)timer
|
|
{
|
|
var sheet = _sheetContext["sheet"];
|
|
|
|
_sheetContext["isOpening"] = NO;
|
|
_sheetContext["isClosing"] = NO;
|
|
|
|
if (_sheetContext["opened"] === YES)
|
|
{
|
|
var sheetFrame = [sheet frame],
|
|
sheetOrigin = CGPointMakeCopy(sheetFrame.origin);
|
|
|
|
[self _removeClipForSheet:sheet];
|
|
[self _setAttachedSheetFrameOrigin];
|
|
|
|
// we wanted to close the sheet while it animated in, do that now
|
|
if (_sheetContext["shouldClose"] === YES)
|
|
[self _detachSheetWindow];
|
|
else
|
|
[sheet makeKeyWindow];
|
|
}
|
|
else
|
|
{
|
|
// sheet is closed and not visible
|
|
[self _cleanupSheetWindow];
|
|
}
|
|
}
|
|
|
|
- (void)_clipSheet:(CPWindow)aSheet
|
|
{
|
|
var clipRect = [_platformWindow contentBounds];
|
|
clipRect.origin.y = [self frame].origin.y + [[self contentView] frame].origin.y;
|
|
|
|
[[_platformWindow layerAtLevel:_level create:NO] clipWindow:aSheet toRect:clipRect];
|
|
}
|
|
|
|
- (void)_removeClipForSheet:(CPWindow)aSheet
|
|
{
|
|
[[_platformWindow layerAtLevel:_level create:NO] removeClipForWindow:aSheet];
|
|
}
|
|
|
|
/*!
|
|
Returns the window's attached sheet.
|
|
*/
|
|
- (CPWindow)attachedSheet
|
|
{
|
|
if (_sheetContext == nil)
|
|
return nil;
|
|
|
|
return _sheetContext["sheet"];
|
|
}
|
|
|
|
/*!
|
|
Returns \c YES if the window has ever run as a sheet.
|
|
*/
|
|
- (BOOL)isSheet
|
|
{
|
|
return _isSheet;
|
|
}
|
|
|
|
- (CGRect)_shadowFrame
|
|
{
|
|
if (_hasShadow)
|
|
return _shadowView ? [_shadowView frame] : [self frame];
|
|
|
|
return [self frame];
|
|
}
|
|
|
|
//
|
|
/*
|
|
Used privately.
|
|
@ignore
|
|
*/
|
|
- (BOOL)becomesKeyOnlyIfNeeded
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
/*!
|
|
Returns \c YES if the receiver is able to receive input events
|
|
even when a modal session is active.
|
|
*/
|
|
- (BOOL)worksWhenModal
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL)performKeyEquivalent:(CPEvent)anEvent
|
|
{
|
|
// FIXME: should we be starting at the root, in other words _windowView?
|
|
// The evidence seems to point to no...
|
|
return [_contentView performKeyEquivalent:anEvent];
|
|
}
|
|
|
|
- (void)keyDown:(CPEvent)anEvent
|
|
{
|
|
// It's not clear why we do performKeyEquivalent again here...
|
|
// Perhaps to allow something to happen between sendEvent: and keyDown:?
|
|
if ([anEvent _couldBeKeyEquivalent] && [self performKeyEquivalent:anEvent])
|
|
return;
|
|
|
|
// Apple's documentation is inconsistent with their behavior here. According to the docs
|
|
// an event going of the responder chain is passed to the input system as a last resort.
|
|
// However, the only methods I could get Cocoa to call automatically are
|
|
// moveUp: moveDown: moveLeft: moveRight: pageUp: pageDown: and complete:
|
|
// Unhandled events just travel further up the responder chain _past_ the window.
|
|
if (![self _processKeyboardUIKey:anEvent])
|
|
[super keyDown:anEvent];
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
Interprets the key event for action messages and sends the action message down the responder chain
|
|
Cocoa only sends moveDown:, moveUp:, moveLeft:, moveRight:, pageUp:, pageDown: and complete: messages.
|
|
We deviate from this by sending (the default) scrollPageUp:, scrollPageDown:, scrollToBeginningOfDocument: and scrollToEndOfDocument: for pageUp, pageDown, home and end keys.
|
|
@param anEvent the event to handle.
|
|
@return YES if the key event was handled, NO if no responder handled the key event
|
|
*/
|
|
- (BOOL)_processKeyboardUIKey:(CPEvent)anEvent
|
|
{
|
|
var character = [anEvent charactersIgnoringModifiers];
|
|
|
|
if (![CPWindowActionMessageKeys containsObject:character])
|
|
return NO;
|
|
|
|
var selectors = [CPKeyBinding selectorsForKey:character modifierFlags:0];
|
|
|
|
if ([selectors count] <= 0)
|
|
return NO;
|
|
|
|
var selector = [selectors objectAtIndex:0];
|
|
|
|
return [[self firstResponder] tryToPerform:selector with:self];
|
|
}
|
|
|
|
- (void)_dirtyKeyViewLoop
|
|
{
|
|
if (_autorecalculatesKeyViewLoop)
|
|
_keyViewLoopIsDirty = YES;
|
|
}
|
|
|
|
/*
|
|
Recursively traverse an array of views (depth last) until we find one that has a next or previous key view set. Return nil if none can be found.
|
|
|
|
We don't use _viewsSortedByPosition here because it is wasteful to enumerate the entire view hierarchy when we will probably find a key view at the top level.
|
|
*/
|
|
- (BOOL)_hasKeyViewLoop:(CPArray)theViews
|
|
{
|
|
var i,
|
|
count = [theViews count];
|
|
|
|
for (i = 0; i < count; ++i)
|
|
{
|
|
var view = theViews[i];
|
|
|
|
if ([view nextKeyView] || [view previousKeyView])
|
|
return YES;
|
|
}
|
|
|
|
for (i = 0; i < count; ++i)
|
|
{
|
|
var subviews = [theViews[i] subviews];
|
|
|
|
if ([subviews count] && [self _hasKeyViewLoop:subviews])
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
/*!
|
|
Recalculates the key view loop, based on geometric position.
|
|
Note that the Cocoa documentation says that this method only marks the loop
|
|
as dirty, the recalculation is not done until the next or previous key view
|
|
of the window is requested. In reality, Cocoa does recalculate the loop
|
|
when this method is called.
|
|
*/
|
|
- (void)recalculateKeyViewLoop
|
|
{
|
|
[self _doRecalculateKeyViewLoop];
|
|
}
|
|
|
|
- (CPArray)_viewsSortedByPosition
|
|
{
|
|
var views = [CPArray arrayWithObject:_contentView];
|
|
|
|
views = views.concat([self _subviewsSortedByPosition:[_contentView subviews]]);
|
|
|
|
return views;
|
|
}
|
|
|
|
- (CPArray)_subviewsSortedByPosition:(CPArray)theSubviews
|
|
{
|
|
/*
|
|
We first sort the subviews according to geometric order.
|
|
Then we go through each subview, and if it has subviews,
|
|
they are sorted and inserted after the superview. This
|
|
is done recursively.
|
|
*/
|
|
theSubviews = [theSubviews copy];
|
|
[theSubviews sortUsingFunction:keyViewComparator context:nil];
|
|
|
|
var sortedViews = [];
|
|
|
|
for (var i = 0, count = [theSubviews count]; i < count; ++i)
|
|
{
|
|
var view = theSubviews[i],
|
|
subviews = [view subviews];
|
|
|
|
sortedViews.push(view);
|
|
|
|
if ([subviews count])
|
|
sortedViews = sortedViews.concat([self _subviewsSortedByPosition:subviews]);
|
|
}
|
|
|
|
return sortedViews;
|
|
}
|
|
|
|
- (void)_doRecalculateKeyViewLoop
|
|
{
|
|
var views = [self _viewsSortedByPosition];
|
|
|
|
for (var index = 0, count = [views count]; index < count; ++index)
|
|
[views[index] setNextKeyView:views[(index + 1) % count]];
|
|
|
|
_keyViewLoopIsDirty = NO;
|
|
}
|
|
|
|
- (void)setAutorecalculatesKeyViewLoop:(BOOL)shouldRecalculate
|
|
{
|
|
if (_autorecalculatesKeyViewLoop === shouldRecalculate)
|
|
return;
|
|
|
|
_autorecalculatesKeyViewLoop = shouldRecalculate;
|
|
}
|
|
|
|
- (BOOL)autorecalculatesKeyViewLoop
|
|
{
|
|
return _autorecalculatesKeyViewLoop;
|
|
}
|
|
|
|
- (void)selectNextKeyView:(id)sender
|
|
{
|
|
if (_keyViewLoopIsDirty)
|
|
[self _doRecalculateKeyViewLoop];
|
|
|
|
var nextValidKeyView = nil;
|
|
|
|
if ([_firstResponder isKindOfClass:[CPView class]])
|
|
nextValidKeyView = [_firstResponder nextValidKeyView];
|
|
|
|
if (!nextValidKeyView)
|
|
{
|
|
if ([_initialFirstResponder acceptsFirstResponder])
|
|
nextValidKeyView = _initialFirstResponder;
|
|
else
|
|
nextValidKeyView = [_initialFirstResponder nextValidKeyView];
|
|
}
|
|
|
|
if (nextValidKeyView)
|
|
[self makeFirstResponder:nextValidKeyView];
|
|
}
|
|
|
|
- (void)selectPreviousKeyView:(id)sender
|
|
{
|
|
if (_keyViewLoopIsDirty)
|
|
[self _doRecalculateKeyViewLoop];
|
|
|
|
var previousValidKeyView = nil;
|
|
|
|
if ([_firstResponder isKindOfClass:[CPView class]])
|
|
previousValidKeyView = [_firstResponder previousValidKeyView];
|
|
|
|
if (!previousValidKeyView)
|
|
{
|
|
if ([_initialFirstResponder acceptsFirstResponder])
|
|
previousValidKeyView = _initialFirstResponder;
|
|
else
|
|
previousValidKeyView = [_initialFirstResponder previousValidKeyView];
|
|
}
|
|
|
|
if (previousValidKeyView)
|
|
[self makeFirstResponder:previousValidKeyView];
|
|
}
|
|
|
|
- (void)selectKeyViewFollowingView:(CPView)aView
|
|
{
|
|
if (_keyViewLoopIsDirty)
|
|
[self _doRecalculateKeyViewLoop];
|
|
|
|
var nextValidKeyView = [aView nextValidKeyView];
|
|
|
|
if ([nextValidKeyView isKindOfClass:[CPView class]])
|
|
[self makeFirstResponder:nextValidKeyView];
|
|
}
|
|
|
|
- (void)selectKeyViewPrecedingView:(CPView)aView
|
|
{
|
|
if (_keyViewLoopIsDirty)
|
|
[self _doRecalculateKeyViewLoop];
|
|
|
|
var previousValidKeyView = [aView previousValidKeyView];
|
|
|
|
if ([previousValidKeyView isKindOfClass:[CPView class]])
|
|
[self makeFirstResponder:previousValidKeyView];
|
|
}
|
|
|
|
/*!
|
|
Sets the default button for the window.
|
|
Note: this method is deprecated use setDefaultButton: instead.
|
|
@param aButton - The button that should become default.
|
|
*/
|
|
- (void)setDefaultButtonCell:(CPButton)aButton
|
|
{
|
|
[self setDefaultButton:aButton];
|
|
}
|
|
|
|
/*!
|
|
Returns the default button of the receiver.
|
|
NOTE: This method is deprecated. Use defaultButton instead.
|
|
*/
|
|
- (CPButton)defaultButtonCell
|
|
{
|
|
return [self defaultButton];
|
|
}
|
|
|
|
/*!
|
|
Sets the default button for the window.
|
|
This is equivalent to setting the the key equivalent of the button to "return".
|
|
Additionally this will turn your button blue (with the Aristo theme).
|
|
@param aButton - The button that should become default.
|
|
*/
|
|
- (void)setDefaultButton:(CPButton)aButton
|
|
{
|
|
if (_defaultButton === aButton)
|
|
return;
|
|
|
|
if ([_defaultButton keyEquivalent] === CPCarriageReturnCharacter)
|
|
[_defaultButton setKeyEquivalent:nil];
|
|
|
|
_defaultButton = aButton;
|
|
|
|
if ([_defaultButton keyEquivalent] !== CPCarriageReturnCharacter)
|
|
[_defaultButton setKeyEquivalent:CPCarriageReturnCharacter];
|
|
}
|
|
|
|
/*!
|
|
Returns the default button of the receiver.
|
|
*/
|
|
- (CPButton)defaultButton
|
|
{
|
|
return _defaultButton;
|
|
}
|
|
|
|
/*!
|
|
Sets the default button key equivalent to "return".
|
|
*/
|
|
- (void)enableKeyEquivalentForDefaultButton
|
|
{
|
|
_defaultButtonEnabled = YES;
|
|
}
|
|
|
|
/*!
|
|
Sets the default button key equivalent to "return".
|
|
NOTE: this method is deprecated. Use enableKeyEquivalentForDefaultButton instead.
|
|
*/
|
|
- (void)enableKeyEquivalentForDefaultButtonCell
|
|
{
|
|
[self enableKeyEquivalentForDefaultButton];
|
|
}
|
|
|
|
/*!
|
|
Removes the key equivalent for the default button.
|
|
*/
|
|
- (void)disableKeyEquivalentForDefaultButton
|
|
{
|
|
_defaultButtonEnabled = NO;
|
|
}
|
|
|
|
- (void)_temporarilyDisableKeyEquivalentForDefaultButton
|
|
{
|
|
_defaultButtonDisabledTemporarily = YES;
|
|
}
|
|
|
|
/*!
|
|
Removes the key equivalent for the default button.
|
|
Note: this method is deprecated. Use disableKeyEquivalentForDefaultButton instead.
|
|
*/
|
|
- (void)disableKeyEquivalentForDefaultButtonCell
|
|
{
|
|
[self disableKeyEquivalentForDefaultButton];
|
|
}
|
|
|
|
- (void)setValue:(id)aValue forKey:(CPString)aKey
|
|
{
|
|
if (aKey === CPDisplayPatternTitleBinding)
|
|
[self setTitle:aValue || @""];
|
|
else
|
|
[super setValue:aValue forKey:aKey];
|
|
}
|
|
|
|
- (void)_didReceivePlatformWindowWillCloseNotification:(CPNotification)aNotification
|
|
{
|
|
if ([aNotification object] != _platformWindow)
|
|
return;
|
|
|
|
[self close];
|
|
}
|
|
|
|
@end
|
|
|
|
var keyViewComparator = function(lhs, rhs, context)
|
|
{
|
|
var lhsBounds = [lhs convertRect:[lhs bounds] toView:nil],
|
|
rhsBounds = [rhs convertRect:[rhs bounds] toView:nil],
|
|
lhsY = CGRectGetMinY(lhsBounds),
|
|
rhsY = CGRectGetMinY(rhsBounds),
|
|
lhsX = CGRectGetMinX(lhsBounds),
|
|
rhsX = CGRectGetMinX(rhsBounds),
|
|
intersectsVertically = MIN(CGRectGetMaxY(lhsBounds), CGRectGetMaxY(rhsBounds)) - MAX(lhsY, rhsY);
|
|
|
|
// If two views are "on the same line" (intersect vertically), then rely on the x comparison.
|
|
if (intersectsVertically > 0)
|
|
{
|
|
if (lhsX < rhsX)
|
|
return CPOrderedAscending;
|
|
|
|
if (lhsX === rhsX)
|
|
return CPOrderedSame;
|
|
|
|
return CPOrderedDescending;
|
|
}
|
|
|
|
if (lhsY < rhsY)
|
|
return CPOrderedAscending;
|
|
|
|
if (lhsY === rhsY)
|
|
return CPOrderedSame;
|
|
|
|
return CPOrderedDescending;
|
|
};
|
|
|
|
|
|
@implementation CPWindow (CPWindowDelegate)
|
|
|
|
/*!
|
|
@ignore
|
|
Check if the delegate implements windowWillReturnUndoManager:
|
|
*/
|
|
- (BOOL)_delegateRespondsToWindowWillUndoManager
|
|
{
|
|
return _implementedDelegateMethods & CPWindowDelegate_windowWillReturnUndoManager_;
|
|
}
|
|
|
|
/*!
|
|
@ignore
|
|
Check if the delegate implements windowShouldClose
|
|
*/
|
|
- (BOOL)_delegateRespondsToWindowShouldClose
|
|
{
|
|
return _implementedDelegateMethods & CPWindowDelegate_windowShouldClose_
|
|
}
|
|
|
|
/*!
|
|
@ignore
|
|
Call delegate windowShouldClose:
|
|
*/
|
|
- (BOOL)_sendDelegateWindowShouldClose
|
|
{
|
|
if (!(_implementedDelegateMethods & CPWindowDelegate_windowShouldClose_))
|
|
return YES;
|
|
|
|
return [_delegate windowShouldClose:self];
|
|
}
|
|
|
|
/*!
|
|
@ignore
|
|
Call delegate windowWillReturnUndoManager:
|
|
*/
|
|
- (BOOL)_sendDelegateWindowWillReturnUndoManager
|
|
{
|
|
if (!(_implementedDelegateMethods & CPWindowDelegate_windowWillReturnUndoManager_))
|
|
return nil;
|
|
|
|
return [_delegate windowWillReturnUndoManager:self];
|
|
}
|
|
|
|
/*!
|
|
@ignore
|
|
Call delegate windowWillClose:
|
|
*/
|
|
- (void)_sendDelegateWindowWillClose
|
|
{
|
|
if (!(_implementedDelegateMethods & CPWindowDelegate_windowWillClose_))
|
|
return;
|
|
|
|
[_delegate windowWillClose:self];
|
|
}
|
|
|
|
/*!
|
|
@ignore
|
|
Call the delegate windowWillResize:toSize:
|
|
*/
|
|
- (CGSize)_sendDelegateWindowWillResizeToSize:(CGSize)aSize
|
|
{
|
|
if (!(_implementedDelegateMethods & CPWindowDelegate_windowWillResize_toSize_))
|
|
return aSize;
|
|
|
|
return [_delegate windowWillResize:self toSize:aSize];
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation CPWindow (BridgeSupport)
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (void)resizeWithOldPlatformWindowSize:(CGSize)aSize
|
|
{
|
|
if ([self isFullPlatformWindow])
|
|
return [self setFrame:[_platformWindow visibleFrame]];
|
|
|
|
// If this window is constrainable and we are globally ignoring constraining, ignore the platform resize
|
|
if ((_constrainsToUsableScreen && !CPWindowConstrainToScreen) || _autoresizingMask === CPWindowNotSizable)
|
|
return;
|
|
|
|
var frame = [_platformWindow contentBounds],
|
|
newFrame = CGRectMakeCopy(_frame),
|
|
dX = (CGRectGetWidth(frame) - aSize.width) /
|
|
(((_autoresizingMask & CPWindowMinXMargin) ? 1 : 0) + (_autoresizingMask & CPWindowWidthSizable ? 1 : 0) + (_autoresizingMask & CPWindowMaxXMargin ? 1 : 0)),
|
|
dY = (CGRectGetHeight(frame) - aSize.height) /
|
|
((_autoresizingMask & CPWindowMinYMargin ? 1 : 0) + (_autoresizingMask & CPWindowHeightSizable ? 1 : 0) + (_autoresizingMask & CPWindowMaxYMargin ? 1 : 0));
|
|
|
|
if (_autoresizingMask & CPWindowMinXMargin)
|
|
newFrame.origin.x += dX;
|
|
|
|
if (_autoresizingMask & CPWindowWidthSizable)
|
|
newFrame.size.width += dX;
|
|
|
|
if (_autoresizingMask & CPWindowMinYMargin)
|
|
newFrame.origin.y += dY;
|
|
|
|
if (_autoresizingMask & CPWindowHeightSizable)
|
|
newFrame.size.height += dY;
|
|
|
|
[self _setFrame:newFrame display:YES animate:NO constrainWidth:YES constrainHeight:YES];
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (void)setAutoresizingMask:(unsigned)anAutoresizingMask
|
|
{
|
|
_autoresizingMask = anAutoresizingMask;
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (unsigned)autoresizingMask
|
|
{
|
|
return _autoresizingMask;
|
|
}
|
|
|
|
/*!
|
|
Converts aPoint from the window coordinate system to the global coordinate system.
|
|
*/
|
|
- (CGPoint)convertBaseToGlobal:(CGPoint)aPoint
|
|
{
|
|
return [CPPlatform isBrowser] ? [self convertBaseToPlatformWindow:aPoint] : [self convertBaseToScreen:aPoint];
|
|
}
|
|
|
|
/*!
|
|
@ignore
|
|
get the scroll offset (if any) from native scroll bars (can happen when the platform window shrinks below minSize)
|
|
*/
|
|
- (CGPoint)_nativeScrollOffset
|
|
{
|
|
#if PLATFORM(DOM)
|
|
return CGPointMake(_windowView._DOMElement.scrollLeft, _windowView._DOMElement.scrollTop);
|
|
#else
|
|
CGPointMake(0, 0);
|
|
#endif
|
|
|
|
}
|
|
|
|
/*!
|
|
Converts aPoint from the global coordinate system to the window coordinate system.
|
|
*/
|
|
- (CGPoint)convertGlobalToBase:(CGPoint)aPoint
|
|
{
|
|
return [CPPlatform isBrowser] ? [self convertPlatformWindowToBase:aPoint] : [self convertScreenToBase:aPoint];
|
|
}
|
|
|
|
/*!
|
|
Converts aPoint from the window coordinate system to the coordinate system of the parent platform window.
|
|
*/
|
|
- (CGPoint)convertBaseToPlatformWindow:(CGPoint)aPoint
|
|
{
|
|
if ([self _sharesChromeWithPlatformWindow])
|
|
return CGPointMakeCopy(aPoint);
|
|
|
|
var origin = [self frame].origin,
|
|
scrollOffset = [self _nativeScrollOffset];
|
|
|
|
return CGPointMake(aPoint.x + origin.x - scrollOffset.x, aPoint.y + origin.y - scrollOffset.y);
|
|
}
|
|
|
|
/*!
|
|
Converts aPoint from the parent platform window coordinate system to the window's coordinate system.
|
|
*/
|
|
- (CGPoint)convertPlatformWindowToBase:(CGPoint)aPoint
|
|
{
|
|
if ([self _sharesChromeWithPlatformWindow])
|
|
return CGPointMakeCopy(aPoint);
|
|
|
|
var origin = [self frame].origin,
|
|
scrollOffset = [self _nativeScrollOffset];
|
|
|
|
return CGPointMake(aPoint.x - origin.x + scrollOffset.x, aPoint.y - origin.y + scrollOffset.y);
|
|
}
|
|
|
|
- (CGPoint)convertScreenToBase:(CGPoint)aPoint
|
|
{
|
|
return [self convertPlatformWindowToBase:[_platformWindow convertScreenToBase:aPoint]];
|
|
}
|
|
|
|
- (CGPoint)convertBaseToScreen:(CGPoint)aPoint
|
|
{
|
|
return [_platformWindow convertBaseToScreen:[self convertBaseToPlatformWindow:aPoint]];
|
|
}
|
|
|
|
- (void)_setSharesChromeWithPlatformWindow:(BOOL)shouldShareFrameWithPlatformWindow
|
|
{
|
|
// We canna' do it captain! We just don't have the power!
|
|
if (shouldShareFrameWithPlatformWindow && [CPPlatform isBrowser])
|
|
return;
|
|
|
|
_sharesChromeWithPlatformWindow = shouldShareFrameWithPlatformWindow;
|
|
|
|
[self _updateShadow];
|
|
}
|
|
|
|
- (BOOL)_sharesChromeWithPlatformWindow
|
|
{
|
|
return _sharesChromeWithPlatformWindow;
|
|
}
|
|
|
|
// Undo and Redo Support
|
|
/*!
|
|
Returns the window's undo manager.
|
|
*/
|
|
- (CPUndoManager)undoManager
|
|
{
|
|
// If we've ever created an undo manager, return it.
|
|
if (_undoManager)
|
|
return _undoManager;
|
|
|
|
// If not, check to see if the document has one.
|
|
var documentUndoManager = [[_windowController document] undoManager];
|
|
|
|
if (documentUndoManager)
|
|
return documentUndoManager;
|
|
|
|
// If not, check to see if the delegate has one.
|
|
if ([self _delegateRespondsToWindowWillUndoManager])
|
|
return [self _sendDelegateWindowWillReturnUndoManager];
|
|
|
|
// If not, create one.
|
|
if (!_undoManager)
|
|
_undoManager = [[CPUndoManager alloc] init];
|
|
|
|
return _undoManager;
|
|
}
|
|
|
|
/*!
|
|
Sends the undo manager an \c -undo: message.
|
|
@param aSender the object requesting this
|
|
*/
|
|
- (void)undo:(id)aSender
|
|
{
|
|
[[self undoManager] undo];
|
|
}
|
|
|
|
/*!
|
|
Sends the undo manager a \c -redo: message.
|
|
@param aSender the object requesting this
|
|
*/
|
|
- (void)redo:(id)aSender
|
|
{
|
|
[[self undoManager] redo];
|
|
}
|
|
|
|
- (BOOL)containsPoint:(CGPoint)aPoint
|
|
{
|
|
return CGRectContainsPoint(_frame, aPoint);
|
|
}
|
|
|
|
/* aPoint should be global */
|
|
- (BOOL)_isValidMousePoint:(CGPoint)aPoint
|
|
{
|
|
// If we are using the new resizing mode, mouse events are valid
|
|
// outside the window's frame for non-full platform windows.
|
|
var mouseFrame = (!_isFullPlatformWindow && (_styleMask & CPResizableWindowMask) && (CPWindowResizeStyle === CPWindowResizeStyleModern)) ? CGRectInset(_frame, -_CPWindowViewResizeSlop, -_CPWindowViewResizeSlop) : _frame;
|
|
|
|
return CGRectContainsPoint(mouseFrame, aPoint);
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CPWindow (Deprecated)
|
|
/*!
|
|
Sets the CPWindow to fill the whole browser window.
|
|
NOTE: this method has been deprecated in favor of setFullPlatformWindow:
|
|
*/
|
|
- (void)setFullBridge:(BOOL)shouldBeFullBridge
|
|
{
|
|
_CPReportLenientDeprecation([self class], _cmd, @selector(setFullPlatformWindow:));
|
|
|
|
[self setFullPlatformWindow:shouldBeFullBridge];
|
|
}
|
|
|
|
/*!
|
|
Returns YES if the window fills the full browser window, otherwise NO.
|
|
NOTE: this method has been deprecated in favor of isFullPlatformWindow.
|
|
*/
|
|
- (BOOL)isFullBridge
|
|
{
|
|
return [self isFullPlatformWindow];
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (CGPoint)convertBaseToBridge:(CGPoint)aPoint
|
|
{
|
|
return [self convertBaseToPlatformWindow:aPoint];
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (CGPoint)convertBridgeToBase:(CGPoint)aPoint
|
|
{
|
|
return [self convertPlatformWindowToBase:aPoint];
|
|
}
|
|
|
|
@end
|
|
|
|
var interpolate = function(fromValue, toValue, progress)
|
|
{
|
|
return fromValue + (toValue - fromValue) * progress;
|
|
};
|
|
|
|
/* @ignore */
|
|
@implementation _CPWindowFrameAnimation : CPAnimation
|
|
{
|
|
CPWindow _window;
|
|
|
|
CGRect _startFrame;
|
|
CGRect _targetFrame;
|
|
}
|
|
|
|
- (id)initWithWindow:(CPWindow)aWindow targetFrame:(CGRect)aTargetFrame
|
|
{
|
|
self = [super initWithDuration:[aWindow animationResizeTime:aTargetFrame] animationCurve:CPAnimationLinear];
|
|
|
|
if (self)
|
|
{
|
|
_window = aWindow;
|
|
|
|
_targetFrame = CGRectMakeCopy(aTargetFrame);
|
|
_startFrame = CGRectMakeCopy([_window frame]);
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)setCurrentProgress:(float)aProgress
|
|
{
|
|
[super setCurrentProgress:aProgress];
|
|
|
|
var value = [self currentValue];
|
|
|
|
if (value == 1.0)
|
|
[_window _setAnimating:NO];
|
|
|
|
var newFrame = CGRectMake(
|
|
interpolate(CGRectGetMinX(_startFrame), CGRectGetMinX(_targetFrame), value),
|
|
interpolate(CGRectGetMinY(_startFrame), CGRectGetMinY(_targetFrame), value),
|
|
interpolate(CGRectGetWidth(_startFrame), CGRectGetWidth(_targetFrame), value),
|
|
interpolate(CGRectGetHeight(_startFrame), CGRectGetHeight(_targetFrame), value));
|
|
|
|
[_window setFrame:newFrame display:YES animate:NO];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation _CPWindowFrameAnimationDelegate : CPObject
|
|
{
|
|
CPWindow _window;
|
|
}
|
|
|
|
- (id)initWithWindow:(CPWindow)aWindow
|
|
{
|
|
self = [super init];
|
|
|
|
_window = aWindow;
|
|
|
|
return self;
|
|
}
|
|
|
|
- (BOOL)animationShouldStart:(CPAnimation)animation
|
|
{
|
|
[_window _setAnimating:YES];
|
|
[_window _startLiveResize];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (void)animationDidStop:(CPAnimation)animation
|
|
{
|
|
[_window _setAnimating:NO];
|
|
[_window _endLiveResize];
|
|
}
|
|
|
|
- (void)animationDidEnd:(CPAnimation)animation
|
|
{
|
|
[_window _setAnimating:NO];
|
|
[_window _endLiveResize];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CPWindow (CPDraggingAdditions)
|
|
|
|
/* @ignore */
|
|
- (id)_dragHitTest:(CGPoint)aPoint pasteboard:(CPPasteboard)aPasteboard
|
|
{
|
|
// If none of our views or ourselves has registered for drag events...
|
|
if (!_inclusiveRegisteredDraggedTypes)
|
|
return nil;
|
|
|
|
// We don't need to do this because the only place this gets called
|
|
// -_dragHitTest: in CPPlatformWindow does this already. Perhaps to
|
|
// be safe?
|
|
// if (![self containsPoint:aPoint])
|
|
// return nil;
|
|
|
|
var adjustedPoint = [self convertPlatformWindowToBase:aPoint],
|
|
hitView = [_windowView hitTest:adjustedPoint];
|
|
|
|
while (hitView && ![aPasteboard availableTypeFromArray:[hitView registeredDraggedTypes]])
|
|
hitView = [hitView superview];
|
|
|
|
if (hitView)
|
|
return hitView;
|
|
|
|
if ([aPasteboard availableTypeFromArray:[self registeredDraggedTypes]])
|
|
return self;
|
|
|
|
return nil;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CPWindow (TrackingAreaAdditions)
|
|
|
|
- (void)_addTrackingAreaView:(CPView)aView
|
|
{
|
|
var trackingAreas = [aView trackingAreas];
|
|
|
|
for (var i = 0; i < trackingAreas.length; i++)
|
|
[self _addTrackingArea:trackingAreas[i]];
|
|
}
|
|
|
|
- (void)_removeTrackingAreaView:(CPView)aView
|
|
{
|
|
var trackingAreas = [aView trackingAreas];
|
|
|
|
for (var i = 0; i < trackingAreas.length; i++)
|
|
[self _removeTrackingArea:trackingAreas[i]];
|
|
}
|
|
|
|
- (void)_addTrackingArea:(CPTrackingArea)trackingArea
|
|
{
|
|
var trackingAreaView = [trackingArea view];
|
|
|
|
if (![_trackingAreaViews containsObjectIdenticalTo:trackingAreaView])
|
|
[_trackingAreaViews addObject:trackingAreaView];
|
|
|
|
// If CPTrackingAssumeInside option is set, insert the tracking area in the events management system
|
|
// in order to have the first event sent only when mouse leaves the tracking area
|
|
|
|
[self _insertTrackingArea:trackingArea assumeInside:([trackingArea options] & CPTrackingAssumeInside)];
|
|
}
|
|
|
|
- (void)_removeTrackingArea:(CPTrackingArea)trackingArea
|
|
{
|
|
// If mouse is in the tracking area, we remove it from the stack to avoid to fire a future mouseExited event
|
|
|
|
[self _purgeTrackingArea:trackingArea];
|
|
|
|
var trackingAreaView = [trackingArea view];
|
|
|
|
[_trackingAreaViews removeObjectIdenticalTo:trackingAreaView];
|
|
}
|
|
|
|
- (void)_insertTrackingArea:(CPTrackingArea)trackingArea assumeInside:(BOOL)assumeInside
|
|
{
|
|
if (_handlingTrackingAreaEvent)
|
|
_restartHandlingTrackingAreaEvent = YES;
|
|
|
|
if (assumeInside)
|
|
{
|
|
if (_handlingTrackingAreaEvent)
|
|
[_mouseEnteredStack addObject:trackingArea];
|
|
else
|
|
[_previousMouseEnteredStack addObject:trackingArea];
|
|
}
|
|
}
|
|
|
|
- (void)_purgeTrackingArea:(CPTrackingArea)trackingArea
|
|
{
|
|
if (_handlingTrackingAreaEvent)
|
|
{
|
|
[_mouseEnteredStack removeObjectIdenticalTo:trackingArea];
|
|
|
|
var i = _queuedEvents.length;
|
|
|
|
while (i--)
|
|
if ([_queuedEvents[i] trackingArea] === trackingArea)
|
|
[_queuedEvents removeObjectAtIndex:i];
|
|
|
|
_cursorUpdateStack = [];
|
|
_activeCursorTrackingArea = nil;
|
|
}
|
|
else
|
|
{
|
|
[_previousMouseEnteredStack removeObjectIdenticalTo:trackingArea];
|
|
[_previousCursorUpdateStack removeObjectIdenticalTo:trackingArea];
|
|
}
|
|
}
|
|
|
|
- (void)_handleTrackingAreaEvent:(CPEvent)anEvent
|
|
{
|
|
_handlingTrackingAreaEvent = YES;
|
|
|
|
var point = [anEvent locationInWindow],
|
|
dragging = ([anEvent type] !== CPMouseMoved);
|
|
|
|
do
|
|
{
|
|
// Initialize this run
|
|
_restartHandlingTrackingAreaEvent = NO;
|
|
|
|
_mouseEnteredStack = [];
|
|
_cursorUpdateStack = [];
|
|
|
|
// Important remark: we must queue events to avoid running conditions when a view uses a mouse event to modify view hierarchy
|
|
|
|
_queuedEvents = [];
|
|
|
|
// Handle mouse entering tracking areas (and calc _mouseEnteredStack and _cursorUpdateStack)
|
|
|
|
[self _handleMouseMovedAndEnteredEventsForEvent:anEvent atPoint:point dragging:dragging];
|
|
|
|
// Handle mouse exiting tracking areas
|
|
|
|
[self _handleMouseExitedEventsForEvent:anEvent atPoint:point dragging:dragging];
|
|
|
|
// Cursor update
|
|
|
|
if (_cursorUpdateStack.length > 0)
|
|
{
|
|
[self _handleCursorUpdateEventsForEvent:anEvent atPoint:point dragging:dragging];
|
|
}
|
|
else if (!dragging)
|
|
{
|
|
// Here, we are outside the window content view tracking area, so let _windowView set the cursor (resize cursor, ...)
|
|
|
|
[_windowView setCursorForLocation:point resizing:NO];
|
|
_activeCursorTrackingArea = nil;
|
|
}
|
|
|
|
// Send all queued events
|
|
// Important remark : as an event can modify the view hierarchy, the events queue could be modified while processing it
|
|
|
|
while (_queuedEvents.length > 0)
|
|
{
|
|
var queuedEvent = _queuedEvents[0],
|
|
trackingArea = [queuedEvent trackingArea],
|
|
trackingOwner = [trackingArea owner];
|
|
|
|
switch ([queuedEvent type])
|
|
{
|
|
case CPMouseEntered:
|
|
[trackingOwner mouseEntered:queuedEvent];
|
|
break;
|
|
|
|
case CPMouseExited:
|
|
[trackingOwner mouseExited:queuedEvent];
|
|
break;
|
|
|
|
case CPCursorUpdate:
|
|
[trackingOwner cursorUpdate:queuedEvent];
|
|
break;
|
|
}
|
|
|
|
if (queuedEvent === _queuedEvents[0])
|
|
[_queuedEvents removeObjectAtIndex:0];
|
|
}
|
|
|
|
// Prepare for next call
|
|
|
|
_previousMouseEnteredStack = _mouseEnteredStack;
|
|
_previousCursorUpdateStack = _cursorUpdateStack;
|
|
}
|
|
while (_restartHandlingTrackingAreaEvent)
|
|
|
|
_handlingTrackingAreaEvent = NO;
|
|
}
|
|
|
|
- (void)_handleMouseMovedAndEnteredEventsForEvent:(CPEvent)anEvent atPoint:(CGPoint)point dragging:(BOOL)dragging
|
|
{
|
|
var isKeyWindow = [self isKeyWindow];
|
|
|
|
for (var i = 0; i < _trackingAreaViews.length; i++)
|
|
{
|
|
var aView = _trackingAreaViews[i],
|
|
trackingAreas = [aView trackingAreas];
|
|
|
|
if ([aView isHidden])
|
|
continue;
|
|
|
|
for (var j = 0; j < trackingAreas.length; j++)
|
|
{
|
|
var aTrackingArea = trackingAreas[j],
|
|
trackingOptions = [aTrackingArea options],
|
|
trackingImplementedMethods = [aTrackingArea implementedOwnerMethods];
|
|
|
|
if (!(((trackingOptions & CPTrackingActiveAlways) ||
|
|
(trackingOptions & CPTrackingActiveInActiveApp) ||
|
|
((trackingOptions & CPTrackingActiveInKeyWindow) && isKeyWindow) ||
|
|
((trackingOptions & CPTrackingActiveWhenFirstResponder) && isKeyWindow && (_firstResponder === aView))) &&
|
|
(CGRectContainsPoint([aTrackingArea windowRect], point))))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
[_mouseEnteredStack addObject:aTrackingArea];
|
|
|
|
if ([_previousMouseEnteredStack containsObjectIdenticalTo:aTrackingArea])
|
|
{
|
|
// Mouse was already in this rect so it's a mouseMoved
|
|
|
|
if (!dragging && (trackingOptions & CPTrackingMouseMoved) && (trackingImplementedMethods & CPTrackingOwnerImplementsMouseMoved))
|
|
[[aTrackingArea owner] mouseMoved:anEvent];
|
|
}
|
|
else if ((trackingOptions & CPTrackingMouseEnteredAndExited) && (trackingImplementedMethods & CPTrackingOwnerImplementsMouseEntered))
|
|
{
|
|
var mouseEnteredEvent = [CPEvent enterExitEventWithType:CPMouseEntered
|
|
location:point
|
|
modifierFlags:[anEvent modifierFlags]
|
|
timestamp:[anEvent timestamp]
|
|
windowNumber:_windowNumber
|
|
context:nil
|
|
eventNumber:-1
|
|
trackingArea:aTrackingArea];
|
|
|
|
if (dragging && !(trackingOptions & CPTrackingEnabledDuringMouseDrag))
|
|
[self _queueTrackingEvent:mouseEnteredEvent];
|
|
else
|
|
[_queuedEvents addObject:mouseEnteredEvent];
|
|
}
|
|
|
|
if ((trackingOptions & CPTrackingCursorUpdate) && (trackingImplementedMethods & CPTrackingOwnerImplementsCursorUpdate))
|
|
[_cursorUpdateStack addObject:aTrackingArea];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)_handleMouseExitedEventsForEvent:(CPEvent)anEvent atPoint:(CGPoint)point dragging:(BOOL)dragging
|
|
{
|
|
// Search for exited views (were in _previousMouseEnteredStack but no more in _mouseEnteredStack)
|
|
|
|
for (var i = 0; i < _previousMouseEnteredStack.length; i++)
|
|
{
|
|
var aTrackingArea = _previousMouseEnteredStack[i],
|
|
trackingOptions = [aTrackingArea options];
|
|
|
|
if ([_mouseEnteredStack containsObjectIdenticalTo:aTrackingArea])
|
|
continue;
|
|
|
|
// Mouse is no more in this area so it's a mouseExited
|
|
|
|
if ((trackingOptions & CPTrackingMouseEnteredAndExited) && ([aTrackingArea implementedOwnerMethods] & CPTrackingOwnerImplementsMouseExited))
|
|
{
|
|
var theView = [aTrackingArea owner],
|
|
mouseExitedEvent = [CPEvent enterExitEventWithType:CPMouseExited
|
|
location:point
|
|
modifierFlags:[anEvent modifierFlags]
|
|
timestamp:[anEvent timestamp]
|
|
windowNumber:_windowNumber
|
|
context:nil
|
|
eventNumber:-1
|
|
trackingArea:aTrackingArea];
|
|
|
|
if (dragging && !(trackingOptions & CPTrackingEnabledDuringMouseDrag))
|
|
[self _queueTrackingEvent:mouseExitedEvent];
|
|
else
|
|
[_queuedEvents addObject:mouseExitedEvent];
|
|
}
|
|
|
|
// If this is the active cursor area, we reset _previousCursorUpdateStack so a new active area will be computed
|
|
|
|
if (aTrackingArea === _activeCursorTrackingArea)
|
|
{
|
|
_previousCursorUpdateStack = [];
|
|
_activeCursorTrackingArea = nil;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)_handleCursorUpdateEventsForEvent:(CPEvent)anEvent atPoint:(CGPoint)point dragging:(BOOL)dragging
|
|
{
|
|
var overlappingTrackingAreas = [];
|
|
|
|
for (var i = 0; i < _cursorUpdateStack.length; i++)
|
|
{
|
|
var aTrackingArea = _cursorUpdateStack[i];
|
|
|
|
if ((![_previousCursorUpdateStack containsObjectIdenticalTo:aTrackingArea]) || (aTrackingArea === _activeCursorTrackingArea))
|
|
[overlappingTrackingAreas addObject:aTrackingArea];
|
|
}
|
|
|
|
if (overlappingTrackingAreas.length === 0)
|
|
return;
|
|
|
|
var frontmostTrackingArea = overlappingTrackingAreas[0],
|
|
frontmostView = [frontmostTrackingArea view];
|
|
|
|
for (var i = 1; i < overlappingTrackingAreas.length; i++)
|
|
{
|
|
var aTrackingArea = overlappingTrackingAreas[i],
|
|
aView = [aTrackingArea view];
|
|
|
|
// First, if aView is _windowView, skip to next overlapping tracking area
|
|
// as _windowView can't be the frontmost view if there's multiple overlapping tracking areas.
|
|
|
|
if (aView === _windowView)
|
|
continue;
|
|
|
|
// Then, if frontmostView is _windowView, aView must become frontmostView
|
|
|
|
if (frontmostView === _windowView)
|
|
{
|
|
frontmostTrackingArea = aTrackingArea;
|
|
frontmostView = aView;
|
|
|
|
continue;
|
|
}
|
|
|
|
// Next verify if aView is a subview of frontmostView
|
|
// If so, it's our new frontmost view
|
|
|
|
var searchingView = aView;
|
|
|
|
while ((searchingView !== _contentView) && ([searchingView superview] !== frontmostView))
|
|
searchingView = [searchingView superview];
|
|
|
|
if (searchingView !== _contentView)
|
|
{
|
|
frontmostTrackingArea = aTrackingArea;
|
|
frontmostView = aView;
|
|
|
|
continue;
|
|
}
|
|
|
|
// aView is not a subview of frontmostView
|
|
// Search in view hierarchy which one will be over the other
|
|
// (this is done by comparing their draw order)
|
|
|
|
var firstView = frontmostView,
|
|
firstSuperview = [firstView superview];
|
|
|
|
while (firstView !== _contentView)
|
|
{
|
|
var secondView = aView,
|
|
secondSuperview = [secondView superview];
|
|
|
|
while ((secondSuperview !== _contentView) && (firstSuperview !== secondSuperview))
|
|
{
|
|
secondView = secondSuperview;
|
|
secondSuperview = [secondView superview];
|
|
}
|
|
|
|
if (firstSuperview === secondSuperview)
|
|
break;
|
|
|
|
firstView = firstSuperview;
|
|
firstSuperview = [firstView superview];
|
|
}
|
|
|
|
if (firstSuperview !== secondSuperview)
|
|
[CPException raise:CPInternalInconsistencyException reason:"Problem with view hierarchy"];
|
|
|
|
var firstSuperviewSubviews = [firstSuperview subviews],
|
|
firstViewIndex = [firstSuperviewSubviews indexOfObject:firstView],
|
|
secondViewIndex = [firstSuperviewSubviews indexOfObject:secondView];
|
|
|
|
if (secondViewIndex > firstViewIndex)
|
|
{
|
|
frontmostTrackingArea = aTrackingArea;
|
|
frontmostView = aView;
|
|
}
|
|
}
|
|
|
|
if (frontmostTrackingArea !== _activeCursorTrackingArea)
|
|
{
|
|
var cursorUpdateEvent = [CPEvent enterExitEventWithType:CPCursorUpdate
|
|
location:point
|
|
modifierFlags:[anEvent modifierFlags]
|
|
timestamp:[anEvent timestamp]
|
|
windowNumber:_windowNumber
|
|
context:nil
|
|
eventNumber:-1
|
|
trackingArea:frontmostTrackingArea];
|
|
|
|
if (dragging)
|
|
[self _queueTrackingEvent:cursorUpdateEvent];
|
|
else
|
|
[_queuedEvents addObject:cursorUpdateEvent];
|
|
|
|
_activeCursorTrackingArea = frontmostTrackingArea;
|
|
}
|
|
}
|
|
|
|
- (void)_queueTrackingEvent:(CPEvent)anEvent
|
|
{
|
|
// This will put a tracking event in the _queuedTrackingEvents queue.
|
|
//
|
|
// We optimize this queue with this policy:
|
|
// - if mouseEntered, search if queue contains a previous mouseExited for the same tracking area. If so, discard both.
|
|
// - if mouseExited, search if queue contains a previous mouseEntered for the same tracking area. If so, discard both.
|
|
//
|
|
// This is not the Cocoa way of doing as it would send every event, but the final result should be the same.
|
|
|
|
var eventType = [anEvent type],
|
|
trackingArea = [anEvent trackingArea];
|
|
|
|
switch ([anEvent type])
|
|
{
|
|
case CPMouseEntered:
|
|
for (var i = 0; i < _queuedTrackingEvents.length; i++)
|
|
{
|
|
var queuedEvent = _queuedTrackingEvents[i];
|
|
|
|
if (([queuedEvent trackingArea] === trackingArea) && ([queuedEvent type] === CPMouseExited))
|
|
{
|
|
[_queuedTrackingEvents removeObjectAtIndex:i];
|
|
return;
|
|
}
|
|
}
|
|
|
|
[_queuedTrackingEvents addObject:anEvent];
|
|
break;
|
|
|
|
case CPMouseExited:
|
|
for (var i = 0; i < _queuedTrackingEvents.length; i++)
|
|
{
|
|
var queuedEvent = _queuedTrackingEvents[i];
|
|
|
|
if (([queuedEvent trackingArea] === trackingArea) && ([queuedEvent type] === CPMouseEntered))
|
|
{
|
|
[_queuedTrackingEvents removeObjectAtIndex:i];
|
|
return;
|
|
}
|
|
}
|
|
|
|
[_queuedTrackingEvents addObject:anEvent];
|
|
break;
|
|
|
|
case CPCursorUpdate:
|
|
[_queuedTrackingEvents addObject:anEvent];
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (void)_flushTrackingEventQueueForMouseAt:(CGPoint)point
|
|
{
|
|
for (var i = 0; i < _queuedTrackingEvents.length; i++)
|
|
{
|
|
var queuedEvent = _queuedTrackingEvents[i],
|
|
trackingArea = [queuedEvent trackingArea],
|
|
trackingOwner = [trackingArea owner];
|
|
|
|
switch ([queuedEvent type])
|
|
{
|
|
case CPMouseEntered:
|
|
[trackingOwner mouseEntered:queuedEvent];
|
|
break;
|
|
|
|
case CPMouseExited:
|
|
[trackingOwner mouseExited:queuedEvent];
|
|
break;
|
|
|
|
case CPCursorUpdate:
|
|
[trackingOwner updateTrackingAreas];
|
|
|
|
if (CGRectContainsPoint([trackingArea windowRect], point))
|
|
[trackingOwner cursorUpdate:queuedEvent];
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
_queuedTrackingEvents = [];
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
@implementation CPWindow (CSSTheming)
|
|
|
|
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
|
|
{
|
|
[[self contentView] _setThemeIncludingDescendants:aTheme];
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
function _CPWindowFullPlatformWindowSessionMake(aWindowView, aContentRect, hasShadow, aLevel)
|
|
{
|
|
return { windowView:aWindowView, contentRect:aContentRect, hasShadow:hasShadow, level:aLevel };
|
|
}
|