Skip to content

Commit

Permalink
Token field disclosure arrow for menu.
Browse files Browse the repository at this point in the history
Disclosure arrow behaves like in Cocoa: it shows on hover and if clicked displays the token specific menu.

This change also removes the delete button by default. The delete button can be enabled with `[tokenField setButtonType: CPTokenFieldDeleteButtonType]`.
  • Loading branch information
aljungberg committed Feb 4, 2013
1 parent 0f64616 commit 0229e19
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 23 deletions.
196 changes: 174 additions & 22 deletions AppKit/CPTokenField.j
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@
@import <Foundation/CPIndexSet.j> @import <Foundation/CPIndexSet.j>
@import <Foundation/CPTimer.j> @import <Foundation/CPTimer.j>


@import "_CPAutocompleteMenu.j"
@import "CPButton.j" @import "CPButton.j"
@import "CPPopUpButton.j"
@import "CPScrollView.j" @import "CPScrollView.j"
@import "CPTableView.j" @import "CPTableView.j"
@import "CPText.j" @import "CPText.j"
@import "CPTextField.j" @import "CPTextField.j"
@import "CPWindow_Constants.j" @import "CPWindow_Constants.j"
@import "_CPAutocompleteMenu.j"


@global CPApp @global CPApp


Expand All @@ -59,6 +60,9 @@ var CPScrollDestinationNone = 0,
CPScrollDestinationLeft = 1, CPScrollDestinationLeft = 1,
CPScrollDestinationRight = 2; CPScrollDestinationRight = 2;


CPTokenFieldDisclosureButtonType = 0;
CPTokenFieldDeleteButtonType = 1;

@implementation CPTokenField : CPTextField @implementation CPTokenField : CPTextField
{ {
CPScrollView _tokenScrollView; CPScrollView _tokenScrollView;
Expand All @@ -76,6 +80,8 @@ var CPScrollDestinationNone = 0,
CPEvent _mouseDownEvent; CPEvent _mouseDownEvent;


BOOL _shouldNotifyTarget; BOOL _shouldNotifyTarget;

int _buttonType @accessors(property=buttonType);
} }


+ (CPCharacterSet)defaultTokenizingCharacterSet + (CPCharacterSet)defaultTokenizingCharacterSet
Expand Down Expand Up @@ -1089,6 +1095,8 @@ var CPScrollDestinationNone = 0,
tokenFrame = fitAndFrame(tokenViewSize.width, tokenViewSize.height); tokenFrame = fitAndFrame(tokenViewSize.width, tokenViewSize.height);


[tokenView setFrame:tokenFrame]; [tokenView setFrame:tokenFrame];

[tokenView setButtonType:_buttonType];
} }


if (isEditing && !_selectedRange.length && _CPMaxRange(_selectedRange) >= [tokens count]) if (isEditing && !_selectedRange.length && _CPMaxRange(_selectedRange) >= [tokens count])
Expand Down Expand Up @@ -1241,6 +1249,16 @@ var CPScrollDestinationNone = 0,
return aString; return aString;
} }


- (BOOL)_hasMenuForRepresentedObject:(id)aRepresentedObject
{
var delegate = [self delegate];
if ([delegate respondsToSelector:@selector(tokenField:hasMenuForRepresentedObject:)] &&
[delegate respondsToSelector:@selector(tokenField:menuForRepresentedObject:)])
return [delegate tokenField:self hasMenuForRepresentedObject:aRepresentedObject];

return NO;
}

- (CPMenu)_menuForRepresentedObject:(id)aRepresentedObject - (CPMenu)_menuForRepresentedObject:(id)aRepresentedObject
{ {
var delegate = [self delegate]; var delegate = [self delegate];
Expand All @@ -1249,7 +1267,7 @@ var CPScrollDestinationNone = 0,
{ {
var hasMenu = [delegate tokenField:self hasMenuForRepresentedObject:aRepresentedObject]; var hasMenu = [delegate tokenField:self hasMenuForRepresentedObject:aRepresentedObject];
if (hasMenu) if (hasMenu)
return [delegate tokenField:self menuForRepresentedObject:aRepresentedObject]; return [delegate tokenField:self menuForRepresentedObject:aRepresentedObject] || nil;
} }


return nil; return nil;
Expand Down Expand Up @@ -1279,13 +1297,28 @@ var CPScrollDestinationNone = 0,
[_autocompleteMenu _hideCompletions]; [_autocompleteMenu _hideCompletions];
} }



- (void)setButtonType:(int)aButtonType
{
if (_buttonType === aButtonType)
return;

_buttonType = aButtonType;
[self setNeedsLayout];
}

@end @end


var _CPTokenFieldDisclosureButtonType = 0,
_CPTokenFieldDeleteButtonType = 1;

@implementation _CPTokenFieldToken : CPTextField @implementation _CPTokenFieldToken : CPTextField
{ {
_CPTokenFieldTokenCloseButton _deleteButton; _CPTokenFieldTokenCloseButton _deleteButton;
CPTokenField _tokenField; _CPTokenFieldTokenDisclosureButton _disclosureButton;
id _representedObject; CPTokenField _tokenField;
id _representedObject;
int _buttonType;
} }


+ (CPString)defaultThemeClass + (CPString)defaultThemeClass
Expand All @@ -1302,12 +1335,10 @@ var CPScrollDestinationNone = 0,
{ {
if (self = [super initWithFrame:frame]) if (self = [super initWithFrame:frame])
{ {
_deleteButton = [[_CPTokenFieldTokenCloseButton alloc] initWithFrame:CGRectMakeZero()];
[self addSubview:_deleteButton];

[self setEditable:NO]; [self setEditable:NO];
[self setHighlighted:NO]; [self setHighlighted:NO];
[self setBezeled:YES]; [self setBezeled:YES];
[self setButtonType:CPTokenFieldDisclosureButtonType];
} }


return self; return self;
Expand Down Expand Up @@ -1343,9 +1374,12 @@ var CPScrollDestinationNone = 0,
{ {
var r = [super setThemeState:aState]; var r = [super setThemeState:aState];


// Share hover state with the delete button. // Share hover state with the disclosure and delete buttons.
if (r && aState === CPThemeStateHovered) if (r && aState & CPThemeStateHovered)
{
[_disclosureButton setThemeState:aState];
[_deleteButton setThemeState:aState]; [_deleteButton setThemeState:aState];
}


return r; return r;
} }
Expand All @@ -1354,9 +1388,12 @@ var CPScrollDestinationNone = 0,
{ {
var r = [super unsetThemeState:aState]; var r = [super unsetThemeState:aState];


// Share hover state with the delete button. // Share hover state with the disclosure and delete button.
if (r && aState === CPThemeStateHovered) if (r && aState & CPThemeStateHovered)
{
[_disclosureButton unsetThemeState:aState];
[_deleteButton unsetThemeState:aState]; [_deleteButton unsetThemeState:aState];
}


return r; return r;
} }
Expand All @@ -1374,6 +1411,47 @@ var CPScrollDestinationNone = 0,
return size; return size;
} }


- (void)setButtonType:(int)aButtonType
{
if (_buttonType === aButtonType)
return;

_buttonType = aButtonType;

if (_buttonType === CPTokenFieldDisclosureButtonType)
{
if (_deleteButton)
{
[_deleteButton removeFromSuperview];
_deleteButton = nil;
}

if (!_disclosureButton)
{
_disclosureButton = [[_CPTokenFieldTokenDisclosureButton alloc] initWithFrame:CGRectMakeZero()];
[self addSubview:_disclosureButton];
}
}
else
{
if (_disclosureButton)
{
[_disclosureButton removeFromSuperview];
_disclosureButton = nil;
}

if (!_deleteButton)
{
_deleteButton = [[_CPTokenFieldTokenCloseButton alloc] initWithFrame:CGRectMakeZero()];
[self addSubview:_deleteButton];
[_deleteButton setTarget:self];
[_deleteButton setAction:@selector(_delete:)];
}
}

[self setNeedsLayout];
}

- (void)layoutSubviews - (void)layoutSubviews
{ {
[super layoutSubviews]; [super layoutSubviews];
Expand All @@ -1384,15 +1462,36 @@ var CPScrollDestinationNone = 0,


if (bezelView) if (bezelView)
{ {
[_deleteButton setTarget:self]; switch (_buttonType)
[_deleteButton setAction:@selector(_delete:)]; {
[_deleteButton setEnabled:[self isEditable]]; case CPTokenFieldDisclosureButtonType:
if (!_disclosureButton)
return;
var shouldBeEnabled = [self hasMenu];
[_disclosureButton setHidden:!shouldBeEnabled];

if (shouldBeEnabled)
[_disclosureButton setMenu:[self menu]];


var frame = [bezelView frame], var frame = [bezelView frame],
buttonOffset = [_deleteButton currentValueForThemeAttribute:@"offset"], buttonOffset = [_disclosureButton currentValueForThemeAttribute:@"offset"],
buttonSize = [_deleteButton currentValueForThemeAttribute:@"min-size"]; buttonSize = [_disclosureButton currentValueForThemeAttribute:@"min-size"];


[_deleteButton setFrame:_CGRectMake(CGRectGetMaxX(frame) - buttonOffset.x, CGRectGetMinY(frame) + buttonOffset.y, buttonSize.width, buttonSize.height)]; [_disclosureButton setFrame:_CGRectMake(CGRectGetMaxX(frame) - buttonOffset.x, CGRectGetMinY(frame) + buttonOffset.y, buttonSize.width, buttonSize.height)];
break;
case CPTokenFieldDeleteButtonType:
if (!_deleteButton)
return;

[_deleteButton setEnabled:[self isEditable] && [self isEnabled]];

var frame = [bezelView frame],
buttonOffset = [_deleteButton currentValueForThemeAttribute:@"offset"],
buttonSize = [_deleteButton currentValueForThemeAttribute:@"min-size"];

[_deleteButton setFrame:_CGRectMake(CGRectGetMaxX(frame) - buttonOffset.x, CGRectGetMinY(frame) + buttonOffset.y, buttonSize.width, buttonSize.height)];
break;
}
} }
} }


Expand All @@ -1412,16 +1511,18 @@ var CPScrollDestinationNone = 0,
[_tokenField _deleteToken:self]; [_tokenField _deleteToken:self];
} }


- (BOOL)hasMenu
{
return [_tokenField _hasMenuForRepresentedObject:_representedObject];
}

- (CPMenu)menu - (CPMenu)menu
{ {
return [_tokenField _menuForRepresentedObject:_representedObject]; return [_tokenField _menuForRepresentedObject:_representedObject];
} }


@end @end


/*
Theming hook.
*/
@implementation _CPTokenFieldTokenCloseButton : CPButton @implementation _CPTokenFieldTokenCloseButton : CPButton
{ {
} }
Expand Down Expand Up @@ -1452,6 +1553,57 @@ var CPScrollDestinationNone = 0,


@end @end


@implementation _CPTokenFieldTokenDisclosureButton : CPPopUpButton
{
}

+ (id)themeAttributes
{
var attributes = [CPButton themeAttributes];

[attributes setObject:_CGPointMake(15, 5) forKey:@"offset"];

return attributes;
}

+ (CPString)defaultThemeClass
{
return "tokenfield-token-disclosure-button";
}

- (id)initWithFrame:(CGRect)aFrame
{
if (self = [self initWithFrame:aFrame pullsDown:YES])
{
[self setBordered:YES];
[super setTitle:@""];
}

return self;
}

- (void)setTitle:(CPString)aTitle
{
// skip
}

- (void)synchronizeTitleAndSelectedItem
{
// skip
}

- (void)mouseEntered:(CPEvent)anEvent
{
// Don't toggle hover state from within the button - we use the hover state of the token field as a whole.
}

- (void)mouseExited:(CPEvent)anEvent
{
// Don't toggle hover state from within the button - we use the hover state of the token field as a whole.
}

@end



var CPTokenFieldTokenizingCharacterSetKey = "CPTokenFieldTokenizingCharacterSetKey", var CPTokenFieldTokenizingCharacterSetKey = "CPTokenFieldTokenizingCharacterSetKey",
CPTokenFieldCompletionDelayKey = "CPTokenFieldCompletionDelay"; CPTokenFieldCompletionDelayKey = "CPTokenFieldCompletionDelay";
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions AppKit/Themes/Aristo/ThemeDescriptors.j
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -1174,6 +1174,32 @@ var themedButtonValues = nil,
return token; return token;
} }


+ (_CPTokenFieldTokenDisclosureButton)themedTokenFieldDisclosureButton
{
var button = [[_CPTokenFieldTokenDisclosureButton alloc] initWithFrame:CGRectMake(0, 0, 9, 9)],

arrowImage = PatternColor("token-disclosure.png", 7.0, 6.0),
arrowImageHiglighted = PatternColor("token-disclosure-highlighted.png", 7.0, 6.0),

themeValues =
[
[@"content-inset", CGInsetMake(0.0, 0.0, 0.0, 0.0), CPThemeStateNormal],

[@"bezel-color", nil, CPThemeStateBordered],
[@"bezel-color", arrowImage, CPThemeStateBordered | CPThemeStateHovered],
[@"bezel-color", arrowImageHiglighted, CPThemeStateBordered | CPThemeStateHovered | CPThemeStateHighlighted],

[@"min-size", CGSizeMake(7.0, 6.0)],
[@"max-size", CGSizeMake(7.0, 6.0)],

[@"offset", CGPointMake(17, 7)]
];

[self registerThemeValues:themeValues forView:button];

return button;
}

+ (_CPTokenFieldTokenCloseButton)themedTokenFieldTokenCloseButton + (_CPTokenFieldTokenCloseButton)themedTokenFieldTokenCloseButton
{ {
var button = [[_CPTokenFieldTokenCloseButton alloc] initWithFrame:CGRectMake(0, 0, 9, 9)], var button = [[_CPTokenFieldTokenCloseButton alloc] initWithFrame:CGRectMake(0, 0, 9, 9)],
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions AppKit/Themes/Aristo2/ThemeDescriptors.j
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -723,6 +723,32 @@ var themedButtonValues = nil,
return token; return token;
} }


+ (_CPTokenFieldTokenDisclosureButton)themedTokenFieldDisclosureButton
{
var button = [[_CPTokenFieldTokenDisclosureButton alloc] initWithFrame:CGRectMake(0, 0, 9, 9)],

arrowImage = PatternColor("token-disclosure.png", 7.0, 6.0),
arrowImageHiglighted = PatternColor("token-disclosure-highlighted.png", 7.0, 6.0),

themeValues =
[
[@"content-inset", CGInsetMake(0.0, 0.0, 0.0, 0.0), CPThemeStateNormal],

[@"bezel-color", nil, CPThemeStateBordered],
[@"bezel-color", arrowImage, CPThemeStateBordered | CPThemeStateHovered],
[@"bezel-color", arrowImageHiglighted, CPThemeStateBordered | CPThemeStateHovered | CPThemeStateHighlighted],

[@"min-size", CGSizeMake(7.0, 6.0)],
[@"max-size", CGSizeMake(7.0, 6.0)],

[@"offset", CGPointMake(17, 7)]
];

[self registerThemeValues:themeValues forView:button];

return button;
}

+ (_CPTokenFieldTokenCloseButton)themedTokenFieldTokenCloseButton + (_CPTokenFieldTokenCloseButton)themedTokenFieldTokenCloseButton
{ {
var button = [[_CPTokenFieldTokenCloseButton alloc] initWithFrame:CGRectMake(0, 0, 9, 9)], var button = [[_CPTokenFieldTokenCloseButton alloc] initWithFrame:CGRectMake(0, 0, 9, 9)],
Expand Down
Loading

0 comments on commit 0229e19

Please sign in to comment.