diff --git a/AppKit/CPEvent.j b/AppKit/CPEvent.j
index 7bc06d501..1d486521a 100644
--- a/AppKit/CPEvent.j
+++ b/AppKit/CPEvent.j
@@ -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
diff --git a/AppKit/CPTextField.j b/AppKit/CPTextField.j
index bdf926b09..03416af31 100644
--- a/AppKit/CPTextField.j
+++ b/AppKit/CPTextField.j
@@ -332,7 +332,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
characters:nil
charactersIgnoringModifiers:nil
isARepeat:NO
- keyCode:nil];
+ keyCode:nil
+ isActionKey:NO];
[CPTextFieldInputOwner keyUp:cappEvent];
diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j
index 9327db2a4..7e862f1ae 100644
--- a/AppKit/CPTextView/CPTextView.j
+++ b/AppKit/CPTextView/CPTextView.j
@@ -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
.
+ // - '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
diff --git a/AppKit/Platform/DOM/CPPlatformPasteboard.j b/AppKit/Platform/DOM/CPPlatformPasteboard.j
index 50310503f..102feb670 100644
--- a/AppKit/Platform/DOM/CPPlatformPasteboard.j
+++ b/AppKit/Platform/DOM/CPPlatformPasteboard.j
@@ -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;
diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j
index 49de64861..7c714c263 100644
--- a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j
+++ b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j
@@ -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;
diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOMKeys.j b/AppKit/Platform/DOM/CPPlatformWindow+DOMKeys.j
index a24d241f3..a76bed207 100644
--- a/AppKit/Platform/DOM/CPPlatformWindow+DOMKeys.j
+++ b/AppKit/Platform/DOM/CPPlatformWindow+DOMKeys.j
@@ -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)
{
diff --git a/Tests/AppKit/CPButtonTest.j b/Tests/AppKit/CPButtonTest.j
index 19c0172fb..9abf42e27 100644
--- a/Tests/AppKit/CPButtonTest.j
+++ b/Tests/AppKit/CPButtonTest.j
@@ -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];
}
diff --git a/Tests/AppKit/CPEventTest.j b/Tests/AppKit/CPEventTest.j
index 7570583dc..feef12694 100644
--- a/Tests/AppKit/CPEventTest.j
+++ b/Tests/AppKit/CPEventTest.j
@@ -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"];
diff --git a/Tests/AppKit/CPMenuTest.j b/Tests/AppKit/CPMenuTest.j
index 6449b65aa..b50afdea3 100644
--- a/Tests/AppKit/CPMenuTest.j
+++ b/Tests/AppKit/CPMenuTest.j
@@ -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"];
}
diff --git a/Tests/AppKit/CPResponderTest.j b/Tests/AppKit/CPResponderTest.j
index 4c3159ab3..2aba5adc3 100644
--- a/Tests/AppKit/CPResponderTest.j
+++ b/Tests/AppKit/CPResponderTest.j
@@ -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];
}