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

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

AntiRSI.[hm]: Reset all timers on session reset. Display hours in session time correctly. Use Myriad only if available.

MainMenu?.nib: Use Helvetica Neue if Myriad is unavailable. Fix initial first responder in preferences window.

File size: 21.2 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        [self drawDockImage];
105}
106
107- (void)setTaking:(NSColor *)c
108{
109        [taking autorelease];
110        taking=[c retain];
111        [self drawDockImage];
112}
113
114// end of bindings
115
116- (void)resetTimers;
117{
118        // set timers to 0
119        micro_pause_t = 0;
120        work_break_t = 0;
121        micro_pause_taking_t = 0;
122        work_break_taking_t = 0;
123        work_break_taking_cached_t = 0;
124        work_break_taking_cached_date = 0;
125    session_t = 0;
126}
127
128- (void)awakeFromNib
129{
130        // want transparancy
131        [NSColor setIgnoresAlpha:NO];
132       
133        // initial colors
134        elapsed = [[NSColor colorWithCalibratedRed:0.3 green:0.3 blue:0.9 alpha:0.95] retain];
135        taking = [[NSColor colorWithCalibratedRed:0.3 green:0.9 blue:0.3 alpha:0.90] retain];
136        background = [NSColor colorWithCalibratedRed:0.9 green:0.9 blue:0.9 alpha:0.7];
137       
138        //initial values
139        micro_pause_period = 4*60;
140        micro_pause_duration = 13;
141        work_break_period = 50*60;
142        work_break_duration = 8*60;
143        sample_interval = 1;
144       
145        // set current state
146        state = s_normal;
147    [self resetTimers];
148       
149        // initialize dock image
150        dock_image = [[NSImage alloc] initWithSize:NSMakeSize(128,128)];
151        [dock_image setCacheMode:NSImageCacheNever];
152        original_dock_image = [NSImage imageNamed:@"AntiRSI"];
153        draw_dock_image_q = YES;
154       
155        // setup main window that will show either micropause or workbreak
156        main_window = [[NSWindow alloc] initWithContentRect:[view frame]
157                                                                                          styleMask:NSBorderlessWindowMask
158                                                                                                backing:NSBackingStoreBuffered defer:YES];
159        [main_window setBackgroundColor:[NSColor clearColor]];
160        [main_window setLevel:NSStatusWindowLevel];
161        [main_window setAlphaValue:0.85];
162        [main_window setOpaque:NO];
163        [main_window setHasShadow:NO];
164        [main_window setMovableByWindowBackground:YES];
165        [main_window center];
166        [main_window setContentView:view];
167    [progress setEnabled:NO];
168    NSFont *myriad = [NSFont fontWithName: @"Myriad" size: 40];
169    if (myriad) [status setFont: myriad];
170       
171        // initialze history filter
172        h0 = 0;
173        h1 = 0;
174        h2 = 0;
175       
176        // initialize ticks
177        date = [NSDate timeIntervalSinceReferenceDate];
178       
179        // set background now
180        [self setBackground:background];
181       
182        // create initial values
183        NSMutableDictionary* initial = [NSMutableDictionary dictionaryWithCapacity:10];
184        [initial setObject:[NSNumber numberWithFloat:4] forKey:@"micro_pause_period"];
185        [initial setObject:[NSNumber numberWithFloat:13] forKey:@"micro_pause_duration"];
186        [initial setObject:[NSNumber numberWithFloat:50] forKey:@"work_break_period"];
187        [initial setObject:[NSNumber numberWithFloat:8] forKey:@"work_break_duration"];
188        [initial setObject:@"Smooth" forKey:@"sample_interval"];
189        [initial setObject:[NSNumber numberWithBool:YES] forKey:@"draw_dock_image"];
190        [initial setObject:[NSNumber numberWithBool:NO] forKey:@"lock_focus"];
191        [initial setObject:[NSArchiver archivedDataWithRootObject:elapsed] forKey:@"elapsed"];
192        [initial setObject:[NSArchiver archivedDataWithRootObject:taking] forKey:@"taking"];
193        [initial setObject:[NSArchiver archivedDataWithRootObject:background] forKey:@"background"];
194        [[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:initial];
195
196        // bind to defauls controller
197        id dc = [NSUserDefaultsController sharedUserDefaultsController];
198        [self bind:@"micro_pause_period" toObject:dc withKeyPath:@"values.micro_pause_period" options:nil];
199        [self bind:@"micro_pause_duration" toObject:dc withKeyPath:@"values.micro_pause_duration" options:nil];
200        [self bind:@"work_break_period" toObject:dc withKeyPath:@"values.work_break_period" options:nil];
201        [self bind:@"work_break_duration" toObject:dc withKeyPath:@"values.work_break_duration" options:nil];
202        [self bind:@"sample_interval" toObject:dc withKeyPath:@"values.sample_interval" options:nil];
203        [self bind:@"draw_dock_image" toObject:dc withKeyPath:@"values.draw_dock_image" options:nil];
204        [self bind:@"lock_focus" toObject:dc withKeyPath:@"values.lock_focus" options:nil];
205        NSDictionary* unarchive = [NSDictionary dictionaryWithObject:NSUnarchiveFromDataTransformerName forKey:@"NSValueTransformerName"];
206        [self bind:@"elapsed" toObject:dc withKeyPath:@"values.elapsed" options:unarchive];
207        [self bind:@"taking" toObject:dc withKeyPath:@"values.taking" options:unarchive];
208        [self bind:@"background" toObject:dc withKeyPath:@"values.background" options:unarchive];
209
210        // alert every binding
211        [[NSUserDefaultsController sharedUserDefaultsController] revert:self];
212
213        // start the timer
214        [self installTimer:sample_interval];
215       
216        // about dialog
217        [version setStringValue:[NSString stringWithFormat:@"Version %@", sVersion]];
218}
219
220// tick every second and update status
221- (void)tick:(NSTimer *)timer
222{
223        // calculate time since last tick
224        double new_date = [NSDate timeIntervalSinceReferenceDate];
225        double tick_time = new_date - date;
226        date = new_date;
227       
228        // check if we are still on track of normal time, otherwise we might have slept or something
229        if (tick_time > work_break_duration) {
230                // set timers to 0
231                micro_pause_t = 0;
232                work_break_t = 0;
233                micro_pause_taking_t = micro_pause_duration;
234                work_break_taking_t = work_break_duration;
235                if (s_normal != state) {
236                        [self endBreak];
237                }
238                // and do stuff on next tick
239                return;
240        }
241       
242        // just did a whole micropause beyond normal time
243        if (tick_time > micro_pause_duration && s_taking_work_break != state) {
244                // set micro_pause timers to 0
245                micro_pause_t = 0;
246                micro_pause_taking_t = micro_pause_duration;
247                if (s_normal != state) {
248                        [self endBreak];
249                }
250                // and do stuff on next tick
251                return;
252        }
253       
254        // get idle time in seconds
255        CFTimeInterval idle_time = CGSSecondsSinceLastInputEvent(kCGAnyInputEventType);
256    // CFTimeInterval cgs_idle_time = idle_time;
257    // from other people's reverse engineering of this function, on MDD G4s this can return a large positive number when input is in progress
258    if (idle_time >= 18446744000.0) {
259        idle_time = 0;
260    } else if (CGEventSourceSecondsSinceLastEventType != NULL) {
261                CGEventType eventTypes[] = { kCGEventLeftMouseDown, kCGEventLeftMouseUp, kCGEventRightMouseDown, kCGEventRightMouseUp, kCGEventMouseMoved, kCGEventLeftMouseDragged, kCGEventRightMouseDragged, kCGEventKeyDown, kCGEventKeyUp, kCGEventFlagsChanged, kCGEventScrollWheel, kCGEventTabletPointer, kCGEventTabletProximity, kCGEventOtherMouseDown, kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventNull };
262        CFTimeInterval event_idle_time;
263        idle_time = DBL_MAX;
264        for (CGEventType *eventType = eventTypes ; *eventType != kCGEventNull ; eventType++) {
265            event_idle_time = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, *eventType);
266            if (event_idle_time < idle_time) idle_time = event_idle_time;
267        }
268    }
269    // NSLog(@"CGEventSource %.2f, CGS %.2f", idle_time, cgs_idle_time);
270   
271        // calculate slack, this gives a sort of 3 history filtered idea.
272        BOOL slack = (h2 + h1 + h0 > 15);
273       
274        // if new event comes in history bumps up
275        if (h0 >= idle_time || idle_time < sample_interval) {
276                h2 = h1;
277                h1 = h0;
278        }
279        h0 = idle_time;
280       
281        switch (state) {
282                case s_normal:
283                        // idle_time needs to be at least 0.3 * micro_pause_duration before kicking in
284                        // but we cut the user some slack based on previous idle_times
285                        if (idle_time <= micro_pause_duration * 0.3 && !slack) {
286                                micro_pause_t += tick_time;
287                                work_break_t += tick_time;
288                if (idle_time < 1) {
289                    session_t += tick_time;
290                }
291                                micro_pause_taking_t = 0;
292                                if (work_break_taking_t > 0) {
293                                        work_break_taking_cached_t = work_break_taking_t;
294                                        work_break_taking_cached_date = date;
295                                }
296                                work_break_taking_t = 0;
297                        } else if (micro_pause_t > 0) {
298                        // oke, leaway is over, increase micro_pause_taking_t unless micro_pause is already over
299                                //micro_pause_t stays put
300                                work_break_t += tick_time;
301                                micro_pause_taking_t += tick_time;
302                                work_break_taking_t = 0;
303                        }
304                       
305                        // if micro_pause_taking_t is above micro_pause_duration, then micro pause is over,
306                        // if still idleing workbreak_taking_t kicks in unless it is already over
307                        if (micro_pause_taking_t >= micro_pause_duration && work_break_t > 0) {
308                                work_break_taking_t += tick_time;
309                                micro_pause_t = 0;
310                        }
311                       
312                        // if work_break_taking_t is above work_break_duration, then work break is over
313                        if (work_break_taking_t >= work_break_duration) {
314                                micro_pause_t = 0;
315                                work_break_t = 0;
316                                // micro_pause_taking_t stays put
317                                // work_break_taking_t stays put
318                        }
319               
320                        // if user needs to take a micro pause
321                        if (micro_pause_t >= micro_pause_period) {
322                                // anticipate next workbreak by not issuing this micro_pause ...
323                                if (work_break_t > work_break_period - (micro_pause_period / 2)) {
324                                        work_break_t = work_break_period;
325                                        [self doWorkBreak];
326                                } else {
327                                        [self doMicroPause];
328                                }
329                        }
330                       
331                        // if user needs to take a work break
332                        if (work_break_t >= work_break_period) {
333                                // stop micro_pause stuff
334                                micro_pause_t = 0;
335                                micro_pause_taking_t = micro_pause_duration;
336                                // and display window
337                                [self doWorkBreak];
338                        }
339                break;
340
341                // taking a micro pause with window
342                case s_taking_micro_pause:
343                        // continue updating timers
344                        micro_pause_taking_t += tick_time;
345                        work_break_t += tick_time;
346                       
347                        // if we don't break, or interrupt the break, reset it
348                        if (idle_time < 1 && !slack) {
349                                micro_pause_taking_t = 0;
350                session_t += tick_time;
351                        }
352                               
353                        // update window
354            [self updateBreakWindowDuration:micro_pause_duration progress:micro_pause_taking_t
355                                  nextBreak:work_break_period - work_break_t];
356
357                        // check if we done enough
358                        if (micro_pause_taking_t > micro_pause_duration) {
359                                micro_pause_t = 0;
360                                [self endBreak];
361                        }
362               
363                        // if workbreak must be run ...
364                        if (work_break_t >= work_break_period) {
365                                // stop micro_pause stuff
366                                micro_pause_t = 0;
367                                micro_pause_taking_t = micro_pause_duration;
368                                // and display window
369                                [self doWorkBreak];
370                        } else {
371                double slip = (micro_pause_duration - micro_pause_taking_t) - (int)(micro_pause_duration - micro_pause_taking_t);
372                [self installTimer: slip < 0.1 ? 1 : slip];
373            }
374                        break;
375               
376                // taking a work break with window
377                case s_taking_work_break:
378                        // increase work_break_taking_t
379                        if (idle_time >= 2 || work_break_taking_t < 3) {
380                                work_break_taking_t += tick_time;
381                        } else if (idle_time < 1) {
382                session_t += tick_time;
383            }
384                       
385                        // draw window
386            [self updateBreakWindowDuration:work_break_duration progress:work_break_taking_t
387                                  nextBreak:work_break_period + work_break_duration - work_break_taking_t];
388
389                        // and check if we done enough
390                        if (work_break_taking_t > work_break_duration) {
391                                micro_pause_t = 0;
392                                micro_pause_taking_t = micro_pause_duration;
393                                work_break_t = 0;
394                                work_break_taking_t = work_break_duration;
395                                [self endBreak];
396                        } else {
397                double slip = (work_break_duration - work_break_taking_t) - (int)(work_break_duration - work_break_taking_t);
398                [self installTimer: slip < 0.1 ? 1 : slip];
399            }
400                        break;
401        }
402       
403        // draw dock image
404        if (draw_dock_image) [self drawDockImage];
405}
406
407// dock image
408- (NSMenu *)applicationDockMenu:(NSApplication *)sender;
409{
410    [session_time_item setTitle:[self sessionTimeString]];
411    return dock_menu;
412}
413
414// draw the dock icon
415- (void)drawDockImage
416{
417        [dock_image lockFocus];
418       
419        // clear all
420        [[NSColor clearColor] set]; 
421        NSRectFill(NSMakeRect(0,0,127,127));
422       
423        NSBezierPath* p;
424        float end;
425       
426        //draw background circle
427        [darkbackground set];
428        p =[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(6,6,115,115)];
429        [p setLineWidth:4];
430        [p stroke];
431       
432        //fill
433        [background set];
434        [[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(8,8,111,111)] fill];
435       
436        //put dot in middle
437        [darkbackground set];
438        [[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(59,59,9,9)] fill];
439
440        // reuse this one
441        p = [NSBezierPath bezierPath];
442
443        // draw work_break
444        [elapsed set];
445        end = 360 - (360.0 / work_break_period * work_break_t - 90);
446        if (end <= 90) end=90.1;
447        [p appendBezierPathWithArcWithCenter:NSMakePoint(63.5, 63.5) radius:40 startAngle:90 endAngle:end clockwise:YES];
448        [p setLineWidth:22];
449        [p stroke];
450       
451        // draw work break taking
452        [taking set];
453        [p removeAllPoints];
454        end = 360 - (360.0 / work_break_duration * work_break_taking_t - 90);
455        if (end <= 90) end=90.1;
456        [p appendBezierPathWithArcWithCenter:NSMakePoint(63.5, 63.5) radius:40 startAngle:90 endAngle:end clockwise:YES];
457        [p setLineWidth:18];
458        [p stroke];
459       
460        // draw micro pause
461        [elapsed set];
462        [p removeAllPoints];
463        end = 360 - (360.0 / micro_pause_period * micro_pause_t - 90);
464        if (end <= 90) end = 90.1;
465        [p appendBezierPathWithArcWithCenter:NSMakePoint(63.5, 63.5) radius:17 startAngle:90 endAngle:end clockwise:YES];
466        [p setLineWidth:22];
467        [p stroke];
468       
469        // draw micro pause taking
470        [taking set];
471        [p removeAllPoints];
472        end = 360 - (360.0 / micro_pause_duration * micro_pause_taking_t - 90);
473        if (end <= 90) end = 90.1;
474        [p appendBezierPathWithArcWithCenter:NSMakePoint(63.5, 63.5) radius:17 startAngle:90 endAngle:end clockwise:YES];
475        [p setLineWidth:18];
476        [p stroke];
477       
478        [dock_image unlockFocus];
479
480        // and set it in the dock check draw_dock_image one last time ...
481        if (draw_dock_image_q) [NSApp setApplicationIconImage:dock_image];
482}
483
484// done with micro pause or work break
485- (void)endBreak
486{
487        [main_window orderOut:NULL];
488        state = s_normal;
489        // reset time interval to user's choice
490        [self installTimer:sample_interval];
491}
492
493// display micro_pause window with appropriate widgets and progress bar
494- (void)doMicroPause
495{
496        micro_pause_taking_t = 0;
497        [status setStringValue:@"Micro Pause"];
498        [progress setMaxValue:micro_pause_duration];
499        [progress setDoubleValue:micro_pause_taking_t];
500    [progress setWarningValue: 1];
501    [progress setCriticalValue: micro_pause_duration];
502        [postpone setHidden:YES];
503        state = s_taking_micro_pause;
504        [self tick: nil];
505        [main_window center];
506        [main_window orderFrontRegardless];
507}
508
509// display work_break window with appropriate widgets and progress bar
510- (void)doWorkBreak
511{
512        work_break_taking_t = 0;
513        // incase you were already having an implicit work break and clicked the take work break now button
514        // not more then 20 seconds ago we took a natural break longer then 0.2 * normal work break duration
515        if (date - work_break_taking_cached_date < 20 && work_break_taking_cached_t > work_break_duration * 0.2) {
516                work_break_taking_t = work_break_taking_cached_t;
517        }
518        [status setStringValue:@"Work Break"];
519        [progress setMaxValue:work_break_duration / 60];
520        [progress setDoubleValue:work_break_taking_t / 60 - 0.5];
521    [progress setWarningValue: 0];
522    [progress setCriticalValue: 0.4];
523        [postpone setHidden:NO];
524        state = s_taking_work_break;
525    [self tick: nil];
526        [main_window center];
527        [main_window orderFrontRegardless];
528}
529
530- (NSString *)sessionTimeString;
531{
532    return [NSString stringWithFormat:@"Session: %d:%02d:%02d", (int)session_t / 3600,
533        ((int)session_t / 60) % 60, lrint(session_t) % 60];
534}
535
536- (void)updateBreakWindowDuration:(double)duration progress:(double)progress_t nextBreak:(double)nextBreak;
537{
538    // progress
539    [progress setDoubleValue:duration >= 60 ? (progress_t / 60 - 0.5) : progress_t];
540   
541    // time left
542    double timeLeft = duration - progress_t;
543        [time setStringValue:[NSString stringWithFormat:@"%d:%02d", (int)timeLeft / 60, lrint(timeLeft) % 60]];
544   
545    // cumulative typing time in this session (e.g. today)
546    [session_time setStringValue:[self sessionTimeString]];
547   
548    // next break
549        int minutes = round(nextBreak / 60.0);
550       
551        // nice hours, minutes ...
552        if (minutes > 60) {
553                [next_break setStringValue:[NSString stringWithFormat:@"next break in %d:%02d hours",
554                        minutes / 60, minutes % 60]];
555        } else {
556                [next_break setStringValue:[NSString stringWithFormat:@"next break in %d minutes", minutes]];
557        }
558
559    // if user likes to be interrupted
560    if (lock_focus) {
561        [NSApp activateIgnoringOtherApps:YES];
562        [main_window makeKeyAndOrderFront:self];
563    }
564}
565
566// goto website
567- (IBAction)gotoWebsite:(id)sender
568{
569        [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:sURL]];
570}
571
572// check for update
573- (IBAction)checkForUpdate:(id)sender
574{
575        NSString *latest_version =
576        [NSString stringWithContentsOfURL: [NSURL URLWithString:sLatestVersionURL]];
577       
578        if (latest_version == Nil) latest_version = @"";
579        latest_version = [latest_version stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
580       
581        if ([latest_version length] == 0) {
582                NSRunInformationalAlertPanel(
583                        @"Unable to Determine",
584                        @"Unable to determine the latest AntiRSI version number.",
585                        @"Ok", nil, nil);
586        } else if ([latest_version compare:sVersion] == NSOrderedDescending) {
587                int r = NSRunInformationalAlertPanel(
588                        @"New Version",
589                        [NSString stringWithFormat:@"A new version (%@) of AntiRSI is available; would you like to go to the website now?", latest_version],
590                        @"Goto Website", @"Cancel", nil);
591                if (r == NSOKButton) {
592                        [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:sURL]];
593                }
594    } else {
595        NSRunInformationalAlertPanel(
596                        @"No Update Available",
597                        @"This is the latest version of AntiRSI.",
598                        @"OK", nil, nil);
599    }
600}
601
602// stop work break and postpone by 10 minutes
603- (IBAction)postpone:(id)sender
604{
605        if (s_taking_work_break == state) {
606                micro_pause_t = 0;
607                micro_pause_taking_t = 0;
608                work_break_taking_t = 0;
609                work_break_taking_cached_t = 0;
610                work_break_t -= 10*60; // decrease with 10 minutes
611                if (work_break_t < 0) work_break_t = 0;
612                [self endBreak];
613        }
614}
615
616- (IBAction)breakNow:(id)sender
617{
618        [self doWorkBreak];
619}
620
621- (IBAction)resetSession:(id)sender;
622{
623    if (s_normal != state) {
624        [self endBreak];
625    }
626    [self resetTimers];
627}
628
629// validate menu items
630- (BOOL)validateMenuItem:(NSMenuItem *)anItem
631{
632        if ([anItem action] == @selector(breakNow:) && state == s_normal)
633                return YES;
634       
635        if ([anItem action] == @selector(postpone:) && state == s_taking_work_break)
636                return YES;
637
638    if ([anItem action] == @selector(resetSession:))
639                return YES;
640
641        if ([anItem action] == @selector(gotoWebsite:))
642                return YES;
643
644    if ([anItem action] == @selector(checkForUpdate:))
645                return YES;
646
647        return NO;
648}
649
650// we are delegate of NSApplication, so we can restore the icon on quit.
651- (void)applicationWillTerminate:(NSNotification *)aNotification
652{
653        // make sure timer doesn't tick once more ...
654        draw_dock_image_q = NO;
655        [mtimer invalidate];
656        [mtimer autorelease];
657        mtimer = nil;
658        [dock_image release];
659        // stupid fix for icon beeing restored ... it is not my fault,
660        // the dock or NSImage or setApplicationIconImage seem to be caching or taking
661        // snapshot or something ... !
662        [NSApp setApplicationIconImage:original_dock_image];
663        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
664        [NSApp setApplicationIconImage:original_dock_image];
665
666}
667
668@end
669
Note: See TracBrowser for help on using the repository browser.