Merge pull request #3122 from daboe01/fixed-keycode-management-in-cptextview

fixed: Keyboard-event handling is based on the deprecated charCode
This commit is contained in:
daboe01
2026-01-28 20:57:07 +01:00
committed by GitHub
10 changed files with 397 additions and 443 deletions
+31 -3
View File
@@ -70,6 +70,7 @@ var _CPEventPeriodicEventPeriod = 0,
BOOL _isARepeat;
unsigned _keyCode;
DOMEvent _DOMEvent;
BOOL _isActionKey;
int _data1;
int _data2;
short _subtype;
@@ -110,17 +111,28 @@ var _CPEventPeriodicEventPeriod = 0,
@param unmodCharacters the string of keys pressed without the presence of any modifiers other than Shift
@param repeatKey \c YES if this is caused by the system repeat as opposed to the user pressing the key again
@param code a number associated with the keyboard key of this event
@param isAnActionKey a BOOL indicating whether this key is an action key (e.g. a function key)
@throws CPInternalInconsistencyException if \c anEventType is not a CPKeyDown,
CPKeyUp or CPFlagsChanged
@return the keyboard event
*/
+ (CPEvent)keyEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned int)modifierFlags
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)repeatKey keyCode:(unsigned short)code
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)repeatKey keyCode:(unsigned short)code isActionKey:(BOOL)isAnActionKey
{
return [[self alloc] _initKeyEventWithType:anEventType location:aPoint modifierFlags:modifierFlags
timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext
characters:characters charactersIgnoringModifiers:unmodCharacters isARepeat:repeatKey keyCode:code];
characters:characters charactersIgnoringModifiers:unmodCharacters isARepeat:repeatKey keyCode:code isActionKey:isAnActionKey];
}
// for backwards compatibility only
+ (CPEvent)keyEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned int)modifierFlags
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)repeatKey
{
return [[self alloc] _initKeyEventWithType:anEventType location:aPoint modifierFlags:modifierFlags
timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext
characters:characters charactersIgnoringModifiers:unmodCharacters isARepeat:repeatKey keyCode:code isActionKey:NO];
}
/*!
@@ -252,7 +264,7 @@ var _CPEventPeriodicEventPeriod = 0,
/* @ignore */
- (id)_initKeyEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned int)modifierFlags
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)isARepeat keyCode:(unsigned short)code
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)isARepeat keyCode:(unsigned short)code isActionKey:(BOOL)isAnActionKey
{
if (self = [self _initWithType:anEventType])
{
@@ -264,6 +276,7 @@ var _CPEventPeriodicEventPeriod = 0,
_charactersIgnoringModifiers = unmodCharacters;
_isARepeat = isARepeat;
_keyCode = code;
_isActionKey = isAnActionKey;
_windowNumber = aWindowNumber;
}
@@ -571,6 +584,21 @@ var _CPEventPeriodicEventPeriod = 0,
return !firstResponderIsText;
}
- (BOOL)_isActionOrCommandEvent
{
// This method is now platform-agnostic. It checks for abstract properties
// of the event, including the _isActionKey flag that was set at creation time.
return (
// Is it a command shortcut?
(_modifierFlags & (CPCommandKeyMask | CPControlKeyMask | CPAlternateKeyMask)) ||
// Is it a key that doesn't produce a character?
([_characters length] === 0) ||
// Was it identified as an action key by the platform-specific layer?
_isActionKey
);
}
/*!
Return YES if this event is a part of processing a browser controlled cut or paste event
where the browser will go ahead and do the work of cutting or pasting within the input
+2 -1
View File
@@ -332,7 +332,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
characters:nil
charactersIgnoringModifiers:nil
isARepeat:NO
keyCode:nil];
keyCode:nil
isActionKey:NO];
[CPTextFieldInputOwner keyUp:cappEvent];
+191 -271
View File
@@ -449,19 +449,25 @@ var kDelegateRespondsTo_textShouldBeginEditing
{
[super copy:sender];
if (![self isRichText])
return;
var selectedRange = [self selectedRange],
pasteboard = [CPPasteboard generalPasteboard],
stringForPasting = [[self textStorage] attributedSubstringFromRange:CPMakeRangeCopy(selectedRange)],
richData = [_CPRTFProducer produceRTF:stringForPasting documentAttributes:@{}];
if ([self isRichText])
{
[pasteboard declareTypes:[CPStringPboardType, CPRTFPboardType, _CPSmartPboardType, _CPASPboardType] owner:nil];
[pasteboard setString:[stringForPasting._string stringByReplacingOccurrencesOfString:_CPAttachmentCharacterAsString withString:''] forType:CPStringPboardType];
[pasteboard setString:richData forType:CPRTFPboardType];
[pasteboard setString:_previousSelectionGranularity + '' forType:_CPSmartPboardType];
[pasteboard setString:[[CPKeyedArchiver archivedDataWithRootObject:stringForPasting] rawString] forType:_CPASPboardType];
}
else
{
[pasteboard declareTypes:[CPStringPboardType, _CPSmartPboardType] owner:nil];
[pasteboard setString:stringForPasting._string forType:CPStringPboardType];
[pasteboard setString:_previousSelectionGranularity + '' forType:_CPSmartPboardType];
}
}
- (void)_pasteString:(id)stringForPasting
@@ -510,8 +516,8 @@ var kDelegateRespondsTo_textShouldBeginEditing
- (void)paste:(id)sender
{
if (![sender isKindOfClass:_CPNativeInputManager] && [[CPApp currentEvent] type] != CPAppKitDefined)
return
if ([[CPApp currentEvent] type] != CPAppKitDefined)
return;
[self _pasteString:[self _stringForPasting]];
}
@@ -1017,35 +1023,6 @@ Sets the selection to a range of characters in response to user action.
}
#endif
// interface to the _CPNativeInputManager
- (void)_activateNativeInputElement:(DOMElement)aNativeField
{
var attributes = [[self typingAttributes] copy];
// make it invisible
[attributes setObject:[CPColor colorWithRed:1 green:1 blue:1 alpha:0] forKey:CPForegroundColorAttributeName];
// FIXME: this hack to provide the visual space for the inputmanager should at least bypass the undomanager
var placeholderString = [[CPAttributedString alloc] initWithString:aNativeField.innerHTML attributes:attributes];
[self insertText:placeholderString];
var caretOrigin = [_layoutManager boundingRectForGlyphRange:CPMakeRange(MAX(0, _selectionRange.location - 1), 1) inTextContainer:_textContainer].origin;
caretOrigin.y += [_layoutManager _characterOffsetAtLocation:MAX(0, _selectionRange.location - 1)];
caretOrigin.x += 2; // two pixel offset to the LHS character
var cumulativeOffset = [self _cumulativeOffset];
#if PLATFORM(DOM)
aNativeField.style.left = (caretOrigin.x + cumulativeOffset.x) + "px";
aNativeField.style.top = (caretOrigin.y + cumulativeOffset.y) + "px";
aNativeField.style.font = [[_typingAttributes objectForKey:CPFontAttributeName] cssString];
aNativeField.style.color = [[_typingAttributes objectForKey:CPForegroundColorAttributeName] cssString];
#endif
[_caret setVisibility:NO]; // hide our caret because now the system caret takes over
}
- (CPArray)selectedRanges
{
return [_selectionRange];
@@ -1056,11 +1033,18 @@ Sets the selection to a range of characters in response to user action.
- (void)keyDown:(CPEvent)event
{
[[_window platformWindow] _propagateCurrentDOMEvent:YES];
[[_window platformWindow] _propagateCurrentDOMEvent:YES]; // for the _CPNativeInputManager (necessary at least on FF and chrome)
if (![_CPNativeInputManager isNativeInputFieldActive] && ![_CPNativeInputManager isDeadKey:event])
if ([event _isActionOrCommandEvent])
{
// This is a navigation key, action key, or command shortcut.
// Let the Cappuccino framework's key binding system handle it.
[self interpretKeyEvents:[event]];
}
// This is a normal printable character ('a', '1', '$', 'é').
// We do nothing, preventing the double-insertion bug. The _CPNativeInputManager
// will capture it from the hidden input field and insert it correctly.
[_caret setPermanentlyVisible:YES];
}
@@ -1121,6 +1105,9 @@ Sets the selection to a range of characters in response to user action.
if (![self isSelectable])
return;
// this is for the ipad-keyboard
[_CPNativeInputManager focusForClipboardOfTextView:self];
[_CPNativeInputManager cancelCurrentInputSessionIfNeeded];
[_caret setVisibility:NO];
@@ -1728,6 +1715,9 @@ Sets the selection to a range of characters in response to user action.
- (void)cut:(id)sender
{
if ([[CPApp currentEvent] type] != CPAppKitDefined)
return;
var selectedRange = [self selectedRange];
if (selectedRange.length < 1)
@@ -2631,59 +2621,28 @@ var CPTextViewAllowsUndoKey = @"CPTextViewAllowsUndoKey",
var _CPNativeInputField,
_CPNativeInputFieldKeyDownCalled,
_CPNativeInputFieldKeyUpCalled,
_CPNativeInputFieldKeyPressedCalled,
_CPNativeInputFieldActive;
_isComposing = NO; // Flag to track if an IME/dead key session is active.
var _CPCopyPlaceholder = '-';
@implementation _CPNativeInputManager : CPObject
+ (BOOL)isNativeInputFieldActive
{
return _CPNativeInputFieldActive;
}
+ (void)isDeadKey:(CPEvent)event
{
#if PLATFORM(DOM)
return event._DOMEvent && (event._DOMEvent.key == 'Dead' || event._DOMEvent.key == 'Process');
return event._DOMEvent && (event._DOMEvent.key === 'Dead' || event._DOMEvent.key === 'Process');
#endif
return NO;
}
+ (void)cancelCurrentNativeInputSession
{
#if PLATFORM(DOM)
_CPNativeInputField.innerHTML = '';
#endif
[self _endInputSessionWithString:_CPNativeInputField.innerHTML];
}
+ (void)cancelCurrentInputSessionIfNeeded
{
if (!_CPNativeInputFieldActive)
return;
[self cancelCurrentNativeInputSession];
}
+ (void)_endInputSessionWithString:(CPString)aStr
{
_CPNativeInputFieldActive = NO;
var currentFirstResponder = [[CPApp keyWindow] firstResponder],
placeholderRange = CPMakeRange([currentFirstResponder selectedRange].location - 1, 1);
[currentFirstResponder setSelectedRange:placeholderRange];
[currentFirstResponder insertText:aStr];
_CPNativeInputField.innerHTML = '';
[self hideInputElement];
[currentFirstResponder updateInsertionPointStateAndRestartTimer:YES];
#if PLATFORM(DOM)
if (_CPNativeInputField) {
_CPNativeInputField.innerHTML = '';
}
_isComposing = NO;
#endif
}
+ (void)initialize
@@ -2691,236 +2650,197 @@ var _CPCopyPlaceholder = '-';
#if PLATFORM(DOM)
_CPNativeInputField = document.createElement("div");
_CPNativeInputField.contentEditable = YES;
_CPNativeInputField.style.width = "64px";
_CPNativeInputField.style.zIndex = 10000;
// Style the input field to be invisible but focusable
_CPNativeInputField.style.position = "absolute";
_CPNativeInputField.style.visibility = "visible";
_CPNativeInputField.style.padding = "0px";
_CPNativeInputField.style.margin = "0px";
_CPNativeInputField.style.top = "-1000px";
_CPNativeInputField.style.left = "-1000px";
_CPNativeInputField.style.width = "1px";
_CPNativeInputField.style.height = "1px";
_CPNativeInputField.style.opacity = "0";
_CPNativeInputField.style.overflow = "hidden";
_CPNativeInputField.style.whiteSpace = "pre";
_CPNativeInputField.style.outline = "0px solid transparent";
_CPNativeInputField.style.zIndex = -1; // Put it behind everything
document.body.appendChild(_CPNativeInputField);
_CPNativeInputField.addEventListener("keyup", function(e)
// Central function to handle inserting text into the CPTextView
var handleInput = function(textToInsert)
{
_CPNativeInputFieldKeyUpCalled = YES;
// filter out the shift-up, cursor keys and friends used to access the deadkeys
// fixme: e.which is depreciated(?) -> find a better way to identify the modifier-keyups
if (e.which < 27 || e.which == 91 || e.which == 93) // include apple command keys
{
if (e.which == 13)
_CPNativeInputField.innerHTML = '';
if (_CPNativeInputField.innerHTML.length == 0 || _CPNativeInputField.innerHTML.length > 2) // backspace
[self cancelCurrentInputSessionIfNeeded];
return false; // prevent the default behaviour
}
var currentFirstResponder = [[CPApp keyWindow] firstResponder];
if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)])
return false; // prevent the default behaviour
// chrome-trigger: keypressed is omitted for deadkeys
if (!_CPNativeInputFieldActive && _CPNativeInputFieldKeyPressedCalled == NO && _CPNativeInputField.innerHTML.length && _CPNativeInputField.innerHTML != _CPCopyPlaceholder && _CPNativeInputField.innerHTML.length < 3)
{
_CPNativeInputFieldActive = YES;
[currentFirstResponder _activateNativeInputElement:_CPNativeInputField];
}
else
{
if (_CPNativeInputFieldActive)
[self _endInputSessionWithString:_CPNativeInputField.innerHTML];
// prevent the copy placeholder beeing removed by cursor keys
if (_CPNativeInputFieldKeyPressedCalled)
_CPNativeInputField.innerHTML = '';
}
_CPNativeInputFieldKeyDownCalled = NO;
return false; // prevent the default behaviour
}, true);
_CPNativeInputField.addEventListener("keydown", function(e)
{
// this protects from heavy typing and the shift key
if (_CPNativeInputFieldKeyDownCalled)
return true;
_CPNativeInputFieldKeyDownCalled = YES;
_CPNativeInputFieldKeyUpCalled = NO;
_CPNativeInputFieldKeyPressedCalled = NO;
var currentFirstResponder = [[CPApp keyWindow] firstResponder];
// webkit-browsers: cursor keys do not emit keypressed and would otherwise activate deadkey mode
if (!CPBrowserIsEngine(CPGeckoBrowserEngine) && e.which >= 37 && e.which <= 40)
_CPNativeInputFieldKeyPressedCalled = YES;
if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)])
if (!textToInsert)
return;
// FF-trigger: here the best way to detect a dead key is the missing keyup event
if (CPBrowserIsEngine(CPGeckoBrowserEngine))
var currentFirstResponder = [[CPApp keyWindow] firstResponder];
if (currentFirstResponder && [currentFirstResponder respondsToSelector:@selector(insertText:)])
// setTimeout to prevent flickering
setTimeout(function(){
_CPNativeInputFieldKeyDownCalled = NO;
if (!_CPNativeInputFieldActive && _CPNativeInputFieldKeyUpCalled == NO && _CPNativeInputField.innerHTML.length && _CPNativeInputField.innerHTML != _CPCopyPlaceholder && _CPNativeInputField.innerHTML.length < 3 && !e.repeat)
{
_CPNativeInputFieldActive = YES;
[currentFirstResponder _activateNativeInputElement:_CPNativeInputField];
}
else if (!_CPNativeInputFieldActive)
[self hideInputElement];
}, 200);
return false;
}, true); // capture mode
_CPNativeInputField.addEventListener("keypress", function(e)
{
_CPNativeInputFieldKeyUpCalled = YES;
_CPNativeInputFieldKeyPressedCalled = YES;
return false;
}, true); // capture mode
_CPNativeInputField.onpaste = function(e)
{
var nativeClipboard = (e.originalEvent || e).clipboardData,
richtext,
pasteboard = [CPPasteboard generalPasteboard],
currentFirstResponder = [[CPApp keyWindow] firstResponder],
isPlain = NO;
if ([currentFirstResponder respondsToSelector:@selector(isRichText)] && ![currentFirstResponder isRichText])
isPlain = YES;
// this is the rich chrome / FF codepath (where we can use RTF directly)
if ((richtext = nativeClipboard.getData('text/rtf')) && !(!!(e.originalEvent || e).shiftKey) && !isPlain)
{
e.preventDefault();
// setTimeout to prevent flickering in FF
setTimeout(function(){
[currentFirstResponder insertText:[[_CPRTFParser new] parseRTF:richtext]]
[currentFirstResponder insertText:textToInsert]
}, 20);
return false;
}
// plain is the same in all browsers...
var data = e.clipboardData.getData('text/plain'),
cappString = [pasteboard stringForType:CPStringPboardType];
if (cappString != data)
{
[pasteboard declareTypes:[CPStringPboardType] owner:nil];
[pasteboard setString:data forType:CPStringPboardType];
}
setTimeout(function(){ // prevent dom-flickering (only needed for FF)
[currentFirstResponder paste:self];
}, 20);
return false;
// CRUCIAL: Clear the field immediately after grabbing its content.
_CPNativeInputField.innerHTML = '';
};
if (CPBrowserIsEngine(CPGeckoBrowserEngine))
{
_CPNativeInputField.oncopy = function(e)
{
var pasteboard = [CPPasteboard generalPasteboard],
string,
currentFirstResponder = [[CPApp keyWindow] firstResponder];
// Intercept problematic keys before the browser acts.
_CPNativeInputField.addEventListener('keydown', function(e) {
[currentFirstResponder copy:self];
var stringForPasting = [pasteboard stringForType:CPStringPboardType];
e.clipboardData.setData('text/plain', stringForPasting);
return false;
};
_CPNativeInputField.oncut = function(e)
{
var pasteboard = [CPPasteboard generalPasteboard],
string,
currentFirstResponder = [[CPApp keyWindow] firstResponder];
// prevent dom-flickering
setTimeout(function(){
[currentFirstResponder cut:self];
}, 20);
// this is necessary because cut will only execute in the future
[currentFirstResponder copy:self];
var stringForPasting = [pasteboard stringForType:CPStringPboardType];
e.clipboardData.setData('text/plain', stringForPasting);
return false;
if (e.key === 'Enter' || (e.key === 'Backspace' && _CPNativeInputField.innerHTML === '')) {
// Prevent browser default action:
// - 'Enter': Prevents inserting <div><br></div>.
// - 'Backspace' on empty: Prevents inserting junk characters on iPadOS.
e.preventDefault();
}
}
});
// This listener handles all other character input.
_CPNativeInputField.addEventListener('input', function(e)
{
// If we are in a composition (e.g., IME), do nothing yet.
if (_isComposing)
return;
// Safety net: ignore deletion events, as they are handled by keydown.
if (e.inputType && e.inputType.startsWith('delete'))
{
_CPNativeInputField.innerHTML = '';
return;
}
// Robustness: Use 'textContent' instead of 'innerHTML' to strip any
// unexpected HTML tags the browser might have inserted.
var textToInsert = e.target.textContent;
handleInput(textToInsert);
});
// Fires when a composition session starts (e.g., user presses a dead key or starts an IME).
_CPNativeInputField.addEventListener('compositionstart', function(e) {
_isComposing = YES;
});
// Fires when the composition is finished.
_CPNativeInputField.addEventListener('compositionend', function(e) {
// The composition is over. `e.data` has the final string (e.g., "é").
handleInput(e.data);
_isComposing = NO;
});
// PASTE handler
_CPNativeInputField.onpaste = function(e)
{
e.preventDefault();
var nativeClipboard = (e.originalEvent || e).clipboardData;
var currentFirstResponder = [[CPApp keyWindow] firstResponder];
// Can we accept richtext? Then this is our preference (fixme: shift key to force plain text paste)
if ([currentFirstResponder isRichText])
{
var richtext = nativeClipboard.getData('text/rtf');
// prefer RTF form the outside of cappuccino
if (richtext)
richtext = [[_CPRTFParser new] parseRTF:richtext];
else
{
var pasteboard = [CPPasteboard generalPasteboard];
// If no RTF is available, try to get the internal represatation of richtext from the pasteboard
var richData = [pasteboard stringForType:_CPASPboardType];
if (richData)
richtext = [CPKeyedUnarchiver unarchiveObjectWithData:[CPData dataWithRawString:richData]];
}
if (richtext)
{
[currentFirstResponder _pasteString:richtext];
return;
}
// If no richtext is available, fall back to plain text
}
var nativeString = nativeClipboard.getData('text/plain');
[currentFirstResponder _pasteString:nativeString || [pasteboard stringForType:CPStringPboardType] || ''];
};
// COPY handler
_CPNativeInputField.oncopy = function(e)
{
e.preventDefault();
var pasteboard = [CPPasteboard generalPasteboard];
var nativeClipboard = (e.originalEvent || e).clipboardData;
// First, copy the data to populate the CP clipboard
[[[CPApp keyWindow] firstResponder] copy:self];
// Now, copy the data over to the native clipboard
var stringForPasting = [pasteboard stringForType:CPStringPboardType] || '';
nativeClipboard.setData('text/plain', stringForPasting);
var rtfForPasting = [pasteboard stringForType:CPRTFPboardType];
if (rtfForPasting)
nativeClipboard.setData('text/rtf', rtfForPasting);
};
// CUT handler
_CPNativeInputField.oncut = function(e)
{
e.preventDefault();
var pasteboard = [CPPasteboard generalPasteboard];
var nativeClipboard = (e.originalEvent || e).clipboardData;
var currentFirstResponder = [[CPApp keyWindow] firstResponder];
// First, copy the data to populate the CP clipboard
[currentFirstResponder copy:self];
// Now, copy the data to the native clipboard
var stringForPasting = [pasteboard stringForType:CPStringPboardType] || '';
nativeClipboard.setData('text/plain', stringForPasting);
var rtfForPasting = [pasteboard stringForType:CPRTFPboardType];
if (rtfForPasting)
nativeClipboard.setData('text/rtf', rtfForPasting);
// Then, perform the delete part of the cut operation in the text view
[currentFirstResponder deleteBackward:self];
};
#endif
}
+ (void)focusForTextView:(CPTextView)currentFirstResponder
{
if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)])
return;
[self hideInputElement];
#if PLATFORM(DOM)
_CPNativeInputField.focus();
#endif
if (_CPNativeInputField && document.activeElement !== _CPNativeInputField)
_CPNativeInputField.focus();
#endif
}
+ (void)focusForClipboardOfTextView:(CPTextView)textview
{
#if PLATFORM(DOM)
if (!_CPNativeInputFieldActive && _CPNativeInputField.innerHTML.length == 0)
_CPNativeInputField.innerHTML = _CPCopyPlaceholder; // make sure we have a selection to allow the native pasteboard work in safari
var selectedRange = [textview selectedRange];
if (selectedRange.length > 0) {
// Put the selected text into the hidden div so the browser can natively copy it.
var textToCopy = [[[textview textStorage] string] substringWithRange:selectedRange];
_CPNativeInputField.innerHTML = textToCopy;
} else {
// For paste, we just need the field to be focusable.
_CPNativeInputField.innerHTML = _CPCopyPlaceholder;
}
[self focusForTextView:textview];
// select all in the contenteditable div (http://stackoverflow.com/questions/12243898/how-to-select-all-text-in-contenteditable-div)
if (document.body.createTextRange)
{
var range = document.body.createTextRange();
range.moveToElementText(_CPNativeInputField);
range.select();
}
else if (window.getSelection)
{
var selection = window.getSelection(),
range = document.createRange();
// Select the content of the hidden div so copy/cut works.
if (window.getSelection && document.createRange) {
var selection = window.getSelection();
var range = document.createRange();
range.selectNodeContents(_CPNativeInputField);
selection.removeAllRanges();
selection.addRange(range);
}
#endif
}
+ (void)hideInputElement
{
#if PLATFORM(DOM)
_CPNativeInputField.style.top = "-10000px";
_CPNativeInputField.style.left = "-10000px";
#endif
}
@end
+1 -1
View File
@@ -305,7 +305,7 @@ var hasEditableTarget = function(aDOMEvent)
location = [[CPApp currentEvent] locationInWindow],
anEvent = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags
timestamp:timestamp windowNumber:windowNumber context:nil
characters:characters charactersIgnoringModifiers:characters isARepeat:NO keyCode:keyCode];
characters:characters charactersIgnoringModifiers:characters isARepeat:NO keyCode:keyCode isActionKey:YES];
anEvent._data1 = @{ "simulated": YES };
anEvent._DOMEvent = aDOMEvent;
+121 -116
View File
@@ -192,6 +192,33 @@ KeyCodesToUnicodeMap[CPKeyCodes.OPEN_SQUARE_BRACKET] = "[";
KeyCodesToUnicodeMap[CPKeyCodes.BACKSLASH] = "\\";
KeyCodesToUnicodeMap[CPKeyCodes.CLOSE_SQUARE_BRACKET] = "]";
var KeyNameToUnicodeMap = {};
KeyNameToUnicodeMap["Backspace"] = CPDeleteCharacter;
KeyNameToUnicodeMap["Delete"] = CPDeleteFunctionKey;
KeyNameToUnicodeMap["Tab"] = CPTabCharacter;
KeyNameToUnicodeMap["Enter"] = CPCarriageReturnCharacter;
KeyNameToUnicodeMap["Escape"] = CPEscapeFunctionKey;
KeyNameToUnicodeMap["PageUp"] = CPPageUpFunctionKey;
KeyNameToUnicodeMap["PageDown"] = CPPageDownFunctionKey;
KeyNameToUnicodeMap["ArrowLeft"] = CPLeftArrowFunctionKey;
KeyNameToUnicodeMap["ArrowUp"] = CPUpArrowFunctionKey;
KeyNameToUnicodeMap["ArrowRight"] = CPRightArrowFunctionKey;
KeyNameToUnicodeMap["ArrowDown"] = CPDownArrowFunctionKey;
KeyNameToUnicodeMap["Home"] = CPHomeFunctionKey;
KeyNameToUnicodeMap["End"] = CPEndFunctionKey;
// Add safeguards for punctuation
KeyNameToUnicodeMap[";"] = ";";
KeyNameToUnicodeMap["-"] = "-";
KeyNameToUnicodeMap["="] = "=";
KeyNameToUnicodeMap[","] = ",";
KeyNameToUnicodeMap["."] = ".";
KeyNameToUnicodeMap["/"] = "/";
KeyNameToUnicodeMap["`"] = "`";
KeyNameToUnicodeMap["'"] = "'";
KeyNameToUnicodeMap["["] = "[";
KeyNameToUnicodeMap["\\"] = "\\";
KeyNameToUnicodeMap["]"] = "]";
var ModifierKeyCodes = [
CPKeyCodes.META,
CPKeyCodes.WEBKIT_RIGHT_META,
@@ -438,7 +465,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio
theDocument.addEventListener("keyup", keyEventCallback, NO);
theDocument.addEventListener("keydown", keyEventCallback, NO);
theDocument.addEventListener("keypress", keyEventCallback, NO);
// "keypress" listener removed as it's deprecated and no longer used in the new logic.
theDocument.addEventListener("touchstart", touchEventCallback, {passive: false});
theDocument.addEventListener("touchend", touchEventCallback, {passive: false});
@@ -470,7 +497,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio
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);
@@ -503,7 +529,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio
theDocument.attachEvent("onkeyup", keyEventCallback);
theDocument.attachEvent("onkeydown", keyEventCallback);
theDocument.attachEvent("onkeypress", keyEventCallback);
// "onkeypress" listener removed.
_DOMWindow.attachEvent("onresize", resizeEventCallback);
@@ -533,7 +559,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio
theDocument.detachEvent("onkeyup", keyEventCallback);
theDocument.detachEvent("onkeydown", keyEventCallback);
theDocument.detachEvent("onkeypress", keyEventCallback);
_DOMWindow.detachEvent("onresize", resizeEventCallback);
@@ -709,157 +734,138 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio
(_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.
// the browser. The logic here allows browser shortcuts (Cmd/Ctrl keys)
// and function keys (F1-F12) to pass through, unless explicitly blacklisted.
StopDOMEventPropagation = YES;
var keyCodeForPropagationCheck = aDOMEvent.keyCode || 0;
var charForPropagationCheck = String.fromCharCode(keyCodeForPropagationCheck).toLowerCase();
// Make sure it is not in the blacklists.
if (!(CharacterKeysToPrevent[String.fromCharCode(aDOMEvent.keyCode || aDOMEvent.charCode).toLowerCase()] || KeyCodesToPrevent[aDOMEvent.keyCode]))
if (!(CharacterKeysToPrevent[charForPropagationCheck] || KeyCodesToPrevent[keyCodeForPropagationCheck]))
{
// 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])
if ((modifierFlags & (CPControlKeyMask | CPCommandKeyMask)) || KeyCodesToAllow[keyCodeForPropagationCheck])
StopDOMEventPropagation = NO;
}
var overrideCharacters = nil,
var characters = @"",
charactersIgnoringModifiers = @"";
var keyCode = aDOMEvent.keyCode;
if (keyCode in MozKeyCodeToKeyCodeMap)
keyCode = MozKeyCodeToKeyCodeMap[keyCode];
var isActionKey;
var key = aDOMEvent.key;
if (key) {
isActionKey =
key === 'Enter' ||
key === 'Backspace' ||
key === 'Tab' ||
key === 'Escape' ||
key === 'Delete' ||
key.startsWith('Arrow') ||
key === 'Home' ||
key === 'End' ||
key === 'PageUp' ||
key === 'PageDown';
}
else
{
isActionKey =
(keyCode === 13) || (keyCode === 8) || (keyCode === 9) ||
(keyCode === 27) || (keyCode === 46) || (keyCode >= 37 && keyCode <= 40);
}
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];
// The problem with keyCode is that this property refers to keys on the keyboard and not to characters
// This is why String.fromCharCode does not always work in more recent versions of Firefox
// E.g. pressing a '#' on a German keyboard gives you a charCode of 163, which refers to '£' and not '#'
// The property key works fine, though. From there we can get the actual character more robustly.
// Therefore we prefer key over keyCode whenever possible
if (!characters)
characters = (aDOMEvent.key && aDOMEvent.key.length == 1) ? aDOMEvent.key.toLowerCase() : String.fromCharCode(_keyCode).toLowerCase();
overrideCharacters = (modifierFlags & CPShiftKeyMask || _capsLockActive) ? characters.toUpperCase() : characters;
// check for caps lock state
if (_keyCode === CPKeyCodes.CAPS_LOCK)
if ([ModifierKeyCodes containsObject:keyCode])
{
_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];
characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:keyCode isActionKey:YES];
break;
}
break;
}
else if (modifierFlags & (CPControlKeyMask | CPCommandKeyMask))
var isARepeat = !!aDOMEvent.repeat || (_charCodes[keyCode] != nil);
_charCodes[keyCode] = YES;
if (aDOMEvent.key)
{
//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, aDOMEvent.key, _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;
if (aDOMEvent.key.length === 1)
{
characters = aDOMEvent.key;
}
// Correctly handle dead keys to prevent inserting "Dead"
else if (aDOMEvent.key === "Dead" || aDOMEvent.key === "Process") {
characters = @"";
}
// For other named keys, map them or fall back to an empty string.
else
{
characters = KeyNameToUnicodeMap[aDOMEvent.key] || @"";
}
}
else
{
//this branch is taken by "remedial" key events
// In this state we continue to keypress and send the CPEvent
characters = KeyCodesToUnicodeMap[keyCode];
if (!characters)
{
characters = String.fromCharCode(keyCode);
if (modifierFlags & CPShiftKeyMask || _capsLockActive)
characters = characters.toUpperCase();
else
characters = characters.toLowerCase();
}
}
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();
charactersIgnoringModifiers = characters.toLowerCase();
event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags
timestamp:timestamp windowNumber:windowNumber context:nil
characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:charCode];
characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:keyCode isActionKey:isActionKey];
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;
_capsLockActive = !_capsLockActive;
if (_capsLockActive)
modifierFlags |= CPAlphaShiftKeyMask;
else
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];
characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:keyCode isActionKey:YES];
break;
}
var characters = KeyCodesToUnicodeMap[charCode] || String.fromCharCode(charCode);
if (aDOMEvent.key)
{
if (aDOMEvent.key.length === 1) {
characters = aDOMEvent.key;
}
// Ensure keyup events also don't produce "Dead"
else if (aDOMEvent.key === "Dead" || aDOMEvent.key === "Process") {
characters = @"";
}
else
{
characters = KeyNameToUnicodeMap[aDOMEvent.key] || @"";
}
}
else
characters = KeyCodesToUnicodeMap[keyCode] || String.fromCharCode(keyCode);
charactersIgnoringModifiers = characters.toLowerCase();
if (!(modifierFlags & CPShiftKeyMask) && (modifierFlags & CPCommandKeyMask) && !_capsLockActive)
@@ -867,7 +873,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio
event = [CPEvent keyEventWithType:CPKeyUp location:location modifierFlags:modifierFlags
timestamp: timestamp windowNumber:windowNumber context:nil
characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:NO keyCode:keyCode];
characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:NO keyCode:keyCode isActionKey:isActionKey];
break;
}
@@ -880,12 +886,11 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio
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;
+29 -29
View File
@@ -132,38 +132,36 @@ CPKeyCodes = {
/*!
* Returns true if the key fires a keypress event in the current browser.
* The keypress event is deprecated, but this function helps manage legacy
* event handling by predicting its behavior.
*
* 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.
* @param {number} keyCode A key code.
* @param {string} key The `key` property from the keyboard event.
* @param {number} opt_heldKeyCode Key code of a currently-held key.
* @param {boolean} opt_shiftKey Whether the shift key is held down.
* @param {boolean} opt_ctrlKey Whether the control key is held down.
* @param {boolean} opt_altKey Whether the alt key is held down.
* @return {boolean} Returns YES if it's a key that fires a keypress event.
*/
CPKeyCodes.firesKeyPressEvent = function(keyCode, key, opt_heldKeyCode, opt_shiftKey, opt_ctrlKey, opt_altKey)
{
// The property key from event is one character wide in case of 'regular' keys (as opposed e.g. to arrow keys)
// Regular keys all fire the keypress event
// Modern approach: Use event.key if available, as it is the most reliable standard.
if (key)
{
// Any key that produces a single, printable character fires a keypress event.
if (key.length === 1)
return true;
if (key && key.length == 1)
return true;
// "Enter" is a special non-printable key that historically fires keypress for compatibility.
if (key === "Enter")
return true;
// For all other non-printable keys (e.g., "ArrowLeft", "Escape", "F1"),
// modern browsers do not fire a keypress event.
return false;
}
// --- Legacy Fallback Logic (for browsers that don't support event.key) ---
if (!CPFeatureIsCompatible(CPJavaScriptRemedialKeySupport))
return true;
@@ -196,9 +194,11 @@ CPKeyCodes.firesKeyPressEvent = function(keyCode, key, opt_heldKeyCode, opt_shif
/*!
* Test for whether or not a given keyCode represents a character key.
* NOTE: This is a legacy function for browsers that don't support `event.key`.
* It is unreliable because `keyCode` represents a physical key, not the character produced.
*
* @param keyCode A key code.
* @return Returns YES if the keyCode is a character key.
* @param {number} keyCode A key code.
* @return {boolean} Returns YES if the keyCode is a character key.
*/
CPKeyCodes.isCharacterKey = function(keyCode)
{
+9 -9
View File
@@ -44,11 +44,11 @@
[button setKeyEquivalent:"a"];
[button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0
timestamp:0 windowNumber:0 context:nil
characters:"b" charactersIgnoringModifiers:"b" isARepeat:NO keyCode:0]];
characters:"b" charactersIgnoringModifiers:"b" isARepeat:NO keyCode:0 isActionKey:NO]];
[self assertFalse:wasClicked];
[button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0
timestamp:0 windowNumber:0 context:nil
characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0]];
characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0 isActionKey:NO]];
[self assertTrue:wasClicked];
}
@@ -60,11 +60,11 @@
[button setKeyEquivalentModifierMask:CPAlternateKeyMask];
[button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0
timestamp:0 windowNumber:0 context:nil
characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0]];
characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0 isActionKey:NO]];
[self assertFalse:wasClicked];
[button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:CPAlternateKeyMask
timestamp:0 windowNumber:0 context:nil
characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0]];
characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0 isActionKey:NO]];
[self assertTrue:wasClicked];
}
@@ -76,12 +76,12 @@
[button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0
timestamp:0 windowNumber:0 context:nil
characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0]];
characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0 isActionKey:NO]];
[self assertFalse:wasClicked];
[button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:CPShiftKeyMask
timestamp:0 windowNumber:0 context:nil
characters:"A" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0]];
characters:"A" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0 isActionKey:NO]];
[self assertTrue:wasClicked];
}
@@ -92,15 +92,15 @@
[button setKeyEquivalent:CPEscapeFunctionKey];
[button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0
timestamp:0 windowNumber:0 context:nil
characters:CPDeleteCharacter charactersIgnoringModifiers:CPDeleteCharacter isARepeat:NO keyCode:0]];
characters:CPDeleteCharacter charactersIgnoringModifiers:CPDeleteCharacter isARepeat:NO keyCode:0 isActionKey:NO]];
[self assertFalse:wasClicked];
[button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0
timestamp:0 windowNumber:0 context:nil
characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0]];
characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0 isActionKey:NO]];
[self assertFalse:wasClicked];
[button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0
timestamp:0 windowNumber:0 context:nil
characters:CPEscapeFunctionKey charactersIgnoringModifiers:CPEscapeFunctionKey isARepeat:NO keyCode:0]];
characters:CPEscapeFunctionKey charactersIgnoringModifiers:CPEscapeFunctionKey isARepeat:NO keyCode:0 isActionKey:NO]];
[self assertTrue:wasClicked];
}
+3 -3
View File
@@ -31,7 +31,7 @@
timestamp:400.5 windowNumber:300 context:nil eventNumber:0 clickCount:2 pressure:0.5];
[self assert:@"CPEvent: type=2 loc={50, 50} time=400.5 flags=0x20000 win=undefined winNum=0 ctxt=null evNum=0 click=2 buttonNumber=0 pressure=0.5" equals:[anEvent description]];
anEvent = [CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPShiftKeyMask | CPCommandKeyMask timestamp:12345.6 windowNumber:10 context:nil characters:"X" charactersIgnoringModifiers:"x" isARepeat:NO keyCode:10];
anEvent = [CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPShiftKeyMask | CPCommandKeyMask timestamp:12345.6 windowNumber:10 context:nil characters:"X" charactersIgnoringModifiers:"x" isARepeat:NO keyCode:10 isActionKey:NO];
[self assert:@"CPEvent: type=10 loc={0, 0} time=12345.6 flags=0x120000 win=null winNum=10 ctxt=null chars=\"X\" unmodchars=\"x\" repeat=0 keyCode=10" equals:[anEvent description]];
@@ -44,13 +44,13 @@
{
[self assert:0 equals:[CPEvent modifierFlags] message:@"no modifier flags active in a newly started app"];
var anEvent = [CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPShiftKeyMask timestamp:0 windowNumber:0 context:nil characters:"A" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0];
var anEvent = [CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPShiftKeyMask timestamp:0 windowNumber:0 context:nil characters:"A" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0 isActionKey:NO];
[CPApp sendEvent:anEvent];
[self assert:CPShiftKeyMask equals:[CPEvent modifierFlags] message:@"shift key pressed"];
// When the key up event is sent the modifier flags are cleared.
anEvent = [CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0 timestamp:0 windowNumber:0 context:nil characters:"A" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0];
anEvent = [CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0 timestamp:0 windowNumber:0 context:nil characters:"A" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0 isActionKey:NO];
[CPApp sendEvent:anEvent];
[self assert:0 equals:[CPEvent modifierFlags] message:@"shift key released"];
+8 -8
View File
@@ -149,24 +149,24 @@
// Don't match anything.
[menu performKeyEquivalent:[CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPPlatformActionKeyMask
timestamp:0 windowNumber:0 context:nil
characters:"b" charactersIgnoringModifiers:"b" isARepeat:NO keyCode:0]];
characters:"b" charactersIgnoringModifiers:"b" isARepeat:NO keyCode:0 isActionKey:NO]];
[self assertFalse:escapeWasCalled || escapeNoModifierWasCalled || openDocumentWasCalled || undoWasCalled];
[menu performKeyEquivalent:[CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:0
timestamp:0 windowNumber:0 context:nil
characters:"o" charactersIgnoringModifiers:"o" isARepeat:NO keyCode:0]];
characters:"o" charactersIgnoringModifiers:"o" isARepeat:NO keyCode:0 isActionKey:NO]];
[self assertFalse:escapeWasCalled || escapeNoModifierWasCalled || openDocumentWasCalled || undoWasCalled];
[menu performKeyEquivalent:[CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPPlatformActionKeyMask
timestamp:0 windowNumber:0 context:nil
characters:"o" charactersIgnoringModifiers:"o" isARepeat:NO keyCode:0]];
characters:"o" charactersIgnoringModifiers:"o" isARepeat:NO keyCode:0 isActionKey:NO]];
[self assertFalse:escapeWasCalled || escapeNoModifierWasCalled || undoWasCalled];
[self assertTrue:openDocumentWasCalled message:"expect openDocumentWasCalled"];
openDocumentWasCalled = NO;
[menu performKeyEquivalent:[CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPPlatformActionKeyMask
timestamp:0 windowNumber:0 context:nil
characters:CPUndoKeyEquivalent charactersIgnoringModifiers:CPUndoKeyEquivalent isARepeat:NO keyCode:0]];
characters:CPUndoKeyEquivalent charactersIgnoringModifiers:CPUndoKeyEquivalent isARepeat:NO keyCode:0 isActionKey:NO]];
[self assertFalse:escapeWasCalled || escapeNoModifierWasCalled || openDocumentWasCalled];
[self assertTrue:undoWasCalled];
}
@@ -177,7 +177,7 @@
[menu performKeyEquivalent:[CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:0
timestamp:0 windowNumber:0 context:nil
characters:CPEscapeFunctionKey charactersIgnoringModifiers:CPEscapeFunctionKey isARepeat:NO keyCode:0]];
characters:CPEscapeFunctionKey charactersIgnoringModifiers:CPEscapeFunctionKey isARepeat:NO keyCode:0 isActionKey:NO]];
[self assertFalse:escapeWasCalled || openDocumentWasCalled || undoWasCalled];
[self assertTrue:escapeNoModifierWasCalled];
@@ -185,7 +185,7 @@
[menu performKeyEquivalent:[CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPPlatformActionKeyMask
timestamp:0 windowNumber:0 context:nil
characters:CPEscapeFunctionKey charactersIgnoringModifiers:CPEscapeFunctionKey isARepeat:NO keyCode:0]];
characters:CPEscapeFunctionKey charactersIgnoringModifiers:CPEscapeFunctionKey isARepeat:NO keyCode:0 isActionKey:NO]];
[self assertFalse:escapeNoModifierWasCalled || openDocumentWasCalled || undoWasCalled];
[self assertTrue:escapeWasCalled];
}
@@ -196,7 +196,7 @@
[menu performKeyEquivalent:[CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPPlatformActionKeyMask
timestamp:0 windowNumber:0 context:nil
characters:@"s" charactersIgnoringModifiers:@"s" isARepeat:NO keyCode:0]];
characters:@"s" charactersIgnoringModifiers:@"s" isARepeat:NO keyCode:0 isActionKey:NO]];
[self assertFalse:escapeWasCalled || escapeNoModifierWasCalled || openDocumentWasCalled || saveDocumentAsWasCalled || undoWasCalled];
[self assertTrue:saveDocumentWasCalled message:"saveDocumentWasCalled"];
@@ -204,7 +204,7 @@
[menu performKeyEquivalent:[CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPPlatformActionKeyMask | CPShiftKeyMask
timestamp:0 windowNumber:0 context:nil
characters:@"s" charactersIgnoringModifiers:@"s" isARepeat:NO keyCode:0]];
characters:@"s" charactersIgnoringModifiers:@"s" isARepeat:NO keyCode:0 isActionKey:NO]];
[self assertFalse:escapeWasCalled || escapeNoModifierWasCalled || openDocumentWasCalled || saveDocumentWasCalled || undoWasCalled];
[self assertTrue:saveDocumentAsWasCalled message:"saveDocumentAsWasCalled"];
}
+2 -2
View File
@@ -45,7 +45,7 @@
var keyEvent = [CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:0
timestamp:0 windowNumber:0 context:nil
characters:character charactersIgnoringModifiers:character isARepeat:NO keyCode:keyCode];
characters:character charactersIgnoringModifiers:character isARepeat:NO keyCode:keyCode isActionKey:YES];
[responder interpretKeyEvents:[keyEvent]];
[self assert:[selector] equals:responder.doCommandCalls];
}
@@ -57,7 +57,7 @@
var keyEvent = [CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPShiftKeyMask
timestamp:0 windowNumber:0 context:nil
characters:CPLeftArrowFunctionKey charactersIgnoringModifiers:CPLeftArrowFunctionKey isARepeat:NO keyCode:CPKeyCodes.LEFT];
characters:CPLeftArrowFunctionKey charactersIgnoringModifiers:CPLeftArrowFunctionKey isARepeat:NO keyCode:CPKeyCodes.LEFT isActionKey:YES];
[responder interpretKeyEvents:[keyEvent]];
[self assert:[@selector(moveLeftAndModifySelection:)] equals:responder.doCommandCalls];
}