diff --git a/AppKit/CPApplication.j b/AppKit/CPApplication.j index 9bad05ec9..0af618766 100644 --- a/AppKit/CPApplication.j +++ b/AppKit/CPApplication.j @@ -319,7 +319,7 @@ CPRunContinuesResponse = -1002; } else { - [[[CPApp mainWindow] platformWindow] _propagateCurrentDOMEvent:YES]; + [[[self keyWindow] platformWindow] _propagateCurrentDOMEvent:YES]; } } @@ -548,13 +548,28 @@ CPRunContinuesResponse = -1002; { _currentEvent = anEvent; + var willPropagate = [[[anEvent window] platformWindow] _willPropagateCurrentDOMEvent]; + + // temporarily pretend we won't propagate the event. we'll restore the saved value later + // we do this outside the if so that changes user code might make in _handleKeyEquiv. are preserved + [[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO]; + // Check if this is a candidate for key equivalent... if ([anEvent _couldBeKeyEquivalent] && [self _handleKeyEquivalent:anEvent]) { - [[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO]; + var characters = [anEvent characters], + modifierFlags = [anEvent modifierFlags]; + + // Unconditionally propagate on these keys to solve browser copy paste bugs + if ((characters == "c" || characters == "x" || characters == "v") && (modifierFlags & CPPlatformActionKeyMask)) + [[[anEvent window] platformWindow] _propagateCurrentDOMEvent:YES]; + return; } + // if we make it this far, then restore the original willPropagate value + [[[anEvent window] platformWindow] _propagateCurrentDOMEvent:willPropagate]; + if (_eventListeners.length) { if (_eventListeners[_eventListeners.length - 1]._mask & (1 << [anEvent type])) diff --git a/AppKit/CPCompatibility.j b/AppKit/CPCompatibility.j index 38a0b1d19..b8457e38c 100644 --- a/AppKit/CPCompatibility.j +++ b/AppKit/CPCompatibility.j @@ -31,6 +31,11 @@ CPKHTMLBrowserEngine = 3; CPOperaBrowserEngine = 4; CPWebKitBrowserEngine = 5; +// Operating Systems +CPMacOperatingSystem = 0; +CPWindowsOperatingSystem = 1; +CPOtherOperatingSystem = 2; + // Features CPCSSRGBAFeature = 1 << 5; @@ -183,11 +188,20 @@ function CPFeatureIsCompatible(aFeature) function CPBrowserIsEngine(anEngine) { - return PLATFORM_ENGINE == anEngine; + return PLATFORM_ENGINE === anEngine; } -if (USER_AGENT.indexOf("Mac") != -1) +function CPBrowserIsOperatingSystem(anOperatingSystem) { + return OPERATING_SYSTEM === anOperatingSystem; +} + +OPERATING_SYSTEM = CPOtherOperatingSystem; + +if (USER_AGENT.indexOf("Mac") !== -1) +{ + OPERATING_SYSTEM = CPMacOperatingSystem; + CPPlatformActionKeyMask = CPCommandKeyMask; CPUndoKeyEquivalent = @"Z"; @@ -198,6 +212,9 @@ if (USER_AGENT.indexOf("Mac") != -1) } else { + if (USER_AGENT.indexOf("Windows") !== -1) + OPERATING_SYSTEM = CPWindowsOperatingSystem; + CPPlatformActionKeyMask = CPControlKeyMask; CPUndoKeyEquivalent = @"Z"; diff --git a/AppKit/CPTextField.j b/AppKit/CPTextField.j index 9be50e5ff..a14b065e1 100644 --- a/AppKit/CPTextField.j +++ b/AppKit/CPTextField.j @@ -639,6 +639,9 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); else [[self window] selectNextKeyView:self]; + if ([[[self window] firstResponder] respondsToSelector:@selector(selectText:)]) + [[[self window] firstResponder] selectText:self]; + [[[self window] platformWindow] _propagateCurrentDOMEvent:NO]; } else @@ -781,11 +784,24 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); #if PLATFORM(DOM) var element = [self _inputElement]; - if (element.parentNode === _DOMElement && ([self isEditable] || [self isSelectable])) - window.setTimeout(function() { element.select(); }, 0); + if (([self isEditable] || [self isSelectable])) + { + if ([[self window] firstResponder] === self) + window.setTimeout(function() { element.select(); }, 0); + else + { + [[self window] makeFirstResponder:self]; + window.setTimeout(function() {[self selectText:sender];}, 0); + } + } #endif } +- (void)selectAll:(id)sender +{ + [self selectText:sender]; +} + #pragma mark Setting the Delegate - (void)setDelegate:(id)aDelegate diff --git a/AppKit/CPWindow/CPWindow.j b/AppKit/CPWindow/CPWindow.j index 9143c17f2..62c0f7665 100644 --- a/AppKit/CPWindow/CPWindow.j +++ b/AppKit/CPWindow/CPWindow.j @@ -1738,6 +1738,9 @@ CPTexturedBackgroundWindowMask [[CPNotificationCenter defaultCenter] postNotificationName:CPWindowWillCloseNotification object:self]; [self orderOut:nil]; + + if ([self isFullBridge]) + [[self platformWindow] _propagateCurrentDOMEvent:YES]; } // Managing Main Status diff --git a/AppKit/Platform/CPPlatformWindow.j b/AppKit/Platform/CPPlatformWindow.j index 5ad005aad..f55be6360 100644 --- a/AppKit/Platform/CPPlatformWindow.j +++ b/AppKit/Platform/CPPlatformWindow.j @@ -30,6 +30,10 @@ var PrimaryPlatformWindow = NULL; Object _charCodes; unsigned _keyCode; + unsigned _lastKey; + BOOL _capsLockActive; + BOOL _ignoreNativeCopyOrCutEvent; + BOOL _ignoreNativePastePreparation; BOOL _DOMEventMode; diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j index fd651cb10..ff0acbffa 100644 --- a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j +++ b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j @@ -66,7 +66,7 @@ * 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 in keypress + * Safari 2 in keypress (not supported) * * charCode keyCode which * ENTER: 13 13 13 @@ -117,27 +117,10 @@ @import "CPPlatform.j" @import "CPPlatformWindow.j" +@import "CPPlatformWindow+DOMKeys.j" #import "../../CoreGraphics/CGGeometry.h" - -var DoubleClick = "dblclick", - MouseDown = "mousedown", - MouseUp = "mouseup", - MouseMove = "mousemove", - MouseDrag = "mousedrag", - KeyUp = "keyup", - KeyDown = "keydown", - KeyPress = "keypress", - Copy = "copy", - Paste = "paste", - Resize = "resize", - ScrollWheel = "mousewheel", - TouchStart = "touchstart", - TouchMove = "touchmove", - TouchEnd = "touchend", - TouchCancel = "touchcancel"; - // Define up here so compressor knows about em. var CPDOMEventGetClickCount, CPDOMEventStop, @@ -148,9 +131,12 @@ var CPDOMEventGetClickCount, //might be mac only, we should investigate futher later. var KeyCodesToPrevent = {}, CharacterKeysToPrevent = {}, - KeyCodesWithoutKeyPressEvents = { '8':1, '9':1, '16':1, '33':1, '34':1, '35':1, '36':1, '37':1, '38':1, '39':1, '40':1, '46':1, '33':1, '34':1 }; + MozKeyCodeToKeyCodeMap = { + 61: 187, // =, equals + 59: 186 // ;, semicolon + }; -var CTRL_KEY_CODE = 17; +KeyCodesToPrevent[CPKeyCodes.A] = YES; var supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop]; @@ -251,6 +237,7 @@ var supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop]; _DOMBodyElement.webkitTouchCallout = "none"; + // 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"; @@ -261,11 +248,11 @@ var supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop]; _DOMBodyElement.appendChild(_DOMFocusElement); // Create Native Pasteboard handler. - _DOMPasteboardElement = theDocument.createElement("input"); + _DOMPasteboardElement = theDocument.createElement("textarea"); _DOMPasteboardElement.style.position = "absolute"; _DOMPasteboardElement.style.top = "-10000px"; - _DOMPasteboardElement.style.zIndex = "99"; + _DOMPasteboardElement.style.zIndex = "999"; _DOMBodyElement.appendChild(_DOMPasteboardElement); @@ -283,6 +270,14 @@ var supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop]; 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); }, @@ -299,6 +294,7 @@ var supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop]; touchEventImplementation = class_getMethodImplementation(theClass, touchEventSelector), touchEventCallback = function (anEvent) { touchEventImplementation(self, nil, anEvent); }; + if (theDocument.addEventListener) { if ([CPPlatform supportsDragAndDrop]) @@ -315,6 +311,10 @@ var supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop]; theDocument.addEventListener("mousedown", mouseEventCallback, NO); theDocument.addEventListener("mousemove", mouseEventCallback, NO); + theDocument.addEventListener("beforecopy", copyEventCallback, NO); + theDocument.addEventListener("beforecut", copyEventCallback, NO); + theDocument.addEventListener("beforepaste", pasteEventCallback, NO); + theDocument.addEventListener("keyup", keyEventCallback, NO); theDocument.addEventListener("keydown", keyEventCallback, NO); theDocument.addEventListener("keypress", keyEventCallback, NO); @@ -342,6 +342,10 @@ var supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop]; theDocument.removeEventListener("keydown", keyEventCallback, NO); theDocument.removeEventListener("keypress", keyEventCallback, NO); + theDocument.removeEventListener("beforecopy", copyEventCallback, NO); + theDocument.removeEventListener("beforecut", copyEventCallback, NO); + theDocument.removeEventListener("beforepaste", pasteEventCallback, NO); + theDocument.removeEventListener("touchstart", touchEventCallback, NO); theDocument.removeEventListener("touchend", touchEventCallback, NO); theDocument.removeEventListener("touchmove", touchEventCallback, NO); @@ -538,12 +542,11 @@ var supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop]; (aDOMEvent.ctrlKey ? CPControlKeyMask : 0) | (aDOMEvent.altKey ? CPAlternateKeyMask : 0) | (aDOMEvent.metaKey ? CPCommandKeyMask : 0); - - - //We want to stop propagation if this is a command key AND this character or keycode has been added to our blacklist - StopDOMEventPropagation = !(modifierFlags & (CPControlKeyMask | CPCommandKeyMask)) || + + //We want to stop propagation if this is a command key AND this character or keycode has been added to our blacklist + StopDOMEventPropagation = !!(!(modifierFlags & (CPControlKeyMask | CPCommandKeyMask)) || CharacterKeysToPrevent[String.fromCharCode(aDOMEvent.keyCode || aDOMEvent.charCode).toLowerCase()] || - KeyCodesToPrevent[aDOMEvent.keyCode]; + KeyCodesToPrevent[aDOMEvent.keyCode]); var isNativePasteEvent = NO, isNativeCopyOrCutEvent = NO, @@ -552,56 +555,80 @@ var supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop]; switch (aDOMEvent.type) { case "keydown": // Grab and store the keycode now since it is correct and consistent at this point. - _keyCode = aDOMEvent.keyCode; + if (aDOMEvent.keyCode.keyCode in MozKeyCodeToKeyCodeMap) + _keyCode = MozKeyCodeToKeyCodeMap[aDOMEvent.keyCode]; + else + _keyCode = aDOMEvent.keyCode; var characters = String.fromCharCode(_keyCode).toLowerCase(); - overrideCharacters = modifierFlags & CPShiftKeyMask ? characters.toUpperCase() : characters; - - // 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" && (modifierFlags & CPPlatformActionKeyMask)) + overrideCharacters = (modifierFlags & CPShiftKeyMask || _capsLockActive) ? characters.toUpperCase() : characters; + + // check for caps lock state + if (_keyCode === CPKeyCodes.CAPS_LOCK) + _capsLockActive = YES; + + if (modifierFlags & (CPControlKeyMask | CPCommandKeyMask | CPAlternateKeyMask)) { - _DOMPasteboardElement.select(); - _DOMPasteboardElement.value = ""; - - isNativePasteEvent = YES; + //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 } - // Normally we return now because we let keypress send the actual CPEvent keyDown event, since we don't have - // a complete set of information yet. - - // 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") && (modifierFlags & CPPlatformActionKeyMask)) - isNativeCopyOrCutEvent = YES; + 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 | CPAlternateKeyMask))) + break; - // Also, certain browsers (IE and Safari), have broken keyboard supportwhere they don't send keypresses for certain events. - // So, allow the keypress event to handle the event if we are not a browser with broken (remedial) key support... - else if (!CPFeatureIsCompatible(CPJavascriptRemedialKeySupport)) - return; - - // Or, if this is not one of those special keycodes, and also not a ctrl+event - else if (!KeyCodesWithoutKeyPressEvents[_keyCode] && (_keyCode == CTRL_KEY_CODE || !(modifierFlags & CPControlKeyMask))) - return; - - // If this is in fact our broke state, continue to keypress and send the keydown. - case "keypress": // If the source of this event is our pasteboard element, then simply let it continue - // as normal, so that the paste event can successfully complete. - - if ((aDOMEvent.target || aDOMEvent.srcElement) == _DOMPasteboardElement) - return; - var keyCode = _keyCode, charCode = aDOMEvent.keyCode || aDOMEvent.charCode, isARepeat = (_charCodes[keyCode] != nil); + _lastKey = keyCode; _charCodes[keyCode] = charCode; var characters = overrideCharacters || String.fromCharCode(charCode), charactersIgnoringModifiers = characters.toLowerCase(); // Safari won't send proper capitalization during cmd-key events - if (!overrideCharacters && (modifierFlags & CPCommandKeyMask) && (modifierFlags & CPShiftKeyMask)) + if (!overrideCharacters && (modifierFlags & CPCommandKeyMask) && ((modifierFlags & CPShiftKeyMask) || _capsLockActive)) characters = characters.toUpperCase(); event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags @@ -612,7 +639,6 @@ var supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop]; { _pasteboardKeyDownEvent = event; window.setNativeTimeout(function () { [self _checkPasteboardElement] }, 0); - return; } break; @@ -620,12 +646,20 @@ var supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop]; 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; + var characters = String.fromCharCode(charCode), charactersIgnoringModifiers = characters.toLowerCase(); - if (!(modifierFlags & CPShiftKeyMask)) + if (!(modifierFlags & CPShiftKeyMask) && (modifierFlags & CPCommandKeyMask) && !_capsLockActive) characters = charactersIgnoringModifiers; event = [CPEvent keyEventWithType:CPKeyUp location:location modifierFlags:modifierFlags @@ -633,40 +667,126 @@ var supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop]; characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:NO keyCode:keyCode]; break; } - - if (event) + + if (event && !isNativePasteEvent) { event._DOMEvent = aDOMEvent; - + [CPApp sendEvent:event]; if (isNativeCopyOrCutEvent) { - var pasteboard = [CPPasteboard generalPasteboard], - types = [pasteboard types]; - // If this is a native copy event, then check if the pasteboard has anything in it. - if (types.length) - { - if ([types indexOfObjectIdenticalTo:CPStringPboardType] != CPNotFound) - _DOMPasteboardElement.value = [pasteboard stringForType:CPStringPboardType]; - else - _DOMPasteboardElement.value = [pasteboard _generateStateUID]; - - _DOMPasteboardElement.select(); - - window.setNativeTimeout(function() { [self _clearPasteboardElement]; }, 0); - } - - return; + [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 = aDOMEvent.timeStamp ? aDOMEvent.timeStamp : new Date(), + 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]; +} + +- (void)pasteEvent:(DOMEvent)aDOMEvent +{ + 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 ( + ((aDOMEvent.target || aDOMEvent.srcElement).nodeName.toUpperCase() !== "INPUT" && + (aDOMEvent.target || aDOMEvent.srcElement).nodeName.toUpperCase() !== "TEXTAREA" + ) || aDOMEvent.target === _DOMPasteboardElement + ) && + (modifierFlags & CPPlatformActionKeyMask); +} + +- (void)_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(); + + window.setNativeTimeout(function() { [self _clearPasteboardElement]; }, 0); + } +} + + +- (void)_checkPasteboardElement +{ + var value = _DOMPasteboardElement.value; + + if ([value length]) + { + var pasteboard = [CPPasteboard generalPasteboard]; + + if ([pasteboard _stateUID] != value) + { + [pasteboard declareTypes:[CPStringPboardType] owner:self]; + [pasteboard setString:value forType:CPStringPboardType]; + } + } + + [self _clearPasteboardElement]; + + [CPApp sendEvent:_pasteboardKeyDownEvent]; + + _pasteboardKeyDownEvent = nil; + + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; +} + +- (void)_clearPasteboardElement +{ + _DOMPasteboardElement.value = ""; + _DOMPasteboardElement.blur(); } - (void)scrollEvent:(DOMEvent)aDOMEvent @@ -1077,8 +1197,16 @@ var supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop]; StopDOMEventPropagation = !aFlag; } +- (BOOL)_willPropagateCurrentDOMEvent +{ + return !StopDOMEventPropagation; +} + - (CPWindow)hitTest:(CPPoint)location -{if (self._only) return self._only; +{ + if (self._only) + return self._only; + var levels = _windowLevels, layers = _windowLayers, levelCount = levels.length, @@ -1101,37 +1229,6 @@ var supportsNativeDragAndDrop = [CPPlatform supportsDragAndDrop]; return theWindow; } -- (void)_checkPasteboardElement -{ - var value = _DOMPasteboardElement.value; - - if ([value length]) - { - var pasteboard = [CPPasteboard generalPasteboard]; - - if ([pasteboard _stateUID] != value) - { - [pasteboard declareTypes:[CPStringPboardType] owner:self]; - - [pasteboard setString:value forType:CPStringPboardType]; - } - } - - [self _clearPasteboardElement]; - - [CPApp sendEvent:_pasteboardKeyDownEvent]; - - _pasteboardKeyDownEvent = nil; - - [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; -} - -- (void)_clearPasteboardElement -{ - _DOMPasteboardElement.value = ""; - _DOMPasteboardElement.blur(); -} - /*! 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 diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOMKeys.j b/AppKit/Platform/DOM/CPPlatformWindow+DOMKeys.j new file mode 100644 index 000000000..88d93d84f --- /dev/null +++ b/AppKit/Platform/DOM/CPPlatformWindow+DOMKeys.j @@ -0,0 +1,231 @@ +/* + * CPPlatformWindow+DOMKeys.j + * AppKit + * + * Created by Ross Boucher. + * Copyright 2009, 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 + */ + +// Keycodes taken and modified from Google Closure, available under Apache 2 License + +CPKeyCodes = { + BACKSPACE: 8, + TAB: 9, + NUM_CENTER: 12, + ENTER: 13, + SHIFT: 16, + CTRL: 17, + ALT: 18, + PAUSE: 19, + CAPS_LOCK: 20, + ESC: 27, + SPACE: 32, + PAGE_UP: 33, // also NUM_NORTH_EAST + PAGE_DOWN: 34, // also NUM_SOUTH_EAST + END: 35, // also NUM_SOUTH_WEST + HOME: 36, // also NUM_NORTH_WEST + LEFT: 37, // also NUM_WEST + UP: 38, // also NUM_NORTH + RIGHT: 39, // also NUM_EAST + DOWN: 40, // also NUM_SOUTH + PRINT_SCREEN: 44, + INSERT: 45, // also NUM_INSERT + DELETE: 46, // also NUM_DELETE + ZERO: 48, + ONE: 49, + TWO: 50, + THREE: 51, + FOUR: 52, + FIVE: 53, + SIX: 54, + SEVEN: 55, + EIGHT: 56, + NINE: 57, + QUESTION_MARK: 63, // needs localization + A: 65, + B: 66, + C: 67, + D: 68, + E: 69, + F: 70, + G: 71, + H: 72, + I: 73, + J: 74, + K: 75, + L: 76, + M: 77, + N: 78, + O: 79, + P: 80, + Q: 81, + R: 82, + S: 83, + T: 84, + U: 85, + V: 86, + W: 87, + X: 88, + Y: 89, + Z: 90, + META: 91, + CONTEXT_MENU: 93, + NUM_ZERO: 96, + NUM_ONE: 97, + NUM_TWO: 98, + NUM_THREE: 99, + NUM_FOUR: 100, + NUM_FIVE: 101, + NUM_SIX: 102, + NUM_SEVEN: 103, + NUM_EIGHT: 104, + NUM_NINE: 105, + NUM_MULTIPLY: 106, + NUM_PLUS: 107, + NUM_MINUS: 109, + NUM_PERIOD: 110, + NUM_DIVISION: 111, + F1: 112, + F2: 113, + F3: 114, + F4: 115, + F5: 116, + F6: 117, + F7: 118, + F8: 119, + F9: 120, + F10: 121, + F11: 122, + F12: 123, + NUMLOCK: 144, + SEMICOLON: 186, // needs localization + DASH: 189, // needs localization + EQUALS: 187, // needs localization + COMMA: 188, // needs localization + PERIOD: 190, // needs localization + SLASH: 191, // needs localization + APOSTROPHE: 192, // needs localization + SINGLE_QUOTE: 222, // needs localization + OPEN_SQUARE_BRACKET: 219, // needs localization + BACKSLASH: 220, // needs localization + CLOSE_SQUARE_BRACKET: 221, // needs localization + WIN_KEY: 224, + MAC_FF_META: 224, // Firefox (Gecko) fires this for the meta key instead of 91 + WIN_IME: 229 +}; + + +/*! + * Returns true if the key fires a keypress event in the current browser. + * + * Accoridng to MSDN [1] IE only fires keypress events for the following keys: + * - Letters: A - Z (uppercase and lowercase) + * - Numerals: 0 - 9 + * - Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~ + * - System: ESC, SPACEBAR, ENTER + * + * That's not entirely correct though, for instance there's no distinction + * between upper and lower case letters. + * + * [1] http://msdn2.microsoft.com/en-us/library/ms536939(VS.85).aspx) + * + * Safari is similar to IE, but does not fire keypress for ESC. + * + * Additionally, IE6 does not fire keydown or keypress events for letters when + * the control or alt keys are held down and the shift key is not. IE7 does + * fire keydown in these cases, though, but not keypress. + * + * @param keyCode A key code. + * @param opt_heldKeyCode Key code of a currently-held key. + * @param opt_shiftKey Whether the shift key is held down. + * @param opt_ctrlKey Whether the control key is held down. + * @param opt_altKey Whether the alt key is held down. + * @return Returns YES if it's a key that fires a keypress event. + */ +CPKeyCodes.firesKeyPressEvent = function(keyCode, opt_heldKeyCode, opt_shiftKey, opt_ctrlKey, opt_altKey) +{ + if (!CPFeatureIsCompatible(CPJavascriptRemedialKeySupport)) + return true; + + if (CPBrowserIsOperatingSystem(CPMacOperatingSystem) && opt_altKey) + return CPKeyCodes.isCharacterKey(keyCode); + + // Alt but not AltGr which is represented as Alt+Ctrl. + if (opt_altKey && !opt_ctrlKey) + return false; + + // Saves Ctrl or Alt + key for IE7, which won't fire keypress. + if (CPBrowserIsEngine(CPInternetExplorerBrowserEngine) && !opt_shiftKey && (opt_ctrlKey || opt_altKey)) + return false; + + // When Ctrl+ is held in IE, it only fires a keypress once, but it + // continues to fire keydown events as the event repeats. + if (CPBrowserIsEngine(CPInternetExplorerBrowserEngine) && opt_ctrlKey && opt_heldKeyCode == keyCode) + return false; + + switch (keyCode) + { + case CPKeyCodes.ENTER: return true; + case CPKeyCodes.ESC: return !CPBrowserIsEngine(CPWebKitBrowserEngine); + } + + return CPKeyCodes.isCharacterKey(keyCode); +}; + + +/*! + * Test for whether or not a given keyCode represents a character key. + * + * @param keyCode A key code. + * @return Returns YES if the keyCode is a character key. + */ +CPKeyCodes.isCharacterKey = function(keyCode) +{ + if (keyCode >= CPKeyCodes.ZERO && keyCode <= CPKeyCodes.NINE) + return true; + + if (keyCode >= CPKeyCodes.NUM_ZERO && keyCode <= CPKeyCodes.NUM_MULTIPLY) + return true; + + if (keyCode >= CPKeyCodes.A && keyCode <= CPKeyCodes.Z) + return true; + + switch (keyCode) + { + case CPKeyCodes.SPACE: + case CPKeyCodes.QUESTION_MARK: + case CPKeyCodes.NUM_PLUS: + case CPKeyCodes.NUM_MINUS: + case CPKeyCodes.NUM_PERIOD: + case CPKeyCodes.NUM_DIVISION: + case CPKeyCodes.SEMICOLON: + case CPKeyCodes.DASH: + case CPKeyCodes.EQUALS: + case CPKeyCodes.COMMA: + case CPKeyCodes.PERIOD: + case CPKeyCodes.SLASH: + case CPKeyCodes.APOSTROPHE: + case CPKeyCodes.SINGLE_QUOTE: + case CPKeyCodes.OPEN_SQUARE_BRACKET: + case CPKeyCodes.BACKSLASH: + case CPKeyCodes.CLOSE_SQUARE_BRACKET: + return true; + + default: + return false; + } +}