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

Last change on this file since 370 was 370, checked in by Nicholas Riley, 15 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.