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