27 @
typedef CPSegmentSwitchTracking
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,
72 - (id)initWithFrame:(CGRect)aRect
81 _selectedSegment = -1;
84 _trackingHighlighted = NO;
85 _trackingSegment = -1;
95 - (void)setControlSize:(CPControlSize)aControlSize
98 [
self _sizeToControlSize];
106 return [[_segments objectAtIndex:_selectedSegment] tag];
110 - (void)setSegments:(CPArray)segments
118 - (void)insertSegments:(CPArray)segments atIndexes:(
CPIndexSet)indices
124 count = [indices
count];
127 [newStates addObject:CPThemeStateNormal];
129 [_segments insertObjects:segments atIndexes:indices];
130 [_themeStates insertObjects:newStates atIndexes:indices];
132 if (_selectedSegment >= [indices firstIndex])
133 _selectedSegment += [indices
count];
139 if ([indices count] == 0)
144 [[_segments objectAtIndex:idx] setSelected:NO];
147 if ([indices containsIndex:_selectedSegment])
148 _selectedSegment = -1;
149 else if ([indices lastIndex] < _selectedSegment)
150 _selectedSegment -= [indices
count];
152 [_segments removeObjectsAtIndexes:indices];
153 [_themeStates removeObjectsAtIndexes:indices];
161 - (void)setSegmentCount:(
unsigned)aCount
163 var prevCount = [_segments count];
165 if (aCount == prevCount)
168 if (aCount > prevCount)
170 var count = aCount - prevCount,
174 [segments addObject:[[_CPSegmentItem alloc] init]];
181 [
self _updateSelectionIfNeeded];
185 - (void)_updateSelectionIfNeeded
188 _selectedSegment = -1;
196 return [_segments count];
205 - (void)setSelectedSegment:(
unsigned)aSegment
208 if (_selectedSegment == aSegment)
218 _selectedSegment = -1;
229 return _selectedSegment;
235 - (BOOL)selectSegmentWithTag:(
int)aTag
239 for (; index < [_segments count]; ++index)
240 if ([[_segments objectAtIndex:index]
tag] == aTag)
250 - (BOOL)_selectSegmentWithLabel:(
CPString)aLabel
254 for (; index < [_segments count]; ++index)
255 if ([[_segments objectAtIndex:index]
label] == aLabel)
272 - (void)setTrackingMode:(CPSegmentSwitchTracking)aTrackingMode
274 if (_trackingMode == aTrackingMode)
277 _trackingMode = aTrackingMode;
307 return _trackingMode;
317 - (void)setWidth:(
float)aWidth forSegment:(
unsigned)aSegment
319 [[_segments objectAtIndex:aSegment] setWidth:aWidth];
328 - (float)widthForSegment:(
unsigned)aSegment
330 return [[_segments objectAtIndex:aSegment] width];
339 - (void)setImage:(
CPImage)anImage forSegment:(
unsigned)aSegment
341 [[_segments objectAtIndex:aSegment] setImage:anImage];
353 return [[_segments objectAtIndex:aSegment] image];
362 - (void)setLabel:(
CPString)aLabel forSegment:(
unsigned)aSegment
364 [[_segments objectAtIndex:aSegment] setLabel:aLabel];
376 return [[_segments objectAtIndex:aSegment] label];
385 - (void)setMenu:(
CPMenu)aMenu forSegment:(
unsigned)aSegment
387 [[_segments objectAtIndex:aSegment] setMenu:aMenu];
395 - (
CPMenu)menuForSegment:(
unsigned)aSegment
397 return [[_segments objectAtIndex:aSegment] menu];
407 - (void)setSelected:(BOOL)isSelected forSegment:(
unsigned)aSegment
409 var segment = [_segments objectAtIndex:aSegment];
412 if ([segment
selected] == isSelected)
415 [segment setSelected:isSelected];
422 var oldSelectedSegment = _selectedSegment;
424 _selectedSegment = aSegment;
426 if (_trackingMode ==
CPSegmentSwitchTrackingSelectOne && oldSelectedSegment != aSegment && oldSelectedSegment != -1 && oldSelectedSegment < _segments.length)
428 [_segments[oldSelectedSegment] setSelected:NO];
429 _themeStates[oldSelectedSegment] = CPThemeStateNormal;
447 - (BOOL)isSelectedForSegment:(
unsigned)aSegment
449 return [[_segments objectAtIndex:aSegment] selected];
458 - (void)setEnabled:(BOOL)shouldBeEnabled forSegment:(
unsigned)aSegment
460 var segment = [_segments objectAtIndex:aSegment];
462 if ([segment
enabled] === shouldBeEnabled)
465 [segment setEnabled:shouldBeEnabled];
481 - (BOOL)isEnabledForSegment:(
unsigned)aSegment
483 return [[_segments objectAtIndex:aSegment] enabled];
491 - (void)setTag:(
int)aTag forSegment:(
unsigned)aSegment
493 [[_segments objectAtIndex:aSegment] setTag:aTag];
500 - (int)tagForSegment:(
unsigned)aSegment
502 return [[_segments objectAtIndex:aSegment] tag];
511 - (void)drawSegmentBezel:(
int)aSegment highlight:(BOOL)shouldHighlight
513 if (aSegment < _themeStates.length)
525 - (float)_leftOffsetForSegment:(
unsigned)segment
528 return [
self currentValueForThemeAttribute:@"bezel-inset"].left;
530 var thickness = [
self currentValueForThemeAttribute:@"divider-thickness"];
532 return [
self _leftOffsetForSegment:segment - 1] + CGRectGetWidth([
self frameForSegment:segment - 1]) + thickness;
535 - (unsigned)_indexOfLastSegment
537 var lastSegmentIndex = [_segments count] - 1;
539 if (lastSegmentIndex < 0)
540 lastSegmentIndex = 0;
542 return lastSegmentIndex;
545 - (CGRect)rectForEphemeralSubviewNamed:(
CPString)aName
547 var height = [
self currentValueForThemeAttribute:@"min-size"].height,
548 contentInset = [
self currentValueForThemeAttribute:@"content-inset"],
549 bezelInset = [
self currentValueForThemeAttribute:@"bezel-inset"],
552 if (aName ===
"left-segment-bezel")
554 return CGRectMake(bezelInset.left, bezelInset.top, contentInset.left, height);
556 else if (aName ===
"right-segment-bezel")
558 return CGRectMake(CGRectGetWidth([
self bounds]) - contentInset.right,
563 else if (aName.indexOf(
"segment-bezel") === 0)
565 var segment = parseInt(aName.substring(
"segment-bezel-".length), 10),
566 frame = CGRectCreateCopy([
self frameForSegment:segment]);
570 frame.origin.x += contentInset.left;
571 frame.size.width -= contentInset.left;
575 frame.size.width = CGRectGetWidth([
self bounds]) - contentInset.right -
frame.origin.x;
579 else if (aName.indexOf(
"divider-bezel") === 0)
581 var segment = parseInt(aName.substring(
"divider-bezel-".length), 10),
582 width = CGRectGetWidth([
self frameForSegment:segment]),
583 left = [
self _leftOffsetForSegment:segment],
584 thickness = [
self currentValueForThemeAttribute:@"divider-thickness"];
586 return CGRectMake(left +
width, bezelInset.top, thickness, height);
588 else if (aName.indexOf(
"segment-content") === 0)
590 var segment = parseInt(aName.substring(
"segment-content-".length), 10);
600 if ([aName hasPrefix:
@"segment-content"])
602 var view = [[_CPImageAndTextView alloc] initWithFrame:CGRectMakeZero()];
603 [view _setUsesSingleLineMode:YES];
608 return [[
CPView alloc] initWithFrame:CGRectMakeZero()];
616 var themeState = _themeStates[0],
617 isDisabled = [
self hasThemeState:CPThemeStateDisabled],
618 isControlSizeSmall = [
self hasThemeState:CPThemeStateControlSizeSmall],
619 isControlSizeMini = [
self hasThemeState:CPThemeStateControlSizeMini];
623 if (isControlSizeSmall)
625 else if (isControlSizeMini)
628 var leftCapColor = [
self valueForThemeAttribute:@"left-segment-bezel-color"
635 [leftBezelView setBackgroundColor:leftCapColor];
637 var themeState = _themeStates[_themeStates.length - 1];
641 if (isControlSizeSmall)
643 else if (isControlSizeMini)
646 var rightCapColor = [
self valueForThemeAttribute:@"right-segment-bezel-color"
653 [rightBezelView setBackgroundColor:rightCapColor];
655 for (var i = 0, count = _themeStates.length; i < count; i++)
657 var themeState = _themeStates[i];
661 if (isControlSizeSmall)
663 else if (isControlSizeMini)
666 var bezelColor = [
self valueForThemeAttribute:@"center-segment-bezel-color"
673 [bezelView setBackgroundColor:bezelColor];
676 var segment = _segments[i],
681 [contentView setText:[segment label]];
682 [contentView setImage:[segment image]];
684 [contentView setFont:[
self valueForThemeAttribute:@"font" inState:themeState]];
685 [contentView setTextColor:[
self valueForThemeAttribute:@"text-color" inState:themeState]];
686 [contentView setAlignment:[
self valueForThemeAttribute:@"alignment" inState:themeState]];
687 [contentView setVerticalAlignment:[
self valueForThemeAttribute:@"vertical-alignment" inState:themeState]];
688 [contentView setLineBreakMode:[
self valueForThemeAttribute:@"line-break-mode" inState:themeState]];
689 [contentView setTextShadowColor:[
self valueForThemeAttribute:@"text-shadow-color" inState:themeState]];
690 [contentView setTextShadowOffset:[
self valueForThemeAttribute:@"text-shadow-offset" inState:themeState]];
691 [contentView setImageScaling:[
self valueForThemeAttribute:@"image-scaling" inState:themeState]];
693 if ([segment image] && [segment
label])
694 [contentView setImagePosition:[
self valueForThemeAttribute:@"image-position" inState:themeState]];
695 else if ([segment image])
696 [contentView setImagePosition:CPImageOnly];
702 var borderState = _themeStates[i].and(_themeStates[i + 1]);
706 if (isControlSizeSmall)
708 else if (isControlSizeMini)
711 var borderColor = [
self valueForThemeAttribute:@"divider-bezel-color"
712 inState:borderState],
718 [borderView setBackgroundColor:borderColor];
728 - (void)drawSegment:(
int)aSegment highlight:(BOOL)shouldHighlight
739 - (void)tileWithChangedSegment:(CPInteger)aSegment
753 [_segments[i] setFrame:CGRectMakeZero()];
755 width = CGRectGetMaxX([
self frameForSegment:(segmentCount - 1)]);
768 - (CGRect)frameForSegment:(
unsigned)aSegment
770 var segment = [_segments objectAtIndex:aSegment],
771 frame = [segment frame];
773 if (CGRectEqualToRect(
frame, CGRectMakeZero()))
776 [segment setFrame:frame];
782 - (CGRect)bezelFrameForSegment:(
unsigned)aSegment
784 var left = [
self _leftOffsetForSegment:aSegment],
785 top = [
self currentValueForThemeAttribute:@"bezel-inset"].top,
787 height = [
self currentValueForThemeAttribute:@"min-size"].height;
792 contentInset = [
self valueForThemeAttribute:@"content-inset" inState:themeState],
793 contentInsetWidth = contentInset.left + contentInset.right,
795 segment = _segments[aSegment],
796 label = [segment label],
797 image = [segment image];
799 width = (label ? [label sizeWithFont:[
self font]].width : 4.0) + (image ? [image size].width : 0) + contentInsetWidth;
802 return CGRectMake(left, top,
width, height);
805 - (CGRect)contentFrameForSegment:(
unsigned)aSegment
807 var height = [
self currentValueForThemeAttribute:@"min-size"].height,
808 contentInset = [
self currentValueForThemeAttribute:@"content-inset"],
809 width = CGRectGetWidth([
self frameForSegment:aSegment]),
810 left = [
self _leftOffsetForSegment:aSegment];
812 return CGRectMake(left + contentInset.left, contentInset.top,
width - contentInset.left - contentInset.right, height - contentInset.top - contentInset.bottom);
815 - (CGSize)_minimumFrameSize
818 return CGSizeMake(CGRectGetWidth([
self frame]), [
self currentValueForThemeAttribute:
@"min-size"].height);
826 - (unsigned)testSegment:(CGPoint)aPoint
832 if (CGRectContainsPoint([
self frameForSegment:count], aPoint))
837 var adjustedLastFrame = CGRectCreateCopy([
self frameForSegment:(_segments.length - 1)]);
838 adjustedLastFrame.size.width = CGRectGetWidth([
self bounds]) - adjustedLastFrame.origin.x;
840 if (CGRectContainsPoint(adjustedLastFrame, aPoint))
866 var type = [anEvent
type],
871 if (_trackingSegment == -1)
874 if (_trackingSegment === [
self testSegment:location])
881 _selectedSegment = _trackingSegment;
898 _trackingSegment = -1;
906 if (trackingSegment > -1 && [
self isEnabledForSegment:trackingSegment])
908 _trackingHighlighted = YES;
909 _trackingSegment = trackingSegment;
916 if (_trackingSegment == -1)
919 var highlighted = [
self testSegment:location] === _trackingSegment;
921 if (highlighted != _trackingHighlighted)
923 _trackingHighlighted = highlighted;
929 [CPApp setTarget:self selector:@selector(trackSegment:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
955 originalWidth = frame.size.width;
957 frame.size.width = 0;
961 _segments = [aCoder decodeObjectForKey:CPSegmentedControlSegmentsKey];
962 _segmentStyle = [aCoder decodeIntForKey:CPSegmentedControlSegmentStyleKey];
966 _selectedSegment = [aCoder decodeIntForKey:CPSegmentedControlSelectedKey];
971 _trackingMode = [aCoder decodeIntForKey:CPSegmentedControlTrackingModeKey];
981 var thickness = [
self currentValueForThemeAttribute:@"divider-thickness"],
982 dividerExtraSpace = ([_segments count] - 1) * thickness,
983 difference = MAX(originalWidth - [
self frame].size.width - dividerExtraSpace, 0.0),
984 remainingWidth = FLOOR(difference / [
self segmentCount]),
985 widthOfAllSegments = 0;
990 var frame = [_segments[i] frame];
991 frame.size.width += remainingWidth;
993 widthOfAllSegments += CGRectGetWidth(frame);
998 var leftOversPixel = originalWidth - (widthOfAllSegments + dividerExtraSpace);
1003 for (var i = 0; i < leftOversPixel; i++)
1005 [_segments[i] frame].size.width += 1;
1020 [aCoder encodeObject:_segments forKey:CPSegmentedControlSegmentsKey];
1021 [aCoder encodeInt:_selectedSegment forKey:CPSegmentedControlSelectedKey];
1022 [aCoder encodeInt:_segmentStyle forKey:CPSegmentedControlSegmentStyleKey];
1023 [aCoder encodeInt:_trackingMode forKey:CPSegmentedControlTrackingModeKey];
1030 + (Class)_binderClassForBinding:(
CPString)aBinding
1032 if ([
self _isSelectionBinding:aBinding])
1033 return [_CPSegmentedControlBinder class];
1035 return [
super _binderClassForBinding:aBinding];
1038 + (BOOL)_isSelectionBinding:(
CPString)aBinding
1045 return [
self _isSelectionBinding:aBinding];
1052 CPLog.warn(
"Binding " + aBinding +
" needs CPSegmentSwitchTrackingSelectOne tracking mode");
1056 [
super bind:aBinding toObject:anObject withKeyPath:aKeyPath options:options];
1059 - (void)_reverseSetBinding
1061 [_CPSegmentedControlBinder _reverseSetValueFromExclusiveBinderForObject:self];
1067 @implementation _CPSegmentedControlBinder :
CPBinder 1072 - (void)_updatePlaceholdersWithOptions:(
CPDictionary)options
1074 [
super _updatePlaceholdersWithOptions:options];
1076 [
self _setPlaceholder:CPSegmentedControlNoSelectionPlaceholder forMarker:CPMultipleValuesMarker isDefault:YES];
1077 [
self _setPlaceholder:CPSegmentedControlNoSelectionPlaceholder forMarker:CPNoSelectionMarker isDefault:YES];
1078 [
self _setPlaceholder:CPSegmentedControlNoSelectionPlaceholder forMarker:CPNotApplicableMarker isDefault:YES];
1079 [
self _setPlaceholder:CPSegmentedControlNoSelectionPlaceholder forMarker:CPNullMarker isDefault:YES];
1082 - (void)setPlaceholderValue:(
id)aValue withMarker:(
CPString)aMarker forBinding:(
CPString)aBinding
1085 [_source setSelected:NO forSegment:[_source selectedSegment]];
1087 [
self setValue:aValue forBinding:aBinding];
1090 - (void)setValue:(
id)aValue forBinding:(
CPString)aBinding
1093 [_source setSelectedSegment:aValue];
1095 [_source selectSegmentWithTag:aValue];
1097 [_source _selectSegmentWithLabel:aValue];
1100 - (id)valueForBinding:(
CPString)aBinding
1102 var selectedIndex = [_source selectedSegment];
1105 return selectedIndex;
1107 return [_source tagForSegment:selectedIndex];
1109 return [_source labelForSegment:selectedIndex];
1114 @implementation _CPSegmentItem :
CPObject 1129 if (
self = [super
init])
1139 frame = CGRectMakeZero();
1154 @implementation _CPSegmentItem (CPCoding)
1156 - (id)initWithCoder:(
CPCoder)aCoder
1158 self = [
super init];
1162 image = [aCoder decodeObjectForKey:CPSegmentItemImageKey];
1163 label = [aCoder decodeObjectForKey:CPSegmentItemLabelKey];
1164 menu = [aCoder decodeObjectForKey:CPSegmentItemMenuKey];
1165 selected = [aCoder decodeBoolForKey:CPSegmentItemSelectedKey];
1166 enabled = [aCoder decodeBoolForKey:CPSegmentItemEnabledKey];
1167 tag = [aCoder decodeIntForKey:CPSegmentItemTagKey];
1168 width = [aCoder decodeFloatForKey:CPSegmentItemWidthKey];
1170 frame = CGRectMakeZero();
1176 - (void)encodeWithCoder:(
CPCoder)aCoder
1178 [aCoder encodeObject:image forKey:CPSegmentItemImageKey];
1179 [aCoder encodeObject:label forKey:CPSegmentItemLabelKey];
1180 [aCoder encodeObject:menu forKey:CPSegmentItemMenuKey];
1181 [aCoder encodeBool:selected forKey:CPSegmentItemSelectedKey];
1182 [aCoder encodeBool:enabled forKey:CPSegmentItemEnabledKey];
1183 [aCoder encodeInt:tag forKey:CPSegmentItemTagKey];
1184 [aCoder encodeFloat:width forKey:CPSegmentItemWidthKey];
var CPSegmentedControlSegmentStyleKey
CPSegmentSwitchTrackingSelectAny
void setFont:(CPFont aFont)
CPThemeStateControlSizeMini
void setSelected:forSegment:(BOOL isSelected, [forSegment] unsigned aSegment)
An object representation of nil.
var CPSegmentedControlSegmentsKey
CPString defaultThemeClass()
void setFrame:(CGRect aFrame)
CGPoint locationInWindow()
id initWithFrame:(CGRect aFrame)
void setControlSize:(CPControlSize aControlSize)
A collection of unique integers.
global CPApp typedef CPSegmentSwitchTracking CPSegmentSwitchTrackingSelectOne
float widthForSegment:(unsigned aSegment)
A mutable key-value pair collection.
void drawSegmentBezel:highlight:(int aSegment, [highlight] BOOL shouldHighlight)
void trackSegment:(CPEvent anEvent)
unsigned selectedSegment()
void tileWithChangedSegment:(CPInteger aSegment)
CGRect bezelFrameForSegment:(unsigned aSegment)
BOOL isSelectedForSegment:(unsigned aSegment)
var CPSegmentItemSelectedKey
An immutable string (collection of characters).
CGPoint convertPoint:fromView:(CGPoint aPoint, [fromView] CPView aView)
BOOL sendAction:to:(SEL anAction, [to] id anObject)
id initWithCoder:(CPCoder aCoder)
void setNeedsDisplay:(BOOL aFlag)
var CPSegmentedControlTrackingModeKey
var CPSegmentedControlSelectedKey
CPDictionary themeAttributes()
void removeSegmentsAtIndexes:(CPIndexSet indices)
Defines methods for use when archiving & restoring (enc/decoding).
CGRect contentFrameForSegment:(unsigned aSegment)
var CPSegmentedControlNoSelectionPlaceholder
void setSelectedSegment:(unsigned aSegment)
void insertSegments:atIndexes:(CPArray segments, [atIndexes] CPIndexSet indices)
void setFrameSize:(CGSize aSize)
var CPSegmentItemWidthKey
var CPSegmentItemImageKey
id indexSetWithIndexesInRange:(CPRange aRange)
unsigned testSegment:(CGPoint aPoint)
CPThemeStateControlSizeSmall
void enumerateIndexesUsingBlock:(Function/*(int idx, @ref BOOL stop) */aFunction)
CPSegmentSwitchTrackingMomentary
CPSegmentSwitchTracking trackingMode()
void encodeWithCoder:(CPCoder aCoder)
var CPSegmentItemLabelKey
var CPSegmentItemEnabledKey
CPView layoutEphemeralSubviewNamed:positioned:relativeToEphemeralSubviewNamed:(CPString aViewName, [positioned] CPWindowOrderingMode anOrderingMode, [relativeToEphemeralSubviewNamed] CPString relativeToViewName)
CPCenterVerticalTextAlignment
CGRect rectForEphemeralSubviewNamed:(CPString aViewName)