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

Last change on this file since 330 was 330, checked in by Nicholas Riley, 13 years ago

Info.plist: Updated for 1.4njr1.

AntiRSI.xcodeproj: Disable prebinding.

AntiRSI.[hm]: Added session time support; consolidated break window update drawing; fixed -[AntiRSI validateMenuItem:] so it compares selectors rather than titles.

English.lproj/MainMenu.nib: Redid preferences window, menu bar, dock menu, etc.

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