mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
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:
+31
-3
@@ -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
|
||||
|
||||
@@ -332,7 +332,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
characters:nil
|
||||
charactersIgnoringModifiers:nil
|
||||
isARepeat:NO
|
||||
keyCode:nil];
|
||||
keyCode:nil
|
||||
isActionKey:NO];
|
||||
|
||||
[CPTextFieldInputOwner keyUp:cappEvent];
|
||||
|
||||
|
||||
+191
-271
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -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"];
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user