44 CPSegmentSwitchTracking _trackingMode;
46 unsigned _trackingSegment;
47 BOOL _trackingHighlighted;
52 return "segmented-control";
62 @"bezel-inset": CGInsetMakeZero(),
63 @"content-inset": CGInsetMakeZero(),
68 @"divider-thickness": 1.0,
69 @"default-height": 24.0,
73 + (Class)_binderClassForBinding:(
CPString)aBinding
75 if ([
self _isSelectionBinding:aBinding])
76 return [_CPSegmentedControlBinder class];
78 return [
super _binderClassForBinding:aBinding];
81 + (BOOL)_isSelectionBinding:(
CPString)aBinding
83 return (aBinding === CPSelectedIndexBinding || aBinding === CPSelectedLabelBinding || aBinding === CPSelectedTagBinding);
86 - (id)initWithFrame:(CGRect)aRect
95 _selectedSegment = -1;
106 CPLog.warn(
"Binding " + aBinding +
" needs CPSegmentSwitchTrackingSelectOne tracking mode");
108 [
super bind:aBinding toObject:anObject withKeyPath:aKeyPath options:options];
111 - (void)_reverseSetBinding
113 [_CPSegmentedControlBinder reverseSetValueForObject:self];
121 return [_segments[_selectedSegment] tag];
129 - (void)setSegmentCount:(
unsigned)aCount
131 if (_segments.length == aCount)
134 var height = CGRectGetHeight([
self bounds]),
135 dividersBefore = MAX(0, _segments.length - 1),
136 dividersAfter = MAX(0, aCount - 1);
138 if (_segments.length < aCount)
140 for (var index = _segments.length; index < aCount; ++index)
142 _segments[index] = [[_CPSegmentItem alloc] init];
143 _themeStates[index] = CPThemeStateNormal;
146 else if (aCount < _segments.length)
148 _segments.length = aCount;
149 _themeStates.length = aCount;
152 if (_selectedSegment >= _segments.length)
153 _selectedSegment = -1;
155 var thickness = [
self currentValueForThemeAttribute:@"divider-thickness"],
156 frame = [
self frame],
157 widthOfAllSegments = 0,
158 dividerExtraSpace = ([_segments count] - 1) * thickness;
160 for (var i = 0; i < [_segments count]; i++)
161 widthOfAllSegments += [_segments[i]
width];
163 [
self setFrameSize:CGSizeMake(widthOfAllSegments + dividerExtraSpace, frame.size.height)];
171 - (unsigned)segmentCount
173 return _segments.length;
182 - (void)setSelectedSegment:(
unsigned)aSegment
191 - (unsigned)selectedSegment
193 return _selectedSegment;
199 - (BOOL)selectSegmentWithTag:(
int)aTag
203 for (; index < _segments.length; ++index)
204 if (_segments[index].
tag == aTag)
214 - (BOOL)_selectSegmentWithLabel:(
CPString)aLabel
218 for (; index < _segments.length; ++index)
219 if (_segments[index].
label == aLabel)
221 [
self setSelectedSegment:index];
236 - (void)setTrackingMode:(CPSegmentSwitchTracking)aTrackingMode
238 if (_trackingMode == aTrackingMode)
241 _trackingMode = aTrackingMode;
248 for (; index < _segments.length; ++index)
260 for (; index < _segments.length; ++index)
269 - (CPSegmentSwitchTracking)trackingMode
271 return _trackingMode;
281 - (void)setWidth:(
float)aWidth forSegment:(
unsigned)aSegment
283 [_segments[aSegment] setWidth:aWidth];
293 - (float)widthForSegment:(
unsigned)aSegment
295 return [_segments[aSegment] width];
304 - (void)setImage:(
CPImage)anImage forSegment:(
unsigned)aSegment
306 [_segments[aSegment] setImage:anImage];
318 return [_segments[aSegment] image];
327 - (void)setLabel:(
CPString)aLabel forSegment:(
unsigned)aSegment
329 [_segments[aSegment] setLabel:aLabel];
341 return [_segments[aSegment] label];
350 - (void)setMenu:(
CPMenu)aMenu forSegment:(
unsigned)aSegment
352 [_segments[aSegment] setMenu:aMenu];
360 - (
CPMenu)menuForSegment:(
unsigned)aSegment
362 return [_segments[aSegment] menu];
372 - (void)setSelected:(BOOL)isSelected forSegment:(
unsigned)aSegment
374 var segment = _segments[aSegment];
377 if ([segment
selected] == isSelected)
380 [segment setSelected:isSelected];
382 _themeStates[aSegment] = isSelected ? CPThemeStateSelected : CPThemeStateNormal;
387 var oldSelectedSegment = _selectedSegment;
389 _selectedSegment = aSegment;
393 [_segments[oldSelectedSegment] setSelected:NO];
394 _themeStates[oldSelectedSegment] = CPThemeStateNormal;
403 [
self setNeedsLayout];
404 [
self setNeedsDisplay:YES];
412 - (BOOL)isSelectedForSegment:(
unsigned)aSegment
414 return [_segments[aSegment] selected];
423 - (void)setEnabled:(BOOL)shouldBeEnabled forSegment:(
unsigned)aSegment
425 if ([_segments[aSegment]
enabled] === shouldBeEnabled)
428 [_segments[aSegment] setEnabled:shouldBeEnabled];
431 _themeStates[aSegment] &= ~CPThemeStateDisabled;
433 _themeStates[aSegment] |= CPThemeStateDisabled;
435 [
self setNeedsLayout];
436 [
self setNeedsDisplay:YES];
444 - (BOOL)isEnabledForSegment:(
unsigned)aSegment
446 return [_segments[aSegment] enabled];
454 - (void)setTag:(
int)aTag forSegment:(
unsigned)aSegment
456 [_segments[aSegment] setTag:aTag];
463 - (int)tagForSegment:(
unsigned)aSegment
465 return [_segments[aSegment] tag];
474 - (void)drawSegmentBezel:(
int)aSegment highlight:(BOOL)shouldHighlight
477 _themeStates[aSegment] |= CPThemeStateHighlighted;
479 _themeStates[aSegment] &= ~CPThemeStateHighlighted;
481 [
self setNeedsLayout];
482 [
self setNeedsDisplay:YES];
485 - (float)_leftOffsetForSegment:(
unsigned)segment
487 var bezelInset = [
self currentValueForThemeAttribute:@"bezel-inset"];
490 return bezelInset.left;
492 var thickness = [
self currentValueForThemeAttribute:@"divider-thickness"];
494 return [
self _leftOffsetForSegment:segment - 1] + [self widthForSegment:segment - 1] + thickness;
497 - (unsigned)_indexOfLastSegment
499 var lastSegmentIndex = [_segments count] - 1;
501 if (lastSegmentIndex < 0)
502 lastSegmentIndex = 0;
504 return lastSegmentIndex;
507 - (CGRect)rectForEphemeralSubviewNamed:(
CPString)aName
509 var height = [
self currentValueForThemeAttribute:@"default-height"],
510 contentInset = [
self currentValueForThemeAttribute:@"content-inset"],
511 bezelInset = [
self currentValueForThemeAttribute:@"bezel-inset"],
512 bounds = [
self bounds];
514 if (aName ===
"left-segment-bezel")
516 return CGRectMake(bezelInset.left, bezelInset.top, contentInset.left, height);
518 else if (aName ===
"right-segment-bezel")
520 return CGRectMake(CGRectGetWidth([
self bounds]) - contentInset.right,
525 else if (aName.indexOf(
"segment-bezel") === 0)
527 var segment = parseInt(aName.substring(
"segment-bezel-".length), 10),
528 frame = CGRectCreateCopy([_segments[segment]
frame]);
532 frame.origin.x += contentInset.left;
533 frame.size.width -= contentInset.left;
536 if (segment === _segments.length - 1)
537 frame.size.width = CGRectGetWidth([
self bounds]) - contentInset.right - frame.origin.x;
541 else if (aName.indexOf(
"divider-bezel") === 0)
543 var segment = parseInt(aName.substring(
"divider-bezel-".length), 10),
544 width = [
self widthForSegment:segment],
545 left = [
self _leftOffsetForSegment:segment],
546 thickness = [
self currentValueForThemeAttribute:
@"divider-thickness"];
548 return CGRectMake(left +
width, bezelInset.top, thickness, height);
550 else if (aName.indexOf(
"segment-content") === 0)
552 var segment = parseInt(aName.substring(
"segment-content-".length), 10);
557 return [
super rectForEphemeralSubviewNamed:aName];
562 if ([aName hasPrefix:
@"segment-content"])
563 return [[_CPImageAndTextView alloc] initWithFrame:CGRectMakeZero()];
565 return [[
CPView alloc] initWithFrame:CGRectMakeZero()];
568 - (void)layoutSubviews
570 if (_segments.length <= 0)
573 var themeState = _themeStates[0];
575 themeState |= _themeState & CPThemeStateDisabled;
577 var leftCapColor = [
self valueForThemeAttribute:@"left-segment-bezel-color"
580 leftBezelView = [
self layoutEphemeralSubviewNamed:@"left-segment-bezel"
581 positioned:CPWindowBelow
582 relativeToEphemeralSubviewNamed:nil];
584 [leftBezelView setBackgroundColor:leftCapColor];
586 var themeState = _themeStates[_themeStates.length - 1];
588 themeState |= _themeState & CPThemeStateDisabled;
590 var rightCapColor = [
self valueForThemeAttribute:@"right-segment-bezel-color"
593 rightBezelView = [
self layoutEphemeralSubviewNamed:@"right-segment-bezel"
594 positioned:CPWindowBelow
595 relativeToEphemeralSubviewNamed:nil];
597 [rightBezelView setBackgroundColor:rightCapColor];
599 for (var i = 0, count = _themeStates.length; i < count; i++)
601 var themeState = _themeStates[i];
603 themeState |= _themeState & CPThemeStateDisabled;
605 var bezelColor = [
self valueForThemeAttribute:@"center-segment-bezel-color"
608 bezelView = [
self layoutEphemeralSubviewNamed:"segment-bezel-" + i
609 positioned:CPWindowBelow
610 relativeToEphemeralSubviewNamed:nil];
612 [bezelView setBackgroundColor:bezelColor];
616 var segment = _segments[i],
617 contentView = [
self layoutEphemeralSubviewNamed:@"segment-content-" + i
618 positioned:CPWindowAbove
619 relativeToEphemeralSubviewNamed:@"segment-bezel-" + i];
621 [contentView setText:[segment label]];
622 [contentView setImage:[segment image]];
624 [contentView setFont:[
self valueForThemeAttribute:@"font" inState:themeState]];
625 [contentView setTextColor:[
self valueForThemeAttribute:@"text-color" inState:themeState]];
626 [contentView setAlignment:[
self valueForThemeAttribute:@"alignment" inState:themeState]];
627 [contentView setVerticalAlignment:[
self valueForThemeAttribute:@"vertical-alignment" inState:themeState]];
628 [contentView setLineBreakMode:[
self valueForThemeAttribute:@"line-break-mode" inState:themeState]];
629 [contentView setTextShadowColor:[
self valueForThemeAttribute:@"text-shadow-color" inState:themeState]];
630 [contentView setTextShadowOffset:[
self valueForThemeAttribute:@"text-shadow-offset" inState:themeState]];
631 [contentView setImageScaling:[
self valueForThemeAttribute:@"image-scaling" inState:themeState]];
633 if ([segment image] && [segment
label])
634 [contentView setImagePosition:[
self valueForThemeAttribute:@"image-position" inState:themeState]];
635 else if ([segment image])
636 [contentView setImagePosition:CPImageOnly];
641 var borderState = _themeStates[i] | _themeStates[i + 1];
643 borderState = (borderState & CPThemeStateSelected & ~CPThemeStateHighlighted) ? CPThemeStateSelected : CPThemeStateNormal;
645 borderState |= _themeState & CPThemeStateDisabled;
647 var borderColor = [
self valueForThemeAttribute:@"divider-bezel-color"
648 inState:borderState],
650 borderView = [
self layoutEphemeralSubviewNamed:"divider-bezel-" + i
651 positioned:CPWindowBelow
652 relativeToEphemeralSubviewNamed:nil];
654 [borderView setBackgroundColor:borderColor];
664 - (void)drawSegment:(
int)aSegment highlight:(BOOL)shouldHighlight
668 - (void)tileWithChangedSegment:(
unsigned)aSegment
670 if (aSegment >= _segments.length)
673 var segment = _segments[aSegment],
674 segmentWidth = [segment width],
675 themeState = _themeStates[aSegment] | (_themeState & CPThemeStateDisabled),
676 contentInset = [
self valueForThemeAttribute:
@"content-inset" inState:themeState],
681 if ([segment image] && [segment
label])
682 segmentWidth = [[segment label] sizeWithFont:font].width + [[segment image] size].width + contentInset.left + contentInset.right;
683 else if (segment.image)
684 segmentWidth = [[segment image] size].width + contentInset.left + contentInset.right;
685 else if (segment.label)
686 segmentWidth = [[segment label] sizeWithFont:font].width + contentInset.left + contentInset.right;
691 var delta = segmentWidth - CGRectGetWidth([segment
frame]);
695 [
self setNeedsLayout];
696 [
self setNeedsDisplay:YES];
702 var
frame = [
self frame];
704 [
self setFrameSize:CGSizeMake(CGRectGetWidth(frame) + delta, CGRectGetHeight(frame))];
707 [segment setWidth:segmentWidth];
711 var index = aSegment + 1;
713 for (; index < _segments.length; ++index)
715 [_segments[index] frame].origin.x += delta;
724 [
self setNeedsLayout];
725 [
self setNeedsDisplay:YES];
732 - (CGRect)frameForSegment:(
unsigned)aSegment
737 - (CGRect)bezelFrameForSegment:(
unsigned)aSegment
739 var height = [
self currentValueForThemeAttribute:@"default-height"],
740 bezelInset = [
self currentValueForThemeAttribute:@"bezel-inset"],
742 left = [
self _leftOffsetForSegment:aSegment];
744 return CGRectMake(left, bezelInset.top,
width, height);
747 - (CGRect)contentFrameForSegment:(
unsigned)aSegment
749 var height = [
self currentValueForThemeAttribute:@"default-height"],
750 contentInset = [
self currentValueForThemeAttribute:@"content-inset"],
752 left = [
self _leftOffsetForSegment:aSegment];
754 return CGRectMake(left + contentInset.left, contentInset.top,
width - contentInset.left - contentInset.right, height - contentInset.top - contentInset.bottom);
762 - (unsigned)testSegment:(CGPoint)aPoint
764 var location = [
self convertPoint:aPoint fromView:nil],
765 count = _segments.length;
768 if (CGRectContainsPoint([_segments[count]
frame], aPoint))
771 if (_segments.length)
773 var adjustedLastFrame = CGRectCreateCopy([_segments[_segments.length - 1]
frame]);
774 adjustedLastFrame.size.width = CGRectGetWidth([
self bounds]) - adjustedLastFrame.origin.x;
776 if (CGRectContainsPoint(adjustedLastFrame, aPoint))
777 return _segments.length - 1;
785 if (![
self isEnabled])
802 var type = [anEvent type],
803 location = [
self convertPoint:[anEvent locationInWindow] fromView:nil];
807 if (_trackingSegment == -1)
810 if (_trackingSegment === [
self testSegment:location])
817 _selectedSegment = _trackingSegment;
828 _selectedSegment = -1;
834 _trackingSegment = -1;
842 if (trackingSegment > -1 && [
self isEnabledForSegment:trackingSegment])
844 _trackingHighlighted = YES;
845 _trackingSegment = trackingSegment;
852 if (_trackingSegment == -1)
855 var highlighted = [
self testSegment:location] === _trackingSegment;
857 if (highlighted != _trackingHighlighted)
859 _trackingHighlighted = highlighted;
865 [CPApp setTarget:self selector:@selector(trackSegment:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
890 var
frame = [
self frame],
891 originalWidth = frame.size.width;
893 frame.size.width = 0;
895 [
self setFrame:frame];
897 _segments = [aCoder decodeObjectForKey:CPSegmentedControlSegmentsKey];
898 _segmentStyle = [aCoder decodeIntForKey:CPSegmentedControlSegmentStyleKey];
902 _selectedSegment = [aCoder decodeIntForKey:CPSegmentedControlSelectedKey];
904 _selectedSegment = -1;
907 _trackingMode = [aCoder decodeIntForKey:CPSegmentedControlTrackingModeKey];
913 for (var i = 0; i < _segments.length; i++)
915 _themeStates[i] = [_segments[i] selected] ? CPThemeStateSelected : CPThemeStateNormal;
919 var difference = MAX(originalWidth - [
self frame].size.width, 0.0),
920 remainingWidth = FLOOR(difference / _segments.length);
922 for (var i = 0; i < _segments.length; i++)
923 [
self setWidth:[_segments[i]
width] + remainingWidth forSegment:i];
935 [aCoder encodeObject:_segments forKey:CPSegmentedControlSegmentsKey];
936 [aCoder encodeInt:_selectedSegment forKey:CPSegmentedControlSelectedKey];
937 [aCoder encodeInt:_segmentStyle forKey:CPSegmentedControlSegmentStyleKey];
938 [aCoder encodeInt:_trackingMode forKey:CPSegmentedControlTrackingModeKey];
946 @implementation _CPSegmentedControlBinder :
CPBinder
951 + (void)reverseSetValueForObject:(
id)aSource
954 [binder reverseSetValueFor:[binder selectionBinding]];
959 self = [
super initWithBinding:aBinding name:aName to:aDestination keyPath:aKeyPath options:options from:aSource];
964 _selectionBinding = aName;
970 - (void)_updatePlaceholdersWithOptions:(
CPDictionary)options
972 [
super _updatePlaceholdersWithOptions:options];
974 [
self _setPlaceholder:CPSegmentedControlNoSelectionPlaceholder forMarker:CPMultipleValuesMarker isDefault:YES];
975 [
self _setPlaceholder:CPSegmentedControlNoSelectionPlaceholder forMarker:CPNoSelectionMarker isDefault:YES];
976 [
self _setPlaceholder:CPSegmentedControlNoSelectionPlaceholder forMarker:CPNotApplicableMarker isDefault:YES];
977 [
self _setPlaceholder:CPSegmentedControlNoSelectionPlaceholder forMarker:CPNullMarker isDefault:YES];
980 - (void)setPlaceholderValue:(
id)aValue withMarker:(
CPString)aMarker forBinding:(
CPString)aBinding
983 [_source setSelected:NO forSegment:[_source selectedSegment]];
985 [
self setValue:aValue forBinding:aBinding];
988 - (void)setValue:(
id)aValue forBinding:(
CPString)aBinding
990 if (aBinding == CPSelectedIndexBinding)
991 [_source setSelectedSegment:aValue];
992 else if (aBinding == CPSelectedTagBinding)
993 [_source selectSegmentWithTag:aValue];
994 else if (aBinding == CPSelectedLabelBinding)
995 [_source _selectSegmentWithLabel:aValue];
998 - (id)valueForBinding:(
CPString)aBinding
1000 var selectedIndex = [_source selectedSegment];
1002 if (aBinding == CPSelectedIndexBinding)
1003 return selectedIndex;
1004 else if (aBinding == CPSelectedTagBinding)
1005 return [_source tagForSegment:selectedIndex];
1006 else if (aBinding == CPSelectedLabelBinding)
1007 return [_source labelForSegment:selectedIndex];
1012 @implementation _CPSegmentItem :
CPObject
1027 if (
self = [super init])
1037 frame = CGRectMakeZero();
1052 @implementation _CPSegmentItem (CPCoding)
1054 - (id)initWithCoder:(
CPCoder)aCoder
1056 self = [
super init];
1060 image = [aCoder decodeObjectForKey:CPSegmentItemImageKey];
1061 label = [aCoder decodeObjectForKey:CPSegmentItemLabelKey];
1062 menu = [aCoder decodeObjectForKey:CPSegmentItemMenuKey];
1063 selected = [aCoder decodeBoolForKey:CPSegmentItemSelectedKey];
1064 enabled = [aCoder decodeBoolForKey:CPSegmentItemEnabledKey];
1065 tag = [aCoder decodeIntForKey:CPSegmentItemTagKey];
1066 width = [aCoder decodeFloatForKey:CPSegmentItemWidthKey];
1068 frame = CGRectMakeZero();
1074 - (void)encodeWithCoder:(
CPCoder)aCoder
1076 [aCoder encodeObject:image forKey:CPSegmentItemImageKey];
1077 [aCoder encodeObject:label forKey:CPSegmentItemLabelKey];
1078 [aCoder encodeObject:menu forKey:CPSegmentItemMenuKey];
1079 [aCoder encodeBool:selected forKey:CPSegmentItemSelectedKey];
1080 [aCoder encodeBool:enabled forKey:CPSegmentItemEnabledKey];
1081 [aCoder encodeInt:tag forKey:CPSegmentItemTagKey];
1082 [aCoder encodeFloat:width forKey:CPSegmentItemWidthKey];