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