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

Last change on this file since 369 was 348, checked in by Nicholas Riley, 16 years ago

AntiRSI.xcodeproj: Explicitly use 10.4 Universal SDK for building on Leopard.

AntiRSI.m: Consistently use rounding instead of truncation for display: fixes times ordered 3:01, 2:00, 2:59, etc. Thanks to Andy Reitz for pointing this out.

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