mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 13:07:02 +00:00
The default was to make the first window in the most background layer the key window if nothing else was used. This does not work well if there is a modal window that should have all the attention. This solution will first choose the modal window if in modal mode. If not the old variant is used.
1914 lines
66 KiB
Plaintext
1914 lines
66 KiB
Plaintext
/*
|
|
* CPPlatformWindow+DOM.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
|
|
*/
|
|
|
|
|
|
/*
|
|
* THIS DOCUMENTATION STOLEN DIRECTLY FROM GOOGLE CLOSURE (licensed under Apache 2)
|
|
*
|
|
* Different web browsers have very different keyboard event handling. Most
|
|
* importantly is that only certain browsers repeat keydown events:
|
|
* IE, Opera, FF/Win32, and Safari 3 repeat keydown events.
|
|
* FF/Mac and Safari 2 do not.
|
|
*
|
|
* For the purposes of this code, "Safari 3" means WebKit 525+, when WebKit
|
|
* decided that they should try to match IE's key handling behavior.
|
|
* Safari 3.0.4, which shipped with Leopard (WebKit 523), has the
|
|
* Safari 2 behavior.
|
|
*
|
|
* Firefox, Safari, Opera prevent on keypress
|
|
*
|
|
* IE prevents on keydown
|
|
*
|
|
* Firefox does not fire keypress for shift, ctrl, alt
|
|
* Firefox does fire keydown for shift, ctrl, alt, meta
|
|
* Firefox does not repeat keydown for shift, ctrl, alt, meta
|
|
*
|
|
* Firefox does not fire keypress for up and down in an input
|
|
*
|
|
* Opera fires keypress for shift, ctrl, alt, meta
|
|
* Opera does not repeat keypress for shift, ctrl, alt, meta
|
|
*
|
|
* Safari 2 and 3 do not fire keypress for shift, ctrl, alt
|
|
* Safari 2 does not fire keydown for shift, ctrl, alt
|
|
* Safari 3 *does* fire keydown for shift, ctrl, alt
|
|
*
|
|
* IE provides the keycode for keyup/down events and the charcode (in the
|
|
* keycode field) for keypress.
|
|
*
|
|
* Mozilla provides the keycode for keyup/down and the charcode for keypress
|
|
* unless it's a non text modifying key in which case the keycode is provided.
|
|
*
|
|
* Safari 3 provides the keycode and charcode for all events.
|
|
*
|
|
* Opera provides the keycode for keyup/down event and either the charcode or
|
|
* the keycode (in the keycode field) for keypress events.
|
|
*
|
|
* Firefox x11 doesn't fire keydown events if a another key is already held down
|
|
* until the first key is released. This can cause a key event to be fired with
|
|
* a keyCode for the first key and a charCode for the second key.
|
|
*
|
|
* Safari 2 in keypress (not supported)
|
|
*
|
|
* charCode keyCode which
|
|
* ENTER: 13 13 13
|
|
* F1: 63236 63236 63236
|
|
* F8: 63243 63243 63243
|
|
* ...
|
|
* p: 112 112 112
|
|
* P: 80 80 80
|
|
*
|
|
* Firefox, keypress:
|
|
*
|
|
* charCode keyCode which
|
|
* ENTER: 0 13 13
|
|
* F1: 0 112 0
|
|
* F8: 0 119 0
|
|
* ...
|
|
* p: 112 0 112
|
|
* P: 80 0 80
|
|
*
|
|
* Opera, Mac+Win32, keypress:
|
|
*
|
|
* charCode keyCode which
|
|
* ENTER: undefined 13 13
|
|
* F1: undefined 112 0
|
|
* F8: undefined 119 0
|
|
* ...
|
|
* p: undefined 112 112
|
|
* P: undefined 80 80
|
|
*
|
|
* IE7, keydown
|
|
*
|
|
* charCode keyCode which
|
|
* ENTER: undefined 13 undefined
|
|
* F1: undefined 112 undefined
|
|
* F8: undefined 119 undefined
|
|
* ...
|
|
* p: undefined 80 undefined
|
|
* P: undefined 80 undefined
|
|
*/
|
|
|
|
@import <Foundation/CPNotificationCenter.j>
|
|
@import <Foundation/CPObject.j>
|
|
@import <Foundation/CPRunLoop.j>
|
|
@import <Foundation/CPSet.j>
|
|
@import <Foundation/CPTimer.j>
|
|
|
|
@import "CPApplication_Constants.j"
|
|
@import "CPCompatibility.j"
|
|
@import "CPCursor.j"
|
|
@import "CPDOMWindowLayer.j"
|
|
@import "CPDragServer_Constants.j"
|
|
@import "CPEvent.j"
|
|
@import "CPPasteboard.j"
|
|
@import "CPPlatform.j"
|
|
@import "CPPlatformPasteboard.j"
|
|
@import "CPPlatformWindow+DOMKeys.j"
|
|
@import "CPPlatformWindow.j"
|
|
@import "CPText.j"
|
|
@import "CPWindow_Constants.j"
|
|
|
|
@class CPApplication
|
|
@class CPDragServer
|
|
@class _CPToolTip
|
|
|
|
@global _CPRunModalLoop
|
|
|
|
// List of all open native windows
|
|
var PlatformWindows = [CPSet set];
|
|
|
|
// Define up here so compressor knows about them.
|
|
var CPDOMEventGetClickCount,
|
|
StopDOMEventPropagation,
|
|
StopContextMenuDOMEventPropagation;
|
|
|
|
var KeyCodesToPrevent = {},
|
|
CharacterKeysToPrevent = {},
|
|
KeyCodesToAllow = {},
|
|
MozKeyCodeToKeyCodeMap = {
|
|
61: 187, // =, equals
|
|
59: 186 // ;, semicolon
|
|
},
|
|
KeyCodesToUnicodeMap = {};
|
|
|
|
KeyCodesToPrevent[CPKeyCodes.A] = YES;
|
|
|
|
KeyCodesToAllow[CPKeyCodes.F1] = YES;
|
|
KeyCodesToAllow[CPKeyCodes.F2] = YES;
|
|
KeyCodesToAllow[CPKeyCodes.F3] = YES;
|
|
KeyCodesToAllow[CPKeyCodes.F4] = YES;
|
|
KeyCodesToAllow[CPKeyCodes.F5] = YES;
|
|
KeyCodesToAllow[CPKeyCodes.F6] = YES;
|
|
KeyCodesToAllow[CPKeyCodes.F7] = YES;
|
|
KeyCodesToAllow[CPKeyCodes.F8] = YES;
|
|
KeyCodesToAllow[CPKeyCodes.F9] = YES;
|
|
KeyCodesToAllow[CPKeyCodes.F10] = YES;
|
|
KeyCodesToAllow[CPKeyCodes.F11] = YES;
|
|
KeyCodesToAllow[CPKeyCodes.F12] = YES;
|
|
|
|
KeyCodesToUnicodeMap[CPKeyCodes.BACKSPACE] = CPDeleteCharacter;
|
|
KeyCodesToUnicodeMap[CPKeyCodes.DELETE] = CPDeleteFunctionKey;
|
|
KeyCodesToUnicodeMap[CPKeyCodes.TAB] = CPTabCharacter;
|
|
KeyCodesToUnicodeMap[CPKeyCodes.ENTER] = CPCarriageReturnCharacter;
|
|
KeyCodesToUnicodeMap[CPKeyCodes.ESC] = CPEscapeFunctionKey;
|
|
KeyCodesToUnicodeMap[CPKeyCodes.PAGE_UP] = CPPageUpFunctionKey;
|
|
KeyCodesToUnicodeMap[CPKeyCodes.PAGE_DOWN] = CPPageDownFunctionKey;
|
|
KeyCodesToUnicodeMap[CPKeyCodes.LEFT] = CPLeftArrowFunctionKey;
|
|
KeyCodesToUnicodeMap[CPKeyCodes.UP] = CPUpArrowFunctionKey;
|
|
KeyCodesToUnicodeMap[CPKeyCodes.RIGHT] = CPRightArrowFunctionKey;
|
|
KeyCodesToUnicodeMap[CPKeyCodes.DOWN] = CPDownArrowFunctionKey;
|
|
KeyCodesToUnicodeMap[CPKeyCodes.HOME] = CPHomeFunctionKey;
|
|
KeyCodesToUnicodeMap[CPKeyCodes.END] = CPEndFunctionKey;
|
|
KeyCodesToUnicodeMap[CPKeyCodes.SEMICOLON] = ";";
|
|
KeyCodesToUnicodeMap[CPKeyCodes.DASH] = "-";
|
|
KeyCodesToUnicodeMap[CPKeyCodes.EQUALS] = "=";
|
|
KeyCodesToUnicodeMap[CPKeyCodes.COMMA] = ",";
|
|
KeyCodesToUnicodeMap[CPKeyCodes.PERIOD] = ".";
|
|
KeyCodesToUnicodeMap[CPKeyCodes.SLASH] = "/";
|
|
KeyCodesToUnicodeMap[CPKeyCodes.APOSTROPHE] = "`";
|
|
KeyCodesToUnicodeMap[CPKeyCodes.SINGLE_QUOTE] = "'";
|
|
KeyCodesToUnicodeMap[CPKeyCodes.OPEN_SQUARE_BRACKET] = "[";
|
|
KeyCodesToUnicodeMap[CPKeyCodes.BACKSLASH] = "\\";
|
|
KeyCodesToUnicodeMap[CPKeyCodes.CLOSE_SQUARE_BRACKET] = "]";
|
|
|
|
var ModifierKeyCodes = [
|
|
CPKeyCodes.META,
|
|
CPKeyCodes.WEBKIT_RIGHT_META,
|
|
CPKeyCodes.MAC_FF_META,
|
|
CPKeyCodes.CTRL,
|
|
CPKeyCodes.ALT,
|
|
CPKeyCodes.SHIFT,
|
|
CPKeyCodes.CAPS_LOCK
|
|
],
|
|
|
|
supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop];
|
|
|
|
var resizeTimer = nil;
|
|
var PreventScroll = true;
|
|
var blurTimer = nil;
|
|
|
|
var touchStartingPointX,
|
|
touchStartingPointY;
|
|
|
|
_CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotification";
|
|
|
|
// When scrolling with an old-style scroll wheel with discete steps ('clicks'), the scroll amount can indicate how many "lines" to
|
|
// scroll.
|
|
#define SCROLLWHEEL_LINE_PIXELS 6.0
|
|
|
|
#if PLATFORM(DOM)
|
|
|
|
@implementation CPPlatformWindow (DOM)
|
|
|
|
- (id)_init
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_DOMWindow = window;
|
|
_contentRect = CGRectMakeZero();
|
|
|
|
_windowLevels = [];
|
|
_windowLayers = @{};
|
|
|
|
_platformPasteboard = [CPPlatformPasteboard new];
|
|
|
|
[self registerDOMWindow];
|
|
[self updateFromNativeContentRect];
|
|
|
|
_charCodes = {};
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (CGRect)nativeContentRect
|
|
{
|
|
if (!_DOMWindow)
|
|
return [self contentRect];
|
|
|
|
var contentRect = CGRectMakeZero();
|
|
|
|
if (window.screenTop)
|
|
contentRect.origin = CGPointMake(_DOMWindow.screenLeft, _DOMWindow.screenTop);
|
|
|
|
else if (window.screenX)
|
|
contentRect.origin = CGPointMake(_DOMWindow.screenX, _DOMWindow.screenY);
|
|
|
|
// Safari, Mozilla, Firefox, and Opera
|
|
if (_DOMWindow.innerWidth)
|
|
contentRect.size = CGSizeMake(_DOMWindow.innerWidth, _DOMWindow.innerHeight);
|
|
|
|
// Internet Explorer 6 in Strict Mode
|
|
else if (document.documentElement && document.documentElement.clientWidth)
|
|
contentRect.size = CGSizeMake(_DOMWindow.document.documentElement.clientWidth, _DOMWindow.document.documentElement.clientHeight);
|
|
|
|
// Internet Explorer X
|
|
else
|
|
contentRect.size = CGSizeMake(_DOMWindow.document.body.clientWidth, _DOMWindow.document.body.clientHeight);
|
|
|
|
return contentRect;
|
|
}
|
|
|
|
- (void)updateNativeContentRect
|
|
{
|
|
if (!_DOMWindow)
|
|
return;
|
|
|
|
var origin = [self contentRect].origin,
|
|
nativeOrigin = [self nativeContentRect].origin;
|
|
|
|
if (origin.x !== nativeOrigin.x || origin.y !== nativeOrigin.y)
|
|
{
|
|
_DOMWindow.moveBy(origin.x - nativeOrigin.x, origin.y - nativeOrigin.y);
|
|
}
|
|
|
|
var size = [self contentRect].size,
|
|
nativeSize = [self nativeContentRect].size;
|
|
|
|
if (size.width !== nativeSize.width || size.height !== nativeSize.height)
|
|
{
|
|
_DOMWindow.resizeBy(size.width - nativeSize.width, size.height - nativeSize.height);
|
|
}
|
|
}
|
|
|
|
- (void)orderBack:(CPWindow)aWindow
|
|
{
|
|
if (_DOMWindow)
|
|
_DOMWindow.blur();
|
|
}
|
|
|
|
- (void)createDOMElements
|
|
{
|
|
var theDocument = _DOMWindow.document;
|
|
|
|
// This guy fixes an issue in Firefox where if you focus the URL field, we stop getting key events
|
|
_DOMFocusElement = theDocument.createElement("input");
|
|
|
|
_DOMFocusElement.style.position = "absolute";
|
|
_DOMFocusElement.style.zIndex = "-1000";
|
|
_DOMFocusElement.style.opacity = "0";
|
|
_DOMFocusElement.style.filter = "alpha(opacity=0)";
|
|
_DOMFocusElement.className = "cpdontremove";
|
|
|
|
_DOMBodyElement.appendChild(_DOMFocusElement);
|
|
|
|
// Create a full screen div to protect against iframes and other elements
|
|
// from consuming events during tracking
|
|
// FIXME: multiple windows
|
|
_DOMEventGuard = theDocument.createElement("div");
|
|
_DOMEventGuard.style.position = "absolute";
|
|
_DOMEventGuard.style.top = "0px";
|
|
_DOMEventGuard.style.left = "0px";
|
|
_DOMEventGuard.style.width = "100%";
|
|
_DOMEventGuard.style.height = "100%";
|
|
_DOMEventGuard.style.zIndex = "999";
|
|
_DOMEventGuard.style.display = "none";
|
|
_DOMEventGuard.className = "cpdontremove";
|
|
_DOMBodyElement.appendChild(_DOMEventGuard);
|
|
|
|
// We get scrolling deltas from this element
|
|
_DOMScrollingElement = theDocument.createElement("div");
|
|
_DOMScrollingElement.style.position = "absolute";
|
|
_DOMScrollingElement.style.visibility = "hidden";
|
|
_DOMScrollingElement.style.zIndex = @"999";
|
|
_DOMScrollingElement.style.height = "500px";
|
|
_DOMScrollingElement.style.width = "500px";
|
|
_DOMScrollingElement.style.overflow = "scroll";
|
|
//_DOMScrollingElement.style.backgroundColor = "rgba(0,0,0,1.0)"; // debug help.
|
|
_DOMScrollingElement.style.opacity = "0";
|
|
_DOMScrollingElement.style.filter = "alpha(opacity=0)";
|
|
_DOMScrollingElement.className = "cpdontremove";
|
|
_DOMBodyElement.appendChild(_DOMScrollingElement);
|
|
|
|
var _DOMInnerScrollingElement = theDocument.createElement("div");
|
|
_DOMInnerScrollingElement.style.width = "5000px";
|
|
_DOMInnerScrollingElement.style.height = "5000px";
|
|
_DOMScrollingElement.appendChild(_DOMInnerScrollingElement);
|
|
|
|
// Set an initial scroll offset
|
|
_DOMScrollingElement.scrollTop = 150;
|
|
_DOMScrollingElement.scrollLeft = 150;
|
|
}
|
|
|
|
- (void)registerDOMWindow
|
|
{
|
|
var theDocument = _DOMWindow.document;
|
|
|
|
_DOMBodyElement = theDocument.getElementById("cappuccino-body") || theDocument.body;
|
|
|
|
// FIXME: Always do this?
|
|
if (supportsNativeDragAndDrop)
|
|
_DOMBodyElement.style["-khtml-user-select"] = "none";
|
|
|
|
_DOMBodyElement.webkitTouchCallout = "none";
|
|
_DOMBodyElement.style[CPBrowserStyleProperty(@"user-select")] = @"none";
|
|
|
|
[self createDOMElements];
|
|
[self _addLayers];
|
|
|
|
[_platformPasteboard setDOMWindow:_DOMWindow];
|
|
|
|
var theClass = [self class],
|
|
|
|
dragEventImplementation = class_getMethodImplementation(theClass, @selector(dragEvent:)),
|
|
dragEventCallback = function (anEvent) { dragEventImplementation(self, nil, anEvent); },
|
|
|
|
resizeEventSelector = @selector(resizeEvent:),
|
|
resizeEventImplementation = class_getMethodImplementation(theClass, resizeEventSelector),
|
|
resizeEventCallback = function (anEvent) { resizeEventImplementation(self, nil, anEvent); },
|
|
|
|
keyEventSelector = @selector(keyEvent:),
|
|
keyEventImplementation = class_getMethodImplementation(theClass, keyEventSelector),
|
|
keyEventCallback = function (anEvent) { return keyEventImplementation(self, nil, anEvent); },
|
|
|
|
mouseEventSelector = @selector(mouseEvent:),
|
|
mouseEventImplementation = class_getMethodImplementation(theClass, mouseEventSelector),
|
|
mouseEventCallback = function (anEvent) { mouseEventImplementation(self, nil, anEvent); },
|
|
|
|
contextMenuEventSelector = @selector(contextMenuEvent:),
|
|
contextMenuEventImplementation = class_getMethodImplementation(theClass, contextMenuEventSelector),
|
|
contextMenuEventCallback = function (anEvent) { return contextMenuEventImplementation(self, nil, anEvent); },
|
|
|
|
scrollEventSelector = @selector(scrollEvent:),
|
|
scrollEventImplementation = class_getMethodImplementation(theClass, scrollEventSelector),
|
|
scrollEventCallback = function (anEvent) { scrollEventImplementation(self, nil, anEvent); },
|
|
|
|
touchEventSelector = @selector(touchEvent:),
|
|
touchEventImplementation = class_getMethodImplementation(theClass, touchEventSelector),
|
|
touchEventCallback = function (anEvent) { touchEventImplementation(self, nil, anEvent); },
|
|
|
|
onFocusEventSelector = @selector(focusEvent:),
|
|
onFocusEventImplementation = class_getMethodImplementation(theClass, onFocusEventSelector),
|
|
onFocusEventCallback = function (anEvent) { onFocusEventImplementation(self, nil, anEvent); },
|
|
|
|
onBlurEventSelector = @selector(blurEvent:),
|
|
onBlurEventImplementation = class_getMethodImplementation(theClass, onBlurEventSelector),
|
|
onBlurEventCallback = function (anEvent) { onBlurEventImplementation(self, nil, anEvent); };
|
|
|
|
if (theDocument.addEventListener)
|
|
{
|
|
if ([CPPlatform supportsDragAndDrop])
|
|
{
|
|
theDocument.addEventListener("dragstart", dragEventCallback, NO);
|
|
theDocument.addEventListener("drag", dragEventCallback, NO);
|
|
theDocument.addEventListener("dragend", dragEventCallback, NO);
|
|
theDocument.addEventListener("dragover", dragEventCallback, NO);
|
|
theDocument.addEventListener("dragleave", dragEventCallback, NO);
|
|
theDocument.addEventListener("drop", dragEventCallback, NO);
|
|
}
|
|
|
|
theDocument.addEventListener("mouseup", mouseEventCallback, NO);
|
|
theDocument.addEventListener("mousedown", mouseEventCallback, NO);
|
|
theDocument.addEventListener("mousemove", mouseEventCallback, NO);
|
|
theDocument.addEventListener("contextmenu", contextMenuEventCallback, NO);
|
|
|
|
theDocument.addEventListener("keyup", keyEventCallback, NO);
|
|
theDocument.addEventListener("keydown", keyEventCallback, NO);
|
|
theDocument.addEventListener("keypress", keyEventCallback, NO);
|
|
|
|
theDocument.addEventListener("touchstart", touchEventCallback, {passive: false});
|
|
theDocument.addEventListener("touchend", touchEventCallback, {passive: false});
|
|
theDocument.addEventListener("touchmove", touchEventCallback, {passive: false});
|
|
theDocument.addEventListener("touchcancel", touchEventCallback, {passive: false});
|
|
|
|
_DOMWindow.addEventListener("DOMMouseScroll", scrollEventCallback, NO);
|
|
_DOMWindow.addEventListener("wheel", scrollEventCallback, NO);
|
|
_DOMWindow.addEventListener("mousewheel", scrollEventCallback, NO);
|
|
|
|
_DOMWindow.addEventListener("resize", resizeEventCallback, NO);
|
|
|
|
_DOMWindow.addEventListener("blur", onBlurEventCallback, NO);
|
|
_DOMWindow.addEventListener("focus", onFocusEventCallback, NO);
|
|
|
|
_DOMWindow.addEventListener("unload", function()
|
|
{
|
|
_DOMWindow.removeEventListener("unload", arguments.callee, NO);
|
|
|
|
[self blurEvent:nil];
|
|
[self _notifyPlatformWindowWillClose];
|
|
[self updateFromNativeContentRect];
|
|
[self _removeLayers];
|
|
|
|
theDocument.removeEventListener("mouseup", mouseEventCallback, NO);
|
|
theDocument.removeEventListener("mousedown", mouseEventCallback, NO);
|
|
theDocument.removeEventListener("mousemove", mouseEventCallback, NO);
|
|
theDocument.removeEventListener("contextmenu", contextMenuEventCallback, NO);
|
|
|
|
theDocument.removeEventListener("keyup", keyEventCallback, NO);
|
|
theDocument.removeEventListener("keydown", keyEventCallback, NO);
|
|
theDocument.removeEventListener("keypress", keyEventCallback, NO);
|
|
|
|
theDocument.removeEventListener("touchstart", touchEventCallback, NO);
|
|
theDocument.removeEventListener("touchend", touchEventCallback, NO);
|
|
theDocument.removeEventListener("touchmove", touchEventCallback, NO);
|
|
|
|
_DOMWindow.removeEventListener("resize", resizeEventCallback, NO);
|
|
|
|
_DOMWindow.removeEventListener("blur", onBlurEventCallback, NO);
|
|
_DOMWindow.removeEventListener("focus", onFocusEventCallback, NO);
|
|
|
|
//FIXME: does firefox really need a different value?
|
|
_DOMWindow.removeEventListener("DOMMouseScroll", scrollEventCallback, NO);
|
|
_DOMWindow.removeEventListener("wheel", scrollEventCallback, NO);
|
|
_DOMWindow.removeEventListener("mousewheel", scrollEventCallback, NO);
|
|
|
|
[PlatformWindows removeObject:self];
|
|
|
|
[_platformPasteboard setDOMWindow:nil];
|
|
|
|
self._DOMWindow = nil;
|
|
}, NO);
|
|
}
|
|
else
|
|
{
|
|
theDocument.attachEvent("onmouseup", mouseEventCallback);
|
|
theDocument.attachEvent("onmousedown", mouseEventCallback);
|
|
theDocument.attachEvent("onmousemove", mouseEventCallback);
|
|
theDocument.attachEvent("ondblclick", mouseEventCallback);
|
|
theDocument.attachEvent("oncontextmenu", contextMenuEventCallback);
|
|
|
|
theDocument.attachEvent("onkeyup", keyEventCallback);
|
|
theDocument.attachEvent("onkeydown", keyEventCallback);
|
|
theDocument.attachEvent("onkeypress", keyEventCallback);
|
|
|
|
_DOMWindow.attachEvent("onresize", resizeEventCallback);
|
|
|
|
_DOMWindow.attachEvent("onfocus", onFocusEventCallback);
|
|
_DOMWindow.attachEvent("onblur", onBlurEventCallback);
|
|
|
|
_DOMWindow.onmousewheel = scrollEventCallback;
|
|
theDocument.onmousewheel = scrollEventCallback;
|
|
|
|
_DOMBodyElement.ondrag = function () { return NO; };
|
|
_DOMBodyElement.onselectstart = function () { return _DOMWindow.event.srcElement === _platformPasteboard._DOMPasteboardElement; };
|
|
|
|
_DOMWindow.attachEvent("onunload", function()
|
|
{
|
|
_DOMWindow.detachEvent("unload", arguments.callee);
|
|
|
|
[self blurEvent:nil];
|
|
[self _notifyPlatformWindowWillClose];
|
|
[self updateFromNativeContentRect];
|
|
[self _removeLayers];
|
|
|
|
theDocument.detachEvent("onmouseup", mouseEventCallback);
|
|
theDocument.detachEvent("onmousedown", mouseEventCallback);
|
|
theDocument.detachEvent("onmousemove", mouseEventCallback);
|
|
theDocument.detachEvent("ondblclick", mouseEventCallback);
|
|
theDocument.detachEvent("oncontextmenu", contextMenuEventCallback);
|
|
|
|
theDocument.detachEvent("onkeyup", keyEventCallback);
|
|
theDocument.detachEvent("onkeydown", keyEventCallback);
|
|
theDocument.detachEvent("onkeypress", keyEventCallback);
|
|
|
|
_DOMWindow.detachEvent("onresize", resizeEventCallback);
|
|
|
|
_DOMWindow.detachEvent("onfocus", onBlurEventCallback);
|
|
_DOMWindow.detachEvent("onblur", onFocusEventCallback);
|
|
|
|
_DOMWindow.onmousewheel = NULL;
|
|
|
|
theDocument.onmousewheel = NULL;
|
|
|
|
_DOMBodyElement.ondrag = NULL;
|
|
_DOMBodyElement.onselectstart = NULL;
|
|
|
|
[PlatformWindows removeObject:self];
|
|
|
|
[_platformPasteboard setDOMWindow:nil];
|
|
|
|
self._DOMWindow = nil;
|
|
}, NO);
|
|
}
|
|
}
|
|
|
|
+ (CPSet)visiblePlatformWindows
|
|
{
|
|
if ([[CPPlatformWindow primaryPlatformWindow] isVisible])
|
|
{
|
|
var set = [CPSet setWithSet:PlatformWindows];
|
|
[set addObject:[CPPlatformWindow primaryPlatformWindow]];
|
|
return set;
|
|
}
|
|
else
|
|
return PlatformWindows;
|
|
}
|
|
|
|
- (void)orderFront:(CPWindow)aWindow
|
|
{
|
|
if ([aWindow parentWindow])
|
|
return;
|
|
|
|
if (_DOMWindow)
|
|
return _DOMWindow.focus();
|
|
|
|
_DOMWindow = window.open("about:blank", "_blank", "menubar=no,location=no,resizable=yes,scrollbars=no,status=no,left=" + CGRectGetMinX(_contentRect) + ",top=" + CGRectGetMinY(_contentRect) + ",width=" + CGRectGetWidth(_contentRect) + ",height=" + CGRectGetHeight(_contentRect));
|
|
|
|
if (!_DOMWindow)
|
|
return;
|
|
|
|
[PlatformWindows addObject:self];
|
|
|
|
_DOMWindow.document.write('<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"></head><body style="background-color:transparent; overflow:hidden"></body></html>');
|
|
_DOMWindow.document.close();
|
|
|
|
if (self != [CPPlatformWindow primaryPlatformWindow])
|
|
_DOMWindow.document.title = _title;
|
|
|
|
[self registerDOMWindow];
|
|
|
|
_DOMBodyElement.style.cursor = [[CPCursor currentCursor] _cssString];
|
|
}
|
|
|
|
- (void)orderOut:(CPWindow)aWindow
|
|
{
|
|
if (!_DOMWindow)
|
|
return;
|
|
|
|
_DOMWindow.close();
|
|
}
|
|
|
|
- (void)dragEvent:(DOMEvent)aDOMEvent
|
|
{
|
|
var type = aDOMEvent.type,
|
|
dragServer = [CPDragServer sharedDragServer],
|
|
location = CGPointMake(aDOMEvent.clientX, aDOMEvent.clientY),
|
|
pasteboard = [_CPDOMDataTransferPasteboard DOMDataTransferPasteboard];
|
|
|
|
[pasteboard _setDataTransfer:aDOMEvent.dataTransfer];
|
|
|
|
if (aDOMEvent.type === "dragstart")
|
|
{
|
|
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
|
|
|
[pasteboard _setPasteboard:[dragServer draggingPasteboard]];
|
|
|
|
var draggedWindow = [dragServer draggedWindow],
|
|
draggedWindowFrame = [draggedWindow frame],
|
|
DOMDragElement = draggedWindow._DOMElement;
|
|
|
|
DOMDragElement.style.left = -CGRectGetWidth(draggedWindowFrame) + "px";
|
|
DOMDragElement.style.top = -CGRectGetHeight(draggedWindowFrame) + "px";
|
|
|
|
var parentNode = DOMDragElement.parentNode;
|
|
|
|
if (parentNode)
|
|
parentNode.removeChild(DOMDragElement);
|
|
|
|
_DOMBodyElement.appendChild(DOMDragElement);
|
|
|
|
var draggingOffset = [dragServer draggingOffset];
|
|
|
|
aDOMEvent.dataTransfer.setDragImage(DOMDragElement, draggingOffset.width, draggingOffset.height);
|
|
aDOMEvent.dataTransfer.effectAllowed = "all";
|
|
|
|
[dragServer draggingStartedInPlatformWindow:self globalLocation:[CPPlatform isBrowser] ? location : CGPointMake(aDOMEvent.screenX, aDOMEvent.screenY)];
|
|
}
|
|
else if (type === "drag")
|
|
{
|
|
var y = aDOMEvent.screenY;
|
|
|
|
if (CPFeatureIsCompatible(CPHTML5DragAndDropSourceYOffBy1))
|
|
y -= 1;
|
|
|
|
[dragServer draggingSourceUpdatedWithGlobalLocation:[CPPlatform isBrowser] ? location : CGPointMake(aDOMEvent.screenX, y)];
|
|
}
|
|
else if (type === "dragover" || type === "dragleave")
|
|
{
|
|
if (aDOMEvent.preventDefault)
|
|
aDOMEvent.preventDefault();
|
|
|
|
var dropEffect = "none",
|
|
dragOperation = [dragServer draggingUpdatedInPlatformWindow:self location:location];
|
|
|
|
if (dragOperation === CPDragOperationMove || dragOperation === CPDragOperationGeneric || dragOperation === CPDragOperationPrivate)
|
|
dropEffect = "move";
|
|
else if (dragOperation === CPDragOperationCopy)
|
|
dropEffect = "copy";
|
|
else if (dragOperation === CPDragOperationLink)
|
|
dropEffect = "link";
|
|
|
|
aDOMEvent.dataTransfer.dropEffect = dropEffect;
|
|
}
|
|
else if (type === "dragend")
|
|
{
|
|
var dropEffect = aDOMEvent.dataTransfer.dropEffect,
|
|
dragOperation;
|
|
|
|
if (dropEffect === "move")
|
|
dragOperation = CPDragOperationMove;
|
|
else if (dropEffect === "copy")
|
|
dragOperation = CPDragOperationCopy;
|
|
else if (dropEffect === "link")
|
|
dragOperation = CPDragOperationLink;
|
|
else
|
|
dragOperation = CPDragOperationNone;
|
|
|
|
[dragServer draggingEndedInPlatformWindow:self globalLocation:[CPPlatform isBrowser] ? location : CGPointMake(aDOMEvent.screenX, aDOMEvent.screenY) operation:dragOperation];
|
|
}
|
|
else //if (type === "drop")
|
|
{
|
|
[dragServer performDragOperationInPlatformWindow:self];
|
|
|
|
// W3C Model
|
|
if (aDOMEvent.preventDefault)
|
|
aDOMEvent.preventDefault();
|
|
|
|
if (aDOMEvent.stopPropagation)
|
|
aDOMEvent.stopPropagation();
|
|
}
|
|
|
|
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
|
}
|
|
|
|
- (void)keyEvent:(DOMEvent)aDOMEvent
|
|
{
|
|
var event,
|
|
location = _lastMouseEventLocation || CGPointMakeZero(),
|
|
timestamp = [CPEvent currentTimestamp],
|
|
sourceElement = aDOMEvent.target || aDOMEvent.srcElement,
|
|
windowNumber = [[CPApp keyWindow] windowNumber],
|
|
modifierFlags = (aDOMEvent.shiftKey ? CPShiftKeyMask : 0) |
|
|
(aDOMEvent.ctrlKey ? CPControlKeyMask : 0) |
|
|
(aDOMEvent.altKey ? CPAlternateKeyMask : 0) |
|
|
(aDOMEvent.metaKey ? CPCommandKeyMask : 0) |
|
|
(_capsLockActive ? CPAlphaShiftKeyMask : 0);
|
|
|
|
// With a few exceptions, all key events are blocked from propagating to
|
|
// the browser. Here the following exceptions are being allowed:
|
|
//
|
|
// - All keys pressed along with a ctrl or cmd key _unless_ they are in
|
|
// one of the two blacklists.
|
|
// - Any key listed in the whitelist.
|
|
//
|
|
// The ctrl/cmd keys are used for browser hotkeys as are the keys listed in
|
|
// the whitelist (F1-F12 at the time of writing).
|
|
//
|
|
// If a key is listed in both the blacklist and whitelist, the blacklist is
|
|
// checked first. The key will be blocked from propagating in that case.
|
|
|
|
StopDOMEventPropagation = YES;
|
|
|
|
// Make sure it is not in the blacklists.
|
|
if (!(CharacterKeysToPrevent[String.fromCharCode(aDOMEvent.keyCode || aDOMEvent.charCode).toLowerCase()] || KeyCodesToPrevent[aDOMEvent.keyCode]))
|
|
{
|
|
// It is not in the blacklist, let it through if the ctrl/cmd key is
|
|
// also down or it's in the whitelist.
|
|
if ((modifierFlags & (CPControlKeyMask | CPCommandKeyMask)) || KeyCodesToAllow[aDOMEvent.keyCode])
|
|
StopDOMEventPropagation = NO;
|
|
}
|
|
|
|
var overrideCharacters = nil,
|
|
charactersIgnoringModifiers = @"";
|
|
|
|
switch (aDOMEvent.type)
|
|
{
|
|
case "keydown":
|
|
// Grab and store the keycode now since it is correct and consistent at this point.
|
|
if (aDOMEvent.keyCode in MozKeyCodeToKeyCodeMap)
|
|
_keyCode = MozKeyCodeToKeyCodeMap[aDOMEvent.keyCode];
|
|
else
|
|
_keyCode = aDOMEvent.keyCode;
|
|
|
|
var characters;
|
|
|
|
// Handle key codes for which String.fromCharCode won't work.
|
|
// Refs #1036: In Internet Explorer, both 'which' and 'charCode' are undefined for special keys.
|
|
if (aDOMEvent.which === 0 || aDOMEvent.charCode === 0 || (aDOMEvent.which === undefined && aDOMEvent.charCode === undefined))
|
|
characters = KeyCodesToUnicodeMap[_keyCode];
|
|
|
|
if (!characters)
|
|
characters = String.fromCharCode(_keyCode).toLowerCase();
|
|
|
|
overrideCharacters = (modifierFlags & CPShiftKeyMask || _capsLockActive) ? characters.toUpperCase() : characters;
|
|
|
|
// check for caps lock state
|
|
if (_keyCode === CPKeyCodes.CAPS_LOCK)
|
|
{
|
|
_capsLockActive = YES;
|
|
|
|
// Make sure the caps lock flag is set in modifierFlags
|
|
modifierFlags |= CPAlphaShiftKeyMask;
|
|
}
|
|
|
|
if ([ModifierKeyCodes containsObject:_keyCode])
|
|
{
|
|
// A modifier key will never fire keypress. We don't need to do any other processing so we just fire it here and break.
|
|
event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags
|
|
timestamp:timestamp windowNumber:windowNumber context:nil
|
|
characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:_keyCode];
|
|
|
|
break;
|
|
}
|
|
else if (modifierFlags & (CPControlKeyMask | CPCommandKeyMask))
|
|
{
|
|
//we are simply going to skip all keypress events that use cmd/ctrl key
|
|
//this lets us be consistent in all browsers and send on the keydown
|
|
//which means we can cancel the event early enough, but only if sendEvent needs to
|
|
}
|
|
else if (CPKeyCodes.firesKeyPressEvent(_keyCode, _lastKey, aDOMEvent.shiftKey, aDOMEvent.ctrlKey, aDOMEvent.altKey))
|
|
{
|
|
// this branch is taken by events which fire keydown, keypress, and keyup.
|
|
// this is the only time we'll ALLOW character keys to propagate (needed for text fields)
|
|
StopDOMEventPropagation = NO;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
//this branch is taken by "remedial" key events
|
|
// In this state we continue to keypress and send the CPEvent
|
|
}
|
|
|
|
case "keypress":
|
|
// we unconditionally break on keypress events with modifiers,
|
|
// because we forced the event to be sent on the keydown
|
|
if (aDOMEvent.type === "keypress" && (modifierFlags & (CPControlKeyMask | CPCommandKeyMask)))
|
|
break;
|
|
|
|
var keyCode = _keyCode,
|
|
charCode = aDOMEvent.keyCode || aDOMEvent.charCode,
|
|
isARepeat = (_charCodes[keyCode] != nil);
|
|
|
|
_lastKey = keyCode;
|
|
_charCodes[keyCode] = charCode;
|
|
|
|
var characters = overrideCharacters;
|
|
// Is this a special key?
|
|
if (!characters && (aDOMEvent.which === 0 || aDOMEvent.charCode === 0))
|
|
characters = KeyCodesToUnicodeMap[charCode];
|
|
|
|
if (!characters)
|
|
characters = String.fromCharCode(charCode);
|
|
|
|
charactersIgnoringModifiers = characters.toLowerCase(); // FIXME: This isn't correct. It SHOULD include Shift.
|
|
|
|
// Safari won't send proper capitalization during cmd-key events
|
|
if (!overrideCharacters && (modifierFlags & CPCommandKeyMask) && ((modifierFlags & CPShiftKeyMask) || _capsLockActive))
|
|
characters = characters.toUpperCase();
|
|
|
|
event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags
|
|
timestamp:timestamp windowNumber:windowNumber context:nil
|
|
characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:charCode];
|
|
|
|
break;
|
|
|
|
case "keyup":
|
|
var keyCode = aDOMEvent.keyCode,
|
|
charCode = _charCodes[keyCode];
|
|
|
|
_keyCode = -1;
|
|
_lastKey = -1;
|
|
_charCodes[keyCode] = nil;
|
|
|
|
// check for caps lock state
|
|
if (keyCode === CPKeyCodes.CAPS_LOCK)
|
|
{
|
|
_capsLockActive = NO;
|
|
|
|
// Make sure the caps lock flag is cleared in modifierFlags
|
|
modifierFlags &= ~CPAlphaShiftKeyMask;
|
|
}
|
|
|
|
if ([ModifierKeyCodes containsObject:keyCode])
|
|
{
|
|
// A modifier key will never fire keypress. We don't need to do any other processing so we just fire it here and break.
|
|
event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags
|
|
timestamp:timestamp windowNumber:windowNumber context:nil
|
|
characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:_keyCode];
|
|
|
|
break;
|
|
}
|
|
|
|
var characters = KeyCodesToUnicodeMap[charCode] || String.fromCharCode(charCode);
|
|
charactersIgnoringModifiers = characters.toLowerCase();
|
|
|
|
if (!(modifierFlags & CPShiftKeyMask) && (modifierFlags & CPCommandKeyMask) && !_capsLockActive)
|
|
characters = charactersIgnoringModifiers;
|
|
|
|
event = [CPEvent keyEventWithType:CPKeyUp location:location modifierFlags:modifierFlags
|
|
timestamp: timestamp windowNumber:windowNumber context:nil
|
|
characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:NO keyCode:keyCode];
|
|
|
|
break;
|
|
}
|
|
|
|
if (event)
|
|
event._DOMEvent = aDOMEvent;
|
|
|
|
[_platformPasteboard windowMaySendKeyEvent:event];
|
|
|
|
if (event && ![_platformPasteboard windowShouldSuppressKeyEvent])
|
|
{
|
|
[CPApp sendEvent:event];
|
|
|
|
[_platformPasteboard windowDidSendKeyEvent:event];
|
|
}
|
|
|
|
var didStop = NO;
|
|
// Platform pasteboard can overrule the decision to stop propagation either way, or it might have no opinion.
|
|
if ([_platformPasteboard windowShouldStopPropagation] || (StopDOMEventPropagation && ![_platformPasteboard windowShouldNotStopPropagation]))
|
|
{
|
|
didStop = YES;
|
|
_CPDOMEventStop(aDOMEvent, self);
|
|
}
|
|
|
|
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
|
|
|
return !didStop;
|
|
}
|
|
|
|
- (void)scrollEvent:(DOMEvent)aDOMEvent
|
|
{
|
|
if (PreventScroll)
|
|
{
|
|
PreventScroll = false;
|
|
aDOMEvent.preventDefault();
|
|
}
|
|
|
|
if (_hideDOMScrollingElementTimeout)
|
|
{
|
|
clearTimeout(_hideDOMScrollingElementTimeout);
|
|
_hideDOMScrollingElementTimeout = nil;
|
|
}
|
|
|
|
if (!aDOMEvent)
|
|
aDOMEvent = window.event;
|
|
|
|
var location = nil;
|
|
if (CPFeatureIsCompatible(CPJavaScriptMouseWheelValues_8_15))
|
|
{
|
|
var x = aDOMEvent._offsetX || 0.0,
|
|
y = aDOMEvent._offsetY || 0.0,
|
|
element = aDOMEvent.target;
|
|
|
|
while (element.nodeType !== 1)
|
|
element = element.parentNode;
|
|
|
|
if (element.offsetParent)
|
|
{
|
|
do
|
|
{
|
|
x += element.offsetLeft;
|
|
y += element.offsetTop;
|
|
} while (element = element.offsetParent);
|
|
}
|
|
|
|
location = CGPointMake((x + ((aDOMEvent.clientX - 8) / 15)), (y + ((aDOMEvent.clientY - 8) / 15)));
|
|
}
|
|
else if (aDOMEvent._overrideLocation)
|
|
location = aDOMEvent._overrideLocation;
|
|
else
|
|
location = CGPointMake(aDOMEvent.clientX, aDOMEvent.clientY);
|
|
|
|
var deltaX = 0.0,
|
|
deltaY = 0.0,
|
|
windowNumber = 0,
|
|
timestamp = [CPEvent currentTimestamp],
|
|
modifierFlags = (aDOMEvent.shiftKey ? CPShiftKeyMask : 0) |
|
|
(aDOMEvent.ctrlKey ? CPControlKeyMask : 0) |
|
|
(aDOMEvent.altKey ? CPAlternateKeyMask : 0) |
|
|
(aDOMEvent.metaKey ? CPCommandKeyMask : 0);
|
|
|
|
// Show the dom element
|
|
_DOMScrollingElement.style.visibility = "visible";
|
|
_DOMScrollingElement.style.top = (location.y - 15) + @"px";
|
|
_DOMScrollingElement.style.left = (location.x - 15) + @"px";
|
|
|
|
// We let the browser handle the scrolling
|
|
StopDOMEventPropagation = NO;
|
|
|
|
var theWindow = [self hitTest:location];
|
|
|
|
if (!theWindow)
|
|
return;
|
|
|
|
windowNumber = [theWindow windowNumber];
|
|
location = [theWindow convertBridgeToBase:location];
|
|
|
|
var event = [CPEvent mouseEventWithType:CPScrollWheel location:location modifierFlags:modifierFlags
|
|
timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:-1 clickCount:1 pressure:0];
|
|
event._DOMEvent = aDOMEvent;
|
|
|
|
// We lag 1 event behind without this approach
|
|
window.requestAnimationFrame(function()
|
|
{
|
|
if (aDOMEvent.deltaMode !== undefined && aDOMEvent.deltaMode !== 0)
|
|
{
|
|
event._hasPreciseScrollingDeltas = NO;
|
|
event._scrollingDeltaX = aDOMEvent.deltaX;
|
|
event._scrollingDeltaY = aDOMEvent.deltaY;
|
|
event._deltaX = aDOMEvent.deltaX * SCROLLWHEEL_LINE_PIXELS;
|
|
event._deltaY = aDOMEvent.deltaY * SCROLLWHEEL_LINE_PIXELS;
|
|
}
|
|
else
|
|
{
|
|
event._hasPreciseScrollingDeltas = YES;
|
|
event._scrollingDeltaX = (_DOMScrollingElement.scrollLeft - 150) || aDOMEvent.deltaX || 0;
|
|
event._scrollingDeltaY = (_DOMScrollingElement.scrollTop - 150) || aDOMEvent.deltaY || 0;
|
|
event._deltaX = event._scrollingDeltaX;
|
|
event._deltaY = event._scrollingDeltaY;
|
|
}
|
|
|
|
// If we scroll super with momentum,
|
|
// there are so many events going off that
|
|
// a tiny percent don't actually have any deltas.
|
|
//
|
|
// This does *not* make scrolling appear sluggish,
|
|
// it just seems like that is something that happens.
|
|
//
|
|
// We get free performance boost if we skip sending these events,
|
|
// as sending a scroll event with no deltas doesn't do anything.
|
|
if (event._deltaX || event._deltaY)
|
|
[CPApp sendEvent:event];
|
|
|
|
// We set StopDOMEventPropagation = NO on line 1008
|
|
//if (StopDOMEventPropagation)
|
|
// _CPDOMEventStop(aDOMEvent, self);
|
|
|
|
// Reset the DOM elements scroll offset
|
|
_DOMScrollingElement.scrollLeft = 150;
|
|
_DOMScrollingElement.scrollTop = 150;
|
|
|
|
// this is needed to prevent flickering during scrolling
|
|
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
|
|
|
});
|
|
|
|
// We hide the dom element after a little bit
|
|
// so that other DOM elements such as inputs
|
|
// can receive events.
|
|
_hideDOMScrollingElementTimeout = setTimeout(function()
|
|
{
|
|
PreventScroll = true;
|
|
_DOMScrollingElement.style.visibility = "hidden";
|
|
}, 300);
|
|
}
|
|
|
|
- (void)resizeEvent:(DOMEvent)aDOMEvent
|
|
{
|
|
// This is a hack for the browser resize bug in safari.
|
|
// See bug ID: 1325
|
|
// https://github.com/cappuccino/cappuccino/issues/1325
|
|
// Addendum by Antoine Mercadal : I also noticed that reszing is causing
|
|
// problem under latest Firefox 13.0. Let's just use this hack
|
|
// for all browser now.
|
|
|
|
[resizeTimer invalidate];
|
|
resizeTimer = [CPTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(_actualResizeEvent) userInfo:nil repeats:NO];
|
|
}
|
|
|
|
- (void)_actualResizeEvent
|
|
{
|
|
_shouldUpdateContentRect = NO;
|
|
resizeTimer = nil;
|
|
|
|
// FIXME: This is not the right way to do this.
|
|
// We should pay attention to mouse down and mouse up in conjunction with this.
|
|
//window.liveResize = YES;
|
|
|
|
if ([CPPlatform isBrowser])
|
|
[CPApp._activeMenu cancelTracking];
|
|
|
|
var oldSize = [self contentRect].size;
|
|
|
|
[self updateFromNativeContentRect];
|
|
|
|
var levels = _windowLevels,
|
|
layers = _windowLayers,
|
|
levelCount = levels.length;
|
|
|
|
while (levelCount--)
|
|
{
|
|
var windows = [layers objectForKey:levels[levelCount]]._windows,
|
|
windowCount = windows.length;
|
|
|
|
while (windowCount--)
|
|
[windows[windowCount] resizeWithOldPlatformWindowSize:oldSize];
|
|
}
|
|
|
|
[[CPNotificationCenter defaultCenter] postNotificationName:CPApplicationDidChangeScreenParametersNotification
|
|
object:CPApp
|
|
userInfo:nil];
|
|
|
|
//window.liveResize = NO;
|
|
|
|
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
|
|
|
_shouldUpdateContentRect = YES;
|
|
}
|
|
|
|
/*!
|
|
@ignore
|
|
*/
|
|
- (void)blurEvent:(DOMEvent)aDOMEvent
|
|
{
|
|
if ([CPApp keyWindow] == _currentKeyWindow)
|
|
[_currentKeyWindow resignKeyWindow];
|
|
|
|
if ([CPApp mainWindow] == _currentMainWindow)
|
|
[_currentMainWindow resignMainWindow];
|
|
|
|
_previousKeyWindow = aDOMEvent ? _currentKeyWindow : nil;
|
|
_previousMainWindow = aDOMEvent ? _currentMainWindow : nil;
|
|
|
|
[blurTimer invalidate];
|
|
blurTimer = [CPTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(_blurEventTimer:) userInfo:nil repeats:NO];
|
|
}
|
|
|
|
/*!
|
|
@ignore
|
|
*/
|
|
- (void)_blurEventTimer:(CPTimer)aTimer
|
|
{
|
|
if (![CPApp mainWindow])
|
|
[[CPApplication sharedApplication] deactivate];
|
|
|
|
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
|
}
|
|
|
|
/*!
|
|
@ignore
|
|
*/
|
|
- (void)focusEvent:(DOMEvent)aDOMEvent
|
|
{
|
|
[blurTimer invalidate];
|
|
[CPApp activateIgnoringOtherApps:YES];
|
|
|
|
var keyWindow = _previousKeyWindow;
|
|
|
|
if (!keyWindow)
|
|
// If we don't have a previous key window take the modal window if we are in a modal session.
|
|
// If not in a modal session just take the first window in the most background layer
|
|
keyWindow = (CPApp._currentSession && CPApp._currentSession._window) || [[[_windowLayers objectForKey:[_windowLevels firstObject]] orderedWindows] firstObject];
|
|
|
|
if (!keyWindow)
|
|
return;
|
|
|
|
[self _makeKeyWindow:keyWindow];
|
|
|
|
if ([keyWindow isKeyWindow] && ([keyWindow firstResponder] === keyWindow || ![keyWindow firstResponder]))
|
|
[keyWindow makeFirstResponder:[keyWindow initialFirstResponder]];
|
|
|
|
[self _makeMainWindow:keyWindow];
|
|
|
|
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
|
|
|
_previousKeyWindow = nil;
|
|
_previousMainWindow = nil;
|
|
}
|
|
|
|
/*!
|
|
@ignore
|
|
*/
|
|
- (void)_makeKeyWindow:(CPWindow)aWindow
|
|
{
|
|
if ([CPApp keyWindow] === aWindow || ![aWindow canBecomeKeyWindow])
|
|
return;
|
|
|
|
[[CPApp keyWindow] resignKeyWindow];
|
|
[aWindow becomeKeyWindow];
|
|
}
|
|
|
|
/*!
|
|
@ignore
|
|
*/
|
|
- (void)_makeMainWindow:(CPWindow)aWindow
|
|
{
|
|
// Sheets cannot be main. Their parent window becomes main.
|
|
if (aWindow._isSheet)
|
|
{
|
|
[self _makeMainWindow:aWindow._parentView];
|
|
return;
|
|
}
|
|
|
|
if ([CPApp mainWindow] === aWindow || ![aWindow canBecomeMainWindow])
|
|
return;
|
|
|
|
[[CPApp mainWindow] resignMainWindow];
|
|
[aWindow becomeMainWindow];
|
|
}
|
|
|
|
|
|
- (void)touchEvent:(DOMEvent)aDOMEvent
|
|
{
|
|
var newEvent = {},
|
|
touch = aDOMEvent.touches.length ? aDOMEvent.touches[0] : aDOMEvent.changedTouches[0];
|
|
|
|
newEvent.timestamp = [CPEvent currentTimestamp];
|
|
newEvent.target = aDOMEvent.target;
|
|
newEvent.shiftKey = newEvent.ctrlKey = newEvent.altKey = newEvent.metaKey = false;
|
|
|
|
newEvent.clientX = touch.clientX;
|
|
|
|
/*
|
|
Normally the document can't scroll in Cappuccino: our body element has top:0 and bottom:0 with absolute positioning. So it should always be exactly the height of the viewport. The below handles a special case. iOS scrolls the document when the virtual keyboard is present and it needs to move a text input upwards visually to avoid covering the input with the keyboard. For most purposes we can ignore this, except here. In theory I think we could always apply this (scrollTop should always be 0 on every other device and situation) but let's be defensive and only apply it for touch events to minimise the risk of surprises.
|
|
*/
|
|
newEvent.clientY = _DOMWindow.document.body.scrollTop + touch.clientY;
|
|
|
|
newEvent.preventDefault = function() { if (aDOMEvent.preventDefault) aDOMEvent.preventDefault() };
|
|
newEvent.stopPropagation = function() { if (aDOMEvent.stopPropagation) aDOMEvent.stopPropagation() };
|
|
|
|
// single finger event-> simulate a simple mouse-click
|
|
if (aDOMEvent.touches && (aDOMEvent.touches.length == 1 || (aDOMEvent.touches.length == 0 && aDOMEvent.changedTouches.length == 1)))
|
|
{
|
|
switch (aDOMEvent.type)
|
|
{
|
|
case CPDOMEventTouchStart: newEvent.type = CPDOMEventMouseDown;
|
|
break;
|
|
case CPDOMEventTouchEnd: newEvent.type = CPDOMEventMouseUp;
|
|
break;
|
|
case CPDOMEventTouchMove: newEvent.type = CPDOMEventMouseMoved;
|
|
break;
|
|
case CPDOMEventTouchCancel: newEvent.type = CPDOMEventMouseUp;
|
|
break;
|
|
}
|
|
|
|
[self mouseEvent:newEvent];
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// two fingers->simulate scrolling events
|
|
if (aDOMEvent.touches && aDOMEvent.touches.length == 2)
|
|
{
|
|
if (aDOMEvent.preventDefault)
|
|
aDOMEvent.preventDefault();
|
|
|
|
if (aDOMEvent.stopPropagation)
|
|
aDOMEvent.stopPropagation();
|
|
|
|
switch (aDOMEvent.type)
|
|
{
|
|
case CPDOMEventTouchStart:
|
|
touchStartingPointX = touch.pageX;
|
|
touchStartingPointY = touch.pageY;
|
|
break;
|
|
case CPDOMEventTouchMove:
|
|
newEvent._hasPreciseScrollingDeltas = YES;
|
|
newEvent.deltaX = touchStartingPointX - touch.pageX;
|
|
newEvent.deltaY = touchStartingPointY - touch.pageY;
|
|
newEvent.type = CPDOMEventScrollWheel;
|
|
|
|
[self scrollEvent:newEvent];
|
|
|
|
touchStartingPointX = touch.pageX;
|
|
touchStartingPointY = touch.pageY;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// cancel other touch cases preventively
|
|
|
|
if (aDOMEvent.preventDefault)
|
|
aDOMEvent.preventDefault();
|
|
|
|
if (aDOMEvent.stopPropagation)
|
|
aDOMEvent.stopPropagation();
|
|
}
|
|
}
|
|
|
|
- (void)mouseEvent:(DOMEvent)aDOMEvent
|
|
{
|
|
var type = _overriddenEventType || aDOMEvent.type;
|
|
|
|
// IE's event order is down, up, up, dblclick, so we have create these events artificially.
|
|
if (type === @"dblclick")
|
|
{
|
|
_overriddenEventType = CPDOMEventMouseDown;
|
|
[self mouseEvent:aDOMEvent];
|
|
|
|
_overriddenEventType = CPDOMEventMouseUp;
|
|
[self mouseEvent:aDOMEvent];
|
|
|
|
_overriddenEventType = nil;
|
|
|
|
return;
|
|
}
|
|
|
|
var event,
|
|
location = CGPointMake(aDOMEvent.clientX, aDOMEvent.clientY),
|
|
timestamp = [CPEvent currentTimestamp],
|
|
sourceElement = (aDOMEvent.target || aDOMEvent.srcElement),
|
|
windowNumber = 0,
|
|
modifierFlags = (aDOMEvent.shiftKey ? CPShiftKeyMask : 0) |
|
|
(aDOMEvent.ctrlKey ? CPControlKeyMask : 0) |
|
|
(aDOMEvent.altKey ? CPAlternateKeyMask : 0) |
|
|
(aDOMEvent.metaKey ? CPCommandKeyMask : 0);
|
|
|
|
StopDOMEventPropagation = YES;
|
|
|
|
if (_mouseDownWindow)
|
|
windowNumber = [_mouseDownWindow windowNumber];
|
|
else
|
|
{
|
|
var theWindow = [self _mouseHitTest:location];
|
|
|
|
if ((aDOMEvent.type === CPDOMEventMouseDown) && theWindow)
|
|
_mouseDownWindow = theWindow;
|
|
|
|
windowNumber = [theWindow windowNumber];
|
|
}
|
|
|
|
if (windowNumber)
|
|
location = [CPApp._windows[windowNumber] convertPlatformWindowToBase:location];
|
|
|
|
if (type === "mouseup")
|
|
{
|
|
if (_mouseIsDown)
|
|
{
|
|
if (aDOMEvent.button !== _firstMouseDownButton)
|
|
return;
|
|
|
|
event = _CPEventFromNativeMouseEvent(aDOMEvent, _mouseDownIsRightClick ? CPRightMouseUp : CPLeftMouseUp, location, modifierFlags, timestamp, windowNumber, nil, -1, CPDOMEventGetClickCount(_lastMouseUp, timestamp, location), 0, nil);
|
|
|
|
_mouseIsDown = NO;
|
|
_lastMouseUp = event;
|
|
_mouseDownWindow = nil;
|
|
_mouseDownIsRightClick = NO;
|
|
_firstMouseDownButton = -1;
|
|
}
|
|
|
|
if (_DOMEventMode)
|
|
{
|
|
_DOMEventMode = NO;
|
|
return;
|
|
}
|
|
}
|
|
|
|
else if (type === "mousedown")
|
|
{
|
|
// If we receive a click event, then we invalidate any scheduled
|
|
// or visible tooltips
|
|
[_CPToolTip invalidateCurrentToolTipIfNeeded];
|
|
|
|
var button = aDOMEvent.button;
|
|
|
|
_mouseDownIsRightClick = button == 2 || (CPBrowserIsOperatingSystem(CPMacOperatingSystem) && button == 0 && modifierFlags & CPControlKeyMask);
|
|
|
|
// If mouse is already down, that means that a second mouse button is pushed. This could interfere in mouse events treatment. Just ignore it.
|
|
// BUT we have to track which button will be first released.
|
|
if (_mouseIsDown)
|
|
{
|
|
_mouseDownIsRightClick = !_mouseDownIsRightClick;
|
|
return;
|
|
}
|
|
|
|
_firstMouseDownButton = button;
|
|
|
|
if ((sourceElement.tagName === "INPUT" || sourceElement.tagName === "TEXTAREA") && sourceElement != _DOMFocusElement)
|
|
{
|
|
if ([CPPlatform supportsDragAndDrop])
|
|
{
|
|
_DOMBodyElement.setAttribute("draggable", "false");
|
|
_DOMBodyElement.style["-khtml-user-drag"] = "none";
|
|
}
|
|
|
|
_DOMEventMode = YES;
|
|
_mouseIsDown = YES;
|
|
|
|
// Fake a down and up event so that event tracking mode will work correctly
|
|
[CPApp sendEvent:[CPEvent mouseEventWithType:_mouseDownIsRightClick ? CPRightMouseDown : CPLeftMouseDown location:location modifierFlags:modifierFlags
|
|
timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:-1
|
|
clickCount:CPDOMEventGetClickCount(_lastMouseDown, timestamp, location) pressure:0]];
|
|
|
|
[CPApp sendEvent:[CPEvent mouseEventWithType:_mouseDownIsRightClick ? CPRightMouseUp : CPLeftMouseUp location:location modifierFlags:modifierFlags
|
|
timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:-1
|
|
clickCount:CPDOMEventGetClickCount(_lastMouseDown, timestamp, location) pressure:0]];
|
|
|
|
return;
|
|
}
|
|
else if ([CPPlatform supportsDragAndDrop])
|
|
{
|
|
_DOMBodyElement.setAttribute("draggable", "true");
|
|
_DOMBodyElement.style["-khtml-user-drag"] = "element";
|
|
}
|
|
|
|
StopContextMenuDOMEventPropagation = YES;
|
|
|
|
event = _CPEventFromNativeMouseEvent(aDOMEvent, _mouseDownIsRightClick ? CPRightMouseDown : CPLeftMouseDown, location, modifierFlags, timestamp, windowNumber, nil, -1, CPDOMEventGetClickCount(_lastMouseDown, timestamp, location), 0, nil);
|
|
|
|
_mouseIsDown = YES;
|
|
_lastMouseDown = event;
|
|
}
|
|
|
|
else // if (type === "mousemove" || type === "drag")
|
|
{
|
|
if (_DOMEventMode)
|
|
return;
|
|
|
|
// _lastMouseEventLocation might be nil on the very first mousemove event. Just send in the current location
|
|
// in this case - this will result in a delta x and delta y of 0 which seems natural for the first event.
|
|
event = _CPEventFromNativeMouseEvent(aDOMEvent, _mouseIsDown ? (_mouseDownIsRightClick ? CPRightMouseDragged : CPLeftMouseDragged) : CPMouseMoved, location, modifierFlags, timestamp, windowNumber, nil, -1, 1, 0, _lastMouseEventLocation || location);
|
|
}
|
|
|
|
var isDragging = [[CPDragServer sharedDragServer] isDragging];
|
|
|
|
if (event && (!isDragging || !supportsNativeDragAndDrop))
|
|
{
|
|
event._DOMEvent = aDOMEvent;
|
|
|
|
[CPApp sendEvent:event];
|
|
}
|
|
|
|
var didStop = NO;
|
|
if (StopDOMEventPropagation && (!supportsNativeDragAndDrop || type !== "mousedown" && !isDragging))
|
|
{
|
|
didStop = YES;
|
|
_CPDOMEventStop(aDOMEvent, self);
|
|
}
|
|
|
|
// If there are any tracking event listeners (listening for CPLeftMouseDraggedMask)
|
|
// then show the event guard so we don't lose events to iframes
|
|
var hasTrackingEventListener = NO;
|
|
|
|
for (var i = 0; i < CPApp._eventListeners.length; i++)
|
|
{
|
|
var listener = CPApp._eventListeners[i];
|
|
|
|
if (listener._callback !== _CPRunModalLoop && (listener._mask & CPLeftMouseDraggedMask))
|
|
{
|
|
hasTrackingEventListener = YES;
|
|
break;
|
|
}
|
|
}
|
|
|
|
_lastMouseEventLocation = location;
|
|
|
|
_DOMEventGuard.style.display = hasTrackingEventListener ? "" : "none";
|
|
|
|
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
|
return !didStop;
|
|
}
|
|
|
|
- (void)contextMenuEvent:(DOMEvent)aDOMEvent
|
|
{
|
|
if (StopContextMenuDOMEventPropagation)
|
|
_CPDOMEventStop(aDOMEvent, self);
|
|
|
|
return !StopContextMenuDOMEventPropagation;
|
|
}
|
|
|
|
- (CPArray)orderedWindowsAtLevel:(int)aLevel
|
|
{
|
|
var layer = [self layerAtLevel:aLevel create:NO];
|
|
|
|
if (!layer)
|
|
return [];
|
|
|
|
return [layer orderedWindows];
|
|
}
|
|
|
|
- (CPDOMWindowLayer)layerAtLevel:(int)aLevel create:(BOOL)aFlag
|
|
{
|
|
var layer = [_windowLayers objectForKey:aLevel];
|
|
|
|
// If the layer doesn't currently exist, and the create flag is true,
|
|
// create the layer.
|
|
if (!layer && aFlag)
|
|
{
|
|
layer = [[CPDOMWindowLayer alloc] initWithLevel:aLevel];
|
|
|
|
[_windowLayers setObject:layer forKey:aLevel];
|
|
|
|
// Find the nearest layer. This is similar to a binary search,
|
|
// only we know we won't find the value.
|
|
var low = 0,
|
|
high = _windowLevels.length - 1,
|
|
middle;
|
|
|
|
while (low <= high)
|
|
{
|
|
middle = FLOOR((low + high) / 2);
|
|
|
|
if (_windowLevels[middle] > aLevel)
|
|
high = middle - 1;
|
|
else
|
|
low = middle + 1;
|
|
}
|
|
|
|
var insertionIndex = 0;
|
|
|
|
if (middle !== undefined)
|
|
insertionIndex = _windowLevels[middle] > aLevel ? middle : middle + 1;
|
|
|
|
[_windowLevels insertObject:aLevel atIndex:insertionIndex];
|
|
layer._DOMElement.style.zIndex = aLevel + 1; // adding one avoids negative zIndices. These have been causing issues in Chrome
|
|
|
|
_DOMBodyElement.appendChild(layer._DOMElement);
|
|
}
|
|
|
|
return layer;
|
|
}
|
|
|
|
- (void)order:(CPWindowOrderingMode)orderingMode window:(CPWindow)aWindow relativeTo:(CPWindow)otherWindow
|
|
{
|
|
if (!_DOMWindow)
|
|
return;
|
|
|
|
[CPPlatform initializeScreenIfNecessary];
|
|
|
|
// Grab the appropriate level for the layer, and create it if
|
|
// necessary (if we are not simply removing the window).
|
|
var layer = [self layerAtLevel:[aWindow level] create:orderingMode !== CPWindowOut];
|
|
|
|
// When ordering out, ignore otherWindow, simply remove aWindow from its level.
|
|
// If layer is nil, this will be a no-op.
|
|
if (orderingMode === CPWindowOut)
|
|
{
|
|
[aWindow _windowWillBeRemovedFromTheDOM];
|
|
return [layer removeWindow:aWindow];
|
|
}
|
|
|
|
/*
|
|
If aWindow is a child of otherWindow and is not yet visible,
|
|
aWindow must actually be ordered relative to:
|
|
|
|
- otherWindow's last child which is not aWindow, or
|
|
- the furthest parent of aWindow
|
|
|
|
whichever is frontmost (orderingMode === CPWindowAbove) or rearmost
|
|
(orderingMode === CPWindowBelow).
|
|
*/
|
|
|
|
if (![aWindow isVisible] && otherWindow && [aWindow parentWindow] === otherWindow)
|
|
{
|
|
var children = [otherWindow childWindows],
|
|
lastChild = [children lastObject];
|
|
|
|
if (lastChild === aWindow)
|
|
{
|
|
if ([children count] > 1)
|
|
otherWindow = [children objectAtIndex:[children count] - 2];
|
|
}
|
|
else if (lastChild)
|
|
otherWindow = lastChild;
|
|
|
|
var furthestParent = [self _furthestParentOf:otherWindow];
|
|
|
|
if ((orderingMode === CPWindowAbove && furthestParent._index > otherWindow._index) ||
|
|
(orderingMode === CPWindowBelow && furthestParent._index < otherWindow._index))
|
|
otherWindow = furthestParent;
|
|
}
|
|
|
|
/*
|
|
If a child window is ordered front, the furthest parent is actually
|
|
the one that is ordered front, and all of the descendent children
|
|
are ordered after it.
|
|
*/
|
|
else if (orderingMode === CPWindowAbove && !otherWindow)
|
|
aWindow = [self _furthestParentOf:aWindow];
|
|
|
|
var insertionIndex = CPNotFound;
|
|
|
|
if (otherWindow)
|
|
insertionIndex = orderingMode === CPWindowAbove ? otherWindow._index + 1 : otherWindow._index;
|
|
|
|
[aWindow _windowWillBeAddedToTheDOM];
|
|
|
|
// Place the window at the appropriate index.
|
|
[layer insertWindow:aWindow atIndex:insertionIndex];
|
|
|
|
// If aWindow is a parent, recursively order all of its children after it
|
|
if ([[aWindow childWindows] count])
|
|
[self _orderChildWindowsOf:aWindow furthestParent:[self _furthestParentOf:aWindow] layer:layer];
|
|
|
|
[aWindow _setHasBeenOrderedIn:YES];
|
|
}
|
|
|
|
- (CPWindow)_furthestParentOf:(CPWindow)aWindow
|
|
{
|
|
var parent;
|
|
|
|
while ((parent = [aWindow parentWindow]))
|
|
aWindow = parent;
|
|
|
|
return aWindow;
|
|
}
|
|
|
|
- (void)_orderChildWindowsOf:(CPWindow)aWindow furthestParent:(CPWindow)furthestParent layer:(CPDOMWindowLayer)aLayer
|
|
{
|
|
// When a parent window is ordered, Cocoa orders its child windows
|
|
// relative to it or the furthest parent.
|
|
var children = [aWindow childWindows],
|
|
count = [children count],
|
|
parent = aWindow,
|
|
parentLevel = [parent level];
|
|
|
|
for (var i = 0; i < count; ++i)
|
|
{
|
|
var child = children[i],
|
|
childWasVisible = [child isVisible];
|
|
|
|
// If a child is not visible and has not yet been ordered in, skip it
|
|
if (!childWasVisible && ![child _hasBeenOrderedIn])
|
|
continue;
|
|
|
|
// If a user moved level of the child window, we should respect that
|
|
if ([child level] !== parentLevel)
|
|
continue;
|
|
|
|
var ordering = [child _childOrdering];
|
|
|
|
if ((ordering === CPWindowAbove && furthestParent._index > parent._index) ||
|
|
(ordering === CPWindowBelow && furthestParent._index < parent._index))
|
|
parent = furthestParent;
|
|
|
|
var index = ordering === CPWindowAbove ? parent._index + 1 : parent._index;
|
|
|
|
if (!childWasVisible)
|
|
[child _windowWillBeAddedToTheDOM];
|
|
|
|
[aLayer insertWindow:child atIndex:index];
|
|
|
|
if (!childWasVisible)
|
|
[child _parentDidOrderInChild];
|
|
|
|
if ([[child childWindows] count])
|
|
[self _orderChildWindowsOf:child furthestParent:furthestParent layer:aLayer];
|
|
|
|
parent = child;
|
|
}
|
|
}
|
|
|
|
- (void)_removeLayers
|
|
{
|
|
var levels = _windowLevels,
|
|
layers = _windowLayers,
|
|
levelCount = levels.length;
|
|
|
|
while (levelCount--)
|
|
{
|
|
var layer = [layers objectForKey:levels[levelCount]];
|
|
|
|
_DOMBodyElement.removeChild(layer._DOMElement);
|
|
}
|
|
}
|
|
|
|
- (void)_addLayers
|
|
{
|
|
var levels = _windowLevels,
|
|
layers = _windowLayers,
|
|
levelCount = levels.length;
|
|
|
|
while (levelCount--)
|
|
{
|
|
var layer = [layers objectForKey:levels[levelCount]];
|
|
|
|
_DOMBodyElement.appendChild(layer._DOMElement);
|
|
}
|
|
}
|
|
|
|
/* @ignore */
|
|
- (id)_dragHitTest:(CGPoint)aPoint pasteboard:(CPPasteboard)aPasteboard
|
|
{
|
|
var levels = _windowLevels,
|
|
layers = _windowLayers,
|
|
levelCount = levels.length;
|
|
|
|
while (levelCount--)
|
|
{
|
|
// Skip any windows above or at the dragging level.
|
|
if (levels[levelCount] >= CPDraggingWindowLevel)
|
|
continue;
|
|
|
|
var windows = [layers objectForKey:levels[levelCount]]._windows,
|
|
windowCount = windows.length;
|
|
|
|
while (windowCount--)
|
|
{
|
|
var theWindow = windows[windowCount];
|
|
|
|
if ([theWindow _sharesChromeWithPlatformWindow])
|
|
return [theWindow _dragHitTest:aPoint pasteboard:aPasteboard];
|
|
|
|
if ([theWindow containsPoint:aPoint])
|
|
return [theWindow _dragHitTest:aPoint pasteboard:aPasteboard];
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
/* @ignore */
|
|
- (void)_propagateCurrentDOMEvent:(BOOL)aFlag
|
|
{
|
|
StopDOMEventPropagation = !aFlag;
|
|
}
|
|
|
|
- (BOOL)_willPropagateCurrentDOMEvent
|
|
{
|
|
return !StopDOMEventPropagation;
|
|
}
|
|
|
|
- (void)_propagateContextMenuDOMEvent:(BOOL)aFlag
|
|
{
|
|
if (aFlag && CPBrowserIsEngine(CPGeckoBrowserEngine))
|
|
StopDOMEventPropagation = !aFlag;
|
|
|
|
StopContextMenuDOMEventPropagation = !aFlag;
|
|
}
|
|
|
|
- (BOOL)_willPropagateContextMenuDOMEvent
|
|
{
|
|
return StopContextMenuDOMEventPropagation;
|
|
}
|
|
|
|
- (CPWindow)_mouseHitTest:(CGPoint)location
|
|
{
|
|
return [self _hitTest:location withTest:@selector(_isValidMousePoint:)]
|
|
}
|
|
|
|
- (CPWindow)hitTest:(CGPoint)location
|
|
{
|
|
return [self _hitTest:location withTest:@selector(containsPoint:)]
|
|
}
|
|
|
|
- (CPWindow)_hitTest:(CGPoint)location withTest:(SEL)aTest
|
|
{
|
|
if (self._only)
|
|
return self._only;
|
|
|
|
var levels = _windowLevels,
|
|
layers = _windowLayers,
|
|
levelCount = levels.length,
|
|
theWindow = nil;
|
|
|
|
while (levelCount-- && !theWindow)
|
|
{
|
|
var windows = [layers objectForKey:levels[levelCount]]._windows,
|
|
windowCount = windows.length;
|
|
|
|
while (windowCount-- && !theWindow)
|
|
{
|
|
var candidateWindow = windows[windowCount];
|
|
|
|
if (!candidateWindow._ignoresMouseEvents && [candidateWindow performSelector:aTest withObject:location])
|
|
theWindow = candidateWindow;
|
|
}
|
|
}
|
|
|
|
return theWindow;
|
|
}
|
|
|
|
/*! @ignore Return the selected text in the DOM window if known. */
|
|
- (CPString)_selectedText
|
|
{
|
|
if (_DOMWindow.getSelection)
|
|
return "" + _DOMWindow.getSelection();
|
|
else if (_DOMWindow.document.getSelection)
|
|
return "" + _DOMWindow.document.getSelection();
|
|
else if (_DOMWindow.selection)
|
|
return "" + _DOMWindow.selection.createRange().text;
|
|
else
|
|
return nil;
|
|
}
|
|
|
|
/*!
|
|
Set the text selection range to the given range within the given element, which must be a child of
|
|
this DOM window.
|
|
*/
|
|
- (void)setSelectedRange:(CPRange)aRange inElement:(DOMElement)anElement
|
|
{
|
|
if (_DOMWindow.getSelection())
|
|
{
|
|
var domRange = _DOMWindow.document.createRange();
|
|
domRange.setStart(anElement.childNodes[0], aRange.location);
|
|
domRange.setEnd(anElement.childNodes[0], CPMaxRange(aRange));
|
|
_DOMWindow.getSelection().removeAllRanges();
|
|
_DOMWindow.getSelection().addRange(domRange);
|
|
}
|
|
else if (_DOMWindow.document.selection)
|
|
{
|
|
var domRange = _DOMWindow.document.body.createTextRange();
|
|
domRange.moveToElementText(anElement);
|
|
domRange.collapse(true);
|
|
domRange.moveStart('character', aRange.location);
|
|
domRange.moveEnd('character', aRange.length);
|
|
domRange.select();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
When using command (mac) or control (windows), keys are propagated to the browser by default.
|
|
To prevent a character key from propagating (to prevent its default action, and instead use it
|
|
in your own application), use these methods. These methods are additive -- the list builds until you clear it.
|
|
|
|
@param characters a list of characters to stop propagating keypresses to the browser.
|
|
*/
|
|
+ (void)preventCharacterKeysFromPropagating:(CPArray)characters
|
|
{
|
|
for (var i = characters.length; i > 0; i--)
|
|
CharacterKeysToPrevent["" + characters[i - 1].toLowerCase()] = YES;
|
|
}
|
|
|
|
/*!
|
|
@param character a character to stop propagating keypresses to the browser.
|
|
*/
|
|
+ (void)preventCharacterKeyFromPropagating:(CPString)character
|
|
{
|
|
CharacterKeysToPrevent[character.toLowerCase()] = YES;
|
|
}
|
|
|
|
/*!
|
|
Clear the list of characters for which we are not sending keypresses to the browser.
|
|
*/
|
|
+ (void)clearCharacterKeysToPreventFromPropagating
|
|
{
|
|
CharacterKeysToPrevent = {};
|
|
}
|
|
|
|
/*!
|
|
Prevent these keyCodes from sending their keypresses to the browser.
|
|
@param keyCodes an array of keycodes to prevent propagation.
|
|
*/
|
|
+ (void)preventKeyCodesFromPropagating:(CPArray)keyCodes
|
|
{
|
|
for (var i = keyCodes.length; i > 0; i--)
|
|
KeyCodesToPrevent[keyCodes[i - 1]] = YES;
|
|
}
|
|
|
|
/*!
|
|
Prevent this keyCode from sending its key events to the browser.
|
|
@param keyCode a keycode to prevent propagation.
|
|
*/
|
|
+ (void)preventKeyCodeFromPropagating:(CPString)keyCode
|
|
{
|
|
KeyCodesToPrevent[keyCode] = YES;
|
|
}
|
|
|
|
/*!
|
|
Clear the list of keyCodes for which we are not sending keypresses to the browser.
|
|
*/
|
|
+ (void)clearKeyCodesToPreventFromPropagating
|
|
{
|
|
KeyCodesToPrevent = {};
|
|
}
|
|
|
|
- (void)_notifyPlatformWindowWillClose
|
|
{
|
|
[[CPNotificationCenter defaultCenter] postNotificationName:_CPPlatformWindowWillCloseNotification
|
|
object:self
|
|
userInfo:nil];
|
|
}
|
|
|
|
@end
|
|
#endif
|
|
|
|
var CPEventClass = [CPEvent class];
|
|
|
|
var _CPEventFromNativeMouseEvent = function(aNativeEvent, anEventType, aPoint, modifierFlags, aTimestamp, aWindowNumber, aGraphicsContext, anEventNumber, aClickCount, aPressure, aMouseDragStart)
|
|
{
|
|
aNativeEvent.isa = CPEventClass;
|
|
|
|
aNativeEvent._type = anEventType;
|
|
aNativeEvent._location = aPoint;
|
|
aNativeEvent._modifierFlags = modifierFlags;
|
|
aNativeEvent._timestamp = aTimestamp;
|
|
aNativeEvent._windowNumber = aWindowNumber;
|
|
aNativeEvent._window = nil;
|
|
aNativeEvent._context = aGraphicsContext;
|
|
aNativeEvent._eventNumber = anEventNumber;
|
|
aNativeEvent._clickCount = aClickCount;
|
|
aNativeEvent._pressure = aPressure;
|
|
if ((anEventType == CPLeftMouseDragged) || (anEventType == CPRightMouseDragged) || (anEventType == CPMouseMoved))
|
|
{
|
|
aNativeEvent._deltaX = aPoint.x - aMouseDragStart.x;
|
|
aNativeEvent._deltaY = aPoint.y - aMouseDragStart.y;
|
|
}
|
|
else
|
|
{
|
|
aNativeEvent._deltaX = 0;
|
|
aNativeEvent._deltaY = 0;
|
|
}
|
|
|
|
|
|
return aNativeEvent;
|
|
};
|
|
|
|
var CLICK_SPACE_DELTA = 5.0,
|
|
CLICK_TIME_DELTA = (typeof document != "undefined" && document.addEventListener) ? 0.55 : 1.0;
|
|
|
|
CPDOMEventGetClickCount = function(aComparisonEvent, aTimestamp, aLocation)
|
|
{
|
|
if (!aComparisonEvent)
|
|
return 1;
|
|
|
|
var comparisonLocation = [aComparisonEvent locationInWindow];
|
|
|
|
return (aTimestamp - [aComparisonEvent timestamp] < CLICK_TIME_DELTA &&
|
|
ABS(comparisonLocation.x - aLocation.x) < CLICK_SPACE_DELTA &&
|
|
ABS(comparisonLocation.y - aLocation.y) < CLICK_SPACE_DELTA) ? [aComparisonEvent clickCount] + 1 : 1;
|
|
};
|
|
|
|
// Global.
|
|
_CPDOMEventStop = function(aDOMEvent, aPlatformWindow)
|
|
{
|
|
if (aDOMEvent.preventDefault) // W3C Model
|
|
aDOMEvent.preventDefault();
|
|
else // IE Model
|
|
aDOMEvent.returnValue = false;
|
|
|
|
if (aDOMEvent.stopPropagation) // W3C Model
|
|
aDOMEvent.stopPropagation();
|
|
else // IE Model
|
|
aDOMEvent.cancelBubble = true;
|
|
};
|
|
|
|
function CPWindowObjectList()
|
|
{
|
|
var platformWindows = [CPPlatformWindow visiblePlatformWindows],
|
|
platformWindowEnumerator = [platformWindows objectEnumerator],
|
|
platformWindow = nil,
|
|
windowObjects = [];
|
|
|
|
while ((platformWindow = [platformWindowEnumerator nextObject]) !== nil)
|
|
{
|
|
var levels = platformWindow._windowLevels,
|
|
layers = platformWindow._windowLayers,
|
|
levelCount = levels.length;
|
|
|
|
while (levelCount--)
|
|
{
|
|
var windows = [layers objectForKey:levels[levelCount]]._windows,
|
|
windowCount = windows.length;
|
|
|
|
while (windowCount--)
|
|
windowObjects.push(windows[windowCount]);
|
|
}
|
|
}
|
|
|
|
return windowObjects;
|
|
}
|
|
|
|
function CPWindowList()
|
|
{
|
|
var windowObjectList = CPWindowObjectList();
|
|
|
|
return [windowObjectList arrayByApplyingBlock:function(windowObject)
|
|
{
|
|
return [windowObject windowNumber];
|
|
}];
|
|
}
|