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.
This commit is contained in:
Alexander Ljungberg
2013-08-12 15:41:10 +01:00
parent 4b4ac5d8b7
commit 377618dcd4
+65 -43
View File
@@ -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];
}