source: trunk/Cocoa/AntiRSI/AntiRSI.m @ 332

Last change on this file since 332 was 332, checked in by Nicholas Riley, 12 years ago

version.plist: Not needed.

Info.plist: Updated for 1.4njr2.

AUTHORS: Updated Onne Gorter's email address and added mine.

AntiRSI.[hm]: Added dock badge session timer display and auto-reset options. Updated URLs to point to my Web site; should probably just switch to Sparkle.

CTBadge: Added customized version.

English.lproj/InfoPlist.strings: Updated for 1.4njr2; credit myself and Chad; remove Onne Gorter's URL.

English.lproj/MainMenu.nib: Preference changes; add cmd-R for manual reset.

File size: 24.4 KB
Line 
1/*
2 author: Onne Gorter
3 
4 This file is part of AntiRSI.
5 
6 AntiRSI is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 
11 AntiRSI is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with AntiRSI; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21#import "AntiRSI.h"
22
23#include <math.h>
24#include <ApplicationServices/ApplicationServices.h>
25
26extern CFTimeInterval CGSSecondsSinceLastInputEvent(unsigned long eventType);
27
28@implementation AntiRSI
29
30// bindings methods
31- (void)setMicro_pause_duration:(float)f
32{
33        micro_pause_duration = round(f);
34        if (s_taking_micro_pause == state) {
35                [progress setMaxValue:micro_pause_duration];
36                [progress setDoubleValue:micro_pause_taking_t];
37        }
38}
39
40- (void)setMicro_pause_period:(float)f
41{       micro_pause_period = 60 * round(f); }
42
43- (void)setWork_break_duration:(float)f
44{   
45        work_break_duration = 60 * round(f);
46        if (s_taking_work_break == state) {
47                [progress setMaxValue:work_break_duration / 60];
48                [progress setDoubleValue:work_break_taking_t / 60 - 0.5];
49        }
50}
51
52- (void)setWork_break_period:(float)f
53{       work_break_period = 60 * round(f); }
54
55- (void)installTimer:(double)interval
56{
57        if (mtimer != nil) {
58                [mtimer invalidate];
59                [mtimer autorelease];
60        }
61        mtimer = [[NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(tick:)
62                                             userInfo:nil repeats:YES] retain];
63}
64
65- (void)setSample_interval:(NSString *)s
66{
67        sample_interval = 1;
68        if ([s isEqualToString:@"Super Smooth"]) sample_interval = 0.1;
69        if ([s isEqualToString:@"Smooth"]) sample_interval = 0.33;
70        if ([s isEqualToString:@"Normal"]) sample_interval = 1;
71        if ([s isEqualToString:@"Low"]) sample_interval = 2;
72       
73        [self installTimer:sample_interval];
74}
75
76- (void)setDraw_dock_image:(BOOL)b
77{
78        draw_dock_image=b;
79        if (!b) {
80                [NSApp setApplicationIconImage:[NSImage imageNamed:@"AntiRSI"]];
81        } else {
82                [self drawDockImage];
83        }
84}
85
86- (void)setBackground:(NSColor *)c
87{
88        [background autorelease];
89        background=[c retain];
90       
91        // make new darkbackground color
92        float r,g,b,a;
93        [background getRed:&r green:&g blue:&b alpha:&a];
94        [darkbackground autorelease];
95        darkbackground=[[NSColor colorWithCalibratedRed:r*0.35 green:g*0.35 blue:b*0.35 alpha:a+0.2] retain];
96       
97        [self drawDockImage];
98}
99
100- (void)setElapsed:(NSColor *)c
101{
102        [elapsed autorelease];
103        elapsed=[c retain];
104    [dock_badge setBadgeColor:elapsed];
105        [self drawDockImage];
106}
107
108- (void)setTaking:(NSColor *)c
109{
110        [taking autorelease];
111        taking=[c retain];
112        [self drawDockImage];
113}
114
115// end of bindings
116
117- (void)resetTimers;
118{
119        // set timers to 0
120        micro_pause_t = 0;
121        work_break_t = 0;
122        micro_pause_taking_t = 0;
123        work_break_taking_t = 0;
124        work_break_taking_cached_t = 0;
125        work_break_taking_cached_date = 0;
126    session_t = 0;
127}
128
129- (void)awakeFromNib
130{
131        // want transparancy
132        [NSColor setIgnoresAlpha:NO];
133       
134        // initial colors
135        elapsed = [[NSColor colorWithCalibratedRed:0.3 green:0.3 blue:0.9 alpha:0.95] retain];
136        taking = [[NSColor colorWithCalibratedRed:0.3 green:0.9 blue:0.3 alpha:0.90] retain];
137        background = [NSColor colorWithCalibratedRed:0.9 green:0.9 blue:0.9 alpha:0.7];
138       
139        //initial values
140        micro_pause_period = 4*60;
141        micro_pause_duration = 13;
142        work_break_period = 50*60;
143        work_break_duration = 8*60;
144        sample_interval = 1;
145       
146        // set current state
147        state = s_normal;
148    [self resetTimers];
149       
150        // initialize dock image
151        dock_image = [[NSImage alloc] initWithSize:NSMakeSize(128,128)];
152        [dock_image setCacheMode:NSImageCacheNever];
153        original_dock_image = [NSImage imageNamed:@"AntiRSI"];
154        draw_dock_image_q = YES;
155    dock_badge = [[CTBadge systemBadge] retain];
156    [dock_badge setBadgeColor:elapsed];
157       
158        // setup main window that will show either micropause or workbreak
159        main_window = [[NSWindow alloc] initWithContentRect:[view frame]
160                                                                                          styleMask:NSBorderlessWindowMask
161                                                                                                backing:NSBackingStoreBuffered defer:YES];
162        [main_window setBackgroundColor:[NSColor clearColor]];
163        [main_window setLevel:NSStatusWindowLevel];
164        [main_window setAlphaValue:0.85];
165        [main_window setOpaque:NO];
166        [main_window setHasShadow:NO];
167        [main_window setMovableByWindowBackground:YES];
168        [main_window center];
169        [main_window setContentView:view];
170    NSTimeZone *utcZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
171    [reset_session_time setTimeZone:utcZone];
172    [progress setEnabled:NO];
173    NSFont *myriad = [NSFont fontWithName: @"Myriad" size: 40];
174    if (myriad) [status setFont: myriad];
175       
176        // initialze history filter
177        h0 = 0;
178        h1 = 0;
179        h2 = 0;
180       
181        // initialize ticks
182        date = [NSDate timeIntervalSinceReferenceDate];
183       
184        // set background now
185        [self setBackground:background];
186       
187        // create initial values
188        NSUserDefaultsController *dc = [NSUserDefaultsController sharedUserDefaultsController];
189        NSMutableDictionary* initial = [NSMutableDictionary dictionaryWithObjectsAndKeys:
190        [NSNumber numberWithFloat:4], @"micro_pause_period",
191        [NSNumber numberWithFloat:13], @"micro_pause_duration",
192        [NSNumber numberWithFloat:50], @"work_break_period",
193        [NSNumber numberWithFloat:8], @"work_break_duration",
194        @"Smooth", @"sample_interval",
195        [NSNumber numberWithBool:YES], @"draw_dock_image",
196        [NSNumber numberWithBool:YES], @"draw_dock_badge",
197        [NSNumber numberWithBool:NO], @"lock_focus",
198        [NSNumber numberWithBool:NO], @"reset_session_timer_daily",
199        [NSNumber numberWithBool:NO], @"reset_session_timer_after",
200        [NSCalendarDate dateWithYear:2000 month:1 day:1 hour:6 minute:0 second:0 timeZone:utcZone], @"reset_session_timer_time",
201        [NSNumber numberWithInt:8], @"reset_session_timer_after_hours",
202        [NSArchiver archivedDataWithRootObject:elapsed], @"elapsed",
203        [NSArchiver archivedDataWithRootObject:taking], @"taking",
204        [NSArchiver archivedDataWithRootObject:background], @"background",
205        nil];
206    [[NSUserDefaults standardUserDefaults] registerDefaults:initial];
207        [dc setInitialValues:initial];
208
209        // bind to defauls controller
210        [self bind:@"micro_pause_period" toObject:dc withKeyPath:@"values.micro_pause_period" options:nil];
211        [self bind:@"micro_pause_duration" toObject:dc withKeyPath:@"values.micro_pause_duration" options:nil];
212        [self bind:@"work_break_period" toObject:dc withKeyPath:@"values.work_break_period" options:nil];
213        [self bind:@"work_break_duration" toObject:dc withKeyPath:@"values.work_break_duration" options:nil];
214        [self bind:@"sample_interval" toObject:dc withKeyPath:@"values.sample_interval" options:nil];
215        [self bind:@"draw_dock_image" toObject:dc withKeyPath:@"values.draw_dock_image" options:nil];
216    [self bind:@"draw_dock_badge" toObject:dc withKeyPath:@"values.draw_dock_badge" options:nil];
217        [self bind:@"lock_focus" toObject:dc withKeyPath:@"values.lock_focus" options:nil];
218    [self bind:@"reset_session_timer_daily" toObject:dc withKeyPath:@"values.reset_session_timer_daily" options:nil];
219    [self bind:@"reset_session_timer_after" toObject:dc withKeyPath:@"values.reset_session_timer_after" options:nil];
220    [self bind:@"reset_session_timer_time" toObject:dc withKeyPath:@"values.reset_session_timer_time" options:nil];
221    [self bind:@"reset_session_timer_after_hours" toObject:dc withKeyPath:@"values.reset_session_timer_after_hours" options:nil];
222        NSDictionary* unarchive = [NSDictionary dictionaryWithObject:NSUnarchiveFromDataTransformerName forKey:@"NSValueTransformerName"];
223        [self bind:@"elapsed" toObject:dc withKeyPath:@"values.elapsed" options:unarchive];
224        [self bind:@"taking" toObject:dc withKeyPath:@"values.taking" options:unarchive];
225        [self bind:@"background" toObject:dc withKeyPath:@"values.background" options:unarchive];
226   
227    // watch for timer changes
228    [dc addObserver:self forKeyPath:@"values.reset_session_timer_time" options:0 context:NULL];
229   
230        // alert every binding
231        [dc revert:self];
232
233        // start the timer
234        [self installTimer:sample_interval];
235       
236        // about dialog
237        [version setStringValue:[NSString stringWithFormat:@"Version %@", sVersion]];
238}
239
240- (void)computeResetSessionDate;
241{
242    NSTimeZone *systemZone = [NSTimeZone systemTimeZone];
243    NSCalendarDate *today = [NSCalendarDate calendarDate];
244    NSCalendarDate *reset = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:[reset_session_timer_time timeIntervalSinceReferenceDate]];
245    [reset setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
246    reset = [NSCalendarDate dateWithYear:[today yearOfCommonEra]
247                                   month:[today monthOfYear]
248                                     day:[today dayOfMonth]
249                                    hour:[reset hourOfDay]
250                                  minute:[reset minuteOfHour]
251                                  second:0
252                                timeZone:systemZone];
253    if ([today compare:reset] != NSOrderedAscending)
254        reset = [reset dateByAddingYears:0 months:0 days:1 hours:0 minutes:0 seconds:0];
255    reset_session_date = [reset timeIntervalSinceReferenceDate];
256}
257
258- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
259{
260    if ([keyPath isEqualToString:@"values.reset_session_timer_time"])
261        [self computeResetSessionDate];
262}
263
264// tick every second and update status
265- (void)tick:(NSTimer *)timer
266{
267        // calculate time since last tick
268        NSTimeInterval new_date = [NSDate timeIntervalSinceReferenceDate];
269        NSTimeInterval tick_time = new_date - date;
270        date = new_date;
271
272    if (reset_session_timer_daily && date >= reset_session_date) {
273        [self resetSession:nil];
274        [self computeResetSessionDate];
275        return;
276    }
277   
278        // check if we are still on track of normal time, otherwise we might have slept or something
279        if (tick_time > work_break_duration) {
280                // set timers to 0
281                micro_pause_t = 0;
282                work_break_t = 0;
283                micro_pause_taking_t = micro_pause_duration;
284                work_break_taking_t = work_break_duration;
285                if (s_normal != state) {
286                        [self endBreak];
287                }
288                // and do stuff on next tick
289                return;
290        }
291       
292        // just did a whole micropause beyond normal time
293        if (tick_time > micro_pause_duration && s_taking_work_break != state) {
294                // set micro_pause timers to 0
295                micro_pause_t = 0;
296                micro_pause_taking_t = micro_pause_duration;
297                if (s_normal != state) {
298                        [self endBreak];
299                }
300                // and do stuff on next tick
301                return;
302        }
303       
304        // get idle time in seconds
305        CFTimeInterval idle_time = CGSSecondsSinceLastInputEvent(kCGAnyInputEventType);
306    // CFTimeInterval cgs_idle_time = idle_time;
307    // from other people's reverse engineering of this function, on MDD G4s this can return a large positive number when input is in progress
308    if (idle_time >= 18446744000.0) {
309        idle_time = 0;
310    } else if (CGEventSourceSecondsSinceLastEventType != NULL) {
311                CGEventType eventTypes[] = { kCGEventLeftMouseDown, kCGEventLeftMouseUp, kCGEventRightMouseDown, kCGEventRightMouseUp, kCGEventMouseMoved, kCGEventLeftMouseDragged, kCGEventRightMouseDragged, kCGEventKeyDown, kCGEventKeyUp, kCGEventFlagsChanged, kCGEventScrollWheel, kCGEventTabletPointer, kCGEventTabletProximity, kCGEventOtherMouseDown, kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventNull };
312        CFTimeInterval event_idle_time;
313        idle_time = DBL_MAX;
314        for (CGEventType *eventType = eventTypes ; *eventType != kCGEventNull ; eventType++) {
315            event_idle_time = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, *eventType);
316            if (event_idle_time < idle_time) idle_time = event_idle_time;
317        }
318    }
319    // NSLog(@"CGEventSource %.2f, CGS %.2f", idle_time, cgs_idle_time);
320   
321    if (reset_session_timer_after && idle_time > reset_session_timer_after_hours * 3600) {
322        [self resetSession:nil];
323        return;
324    }
325   
326        // calculate slack, this gives a sort of 3 history filtered idea.
327        BOOL slack = (h2 + h1 + h0 > 15);
328       
329        // if new event comes in history bumps up
330        if (h0 >= idle_time || idle_time < sample_interval) {
331                h2 = h1;
332                h1 = h0;
333        }
334        h0 = idle_time;
335       
336        switch (state) {
337                case s_normal:
338                        // idle_time needs to be at least 0.3 * micro_pause_duration before kicking in
339                        // but we cut the user some slack based on previous idle_times
340                        if (idle_time <= micro_pause_duration * 0.3 && !slack) {
341                                micro_pause_t += tick_time;
342                                work_break_t += tick_time;
343                if (idle_time < 1) {
344                    session_t += tick_time;
345                }
346                                micro_pause_taking_t = 0;
347                                if (work_break_taking_t > 0) {
348                                        work_break_taking_cached_t = work_break_taking_t;
349                                        work_break_taking_cached_date = date;
350                                }
351                                work_break_taking_t = 0;
352                        } else if (micro_pause_t > 0) {
353                        // oke, leaway is over, increase micro_pause_taking_t unless micro_pause is already over
354                                //micro_pause_t stays put
355                                work_break_t += tick_time;
356                                micro_pause_taking_t += tick_time;
357                                work_break_taking_t = 0;
358                        }
359                       
360                        // if micro_pause_taking_t is above micro_pause_duration, then micro pause is over,
361                        // if still idleing workbreak_taking_t kicks in unless it is already over
362                        if (micro_pause_taking_t >= micro_pause_duration && work_break_t > 0) {
363                                work_break_taking_t += tick_time;
364                                micro_pause_t = 0;
365                        }
366                       
367                        // if work_break_taking_t is above work_break_duration, then work break is over
368                        if (work_break_taking_t >= work_break_duration) {
369                                micro_pause_t = 0;
370                                work_break_t = 0;
371                                // micro_pause_taking_t stays put
372                                // work_break_taking_t stays put
373                        }
374               
375                        // if user needs to take a micro pause
376                        if (micro_pause_t >= micro_pause_period) {
377                                // anticipate next workbreak by not issuing this micro_pause ...
378                                if (work_break_t > work_break_period - (micro_pause_period / 2)) {
379                                        work_break_t = work_break_period;
380                                        [self doWorkBreak];
381                                } else {
382                                        [self doMicroPause];
383                                }
384                        }
385                       
386                        // if user needs to take a work break
387                        if (work_break_t >= work_break_period) {
388                                // stop micro_pause stuff
389                                micro_pause_t = 0;
390                                micro_pause_taking_t = micro_pause_duration;
391                                // and display window
392                                [self doWorkBreak];
393                        }
394                break;
395
396                // taking a micro pause with window
397                case s_taking_micro_pause:
398                        // continue updating timers
399                        micro_pause_taking_t += tick_time;
400                        work_break_t += tick_time;
401                       
402                        // if we don't break, or interrupt the break, reset it
403                        if (idle_time < 1 && !slack) {
404                                micro_pause_taking_t = 0;
405                session_t += tick_time;
406                        }
407                               
408                        // update window
409            [self updateBreakWindowDuration:micro_pause_duration progress:micro_pause_taking_t
410                                  nextBreak:work_break_period - work_break_t];
411
412                        // check if we done enough
413                        if (micro_pause_taking_t > micro_pause_duration) {
414                                micro_pause_t = 0;
415                                [self endBreak];
416                        }
417               
418                        // if workbreak must be run ...
419                        if (work_break_t >= work_break_period) {
420                                // stop micro_pause stuff
421                                micro_pause_t = 0;
422                                micro_pause_taking_t = micro_pause_duration;
423                                // and display window
424                                [self doWorkBreak];
425                        } else {
426                double slip = (micro_pause_duration - micro_pause_taking_t) - (int)(micro_pause_duration - micro_pause_taking_t);
427                [self installTimer: slip < 0.1 ? 1 : slip];
428            }
429                        break;
430               
431                // taking a work break with window
432                case s_taking_work_break:
433                        // increase work_break_taking_t
434                        if (idle_time >= 2 || work_break_taking_t < 3) {
435                                work_break_taking_t += tick_time;
436                        } else if (idle_time < 1) {
437                session_t += tick_time;
438            }
439                       
440                        // draw window
441            [self updateBreakWindowDuration:work_break_duration progress:work_break_taking_t
442                                  nextBreak:work_break_period + work_break_duration - work_break_taking_t];
443
444                        // and check if we done enough
445                        if (work_break_taking_t > work_break_duration) {
446                                micro_pause_t = 0;
447                                micro_pause_taking_t = micro_pause_duration;
448                                work_break_t = 0;
449                                work_break_taking_t = work_break_duration;
450                                [self endBreak];
451                        } else {
452                double slip = (work_break_duration - work_break_taking_t) - (int)(work_break_duration - work_break_taking_t);
453                [self installTimer: slip < 0.1 ? 1 : slip];
454            }
455                        break;
456        }
457       
458        // draw dock image
459        if (draw_dock_image) [self drawDockImage];
460}
461
462// dock image
463- (NSMenu *)applicationDockMenu:(NSApplication *)sender;
464{
465    [session_time_item setTitle:[self sessionTimeString]];
466    return dock_menu;
467}
468
469// draw the dock icon
470- (void)drawDockImage
471{
472        [dock_image lockFocus];
473       
474        // clear all
475        [[NSColor clearColor] set]; 
476        NSRectFill(NSMakeRect(0,0,127,127));
477       
478        NSBezierPath* p;
479        float end;
480       
481        //draw background circle
482        [darkbackground set];
483        p =[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(6,6,115,115)];
484        [p setLineWidth:4];
485        [p stroke];
486       
487        //fill
488        [background set];
489        [[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(8,8,111,111)] fill];
490       
491        //put dot in middle
492        [darkbackground set];
493        [[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(59,59,9,9)] fill];
494
495        // reuse this one
496        p = [NSBezierPath bezierPath];
497
498        // draw work_break
499        [elapsed set];
500        end = 360 - (360.0 / work_break_period * work_break_t - 90);
501        if (end <= 90) end=90.1;
502        [p appendBezierPathWithArcWithCenter:NSMakePoint(63.5, 63.5) radius:40 startAngle:90 endAngle:end clockwise:YES];
503        [p setLineWidth:22];
504        [p stroke];
505       
506        // draw work break taking
507        [taking set];
508        [p removeAllPoints];
509        end = 360 - (360.0 / work_break_duration * work_break_taking_t - 90);
510        if (end <= 90) end=90.1;
511        [p appendBezierPathWithArcWithCenter:NSMakePoint(63.5, 63.5) radius:40 startAngle:90 endAngle:end clockwise:YES];
512        [p setLineWidth:18];
513        [p stroke];
514       
515        // draw micro pause
516        [elapsed set];
517        [p removeAllPoints];
518        end = 360 - (360.0 / micro_pause_period * micro_pause_t - 90);
519        if (end <= 90) end = 90.1;
520        [p appendBezierPathWithArcWithCenter:NSMakePoint(63.5, 63.5) radius:17 startAngle:90 endAngle:end clockwise:YES];
521        [p setLineWidth:22];
522        [p stroke];
523       
524        // draw micro pause taking
525        [taking set];
526        [p removeAllPoints];
527        end = 360 - (360.0 / micro_pause_duration * micro_pause_taking_t - 90);
528        if (end <= 90) end = 90.1;
529        [p appendBezierPathWithArcWithCenter:NSMakePoint(63.5, 63.5) radius:17 startAngle:90 endAngle:end clockwise:YES];
530        [p setLineWidth:18];
531        [p stroke];
532       
533    // draw session time
534    if (draw_dock_badge) {
535        static NSImage *badge = nil;
536        static int badge_session_minutes = -1;
537        int session_minutes = (int)session_t / 60;
538        if (badge_session_minutes != session_minutes) {
539            if (badge != nil) [badge release];
540            badge = [[dock_badge badgeOverlayImageForString: [NSString stringWithFormat:@"%d:%02d", session_minutes / 60, session_minutes % 60] insetX: 3 y: 3] retain];
541            badge_session_minutes = session_minutes;
542        }
543        [badge compositeToPoint:NSZeroPoint operation:NSCompositeSourceOver];
544    }
545   
546        [dock_image unlockFocus];
547
548        // and set it in the dock check draw_dock_image one last time ...
549        if (draw_dock_image_q) [NSApp setApplicationIconImage:dock_image];
550}
551
552// done with micro pause or work break
553- (void)endBreak
554{
555        [main_window orderOut:NULL];
556        state = s_normal;
557        // reset time interval to user's choice
558        [self installTimer:sample_interval];
559}
560
561// display micro_pause window with appropriate widgets and progress bar
562- (void)doMicroPause
563{
564        micro_pause_taking_t = 0;
565        [status setStringValue:@"Micro Pause"];
566        [progress setMaxValue:micro_pause_duration];
567        [progress setDoubleValue:micro_pause_taking_t];
568    [progress setWarningValue: 1];
569    [progress setCriticalValue: micro_pause_duration];
570        [postpone setHidden:YES];
571        state = s_taking_micro_pause;
572        [self tick: nil];
573        [main_window center];
574        [main_window orderFrontRegardless];
575}
576
577// display work_break window with appropriate widgets and progress bar
578- (void)doWorkBreak
579{
580        work_break_taking_t = 0;
581        // incase you were already having an implicit work break and clicked the take work break now button
582        // not more then 20 seconds ago we took a natural break longer then 0.2 * normal work break duration
583        if (date - work_break_taking_cached_date < 20 && work_break_taking_cached_t > work_break_duration * 0.2) {
584                work_break_taking_t = work_break_taking_cached_t;
585        }
586        [status setStringValue:@"Work Break"];
587        [progress setMaxValue:work_break_duration / 60];
588        [progress setDoubleValue:work_break_taking_t / 60 - 0.5];
589    [progress setWarningValue: 0];
590    [progress setCriticalValue: 0.4];
591        [postpone setHidden:NO];
592        state = s_taking_work_break;
593    [self tick: nil];
594        [main_window center];
595        [main_window orderFrontRegardless];
596}
597
598- (NSString *)sessionTimeString;
599{
600    return [NSString stringWithFormat:@"Session: %d:%02d:%02d", (int)session_t / 3600,
601        ((int)session_t / 60) % 60, lrint(session_t) % 60];
602}
603
604- (void)updateBreakWindowDuration:(double)duration progress:(double)progress_t nextBreak:(double)nextBreak;
605{
606    // progress
607    [progress setDoubleValue:duration >= 60 ? (progress_t / 60 - 0.5) : progress_t];
608   
609    // time left
610    double timeLeft = duration - progress_t;
611        [time setStringValue:[NSString stringWithFormat:@"%d:%02d", (int)timeLeft / 60, lrint(timeLeft) % 60]];
612   
613    // cumulative typing time in this session (e.g. today)
614    [session_time setStringValue:[self sessionTimeString]];
615   
616    // next break
617        int minutes = round(nextBreak / 60.0);
618       
619        // nice hours, minutes ...
620        if (minutes > 60) {
621                [next_break setStringValue:[NSString stringWithFormat:@"next break in %d:%02d hours",
622                        minutes / 60, minutes % 60]];
623        } else {
624                [next_break setStringValue:[NSString stringWithFormat:@"next break in %d minutes", minutes]];
625        }
626
627    // if user likes to be interrupted
628    if (lock_focus) {
629        [NSApp activateIgnoringOtherApps:YES];
630        [main_window makeKeyAndOrderFront:self];
631    }
632}
633
634// goto website
635- (IBAction)gotoWebsite:(id)sender
636{
637        [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:sURL]];
638}
639
640// check for update
641- (IBAction)checkForUpdate:(id)sender
642{
643        NSString *latest_version =
644        [NSString stringWithContentsOfURL: [NSURL URLWithString:sLatestVersionURL]];
645       
646        if (latest_version == Nil) latest_version = @"";
647        latest_version = [latest_version stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
648       
649        if ([latest_version length] == 0) {
650                NSRunInformationalAlertPanel(
651                        @"Unable to Determine",
652                        @"Unable to determine the latest AntiRSI version number.",
653                        @"Ok", nil, nil);
654        } else if ([latest_version compare:sVersion] == NSOrderedDescending) {
655                int r = NSRunInformationalAlertPanel(
656                        @"New Version",
657                        [NSString stringWithFormat:@"A new version (%@) of AntiRSI is available; would you like to go to the website now?", latest_version],
658                        @"Goto Website", @"Cancel", nil);
659                if (r == NSOKButton) {
660                        [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:sURL]];
661                }
662    } else {
663        NSRunInformationalAlertPanel(
664                        @"No Update Available",
665                        @"This is the latest version of AntiRSI.",
666                        @"OK", nil, nil);
667    }
668}
669
670// stop work break and postpone by 10 minutes
671- (IBAction)postpone:(id)sender
672{
673        if (s_taking_work_break == state) {
674                micro_pause_t = 0;
675                micro_pause_taking_t = 0;
676                work_break_taking_t = 0;
677                work_break_taking_cached_t = 0;
678                work_break_t -= 10*60; // decrease with 10 minutes
679                if (work_break_t < 0) work_break_t = 0;
680                [self endBreak];
681        }
682}
683
684- (IBAction)breakNow:(id)sender
685{
686        [self doWorkBreak];
687}
688
689- (IBAction)resetSession:(id)sender;
690{
691    if (s_normal != state) {
692        [self endBreak];
693    }
694    [self resetTimers];
695}
696
697// validate menu items
698- (BOOL)validateMenuItem:(NSMenuItem *)anItem
699{
700        if ([anItem action] == @selector(breakNow:) && state == s_normal)
701                return YES;
702       
703        if ([anItem action] == @selector(postpone:) && state == s_taking_work_break)
704                return YES;
705
706    if ([anItem action] == @selector(resetSession:))
707                return YES;
708
709        if ([anItem action] == @selector(gotoWebsite:))
710                return YES;
711
712    if ([anItem action] == @selector(checkForUpdate:))
713                return YES;
714
715        return NO;
716}
717
718// we are delegate of NSApplication, so we can restore the icon on quit.
719- (void)applicationWillTerminate:(NSNotification *)aNotification
720{
721        // make sure timer doesn't tick once more ...
722        draw_dock_image_q = NO;
723        [mtimer invalidate];
724        [mtimer autorelease];
725        mtimer = nil;
726        [dock_image release];
727        // stupid fix for icon beeing restored ... it is not my fault,
728        // the dock or NSImage or setApplicationIconImage seem to be caching or taking
729        // snapshot or something ... !
730        [NSApp setApplicationIconImage:original_dock_image];
731        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
732        [NSApp setApplicationIconImage:original_dock_image];
733
734}
735
736@end
737
Note: See TracBrowser for help on using the repository browser.