source: trunk/Cocoa/AntiRSI/AntiRSI.m

Last change on this file was 675, checked in by Nicholas Riley, 5 years ago

AntiRSI.m: Fix format string issue and compilation on >10.4.

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