API  0.9.6
 All Classes Files Functions Variables Macros Groups Pages
CPButton.j
Go to the documentation of this file.
1 /*
2  * CPButton.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 
26 /* @group CPBezelStyle */
27 
28  // IB style
29 CPRoundedBezelStyle = 1; // Push
33 CPDisclosureBezelStyle = 5; // Disclosure triangle
35 CPCircularBezelStyle = 7; // Round
38 CPSmallSquareBezelStyle = 10; // Gradient
39 CPTexturedRoundedBezelStyle = 11; // Round Textured
40 CPRoundRectBezelStyle = 12; // Round Rect
41 CPRecessedBezelStyle = 13; // Recessed
42 CPRoundedDisclosureBezelStyle = 14; // Disclosure
44 
45 
46 /* @group CPButtonType */
50 CPSwitchButton = 3; // Deprecated, use CPCheckBox instead.
51 CPRadioButton = 4; // Deprecated, use CPRadio instead.
57 
63 
69 
72 
73 // add all future correspondance between bezel styles and theme state here.
76 
77 
80 
89 @implementation CPButton : CPControl
90 {
91  BOOL _allowsMixedState;
92 
93  CPString _title;
94  CPString _alternateTitle;
95 
96  CPInteger _showsStateBy;
97  CPInteger _highlightsBy;
98  BOOL _imageDimsWhenDisabled;
99 
100  // NS-style Display Properties
101  CPBezelStyle _bezelStyle;
102  CPControlSize _controlSize;
103 
104  CPString _keyEquivalent;
105  unsigned _keyEquivalentModifierMask;
106 
107  CPTimer _continuousDelayTimer;
108  CPTimer _continuousTimer;
109  float _periodicDelay;
110  float _periodicInterval;
111 
112  BOOL _isTracking;
113 }
114 
115 + (id)buttonWithTitle:(CPString)aTitle
116 {
117  return [self buttonWithTitle:aTitle theme:[CPTheme defaultTheme]];
118 }
119 
120 + (id)buttonWithTitle:(CPString)aTitle theme:(CPTheme)aTheme
121 {
122  var button = [[self alloc] init];
123 
124  [button setTheme:aTheme];
125  [button setTitle:aTitle];
126  [button sizeToFit];
127 
128  return button;
129 }
130 
131 + (CPString)defaultThemeClass
132 {
133  return @"button";
134 }
135 
136 + (id)themeAttributes
137 {
138  return [CPDictionary dictionaryWithObjects:[[CPNull null], 0.0, _CGInsetMakeZero(), _CGInsetMakeZero(), [CPNull null]]
139  forKeys:[@"image", @"image-offset", @"bezel-inset", @"content-inset", @"bezel-color"]];
140 }
141 
147 - (id)initWithFrame:(CGRect)aFrame
148 {
149  self = [super initWithFrame:aFrame];
150 
151  if (self)
152  {
153  // Should we instead override the defaults?
154  [self setValue:CPCenterTextAlignment forThemeAttribute:@"alignment"];
155  [self setValue:CPCenterVerticalTextAlignment forThemeAttribute:@"vertical-alignment"];
156  [self setValue:CPImageLeft forThemeAttribute:@"image-position"];
157  [self setValue:CPImageScaleNone forThemeAttribute:@"image-scaling"];
158 
159  [self setBezelStyle:CPRoundRectBezelStyle];
160  [self setBordered:YES];
161 
162  [self _init];
163  }
164 
165  return self;
166 }
167 
168 - (void)_init
169 {
170  _controlSize = CPRegularControlSize;
171 
172  _keyEquivalent = @"";
173  _keyEquivalentModifierMask = 0;
174 
175  // Continuous button defaults.
176  _periodicInterval = 0.05;
177  _periodicDelay = 0.5;
178 
179  [self setButtonType:CPMomentaryPushInButton];
180 }
181 
182 // Setting the state
187 - (BOOL)allowsMixedState
188 {
189  return _allowsMixedState;
190 }
191 
196 - (void)setAllowsMixedState:(BOOL)aFlag
197 {
198  aFlag = !!aFlag;
199 
200  if (_allowsMixedState === aFlag)
201  return;
202 
203  _allowsMixedState = aFlag;
204 
205  if (!_allowsMixedState && [self state] === CPMixedState)
206  [self setState:CPOnState];
207 }
208 
213 - (void)setObjectValue:(id)anObjectValue
214 {
215  if (!anObjectValue || anObjectValue === @"" || ([anObjectValue intValue] === 0))
216  anObjectValue = CPOffState;
217  else if (![anObjectValue isKindOfClass:[CPNumber class]])
218  anObjectValue = CPOnState;
219  else if (anObjectValue >= CPOnState)
220  anObjectValue = CPOnState
221  else if (anObjectValue < CPOffState)
222  if ([self allowsMixedState])
223  anObjectValue = CPMixedState;
224  else
225  anObjectValue = CPOnState;
226 
227  [super setObjectValue:anObjectValue];
228 
229  switch ([self objectValue])
230  {
231  case CPMixedState:
232  [self unsetThemeState:CPThemeStateSelected];
233  [self setThemeState:CPButtonStateMixed];
234  if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
235  [self setThemeState:CPThemeStateHighlighted];
236  else
237  [self unsetThemeState:CPThemeStateHighlighted];
238  break;
239 
240  case CPOnState:
241  [self unsetThemeState:CPButtonStateMixed];
242  [self setThemeState:CPThemeStateSelected];
243  if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
244  [self setThemeState:CPThemeStateHighlighted];
245  else
246  [self unsetThemeState:CPThemeStateHighlighted];
247  break;
248 
249  case CPOffState:
250  [self unsetThemeState:CPThemeStateSelected | CPButtonStateMixed | CPThemeStateHighlighted];
251  }
252 }
253 
261 - (CPInteger)nextState
262 {
263  if ([self allowsMixedState])
264  {
265  var value = [self state];
266 
267  return value - ((value === -1) ? -2 : 1);
268  }
269 
270  return 1 - [self state];
271 }
272 
278 - (void)setNextState
279 {
280  if ([self infoForBinding:CPValueBinding])
281  [self setAllowsMixedState:NO];
282 
283  [self setState:[self nextState]];
284 }
285 
291 - (void)setState:(CPInteger)aState
292 {
293  [self setIntValue:aState];
294 }
295 
299 - (CPInteger)state
300 {
301  return [self intValue];
302 }
303 
309 - (void)setTitle:(CPString)aTitle
310 {
311  if (_title === aTitle)
312  return;
313 
314  _title = aTitle;
315 
316  [self setNeedsLayout];
317  [self setNeedsDisplay:YES];
318 }
319 
325 - (CPString)title
326 {
327  return _title;
328 }
329 
330 - (void)setAlternateTitle:(CPString)aTitle
331 {
332  if (_alternateTitle === aTitle)
333  return;
334 
335  _alternateTitle = aTitle;
336 
337  [self setNeedsLayout];
338  [self setNeedsDisplay:YES];
339 }
340 
341 - (CPString)alternateTitle
342 {
343  return _alternateTitle;
344 }
345 
346 - (void)setImage:(CPImage)anImage
347 {
348  [self setValue:anImage forThemeAttribute:@"image"];
349 }
350 
351 - (CPImage)image
352 {
353  return [self valueForThemeAttribute:@"image" inState:CPThemeStateNormal];
354 }
355 
360 - (void)setAlternateImage:(CPImage)anImage
361 {
362  [self setValue:anImage forThemeAttribute:@"image" inState:CPThemeStateHighlighted];
363 }
364 
368 - (CPImage)alternateImage
369 {
370  return [self valueForThemeAttribute:@"image" inState:CPThemeStateHighlighted];
371 }
372 
373 - (void)setImageOffset:(float)theImageOffset
374 {
375  [self setValue:theImageOffset forThemeAttribute:@"image-offset"];
376 }
377 
378 - (float)imageOffset
379 {
380  return [self valueForThemeAttribute:@"image-offset"];
381 }
382 
383 - (void)setShowsStateBy:(CPInteger)aMask
384 {
385  // CPPushInCellMask cannot be set for showsStateBy.
386  aMask &= ~CPPushInCellMask;
387 
388  if (_showsStateBy === aMask)
389  return;
390 
391  _showsStateBy = aMask;
392 
393  if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask) && [self state] != CPOffState)
394  [self setThemeState:CPThemeStateHighlighted];
395  else
396  [self unsetThemeState:CPThemeStateHighlighted];
397 
398  [self setNeedsDisplay:YES];
399  [self setNeedsLayout];
400 }
401 
402 - (CPInteger)showsStateBy
403 {
404  return _showsStateBy;
405 }
406 
407 - (void)setHighlightsBy:(CPInteger)aMask
408 {
409  if (_highlightsBy === aMask)
410  return;
411 
412  _highlightsBy = aMask;
413 
414  if ([self hasThemeState:CPThemeStateHighlighted])
415  {
416  [self setNeedsDisplay:YES];
417  [self setNeedsLayout];
418  }
419 }
420 
421 - (CPInteger)highlightsBy
422 {
423  return _highlightsBy;
424 }
425 
426 - (void)setButtonType:(CPButtonType)aButtonType
427 {
428  switch (aButtonType)
429  {
430  case CPMomentaryLightButton: [self setHighlightsBy:CPChangeGrayCellMask | CPChangeBackgroundCellMask];
431  [self setShowsStateBy:CPNoCellMask];
432  break;
433 
434  case CPMomentaryPushInButton: [self setHighlightsBy:CPPushInCellMask | CPChangeGrayCellMask | CPChangeBackgroundCellMask];
435  [self setShowsStateBy:CPNoCellMask];
436  break;
437 
438  case CPMomentaryChangeButton: [self setHighlightsBy:CPContentsCellMask];
439  [self setShowsStateBy:CPNoCellMask];
440  break;
441 
442  case CPPushOnPushOffButton: [self setHighlightsBy:CPPushInCellMask | CPChangeGrayCellMask | CPChangeBackgroundCellMask];
443  [self setShowsStateBy:CPChangeBackgroundCellMask | CPChangeGrayCellMask];
444  break;
445 
446  case CPOnOffButton: [self setHighlightsBy:CPChangeGrayCellMask | CPChangeBackgroundCellMask];
447  [self setShowsStateBy:CPChangeGrayCellMask | CPChangeBackgroundCellMask];
448  break;
449 
450  case CPToggleButton: [self setHighlightsBy:CPPushInCellMask | CPContentsCellMask];
451  [self setShowsStateBy:CPContentsCellMask];
452  break;
453 
454  case CPSwitchButton: [CPException raise:CPInvalidArgumentException
455  reason:"The CPSwitchButton type is not supported in Cappuccino, use the CPCheckBox class instead."];
456 
457  case CPRadioButton: [CPException raise:CPInvalidArgumentException
458  reason:"The CPRadioButton type is not supported in Cappuccino, use the CPRadio class instead."];
459 
460  default: [CPException raise:CPInvalidArgumentException
461  reason:"Unknown button type."];
462  }
463 
464  [self setImageDimsWhenDisabled:YES];
465 }
466 
467 - (void)setImageDimsWhenDisabled:(BOOL)imageShouldDimWhenDisabled
468 {
469  imageShouldDimWhenDisabled = !!imageShouldDimWhenDisabled;
470 
471  if (_imageDimsWhenDisabled === imageShouldDimWhenDisabled)
472  return;
473 
474  _imageDimsWhenDisabled = imageShouldDimWhenDisabled;
475 
476  if ([self hasThemeState:CPThemeStateDisabled])
477  {
478  [self setNeedsDisplay:YES];
479  [self setNeedsLayout];
480  }
481 }
482 
483 - (BOOL)imageDimsWhenDisabled
484 {
485  return _imageDimsWhenDisabled;
486 }
487 
488 - (void)setPeriodicDelay:(float)aDelay interval:(float)anInterval
489 {
490  _periodicDelay = aDelay;
491  _periodicInterval = anInterval;
492 }
493 
494 - (void)mouseDown:(CPEvent)anEvent
495 {
496  if ([self isContinuous])
497  {
498  _continuousDelayTimer = [CPTimer scheduledTimerWithTimeInterval:_periodicDelay callback: function()
499  {
500  if (!_continuousTimer)
501  _continuousTimer = [CPTimer scheduledTimerWithTimeInterval:_periodicInterval target:self selector:@selector(onContinousEvent:) userInfo:anEvent repeats:YES];
502  }
503 
504  repeats:NO];
505  }
506 
507  [super mouseDown:anEvent];
508 }
509 
510 - (void)onContinousEvent:(CPTimer)aTimer
511 {
512  if (_target && _action && [_target respondsToSelector:_action])
513  [_target performSelector:_action withObject:self];
514 }
515 
516 - (BOOL)startTrackingAt:(CGPoint)aPoint
517 {
518  _isTracking = YES;
519 
520  var startedTracking = [super startTrackingAt:aPoint];
521  if (_highlightsBy & (CPPushInCellMask | CPChangeGrayCellMask))
522  {
523  if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
524  [self highlight:[self state] == CPOffState];
525  else
526  [self highlight:YES];
527  }
528  else
529  {
530  if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
531  [self highlight:[self state] != CPOffState];
532  else
533  [self highlight:NO];
534  }
535  return startedTracking;
536 }
537 
538 - (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
539 {
540  _isTracking = NO;
541 
542  if (mouseIsUp && CGRectContainsPoint([self bounds], aPoint))
543  [self setNextState];
544  else
545  {
546  if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
547  [self highlight:[self state] != CPOffState];
548  else
549  [self highlight:NO];
550  }
551 
552  [self setNeedsLayout];
553  [self setNeedsDisplay:YES];
554  [self invalidateTimers];
555 }
556 
557 - (void)invalidateTimers
558 {
559  if (_continuousTimer)
560  {
561  [_continuousTimer invalidate];
562  _continuousTimer = nil;
563  }
564 
565  if (_continuousDelayTimer)
566  {
567  [_continuousDelayTimer invalidate];
568  _continuousDelayTimer = nil;
569  }
570 }
571 
572 - (CGRect)contentRectForBounds:(CGRect)bounds
573 {
574  var contentInset = [self currentValueForThemeAttribute:@"content-inset"];
575 
576  return _CGRectInsetByInset(bounds, contentInset);
577 }
578 
579 - (CGRect)bezelRectForBounds:(CGRect)bounds
580 {
581  // Is this necessary? The theme itself can just change its inset to a zero inset when !CPThemeStateBordered.
582  if (![self isBordered])
583  return bounds;
584 
585  var bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"];
586 
587  return _CGRectInsetByInset(bounds, bezelInset);
588 }
589 
590 - (CGSize)_minimumFrameSize
591 {
592  var size = CGSizeMakeZero(),
593  contentView = [self ephemeralSubviewNamed:@"content-view"];
594 
595  if (contentView)
596  {
597  [contentView sizeToFit];
598  size = [contentView frameSize];
599  }
600  else
601  size = [([self title] || " ") sizeWithFont:[self currentValueForThemeAttribute:@"font"]];
602 
603  var contentInset = [self currentValueForThemeAttribute:@"content-inset"],
604  minSize = [self currentValueForThemeAttribute:@"min-size"],
605  maxSize = [self currentValueForThemeAttribute:@"max-size"];
606 
607  size.width = MAX(size.width + contentInset.left + contentInset.right, minSize.width);
608  size.height = MAX(size.height + contentInset.top + contentInset.bottom, minSize.height);
609 
610  if (maxSize.width >= 0.0)
611  size.width = MIN(size.width, maxSize.width);
612 
613  if (maxSize.height >= 0.0)
614  size.height = MIN(size.height, maxSize.height);
615 
616  return size;
617 }
618 
622 - (void)sizeToFit
623 {
624  [self layoutSubviews];
625 
626  [self setFrameSize:[self _minimumFrameSize]];
627 
628  if ([self ephemeralSubviewNamed:@"content-view"])
629  [self layoutSubviews];
630 }
631 
632 - (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
633 {
634  if (aName === "bezel-view")
635  return [self bezelRectForBounds:[self bounds]];
636 
637  else if (aName === "content-view")
638  return [self contentRectForBounds:[self bounds]];
639 
640  return [super rectForEphemeralSubviewNamed:aName];
641 }
642 
643 - (CPView)createEphemeralSubviewNamed:(CPString)aName
644 {
645  if (aName === "bezel-view")
646  {
647  var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
648 
649  [view setHitTests:NO];
650 
651  return view;
652  }
653  else
654  return [[_CPImageAndTextView alloc] initWithFrame:_CGRectMakeZero()];
655 }
656 
657 - (void)layoutSubviews
658 {
659  var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
660  positioned:CPWindowBelow
661  relativeToEphemeralSubviewNamed:@"content-view"];
662 
663  [bezelView setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
664 
665  var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
666  positioned:CPWindowAbove
667  relativeToEphemeralSubviewNamed:@"bezel-view"];
668 
669  if (contentView)
670  {
671  var title = nil,
672  image = nil;
673 
674  if (_isTracking)
675  {
676  if (_highlightsBy & CPContentsCellMask)
677  {
678  if (_showsStateBy & CPContentsCellMask)
679  {
680  title = ([self state] == CPOffState && _alternateTitle) ? _alternateTitle : _title;
681  image = ([self state] == CPOffState && [self alternateImage]) ? [self alternateImage] : [self image];
682  }
683  else
684  {
685  title = [self alternateTitle];
686  image = [self alternateImage];
687  }
688  }
689  else if (_showsStateBy & CPContentsCellMask)
690  {
691  title = ([self state] != CPOffState && _alternateTitle) ? _alternateTitle : _title;
692  image = ([self state] != CPOffState && [self alternateImage]) ? [self alternateImage] : [self image];
693  }
694  else
695  {
696  title = _title;
697  image = [self image];
698  }
699  }
700  else
701  {
702  if (_showsStateBy & CPContentsCellMask)
703  {
704  title = ([self state] != CPOffState && _alternateTitle) ? _alternateTitle : _title;
705  image = ([self state] != CPOffState && [self alternateImage]) ? [self alternateImage] : [self image];
706  }
707  else
708  {
709  title = _title;
710  image = [self image];
711  }
712  }
713 
714  [contentView setText:title];
715  [contentView setImage:image];
716  [contentView setImageOffset:[self currentValueForThemeAttribute:@"image-offset"]];
717 
718  [contentView setFont:[self currentValueForThemeAttribute:@"font"]];
719  [contentView setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
720  [contentView setAlignment:[self currentValueForThemeAttribute:@"alignment"]];
721  [contentView setVerticalAlignment:[self currentValueForThemeAttribute:@"vertical-alignment"]];
722  [contentView setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
723  [contentView setTextShadowColor:[self currentValueForThemeAttribute:@"text-shadow-color"]];
724  [contentView setTextShadowOffset:[self currentValueForThemeAttribute:@"text-shadow-offset"]];
725  [contentView setImagePosition:[self currentValueForThemeAttribute:@"image-position"]];
726  [contentView setImageScaling:[self currentValueForThemeAttribute:@"image-scaling"]];
727  [contentView setDimsImage:[self hasThemeState:CPThemeStateDisabled] && _imageDimsWhenDisabled];
728  }
729 }
730 
731 - (void)setBordered:(BOOL)shouldBeBordered
732 {
733  if (shouldBeBordered)
734  [self setThemeState:CPThemeStateBordered];
735  else
736  [self unsetThemeState:CPThemeStateBordered];
737 }
738 
739 - (BOOL)isBordered
740 {
741  return [self hasThemeState:CPThemeStateBordered];
742 }
743 
750 - (void)setKeyEquivalent:(CPString)aString
751 {
752  _keyEquivalent = aString || @"";
753 
754  // Check if the key equivalent is the enter key
755  // Treat \r and \n as the same key equivalent. See issue #710.
756  if (aString === CPNewlineCharacter || aString === CPCarriageReturnCharacter)
757  [self setThemeState:CPThemeStateDefault];
758  else
759  [self unsetThemeState:CPThemeStateDefault];
760 }
761 
762 - (void)viewWillMoveToWindow:(CPWindow)aWindow
763 {
764  var selfWindow = [self window];
765 
766  if (selfWindow === aWindow || aWindow === nil)
767  return;
768 
769  if ([selfWindow defaultButton] === self)
770  [selfWindow setDefaultButton:nil];
771 
772  if ([self keyEquivalent] === CPNewlineCharacter || [self keyEquivalent] === CPCarriageReturnCharacter)
773  [aWindow setDefaultButton:self];
774 }
775 
779 - (CPString)keyEquivalent
780 {
781  return _keyEquivalent;
782 }
783 
787 - (void)setKeyEquivalentModifierMask:(unsigned)aMask
788 {
789  _keyEquivalentModifierMask = aMask;
790 }
791 
795 - (unsigned)keyEquivalentModifierMask
796 {
797  return _keyEquivalentModifierMask;
798 }
799 
804 - (BOOL)performKeyEquivalent:(CPEvent)anEvent
805 {
806  // Don't handle the key equivalent for the default window because the window will handle it for us
807  if ([[self window] defaultButton] === self)
808  return NO;
809 
810  if (![anEvent _triggersKeyEquivalent:[self keyEquivalent] withModifierMask:[self keyEquivalentModifierMask]])
811  return NO;
812 
813  [self performClick:nil];
814 
815  return YES;
816 }
817 
823 - (void)performClick:(id)sender
824 {
825  // This is slightly different from [super performClick:] in that the highlight behaviour is dependent on
826  // highlightsBy and showsStateBy.
827  if (![self isEnabled])
828  return;
829 
830  [self setState:[self nextState]];
831 
832  var shouldHighlight = NO;
833 
834  if (_highlightsBy & (CPPushInCellMask | CPChangeGrayCellMask))
835  {
836  if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
837  shouldHighlight = [self state] == CPOffState;
838  else
839  shouldHighlight = YES;
840  }
841 
842  [self highlight:shouldHighlight];
843 
844  try
845  {
846  [self sendAction:[self action] to:[self target]];
847  }
848  catch (e)
849  {
850  throw e;
851  }
852  finally
853  {
854  if (shouldHighlight)
856  }
857 }
858 
859 @end
860 
861 @implementation CPButton (NS)
862 
863 - (void)setBezelStyle:(unsigned)aBezelStyle
864 {
865  if (aBezelStyle === _bezelStyle)
866  return;
867 
868  var currentState = [CPButtonBezelStyleStateMap objectForKey:_bezelStyle],
869  newState = [CPButtonBezelStyleStateMap objectForKey:aBezelStyle];
870 
871  if (currentState)
872  [self unsetThemeState:currentState];
873 
874  if (newState)
875  [self setThemeState:newState];
876 
877  _bezelStyle = aBezelStyle;
878 }
879 
880 - (unsigned)bezelStyle
881 {
882  return _bezelStyle;
883 }
884 
885 @end
886 
887 
888 var CPButtonImageKey = @"CPButtonImageKey",
889  CPButtonAlternateImageKey = @"CPButtonAlternateImageKey",
890  CPButtonTitleKey = @"CPButtonTitleKey",
891  CPButtonAlternateTitleKey = @"CPButtonAlternateTitleKey",
892  CPButtonIsBorderedKey = @"CPButtonIsBorderedKey",
893  CPButtonAllowsMixedStateKey = @"CPButtonAllowsMixedStateKey",
894  CPButtonImageDimsWhenDisabledKey = @"CPButtonImageDimsWhenDisabledKey",
895  CPButtonImagePositionKey = @"CPButtonImagePositionKey",
896  CPButtonKeyEquivalentKey = @"CPButtonKeyEquivalentKey",
897  CPButtonKeyEquivalentMaskKey = @"CPButtonKeyEquivalentMaskKey",
898  CPButtonPeriodicDelayKey = @"CPButtonPeriodicDelayKey",
899  CPButtonPeriodicIntervalKey = @"CPButtonPeriodicIntervalKey",
900  CPButtonHighlightsByKey = @"CPButtonHighlightsByKey",
901  CPButtonShowsStateByKey = @"CPButtonShowsStateByKey";
902 
903 @implementation CPButton (CPCoding)
904 
909 - (id)initWithCoder:(CPCoder)aCoder
910 {
911  self = [super initWithCoder:aCoder];
912 
913  if (self)
914  {
915  [self _init];
916 
917  _title = [aCoder decodeObjectForKey:CPButtonTitleKey];
918  _alternateTitle = [aCoder decodeObjectForKey:CPButtonAlternateTitleKey];
919  _allowsMixedState = [aCoder decodeBoolForKey:CPButtonAllowsMixedStateKey];
920 
921  if ([aCoder containsValueForKey:CPButtonHighlightsByKey])
922  {
923  // If one exists, assume both do.
924  _highlightsBy = [aCoder decodeIntForKey:CPButtonHighlightsByKey];
925  _showsStateBy = [aCoder decodeIntForKey:CPButtonShowsStateByKey];
926  }
927  else
928  {
929  // Backwards compatibility: if this CPButton was encoded before coding of
930  // highlightsBy and showsStateBy were added, we should just use the
931  // default values from _init rather than overwriting with 0, 0.
932  }
933 
934  [self setImageDimsWhenDisabled:[aCoder decodeObjectForKey:CPButtonImageDimsWhenDisabledKey]];
935 
936  if ([aCoder containsValueForKey:CPButtonImagePositionKey])
937  [self setImagePosition:[aCoder decodeIntForKey:CPButtonImagePositionKey]];
938 
939  if ([aCoder containsValueForKey:CPButtonKeyEquivalentKey])
940  [self setKeyEquivalent:CFData.decodeBase64ToUtf16String([aCoder decodeObjectForKey:CPButtonKeyEquivalentKey])];
941 
942  if ([aCoder containsValueForKey:CPButtonPeriodicDelayKey])
943  _periodicDelay = [aCoder decodeObjectForKey:CPButtonPeriodicDelayKey];
944 
945  if ([aCoder containsValueForKey:CPButtonPeriodicIntervalKey])
946  _periodicInterval = [aCoder decodeObjectForKey:CPButtonPeriodicIntervalKey];
947 
948  _keyEquivalentModifierMask = [aCoder decodeIntForKey:CPButtonKeyEquivalentMaskKey];
949 
950  [self setNeedsLayout];
951  [self setNeedsDisplay:YES];
952  }
953 
954  return self;
955 }
956 
961 - (void)encodeWithCoder:(CPCoder)aCoder
962 {
963  [super encodeWithCoder:aCoder];
964  [self invalidateTimers];
965 
966  [aCoder encodeObject:_title forKey:CPButtonTitleKey];
967  [aCoder encodeObject:_alternateTitle forKey:CPButtonAlternateTitleKey];
968 
969  [aCoder encodeBool:_allowsMixedState forKey:CPButtonAllowsMixedStateKey];
970 
971  [aCoder encodeInt:_highlightsBy forKey:CPButtonHighlightsByKey];
972  [aCoder encodeInt:_showsStateBy forKey:CPButtonShowsStateByKey];
973 
974  [aCoder encodeBool:[self imageDimsWhenDisabled] forKey:CPButtonImageDimsWhenDisabledKey];
975  [aCoder encodeInt:[self imagePosition] forKey:CPButtonImagePositionKey];
976 
977  if (_keyEquivalent)
978  [aCoder encodeObject:CFData.encodeBase64Utf16String(_keyEquivalent) forKey:CPButtonKeyEquivalentKey];
979 
980  [aCoder encodeInt:_keyEquivalentModifierMask forKey:CPButtonKeyEquivalentMaskKey];
981 
982  [aCoder encodeObject:_periodicDelay forKey:CPButtonPeriodicDelayKey];
983  [aCoder encodeObject:_periodicInterval forKey:CPButtonPeriodicIntervalKey];
984 }
985 
986 @end
987