mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 21:17:03 +00:00
591 lines
16 KiB
Plaintext
591 lines
16 KiB
Plaintext
/*
|
|
* CPProgressIndicator.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 "CGGeometry.j"
|
|
@import "CPImageView.j"
|
|
@import "CPView.j"
|
|
@import "CPWindow_Constants.j"
|
|
|
|
|
|
@typedef CPProgressIndicatorStyle
|
|
/*
|
|
@global
|
|
@group CPProgressIndicatorStyle
|
|
*/
|
|
CPProgressIndicatorBarStyle = 0;
|
|
/*
|
|
@global
|
|
@group CPProgressIndicatorStyle
|
|
*/
|
|
CPProgressIndicatorSpinningStyle = 1;
|
|
/*
|
|
@global
|
|
@group CPProgressIndicatorStyle
|
|
*/
|
|
CPProgressIndicatorHUDBarStyle = 2;
|
|
|
|
var CPProgressIndicatorSpinningStyleColors = [];
|
|
|
|
/*!
|
|
@ingroup appkit
|
|
@class CPProgressIndicator
|
|
|
|
This class is used in a Cappuccino GUI to display the progress of a
|
|
function or task. If the duration of the task is unknown, there is
|
|
also an indeterminate mode for the indicator.
|
|
*/
|
|
@implementation CPProgressIndicator : CPView
|
|
{
|
|
double _minValue;
|
|
double _maxValue;
|
|
|
|
double _doubleValue;
|
|
|
|
CPControlSize _controlSize;
|
|
|
|
BOOL _indeterminate;
|
|
CPProgressIndicatorStyle _style;
|
|
|
|
BOOL _isAnimating;
|
|
|
|
BOOL _isDisplayedWhenStoppedSet;
|
|
BOOL _isDisplayedWhenStopped;
|
|
}
|
|
|
|
+ (CPString)defaultThemeClass
|
|
{
|
|
return @"progress-indicator";
|
|
}
|
|
|
|
+ (CPDictionary)themeAttributes
|
|
{
|
|
return @{
|
|
@"indeterminate-bar-color": [CPNull null],
|
|
@"bar-color": [CPNull null],
|
|
@"default-height": 20,
|
|
@"bezel-color": [CPNull null],
|
|
@"spinning-mini-gif": [CPNull null],
|
|
@"spinning-small-gif": [CPNull null],
|
|
@"spinning-regular-gif": [CPNull null],
|
|
@"circular-border-color": [CPNull null],
|
|
@"circular-border-size": 1,
|
|
@"circular-color": [CPNull null]
|
|
};
|
|
}
|
|
|
|
+ (Class)_binderClassForBinding:(CPString)aBinding
|
|
{
|
|
if (aBinding === CPValueBinding || aBinding === @"isIndeterminate")
|
|
return [_CPProgressIndicatorBinder class];
|
|
|
|
return [super _binderClassForBinding:aBinding];
|
|
}
|
|
|
|
- (id)initWithFrame:(CGRect)aFrame
|
|
{
|
|
self = [super initWithFrame:aFrame];
|
|
|
|
if (self)
|
|
{
|
|
_minValue = 0.0;
|
|
_maxValue = 100.0;
|
|
|
|
_doubleValue = 0.0;
|
|
|
|
_style = CPProgressIndicatorBarStyle;
|
|
_isDisplayedWhenStoppedSet = NO;
|
|
|
|
_controlSize = CPRegularControlSize;
|
|
|
|
[self setNeedsLayout];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (void)setUsesThreadedAnimation:(BOOL)aFlag
|
|
{
|
|
}
|
|
|
|
/*!
|
|
Starts the animation of the progress indicator in indeterminate mode.
|
|
@param the requesting object
|
|
*/
|
|
- (void)startAnimation:(id)aSender
|
|
{
|
|
_isAnimating = YES;
|
|
|
|
[self _hideOrDisplay];
|
|
}
|
|
|
|
/*!
|
|
Stops the animation of the progress indicator in indeterminate mode.
|
|
@param the requesting object
|
|
*/
|
|
- (void)stopAnimation:(id)aSender
|
|
{
|
|
_isAnimating = NO;
|
|
|
|
[self _hideOrDisplay];
|
|
}
|
|
|
|
/*!
|
|
Always returns \c NO. Cappuccino does not have multiple threads.
|
|
*/
|
|
- (BOOL)usesThreadedAnimation
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
// Advancing the Progress Bar
|
|
/*!
|
|
Increases the progress of the bar by the specified value.
|
|
@param aValue the amount to increase the progress value
|
|
*/
|
|
- (void)incrementBy:(double)aValue
|
|
{
|
|
[self setDoubleValue:_doubleValue + aValue];
|
|
}
|
|
|
|
/*!
|
|
Sets the progress value of the indicator.
|
|
*/
|
|
- (void)setDoubleValue:(double)aValue
|
|
{
|
|
_doubleValue = MIN(MAX(aValue, _minValue), _maxValue);
|
|
|
|
[self drawBar];
|
|
}
|
|
|
|
/*!
|
|
Returns the value of the progress indicator.
|
|
*/
|
|
- (double)doubleValue
|
|
{
|
|
return _doubleValue;
|
|
}
|
|
|
|
/*!
|
|
Sets the minimum value of the progress indicator. The default is 0.0.
|
|
@param aValue the new minimum value
|
|
*/
|
|
- (void)setMinValue:(double)aValue
|
|
{
|
|
_minValue = aValue;
|
|
}
|
|
|
|
/*!
|
|
Returns the minimum value of the progress indicator.
|
|
*/
|
|
- (double)minValue
|
|
{
|
|
return _minValue;
|
|
}
|
|
|
|
/*!
|
|
Sets the maximum value of the progress indicator. The default is 100.0.
|
|
@param aValue the new maximum value.
|
|
*/
|
|
- (void)setMaxValue:(double)aValue
|
|
{
|
|
_maxValue = aValue;
|
|
}
|
|
|
|
/*!
|
|
Returns the maximum value of the progress indicator.
|
|
*/
|
|
- (double)maxValue
|
|
{
|
|
return _maxValue;
|
|
}
|
|
|
|
// Setting the Appearance
|
|
/*!
|
|
Sets the progress indicator's size.
|
|
@param aControlSize the new size
|
|
*/
|
|
- (void)setControlSize:(CPControlSize)aControlSize
|
|
{
|
|
if (_controlSize == aControlSize)
|
|
return;
|
|
|
|
_controlSize = aControlSize;
|
|
|
|
[self updateBackgroundColor];
|
|
}
|
|
|
|
/*!
|
|
Returns the progress indicator's size
|
|
*/
|
|
- (CPControlSize)controlSize
|
|
{
|
|
return _controlSize;
|
|
}
|
|
|
|
/*
|
|
Not yet implemented
|
|
*/
|
|
- (void)setControlTint:(CPControlTint)aControlTint
|
|
{
|
|
}
|
|
|
|
/*
|
|
Not yet implemented.
|
|
*/
|
|
- (CPControlTint)controlTint
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
Not yet implemented.
|
|
*/
|
|
- (void)setBezeled:(BOOL)isBezeled
|
|
{
|
|
}
|
|
|
|
/*
|
|
Not yet implemented.
|
|
*/
|
|
- (BOOL)isBezeled
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
/*!
|
|
Specifies whether this progress indicator should be indeterminate or display progress based on it's max and min.
|
|
@param isDeterminate \c YES makes the indicator indeterminate
|
|
*/
|
|
- (void)setIndeterminate:(BOOL)indeterminate
|
|
{
|
|
if (_indeterminate == indeterminate)
|
|
return;
|
|
|
|
_indeterminate = indeterminate;
|
|
|
|
[self updateBackgroundColor];
|
|
}
|
|
|
|
/*!
|
|
Returns \c YES if the progress bar is indeterminate.
|
|
*/
|
|
- (BOOL)isIndeterminate
|
|
{
|
|
return _indeterminate;
|
|
}
|
|
|
|
/*!
|
|
Sets the progress indicator's style
|
|
@param aStyle the style to set it to
|
|
*/
|
|
- (void)setStyle:(CPProgressIndicatorStyle)aStyle
|
|
{
|
|
if (_style == aStyle)
|
|
return;
|
|
|
|
_style = aStyle;
|
|
|
|
[self setTheme:(_style === CPProgressIndicatorHUDBarStyle) ? [CPTheme defaultHudTheme] : [CPTheme defaultTheme]];
|
|
|
|
[self updateBackgroundColor];
|
|
}
|
|
|
|
/*!
|
|
Resizes the indicator based on it's style.
|
|
*/
|
|
- (void)sizeToFit
|
|
{
|
|
if (_style == CPProgressIndicatorSpinningStyle)
|
|
[self setFrameSize:[[CPProgressIndicatorSpinningStyleColors[_controlSize] patternImage] size]];
|
|
else
|
|
[self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), [self valueForThemeAttribute:@"default-height"])];
|
|
}
|
|
|
|
/*!
|
|
Sets whether the indicator should be displayed when it isn't animating. By default this is \c YES if the style
|
|
is CPProgressIndicatorBarStyle, and \c NO if it's CPProgressIndicatorSpinningStyle.
|
|
@param isDisplayedWhenStopped \c YES means the indicator will be displayed when it's not animating.
|
|
*/
|
|
- (void)setDisplayedWhenStopped:(BOOL)isDisplayedWhenStopped
|
|
{
|
|
if (_isDisplayedWhenStoppedSet && _isDisplayedWhenStopped == isDisplayedWhenStopped)
|
|
return;
|
|
|
|
_isDisplayedWhenStoppedSet = YES;
|
|
|
|
_isDisplayedWhenStopped = isDisplayedWhenStopped;
|
|
|
|
[self _hideOrDisplay];
|
|
}
|
|
|
|
/*!
|
|
Returns \c YES if the progress bar is displayed when not animating.
|
|
*/
|
|
- (BOOL)isDisplayedWhenStopped
|
|
{
|
|
if (_isDisplayedWhenStoppedSet)
|
|
return _isDisplayedWhenStopped;
|
|
|
|
if (_style == CPProgressIndicatorBarStyle || _style == CPProgressIndicatorHUDBarStyle)
|
|
return YES;
|
|
|
|
return NO;
|
|
}
|
|
|
|
/* @ignore */
|
|
- (void)_hideOrDisplay
|
|
{
|
|
[self setHidden:!_isAnimating && ![self isDisplayedWhenStopped]];
|
|
}
|
|
|
|
- (void)setFrameSize:(CGSize)aSize
|
|
{
|
|
[super setFrameSize:aSize];
|
|
|
|
[self drawBar];
|
|
}
|
|
|
|
/* @ignore */
|
|
- (void)drawBar
|
|
{
|
|
[self setNeedsLayout];
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
|
|
- (CPView)createEphemeralSubviewNamed:(CPString)aName
|
|
{
|
|
return [[CPView alloc] initWithFrame:CGRectMakeZero()];
|
|
}
|
|
|
|
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aViewName
|
|
{
|
|
if (aViewName === @"bar-view" && _style !== CPProgressIndicatorSpinningStyle)
|
|
{
|
|
var width = CGRectGetWidth([self bounds]),
|
|
barWidth = width * ((_doubleValue - _minValue) / (_maxValue - _minValue));
|
|
|
|
if (barWidth > 0.0 && barWidth < 4.0)
|
|
barWidth = 4.0;
|
|
|
|
if (_indeterminate)
|
|
barWidth = width;
|
|
|
|
return CGRectMake(0, 0, barWidth, [self valueForThemeAttribute:@"default-height"]);
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
/* @ignore */
|
|
- (void)updateBackgroundColor
|
|
{
|
|
if ([CPProgressIndicatorSpinningStyleColors count] === 0)
|
|
{
|
|
CPProgressIndicatorSpinningStyleColors[CPMiniControlSize] = [self valueForThemeAttribute:@"spinning-mini-gif"];
|
|
CPProgressIndicatorSpinningStyleColors[CPSmallControlSize] = [self valueForThemeAttribute:@"spinning-small-gif"];
|
|
CPProgressIndicatorSpinningStyleColors[CPRegularControlSize] = [self valueForThemeAttribute:@"spinning-regular-gif"];
|
|
}
|
|
|
|
[self setNeedsLayout];
|
|
}
|
|
|
|
- (void)layoutSubviews
|
|
{
|
|
if (YES)//_isBezeled)
|
|
{
|
|
if (_style == CPProgressIndicatorSpinningStyle)
|
|
{
|
|
if (!_indeterminate)
|
|
return;
|
|
|
|
// This will cause the bar view to go away due to having a nil rect when _style == CPProgressIndicatorSpinningStyle.
|
|
[self layoutEphemeralSubviewNamed:"bar-view"
|
|
positioned:CPWindowBelow
|
|
relativeToEphemeralSubviewNamed:nil];
|
|
|
|
[self setBackgroundColor:CPProgressIndicatorSpinningStyleColors[_controlSize]];
|
|
}
|
|
else
|
|
{
|
|
[self setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
|
|
|
|
var barView = [self layoutEphemeralSubviewNamed:"bar-view"
|
|
positioned:CPWindowBelow
|
|
relativeToEphemeralSubviewNamed:nil];
|
|
|
|
if (_indeterminate)
|
|
[barView setBackgroundColor:[self currentValueForThemeAttribute:@"indeterminate-bar-color"]];
|
|
else
|
|
[barView setBackgroundColor:[self currentValueForThemeAttribute:@"bar-color"]];
|
|
}
|
|
}
|
|
else
|
|
[self setBackgroundColor:nil];
|
|
}
|
|
|
|
- (void)drawRect:(CGRect)aRect
|
|
{
|
|
if (_style == CPProgressIndicatorSpinningStyle && !_indeterminate)
|
|
{
|
|
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
|
rect = CGRectMakeCopy(aRect),
|
|
borderSize = [self currentValueForThemeAttribute:@"circular-border-size"];
|
|
|
|
rect.origin.x += borderSize;
|
|
rect.origin.y += borderSize;
|
|
rect.size.width = rect.size.width - borderSize * 2;
|
|
rect.size.height = rect.size.height - borderSize * 2;
|
|
|
|
if ([self doubleValue] > [self minValue] && [self doubleValue] < [self maxValue])
|
|
{
|
|
var midX = CGRectGetMidX(rect),
|
|
midY = CGRectGetMidY(rect),
|
|
endAngle = Math.PI * 2 * (([self doubleValue] - [self minValue]) / ([self maxValue] - [self minValue])) - Math.PI / 2,
|
|
radius = MIN(rect.size.width / 2, rect.size.height / 2)
|
|
|
|
CGContextBeginPath(context);
|
|
CGContextSetLineWidth(context, borderSize);
|
|
CGContextSetFillColor(context, [self currentValueForThemeAttribute:@"circular-color"])
|
|
CGContextMoveToPoint(context, midX, midY);
|
|
CGContextAddArc(context, midX, midY, radius, 3 * Math.PI / 2, endAngle, YES)
|
|
CGContextAddLineToPoint(context, midX, midY);
|
|
CGContextClosePath(context);
|
|
CGContextFillPath(context);
|
|
CGContextStrokePath(context);
|
|
}
|
|
else if ([self doubleValue] == [self maxValue])
|
|
{
|
|
CGContextBeginPath(context);
|
|
CGContextSetFillColor(context, [self currentValueForThemeAttribute:@"circular-color"])
|
|
CGContextAddEllipseInRect(context, rect);
|
|
CGContextClosePath(context);
|
|
CGContextFillPath(context);
|
|
}
|
|
|
|
CGContextBeginPath(context);
|
|
CGContextSetStrokeColor(context , [self currentValueForThemeAttribute:@"circular-border-color"]);
|
|
CGContextSetLineWidth(context, borderSize);
|
|
CGContextAddEllipseInRect(context, rect);
|
|
CGContextClosePath(context);
|
|
CGContextStrokePath(context);
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation CPProgressIndicator (CPCoding)
|
|
|
|
- (id)initWithCoder:(CPCoder)aCoder
|
|
{
|
|
if (self = [super initWithCoder:aCoder])
|
|
{
|
|
_minValue = [aCoder decodeObjectForKey:@"_minValue"];
|
|
_maxValue = [aCoder decodeObjectForKey:@"_maxValue"];
|
|
_doubleValue = [aCoder decodeObjectForKey:@"_doubleValue"];
|
|
_controlSize = [aCoder decodeObjectForKey:@"_controlSize"];
|
|
_indeterminate = [aCoder decodeObjectForKey:@"_indeterminate"];
|
|
_style = [aCoder decodeIntForKey:@"_style"];
|
|
_isAnimating = [aCoder decodeObjectForKey:@"_isAnimating"];
|
|
_isDisplayedWhenStoppedSet = [aCoder decodeObjectForKey:@"_isDisplayedWhenStoppedSet"];
|
|
_isDisplayedWhenStopped = [aCoder decodeObjectForKey:@"_isDisplayedWhenStopped"];
|
|
|
|
[self updateBackgroundColor];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)encodeWithCoder:(CPCoder)aCoder
|
|
{
|
|
// Don't encode the background colour. It can be recreated based on the flags
|
|
// and if encoded causes hardcoded image paths in the cib while just wasting space.
|
|
var backgroundColor = [self backgroundColor];
|
|
[self setBackgroundColor:nil];
|
|
[super encodeWithCoder:aCoder];
|
|
[self setBackgroundColor:backgroundColor];
|
|
|
|
[aCoder encodeObject:_minValue forKey:@"_minValue"];
|
|
[aCoder encodeObject:_maxValue forKey:@"_maxValue"];
|
|
[aCoder encodeObject:_doubleValue forKey:@"_doubleValue"];
|
|
[aCoder encodeObject:_controlSize forKey:@"_controlSize"];
|
|
[aCoder encodeObject:_indeterminate forKey:@"_indeterminate"];
|
|
[aCoder encodeInt:_style forKey:@"_style"];
|
|
[aCoder encodeObject:_isAnimating forKey:@"_isAnimating"];
|
|
[aCoder encodeObject:_isDisplayedWhenStoppedSet forKey:@"_isDisplayedWhenStoppedSet"];
|
|
[aCoder encodeObject:_isDisplayedWhenStopped forKey:@"_isDisplayedWhenStopped"];
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation _CPProgressIndicatorBinder : CPBinder
|
|
|
|
- (void)_updatePlaceholdersWithOptions:(CPDictionary)options forBinding:(CPString)aBinding
|
|
{
|
|
var value = aBinding === CPValueBinding ? 0.0 : YES;
|
|
|
|
[self _setPlaceholder:value forMarker:CPMultipleValuesMarker isDefault:YES];
|
|
[self _setPlaceholder:value forMarker:CPNoSelectionMarker isDefault:YES];
|
|
[self _setPlaceholder:value forMarker:CPNotApplicableMarker isDefault:YES];
|
|
[self _setPlaceholder:value forMarker:CPNullMarker isDefault:YES];
|
|
}
|
|
|
|
- (id)valueForBinding:(CPString)aBinding
|
|
{
|
|
if (aBinding === CPValueBinding)
|
|
return [_source doubleValue];
|
|
else if (aBinding === @"isIndeterminate")
|
|
[_source isIndeterminate];
|
|
else
|
|
return [super valueForBinding:aBinding];
|
|
}
|
|
|
|
- (BOOL)_setValue:(id)aValue forBinding:(CPString)aBinding
|
|
{
|
|
if (aBinding === CPValueBinding)
|
|
[_source setDoubleValue:aValue];
|
|
else if (aBinding === @"isIndeterminate")
|
|
[_source setIndeterminate:aValue];
|
|
else
|
|
return NO;
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (void)setValue:(id)aValue forBinding:(CPString)aBinding
|
|
{
|
|
if (![self _setValue:aValue forBinding:aBinding])
|
|
[super setValue:aValue forBinding:aBinding];
|
|
}
|
|
|
|
- (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
|
|
{
|
|
if (![self _setValue:aValue forBinding:aBinding])
|
|
[super setPlaceholderValue:aValue withMarker:aMarker forBinding:aBinding];
|
|
}
|
|
|
|
@end
|