Created
August 8, 2012 16:18
-
-
Save aljungberg/3296312 to your computer and use it in GitHub Desktop.
paste event handling edition
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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/CPObject.j> | |
@import <Foundation/CPRunLoop.j> | |
@import "CPEvent.j" | |
@import "CPText.j" | |
@import "CPCompatibility.j" | |
@import "CPDOMWindowLayer.j" | |
@import "CPPlatform.j" | |
@import "CPPlatformWindow.j" | |
@import "CPPlatformWindow+DOMKeys.j" | |
// List of all open native windows | |
var PlatformWindows = [CPSet set]; | |
// Define up here so compressor knows about em. | |
var CPDOMEventGetClickCount, | |
CPDOMEventStop, | |
StopDOMEventPropagation, | |
StopContextMenuDOMEventPropagation; | |
//right now we hard code q, w, r and t as keys to propogate | |
//these aren't normal keycodes, they are with modifier key codes | |
//might be mac only, we should investigate futher later. | |
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 | |
], | |
supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop]; | |
var resizeTimer = nil; | |
#define HAS_INPUT_OR_TEXTAREA_TARGET(aDOMEvent) (((aDOMEvent).target || (aDOMEvent).srcElement).nodeName.toUpperCase() == "INPUT" || ((aDOMEvent).target || (aDOMEvent).srcElement).nodeName.toUpperCase() == "TEXTAREA") | |
@implementation CPPlatformWindow (DOM) | |
- (id)_init | |
{ | |
self = [super init]; | |
if (self) | |
{ | |
_DOMWindow = window; | |
_contentRect = _CGRectMakeZero(); | |
_windowLevels = []; | |
_windowLayers = [CPDictionary dictionary]; | |
[self registerDOMWindow]; | |
[self updateFromNativeContentRect]; | |
_charCodes = {}; | |
} | |
return self; | |
} | |
- (CGRect)nativeContentRect | |
{ | |
if (!_DOMWindow) | |
return [self contentRect]; | |
if (_DOMWindow.cpFrame) | |
return _DOMWindow.cpFrame(); | |
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; | |
if (typeof _DOMWindow["cpSetFrame"] === "function") | |
return _DOMWindow.cpSetFrame([self contentRect]); | |
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:(id)aSender | |
{ | |
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 Native Pasteboard handler. | |
_DOMPasteboardElement = theDocument.createElement("textarea"); | |
_DOMPasteboardElement.style.position = "absolute"; | |
_DOMPasteboardElement.style.top = "-10000px"; | |
_DOMPasteboardElement.style.zIndex = "999"; | |
_DOMPasteboardElement.className = "cpdontremove"; | |
_DOMBodyElement.appendChild(_DOMPasteboardElement); | |
// Make sure the pastboard element is blurred. | |
_DOMPasteboardElement.blur(); | |
// 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 = "60px"; | |
_DOMScrollingElement.style.width = "60px"; | |
_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 = "400px"; | |
_DOMInnerScrollingElement.style.height = "400px"; | |
_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"; | |
[self createDOMElements]; | |
[self _addLayers]; | |
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); }, | |
copyEventSelector = @selector(copyEvent:), | |
copyEventImplementation = class_getMethodImplementation(theClass, copyEventSelector), | |
copyEventCallback = function (anEvent) {copyEventImplementation(self, nil, anEvent); }, | |
pasteEventSelector = @selector(pasteEvent:), | |
pasteEventImplementation = class_getMethodImplementation(theClass, pasteEventSelector), | |
pasteEventCallback = function (anEvent) {pasteEventImplementation(self, nil, anEvent); }, | |
keyEventSelector = @selector(keyEvent:), | |
keyEventImplementation = class_getMethodImplementation(theClass, keyEventSelector), | |
keyEventCallback = function (anEvent) { 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); }; | |
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("beforecopy", copyEventCallback, NO); | |
theDocument.addEventListener("beforecut", copyEventCallback, NO); | |
CPLog.info("CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature)? " + CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature)); | |
if (CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature)) | |
{ | |
theDocument.addEventListener("paste", pasteEventCallback, NO); | |
} | |
else | |
theDocument.addEventListener("beforepaste", pasteEventCallback, NO); | |
theDocument.addEventListener("keyup", keyEventCallback, NO); | |
theDocument.addEventListener("keydown", keyEventCallback, NO); | |
theDocument.addEventListener("keypress", keyEventCallback, NO); | |
theDocument.addEventListener("touchstart", touchEventCallback, NO); | |
theDocument.addEventListener("touchend", touchEventCallback, NO); | |
theDocument.addEventListener("touchmove", touchEventCallback, NO); | |
theDocument.addEventListener("touchcancel", touchEventCallback, NO); | |
_DOMWindow.addEventListener("DOMMouseScroll", scrollEventCallback, NO); | |
_DOMWindow.addEventListener("mousewheel", scrollEventCallback, NO); | |
_DOMWindow.addEventListener("resize", resizeEventCallback, NO); | |
_DOMWindow.addEventListener("unload", function() | |
{ | |
[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("beforecopy", copyEventCallback, NO); | |
theDocument.removeEventListener("beforecut", copyEventCallback, NO); | |
if (CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature)) | |
{ | |
_DOMWindow.removeEventListener("paste", pasteEventCallback, NO); | |
theDocument.removeEventListener("paste", pasteEventCallback, NO); | |
} | |
else | |
theDocument.removeEventListener("beforepaste", pasteEventCallback, NO); | |
theDocument.removeEventListener("touchstart", touchEventCallback, NO); | |
theDocument.removeEventListener("touchend", touchEventCallback, NO); | |
theDocument.removeEventListener("touchmove", touchEventCallback, NO); | |
_DOMWindow.removeEventListener("resize", resizeEventCallback, NO); | |
//FIXME: does firefox really need a different value? | |
_DOMWindow.removeEventListener("DOMMouseScroll", scrollEventCallback, NO); | |
_DOMWindow.removeEventListener("mousewheel", scrollEventCallback, NO); | |
//_DOMWindow.removeEventListener("beforeunload", this, NO); | |
[PlatformWindows removeObject:self]; | |
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.onmousewheel = scrollEventCallback; | |
theDocument.onmousewheel = scrollEventCallback; | |
_DOMBodyElement.ondrag = function () { return NO; }; | |
_DOMBodyElement.onselectstart = function () { return _DOMWindow.event.srcElement === _DOMPasteboardElement; }; | |
_DOMWindow.attachEvent("onunload", function() | |
{ | |
[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.onmousewheel = NULL; | |
theDocument.onmousewheel = NULL; | |
_DOMBodyElement.ondrag = NULL; | |
_DOMBodyElement.onselectstart = NULL; | |
//_DOMWindow.removeEvent("beforeunload", this); | |
[PlatformWindows removeObject:self]; | |
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:(id)aSender | |
{ | |
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)); | |
[PlatformWindows addObject:self]; | |
// FIXME: cpSetFrame? | |
_DOMWindow.document.write("<!DOCTYPE html><html lang='en'><head></head><body style='background-color:transparent;'></body></html>"); | |
_DOMWindow.document.close(); | |
if (self != [CPPlatformWindow primaryPlatformWindow]) | |
_DOMWindow.document.title = _title; | |
if (![CPPlatform isBrowser]) | |
{ | |
_DOMWindow.cpWindowNumber = [self._only windowNumber]; | |
_DOMWindow.cpSetFrame(_contentRect); | |
_DOMWindow.cpSetLevel(_level); | |
_DOMWindow.cpSetHasShadow(_hasShadow); | |
_DOMWindow.cpSetShadowStyle(_shadowStyle); | |
} | |
[self registerDOMWindow]; | |
_DOMBodyElement.style.cursor = [[CPCursor currentCursor] _cssString]; | |
} | |
- (void)orderOut:(id)aSender | |
{ | |
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; | |
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, | |
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); | |
// 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 isNativePasteEvent = NO, | |
isNativeCopyOrCutEvent = NO, | |
overrideCharacters = nil; | |
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; | |
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 | |
var eligibleForCopyPaste = [self _validateCopyCutOrPasteEvent:aDOMEvent flags:modifierFlags]; | |
// If this could be a native PASTE event, then we need to further examine it before | |
// sending a CPEvent. Select our element to see if anything gets pasted in it. | |
if (characters === "v" && eligibleForCopyPaste) | |
{ | |
if (!_ignoreNativePastePreparation) | |
{ | |
_DOMPasteboardElement.select(); | |
_DOMPasteboardElement.value = ""; | |
} | |
isNativePasteEvent = YES; | |
} | |
// However, of this could be a native COPY event, we need to let the normal event-process take place so it | |
// can capture our internal Cappuccino pasteboard. | |
else if ((characters == "c" || characters == "x") && eligibleForCopyPaste) | |
{ | |
isNativeCopyOrCutEvent = YES; | |
if (_ignoreNativeCopyOrCutEvent) | |
break; | |
} | |
} | |
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]; | |
if (isNativePasteEvent) | |
{ | |
CPLog.info("native paste event: %@", event); | |
_pasteboardKeyDownEvent = event; | |
// If we are using the paste into field hack, we need to check it in a moment. | |
if (!(CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature) && !CPFeatureIsCompatible(CPJavaScriptClipboardAccessFeature))) | |
window.setNativeTimeout(function () { [self _checkPasteboardElement]; }, 0); | |
} | |
break; | |
case "keyup": var keyCode = aDOMEvent.keyCode, | |
charCode = _charCodes[keyCode]; | |
_keyCode = -1; | |
_lastKey = -1; | |
_charCodes[keyCode] = nil; | |
_ignoreNativeCopyOrCutEvent = NO; | |
_ignoreNativePastePreparation = NO; | |
// check for caps lock state | |
if (keyCode === CPKeyCodes.CAPS_LOCK) | |
_capsLockActive = NO; | |
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 && !isNativePasteEvent) | |
{ | |
event._DOMEvent = aDOMEvent; | |
[CPApp sendEvent:event]; | |
if (isNativeCopyOrCutEvent) | |
{ | |
CPLog.info("isNativeCopyOrCutEvent %@", event); | |
// If this is a native copy event, then check if the pasteboard has anything in it. | |
[self _primePasteboardElement]; | |
} | |
} | |
if (StopDOMEventPropagation) | |
CPDOMEventStop(aDOMEvent, self); | |
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; | |
} | |
- (void)copyEvent:(DOMEvent)aDOMEvent | |
{ | |
if ([self _validateCopyCutOrPasteEvent:aDOMEvent flags:CPPlatformActionKeyMask] && !_ignoreNativeCopyOrCutEvent) | |
{ | |
// we have to send out a fake copy or cut event so that we can force the copy/cut mechanisms to take place | |
var cut = aDOMEvent.type === "beforecut", | |
keyCode = cut ? CPKeyCodes.X : CPKeyCodes.C, | |
characters = cut ? "x" : "c", | |
timestamp = [CPEvent currentTimestamp], // fake event, might as well use current timestamp | |
windowNumber = [[CPApp keyWindow] windowNumber], | |
modifierFlags = CPPlatformActionKeyMask; | |
event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags | |
timestamp:timestamp windowNumber:windowNumber context:nil | |
characters:characters charactersIgnoringModifiers:characters isARepeat:NO keyCode:keyCode]; | |
event._DOMEvent = aDOMEvent; | |
[CPApp sendEvent:event]; | |
[self _primePasteboardElement]; | |
//then we have to IGNORE the real keyboard event to prevent a double copy | |
//safari also sends the beforecopy event twice, so we additionally check here and prevent two events | |
_ignoreNativeCopyOrCutEvent = YES; | |
} | |
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; | |
} | |
- (boolean)pasteEvent:(DOMEvent)aDOMEvent | |
{ | |
console.log("pasteEvent: ", aDOMEvent); | |
if (CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature) || CPFeatureIsCompatible(CPJavaScriptClipboardAccessFeature)) | |
{ | |
var value = CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature) ? aDOMEvent.clipboardData.getData('text/plain') : window.clipboardData.getData("Text"); | |
CPLog.info("pasteEvent value=%@", value); | |
if ([value length]) | |
{ | |
var pasteboard = [CPPasteboard generalPasteboard]; | |
if ([pasteboard _stateUID] != value) | |
{ | |
[pasteboard declareTypes:[CPStringPboardType] owner:self]; | |
[pasteboard setString:value forType:CPStringPboardType]; | |
} | |
} | |
[CPApp sendEvent:_pasteboardKeyDownEvent]; | |
_pasteboardKeyDownEvent = nil; | |
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; | |
if (!HAS_INPUT_OR_TEXTAREA_TARGET(aDOMEvent)) | |
CPDOMEventStop(aDOMEvent, self); | |
return; | |
} | |
// Set up to capture the paste in a temporary input field. We'll send the event after capture. | |
if ([self _validateCopyCutOrPasteEvent:aDOMEvent flags:CPPlatformActionKeyMask]) | |
{ | |
_DOMPasteboardElement.focus(); | |
_DOMPasteboardElement.select(); | |
_DOMPasteboardElement.value = ""; | |
_ignoreNativePastePreparation = YES; | |
} | |
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; | |
} | |
- (void)_validateCopyCutOrPasteEvent:(DOMEvent)aDOMEvent flags:(unsigned)modifierFlags | |
{ | |
return (!HAS_INPUT_OR_TEXTAREA_TARGET(aDOMEvent) || aDOMEvent.target === _DOMPasteboardElement) && (modifierFlags & CPPlatformActionKeyMask); | |
} | |
- (void)_primePasteboardElement | |
{ | |
CPLog.info("_primePasteboardElement"); | |
var pasteboard = [CPPasteboard generalPasteboard], | |
types = [pasteboard types]; | |
if (types.length) | |
{ | |
if ([types indexOfObjectIdenticalTo:CPStringPboardType] != CPNotFound) | |
_DOMPasteboardElement.value = [pasteboard stringForType:CPStringPboardType]; | |
else | |
_DOMPasteboardElement.value = [pasteboard _generateStateUID]; | |
_DOMPasteboardElement.focus(); | |
_DOMPasteboardElement.select(); | |
CPLog.info("primed with %@", _DOMPasteboardElement.value); | |
window.setNativeTimeout(function() { [self _clearPasteboardElement]; }, 0); | |
} | |
} | |
- (void)_checkPasteboardElement | |
{ | |
var value = _DOMPasteboardElement.value; | |
CPLog.info("_checkPasteboardElement value=%@", value); | |
if ([value length]) | |
{ | |
var pasteboard = [CPPasteboard generalPasteboard]; | |
if ([pasteboard _stateUID] != value) | |
{ | |
[pasteboard declareTypes:[CPStringPboardType] owner:self]; | |
[pasteboard setString:value forType:CPStringPboardType]; | |
} | |
} | |
[self _clearPasteboardElement]; | |
CPLog.info("sendEvent: %@", _pasteboardKeyDownEvent); | |
[CPApp sendEvent:_pasteboardKeyDownEvent]; | |
_pasteboardKeyDownEvent = nil; | |
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; | |
} | |
- (void)_clearPasteboardElement | |
{ | |
CPLog.info("_clearPasteboardElement"); | |
_DOMPasteboardElement.value = ""; | |
_DOMPasteboardElement.blur(); | |
} | |
- (void)scrollEvent:(DOMEvent)aDOMEvent | |
{ | |
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; | |
var 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 timeout. | |
setTimeout(function() | |
{ | |
// Find the scroll delta | |
var deltaX = _DOMScrollingElement.scrollLeft - 150, | |
deltaY = _DOMScrollingElement.scrollTop - 150; | |
// 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 (deltaX || deltaY) | |
{ | |
event._deltaX = deltaX; | |
event._deltaY = 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; | |
// Is this needed? | |
//[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; | |
}, 0); | |
// We hide the dom element after a little bit | |
// so that other DOM elements such as inputs | |
// can receive events. | |
_hideDOMScrollingElementTimeout = setTimeout(function() | |
{ | |
_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 | |
{ | |
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]; | |
} | |
//window.liveResize = NO; | |
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; | |
} | |
- (void)touchEvent:(DOMEvent)aDOMEvent | |
{ | |
if (aDOMEvent.touches && (aDOMEvent.touches.length == 1 || (aDOMEvent.touches.length == 0 && aDOMEvent.changedTouches.length == 1))) | |
{ | |
var newEvent = {}; | |
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; | |
} | |
var touch = aDOMEvent.touches.length ? aDOMEvent.touches[0] : aDOMEvent.changedTouches[0]; | |
newEvent.clientX = touch.clientX; | |
newEvent.clientY = touch.clientY; | |
newEvent.timestamp = [CPEvent currentTimestamp]; | |
newEvent.target = aDOMEvent.target; | |
newEvent.shiftKey = newEvent.ctrlKey = newEvent.altKey = newEvent.metaKey = false; | |
newEvent.preventDefault = function() { if (aDOMEvent.preventDefault) aDOMEvent.preventDefault() }; | |
newEvent.stopPropagation = function() { if (aDOMEvent.stopPropagation) aDOMEvent.stopPropagation() }; | |
[self mouseEvent:newEvent]; | |
return; | |
} | |
else | |
{ | |
if (aDOMEvent.preventDefault) | |
aDOMEvent.preventDefault(); | |
if (aDOMEvent.stopPropagation) | |
aDOMEvent.stopPropagation(); | |
} | |
// handle touch cases specifically | |
} | |
- (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 hitTest:location]; | |
if ((aDOMEvent.type === CPDOMEventMouseDown) && theWindow) | |
_mouseDownWindow = theWindow; | |
windowNumber = [theWindow windowNumber]; | |
} | |
if (windowNumber) | |
location = [CPApp._windows[windowNumber] convertPlatformWindowToBase:location]; | |
if (type === "mouseup") | |
{ | |
if (_mouseIsDown) | |
{ | |
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; | |
} | |
if (_DOMEventMode) | |
{ | |
_DOMEventMode = NO; | |
return; | |
} | |
} | |
else if (type === "mousedown") | |
{ | |
var button = aDOMEvent.button; | |
_mouseDownIsRightClick = button == 2 || (CPBrowserIsOperatingSystem(CPMacOperatingSystem) && button == 0 && modifierFlags & CPControlKeyMask); | |
if (sourceElement.tagName === "INPUT" && 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]; | |
} | |
if (StopDOMEventPropagation && (!supportsNativeDragAndDrop || type !== "mousedown" && !isDragging)) | |
CPDOMEventStop(aDOMEvent, self); | |
// if there are any tracking event listeners then show the event guard so we don't lose events to iframes | |
// TODO Actually check for tracking event listeners, not just any listener but _CPRunModalLoop. | |
var hasTrackingEventListener = NO; | |
for (var i = 0; i < CPApp._eventListeners.length; i++) | |
{ | |
if (CPApp._eventListeners[i]._callback !== _CPRunModalLoop) | |
{ | |
hasTrackingEventListener = YES; | |
break; | |
} | |
} | |
_lastMouseEventLocation = location; | |
_DOMEventGuard.style.display = hasTrackingEventListener ? "" : "none"; | |
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; | |
} | |
- (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; | |
_DOMBodyElement.appendChild(layer._DOMElement); | |
} | |
return layer; | |
} | |
- (void)order:(CPWindowOrderingMode)aPlace window:(CPWindow)aWindow relativeTo:(CPWindow)otherWindow | |
{ | |
[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:aPlace !== CPWindowOut]; | |
// Ignore otherWindow, simply remove this window from it's level. | |
// If layer is nil, this will be a no-op. | |
if (aPlace === CPWindowOut) | |
return [layer removeWindow:aWindow]; | |
var insertionIndex = CPNotFound; | |
if (otherWindow) | |
insertionIndex = aPlace === CPWindowAbove ? otherWindow._index + 1 : otherWindow._index; | |
// Place the window at the appropriate index. | |
[layer insertWindow:aWindow atIndex:insertionIndex]; | |
} | |
- (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:(CPPoint)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)hitTest:(CPPoint)location | |
{ | |
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 containsPoint:location]) | |
theWindow = candidateWindow; | |
} | |
} | |
return theWindow; | |
} | |
/*! | |
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 = {}; | |
} | |
@end | |
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) ? 350.0 : 1000.0; | |
var 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; | |
}; | |
var CPDOMEventStop = function(aDOMEvent, aPlatformWindow) | |
{ | |
// IE Model | |
aDOMEvent.cancelBubble = true; | |
aDOMEvent.returnValue = false; | |
// W3C Model | |
if (aDOMEvent.preventDefault) | |
aDOMEvent.preventDefault(); | |
if (aDOMEvent.stopPropagation) | |
aDOMEvent.stopPropagation(); | |
if (aDOMEvent.type === CPDOMEventMouseDown) | |
{ | |
aPlatformWindow._DOMFocusElement.focus(); | |
aPlatformWindow._DOMFocusElement.blur(); | |
} | |
}; | |
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(), | |
windowList = []; | |
for (var i = 0, count = [windowObjectList count]; i < count; i++) | |
windowList.push([windowObjectList[i] windowNumber]); | |
return windowList; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment