source: releases/AntiRSI/1.4njr4/AntiRSI.m @ 488

Last change on this file since 488 was 370, checked in by Nicholas Riley, 14 years ago

Apparently in Xcode, 'Commit Entire Project' doesn't. See the log message from [369].

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