00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 @import "_CPImageAndTextView.j"
00024 @import "CGGeometry.j"
00025
00026 @import "CPControl.j"
00027 @import "CPStringDrawing.j"
00028
00029 #include "CoreGraphics/CGGeometry.h"
00030
00031
00032
00033
00034 CPRoundedBezelStyle = 1;
00035 CPRegularSquareBezelStyle = 2;
00036 CPThickSquareBezelStyle = 3;
00037 CPThickerSquareBezelStyle = 4;
00038 CPDisclosureBezelStyle = 5;
00039 CPShadowlessSquareBezelStyle = 6;
00040 CPCircularBezelStyle = 7;
00041 CPTexturedSquareBezelStyle = 8;
00042 CPHelpButtonBezelStyle = 9;
00043 CPSmallSquareBezelStyle = 10;
00044 CPTexturedRoundedBezelStyle = 11;
00045 CPRoundRectBezelStyle = 12;
00046 CPRecessedBezelStyle = 13;
00047 CPRoundedDisclosureBezelStyle = 14;
00048 CPHUDBezelStyle = -1;
00049
00050
00051
00052 CPMomentaryLightButton = 0;
00053 CPPushOnPushOffButton = 1;
00054 CPToggleButton = 2;
00055 CPSwitchButton = 3;
00056 CPRadioButton = 4;
00057 CPMomentaryChangeButton = 5;
00058 CPOnOffButton = 6;
00059 CPMomentaryPushInButton = 7;
00060 CPMomentaryPushButton = 0;
00061 CPMomentaryLight = 7;
00062
00063 CPNoButtonMask = 0;
00064 CPContentsButtonMask = 1;
00065 CPPushInButtonMask = 2;
00066 CPGrayButtonMask = 4;
00067 CPBackgroundButtonMask = 8;
00068
00069 CPNoCellMask = CPNoButtonMask;
00070 CPContentsCellMask = CPContentsButtonMask;
00071 CPPushInCellMask = CPPushInButtonMask;
00072 CPChangeGrayCellMask = CPGrayButtonMask;
00073 CPChangeBackgroundCellMask = CPBackgroundButtonMask;
00074
00075 CPButtonStateMixed = CPThemeState("mixed");
00076
00085 @implementation CPButton : CPControl
00086 {
00087 BOOL _allowsMixedState;
00088
00089 CPString _title;
00090 CPString _alternateTitle;
00091
00092 CPImage _image;
00093 CPImage _alternateImage;
00094
00095 CPInteger _showsStateBy;
00096 CPInteger _highlightsBy;
00097 BOOL _imageDimsWhenDisabled;
00098
00099
00100 CPBezelStyle _bezelStyle;
00101 CPControlSize _controlSize;
00102 }
00103
00104 + (id)buttonWithTitle:(CPString)aTitle
00105 {
00106 return [self buttonWithTitle:aTitle theme:[CPTheme defaultTheme]];
00107 }
00108
00109 + (id)buttonWithTitle:(CPString)aTitle theme:(CPTheme)aTheme
00110 {
00111 var button = [[self alloc] init];
00112
00113 [button setTheme:aTheme];
00114 [button setTitle:aTitle];
00115 [button sizeToFit];
00116
00117 return button;
00118 }
00119
00120 + (CPString)themeClass
00121 {
00122 return @"button";
00123 }
00124
00125 + (id)themeAttributes
00126 {
00127 return [CPDictionary dictionaryWithObjects:[_CGInsetMakeZero(), _CGInsetMakeZero(), [CPNull null]]
00128 forKeys:[@"bezel-inset", @"content-inset", @"bezel-color"]];
00129 }
00130
00131 - (id)initWithFrame:(CGRect)aFrame
00132 {
00133 self = [super initWithFrame:aFrame];
00134
00135 if (self)
00136 {
00137
00138 [self setValue:CPCenterTextAlignment forThemeAttribute:@"alignment"];
00139 [self setValue:CPCenterVerticalTextAlignment forThemeAttribute:@"vertical-alignment"];
00140 [self setValue:CPImageLeft forThemeAttribute:@"image-position"];
00141 [self setValue:CPScaleNone forThemeAttribute:@"image-scaling"];
00142
00143 _controlSize = CPRegularControlSize;
00144
00145
00146 [self setBordered:YES];
00147 }
00148
00149 return self;
00150 }
00151
00152
00156 - (BOOL)allowsMixedState
00157 {
00158 return _allowsMixedState;
00159 }
00160
00165 - (void)setAllowsMixedState:(BOOL)aFlag
00166 {
00167 aFlag = !!aFlag;
00168
00169 if (_allowsMixedState === aFlag)
00170 return;
00171
00172 _allowsMixedState = aFlag;
00173
00174 if (!_allowsMixedState && [self state] === CPMixedState)
00175 [self setState:CPOnState];
00176 }
00177
00178 - (void)setObjectValue:(id)anObjectValue
00179 {
00180 if (!anObjectValue || anObjectValue === @"" || ([anObjectValue intValue] === 0))
00181 anObjectValue = CPOffState;
00182
00183 else if (![anObjectValue isKindOfClass:[CPNumber class]])
00184 anObjectValue = CPOnState;
00185
00186 else if (anObjectValue > CPOnState)
00187 anObjectValue = CPOnState
00188
00189 else if (anObjectValue < CPOffState)
00190 if ([self allowsMixedState])
00191 anObjectValue = CPMixedState;
00192
00193 else
00194 anObjectValue = CPOnState;
00195
00196 [super setObjectValue:anObjectValue];
00197
00198 switch ([self objectValue])
00199 {
00200 case CPMixedState: [self unsetThemeState:CPThemeStateSelected];
00201 [self setThemeState:CPButtonStateMixed];
00202 break;
00203
00204 case CPOnState: [self unsetThemeState:CPButtonStateMixed];
00205 [self setThemeState:CPThemeStateSelected];
00206 break;
00207
00208 case CPOffState: [self unsetThemeState:CPThemeStateSelected | CPButtonStateMixed];
00209 }
00210 }
00211
00212 - (CPInteger)nextState
00213 {
00214 if ([self allowsMixedState])
00215 {
00216 var value = [self state];
00217
00218 return value - ((value === -1) ? -2 : 1);
00219 }
00220
00221 return 1 - [self state];
00222 }
00223
00224 - (void)setNextState
00225 {
00226 [self setState:[self nextState]];
00227 }
00228
00234 - (void)setState:(CPInteger)aState
00235 {
00236 [self setIntValue:aState];
00237 }
00238
00242 - (CPInteger)state
00243 {
00244 return [self intValue];
00245 }
00246
00247 - (void)setTitle:(CPString)aTitle
00248 {
00249 if (_title === aTitle)
00250 return;
00251
00252 _title = aTitle;
00253
00254 [self setNeedsLayout];
00255 [self setNeedsDisplay:YES];
00256 }
00257
00258 - (CPString)title
00259 {
00260 return _title;
00261 }
00262
00263 - (void)setAlternateTitle:(CPString)aTitle
00264 {
00265 if (_alternateTitle === aTitle)
00266 return;
00267
00268 _alternateTitle = aTitle;
00269
00270 [self setNeedsLayout];
00271 [self setNeedsDisplay:YES];
00272 }
00273
00274 - (CPString)alternateTitle
00275 {
00276 return _alternateTitle;
00277 }
00278
00279 - (void)setImage:(CPImage)anImage
00280 {
00281 if (_image === anImage)
00282 return;
00283
00284 _image = anImage;
00285
00286 [self setNeedsLayout];
00287 [self setNeedsDisplay:YES];
00288 }
00289
00290 - (CPImage)image
00291 {
00292 return _image;
00293 }
00294
00299 - (void)setAlternateImage:(CPImage)anImage
00300 {
00301 if (_alternateImage === anImage)
00302 return;
00303
00304 _alternateImage = anImage;
00305
00306 [self setNeedsLayout];
00307 [self setNeedsDisplay:YES];
00308 }
00309
00313 - (CPImage)alternateImage
00314 {
00315 return _alternateImage;
00316 }
00317
00318 - (void)setShowsStateBy:(CPInteger)aMask
00319 {
00320 if (_showsStateBy === aMask)
00321 return;
00322
00323 _showsStateBy = aMask;
00324
00325 [self setNeedsDisplay:YES];
00326 [self setNeedsLayout];
00327 }
00328
00329 - (CPInteger)showsStateBy
00330 {
00331 return _showsStateBy;
00332 }
00333
00334 - (void)setHighlightsBy:(CPInteger)aMask
00335 {
00336 if (_highlightsBy === aMask)
00337 return;
00338
00339 _highlightsBy = aMask;
00340
00341 if ([self hasThemeState:CPThemeStateHighlighted])
00342 {
00343 [self setNeedsDisplay:YES];
00344 [self setNeedsLayout];
00345 }
00346 }
00347
00348 - (void)setButtonType:(CPButtonType)aButtonType
00349 {
00350 switch (aButtonType)
00351 {
00352 case CPMomentaryLightButton: [self setHighlightsBy:CPChangeBackgroundCellMask];
00353 [self setShowsStateBy:CPNoCellMask];
00354 break;
00355
00356 case CPMomentaryPushInButton: [self setHighlightsBy:CPPushInCellMask | CPChangeGrayCellMask];
00357 [self setShowsStateBy:CPNoCellMask];
00358 break;
00359
00360 case CPMomentaryChangeButton: [self setHighlightsBy:CPContentsCellMask];
00361 [self setShowsStateBy:CPNoCellMask];
00362 break;
00363
00364 case CPPushOnPushOffButton: [self setHighlightsBy:CPPushInCellMask | CPChangeGrayCellMask];
00365 [self setShowsStateBy:CPChangeBackgroundCellMask];
00366 break;
00367
00368 case CPOnOffButton: [self setHighlightsBy:CPChangeBackgroundCellMask];
00369 [self setShowsStateBy:CPChangeBackgroundCellMask];
00370 break;
00371
00372 case CPToggleButton: [self setHighlightsBy:CPPushInCellMask | CPContentsCellMask];
00373 [self setShowsStateBy:CPContentsCellMask];
00374 break;
00375
00376 case CPSwitchButton: [CPException raise:CPInvalidArgumentException
00377 reason:"The CPSwitchButton type is not supported in Cappuccino, use the CPCheckBox class instead."];
00378
00379 case CPRadioButton: [CPException raise:CPInvalidArgumentException
00380 reason:"The CPRadioButton type is not supported in Cappuccino, use the CPRadio class instead."];
00381
00382 default: [CPException raise:CPInvalidArgumentException
00383 reason:"Unknown button type."];
00384 }
00385
00386 [self setImageDimsWhenDisabled:YES];
00387 }
00388
00389 - (void)setImageDimsWhenDisabled:(BOOL)imageShouldDimWhenDisabled
00390 {
00391 imageShouldDimWhenDisabled = !!imageShouldDimWhenDisabled;
00392
00393 if (_imageDimsWhenDisabled === imageShouldDimWhenDisabled)
00394 return;
00395
00396 _imageDimsWhenDisabled = imageShouldDimWhenDisabled;
00397
00398 if (_imageDimsWhenDisabled)
00399 {
00400 [self setNeedsDisplay:YES];
00401 [self setNeedsLayout];
00402 }
00403 }
00404
00405 - (BOOL)imageDimsWhenDisabled
00406 {
00407 return _imageDimsWhenDisabled;
00408 }
00409
00410 - (BOOL)startTrackingAt:(CGPoint)aPoint
00411 {
00412 [self highlight:YES];
00413
00414 return [super startTrackingAt:aPoint];
00415 }
00416
00417 - (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
00418 {
00419 [self highlight:NO];
00420
00421 [super stopTracking:lastPoint at:aPoint mouseIsUp:mouseIsUp];
00422
00423 if (mouseIsUp && CGRectContainsPoint([self bounds], aPoint))
00424 [self setNextState];
00425 }
00426
00427 - (CGRect)contentRectForBounds:(CGRect)bounds
00428 {
00429 var contentInset = [self currentValueForThemeAttribute:@"content-inset"];
00430
00431 if (_CGInsetIsEmpty(contentInset))
00432 return bounds;
00433
00434 bounds.origin.x += contentInset.left;
00435 bounds.origin.y += contentInset.top;
00436 bounds.size.width -= contentInset.left + contentInset.right;
00437 bounds.size.height -= contentInset.top + contentInset.bottom;
00438
00439 return bounds;
00440 }
00441
00442 - (CGRect)bezelRectForBounds:(CGRect)bounds
00443 {
00444 if (![self isBordered])
00445 return _CGRectMakeZero();
00446
00447 var bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"];
00448
00449 if (_CGInsetIsEmpty(bezelInset))
00450 return bounds;
00451
00452 bounds.origin.x += bezelInset.left;
00453 bounds.origin.y += bezelInset.top;
00454 bounds.size.width -= bezelInset.left + bezelInset.right;
00455 bounds.size.height -= bezelInset.top + bezelInset.bottom;
00456
00457 return bounds;
00458 }
00459
00463 - (void)sizeToFit
00464 {
00465 var size = [([self title] || " ") sizeWithFont:[self currentValueForThemeAttribute:@"font"]],
00466 contentInset = [self currentValueForThemeAttribute:@"content-inset"],
00467 minSize = [self currentValueForThemeAttribute:@"min-size"],
00468 maxSize = [self currentValueForThemeAttribute:@"max-size"];
00469
00470 size.width = MAX(size.width + contentInset.left + contentInset.right, minSize.width);
00471 size.height = MAX(size.height + contentInset.top + contentInset.bottom, minSize.height);
00472
00473 if (maxSize.width >= 0.0)
00474 size.width = MIN(size.width, maxSize.width);
00475
00476 if (maxSize.height >= 0.0)
00477 size.height = MIN(size.height, maxSize.height);
00478
00479 [self setFrameSize:size];
00480 }
00481
00482 - (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
00483 {
00484 if (aName === "bezel-view")
00485 return [self bezelRectForBounds:[self bounds]];
00486
00487 else if (aName === "content-view")
00488 return [self contentRectForBounds:[self bounds]];
00489
00490 return [super rectForEphemeralSubviewNamed:aName];
00491 }
00492
00493 - (CPView)createEphemeralSubviewNamed:(CPString)aName
00494 {
00495 if (aName === "bezel-view")
00496 {
00497 var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
00498
00499 [view setHitTests:NO];
00500
00501 return view;
00502 }
00503 else
00504 return [[_CPImageAndTextView alloc] initWithFrame:_CGRectMakeZero()];
00505 }
00506
00507 - (void)layoutSubviews
00508 {
00509 var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
00510 positioned:CPWindowBelow
00511 relativeToEphemeralSubviewNamed:@"content-view"];
00512
00513 [bezelView setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
00514
00515 var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
00516 positioned:CPWindowAbove
00517 relativeToEphemeralSubviewNamed:@"bezel-view"];
00518
00519 if (contentView)
00520 {
00521 [contentView setText:([self hasThemeState:CPThemeStateHighlighted] && _alternateTitle) ? _alternateTitle : _title];
00522 [contentView setImage:([self hasThemeState:CPThemeStateHighlighted] && _alternateImage) ? _alternateImage : _image];
00523
00524 [contentView setFont:[self currentValueForThemeAttribute:@"font"]];
00525 [contentView setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
00526 [contentView setAlignment:[self currentValueForThemeAttribute:@"alignment"]];
00527 [contentView setVerticalAlignment:[self currentValueForThemeAttribute:@"vertical-alignment"]];
00528 [contentView setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
00529 [contentView setTextShadowColor:[self currentValueForThemeAttribute:@"text-shadow-color"]];
00530 [contentView setTextShadowOffset:[self currentValueForThemeAttribute:@"text-shadow-offset"]];
00531 [contentView setImagePosition:[self currentValueForThemeAttribute:@"image-position"]];
00532 [contentView setImageScaling:[self currentValueForThemeAttribute:@"image-scaling"]];
00533 }
00534 }
00535
00536 - (void)setDefaultButton:(BOOL)shouldBeDefaultButton
00537 {
00538 if (shouldBeDefaultButton)
00539 [self setThemeState:CPThemeStateDefault];
00540 else
00541 [self unsetThemeState:CPThemeStateDefault];
00542 }
00543
00544 - (void)setBordered:(BOOL)shouldBeBordered
00545 {
00546 if (shouldBeBordered)
00547 [self setThemeState:CPThemeStateBordered];
00548 else
00549 [self unsetThemeState:CPThemeStateBordered];
00550 }
00551
00552 - (BOOL)isBordered
00553 {
00554 return [self hasThemeState:CPThemeStateBordered];
00555 }
00556
00557 @end
00558
00559 @implementation CPButton (NS)
00560
00561 - (void)setBezelStyle:(unsigned)aBezelStyle
00562 {
00563 }
00564
00565 - (unsigned)bezelStyle
00566 {
00567 }
00568
00569 @end
00570
00571
00572 var CPButtonImageKey = @"CPButtonImageKey",
00573 CPButtonAlternateImageKey = @"CPButtonAlternateImageKey",
00574 CPButtonTitleKey = @"CPButtonTitleKey",
00575 CPButtonAlternateTitleKey = @"CPButtonAlternateTitleKey",
00576 CPButtonIsBorderedKey = @"CPButtonIsBorderedKey";
00577
00578 @implementation CPButton (CPCoding)
00579
00584 - (id)initWithCoder:(CPCoder)aCoder
00585 {
00586 self = [super initWithCoder:aCoder];
00587
00588 if (self)
00589 {
00590 _controlSize = CPRegularControlSize;
00591
00592 [self setImage:[aCoder decodeObjectForKey:CPButtonImageKey]];
00593 [self setAlternateImage:[aCoder decodeObjectForKey:CPButtonAlternateImageKey]];
00594
00595 [self setTitle:[aCoder decodeObjectForKey:CPButtonTitleKey]];
00596 [self setAlternateTitle:[aCoder decodeObjectForKey:CPButtonAlternateTitleKey]];
00597
00598 [self setNeedsLayout];
00599 [self setNeedsDisplay:YES];
00600 }
00601
00602 return self;
00603 }
00604
00609 - (void)encodeWithCoder:(CPCoder)aCoder
00610 {
00611 [super encodeWithCoder:aCoder];
00612
00613 [aCoder encodeObject:_image forKey:CPButtonImageKey];
00614 [aCoder encodeObject:_alternateImage forKey:CPButtonAlternateImageKey];
00615
00616 [aCoder encodeObject:_title forKey:CPButtonTitleKey];
00617 [aCoder encodeObject:_alternateTitle forKey:CPButtonAlternateTitleKey];
00618 }
00619
00620 @end
00621
00622 @import "CPCheckBox.j"
00623 @import "CPRadio.j"