1/** <title>NSProgressIndicator</title>
2
3   Copyright (C) 1999 Free Software Foundation, Inc.
4
5   Author:  Gerrit van Dyk <gerritvd@decimax.com>
6   Date: 1999
7   Author:  Fred Kiefer <fredkiefer@gmx.de>
8   Date: 2009
9
10   This file is part of the GNUstep GUI Library.
11
12   This library is free software; you can redistribute it and/or
13   modify it under the terms of the GNU Lesser General Public
14   License as published by the Free Software Foundation; either
15   version 2 of the License, or (at your option) any later version.
16
17   This library is distributed in the hope that it will be useful,
18   but WITHOUT ANY WARRANTY; without even the implied warranty of
19   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20   Lesser General Public License for more details.
21
22   You should have received a copy of the GNU Lesser General Public
23   License along with this library; see the file COPYING.LIB.
24   If not, see <http://www.gnu.org/licenses/> or write to the
25   Free Software Foundation, 51 Franklin Street, Fifth Floor,
26   Boston, MA 02110-1301, USA.
27*/
28
29#import <Foundation/NSAutoreleasePool.h>
30#import <Foundation/NSRunLoop.h>
31#import <Foundation/NSThread.h>
32#import <Foundation/NSTimer.h>
33#import "AppKit/NSApplication.h"
34#import "AppKit/NSProgressIndicator.h"
35#import "AppKit/NSGraphics.h"
36#import "AppKit/NSImage.h"
37#import "AppKit/NSWindow.h"
38#import "GNUstepGUI/GSTheme.h"
39#import "GNUstepGUI/GSNibLoading.h"
40
41@implementation NSProgressIndicator
42
43+ (void) initialize
44{
45  if (self == [NSProgressIndicator class])
46    {
47      [self setVersion: 1];
48    }
49}
50
51- (id) initWithFrame: (NSRect)frameRect
52{
53  self = [super initWithFrame: frameRect];
54  if (!self)
55    return nil;
56
57  _isIndeterminate = YES;
58  _isDisplayedWhenStopped = YES;
59  _isBezeled = YES;
60  _animationDelay = 5.0 / 60.0;  // 1 twelfth a a second
61  _doubleValue = 0.0;
62  _minValue = 0.0;
63  _maxValue = 100.0;
64  _controlTint = NSDefaultControlTint;
65  _controlSize = NSRegularControlSize;
66  [self setStyle: NSProgressIndicatorBarStyle];
67  //_isVertical = NO;
68  //_usesThreadedAnimation = NO;
69
70  return self;
71}
72
73- (void) dealloc
74{
75  [self stopAnimation: self];
76  [super dealloc];
77}
78
79- (BOOL) isFlipped
80{
81  return YES;
82}
83
84- (void) animate: (id)sender
85{
86  if (!_isIndeterminate && (_style == NSProgressIndicatorBarStyle))
87    return;
88
89  // Let this value overflow when it reachs the limit
90  _count++;
91
92  [self setNeedsDisplay: YES];
93}
94
95- (NSTimeInterval) animationDelay
96{
97  return _animationDelay;
98}
99
100- (void) setAnimationDelay: (NSTimeInterval)delay
101{
102  _animationDelay = delay;
103  if (_isRunning && (_isIndeterminate
104                     || (_style == NSProgressIndicatorSpinningStyle)))
105    {
106      [self stopAnimation: self];
107      [self startAnimation: self];
108    }
109}
110
111- (void) _animationLoop
112{
113  while (_isRunning)
114    {
115      CREATE_AUTORELEASE_POOL(pool);
116
117      [self animate: self];
118      [NSThread sleepForTimeInterval: _animationDelay];
119      [pool drain];
120    }
121}
122
123- (void) startAnimation: (id)sender
124{
125  if (_isRunning || (!_isIndeterminate
126                     && (_style == NSProgressIndicatorBarStyle)))
127    return;
128
129  _isRunning = YES;
130  if (!_usesThreadedAnimation)
131    {
132      ASSIGN(_timer, [NSTimer scheduledTimerWithTimeInterval: _animationDelay
133                              target: self
134                              selector: @selector(animate:)
135                              userInfo: nil
136                              repeats: YES]);
137      [[NSRunLoop currentRunLoop] addTimer: _timer forMode: NSModalPanelRunLoopMode];
138    }
139  else
140    {
141      [NSThread detachNewThreadSelector: @selector(_animationLoop)
142                toTarget: self
143                withObject: nil];
144    }
145}
146
147- (void) stopAnimation: (id)sender
148{
149  if (!_isRunning || (!_isIndeterminate
150                      && (_style == NSProgressIndicatorBarStyle)))
151    return;
152
153  if (!_usesThreadedAnimation)
154    {
155      [_timer invalidate];
156      DESTROY(_timer);
157    }
158  else
159    {
160      // Done automatically
161    }
162
163  _isRunning = NO;
164  _count = 0;
165  [self setNeedsDisplay: YES];
166}
167
168- (BOOL) isHidden
169{
170  if (!_isRunning && !_isDisplayedWhenStopped)
171    {
172      return YES;
173    }
174  return [super isHidden];
175}
176
177- (BOOL) usesThreadedAnimation
178{
179  return _usesThreadedAnimation;
180}
181
182- (void) setUsesThreadedAnimation: (BOOL)flag
183{
184  if (_usesThreadedAnimation != flag)
185    {
186      BOOL wasRunning = _isRunning;
187
188      if (wasRunning)
189        [self stopAnimation: self];
190
191      _usesThreadedAnimation = flag;
192
193      if (wasRunning)
194        [self startAnimation: self];
195    }
196}
197
198- (void) incrementBy: (double)delta
199{
200  [self setDoubleValue: _doubleValue + delta];
201}
202
203- (double) doubleValue
204{
205  return _doubleValue;
206}
207
208- (void) setDoubleValue: (double)aValue
209{
210  if (aValue > _maxValue)
211    aValue = _maxValue;
212  else if (aValue < _minValue)
213    aValue = _minValue;
214
215  if (_doubleValue != aValue)
216    {
217      _doubleValue = aValue;
218      [self setNeedsDisplay: YES];
219    }
220}
221
222- (double) minValue
223{
224  return _minValue;
225}
226
227- (void) setMinValue: (double)newMinimum
228{
229  if (_minValue != newMinimum)
230    {
231      _minValue = newMinimum;
232      if (_minValue > _doubleValue)
233        _doubleValue = _minValue;
234      [self setNeedsDisplay: YES];
235    }
236}
237
238- (double) maxValue
239{
240  return _maxValue;
241}
242
243- (void) setMaxValue: (double)newMaximum
244{
245  if (_maxValue != newMaximum)
246    {
247      _maxValue = newMaximum;
248      if (_maxValue < _doubleValue)
249        _doubleValue = _maxValue;
250      [self setNeedsDisplay: YES];
251    }
252}
253
254- (BOOL)isBezeled
255{
256  return _isBezeled;
257}
258
259- (void) setBezeled: (BOOL)flag
260{
261  if (_isBezeled != flag)
262    {
263      _isBezeled = flag;
264      [self setNeedsDisplay: YES];
265    }
266}
267
268- (BOOL) isIndeterminate
269{
270  return _isIndeterminate;
271}
272
273- (void) setIndeterminate: (BOOL)flag
274{
275  /* Note: We must stop a running animation before setting _isIndeterminate
276     because -stopAnimation: has no effect when _isIndeterminate is NO. */
277  if (flag == NO && _isRunning)
278    [self stopAnimation: self];
279
280  _isIndeterminate = flag;
281   // Maybe we need more functionality here when we implement indeterminate
282
283  [self setNeedsDisplay: YES];
284}
285
286- (BOOL) isDisplayedWhenStopped
287{
288  return _isDisplayedWhenStopped;
289}
290
291- (void) setDisplayedWhenStopped: (BOOL)flag
292{
293  if (flag != _isDisplayedWhenStopped)
294    {
295      _isDisplayedWhenStopped = flag;
296      [self setNeedsDisplay: YES];
297    }
298}
299
300- (NSProgressIndicatorStyle) style
301{
302  return _style;
303}
304
305- (void) setStyle: (NSProgressIndicatorStyle)style
306{
307  _style = style;
308  _count = 0;
309  [self setDisplayedWhenStopped: (style == NSProgressIndicatorBarStyle)];
310  [self setBezeled: (style == NSProgressIndicatorBarStyle)];
311  [self sizeToFit];
312  [self setNeedsDisplay: YES];
313}
314
315- (NSControlSize) controlSize
316{
317  return _controlSize;
318}
319
320- (void) setControlSize: (NSControlSize)size
321{
322  _controlSize = size;
323  [self sizeToFit];
324  [self setNeedsDisplay: YES];
325}
326
327- (NSControlTint) controlTint
328{
329  return _controlTint;
330}
331
332- (void) setControlTint: (NSControlTint)tint
333{
334  _controlTint = tint;
335  [self setNeedsDisplay: YES];
336}
337
338- (void) sizeToFit
339{
340  // FIXME
341}
342
343- (void) drawRect: (NSRect)rect
344{
345   double val;
346
347   if (_doubleValue < _minValue)
348     val = 0.0;
349   else if (_doubleValue > _maxValue)
350     val = 1.0;
351   else
352     val = (_doubleValue - _minValue) / (_maxValue - _minValue);
353   [[GSTheme theme] drawProgressIndicator: self
354                    withBounds: _bounds
355                    withClip: rect
356                    atCount: _count
357                    forValue: val];
358}
359
360// It does not seem that Gnustep has a copyWithZone: on NSView, it is private
361// under openstep
362
363// NSCopying
364/* - (id)copyWithZone:(NSZone *)zone
365{
366   NSProgressIndicator  *newInd;
367
368   newInd = [super copyWithZone:zone];
369   [newInd setIndeterminate:_isIndeterminate];
370   [newInd setBezeled:_isBezeled];
371   [newInd setUsesThreadedAnimation:_usesThreadedAnimation];
372   [newInd setAnimimationDelay:_animationDelay];
373   [newInd setDoubleValue:_doubleValue];
374   [newInd setMinValue:_minValue];
375   [newInd setMaxValue:_maxValue];
376   [newInd setVertical:_isVertical];
377   return newInd;
378}
379*/
380
381// NSCoding
382- (void) encodeWithCoder: (NSCoder *)aCoder
383{
384   [super encodeWithCoder: aCoder];
385   if ([aCoder allowsKeyedCoding])
386     {
387       unsigned long flags = 0;
388       id matrix = AUTORELEASE([[NSPSMatrix alloc] init]);
389
390       [aCoder encodeDouble: _minValue forKey: @"NSMinValue"];
391       [aCoder encodeDouble: _maxValue forKey: @"NSMaxValue"];
392       [aCoder encodeObject: matrix forKey: @"NSDrawMatrix"];
393
394       // add flag values.
395       flags |= (_isIndeterminate)? 2 : 0;
396       // Hard coded...
397       flags |= 8;
398       flags |= (_controlSize == NSSmallControlSize) ? 0x100 : 0;
399       flags |= (_style == NSProgressIndicatorSpinningStyle) ? 0x1000 : 0;
400       flags |= _isDisplayedWhenStopped ? 0 : 0x2000;
401       [aCoder encodeInt: flags forKey: @"NSpiFlags"];
402
403       // things which Gorm encodes, but IB doesn't care about.
404       [aCoder encodeDouble: _doubleValue forKey: @"GSDoubleValue"];
405       [aCoder encodeBool: _isBezeled forKey: @"GSIsBezeled"];
406       [aCoder encodeBool: _isVertical forKey: @"GSIsVertical"];
407       [aCoder encodeBool: _usesThreadedAnimation forKey: @"GSUsesThreadAnimation"];
408       [aCoder encodeDouble: _animationDelay forKey: @"GSAnimationDelay"];
409     }
410   else
411     {
412       [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_isIndeterminate];
413       [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_isBezeled];
414       [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_usesThreadedAnimation];
415       [aCoder encodeValueOfObjCType: @encode(NSTimeInterval) at: &_animationDelay];
416       [aCoder encodeValueOfObjCType: @encode(double) at: &_doubleValue];
417       [aCoder encodeValueOfObjCType: @encode(double) at: &_minValue];
418       [aCoder encodeValueOfObjCType: @encode(double) at: &_maxValue];
419       [aCoder encodeValueOfObjCType: @encode(BOOL) at: &_isVertical];
420     }
421}
422
423- (id) initWithCoder: (NSCoder *)aDecoder
424{
425  self = [super initWithCoder: aDecoder];
426  if (!self)
427    return nil;
428
429  if ([aDecoder allowsKeyedCoding])
430    {
431      // things which Gorm encodes, but IB doesn't care about.
432      // process Gorm encodings that IB doesn't care about first
433      // otherwise we overwrite settings read in from XIB...
434      if ([aDecoder containsValueForKey: @"GSDoubleValue"])
435        {
436          _doubleValue = [aDecoder decodeDoubleForKey: @"GSDoubleValue"];
437        }
438      else
439        {
440          _doubleValue = _minValue;
441        }
442
443      if ([aDecoder containsValueForKey: @"GSIsBezeled"])
444        {
445          _isBezeled = [aDecoder decodeBoolForKey: @"GSIsBezeled"];
446        }
447      else
448        {
449          _isBezeled = YES;
450        }
451
452      if ([aDecoder containsValueForKey: @"GSIsVertical"])
453        {
454          _isVertical = [aDecoder decodeBoolForKey: @"GSIsVertical"];
455        }
456      else
457        {
458          _isVertical = NO;
459        }
460
461      if ([aDecoder containsValueForKey: @"GSUsesThreadAnimation"])
462        {
463          _usesThreadedAnimation = [aDecoder decodeBoolForKey: @"GSUsesThreadAnimation"];
464        }
465      else
466        {
467          _usesThreadedAnimation = NO;
468        }
469
470      if ([aDecoder containsValueForKey: @"GSAnimationDelay"])
471        {
472          _animationDelay = [aDecoder decodeDoubleForKey: @"GSAnimationDelay"];
473        }
474      else
475        {
476          _animationDelay = 5.0 / 60.0;  // 1 twelfth a a second
477        }
478
479      // id matrix = [aDecoder decodeObjectForKey: @"NSDrawMatrix"];
480      if ([aDecoder containsValueForKey: @"NSMaxValue"])
481        {
482          double max = [aDecoder decodeDoubleForKey: @"NSMaxValue"];
483
484          [self setMaxValue: max];
485        }
486      else
487        {
488          _maxValue = 100.0;
489        }
490      if ([aDecoder containsValueForKey: @"NSMinValue"])
491        {
492          double min = [aDecoder decodeDoubleForKey: @"NSMinValue"];
493
494          [self setMinValue: min];
495        }
496      else
497        {
498          _minValue = 0.0;
499        }
500
501      if ([aDecoder containsValueForKey: @"NSpiFlags"])
502        {
503          int flags = [aDecoder decodeIntForKey: @"NSpiFlags"];
504
505          _isIndeterminate = ((flags & 2) == 2);
506          _controlTint = NSDefaultControlTint;
507          _controlSize = (flags & 0x100) ? NSSmallControlSize : NSRegularControlSize;
508          [self setStyle: (flags & 0x1000) ? NSProgressIndicatorSpinningStyle
509                : NSProgressIndicatorBarStyle];
510          _isDisplayedWhenStopped = ((flags & 0x2000) != 0x2000);
511          // ignore the rest, since they are not pertinent to GNUstep.
512        }
513      else
514        {
515          _isIndeterminate = YES;
516          _isDisplayedWhenStopped = YES;
517          _controlTint = NSDefaultControlTint;
518          _controlSize = NSRegularControlSize;
519          [self setStyle: NSProgressIndicatorBarStyle];
520        }
521    }
522  else
523    {
524      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_isIndeterminate];
525      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_isBezeled];
526      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_usesThreadedAnimation];
527      [aDecoder decodeValueOfObjCType: @encode(NSTimeInterval)
528                at: &_animationDelay];
529      [aDecoder decodeValueOfObjCType: @encode(double) at: &_doubleValue];
530      [aDecoder decodeValueOfObjCType: @encode(double) at: &_minValue];
531      [aDecoder decodeValueOfObjCType: @encode(double) at: &_maxValue];
532      [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &_isVertical];
533
534      _isDisplayedWhenStopped = YES;
535      _controlTint = NSDefaultControlTint;
536      _controlSize = NSRegularControlSize;
537      [self setStyle: NSProgressIndicatorBarStyle];
538    }
539   return self;
540}
541
542@end
543
544@implementation NSProgressIndicator (GNUstepExtensions)
545
546- (BOOL) isVertical
547{
548  return _isVertical;
549}
550
551- (void) setVertical: (BOOL)flag
552{
553  if (_isVertical != flag)
554    {
555      _isVertical = flag;
556      [self setNeedsDisplay:YES];
557    }
558}
559
560@end
561
562