API  1.0.0
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 @typedef CPBezelStyle
27 CPRoundedBezelStyle = 1; // Push
31 CPDisclosureBezelStyle = 5; // Disclosure triangle
33 CPCircularBezelStyle = 7; // Round
36 CPSmallSquareBezelStyle = 10; // Gradient
37 CPTexturedRoundedBezelStyle = 11; // Round Textured
38 CPRoundRectBezelStyle = 12; // Round Rect
39 CPRecessedBezelStyle = 13; // Recessed
40 CPRoundedDisclosureBezelStyle = 14; // Disclosure
42 
43 
44 /* @group CPButtonType */
45 @typedef 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 CPButtonStateBezelStyleRoundRect = CPThemeState("roundRect");
72 
73 // add all future correspondance between bezel styles and theme state here.
77  };
78 
80 CPButtonDefaultHeight = 25.0;
81 CPButtonImageOffset = 3.0;
83 
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 
105  CPString _keyEquivalent;
106  unsigned _keyEquivalentModifierMask;
107 
108  CPTimer _continuousDelayTimer;
109  CPTimer _continuousTimer;
110  float _periodicDelay;
111  float _periodicInterval;
112 
113  BOOL _isTracking;
114 }
115 
116 + (Class)_binderClassForBinding:(CPString)aBinding
117 {
118  if (aBinding === CPTargetBinding || [aBinding hasPrefix:CPArgumentBinding])
119  return [CPActionBinding class];
120 
121  return [super _binderClassForBinding:aBinding];
122 }
123 
124 + (id)buttonWithTitle:(CPString)aTitle
125 {
126  return [self buttonWithTitle:aTitle theme:[CPTheme defaultTheme]];
127 }
128 
129 + (id)buttonWithTitle:(CPString)aTitle theme:(CPTheme)aTheme
130 {
131  var button = [[self alloc] init];
132 
133  [button setTheme:aTheme];
134  [button setTitle:aTitle];
135  [button sizeToFit];
136 
137  return button;
138 }
139 
140 + (CPString)defaultThemeClass
141 {
142  return @"button";
143 }
144 
145 + (CPDictionary)themeAttributes
146 {
147  return @{
148  @"image": [CPNull null],
149  @"image-offset": 0.0,
150  @"bezel-inset": CGInsetMakeZero(),
151  @"content-inset": CGInsetMakeZero(),
152  @"bezel-color": [CPNull null],
153  };
154 }
155 
161 - (id)initWithFrame:(CGRect)aFrame
162 {
163  self = [super initWithFrame:aFrame];
164 
165  if (self)
166  {
167  // Should we instead override the defaults?
168  [self setValue:CPCenterTextAlignment forThemeAttribute:@"alignment"];
169  [self setValue:CPCenterVerticalTextAlignment forThemeAttribute:@"vertical-alignment"];
170  [self setValue:CPImageLeft forThemeAttribute:@"image-position"];
171  [self setValue:CPImageScaleNone forThemeAttribute:@"image-scaling"];
172 
173  [self setBezelStyle:CPRoundRectBezelStyle];
174  [self setBordered:YES];
175 
176  [self _init];
177  }
178 
179  return self;
180 }
181 
182 - (void)_init
183 {
184  _keyEquivalent = @"";
185  _keyEquivalentModifierMask = 0;
186 
187  // Continuous button defaults.
188  _periodicInterval = 0.05;
189  _periodicDelay = 0.5;
190 
191  [self setButtonType:CPMomentaryPushInButton];
192 }
193 
194 #pragma mark -
195 #pragma mark Control Size
196 
197 - (void)setControlSize:(CPControlSize)aControlSize
198 {
199  [super setControlSize:aControlSize];
200 
201  if ([self isBordered])
202  [self _sizeToControlSize];
203 }
204 
205 
206 #pragma mark -
207 
208 // Setting the state
213 - (BOOL)allowsMixedState
214 {
215  return _allowsMixedState;
216 }
217 
222 - (void)setAllowsMixedState:(BOOL)aFlag
223 {
224  aFlag = !!aFlag;
225 
226  if (_allowsMixedState === aFlag)
227  return;
228 
229  _allowsMixedState = aFlag;
230 
231  if (!_allowsMixedState && [self state] === CPMixedState)
232  [self setState:CPOnState];
233 }
234 
239 - (void)setObjectValue:(id)anObjectValue
240 {
241  if (!anObjectValue || anObjectValue === @"" || ([anObjectValue intValue] === 0))
242  anObjectValue = CPOffState;
243  else if (![anObjectValue isKindOfClass:[CPNumber class]])
244  anObjectValue = CPOnState;
245  else if (anObjectValue >= CPOnState)
246  anObjectValue = CPOnState;
247  else if (anObjectValue < CPOffState)
248  if ([self allowsMixedState])
249  anObjectValue = CPMixedState;
250  else
251  anObjectValue = CPOnState;
252 
253  [super setObjectValue:anObjectValue];
254 
255  switch ([self objectValue])
256  {
257  case CPMixedState:
258  [self unsetThemeState:CPThemeStateSelected];
259  [self setThemeState:CPButtonStateMixed];
260  if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
261  [self setThemeState:CPThemeStateHighlighted];
262  else
263  [self unsetThemeState:CPThemeStateHighlighted];
264  break;
265 
266  case CPOnState:
267  [self unsetThemeState:CPButtonStateMixed];
268  [self setThemeState:CPThemeStateSelected];
269  if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
270  [self setThemeState:CPThemeStateHighlighted];
271  else
272  [self unsetThemeState:CPThemeStateHighlighted];
273  break;
274 
275  case CPOffState:
276  [self unsetThemeStates:[CPThemeStateSelected, CPButtonStateMixed, CPThemeStateHighlighted]];
277  }
278 }
279 
287 - (CPInteger)nextState
288 {
289  if ([self allowsMixedState])
290  {
291  var value = [self state];
292 
293  return value - ((value === -1) ? -2 : 1);
294  }
295 
296  return 1 - [self state];
297 }
298 
304 - (void)setNextState
305 {
306  if ([self infoForBinding:CPValueBinding])
307  [self setAllowsMixedState:NO];
308 
309  [self setState:[self nextState]];
310 }
311 
317 - (void)setState:(CPInteger)aState
318 {
319  [self setIntValue:aState];
320 }
321 
325 - (CPInteger)state
326 {
327  return [self intValue];
328 }
329 
335 - (void)setTitle:(CPString)aTitle
336 {
337  if (_title === aTitle)
338  return;
339 
340  _title = aTitle;
341 
342  [self setNeedsLayout];
343  [self setNeedsDisplay:YES];
344 }
345 
351 - (CPString)title
352 {
353  return _title;
354 }
355 
356 - (void)setAlternateTitle:(CPString)aTitle
357 {
358  if (_alternateTitle === aTitle)
359  return;
360 
361  _alternateTitle = aTitle;
362 
363  [self setNeedsLayout];
364  [self setNeedsDisplay:YES];
365 }
366 
367 - (CPString)alternateTitle
368 {
369  return _alternateTitle;
370 }
371 
372 - (void)setImage:(CPImage)anImage
373 {
374  [self setValue:anImage forThemeAttribute:@"image" inState:CPThemeStateNormal];
375 }
376 
377 - (CPImage)image
378 {
379  return [self valueForThemeAttribute:@"image" inState:CPThemeStateNormal];
380 }
381 
386 - (void)setAlternateImage:(CPImage)anImage
387 {
388  [self setValue:anImage forThemeAttribute:@"image" inState:CPThemeStateHighlighted];
389 }
390 
394 - (CPImage)alternateImage
395 {
396  return [self valueForThemeAttribute:@"image" inState:CPThemeStateHighlighted];
397 }
398 
399 - (void)setImageOffset:(float)theImageOffset
400 {
401  [self setValue:theImageOffset forThemeAttribute:@"image-offset"];
402 }
403 
404 - (float)imageOffset
405 {
406  return [self valueForThemeAttribute:@"image-offset"];
407 }
408 
409 - (void)setShowsStateBy:(CPInteger)aMask
410 {
411  // CPPushInCellMask cannot be set for showsStateBy.
412  aMask &= ~CPPushInCellMask;
413 
414  if (_showsStateBy === aMask)
415  return;
416 
417  _showsStateBy = aMask;
418 
419  if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask) && [self state] != CPOffState)
420  [self setThemeState:CPThemeStateHighlighted];
421  else
422  [self unsetThemeState:CPThemeStateHighlighted];
423 
424  [self setNeedsDisplay:YES];
425  [self setNeedsLayout];
426 }
427 
428 - (CPInteger)showsStateBy
429 {
430  return _showsStateBy;
431 }
432 
433 - (void)setHighlightsBy:(CPInteger)aMask
434 {
435  if (_highlightsBy === aMask)
436  return;
437 
438  _highlightsBy = aMask;
439 
440  if ([self hasThemeState:CPThemeStateHighlighted])
441  {
442  [self setNeedsDisplay:YES];
443  [self setNeedsLayout];
444  }
445 }
446 
447 - (CPInteger)highlightsBy
448 {
449  return _highlightsBy;
450 }
451 
452 - (void)setButtonType:(CPButtonType)aButtonType
453 {
454  switch (aButtonType)
455  {
457  [self setHighlightsBy:CPChangeGrayCellMask | CPChangeBackgroundCellMask];
458  [self setShowsStateBy:CPNoCellMask];
459  break;
460 
462  [self setHighlightsBy:CPPushInCellMask | CPChangeGrayCellMask | CPChangeBackgroundCellMask];
463  [self setShowsStateBy:CPNoCellMask];
464  break;
465 
467  [self setHighlightsBy:CPContentsCellMask];
468  [self setShowsStateBy:CPNoCellMask];
469  break;
470 
472  [self setHighlightsBy:CPPushInCellMask | CPChangeGrayCellMask | CPChangeBackgroundCellMask];
473  [self setShowsStateBy:CPChangeBackgroundCellMask | CPChangeGrayCellMask];
474  break;
475 
476  case CPOnOffButton:
477  [self setHighlightsBy:CPChangeGrayCellMask | CPChangeBackgroundCellMask];
478  [self setShowsStateBy:CPChangeGrayCellMask | CPChangeBackgroundCellMask];
479  break;
480 
481  case CPToggleButton:
482  [self setHighlightsBy:CPPushInCellMask | CPContentsCellMask];
483  [self setShowsStateBy:CPContentsCellMask];
484  break;
485 
486  case CPSwitchButton:
487  [CPException raise:CPInvalidArgumentException
488  reason:"The CPSwitchButton type is not supported in Cappuccino, use the CPCheckBox class instead."];
489 
490  case CPRadioButton:
491  [CPException raise:CPInvalidArgumentException
492  reason:"The CPRadioButton type is not supported in Cappuccino, use the CPRadio class instead."];
493 
494  default:
495  [CPException raise:CPInvalidArgumentException
496  reason:"Unknown button type."];
497  }
498 
499  [self setImageDimsWhenDisabled:YES];
500 }
501 
502 - (void)setImageDimsWhenDisabled:(BOOL)imageShouldDimWhenDisabled
503 {
504  imageShouldDimWhenDisabled = !!imageShouldDimWhenDisabled;
505 
506  if (_imageDimsWhenDisabled === imageShouldDimWhenDisabled)
507  return;
508 
509  _imageDimsWhenDisabled = imageShouldDimWhenDisabled;
510 
511  if ([self hasThemeState:CPThemeStateDisabled])
512  {
513  [self setNeedsDisplay:YES];
514  [self setNeedsLayout];
515  }
516 }
517 
518 - (BOOL)imageDimsWhenDisabled
519 {
520  return _imageDimsWhenDisabled;
521 }
522 
523 - (void)setPeriodicDelay:(float)aDelay interval:(float)anInterval
524 {
525  _periodicDelay = aDelay;
526  _periodicInterval = anInterval;
527 }
528 
529 - (void)mouseDown:(CPEvent)anEvent
530 {
531  if ([self isContinuous])
532  {
533  _continuousDelayTimer = [CPTimer scheduledTimerWithTimeInterval:_periodicDelay callback: function()
534  {
535  if (!_continuousTimer)
536  _continuousTimer = [CPTimer scheduledTimerWithTimeInterval:_periodicInterval target:self selector:@selector(onContinousEvent:) userInfo:anEvent repeats:YES];
537  }
538 
539  repeats:NO];
540  }
541 
542  [super mouseDown:anEvent];
543 }
544 
545 - (void)onContinousEvent:(CPTimer)aTimer
546 {
547  if (_target && _action && [_target respondsToSelector:_action])
548  [_target performSelector:_action withObject:self];
549 }
550 
551 - (BOOL)startTrackingAt:(CGPoint)aPoint
552 {
553  _isTracking = YES;
554 
555  var startedTracking = [super startTrackingAt:aPoint];
556 
557  if (_highlightsBy & (CPPushInCellMask | CPChangeGrayCellMask))
558  {
559  if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
560  [self highlight:[self state] == CPOffState];
561  else
562  [self highlight:YES];
563  }
564  else
565  {
566  if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
567  [self highlight:[self state] != CPOffState];
568  else
569  [self highlight:NO];
570  }
571 
572  return startedTracking;
573 }
574 
575 - (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
576 {
577  _isTracking = NO;
578 
579  if (mouseIsUp && CGRectContainsPoint([self bounds], aPoint))
580  [self setNextState];
581  else
582  {
583  if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
584  [self highlight:[self state] != CPOffState];
585  else
586  [self highlight:NO];
587  }
588 
589  [self setNeedsLayout];
590  [self setNeedsDisplay:YES];
591  [self invalidateTimers];
592 }
593 
594 - (void)invalidateTimers
595 {
596  if (_continuousTimer)
597  {
598  [_continuousTimer invalidate];
599  _continuousTimer = nil;
600  }
601 
602  if (_continuousDelayTimer)
603  {
604  [_continuousDelayTimer invalidate];
605  _continuousDelayTimer = nil;
606  }
607 }
608 
609 - (CGRect)contentRectForBounds:(CGRect)bounds
610 {
611  var contentInset = [self currentValueForThemeAttribute:@"content-inset"];
612 
613  return CGRectInsetByInset(bounds, contentInset);
614 }
615 
616 - (CGRect)bezelRectForBounds:(CGRect)bounds
617 {
618  // Is this necessary? The theme itself can just change its inset to a zero inset when !CPThemeStateBordered.
619  if (![self isBordered])
620  return bounds;
621 
622  var bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"];
623 
624  return CGRectInsetByInset(bounds, bezelInset);
625 }
626 
627 - (CGSize)_minimumFrameSize
628 {
629  var size = CGSizeMakeZero(),
630  contentView = [self ephemeralSubviewNamed:@"content-view"];
631 
632  if (contentView)
633  {
634  [contentView sizeToFit];
635  size = [contentView frameSize];
636  }
637  else
638  size = [([self title] || " ") sizeWithFont:[self currentValueForThemeAttribute:@"font"]];
639 
640  var contentInset = [self currentValueForThemeAttribute:@"content-inset"],
641  minSize = [self currentValueForThemeAttribute:@"min-size"],
642  maxSize = [self currentValueForThemeAttribute:@"max-size"];
643 
644  size.width = MAX(size.width + contentInset.left + contentInset.right, minSize.width);
645  size.height = MAX(size.height + contentInset.top + contentInset.bottom, minSize.height);
646 
647  if (maxSize.width >= 0.0)
648  size.width = MIN(size.width, maxSize.width);
649 
650  if (maxSize.height >= 0.0)
651  size.height = MIN(size.height, maxSize.height);
652 
653  return size;
654 }
655 
659 - (void)sizeToFit
660 {
661  [self layoutSubviews];
662 
663  [self setFrameSize:[self _minimumFrameSize]];
664 
665  if ([self ephemeralSubviewNamed:@"content-view"])
666  [self layoutSubviews];
667 }
668 
669 - (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
670 {
671  if (aName === "bezel-view")
672  return [self bezelRectForBounds:[self bounds]];
673 
674  else if (aName === "content-view")
675  return [self contentRectForBounds:[self bounds]];
676 
677  return [super rectForEphemeralSubviewNamed:aName];
678 }
679 
680 - (CPView)createEphemeralSubviewNamed:(CPString)aName
681 {
682  if (aName === "bezel-view")
683  {
684  var view = [[CPView alloc] initWithFrame:CGRectMakeZero()];
685 
686  [view setHitTests:NO];
687 
688  return view;
689  }
690  else
691  return [[_CPImageAndTextView alloc] initWithFrame:CGRectMakeZero()];
692 }
693 
694 - (void)layoutSubviews
695 {
696  var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
697  positioned:CPWindowBelow
698  relativeToEphemeralSubviewNamed:@"content-view"];
699 
700  [bezelView setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
701 
702  var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
703  positioned:CPWindowAbove
704  relativeToEphemeralSubviewNamed:@"bezel-view"];
705 
706  if (contentView)
707  {
708  var title = nil,
709  image = nil;
710 
711  if (_isTracking)
712  {
713  if (_highlightsBy & CPContentsCellMask)
714  {
715  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 = [self alternateTitle];
723  image = [self alternateImage];
724  }
725  }
726  else if (_showsStateBy & CPContentsCellMask)
727  {
728  title = ([self state] != CPOffState && _alternateTitle) ? _alternateTitle : _title;
729  image = ([self state] != CPOffState && [self alternateImage]) ? [self alternateImage] : [self image];
730  }
731  else
732  {
733  title = _title;
734  image = [self image];
735  }
736  }
737  else
738  {
739  if (_showsStateBy & CPContentsCellMask)
740  {
741  title = ([self state] != CPOffState && _alternateTitle) ? _alternateTitle : _title;
742  image = ([self state] != CPOffState && [self alternateImage]) ? [self alternateImage] : [self image];
743  }
744  else
745  {
746  title = _title;
747  image = [self image];
748  }
749  }
750 
751  [contentView setText:title];
752  [contentView setImage:image];
753  [contentView setImageOffset:[self currentValueForThemeAttribute:@"image-offset"]];
754 
755  [contentView setFont:[self currentValueForThemeAttribute:@"font"]];
756  [contentView setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
757  [contentView setAlignment:[self currentValueForThemeAttribute:@"alignment"]];
758  [contentView setVerticalAlignment:[self currentValueForThemeAttribute:@"vertical-alignment"]];
759  [contentView setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
760  [contentView setTextShadowColor:[self currentValueForThemeAttribute:@"text-shadow-color"]];
761  [contentView setTextShadowOffset:[self currentValueForThemeAttribute:@"text-shadow-offset"]];
762  [contentView setImagePosition:[self currentValueForThemeAttribute:@"image-position"]];
763  [contentView setImageScaling:[self currentValueForThemeAttribute:@"image-scaling"]];
764  [contentView setDimsImage:[self hasThemeState:CPThemeStateDisabled] && _imageDimsWhenDisabled];
765  }
766 }
767 
768 - (void)setBordered:(BOOL)shouldBeBordered
769 {
770  if (shouldBeBordered)
771  [self setThemeState:CPThemeStateBordered];
772  else
773  [self unsetThemeState:CPThemeStateBordered];
774 }
775 
776 - (BOOL)isBordered
777 {
778  return [self hasThemeState:CPThemeStateBordered];
779 }
780 
787 - (void)setKeyEquivalent:(CPString)aString
788 {
789  _keyEquivalent = aString || @"";
790 
791  // Check if the key equivalent is the enter key
792  // Treat \r and \n as the same key equivalent. See issue #710.
793  if (aString === CPNewlineCharacter || aString === CPCarriageReturnCharacter)
794  [self setThemeState:CPThemeStateDefault];
795  else
796  [self unsetThemeState:CPThemeStateDefault];
797 }
798 
799 - (void)viewWillMoveToWindow:(CPWindow)aWindow
800 {
801  var selfWindow = [self window];
802 
803  if (selfWindow === aWindow || aWindow === nil)
804  return;
805 
806  if ([selfWindow defaultButton] === self)
807  [selfWindow setDefaultButton:nil];
808 
809  if ([self keyEquivalent] === CPNewlineCharacter || [self keyEquivalent] === CPCarriageReturnCharacter)
810  [aWindow setDefaultButton:self];
811 }
812 
816 - (CPString)keyEquivalent
817 {
818  return _keyEquivalent;
819 }
820 
824 - (void)setKeyEquivalentModifierMask:(unsigned)aMask
825 {
826  _keyEquivalentModifierMask = aMask;
827 }
828 
832 - (unsigned)keyEquivalentModifierMask
833 {
834  return _keyEquivalentModifierMask;
835 }
836 
841 - (BOOL)performKeyEquivalent:(CPEvent)anEvent
842 {
843  // Don't handle the key equivalent for the default window because the window will handle it for us
844  if ([[self window] defaultButton] === self)
845  return NO;
846 
847  if (![anEvent _triggersKeyEquivalent:[self keyEquivalent] withModifierMask:[self keyEquivalentModifierMask]])
848  return NO;
849 
850  [self performClick:nil];
851 
852  return YES;
853 }
854 
860 - (void)performClick:(id)sender
861 {
862  // This is slightly different from [super performClick:] in that the highlight behaviour is dependent on
863  // highlightsBy and showsStateBy.
864  if (![self isEnabled])
865  return;
866 
867  [self setState:[self nextState]];
868 
869  var shouldHighlight = NO;
870 
871  if (_highlightsBy & (CPPushInCellMask | CPChangeGrayCellMask))
872  {
873  if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
874  shouldHighlight = [self state] == CPOffState;
875  else
876  shouldHighlight = YES;
877  }
878 
879  [self highlight:shouldHighlight];
880 
881  try
882  {
883  [self sendAction:[self action] to:[self target]];
884  }
885  catch (e)
886  {
887  throw e;
888  }
889  finally
890  {
891  if (shouldHighlight)
893  }
894 }
895 
896 @end
897 
898 @implementation CPButton (NS)
899 
900 - (void)setBezelStyle:(unsigned)aBezelStyle
901 {
902  if (aBezelStyle === _bezelStyle)
903  return;
904 
905  var currentState = [CPButtonBezelStyleStateMap objectForKey:_bezelStyle],
906  newState = [CPButtonBezelStyleStateMap objectForKey:aBezelStyle];
907 
908  if (currentState)
909  [self unsetThemeState:currentState];
910 
911  if (newState)
912  [self setThemeState:newState];
913 
914  _bezelStyle = aBezelStyle;
915 }
916 
917 - (unsigned)bezelStyle
918 {
919  return _bezelStyle;
920 }
921 
922 @end
923 
924 
925 var CPButtonImageKey = @"CPButtonImageKey",
926  CPButtonAlternateImageKey = @"CPButtonAlternateImageKey",
927  CPButtonTitleKey = @"CPButtonTitleKey",
928  CPButtonAlternateTitleKey = @"CPButtonAlternateTitleKey",
929  CPButtonIsBorderedKey = @"CPButtonIsBorderedKey",
930  CPButtonAllowsMixedStateKey = @"CPButtonAllowsMixedStateKey",
931  CPButtonImageDimsWhenDisabledKey = @"CPButtonImageDimsWhenDisabledKey",
932  CPButtonImagePositionKey = @"CPButtonImagePositionKey",
933  CPButtonKeyEquivalentKey = @"CPButtonKeyEquivalentKey",
934  CPButtonKeyEquivalentMaskKey = @"CPButtonKeyEquivalentMaskKey",
935  CPButtonPeriodicDelayKey = @"CPButtonPeriodicDelayKey",
936  CPButtonPeriodicIntervalKey = @"CPButtonPeriodicIntervalKey",
937  CPButtonHighlightsByKey = @"CPButtonHighlightsByKey",
938  CPButtonShowsStateByKey = @"CPButtonShowsStateByKey";
939 
940 @implementation CPButton (CPCoding)
941 
946 - (id)initWithCoder:(CPCoder)aCoder
947 {
948  self = [super initWithCoder:aCoder];
949 
950  if (self)
951  {
952  [self _init];
953 
954  _title = [aCoder decodeObjectForKey:CPButtonTitleKey];
955  _alternateTitle = [aCoder decodeObjectForKey:CPButtonAlternateTitleKey];
956  _allowsMixedState = [aCoder decodeBoolForKey:CPButtonAllowsMixedStateKey];
957 
958  if ([aCoder containsValueForKey:CPButtonHighlightsByKey])
959  {
960  // If one exists, assume both do.
961  _highlightsBy = [aCoder decodeIntForKey:CPButtonHighlightsByKey];
962  _showsStateBy = [aCoder decodeIntForKey:CPButtonShowsStateByKey];
963  }
964  else
965  {
966  // Backwards compatibility: if this CPButton was encoded before coding of
967  // highlightsBy and showsStateBy were added, we should just use the
968  // default values from _init rather than overwriting with 0, 0.
969  }
970 
971  [self setImageDimsWhenDisabled:[aCoder decodeObjectForKey:CPButtonImageDimsWhenDisabledKey]];
972 
973  if ([aCoder containsValueForKey:CPButtonImagePositionKey])
974  [self setImagePosition:[aCoder decodeIntForKey:CPButtonImagePositionKey]];
975 
976  if ([aCoder containsValueForKey:CPButtonKeyEquivalentKey])
977  [self setKeyEquivalent:CFData.decodeBase64ToUtf16String([aCoder decodeObjectForKey:CPButtonKeyEquivalentKey])];
978 
979  if ([aCoder containsValueForKey:CPButtonPeriodicDelayKey])
980  _periodicDelay = [aCoder decodeObjectForKey:CPButtonPeriodicDelayKey];
981 
982  if ([aCoder containsValueForKey:CPButtonPeriodicIntervalKey])
983  _periodicInterval = [aCoder decodeObjectForKey:CPButtonPeriodicIntervalKey];
984 
985  _keyEquivalentModifierMask = [aCoder decodeIntForKey:CPButtonKeyEquivalentMaskKey];
986 
987  [self setNeedsLayout];
988  [self setNeedsDisplay:YES];
989  }
990 
991  return self;
992 }
993 
998 - (void)encodeWithCoder:(CPCoder)aCoder
999 {
1000  [super encodeWithCoder:aCoder];
1001  [self invalidateTimers];
1002 
1003  [aCoder encodeObject:_title forKey:CPButtonTitleKey];
1004  [aCoder encodeObject:_alternateTitle forKey:CPButtonAlternateTitleKey];
1005 
1006  [aCoder encodeBool:_allowsMixedState forKey:CPButtonAllowsMixedStateKey];
1007 
1008  [aCoder encodeInt:_highlightsBy forKey:CPButtonHighlightsByKey];
1009  [aCoder encodeInt:_showsStateBy forKey:CPButtonShowsStateByKey];
1010 
1011  [aCoder encodeBool:[self imageDimsWhenDisabled] forKey:CPButtonImageDimsWhenDisabledKey];
1012  [aCoder encodeInt:[self imagePosition] forKey:CPButtonImagePositionKey];
1013 
1014  if (_keyEquivalent)
1015  [aCoder encodeObject:CFData.encodeBase64Utf16String(_keyEquivalent) forKey:CPButtonKeyEquivalentKey];
1016 
1017  [aCoder encodeInt:_keyEquivalentModifierMask forKey:CPButtonKeyEquivalentMaskKey];
1018 
1019  [aCoder encodeObject:_periodicDelay forKey:CPButtonPeriodicDelayKey];
1020  [aCoder encodeObject:_periodicInterval forKey:CPButtonPeriodicIntervalKey];
1021 }
1022 
1023 @end
Used to implement exception handling (creating & raising).
Definition: CPException.h:2
CPToggleButton
Definition: CPButton.j:48
CPImage alternateImage()
Definition: CPButton.j:394
void layoutSubviews()
Definition: CPButton.j:694
CPBezelStyle CPRoundedBezelStyle
Definition: CPButton.j:27
CPRoundedDisclosureBezelStyle
Definition: CPButton.j:40
var CPButtonPeriodicDelayKey
Definition: CPButton.j:935
var CPButtonAlternateImageKey
Definition: CPButton.j:926
BOOL setThemeState:(ThemeState aState)
Definition: CPView.j:3255
CPChangeGrayCellMask
Definition: CPButton.j:66
void setValue:forThemeAttribute:inState:(id aValue, [forThemeAttribute] CPString aName, [inState] ThemeState aState)
Definition: CPView.j:3371
CPThickSquareBezelStyle
Definition: CPButton.j:29
An object representation of nil.
Definition: CPNull.h:2
CPSwitchButton
Definition: CPButton.j:49
CGRect bezelRectForBounds:(CGRect bounds)
Definition: CPButton.j:616
void setBordered:(BOOL shouldBeBordered)
Definition: CPButton.j:768
void setAllowsMixedState:(BOOL aFlag)
Definition: CPButton.j:222
BOOL startTrackingAt:(CGPoint aPoint)
Definition: CPControl.j:468
void setImageDimsWhenDisabled:(BOOL imageShouldDimWhenDisabled)
Definition: CPButton.j:502
CPInteger nextState()
Definition: CPButton.j:287
CPButtonStateMixed
Definition: CPButton.j:69
CPInteger state()
Definition: CPButton.j:325
CPButtonStateBezelStyleRounded
Definition: CPButton.j:70
CPCellImagePosition imagePosition()
Definition: CPControl.j:925
void setIntValue:(int anObject)
Definition: CPControl.j:588
CPRoundRectBezelStyle
Definition: CPButton.j:38
id initWithFrame:(CGRect aFrame)
Definition: CPControl.j:183
CGRect bounds()
Definition: CPView.j:1326
CPButtonStateBezelStyleRoundRect
Definition: CPButton.j:71
void raise:reason:(CPString aName, [reason] CPString aReason)
Definition: CPException.j:66
void setControlSize:(CPControlSize aControlSize)
Definition: CPControl.j:211
CPTimer scheduledTimerWithTimeInterval:callback:repeats:(CPTimeInterval seconds, [callback] Function aFunction, [repeats] BOOL shouldRepeat)
Definition: CPTimer.j:70
var CPButtonShowsStateByKey
Definition: CPButton.j:938
void setValue:forThemeAttribute:(id aValue, [forThemeAttribute] CPString aName)
Definition: CPView.j:3384
void setImagePosition:(CPCellImagePosition position)
Definition: CPControl.j:917
CPMomentaryLight
Definition: CPButton.j:55
var CPButtonBezelStyleStateMap
Definition: CPButton.j:74
id buttonWithTitle:theme:(CPString aTitle, [theme] CPTheme aTheme)
Definition: CPButton.j:129
A mutable key-value pair collection.
Definition: CPDictionary.h:2
CGRect contentRectForBounds:(CGRect bounds)
Definition: CPButton.j:609
void invalidateTimers()
Definition: CPButton.j:594
CGRect bounds()
Definition: CALayer.j:203
CPWindow window()
Definition: CPView.j:527
CPMixedState
Definition: CPControl.j:79
var CPButtonIsBorderedKey
Definition: CPButton.j:929
CPDisclosureBezelStyle
Definition: CPButton.j:31
CPThemeStateDisabled
Definition: CPTheme.j:610
CPChangeBackgroundCellMask
Definition: CPButton.j:67
void setKeyEquivalent:(CPString aString)
Definition: CPButton.j:787
An immutable string (collection of characters).
Definition: CPString.h:2
CPNull null()
Definition: CPNull.j:51
var CPButtonTitleKey
Definition: CPButton.j:927
void setState:(CPInteger aState)
Definition: CPButton.j:317
var CPButtonAlternateTitleKey
Definition: CPButton.j:928
void setHighlightsBy:(CPInteger aMask)
Definition: CPButton.j:433
Definition: CPImage.h:2
void mouseDown:(CPEvent anEvent)
Definition: CPControl.j:496
CPShadowlessSquareBezelStyle
Definition: CPButton.j:32
BOOL sendAction:to:(SEL anAction, [to] id anObject)
Definition: CPControl.j:319
SEL action()
Definition: CPControl.j:290
CPPushOnPushOffButton
Definition: CPButton.j:47
id initWithCoder:(CPCoder aCoder)
Definition: CPControl.j:1092
var CPButtonKeyEquivalentKey
Definition: CPButton.j:933
CPContentsCellMask
Definition: CPButton.j:64
CPOnOffButton
Definition: CPButton.j:52
var CPButtonImagePositionKey
Definition: CPButton.j:932
void setNeedsDisplay:(BOOL aFlag)
Definition: CPView.j:2597
void setObjectValue:(id anObject)
Definition: CPControl.j:534
BOOL imageDimsWhenDisabled()
Definition: CPButton.j:518
CPBackgroundButtonMask
Definition: CPButton.j:61
CPHUDBezelStyle
Definition: CPButton.j:41
CPRecessedBezelStyle
Definition: CPButton.j:39
CPPushInCellMask
Definition: CPButton.j:65
CPNoButtonMask
Definition: CPButton.j:57
void performClick:(id sender)
Definition: CPButton.j:860
CPTimer scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:(CPTimeInterval seconds, [target] id aTarget, [selector] SEL aSelector, [userInfo] id userInfo, [repeats] BOOL shouldRepeat)
Definition: CPTimer.j:58
var CPButtonPeriodicIntervalKey
Definition: CPButton.j:936
void setDefaultButton:(CPButton aButton)
Definition: CPWindow.j:3366
CPRegularSquareBezelStyle
Definition: CPButton.j:28
CPMomentaryPushInButton
Definition: CPButton.j:53
CPContentsButtonMask
Definition: CPButton.j:58
CPTheme defaultTheme()
Definition: CPTheme.j:44
void setNeedsLayout()
Definition: CPView.j:2748
id target()
Definition: CPControl.j:308
void setBezelStyle:(unsigned aBezelStyle)
Definition: CPButton.j:900
CPPushInButtonMask
Definition: CPButton.j:59
void highlight:(BOOL shouldHighlight)
Definition: CPControl.j:980
A timer object that can send a message after the given time interval.
Definition: CPTimer.h:2
Defines methods for use when archiving & restoring (enc/decoding).
Definition: CPCoder.h:2
CPNoCellMask
Definition: CPButton.j:63
CPThemeStateHighlighted
Definition: CPTheme.j:612
CPGrayButtonMask
Definition: CPButton.j:60
BOOL unsetThemeState:(ThemeState aState)
Definition: CPView.j:3268
var CPButtonKeyEquivalentMaskKey
Definition: CPButton.j:934
CPString alternateTitle()
Definition: CPButton.j:367
void setShowsStateBy:(CPInteger aMask)
Definition: CPButton.j:409
var CPButtonImageKey
Definition: CPButton.j:925
CPMomentaryChangeButton
Definition: CPButton.j:51
Definition: CPTheme.h:2
CPNewlineCharacter
Definition: CPText.j:52
CPRadioButton
Definition: CPButton.j:50
void setNextState()
Definition: CPButton.j:304
CPCarriageReturnCharacter
Definition: CPText.j:54
void setFrameSize:(CGSize aSize)
Definition: CPView.j:1124
CPHelpButtonBezelStyle
Definition: CPButton.j:35
CPMomentaryPushButton
Definition: CPButton.j:54
CPImage image()
Definition: CPButton.j:377
var CPButtonAllowsMixedStateKey
Definition: CPButton.j:930
Definition: CPEvent.h:2
CPTexturedSquareBezelStyle
Definition: CPButton.j:34
CPOffState
Definition: CPControl.j:78
int intValue()
Definition: CPControl.j:579
CPOnState
Definition: CPControl.j:77
A bridged object to native Javascript numbers.
Definition: CPNumber.h:2
CPCircularBezelStyle
Definition: CPButton.j:33
CPSmallSquareBezelStyle
Definition: CPButton.j:36
void encodeWithCoder:(CPCoder aCoder)
Definition: CPControl.j:1121
var CPButtonImageDimsWhenDisabledKey
Definition: CPButton.j:931
CPTexturedRoundedBezelStyle
Definition: CPButton.j:37
CPButtonType CPMomentaryLightButton
Definition: CPButton.j:46
CPView layoutEphemeralSubviewNamed:positioned:relativeToEphemeralSubviewNamed:(CPString aViewName, [positioned] CPWindowOrderingMode anOrderingMode, [relativeToEphemeralSubviewNamed] CPString relativeToViewName)
Definition: CPView.j:3407
CGRect rectForEphemeralSubviewNamed:(CPString aViewName)
Definition: CPView.j:3402
Definition: CPView.j:137
CPThickerSquareBezelStyle
Definition: CPButton.j:30
var CPButtonHighlightsByKey
Definition: CPButton.j:937