/*
* CPButton.j
* AppKit
*
* Created by Francisco Tolmasky.
* Copyright 2008, 280 North, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import "_CPImageAndTitleView.j"
@import "CGGeometry.j"
@import "CPControl.j"
#include "CoreGraphics/CGGeometry.h"
CPScaleProportionally = 0;
CPScaleToFit = 1;
CPScaleNone = 2;
/*
@global
@group CPCellImagePosition
*/
CPNoImage = 0;
/*
@global
@group CPCellImagePosition
*/
CPImageOnly = 1;
/*
@global
@group CPCellImagePosition
*/
CPImageLeft = 2;
/*
@global
@group CPCellImagePosition
*/
CPImageRight = 3;
/*
@global
@group CPCellImagePosition
*/
CPImageBelow = 4;
/*
@global
@group CPCellImagePosition
*/
CPImageAbove = 5;
/*
@global
@group CPCellImagePosition
*/
CPImageOverlaps = 6;
/*
@global
@class CPButton
*/
CPOnState = 1;
/*
@global
@class CPButton
*/
CPOffState = 0;
/*
@global
@class CPButton
*/
CPMixedState = -1;
/*
@global
@group CPBezelStyle
*/
CPRoundedBezelStyle = 1;
/*
@global
@group CPBezelStyle
*/
CPRegularSquareBezelStyle = 2;
/*
@global
@group CPBezelStyle
*/
CPThickSquareBezelStyle = 3;
/*
@global
@group CPBezelStyle
*/
CPThickerSquareBezelStyle = 4;
/*
@global
@group CPBezelStyle
*/
CPDisclosureBezelStyle = 5;
/*
@global
@group CPBezelStyle
*/
CPShadowlessSquareBezelStyle = 6;
/*
@global
@group CPBezelStyle
*/
CPCircularBezelStyle = 7;
/*
@global
@group CPBezelStyle
*/
CPTexturedSquareBezelStyle = 8;
/*
@global
@group CPBezelStyle
*/
CPHelpButtonBezelStyle = 9;
/*
@global
@group CPBezelStyle
*/
CPSmallSquareBezelStyle = 10;
/*
@global
@group CPBezelStyle
*/
CPTexturedRoundedBezelStyle = 11;
/*
@global
@group CPBezelStyle
*/
CPRoundRectBezelStyle = 12;
/*
@global
@group CPBezelStyle
*/
CPRecessedBezelStyle = 13;
/*
@global
@group CPBezelStyle
*/
CPRoundedDisclosureBezelStyle = 14;
/*
@global
@group CPBezelStyle
*/
CPHUDBezelStyle = -1;
/*
@global
@group CPButtonType
*/
CPMomentaryLightButton = 0;
/*
@global
@group CPButtonType
*/
CPPushOnPushOffButton = 1;
/*
@global
@group CPButtonType
*/
CPToggleButton = 2;
/*
@global
@group CPButtonType
*/
CPSwitchButton = 3;
/*
@global
@group CPButtonType
*/
CPRadioButton = 4;
/*
@global
@group CPButtonType
*/
CPMomentaryChangeButton = 5;
/*
@global
@group CPButtonType
*/
CPOnOffButton = 6;
/*
@global
@group CPButtonType
*/
CPMomentaryPushInButton = 7;
/*
@global
@group CPButtonType
*/
CPMomentaryPushButton = 0;
/*
@global
@group CPButtonType
*/
CPMomentaryLight = 7;
var CPHUDBezelStyleTextColor = nil;
var _CPButtonClassName = nil,
_CPButtonBezelStyleSizes = {},
_CPButtonBezelStyleIdentifiers = {},
_CPButtonBezelStyleHighlightedIdentifier = @"Highlighted";
/*! @class CPButton
CPButton is a subclass of CPControl that
intercepts mouse-down events and sends an action message to a
target object when it's clicked or pressed.
*/
@implementation CPButton : CPControl
{
int _tag;
int _state;
BOOL _allowsMixedState;
BOOL _isHighlighted;
CPImage _image;
CPImage _alternateImage;
CPCellImagePosition _imagePosition;
CPImageScaling _imageScaling;
CPString _title;
CPString _alternateTitle;
CPBezelStyle _bezelStyle;
BOOL _isBordered;
CPControlSize _controlSize;
BOOL _bezelBorderNeedsUpdate;
_CPImageAndTitleView _imageAndTitleView;
}
/*!
Initializes the CPButton class.
@ignore
*/
+ (void)initialize
{
if (self != [CPButton class])
return;
_CPButtonClassName = [CPButton className];
// Textured Rounded
_CPButtonBezelStyleIdentifiers[CPRoundedBezelStyle] = @"Rounded";
_CPButtonBezelStyleIdentifiers[CPRegularSquareBezelStyle] = @"RegularSquare";
_CPButtonBezelStyleIdentifiers[CPThickSquareBezelStyle] = @"ThickSquare";
_CPButtonBezelStyleIdentifiers[CPThickerSquareBezelStyle] = @"ThickerSquare";
_CPButtonBezelStyleIdentifiers[CPDisclosureBezelStyle] = @"Disclosure";
_CPButtonBezelStyleIdentifiers[CPShadowlessSquareBezelStyle] = @"ShadowlessSquare";
_CPButtonBezelStyleIdentifiers[CPCircularBezelStyle] = @"Circular";
_CPButtonBezelStyleIdentifiers[CPTexturedSquareBezelStyle] = @"TexturedSquare";
_CPButtonBezelStyleIdentifiers[CPHelpButtonBezelStyle] = @"HelpButton";
_CPButtonBezelStyleIdentifiers[CPSmallSquareBezelStyle] = @"SmallSquare";
_CPButtonBezelStyleIdentifiers[CPTexturedRoundedBezelStyle] = @"TexturedRounded";
_CPButtonBezelStyleIdentifiers[CPRoundRectBezelStyle] = @"RoundRect";
_CPButtonBezelStyleIdentifiers[CPRecessedBezelStyle] = @"Recessed";
_CPButtonBezelStyleIdentifiers[CPRoundedDisclosureBezelStyle] = @"RoundedDisclosure";
_CPButtonBezelStyleIdentifiers[CPHUDBezelStyle] = @"HUD";
var regularIdentifier = _CPControlIdentifierForControlSize(CPRegularControlSize),
smallIdentifier = _CPControlIdentifierForControlSize(CPSmallControlSize),
miniIdentifier = _CPControlIdentifierForControlSize(CPMiniControlSize);
// Rounded Rect
var prefix = _CPButtonClassName + _CPButtonBezelStyleIdentifiers[CPRoundRectBezelStyle];
_CPButtonBezelStyleSizes[prefix + regularIdentifier] = [_CGSizeMake(10.0, 18.0), _CGSizeMake(1.0, 18.0), _CGSizeMake(10.0, 18.0)];
_CPButtonBezelStyleSizes[prefix + regularIdentifier + _CPButtonBezelStyleHighlightedIdentifier] = [_CGSizeMake(10.0, 18.0), _CGSizeMake(1.0, 18.0), _CGSizeMake(10.0, 18.0)];
// HUD
var prefix = _CPButtonClassName + _CPButtonBezelStyleIdentifiers[CPHUDBezelStyle];
_CPButtonBezelStyleSizes[prefix + regularIdentifier] = [_CGSizeMake(13.0, 20.0), _CGSizeMake(1.0, 20.0), _CGSizeMake(13.0, 20.0)];
_CPButtonBezelStyleSizes[prefix + regularIdentifier + _CPButtonBezelStyleHighlightedIdentifier] = [_CGSizeMake(13.0, 20.0), _CGSizeMake(1.0, 20.0), _CGSizeMake(13.0, 20.0)];
CPHUDBezelStyleTextColor = [CPColor whiteColor];
// Textured Rounded
var prefix = _CPButtonClassName + _CPButtonBezelStyleIdentifiers[CPTexturedRoundedBezelStyle];
_CPButtonBezelStyleSizes[prefix + regularIdentifier] = [_CGSizeMake(7.0, 20.0), _CGSizeMake(1.0, 20.0), _CGSizeMake(7.0, 20.0)];
_CPButtonBezelStyleSizes[prefix + regularIdentifier + _CPButtonBezelStyleHighlightedIdentifier] = [_CGSizeMake(7.0, 20.0), _CGSizeMake(1.0, 20.0), _CGSizeMake(7.0, 20.0)];
}
// Configuring Buttons
/*!
Sets how the button highlights and shows its state.
@param aButtonType Defines the behavior of the button.
*/
- (void)setButtonType:(CPButtonType)aButtonType
{
if (aButtonType == CPSwitchButton)
{
[self setBordered:NO];
[self setImage:nil];
[self setAlternateImage:nil];
[self setAlignment:CPLeftTextAlignment];
}
}
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
if (self)
{
_imagePosition = CPNoImage;
_imageScaling = CPScaleNone;
_controlSize = CPRegularControlSize;
[self setBezelStyle:CPRoundRectBezelStyle];
[self setBordered:YES];
[self setAlignment:CPCenterTextAlignment];
}
return self;
}
/*!
Sets the position of the button's image to anImagePosition.
@param anImagePosition the position for the button's image
*/
- (void)setImagePosition:(CPCellImagePosition)anImagePosition
{
if (_imagePosition == anImagePosition)
return;
_imagePosition = anImagePosition;
[self drawContentsWithHighlight:_isHighlighted];
}
/*!
Returns the buton's image position
*/
- (CPCellImagePosition)imagePosition
{
return _imagePosition;
}
/*!
Sets the button's images scaling method
@param anImageScaling the image scaling method
*/
- (void)setImageScaling:(CPImageScaling)anImageScaling
{
if (_imageScaling == anImageScaling)
return;
_imageScaling = anImageScaling;
[self drawContentsWithHighlight:_isHighlighted];
}
/*!
Returns the button's image scaling method
*/
- (CPImageScaling)imageScaling
{
return _imageScaling;
}
/*!
Sets the color of the button's text
@param aColor the color to use for drawing the button text
*/
- (void)setTextColor:(CPColor)aColor
{
[super setTextColor:aColor];
[self drawContentsWithHighlight:_isHighlighted];
}
/*!
Sets the font that will be used to draw the button text
@param aFont the font used to draw the button text
*/
- (void)setFont:(CPFont)aFont
{
[super setFont:aFont];
[self drawContentsWithHighlight:_isHighlighted];
}
// Setting the state
/*!
Returns YES if the button has a 'mixed' state in addition to on and off.
*/
- (BOOL)allowsMixedState
{
return _allowsMixedState;
}
/*!
Sets whether the button can have a 'mixed' state.
@param aFlag specifies whether a 'mixed' state is allowed or not
*/
- (void)setAllowsMixedState:(BOOL)aFlag
{
_allowsMixedState = aFlag;
}
/*!
Sets the button to its next state.
*/
- (void)setNextState
{
if (_state == CPOffState)
_state = CPOnState;
else
_state = (_state >= CPOnState && _allowsMixedState) ? CPMixedState : CPOffState;
}
/*!
Sets the button's state to aState.
@param aState Possible states are any of the CPButton globals:
CPOffState, CPOnState, CPMixedState
*/
- (void)setState:(int)aState
{
_state = aState;
}
/*!
Returns the button's current state
*/
- (int)state
{
return _state;
}
/*!
Sets the alignment of the text on the button.
@param anAlignment an alignment object
*/
- (void)setAlignment:(CPTextAlignment)anAlignment
{
[super setAlignment:anAlignment];
[self drawContentsWithHighlight:_isHighlighted];
}
/*!
Sets the image that will be drawn on the button.
@param anImage the image that will be drawn
*/
- (void)setImage:(CPImage)anImage
{
if (_image == anImage)
return;
_image = anImage;
[self drawContentsWithHighlight:_isHighlighted];
}
/*!
Returns the image that will be drawn on the button
*/
- (CPImage)image
{
return _image;
}
/*!
Sets the button's image which is used in its alternate state.
@param anImage the image to be used while the button is in an alternate state
*/
- (void)setAlternateImage:(CPImage)anImage
{
_alternateImage = anImage;
}
/*!
Returns the image used when the button is in an alternate state.
*/
- (CPImage)alternateImage
{
return _alternateImage;
}
/*!
Sets the button's title.
@param aTitle the new title for the button
*/
- (void)setTitle:(CPString)aTitle
{
if (_title == aTitle)
return;
_title = aTitle;
[self drawContentsWithHighlight:_isHighlighted];
}
/*!
Returns the button's title string
*/
- (CPString)title
{
return _title;
}
/*!
Lays out the button.
*/
- (void)tile
{
var size = [self bounds].size;
if (_isBordered)
{
var imageAndTitleSize = CGSizeMakeCopy(size);
if (_bezelStyle == CPHUDBezelStyle)
imageAndTitleSize.height -= 4.0;
else if (_bezelStyle == CPRoundRectBezelStyle)
imageAndTitleSize.height -= 2.0;
else if (_bezelStyle == CPTexturedRoundedBezelStyle)
imageAndTitleSize.height -= 2.0;
[_imageAndTitleView setFrameSize:imageAndTitleSize];
}
else
[_imageAndTitleView setFrameSize:size];
}
/*!
Compacts the button's frame to fit its contents.
*/
- (void)sizeToFit
{
[_imageAndTitleView sizeToFit];
var frame = [_imageAndTitleView frame],
height = CGRectGetHeight(frame);
/*
if (_isBordered)
if (_bezelStyle == CPHUDBezelStyle)
height += 2.0;
else if (_bezelStyle == CPRoundRectBezelStyle)
height += 1.0;
else if (_bezelStyle == CPTexturedRoundedBezelStyle)
height += 2.0;
*/
[self setFrameSize:CGSizeMake(CGRectGetWidth(frame), height)];
}
/*!
Sets the frame size for the button.
@param the new frame size
*/
- (void)setFrameSize:(CGSize)aSize
{
[super setFrameSize:aSize];
[self tile];
}
/*!
Highlights the receiver based on aFlag.
@param If YES the button will highlight, NO the button will unhighlight.
*/
- (void)highlight:(BOOL)aFlag
{
[self drawBezelWithHighlight:aFlag];
[self drawContentsWithHighlight:aFlag];
}
/*!
Sets button's tag.
@param aTag the button's new tag
*/
- (void)setTag:(int)aTag
{
_tag = aTag;
}
/*!
Returns the button's tag.
*/
- (int)tag
{
return _tag;
}
/*!
Called when the user clicks on this button.
@param anEvent the event with information about the click
*/
- (void)mouseDown:(CPEvent)anEvent
{
_isHighlighted = YES;
[self highlight:_isHighlighted];
}
/*!
Called when the user drags on the button.
@param anEvent the event with information about the drag
*/
- (void)mouseDragged:(CPEvent)anEvent
{
_isHighlighted = CGRectContainsPoint([self bounds], [self convertPoint:[anEvent locationInWindow] fromView:nil]);
[self highlight:_isHighlighted];
}
/*!
Called when the user releases the mouse button
@param anEvent the event with information about the mouse release
*/
- (void)mouseUp:(CPEvent)anEvent
{
_isHighlighted = NO;
[self highlight:_isHighlighted];
[super mouseUp:anEvent];
}
/*!
Sets the button's control size.
@param aControlSize the button's new control size
*/
- (void)setControlSize:(CPControlSize)aControlSize
{
if (_controlSize == aControlSize)
return;
_controlSize = aControlSize;
[self drawBezelWithHighlight:_isHighlighted];
[self _updateTextAttributes];
}
/*!
Returns the button's control size.
*/
- (CPControlSize)controlSize
{
return _controlSize;
}
/*!
Sets whether the button has a bezeled border.
@param If YES, the the button will have a bezeled border.
*/
- (void)setBordered:(BOOL)isBordered
{
if (_isBordered == isBordered)
return;
_isBordered = isBordered;
[self updateBackgroundColors];
[self drawBezelWithHighlight:_isHighlighted];
[self tile];
}
/*!
Returns YES if the border is bezeled.
*/
- (BOOL)isBordered
{
return _isBordered;
}
/*!
Sets the button's bezel style.
@param aBezelStye one of the predefined bezel styles
*/
- (void)setBezelStyle:(CPBezelStyle)aBezelStyle
{
// FIXME: We need real support for these:
if (aBezelStyle == CPRoundedBezelStyle ||
aBezelStyle == CPRoundedBezelStyle ||
aBezelStyle == CPRegularSquareBezelStyle ||
aBezelStyle == CPThickSquareBezelStyle ||
aBezelStyle == CPThickerSquareBezelStyle ||
aBezelStyle == CPDisclosureBezelStyle ||
aBezelStyle == CPShadowlessSquareBezelStyle ||
aBezelStyle == CPCircularBezelStyle ||
aBezelStyle == CPTexturedSquareBezelStyle ||
aBezelStyle == CPHelpButtonBezelStyle ||
aBezelStyle == CPSmallSquareBezelStyle ||
aBezelStyle == CPRecessedBezelStyle ||
aBezelStyle == CPRoundedDisclosureBezelStyle)
aBezelStyle = CPRoundRectBezelStyle;
if (_bezelStyle == aBezelStyle)
return;
_bezelStyle = aBezelStyle;
[self updateBackgroundColors];
[self drawBezelWithHighlight:_isHighlighted];
[self _updateTextAttributes];
[self tile];
}
/*!
Returns the current bezel style
*/
- (int)bezelStyle
{
return _bezelStyle;
}
/* @ignore */
- (void)updateBackgroundColors
{
if (_isBordered)
{
[self setBackgroundColor:_CPControlThreePartImagePattern(
NO,
_CPButtonBezelStyleSizes,
_CPButtonClassName,
_CPButtonBezelStyleIdentifiers[_bezelStyle],
_CPControlIdentifierForControlSize(_controlSize)) forName:CPControlNormalBackgroundColor];
[self setBackgroundColor:_CPControlThreePartImagePattern(
NO,
_CPButtonBezelStyleSizes,
_CPButtonClassName,
_CPButtonBezelStyleIdentifiers[_bezelStyle],
_CPControlIdentifierForControlSize(_controlSize),
_CPButtonBezelStyleHighlightedIdentifier) forName:CPControlHighlightedBackgroundColor];
}
else
{
[self setBackgroundColor:nil forName:CPControlNormalBackgroundColor];
[self setBackgroundColor:nil forName:CPControlHighlightedBackgroundColor];
}
}
/* @ignore */
- (void)drawBezelWithHighlight:(BOOL)shouldHighlight
{
_bezelBorderNeedsUpdate = ![self window];
if (_bezelBorderNeedsUpdate)
return;
[self setBackgroundColorWithName:shouldHighlight ? CPControlHighlightedBackgroundColor : CPControlNormalBackgroundColor];
}
/* @ignore */
- (void)drawContentsWithHighlight:(BOOL)isHighlighted
{
if (!_title && !_image && !_alternateTitle && !_alternateImage && !_imageAndTitleView)
return;
if (!_imageAndTitleView)
{
_imageAndTitleView = [[_CPImageAndTitleView alloc] initWithFrame:[self bounds]];
[self addSubview:_imageAndTitleView];
[self tile];
}
[_imageAndTitleView setFont:[self font]];
[_imageAndTitleView setTextColor:[self textColor]];
[_imageAndTitleView setAlignment:[self alignment]];
[_imageAndTitleView setImagePosition:_imagePosition];
[_imageAndTitleView setImageScaling:_imageScaling];
[_imageAndTitleView setTitle:isHighlighted && _alternateTitle ? _alternateTitle : _title];
[_imageAndTitleView setImage:isHighlighted && _alternateImage ? _alternateImage : _image];
}
- (void)viewDidMoveToWindow
{
if (_bezelBorderNeedsUpdate)
[self drawBezelWithHighlight:_isHighlighted];
}
/* @ignore */
- (void)_updateTextAttributes
{
if (_bezelStyle == CPHUDBezelStyle)
[self setTextColor:CPHUDBezelStyleTextColor];
if (_controlSize == CPRegularControlSize)
[self setFont:[CPFont systemFontOfSize:11.0]];
}
@end
var CPButtonImageKey = @"CPButtonImageKey",
CPButtonAlternateImageKey = @"CPButtonAlternateImageKey",
CPButtonTitleKey = @"CPButtonTitleKey",
CPButtonAlteranteTitleKey = @"CPButtonAlternateTitleKey",
CPButtonImageAndTitleViewKey = @"CPButtonImageAndTitleViewKey",
CPButtonImagePositionKey = @"CPButtonImagePositionKey",
CPButtonImageScalingKey = @"CPButtonImageScalingKey",
CPButtonIsBorderedKey = @"CPButtonIsBorderedKey",
CPButtonBezelStyleKey = @"CPButtonBezelStyleKey",
CPButtonImageAndTitleViewKey = @"CPButtonImageAndTitleViewKey";
@implementation CPButton (CPCoding)
/*!
Initializes the button by unarchiving data from aCoder.
@param aCoder the coder containing the archived CPButton.
*/
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super initWithCoder:aCoder];
if (self)
{
_controlSize = CPRegularControlSize;
[self setImage:[aCoder decodeObjectForKey:CPButtonImageKey]];
[self setAlternateImage:[aCoder decodeObjectForKey:CPButtonAlternateImageKey]];
[self setTitle:[aCoder decodeObjectForKey:CPButtonTitleKey]];
[self setImagePosition:[aCoder decodeIntForKey:CPButtonImagePositionKey]];
[self setImageScaling:[aCoder decodeIntForKey:CPButtonImageScalingKey]];
[self setBezelStyle:[aCoder decodeIntForKey:CPButtonBezelStyleKey]];
[self setBordered:[aCoder decodeBoolForKey:CPButtonIsBorderedKey]];
}
return self;
}
/*!
Archives this button into the provided coder.
@param aCoder the coder to which the button's instance data will be written.
*/
- (void)encodeWithCoder:(CPCoder)aCoder
{
// We do this in order to avoid encoding the _imageAndTitleView, which
// should just automatically be created programmatically as needed.
var actualSubviews = _subviews;
_subviews = [_subviews copy];
[_subviews removeObjectIdenticalTo:_imageAndTitleView];
[super encodeWithCoder:aCoder];
_subviews = actualSubviews;
[aCoder encodeObject:_image forKey:CPButtonImageKey];
[aCoder encodeObject:_alternateImage forKey:CPButtonAlternateImageKey];
[aCoder encodeObject:_title forKey:CPButtonTitleKey];
[aCoder encodeInt:_imagePosition forKey:CPButtonImagePositionKey];
[aCoder encodeInt:_imageScaling forKey:CPButtonImageScalingKey];
[aCoder encodeBool:_isBordered forKey:CPButtonIsBorderedKey];
[aCoder encodeInt:_bezelStyle forKey:CPButtonBezelStyleKey];
}
@end