API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPSegmentedControl.j
Go to the documentation of this file.
1 /*
2  * CPSegmentedControl.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
25 @global CPApp
26 
30 
37 @implementation CPSegmentedControl : CPControl
38 {
39  CPArray _segments;
40  CPArray _themeStates;
41 
42  int _selectedSegment;
43  int _segmentStyle;
44  CPSegmentSwitchTracking _trackingMode;
45 
46  unsigned _trackingSegment;
47  BOOL _trackingHighlighted;
48 }
49 
50 + (CPString)defaultThemeClass
51 {
52  return "segmented-control";
53 }
54 
55 + (CPDictionary)themeAttributes
56 {
57  return @{
58  @"alignment": CPCenterTextAlignment,
59  @"vertical-alignment": CPCenterVerticalTextAlignment,
60  @"image-position": CPImageLeft,
61  @"image-scaling": CPImageScaleNone,
62  @"bezel-inset": CGInsetMakeZero(),
63  @"content-inset": CGInsetMakeZero(),
64  @"left-segment-bezel-color": [CPNull null],
65  @"right-segment-bezel-color": [CPNull null],
66  @"center-segment-bezel-color": [CPNull null],
67  @"divider-bezel-color": [CPNull null],
68  @"divider-thickness": 1.0,
69  @"default-height": 24.0,
70  };
71 }
72 
73 + (Class)_binderClassForBinding:(CPString)aBinding
74 {
75  if ([self _isSelectionBinding:aBinding])
76  return [_CPSegmentedControlBinder class];
77 
78  return [super _binderClassForBinding:aBinding];
79 }
80 
81 + (BOOL)_isSelectionBinding:(CPString)aBinding
82 {
83  return (aBinding === CPSelectedIndexBinding || aBinding === CPSelectedLabelBinding || aBinding === CPSelectedTagBinding);
84 }
85 
86 - (id)initWithFrame:(CGRect)aRect
87 {
88  _segments = [];
89  _themeStates = [];
90 
91  self = [super initWithFrame:aRect];
92 
93  if (self)
94  {
95  _selectedSegment = -1;
96 
97  _trackingMode = CPSegmentSwitchTrackingSelectOne;
98  }
99 
100  return self;
101 }
102 
103 - (void)bind:(CPString)aBinding toObject:(id)anObject withKeyPath:(CPString)aKeyPath options:(CPDictionary)options
104 {
105  if ([[self class] _isSelectionBinding:aBinding] && _trackingMode !== CPSegmentSwitchTrackingSelectOne)
106  CPLog.warn("Binding " + aBinding + " needs CPSegmentSwitchTrackingSelectOne tracking mode");
107  else
108  [super bind:aBinding toObject:anObject withKeyPath:aKeyPath options:options];
109 }
110 
111 - (void)_reverseSetBinding
112 {
113  [_CPSegmentedControlBinder reverseSetValueForObject:self];
114 }
115 
119 - (int)selectedTag
120 {
121  return [_segments[_selectedSegment] tag];
122 }
123 
124 // Specifying the number of segments
129 - (void)setSegmentCount:(unsigned)aCount
130 {
131  if (_segments.length == aCount)
132  return;
133 
134  var height = CGRectGetHeight([self bounds]),
135  dividersBefore = MAX(0, _segments.length - 1),
136  dividersAfter = MAX(0, aCount - 1);
137 
138  if (_segments.length < aCount)
139  {
140  for (var index = _segments.length; index < aCount; ++index)
141  {
142  _segments[index] = [[_CPSegmentItem alloc] init];
143  _themeStates[index] = CPThemeStateNormal;
144  }
145  }
146  else if (aCount < _segments.length)
147  {
148  _segments.length = aCount;
149  _themeStates.length = aCount;
150  }
151 
152  if (_selectedSegment >= _segments.length)
153  _selectedSegment = -1;
154 
155  var thickness = [self currentValueForThemeAttribute:@"divider-thickness"],
156  frame = [self frame],
157  widthOfAllSegments = 0,
158  dividerExtraSpace = ([_segments count] - 1) * thickness;
159 
160  for (var i = 0; i < [_segments count]; i++)
161  widthOfAllSegments += [_segments[i] width];
162 
163  [self setFrameSize:CGSizeMake(widthOfAllSegments + dividerExtraSpace, frame.size.height)];
164 
165  [self tileWithChangedSegment:0];
166 }
167 
171 - (unsigned)segmentCount
172 {
173  return _segments.length;
174 }
175 
176 // Specifying Selected Segment
182 - (void)setSelectedSegment:(unsigned)aSegment
183 {
184  // setSelected:forSegment throws the exception for us (if necessary)
185  [self setSelected:YES forSegment:aSegment];
186 }
187 
191 - (unsigned)selectedSegment
192 {
193  return _selectedSegment;
194 }
195 
199 - (BOOL)selectSegmentWithTag:(int)aTag
200 {
201  var index = 0;
202 
203  for (; index < _segments.length; ++index)
204  if (_segments[index].tag == aTag)
205  {
206  [self setSelectedSegment:index];
207 
208  return YES;
209  }
210 
211  return NO;
212 }
213 
214 - (BOOL)_selectSegmentWithLabel:(CPString)aLabel
215 {
216  var index = 0;
217 
218  for (; index < _segments.length; ++index)
219  if (_segments[index].label == aLabel)
220  {
221  [self setSelectedSegment:index];
222 
223  return YES;
224  }
225 
226  return NO;
227 }
228 
229 // Specifying Tracking Mode
230 
231 - (BOOL)isTracking
232 {
233 
234 }
235 
236 - (void)setTrackingMode:(CPSegmentSwitchTracking)aTrackingMode
237 {
238  if (_trackingMode == aTrackingMode)
239  return;
240 
241  _trackingMode = aTrackingMode;
242 
243  if (_trackingMode == CPSegmentSwitchTrackingSelectOne)
244  {
245  var index = 0,
246  selected = NO;
247 
248  for (; index < _segments.length; ++index)
249  if ([_segments[index] selected])
250  if (selected)
251  [self setSelected:NO forSegment:index];
252  else
253  selected = YES;
254  }
255 
256  else if (_trackingMode == CPSegmentSwitchTrackingMomentary)
257  {
258  var index = 0;
259 
260  for (; index < _segments.length; ++index)
261  if ([_segments[index] selected])
262  [self setSelected:NO forSegment:index];
263  }
264 }
265 
269 - (CPSegmentSwitchTracking)trackingMode
270 {
271  return _trackingMode;
272 }
273 
274 // Working with Individual Segments
281 - (void)setWidth:(float)aWidth forSegment:(unsigned)aSegment
282 {
283  [_segments[aSegment] setWidth:aWidth];
284 
285  [self tileWithChangedSegment:aSegment];
286 }
287 
293 - (float)widthForSegment:(unsigned)aSegment
294 {
295  return [_segments[aSegment] width];
296 }
297 
304 - (void)setImage:(CPImage)anImage forSegment:(unsigned)aSegment
305 {
306  [_segments[aSegment] setImage:anImage];
307 
308  [self tileWithChangedSegment:aSegment];
309 }
310 
316 - (CPImage)imageForSegment:(unsigned)aSegment
317 {
318  return [_segments[aSegment] image];
319 }
320 
327 - (void)setLabel:(CPString)aLabel forSegment:(unsigned)aSegment
328 {
329  [_segments[aSegment] setLabel:aLabel];
330 
331  [self tileWithChangedSegment:aSegment];
332 }
333 
339 - (CPString)labelForSegment:(unsigned)aSegment
340 {
341  return [_segments[aSegment] label];
342 }
343 
350 - (void)setMenu:(CPMenu)aMenu forSegment:(unsigned)aSegment
351 {
352  [_segments[aSegment] setMenu:aMenu];
353 }
354 
360 - (CPMenu)menuForSegment:(unsigned)aSegment
361 {
362  return [_segments[aSegment] menu];
363 }
364 
372 - (void)setSelected:(BOOL)isSelected forSegment:(unsigned)aSegment
373 {
374  var segment = _segments[aSegment];
375 
376  // If we're already in this state, bail.
377  if ([segment selected] == isSelected)
378  return;
379 
380  [segment setSelected:isSelected];
381 
382  _themeStates[aSegment] = isSelected ? CPThemeStateSelected : CPThemeStateNormal;
383 
384  // We need to do some cleanup if we only allow one selection.
385  if (isSelected)
386  {
387  var oldSelectedSegment = _selectedSegment;
388 
389  _selectedSegment = aSegment;
390 
391  if (_trackingMode == CPSegmentSwitchTrackingSelectOne && oldSelectedSegment != aSegment && oldSelectedSegment != -1)
392  {
393  [_segments[oldSelectedSegment] setSelected:NO];
394  _themeStates[oldSelectedSegment] = CPThemeStateNormal;
395 
396  [self drawSegmentBezel:oldSelectedSegment highlight:NO];
397  }
398  }
399 
400  if (_trackingMode != CPSegmentSwitchTrackingMomentary)
401  [self drawSegmentBezel:aSegment highlight:NO];
402 
403  [self setNeedsLayout];
404  [self setNeedsDisplay:YES];
405 }
406 
412 - (BOOL)isSelectedForSegment:(unsigned)aSegment
413 {
414  return [_segments[aSegment] selected];
415 }
416 
423 - (void)setEnabled:(BOOL)shouldBeEnabled forSegment:(unsigned)aSegment
424 {
425  if ([_segments[aSegment] enabled] === shouldBeEnabled)
426  return;
427 
428  [_segments[aSegment] setEnabled:shouldBeEnabled];
429 
430  if (shouldBeEnabled)
431  _themeStates[aSegment] &= ~CPThemeStateDisabled;
432  else
433  _themeStates[aSegment] |= CPThemeStateDisabled;
434 
435  [self setNeedsLayout];
436  [self setNeedsDisplay:YES];
437 }
438 
444 - (BOOL)isEnabledForSegment:(unsigned)aSegment
445 {
446  return [_segments[aSegment] enabled];
447 }
448 
454 - (void)setTag:(int)aTag forSegment:(unsigned)aSegment
455 {
456  [_segments[aSegment] setTag:aTag];
457 }
458 
463 - (int)tagForSegment:(unsigned)aSegment
464 {
465  return [_segments[aSegment] tag];
466 }
467 
468 // Drawings
474 - (void)drawSegmentBezel:(int)aSegment highlight:(BOOL)shouldHighlight
475 {
476  if (shouldHighlight)
477  _themeStates[aSegment] |= CPThemeStateHighlighted;
478  else
479  _themeStates[aSegment] &= ~CPThemeStateHighlighted;
480 
481  [self setNeedsLayout];
482  [self setNeedsDisplay:YES];
483 }
484 
485 - (float)_leftOffsetForSegment:(unsigned)segment
486 {
487  var bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"];
488 
489  if (segment == 0)
490  return bezelInset.left;
491 
492  var thickness = [self currentValueForThemeAttribute:@"divider-thickness"];
493 
494  return [self _leftOffsetForSegment:segment - 1] + [self widthForSegment:segment - 1] + thickness;
495 }
496 
497 - (unsigned)_indexOfLastSegment
498 {
499  var lastSegmentIndex = [_segments count] - 1;
500 
501  if (lastSegmentIndex < 0)
502  lastSegmentIndex = 0;
503 
504  return lastSegmentIndex;
505 }
506 
507 - (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
508 {
509  var height = [self currentValueForThemeAttribute:@"default-height"],
510  contentInset = [self currentValueForThemeAttribute:@"content-inset"],
511  bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"],
512  bounds = [self bounds];
513 
514  if (aName === "left-segment-bezel")
515  {
516  return CGRectMake(bezelInset.left, bezelInset.top, contentInset.left, height);
517  }
518  else if (aName === "right-segment-bezel")
519  {
520  return CGRectMake(CGRectGetWidth([self bounds]) - contentInset.right,
521  bezelInset.top,
522  contentInset.right,
523  height);
524  }
525  else if (aName.indexOf("segment-bezel") === 0)
526  {
527  var segment = parseInt(aName.substring("segment-bezel-".length), 10),
528  frame = CGRectCreateCopy([_segments[segment] frame]);
529 
530  if (segment === 0)
531  {
532  frame.origin.x += contentInset.left;
533  frame.size.width -= contentInset.left;
534  }
535 
536  if (segment === _segments.length - 1)
537  frame.size.width = CGRectGetWidth([self bounds]) - contentInset.right - frame.origin.x;
538 
539  return frame;
540  }
541  else if (aName.indexOf("divider-bezel") === 0)
542  {
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"];
547 
548  return CGRectMake(left + width, bezelInset.top, thickness, height);
549  }
550  else if (aName.indexOf("segment-content") === 0)
551  {
552  var segment = parseInt(aName.substring("segment-content-".length), 10);
553 
554  return [self contentFrameForSegment:segment];
555  }
556 
557  return [super rectForEphemeralSubviewNamed:aName];
558 }
559 
560 - (CPView)createEphemeralSubviewNamed:(CPString)aName
561 {
562  if ([aName hasPrefix:@"segment-content"])
563  return [[_CPImageAndTextView alloc] initWithFrame:CGRectMakeZero()];
564 
565  return [[CPView alloc] initWithFrame:CGRectMakeZero()];
566 }
567 
568 - (void)layoutSubviews
569 {
570  if (_segments.length <= 0)
571  return;
572 
573  var themeState = _themeStates[0];
574 
575  themeState |= _themeState & CPThemeStateDisabled;
576 
577  var leftCapColor = [self valueForThemeAttribute:@"left-segment-bezel-color"
578  inState:themeState],
579 
580  leftBezelView = [self layoutEphemeralSubviewNamed:@"left-segment-bezel"
581  positioned:CPWindowBelow
582  relativeToEphemeralSubviewNamed:nil];
583 
584  [leftBezelView setBackgroundColor:leftCapColor];
585 
586  var themeState = _themeStates[_themeStates.length - 1];
587 
588  themeState |= _themeState & CPThemeStateDisabled;
589 
590  var rightCapColor = [self valueForThemeAttribute:@"right-segment-bezel-color"
591  inState:themeState],
592 
593  rightBezelView = [self layoutEphemeralSubviewNamed:@"right-segment-bezel"
594  positioned:CPWindowBelow
595  relativeToEphemeralSubviewNamed:nil];
596 
597  [rightBezelView setBackgroundColor:rightCapColor];
598 
599  for (var i = 0, count = _themeStates.length; i < count; i++)
600  {
601  var themeState = _themeStates[i];
602 
603  themeState |= _themeState & CPThemeStateDisabled;
604 
605  var bezelColor = [self valueForThemeAttribute:@"center-segment-bezel-color"
606  inState:themeState],
607 
608  bezelView = [self layoutEphemeralSubviewNamed:"segment-bezel-" + i
609  positioned:CPWindowBelow
610  relativeToEphemeralSubviewNamed:nil];
611 
612  [bezelView setBackgroundColor:bezelColor];
613 
614 
615  // layout image/title views
616  var segment = _segments[i],
617  contentView = [self layoutEphemeralSubviewNamed:@"segment-content-" + i
618  positioned:CPWindowAbove
619  relativeToEphemeralSubviewNamed:@"segment-bezel-" + i];
620 
621  [contentView setText:[segment label]];
622  [contentView setImage:[segment image]];
623 
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]];
632 
633  if ([segment image] && [segment label])
634  [contentView setImagePosition:[self valueForThemeAttribute:@"image-position" inState:themeState]];
635  else if ([segment image])
636  [contentView setImagePosition:CPImageOnly];
637 
638  if (i == count - 1)
639  continue;
640 
641  var borderState = _themeStates[i] | _themeStates[i + 1];
642 
643  borderState = (borderState & CPThemeStateSelected & ~CPThemeStateHighlighted) ? CPThemeStateSelected : CPThemeStateNormal;
644 
645  borderState |= _themeState & CPThemeStateDisabled;
646 
647  var borderColor = [self valueForThemeAttribute:@"divider-bezel-color"
648  inState:borderState],
649 
650  borderView = [self layoutEphemeralSubviewNamed:"divider-bezel-" + i
651  positioned:CPWindowBelow
652  relativeToEphemeralSubviewNamed:nil];
653 
654  [borderView setBackgroundColor:borderColor];
655  }
656 }
657 
658 
664 - (void)drawSegment:(int)aSegment highlight:(BOOL)shouldHighlight
665 {
666 }
667 
668 - (void)tileWithChangedSegment:(unsigned)aSegment
669 {
670  if (aSegment >= _segments.length)
671  return;
672 
673  var segment = _segments[aSegment],
674  segmentWidth = [segment width],
675  themeState = _themeStates[aSegment] | (_themeState & CPThemeStateDisabled),
676  contentInset = [self valueForThemeAttribute:@"content-inset" inState:themeState],
677  font = [self font];
678 
679  if (!segmentWidth)
680  {
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;
687  else
688  segmentWidth = 0.0;
689  }
690 
691  var delta = segmentWidth - CGRectGetWidth([segment frame]);
692 
693  if (!delta)
694  {
695  [self setNeedsLayout];
696  [self setNeedsDisplay:YES];
697 
698  return;
699  }
700 
701  // Update control size
702  var frame = [self frame];
703 
704  [self setFrameSize:CGSizeMake(CGRectGetWidth(frame) + delta, CGRectGetHeight(frame))];
705 
706  // Update segment width
707  [segment setWidth:segmentWidth];
708  [segment setFrame:[self frameForSegment:aSegment]];
709 
710  // Update following segments widths
711  var index = aSegment + 1;
712 
713  for (; index < _segments.length; ++index)
714  {
715  [_segments[index] frame].origin.x += delta;
716 
717  [self drawSegmentBezel:index highlight:NO];
718  [self drawSegment:index highlight:NO];
719  }
720 
721  [self drawSegmentBezel:aSegment highlight:NO];
722  [self drawSegment:aSegment highlight:NO];
723 
724  [self setNeedsLayout];
725  [self setNeedsDisplay:YES];
726 }
727 
732 - (CGRect)frameForSegment:(unsigned)aSegment
733 {
734  return [self bezelFrameForSegment:aSegment];
735 }
736 
737 - (CGRect)bezelFrameForSegment:(unsigned)aSegment
738 {
739  var height = [self currentValueForThemeAttribute:@"default-height"],
740  bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"],
741  width = [self widthForSegment:aSegment],
742  left = [self _leftOffsetForSegment:aSegment];
743 
744  return CGRectMake(left, bezelInset.top, width, height);
745 }
746 
747 - (CGRect)contentFrameForSegment:(unsigned)aSegment
748 {
749  var height = [self currentValueForThemeAttribute:@"default-height"],
750  contentInset = [self currentValueForThemeAttribute:@"content-inset"],
751  width = [self widthForSegment:aSegment],
752  left = [self _leftOffsetForSegment:aSegment];
753 
754  return CGRectMake(left + contentInset.left, contentInset.top, width - contentInset.left - contentInset.right, height - contentInset.top - contentInset.bottom);
755 }
756 
762 - (unsigned)testSegment:(CGPoint)aPoint
763 {
764  var location = [self convertPoint:aPoint fromView:nil],
765  count = _segments.length;
766 
767  while (count--)
768  if (CGRectContainsPoint([_segments[count] frame], aPoint))
769  return count;
770 
771  if (_segments.length)
772  {
773  var adjustedLastFrame = CGRectCreateCopy([_segments[_segments.length - 1] frame]);
774  adjustedLastFrame.size.width = CGRectGetWidth([self bounds]) - adjustedLastFrame.origin.x;
775 
776  if (CGRectContainsPoint(adjustedLastFrame, aPoint))
777  return _segments.length - 1;
778  }
779 
780  return -1;
781 }
782 
783 - (void)mouseDown:(CPEvent)anEvent
784 {
785  if (![self isEnabled])
786  return;
787 
788  [self trackSegment:anEvent];
789 }
790 
791 // FIXME: this should be fixed way up in cpbutton/cpcontrol.
792 - (void)mouseUp:(CPEvent)anEvent
793 {
794 }
795 
800 - (void)trackSegment:(CPEvent)anEvent
801 {
802  var type = [anEvent type],
803  location = [self convertPoint:[anEvent locationInWindow] fromView:nil];
804 
805  if (type == CPLeftMouseUp)
806  {
807  if (_trackingSegment == -1)
808  return;
809 
810  if (_trackingSegment === [self testSegment:location])
811  {
812  if (_trackingMode == CPSegmentSwitchTrackingSelectAny)
813  {
814  [self setSelected:![self isSelectedForSegment:_trackingSegment] forSegment:_trackingSegment];
815 
816  // With ANY, _selectedSegment means last pressed.
817  _selectedSegment = _trackingSegment;
818  }
819  else
820  [self setSelected:YES forSegment:_trackingSegment];
821 
822  [self sendAction:[self action] to:[self target]];
823 
824  if (_trackingMode == CPSegmentSwitchTrackingMomentary)
825  {
826  [self setSelected:NO forSegment:_trackingSegment];
827 
828  _selectedSegment = -1;
829  }
830  }
831 
832  [self drawSegmentBezel:_trackingSegment highlight:NO];
833 
834  _trackingSegment = -1;
835 
836  return;
837  }
838 
839  if (type == CPLeftMouseDown)
840  {
841  var trackingSegment = [self testSegment:location];
842  if (trackingSegment > -1 && [self isEnabledForSegment:trackingSegment])
843  {
844  _trackingHighlighted = YES;
845  _trackingSegment = trackingSegment;
846  [self drawSegmentBezel:_trackingSegment highlight:YES];
847  }
848  }
849 
850  else if (type == CPLeftMouseDragged)
851  {
852  if (_trackingSegment == -1)
853  return;
854 
855  var highlighted = [self testSegment:location] === _trackingSegment;
856 
857  if (highlighted != _trackingHighlighted)
858  {
859  _trackingHighlighted = highlighted;
860 
861  [self drawSegmentBezel:_trackingSegment highlight:_trackingHighlighted];
862  }
863  }
864 
865  [CPApp setTarget:self selector:@selector(trackSegment:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
866 }
867 
868 - (void)setFont:(CPFont)aFont
869 {
870  [super setFont:aFont];
871 
872  [self tileWithChangedSegment:0];
873 }
874 
875 @end
876 
877 var CPSegmentedControlSegmentsKey = "CPSegmentedControlSegmentsKey",
878  CPSegmentedControlSelectedKey = "CPSegmentedControlSelectedKey",
879  CPSegmentedControlSegmentStyleKey = "CPSegmentedControlSegmentStyleKey",
880  CPSegmentedControlTrackingModeKey = "CPSegmentedControlTrackingModeKey";
881 
883 
884 - (id)initWithCoder:(CPCoder)aCoder
885 {
886  self = [super initWithCoder:aCoder];
887 
888  if (self)
889  {
890  var frame = [self frame],
891  originalWidth = frame.size.width;
892 
893  frame.size.width = 0;
894 
895  [self setFrame:frame];
896 
897  _segments = [aCoder decodeObjectForKey:CPSegmentedControlSegmentsKey];
898  _segmentStyle = [aCoder decodeIntForKey:CPSegmentedControlSegmentStyleKey];
899  _themeStates = [];
900 
901  if ([aCoder containsValueForKey:CPSegmentedControlSelectedKey])
902  _selectedSegment = [aCoder decodeIntForKey:CPSegmentedControlSelectedKey];
903  else
904  _selectedSegment = -1;
905 
906  if ([aCoder containsValueForKey:CPSegmentedControlTrackingModeKey])
907  _trackingMode = [aCoder decodeIntForKey:CPSegmentedControlTrackingModeKey];
908  else
909  _trackingMode = CPSegmentSwitchTrackingSelectOne;
910 
911  // HACK
912 
913  for (var i = 0; i < _segments.length; i++)
914  {
915  _themeStates[i] = [_segments[i] selected] ? CPThemeStateSelected : CPThemeStateNormal;
916  [self tileWithChangedSegment:i];
917  }
918 
919  var difference = MAX(originalWidth - [self frame].size.width, 0.0),
920  remainingWidth = FLOOR(difference / _segments.length);
921 
922  for (var i = 0; i < _segments.length; i++)
923  [self setWidth:[_segments[i] width] + remainingWidth forSegment:i];
924 
925  [self tileWithChangedSegment:0];
926  }
927 
928  return self;
929 }
930 
931 - (void)encodeWithCoder:(CPCoder)aCoder
932 {
933  [super encodeWithCoder:aCoder];
934 
935  [aCoder encodeObject:_segments forKey:CPSegmentedControlSegmentsKey];
936  [aCoder encodeInt:_selectedSegment forKey:CPSegmentedControlSelectedKey];
937  [aCoder encodeInt:_segmentStyle forKey:CPSegmentedControlSegmentStyleKey];
938  [aCoder encodeInt:_trackingMode forKey:CPSegmentedControlTrackingModeKey];
939 }
940 
941 @end
942 
944  CPSegmentedControlNoSelectionPlaceholder = "CPSegmentedControlNoSelectionPlaceholder";
945 
946 @implementation _CPSegmentedControlBinder : CPBinder
947 {
948  CPString _selectionBinding;
949 }
950 
951 + (void)reverseSetValueForObject:(id)aSource
952 {
953  var binder = CPSegmentedControlBindersMap[[aSource UID]];
954  [binder reverseSetValueFor:[binder selectionBinding]];
955 }
956 
957 - (id)initWithBinding:(CPString)aBinding name:(CPString)aName to:(id)aDestination keyPath:(CPString)aKeyPath options:(CPDictionary)options from:(id)aSource
958 {
959  self = [super initWithBinding:aBinding name:aName to:aDestination keyPath:aKeyPath options:options from:aSource];
960 
961  if (self)
962  {
963  CPSegmentedControlBindersMap[[aSource UID]] = self;
964  _selectionBinding = aName;
965  }
966 
967  return self;
968 }
969 
970 - (void)_updatePlaceholdersWithOptions:(CPDictionary)options
971 {
972  [super _updatePlaceholdersWithOptions:options];
973 
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];
978 }
979 
980 - (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
981 {
983  [_source setSelected:NO forSegment:[_source selectedSegment]];
984  else
985  [self setValue:aValue forBinding:aBinding];
986 }
987 
988 - (void)setValue:(id)aValue forBinding:(CPString)aBinding
989 {
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];
996 }
997 
998 - (id)valueForBinding:(CPString)aBinding
999 {
1000  var selectedIndex = [_source selectedSegment];
1001 
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];
1008 }
1009 
1010 @end
1011 
1012 @implementation _CPSegmentItem : CPObject
1013 {
1014  CPImage image;
1017  BOOL selected;
1018  BOOL enabled;
1019  int tag;
1020  int width;
1021 
1022  CGRect frame;
1023 }
1024 
1025 - (id)init
1026 {
1027  if (self = [super init])
1028  {
1029  image = nil;
1030  label = @"";
1031  menu = nil;
1032  selected = NO;
1033  enabled = YES;
1034  tag = -1;
1035  width = 0;
1036 
1037  frame = CGRectMakeZero();
1038  }
1039  return self;
1040 }
1041 
1042 @end
1043 
1044 var CPSegmentItemImageKey = "CPSegmentItemImageKey",
1045  CPSegmentItemLabelKey = "CPSegmentItemLabelKey",
1046  CPSegmentItemMenuKey = "CPSegmentItemMenuKey",
1047  CPSegmentItemSelectedKey = "CPSegmentItemSelectedKey",
1048  CPSegmentItemEnabledKey = "CPSegmentItemEnabledKey",
1049  CPSegmentItemTagKey = "CPSegmentItemTagKey",
1050  CPSegmentItemWidthKey = "CPSegmentItemWidthKey";
1051 
1052 @implementation _CPSegmentItem (CPCoding)
1053 
1054 - (id)initWithCoder:(CPCoder)aCoder
1055 {
1056  self = [super init];
1057 
1058  if (self)
1059  {
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];
1067 
1068  frame = CGRectMakeZero();
1069  }
1070 
1071  return self;
1072 }
1073 
1074 - (void)encodeWithCoder:(CPCoder)aCoder
1075 {
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];
1083 }
1084 
1085 @end