mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-10-11 11:07:07 +00:00
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:
1 parent
4b4ac5d8b7
commit
377618dcd4
1 file changed
+65
-43
+65
-43
@@ -351,8 +351,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
|||||||
else
|
else
|
||||||
[self unsetThemeState:CPThemeStateEditable];
|
[self unsetThemeState:CPThemeStateEditable];
|
||||||
|
|
||||||
// We only allow first responder status if the field is editable and enabled.
|
// We only allow first responder status if the field is enable, and editable or selectable.
|
||||||
if (!shouldBeEditable && [[self window] firstResponder] === self)
|
if (!(shouldBeEditable && ![self isSelectable]) && [[self window] firstResponder] === self)
|
||||||
[[self window] makeFirstResponder:nil];
|
[[self window] makeFirstResponder:nil];
|
||||||
|
|
||||||
if (shouldBeEditable)
|
if (shouldBeEditable)
|
||||||
@@ -377,7 +377,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
|||||||
{
|
{
|
||||||
[super setEnabled:shouldBeEnabled];
|
[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)
|
if (!shouldBeEnabled && [[self window] firstResponder] === self)
|
||||||
[[self window] makeFirstResponder:nil];
|
[[self window] makeFirstResponder:nil];
|
||||||
}
|
}
|
||||||
@@ -531,7 +531,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
|||||||
/*! @ignore */
|
/*! @ignore */
|
||||||
- (BOOL)acceptsFirstResponder
|
- (BOOL)acceptsFirstResponder
|
||||||
{
|
{
|
||||||
return [self isEditable] && [self isEnabled] && [self _isWithinUsablePlatformRect];
|
return ([self isEditable] || [self isSelectable]) && [self isEnabled] && [self _isWithinUsablePlatformRect];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! @ignore */
|
/*! @ignore */
|
||||||
@@ -542,7 +542,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
|||||||
|
|
||||||
_isEditing = NO;
|
_isEditing = NO;
|
||||||
|
|
||||||
if ([[self window] isKeyWindow])
|
if ([[self window] isKeyWindow] && [self isEditable])
|
||||||
return [self _becomeFirstKeyResponder];
|
return [self _becomeFirstKeyResponder];
|
||||||
|
|
||||||
return YES;
|
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.
|
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.)
|
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
|
- (BOOL)_becomeFirstKeyResponder
|
||||||
{
|
{
|
||||||
@@ -560,6 +563,11 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
|||||||
if (![self _isWithinUsablePlatformRect])
|
if (![self _isWithinUsablePlatformRect])
|
||||||
return NO;
|
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 setThemeState:CPThemeStateEditing];
|
||||||
|
|
||||||
[self _updatePlaceholderState];
|
[self _updatePlaceholderState];
|
||||||
@@ -680,24 +688,26 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
|||||||
- (BOOL)resignFirstResponder
|
- (BOOL)resignFirstResponder
|
||||||
{
|
{
|
||||||
#if PLATFORM(DOM)
|
#if PLATFORM(DOM)
|
||||||
|
// We might have been the first responder without actually editing.
|
||||||
var element = [self _inputElement],
|
if (_isEditing)
|
||||||
newValue = element.value,
|
|
||||||
error = @"";
|
|
||||||
|
|
||||||
if (newValue !== _stringValue)
|
|
||||||
{
|
{
|
||||||
[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,
|
if (newValue !== _stringValue)
|
||||||
// even if the value has not changed.
|
{
|
||||||
if ([self _valueIsValid:newValue] === NO)
|
[self _setStringValue:newValue];
|
||||||
{
|
}
|
||||||
element.focus();
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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
|
#endif
|
||||||
|
|
||||||
// When we are no longer the first responder we don't worry about the key status of our window anymore.
|
// 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];
|
[self _resignFirstKeyResponder];
|
||||||
|
|
||||||
_isEditing = NO;
|
_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])
|
if ([self sendsActionOnEndEditing])
|
||||||
[self sendAction:[self action] to:[self target]];
|
[self sendAction:[self action] to:[self target]];
|
||||||
|
}
|
||||||
|
|
||||||
[self textDidBlur:[CPNotification notificationWithName:CPTextFieldDidBlurNotification object:self userInfo:nil]];
|
[self textDidBlur:[CPNotification notificationWithName:CPTextFieldDidBlurNotification object:self userInfo:nil]];
|
||||||
|
|
||||||
@@ -782,6 +795,9 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
|||||||
|
|
||||||
- (void)_windowDidBecomeKey:(CPNotification)aNotification
|
- (void)_windowDidBecomeKey:(CPNotification)aNotification
|
||||||
{
|
{
|
||||||
|
if (![self isEditable])
|
||||||
|
return;
|
||||||
|
|
||||||
var wind = [self window];
|
var wind = [self window];
|
||||||
|
|
||||||
if ([wind isKeyWindow] && [wind firstResponder] === self)
|
if ([wind isKeyWindow] && [wind firstResponder] === self)
|
||||||
@@ -828,6 +844,17 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
|||||||
return [self acceptsFirstResponder];
|
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
|
- (void)mouseDown:(CPEvent)anEvent
|
||||||
{
|
{
|
||||||
// Don't track! (ever?)
|
// Don't track! (ever?)
|
||||||
@@ -880,26 +907,21 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
|||||||
|
|
||||||
- (void)keyUp:(CPEvent)anEvent
|
- (void)keyUp:(CPEvent)anEvent
|
||||||
{
|
{
|
||||||
#if PLATFORM(DOM)
|
if (![self isEditable])
|
||||||
|
return;
|
||||||
|
|
||||||
|
#if PLATFORM(DOM)
|
||||||
var newValue = [self _inputElement].value;
|
var newValue = [self _inputElement].value;
|
||||||
|
|
||||||
if (newValue !== _stringValue)
|
if (newValue !== _stringValue)
|
||||||
{
|
{
|
||||||
[self _setStringValue:newValue];
|
[self _setStringValue:newValue];
|
||||||
|
|
||||||
if (!_isEditing)
|
[self _didEdit];
|
||||||
{
|
|
||||||
_isEditing = YES;
|
|
||||||
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
|
|
||||||
}
|
|
||||||
|
|
||||||
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
|
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)keyDown:(CPEvent)anEvent
|
- (void)keyDown:(CPEvent)anEvent
|
||||||
@@ -940,6 +962,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
|||||||
if (newValue !== _stringValue)
|
if (newValue !== _stringValue)
|
||||||
{
|
{
|
||||||
[self _setStringValue:newValue];
|
[self _setStringValue:newValue];
|
||||||
|
[self _didEdit];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([self _valueIsValid:_stringValue])
|
if ([self _valueIsValid:_stringValue])
|
||||||
@@ -993,13 +1016,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
|||||||
// NOTE: _stringValue is now the current input element value
|
// NOTE: _stringValue is now the current input element value
|
||||||
if (oldValue !== _stringValue)
|
if (oldValue !== _stringValue)
|
||||||
{
|
{
|
||||||
if (!_isEditing)
|
[self _didEdit];
|
||||||
{
|
|
||||||
_isEditing = YES;
|
|
||||||
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
|
|
||||||
}
|
|
||||||
|
|
||||||
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1298,6 +1315,9 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
|||||||
|
|
||||||
- (void)cut:(id)sender
|
- (void)cut:(id)sender
|
||||||
{
|
{
|
||||||
|
if (![self isEnabled])
|
||||||
|
return;
|
||||||
|
|
||||||
[self copy:sender];
|
[self copy:sender];
|
||||||
|
|
||||||
if (![self isEditable])
|
if (![self isEditable])
|
||||||
@@ -1336,7 +1356,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
|||||||
pasteString = [pasteboard stringForType:CPStringPboardType],
|
pasteString = [pasteboard stringForType:CPStringPboardType],
|
||||||
newValue = [_stringValue stringByReplacingCharactersInRange:selectedRange withString:pasteString];
|
newValue = [_stringValue stringByReplacingCharactersInRange:selectedRange withString:pasteString];
|
||||||
|
|
||||||
[self setStringValue:newValue];
|
[self _setStringValue:newValue];
|
||||||
|
[self _didEdit];
|
||||||
[self setSelectedRange:CPMakeRange(selectedRange.location + pasteString.length, 0)];
|
[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".
|
// 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:""];
|
var newValue = [_stringValue stringByReplacingCharactersInRange:selectedRange withString:""];
|
||||||
|
|
||||||
[self setStringValue:newValue];
|
[self _setStringValue:newValue];
|
||||||
[self setSelectedRange:CPMakeRange(selectedRange.location, 0)];
|
[self setSelectedRange:CPMakeRange(selectedRange.location, 0)];
|
||||||
|
[self _didEdit];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark Setting the Delegate
|
#pragma mark Setting the Delegate
|
||||||
@@ -1629,7 +1651,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
|||||||
{
|
{
|
||||||
[super viewDidUnhide];
|
[super viewDidUnhide];
|
||||||
|
|
||||||
if ([[self window] firstResponder] === self)
|
if ([self isEditable] && [[self window] firstResponder] === self)
|
||||||
[self _becomeFirstKeyResponder];
|
[self _becomeFirstKeyResponder];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user