Merge branch 'flllow-tokenfield-improvements'

This commit is contained in:
Alexander Ljungberg
2010-10-23 20:53:34 -03:00
6 changed files with 348 additions and 136 deletions
+327 -133
View File
@@ -53,11 +53,20 @@ var CPTokenFieldDOMInputElement = nil,
#endif
var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
CPTokenFieldTableColumnIdentifier = @"CPTokenFieldTableColumnIdentifier";
var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
CPTokenFieldTableColumnIdentifier = @"CPTokenFieldTableColumnIdentifier",
CPScrollDestinationNone = 0,
CPScrollDestinationLeft = 1,
CPScrollDestinationRight = 2;
@implementation CPTokenField : CPTextField
{
CPScrollView _tokenScrollView;
int _shouldScrollTo;
CPRange _selectedRange;
CPView _autocompleteContainer;
CPScrollView _autocompleteScrollView;
CPTableView _autocompleteView;
@@ -66,9 +75,11 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
CPArray _cachedCompletions;
CPIndexSet _selectedTokenIndexes;
CPCharacterSet _tokenizingCharacterSet @accessors(property=tokenizingCharacterSet);
CPEvent _mouseDownEvent;
BOOL _preventResign;
}
+ (CPCharacterSet)defaultTokenizingCharacterSet
@@ -85,8 +96,20 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
{
if (self = [super initWithFrame:frame])
{
_selectedRange = CPMakeRange(0, 0);
_tokenScrollView = [[CPScrollView alloc] initWithFrame:CGRectMakeZero()];
[_tokenScrollView setHasHorizontalScroller:NO];
[_tokenScrollView setHasVerticalScroller:NO];
[_tokenScrollView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
var contentView = [[CPView alloc] initWithFrame:CGRectMakeZero()];
[contentView setAutoresizingMask:CPViewWidthSizable];
[_tokenScrollView setDocumentView:contentView];
[self addSubview:_tokenScrollView];
_tokenIndex = 0;
_selectedTokenIndexes = [CPIndexSet indexSet];
_cachedCompletions = [];
_completionDelay = [CPTokenField defaultCompletionDelay];
@@ -122,6 +145,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
[self setBezeled:YES];
[self setObjectValue:[]];
[self setNeedsLayout];
}
return self;
@@ -175,16 +199,12 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
// Remove the uncompleted token and add the token string.
// Explicitely remove the last object because the array contains strings and removeObject uses isEqual to compare objects
if (shouldRemoveLastObject)
{
var indexOfLastObject = [objectValue count] - 1;
if (!indexOfLastObject)
indexOfLastObject = 0;
[objectValue removeObjectAtIndex:_selectedRange.location];
[objectValue removeObjectAtIndex:indexOfLastObject];
}
[objectValue addObject:token];
[objectValue insertObject:token atIndex:_selectedRange.location];
var location = _selectedRange.location;
[self setObjectValue:objectValue];
_selectedRange = CPMakeRange(location + 1, 0);
[self _inputElement].value = @"";
[self setNeedsLayout];
@@ -204,10 +224,15 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
{
var indexOfToken = [[self _tokens] indexOfObject:token];
if (extend)
[_selectedTokenIndexes addIndex:indexOfToken];
if (indexOfToken == CPNotFound)
{
if (!extend)
_selectedRange = CPMakeRange([[self _tokens] count], 0);
}
else if (extend)
_selectedRange = CPUnionRange(_selectedRange, CPMakeRange(indexOfToken, 1));
else
_selectedTokenIndexes = [CPIndexSet indexSetWithIndex:indexOfToken];
_selectedRange = CPMakeRange(indexOfToken, 1);
[self setNeedsLayout];
}
@@ -215,7 +240,9 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
- (void)_deselectToken:(_CPTokenFieldToken)token
{
var indexOfToken = [[self _tokens] indexOfObject:token];
[_selectedTokenIndexes removeIndex:indexOfToken];
if (CPLocationInRange(indexOfToken, _selectedRange))
_selectedRange = CPMakeRange(MAX(indexOfToken, _selectedRange.location), MIN(_selectedRange.length, indexOfToken - _selectedRange.location));
[self setNeedsLayout];
}
@@ -225,9 +252,20 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
var indexOfToken = [[self _tokens] indexOfObject:token],
objectValue = [self objectValue];
// If the token was selected, deselect it for selection preservation.
[self _deselectToken:token];
// Preserve selection.
var selection = CPCopyRange(_selectedRange);
[objectValue removeObjectAtIndex:indexOfToken];
[self setObjectValue:objectValue];
_selectedRange = selection;
[self setNeedsLayout];
[self _controlTextDidChange];
}
- (void)_controlTextDidChange
{
var theBinding = [CPKeyValueBinding getBinding:CPValueBinding forObject:self];
if (theBinding)
@@ -236,15 +274,20 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
}
- (CPIndexSet)_selectedTokenIndexes
- (void)_removeSelectedTokens:(id)sender
{
return _selectedTokenIndexes;
}
var tokens = [self objectValue];
- (void)_setSelectedTokenIndexes:(CPIndexSet)selectedIndexes
{
_selectedTokenIndexes = selectedIndexes;
[self setNeedsLayout];
for (var i = _selectedRange.length - 1; i >= 0; i--)
[tokens removeObjectAtIndex:_selectedRange.location + i];
var collapsedSelection = _selectedRange.location;
[self setObjectValue:tokens];
// setObjectValue moves the cursor to the end of the selection. We want it to stay
// where the selected tokens were.
_selectedRange = CPMakeRange(collapsedSelection, 0);
[self _controlTextDidChange];
}
// =============
@@ -288,7 +331,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
element.style.width = CGRectGetWidth(contentRect) + "px";
element.style.height = CGRectGetHeight(contentRect) + "px";
_DOMElement.appendChild(element);
[_tokenScrollView documentView]._DOMElement.appendChild(element);
window.setTimeout(function()
{
@@ -319,12 +362,11 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
- (BOOL)resignFirstResponder
{
if (_preventResign)
return NO;
[self unsetThemeState:CPThemeStateEditing];
[self _updatePlaceholderState];
[self setNeedsLayout];
[self _autocomplete];
#if PLATFORM(DOM)
@@ -340,7 +382,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
CPTokenFieldInputDidBlur = NO;
CPTokenFieldInputResigning = NO;
if (element.parentNode == _DOMElement)
if (element.parentNode == [_tokenScrollView documentView]._DOMElement)
element.parentNode.removeChild(element);
CPTokenFieldInputIsActive = NO;
@@ -355,7 +397,10 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
}
#endif
_selectedTokenIndexes = [CPIndexSet indexSet];
[self _updatePlaceholderState];
[self setNeedsLayout];
[self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
@@ -364,12 +409,38 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
- (void)mouseDown:(CPEvent)anEvent
{
_selectedTokenIndexes = [CPIndexSet indexSet];
_preventResign = YES;
_mouseDownEvent = anEvent;
[self _selectToken:nil byExtendingSelection:NO];
// CPTokenFieldFocusInput = YES;
[super mouseDown:anEvent];
}
- (void)mouseUp:(CPEvent)anEvent
{
_preventResign = NO;
_mouseDownEvent = nil;
}
- (void)mouseDownOnToken:(_CPTokenFieldToken)aToken withEvent:(CPEvent)anEvent
{
_preventResign = YES;
_mouseDownEvent = anEvent;
}
- (void)mouseUpOnToken:(_CPTokenFieldToken)aToken withEvent:(CPEvent)anEvent
{
if (_mouseDownEvent && CGPointEqualToPoint([_mouseDownEvent locationInWindow], [anEvent locationInWindow]))
{
[self _selectToken:aToken byExtendingSelection:[anEvent modifierFlags] & CPShiftKeyMask];
[[self window] makeFirstResponder:self];
// Snap to the token if it's only half visible due to mouse wheel scrolling.
_shouldScrollTo = aToken;
}
_preventResign = NO;
}
// ===========
// = CONTROL =
// ===========
@@ -388,7 +459,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
- (id)objectValue
{
var objectValue = [];
for (var i = 0; i < [[self _tokens] count]; i++)
for (var i = 0, count = [[self _tokens] count]; i < count; i++)
{
var token = [[self _tokens] objectAtIndex:i];
@@ -401,7 +472,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
#if PLATFORM(DOM)
if ([self _inputElement].value != @"")
[objectValue addObject:[self _inputElement].value];
[objectValue insertObject:[self _inputElement].value atIndex:_selectedRange.location];
#endif
@@ -420,10 +491,11 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
if (aValue === superValue || [aValue isEqualToArray:superValue])
return;
var objectValue = [aValue copy];
var objectValue = [aValue copy],
contentView = [_tokenScrollView documentView];
// Because we do not know for sure which tokens are removed we remove them all
for (var i = 0; i < [[self _tokens] count]; i++)
for (var i = 0, count = [[self _tokens] count]; i < count; i++)
[[[self _tokens] objectAtIndex:i] removeFromSuperview];
objectValue = [];
@@ -431,7 +503,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
if (aValue !== nil)
{
// Re-add all tokens
for (var i = 0; i < [aValue count]; i++)
for (var i = 0, count = [aValue count]; i < count; i++)
{
var token = [aValue objectAtIndex:i],
tokenView = [[_CPTokenFieldToken alloc] init];
@@ -440,7 +512,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
[tokenView setStringValue:token];
[objectValue addObject:tokenView];
[self addSubview:tokenView];
[contentView addSubview:tokenView];
}
}
@@ -454,8 +526,12 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
*/
_value = objectValue;
// Reset the selection.
[self _selectToken:nil byExtendingSelection:NO];
[self _updatePlaceholderState];
_shouldScrollTo = CPScrollDestinationRight;
[self setNeedsLayout];
[self setNeedsDisplay:YES];
}
@@ -500,9 +576,12 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
CPTokenFieldBlurFunction = function(anEvent)
{
if (CPTokenFieldInputOwner && CPTokenFieldInputOwner._DOMElement != CPTokenFieldDOMInputElement.parentNode)
if (CPTokenFieldInputOwner && [CPTokenFieldInputOwner._tokenScrollView documentView]._DOMElement != CPTokenFieldDOMInputElement.parentNode)
return;
if (CPTokenFieldInputOwner && CPTokenFieldInputOwner._preventResign)
return false;
if (!CPTokenFieldInputResigning && !CPTokenFieldFocusInput)
{
[[CPTokenFieldInputOwner window] makeFirstResponder:nil];
@@ -517,17 +596,15 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
CPTokenFieldKeyDownFunction = function(aDOMEvent)
{
CPTokenFieldTextDidChangeValue = [CPTokenFieldInputOwner stringValue];
// CPTokenFieldKeyPressFunction(anEvent);
aDOMEvent = aDOMEvent || window.event
CPTokenFieldTextDidChangeValue = [CPTokenFieldInputOwner stringValue];
// Update the selectedIndex if necesary
var index = [[CPTokenFieldInputOwner autocompleteView] selectedRow];
if (aDOMEvent.keyCode === CPUpArrowKeyCode)
index -= 1;
else if (aDOMEvent.keyCode === CPDownArrowKeyCode)
index += 1;
@@ -541,10 +618,9 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
var autocompleteView = [CPTokenFieldInputOwner autocompleteView],
clipView = [[autocompleteView enclosingScrollView] contentView],
rowRect = [autocompleteView rectOfRow:index];
rowRect = [autocompleteView rectOfRow:index],
owner = CPTokenFieldInputOwner;
// The clipview's and row it's width are equal, this makes sure the clipview can contain the row rect
// rowRect.size.width -= 2.0;
if (rowRect && !CPRectContainsRect([clipView bounds], rowRect))
[clipView scrollToPoint:[autocompleteView rectOfRow:index].origin];
@@ -556,21 +632,15 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
aDOMEvent.stopPropagation();
aDOMEvent.cancelBubble = true;
var owner = CPTokenFieldInputOwner;
if (aDOMEvent && aDOMEvent.keyCode === CPReturnKeyCode)
// Only resign first responder if we weren't autocompleting
if (![CPTokenFieldInputOwner hasThemeState:CPThemeStateAutoCompleting])
{
// Only resign first responder if we weren't autocompleting
if (![CPTokenFieldInputOwner hasThemeState:CPThemeStateAutoCompleting])
if (aDOMEvent && aDOMEvent.keyCode === CPReturnKeyCode)
{
[owner sendAction:[owner action] to:[owner target]];
[[owner window] makeFirstResponder:nil];
}
}
else if (aDOMEvent && aDOMEvent.keyCode === CPTabKeyCode)
{
// Only resign first responder if we weren't autocompleting
if (![CPTokenFieldInputOwner hasThemeState:CPThemeStateAutoCompleting])
else if (aDOMEvent && aDOMEvent.keyCode === CPTabKeyCode)
{
if (!aDOMEvent.shiftKey)
[[owner window] selectNextKeyView:owner];
@@ -594,6 +664,50 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
aDOMEvent.stopPropagation();
aDOMEvent.cancelBubble = true;
}
else if (aDOMEvent.keyCode == CPLeftArrowKeyCode && owner._selectedRange.location > 0 && CPTokenFieldDOMInputElement.value == "")
{
// Move the cursor back one token if the input is empty and the left arrow key is pressed.
if (!aDOMEvent.shiftKey)
{
if (owner._selectedRange.length)
// Simply collapse the range.
owner._selectedRange.length = 0;
else
owner._selectedRange.location--;
}
else
{
owner._selectedRange.location--;
// When shift is depressed, select the next token backwards.
owner._selectedRange.length++;
}
owner._shouldScrollTo = CPScrollDestinationLeft;
[owner setNeedsLayout];
}
else if (aDOMEvent.keyCode == CPRightArrowKeyCode && owner._selectedRange.location < [[owner _tokens] count] && CPTokenFieldDOMInputElement.value == "")
{
if (!aDOMEvent.shiftKey)
{
if (owner._selectedRange.length)
{
// Place the cursor at the end of the selection and collapse.
owner._selectedRange.location = CPMaxRange(owner._selectedRange);
owner._selectedRange.length = 0;
}
else
{
// Move the cursor forward one token if the input is empty and the right arrow key is pressed.
owner._selectedRange.location = MIN([[owner _tokens] count], owner._selectedRange.location + owner._selectedRange.length + 1);
}
}
else
{
// Leave the selection location in place but include the next token to the right.
owner._selectedRange.length++;
}
owner._shouldScrollTo = CPScrollDestinationRight;
[owner setNeedsLayout];
}
else if (aDOMEvent.keyCode === CPDeleteKeyCode)
{
// Highlight the previous token if backspace was pressed in an empty input element or re-show the completions view
@@ -601,34 +715,35 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
{
[self _hideCompletions];
// var tokenViews = [[CPTokenFieldInputOwner _tokens] lastObject];
var tokens = [CPTokenFieldInputOwner _tokens];
if (![[CPTokenFieldInputOwner _selectedTokenIndexes] count])
if (CPEmptyRange(CPTokenFieldInputOwner._selectedRange))
{
var tokenView = [tokens lastObject];
[CPTokenFieldInputOwner _setSelectedTokenIndexes:[CPIndexSet indexSetWithIndex:[tokens indexOfObject:tokenView]]];
[CPTokenFieldInputOwner _hideCompletions];
if (CPTokenFieldInputOwner._selectedRange.location > 0)
{
var tokens = [CPTokenFieldInputOwner _tokens],
tokenView = [tokens objectAtIndex:(CPTokenFieldInputOwner._selectedRange.location - 1)];
[CPTokenFieldInputOwner _selectToken:tokenView byExtendingSelection:NO];
}
}
else
{
var tokenViews = [tokens objectsAtIndexes:[CPTokenFieldInputOwner _selectedTokenIndexes]];
for (var i = 0; i < [tokenViews count]; i++)
{
var tokenView = [tokenViews objectAtIndex:i];
[tokenView removeFromSuperview];
[[CPTokenFieldInputOwner _tokens] removeObject:tokenView];
}
[CPTokenFieldInputOwner _setSelectedTokenIndexes:[CPIndexSet indexSet]];
}
[CPTokenFieldInputOwner _removeSelectedTokens:nil];
}
else
[CPTokenFieldInputOwner _delayedShowCompletions];
}
else if (aDOMEvent.keyCode === CPDeleteForwardKeyCode && CPTokenFieldDOMInputElement.value == @"")
{
// Delete forward if nothing is selected, else delete all selected.
[self _hideCompletions];
if (CPEmptyRange(CPTokenFieldInputOwner._selectedRange))
{
var tokens = [CPTokenFieldInputOwner _tokens];
if (CPTokenFieldInputOwner._selectedRange.location < [tokens count])
[CPTokenFieldInputOwner _deleteToken:tokens[CPTokenFieldInputOwner._selectedRange.location]];
}
else
[CPTokenFieldInputOwner _removeSelectedTokens:nil];
}
return true;
}
@@ -655,8 +770,11 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
}
[CPTokenFieldInputOwner _delayedShowCompletions];
_selectedTokenIndexes = [CPIndexSet indexSet];
// If there was a selection, collapse it now since we're typing in a new token.
owner._selectedRange.length = 0;
// Force immediate layout in case word wrapping is now necessary.
[owner setNeedsLayout];
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
}
@@ -675,7 +793,6 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
CPTokenFieldHandleBlur = function(anEvent)
{
var owner = CPTokenFieldInputOwner;
CPTokenFieldInputOwner = nil;
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
@@ -756,9 +873,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
- (void)_cancelShowCompletions
{
if ([_showCompletionsTimer isValid])
{
[_showCompletionsTimer invalidate];
}
}
- (void)_hideCompletions
@@ -769,11 +884,6 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
[self setNeedsLayout];
}
// - (void)setTokenizingCharacterSet:(NSCharacterSet *)characterSet;
// - (NSCharacterSet *)tokenizingCharacterSet;
// + (NSCharacterSet *)defaultTokenizingCharacterSet
// ==========
// = LAYOUT =
// ==========
@@ -781,14 +891,18 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
{
[super layoutSubviews];
[_tokenScrollView setFrame:[self rectForEphemeralSubviewNamed:"content-view"]];
var textFieldContentView = [self layoutEphemeralSubviewNamed:@"content-view"
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"bezel-view"];
if (textFieldContentView)
[textFieldContentView setHidden:[self stringValue] !== @""];
var frame = [self frame],
contentView = [self layoutEphemeralSubviewNamed:@"content-view"
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"bezel-view"];
if (contentView)
[contentView setHidden:[self stringValue] !== @""];
contentView = [_tokenScrollView documentView],
tokens = [self _tokens];
// Correctly size the tableview
// FIXME Horizontal scrolling will not work because we are not actually looking at the content to set the width for the table column
@@ -806,60 +920,132 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
else
[_autocompleteContainer setHidden:YES];
// Add every token as a seperate view
var contentRect = [self contentRectForBounds:[self bounds]],
// Hack to make sure we are handling an array
if (![tokens isKindOfClass:[CPArray class]])
return;
// Move each token into the right position.
var contentRect = CGRectMakeCopy([contentView bounds]),
contentOrigin = contentRect.origin,
contentSize = contentRect.size,
offset = CPPointMake(contentOrigin.x, contentOrigin.y),
spaceBetweenTokens = CPSizeMake(2.0, 2.0);
spaceBetweenTokens = CPSizeMake(2.0, 2.0),
isEditing = [[self window] firstResponder] == self,
tokenToken = [_CPTokenFieldToken new];
// Hack to make sure we are handling with an array
if (![[self _tokens] isKindOfClass:[CPArray class]])
return;
// Get the height of a typical token, or a token token if you will.
[tokenToken sizeToFit];
for (var i = 0; i < [[self _tokens] count]; i++)
var tokenHeight = CGRectGetHeight([tokenToken bounds]);
var fitAndFrame = function(width, height)
{
var tokenView = [[self _tokens] objectAtIndex:i];
var r = CGRectMake(0, 0, width, height);
if (offset.x + width >= contentSize.width && offset.x > contentOrigin.x)
{
offset.x = contentOrigin.x;
offset.y += height + spaceBetweenTokens.height;
}
r.origin.x = offset.x;
r.origin.y = offset.y;
// Make sure the frame fits.
if (CGRectGetHeight([contentView bounds]) < offset.y + height)
[contentView setFrame:CGRectMake(0, 0, CGRectGetWidth([_tokenScrollView bounds]), offset.y + height)];
offset.x += width + spaceBetweenTokens.width;
return r;
}
var placeEditor = function(useRemainingWidth)
{
var element = [self _inputElement],
textWidth = 1;
if (_selectedRange.length === 0)
{
// XXX The "X" here is used to estimate the space needed to fit the next character
// without clipping. Since different fonts might have different sizes of "X" this
// solution is not ideal, but it works.
textWidth = [(element.value || @"") + "X" sizeWithFont:[self font]].width;
if (useRemainingWidth)
textWidth = MAX(contentSize.width - offset.x - 1, textWidth);
}
var inputFrame = fitAndFrame(textWidth, tokenHeight);
element.style.left = inputFrame.origin.x + "px";
element.style.top = inputFrame.origin.y + "px";
element.style.width = inputFrame.size.width + "px";
element.style.height = inputFrame.size.height + "px";
// When editing, always scroll to the cursor.
if (_selectedRange.length == 0)
[[_tokenScrollView documentView] scrollRectToVisible:inputFrame];
}
for (var i = 0, count = [tokens count]; i < count; i++)
{
if (isEditing && i == CPMaxRange(_selectedRange))
placeEditor(false);
var tokenView = [tokens objectAtIndex:i];
// Make sure we are only changing completed tokens
if ([tokenView isKindOfClass:[CPString class]])
continue;
[tokenView setHighlighted:[_selectedTokenIndexes containsIndex:i]];
[tokenView setHighlighted:CPLocationInRange(i, _selectedRange)];
[tokenView sizeToFit];
// Increase the token fields height if the token view is outside of the bounds
var size = [self bounds].size,
tokenViewSize = [tokenView bounds].size;
var size = [contentView bounds].size,
tokenViewSize = [tokenView bounds].size,
tokenFrame = fitAndFrame(tokenViewSize.width, tokenViewSize.height);
if (contentSize.width < offset.x + tokenViewSize.width)
{
// Reset the x coordinate to the beginnning of the field
offset.x = contentOrigin.x;
// Increase the y offset to fall below the current tokens
offset.y += tokenViewSize.height + spaceBetweenTokens.height;
if (offset.y + tokenViewSize.height > contentSize.height)
{
size.height += offset.y + tokenViewSize.height;
[self setFrameSize:size];
}
}
[tokenView setFrameOrigin:offset];
offset.x += [tokenView bounds].size.width + spaceBetweenTokens.width;
[tokenView setFrame:tokenFrame];
}
if ([[self window] firstResponder] != self)
if (isEditing && CPMaxRange(_selectedRange) >= [tokens count])
placeEditor(true);
// Hide the editor if there are selected tokens, but still keep it active
// so we can continue using our standard keyboard handling events.
if (isEditing && _selectedRange.length)
{
[self _inputElement].style.left = "-10000px";
[self _inputElement].focus();
}
// Trim off any excess height downwards.
if (CGRectGetHeight([contentView bounds]) > offset.y + tokenHeight)
[contentView setFrame:CGRectMake(0, 0, CGRectGetWidth([_tokenScrollView bounds]), offset.y + tokenHeight)];
if (_shouldScrollTo !== CPScrollDestinationNone)
{
// Only carry out the scroll if the cursor isn't visible.
if (!(isEditing && _selectedRange.length == 0))
{
var scrollToToken = _shouldScrollTo;
if (scrollToToken === CPScrollDestinationLeft)
scrollToToken = tokens[_selectedRange.location]
else if (scrollToToken === CPScrollDestinationRight)
scrollToToken = tokens[MAX(0, CPMaxRange(_selectedRange) - 1)];
[self _scrollTokenViewToVisible:scrollToToken];
}
_shouldScrollTo = CPScrollDestinationNone;
}
}
- (BOOL)_scrollTokenViewToVisible:(_CPTokenFieldToken)aToken
{
if (!aToken)
return;
var element = [self _inputElement];
element.style.left = offset.x + @"px";
element.style.top = offset.y + @"px";
element.style.width = [self bounds].size.width - offset.x - 8.0 + "px";
element.style.height = contentRect.size.height;
return [[_tokenScrollView documentView] scrollRectToVisible:[aToken frame]];
}
// ======================
@@ -878,7 +1064,10 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
- (void)tableViewSelectionDidChange:(CPNotification)notification
{
// make sure a mouse click in the tableview doesn't steal first responder state
window.setTimeout(function() { [[self window] makeFirstResponder:self]; }, 2.0);
window.setTimeout(function()
{
[[self window] makeFirstResponder:self];
}, 2.0);
}
// =============
@@ -972,7 +1161,7 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
- (CGSize)_minimumFrameSize
{
var size = CGRectMakeZero(),
var size = CGSizeMakeZero(),
minSize = [self currentValueForThemeAttribute:@"min-size"],
contentInset = [self currentValueForThemeAttribute:@"content-inset"];
@@ -1006,7 +1195,12 @@ var CPThemeStateAutoCompleting = @"CPThemeStateAutoCompleting",
- (void)mouseDown:(CPEvent)anEvent
{
[_tokenField mouseDown:anEvent];
[_tokenField mouseDownOnToken:self withEvent:anEvent];
}
- (void)mouseUp:(CPEvent)anEvent
{
[_tokenField mouseUpOnToken:self withEvent:anEvent];
}
- (void)_delete:(id)sender
Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 B

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 511 B

After

Width:  |  Height:  |  Size: 530 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 511 B

After

Width:  |  Height:  |  Size: 537 B

+7 -3
View File
@@ -821,10 +821,10 @@ var themedButtonValues = nil,
overrides =
[
[@"content-inset", CGInsetMake(7.0, 0.0, 7.0, 0.0)],
[@"content-inset", CGInsetMake(7.0, 0.0, 4.0, 0.0)],
// Placeholder is displayed as regular text, not tokens; requires a different inset.
[@"content-inset", CGInsetMake(9.0, 0.0, 5.0, 2.0), CPTextFieldStatePlaceholder],
[@"content-inset", CGInsetMake(7.0, 5.0, 7.0, 6.0), CPThemeStateBezeled],
[@"content-inset", CGInsetMake(7.0, 5.0, 4.0, 6.0), CPThemeStateBezeled],
[@"content-inset", CGInsetMake(9.0, 7.0, 5.0, 8.0), CPThemeStateBezeled | CPTextFieldStatePlaceholder],
];
@@ -853,12 +853,16 @@ var themedButtonValues = nil,
],
PatternIsHorizontal),
textColor = [CPColor colorWithRed:41.0 / 255.0 green:51.0 / 255.0 blue:64.0 / 255.0 alpha:1.0],
textHighlightedColor = [CPColor whiteColor],
themeValues =
[
[@"bezel-color", bezelColor, CPThemeStateBezeled],
[@"bezel-color", bezelHighlightedColor, CPThemeStateBezeled | CPThemeStateHighlighted],
[@"text-color", [CPColor colorWithRed:41.0 / 255.0 green:51.0 / 255.0 blue:64.0 / 255.0 alpha:1.0]],
[@"text-color", textColor],
[@"text-color", textHighlightedColor, CPThemeStateHighlighted],
[@"bezel-inset", CGInsetMake(0.0, 0.0, 0.0, 0.0), CPThemeStateBezeled],
[@"content-inset", CGInsetMake(1.0, 24.0, 2.0, 16.0), CPThemeStateBezeled],
@@ -47,6 +47,20 @@ var STATES = ['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorad
[contentView addSubview:tokenFieldB];
var tokenFieldC = [[CPTokenField alloc] initWithFrame:CGRectMake(15, 170, 500, 30)],
labelC = [[CPTextField alloc] initWithFrame:CGRectMake(15, 150, 500, 24)];
[labelC setStringValue:"This token field can't fit all its tokens."];
[contentView addSubview:labelC];
[tokenFieldC setEditable:YES];
[tokenFieldC setPlaceholderString:"Edit me!"];
[tokenFieldC setObjectValue:['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado']];
[tokenFieldC setDelegate:self];
[contentView addSubview:tokenFieldC];
[theWindow orderFront:self];
}