From 377618dcd4bb052d16a06eb89a2e03aec6d569e4 Mon Sep 17 00:00:00 2001 From: Alexander Ljungberg Date: Mon, 12 Aug 2013 15:41:10 +0100 Subject: [PATCH] New: non-editable but selectable text fields can become the first responder. Without this change, text fields with setEditable:NO but setSelectable:YES could not become the first responder, and so would not listen for copy: events. This change allows selectable fields to become the first responder, taking advantage of the distinction between first responder and first key responder maintained internally in CPTextField - a merely selectable field does not become the first key responder. Refs #1972. --- AppKit/CPTextField.j | 108 ++++++++++++++++++++++++++----------------- 1 file changed, 65 insertions(+), 43 deletions(-) diff --git a/AppKit/CPTextField.j b/AppKit/CPTextField.j index fbe353d82..e77e1eeef 100644 --- a/AppKit/CPTextField.j +++ b/AppKit/CPTextField.j @@ -351,8 +351,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); else [self unsetThemeState:CPThemeStateEditable]; - // We only allow first responder status if the field is editable and enabled. - if (!shouldBeEditable && [[self window] firstResponder] === self) + // We only allow first responder status if the field is enable, and editable or selectable. + if (!(shouldBeEditable && ![self isSelectable]) && [[self window] firstResponder] === self) [[self window] makeFirstResponder:nil]; if (shouldBeEditable) @@ -377,7 +377,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); { [super setEnabled:shouldBeEnabled]; - // We only allow first responder status if the field is editable and enabled. + // We only allow first responder status if the field is enabled. if (!shouldBeEnabled && [[self window] firstResponder] === self) [[self window] makeFirstResponder:nil]; } @@ -531,7 +531,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); /*! @ignore */ - (BOOL)acceptsFirstResponder { - return [self isEditable] && [self isEnabled] && [self _isWithinUsablePlatformRect]; + return ([self isEditable] || [self isSelectable]) && [self isEnabled] && [self _isWithinUsablePlatformRect]; } /*! @ignore */ @@ -542,7 +542,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); _isEditing = NO; - if ([[self window] isKeyWindow]) + if ([[self window] isKeyWindow] && [self isEditable]) return [self _becomeFirstKeyResponder]; return YES; @@ -552,6 +552,9 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); A text field can be the first responder without necessarily being the focus of keyboard input. For example, it might be the first responder of window A but window B is the main and key window. It's important we don't put a focused input field into a text field in a non-key window, even if that field is the first responder, because the key window might also have a first responder text field which the user will expect to receive keyboard input. Since a first responder but non-key window text field can't receive input it should not even look like an active text field (Cocoa has a "slightly active" text field look it uses when another window is the key window, but Cappuccino doesn't today.) + + It's also possible for a text field to be non-editable but selectable in which case it can also become the first responder - + this is what allows text to be copied from it. */ - (BOOL)_becomeFirstKeyResponder { @@ -560,6 +563,11 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); if (![self _isWithinUsablePlatformRect]) return NO; + // A selectable but non-editable text field may be the first responder, but never the + // first key responder (first key responder indicating editability.) + if (![self isEditable]) + return NO; + [self setThemeState:CPThemeStateEditing]; [self _updatePlaceholderState]; @@ -680,24 +688,26 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); - (BOOL)resignFirstResponder { #if PLATFORM(DOM) - - var element = [self _inputElement], - newValue = element.value, - error = @""; - - if (newValue !== _stringValue) + // We might have been the first responder without actually editing. + if (_isEditing) { - [self _setStringValue:newValue]; - } + var element = [self _inputElement], + newValue = element.value, + error = @""; - // If there is a formatter, always give it a chance to reject the resignation, - // even if the value has not changed. - if ([self _valueIsValid:newValue] === NO) - { - element.focus(); - return NO; - } + if (newValue !== _stringValue) + { + [self _setStringValue:newValue]; + } + // If there is a formatter, always give it a chance to reject the resignation, + // even if the value has not changed. + if ([self _valueIsValid:newValue] === NO) + { + element.focus(); + return NO; + } + } #endif // When we are no longer the first responder we don't worry about the key status of our window anymore. @@ -706,10 +716,13 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); [self _resignFirstKeyResponder]; _isEditing = NO; - [self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidEndEditingNotification object:self userInfo:nil]]; + if ([self isEditable]) + { + [self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidEndEditingNotification object:self userInfo:nil]]; - if ([self sendsActionOnEndEditing]) - [self sendAction:[self action] to:[self target]]; + if ([self sendsActionOnEndEditing]) + [self sendAction:[self action] to:[self target]]; + } [self textDidBlur:[CPNotification notificationWithName:CPTextFieldDidBlurNotification object:self userInfo:nil]]; @@ -782,6 +795,9 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); - (void)_windowDidBecomeKey:(CPNotification)aNotification { + if (![self isEditable]) + return; + var wind = [self window]; if ([wind isKeyWindow] && [wind firstResponder] === self) @@ -828,6 +844,17 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); return [self acceptsFirstResponder]; } +- (void)_didEdit +{ + if (!_isEditing) + { + _isEditing = YES; + [self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]]; + } + + [self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]]; +} + - (void)mouseDown:(CPEvent)anEvent { // Don't track! (ever?) @@ -880,26 +907,21 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); - (void)keyUp:(CPEvent)anEvent { -#if PLATFORM(DOM) + if (![self isEditable]) + return; +#if PLATFORM(DOM) var newValue = [self _inputElement].value; if (newValue !== _stringValue) { [self _setStringValue:newValue]; - if (!_isEditing) - { - _isEditing = YES; - [self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]]; - } - - [self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]]; + [self _didEdit]; } -#endif - [[[self window] platformWindow] _propagateCurrentDOMEvent:YES]; +#endif } - (void)keyDown:(CPEvent)anEvent @@ -940,6 +962,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); if (newValue !== _stringValue) { [self _setStringValue:newValue]; + [self _didEdit]; } if ([self _valueIsValid:_stringValue]) @@ -993,13 +1016,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); // NOTE: _stringValue is now the current input element value if (oldValue !== _stringValue) { - if (!_isEditing) - { - _isEditing = YES; - [self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]]; - } - - [self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]]; + [self _didEdit]; } #endif @@ -1298,6 +1315,9 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); - (void)cut:(id)sender { + if (![self isEnabled]) + return; + [self copy:sender]; if (![self isEditable]) @@ -1336,7 +1356,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); pasteString = [pasteboard stringForType:CPStringPboardType], newValue = [_stringValue stringByReplacingCharactersInRange:selectedRange withString:pasteString]; - [self setStringValue:newValue]; + [self _setStringValue:newValue]; + [self _didEdit]; [self setSelectedRange:CPMakeRange(selectedRange.location + pasteString.length, 0)]; } // If we don't have an oninput listener, we won't detect the change made by the cut and need to fake a key up "soon". @@ -1447,8 +1468,9 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); var newValue = [_stringValue stringByReplacingCharactersInRange:selectedRange withString:""]; - [self setStringValue:newValue]; + [self _setStringValue:newValue]; [self setSelectedRange:CPMakeRange(selectedRange.location, 0)]; + [self _didEdit]; } #pragma mark Setting the Delegate @@ -1629,7 +1651,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); { [super viewDidUnhide]; - if ([[self window] firstResponder] === self) + if ([self isEditable] && [[self window] firstResponder] === self) [self _becomeFirstKeyResponder]; }