1 /* Copyright (c) 2006-2007 Christopher J. W. Lloyd <cjwl@objc.net>
3 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
8 #import <AppKit/NSProgressIndicator.h>
9 #import <AppKit/NSColor.h>
10 #import <AppKit/NSWindow.h>
11 #import <AppKit/NSApplication.h>
12 #import <AppKit/NSGraphicsStyle.h>
13 #import <Foundation/NSKeyedArchiver.h>
14 #import <AppKit/NSGraphicsContextFunctions.h>
15 #import <AppKit/NSObject+BindingSupport.h>
16 #import <ApplicationServices/ApplicationServices.h>
17 #import <AppKit/NSRaise.h>
19 @implementation NSProgressIndicator
21 -(void)encodeWithCoder:(NSCoder *)coder {
22 NSUnimplementedMethod();
25 -initWithCoder:(NSCoder *)coder {
26 [super initWithCoder:coder];
28 if([coder allowsKeyedCoding]){
29 NSKeyedUnarchiver *keyed=(NSKeyedUnarchiver *)coder;
30 unsigned flags=[keyed decodeIntForKey:@"NSpiFlags"];
32 _minValue=[keyed decodeDoubleForKey:@"NSMinValue"];
33 _maxValue=[keyed decodeDoubleForKey:@"NSMaxValue"];
35 _animationDelay=5.0/60.0;
38 _style=(flags&0x1000)?NSProgressIndicatorSpinningStyle:NSProgressIndicatorBarStyle;
39 _size=(flags&0x100)?NSSmallControlSize:NSRegularControlSize;
40 _displayWhenStopped=(flags&0x2000)?NO:YES; // inverted
42 _isIndeterminate=(flags&0x02)?YES:NO;
43 _usesThreadedAnimation=NO;
46 [NSException raise:NSInvalidArgumentException format:@"-[%@ %s] is not implemented for coder %@",isa,sel_getName(_cmd),coder];
52 -initWithFrame:(NSRect)frame {
53 [super initWithFrame:frame];
57 _animationDelay=5.0/60.0;
60 _style=NSProgressIndicatorBarStyle;
61 _size=NSRegularControlSize;
63 _displayWhenStopped=NO;
66 _usesThreadedAnimation=NO;
71 -(void)_invalidateTimer {
72 if(_animationTimer!=nil){
73 [self willChangeValueForKey:@"animate"];
74 [_animationTimer invalidate];
75 [_animationTimer release];
77 [self didChangeValueForKey:@"animate"];
82 if(_animationTimer==nil){
83 [self willChangeValueForKey:@"animate"];
84 _animationTimer = [[NSTimer timerWithTimeInterval:_animationDelay
86 selector:@selector(animate:)
90 // FIXME: does it do this? Or does it add it to the current mode?
91 // Apple's does work in a modal panel (right?)_
92 [[NSRunLoop currentRunLoop] addTimer:_animationTimer forMode:NSDefaultRunLoopMode];
93 [[NSRunLoop currentRunLoop] addTimer:_animationTimer forMode:NSModalPanelRunLoopMode];
95 [self didChangeValueForKey:@"animate"];
100 [self _invalidateTimer];
109 -(void)drawIndeterminateCircular {
110 CGContextRef context=[[NSGraphicsContext currentContext] graphicsPort];
111 int i,numberOfRays=12;
112 int offset=roundf(_animationValue*(numberOfRays-1));
113 NSRect bounds=[self bounds];
114 CGPoint center=CGPointMake(bounds.origin.x+bounds.size.width/2,bounds.origin.y+bounds.size.height/2);
115 CGFloat angle=0;//MI_PI*2.0/4.0;
116 CGFloat arc=M_PI*2.0/numberOfRays;
117 CGFloat minAxis=MIN(bounds.size.width,bounds.size.height);
118 CGFloat lineWidth=minAxis/numberOfRays/2;
120 lineWidth+=lineWidth*0.50;
121 CGContextSaveGState(context);
122 CGContextSetLineWidth(context,lineWidth);
123 CGContextSetLineCap(context,kCGLineCapRound);
124 for(i=0;i<numberOfRays;i++){
126 CGFloat endGray=0.90;
127 int place=(i-offset<0)?numberOfRays+(i-offset):i-offset;
128 CGFloat gray=startGray+((endGray-startGray)/numberOfRays)*place;
129 CGAffineTransform rotate=CGAffineTransformMakeRotation(angle+arc*i);
132 CGContextSetGrayStrokeColor(context,gray,1.0);
134 ray.y=minAxis/2-lineWidth;
135 ray=CGPointApplyAffineTransform(ray,rotate);
136 CGContextMoveToPoint(context,center.x+ray.x,center.y+ray.y);
140 ray=CGPointApplyAffineTransform(ray,rotate);
141 CGContextAddLineToPoint(context,center.x+ray.x,center.y+ray.y);
142 CGContextStrokePath(context);
144 CGContextRestoreGState(context);
147 -(void)drawRect:(NSRect)clipRect {
148 if(_style==NSProgressIndicatorBarStyle){
149 if(_isIndeterminate){
150 if([self isDisplayedWhenStopped] || (_animationTimer!=nil)){
151 [[self graphicsStyle] drawProgressIndicatorIndeterminate:_bounds clipRect:clipRect bezeled:_isBezeled animation:_animationValue];
155 double value=(_value-_minValue)/(_maxValue-_minValue);
157 [[self graphicsStyle] drawProgressIndicatorDeterminate:_bounds clipRect:clipRect bezeled:_isBezeled value:value];
161 if([self isIndeterminate]){
162 if([self isDisplayedWhenStopped] || (_animationTimer!=nil)){
163 [self drawIndeterminateCircular];
168 [[NSColor redColor] set];
169 NSRectFill([self bounds]);
174 -(NSProgressIndicatorStyle)style {
178 -(NSControlSize)controlSize {
182 -(NSControlTint)controlTint {
186 -(BOOL)isDisplayedWhenStopped {
187 return _displayWhenStopped;
190 -(BOOL)usesThreadedAnimation {
191 return _usesThreadedAnimation;
202 -(double)doubleValue {
206 -(NSTimeInterval)animationDelay {
207 return _animationDelay;
210 -(BOOL)isIndeterminate {
211 return _isIndeterminate;
218 -(void)setStyle:(NSProgressIndicatorStyle)value {
221 [self setNeedsDisplay:YES];
224 -(void)setControlSize:(NSControlSize)value {
227 [self setNeedsDisplay:YES];
230 -(void)setControlTint:(NSControlTint)value {
232 [self setNeedsDisplay:YES];
235 -(void)setDisplayedWhenStopped:(BOOL)value {
236 _displayWhenStopped=value;
237 [self setNeedsDisplay:YES];
240 -(void)setUsesThreadedAnimation:(BOOL)value {
241 // This currently has no effect on the indicator - but no need to complain about it
242 _usesThreadedAnimation=value;
245 -(void)setMinValue:(double)value {
247 [self setNeedsDisplay:YES];
250 -(void)setMaxValue:(double)value {
252 [self setNeedsDisplay:YES];
255 -(void)setDoubleValue:(double)value {
262 [self setNeedsDisplay:YES];
265 -(void)setAnimationDelay:(double)delay {
266 _animationDelay = delay;
267 [self _invalidateTimer];
271 -(void)setIndeterminate:(BOOL)flag {
272 _isIndeterminate = flag;
273 [self setNeedsDisplay:YES];
276 -(void)setBezeled:(BOOL)flag {
278 [self setNeedsDisplay:YES];
281 -(void)incrementBy:(double)value {
282 [self setDoubleValue:_value+value];
288 - (void)_runThreadedAnimation:(id)arg
290 _endThreadedAnimation = NO;
292 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
295 [self setNeedsDisplay:YES];
297 BOOL isRunning = YES;
299 isRunning = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
300 beforeDate:[NSDate distantFuture]];
301 } while (isRunning && _endThreadedAnimation == NO);
303 [self _invalidateTimer];
308 - (void)_stopThreadedAnimation
310 _endThreadedAnimation = YES;
313 -(void)startAnimation:sender {
314 if (!_isIndeterminate)
317 if (_usesThreadedAnimation) {
318 [NSThread detachNewThreadSelector: @selector(_runThreadedAnimation:) toTarget: self withObject: nil];
321 [self setNeedsDisplay:YES];
325 -(void)stopAnimation:sender {
327 if (!_isIndeterminate)
330 if (_usesThreadedAnimation) {
331 [self _stopThreadedAnimation];
334 [self _invalidateTimer];
335 [self setNeedsDisplay:YES];
339 -(void)animate:sender {
340 _animationValue+=1.0/[self bounds].size.width;
342 if(_animationValue>1)
345 [self setNeedsDisplay:YES];
351 @implementation NSProgressIndicator (Bindings)
354 return _animationTimer!=nil;
357 -(void)_setAnimate:(BOOL)animate {
359 [self startAnimation:nil];
362 [self stopAnimation:nil];
366 -_replacementKeyPathForBinding:(id)binding {
367 if([binding isEqual:@"value"])
368 return @"doubleValue";
369 return [super _replacementKeyPathForBinding:binding];