Native Mac OS X OtaClock replica
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MainWindow.m 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. //
  2. // MainWindow.m
  3. // OtaClock
  4. //
  5. // Created by Thomas Buck on 16.08.15.
  6. // Copyright (c) 2015 xythobuz. All rights reserved.
  7. //
  8. #import "MainView.h"
  9. #import "MainWindow.h"
  10. #define RESIZE_START 1
  11. #define RESIZE_END 10
  12. #define RESIZE_STEP 1
  13. #define CONFIG_START_SCALE @"window_scale"
  14. #define CONFIG_KEEP_POSITION @"keep_position"
  15. #define CONFIG_KEEP_ON_TOP @"keep_on_top"
  16. #define CONFIG_MILITARY_TIME @"military_time"
  17. #define CONFIG_SHOW_DATE @"show_date"
  18. #define CONFIG_ALARM_TIME @"alarm_time"
  19. #define MOUSE_CENTER_X 67
  20. #define MOUSE_CENTER_Y 47
  21. #define EYE_BLINK 0
  22. #define EYE_TOP_RIGHT 4
  23. #define EYE_TOP_LEFT 2
  24. #define EYE_BOTTOM_RIGHT 3
  25. #define EYE_BOTTOM_LEFT 1
  26. #define MAX_BLINK_DELAY 5.0
  27. #define UNBLINK_DELAY 0.1
  28. #define TIME_KEEPING_DELAY 0.25
  29. // The shadow is not properly recomputed if we don't do it multiple times
  30. #define ANIMATION_SHADOW_HACK 3
  31. #define ALARM_ANIMATION_DELAY (0.25 / ANIMATION_SHADOW_HACK)
  32. #define ALARM_ANIMATION_DELAY_END 3.0
  33. @interface MainWindow ()
  34. @property (assign) NSSize defaultSize;
  35. @property (assign) NSInteger startScale;
  36. @property (assign) NSInteger lastEyeState;
  37. @property (assign) BOOL currentlyBlinking, showDate;
  38. @property (weak) IBOutlet MainView *mainView;
  39. @property (weak) IBOutlet NSDatePicker *alarmDatePicker;
  40. @property (weak) IBOutlet NSMenuItem *lockPositionItem;
  41. @property (weak) IBOutlet NSMenuItem *keepOnTopItem;
  42. @property (weak) IBOutlet NSMenuItem *setAlarmItem;
  43. @property (weak) IBOutlet NSMenuItem *showDateItem;
  44. @property (weak) IBOutlet NSMenuItem *changeSize1;
  45. @property (weak) IBOutlet NSMenuItem *changeSize2;
  46. @property (weak) IBOutlet NSMenuItem *changeSize3;
  47. @property (weak) IBOutlet NSMenuItem *changeSize4;
  48. @property (weak) IBOutlet NSMenuItem *changeSize5;
  49. @property (weak) IBOutlet NSMenuItem *changeSize6;
  50. @property (weak) IBOutlet NSMenuItem *changeSize7;
  51. @property (weak) IBOutlet NSMenuItem *changeSize8;
  52. @property (weak) IBOutlet NSMenuItem *changeSize9;
  53. @property (weak) IBOutlet NSMenuItem *changeSize10;
  54. @end
  55. @implementation MainWindow
  56. @synthesize dragStart;
  57. @synthesize keepPosition;
  58. @synthesize defaultSize;
  59. @synthesize startScale;
  60. @synthesize lastEyeState;
  61. @synthesize currentlyBlinking, showDate;
  62. - (id)initWithContentRect:(NSRect)contentRect
  63. styleMask:(NSUInteger)aStyle
  64. backing:(NSBackingStoreType)bufferingType
  65. defer:(BOOL)flag {
  66. self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
  67. if (self != nil) {
  68. [self setAlphaValue:1.0];
  69. [self setOpaque:NO];
  70. lastEyeState = EYE_TOP_LEFT;
  71. currentlyBlinking = NO;
  72. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  73. // load and see if window should be kept on top
  74. if ([defaults objectForKey:CONFIG_KEEP_ON_TOP] != nil) {
  75. if ([defaults boolForKey:CONFIG_KEEP_ON_TOP]) {
  76. [self setLevel:NSFloatingWindowLevel];
  77. }
  78. }
  79. // load and see if window pos should be fixed
  80. if ([defaults objectForKey:CONFIG_KEEP_POSITION] == nil) {
  81. keepPosition = NO;
  82. } else {
  83. keepPosition = [defaults boolForKey:CONFIG_KEEP_POSITION];
  84. }
  85. // load window scale factor
  86. if ([defaults objectForKey:CONFIG_START_SCALE] == nil) {
  87. startScale = 1;
  88. } else {
  89. startScale = [defaults integerForKey:CONFIG_START_SCALE];
  90. }
  91. }
  92. [self setAcceptsMouseMovedEvents:YES];
  93. [NSEvent addGlobalMonitorForEventsMatchingMask:NSMouseMovedMask handler:^(NSEvent *mouseMovedEvent) { [self mouseMoved:mouseMovedEvent]; }];
  94. return self;
  95. }
  96. - (void)setDefaultBackgroundSize:(NSSize)size {
  97. defaultSize = size;
  98. NSRect frame = [self frame];
  99. frame.size = defaultSize;
  100. frame.size.width *= startScale;
  101. frame.size.height *= startScale;
  102. // We need to do all initialization of view state in here, because they are not ready in init
  103. if (keepPosition) [self.lockPositionItem setState:NSOnState];
  104. if ([self level] == NSFloatingWindowLevel) [self.keepOnTopItem setState:NSOnState];
  105. if (startScale == 1) [self.changeSize1 setState:NSOnState];
  106. if (startScale == 2) [self.changeSize2 setState:NSOnState];
  107. if (startScale == 3) [self.changeSize3 setState:NSOnState];
  108. if (startScale == 4) [self.changeSize4 setState:NSOnState];
  109. if (startScale == 5) [self.changeSize5 setState:NSOnState];
  110. if (startScale == 6) [self.changeSize6 setState:NSOnState];
  111. if (startScale == 7) [self.changeSize7 setState:NSOnState];
  112. if (startScale == 8) [self.changeSize8 setState:NSOnState];
  113. if (startScale == 9) [self.changeSize9 setState:NSOnState];
  114. if (startScale == 10) [self.changeSize10 setState:NSOnState];
  115. [[self.mainView render] drawWithEye:lastEyeState]; // Initialize render image
  116. [self unblink]; // Schedule next blinking
  117. // Start time keeping
  118. [self updateTime:nil];
  119. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  120. // load alarm date
  121. if ([defaults objectForKey:CONFIG_ALARM_TIME] == nil) {
  122. self.alarmDatePicker.dateValue = [NSDate date];
  123. } else {
  124. self.alarmDatePicker.dateValue = [defaults objectForKey:CONFIG_ALARM_TIME];
  125. }
  126. self.setAlarmItem.view = self.alarmDatePicker;
  127. // load show date state
  128. if ([defaults objectForKey:CONFIG_SHOW_DATE] == nil) {
  129. showDate = YES;
  130. } else {
  131. showDate = [defaults boolForKey:CONFIG_SHOW_DATE];
  132. }
  133. if (showDate == YES) {
  134. self.showDateItem.state = NSOnState;
  135. } else {
  136. self.showDateItem.state = NSOffState;
  137. }
  138. [[self.mainView render] drawDate:showDate];
  139. [self setFrame:frame display:YES];
  140. }
  141. - (void)drawAnimation:(NSNumber *)anim {
  142. if ([anim integerValue] > (3 * ANIMATION_SHADOW_HACK)) {
  143. [[self.mainView render] drawAnimation:0]; // reset
  144. } else {
  145. [[self.mainView render] drawAnimation:([anim integerValue] / ANIMATION_SHADOW_HACK)];
  146. }
  147. if ([anim integerValue] < (3 * ANIMATION_SHADOW_HACK)) {
  148. [self performSelector:@selector(drawAnimation:) withObject:[NSNumber numberWithInteger:([anim integerValue] + 1)] afterDelay:ALARM_ANIMATION_DELAY];
  149. } else if ([anim integerValue] < (4 * ANIMATION_SHADOW_HACK)) {
  150. [self performSelector:@selector(drawAnimation:) withObject:[NSNumber numberWithInteger:([anim integerValue] + 1)] afterDelay:ALARM_ANIMATION_DELAY_END];
  151. }
  152. [self invalidateShadow];
  153. self.mainView.needsDisplay = YES;
  154. }
  155. - (void)updateTime:(id)sender {
  156. [self performSelector:@selector(updateTime:) withObject:nil afterDelay:TIME_KEEPING_DELAY];
  157. // Check if a second went by
  158. NSDate *now = [NSDate date];
  159. NSDateComponents *components = [[NSCalendar currentCalendar] components:NSSecondCalendarUnit fromDate:now];
  160. static NSInteger lastSec = -1;
  161. if (lastSec == [components second]) {
  162. return;
  163. }
  164. lastSec = [components second];
  165. [[self.mainView render] drawWithDate:now];
  166. [[self.mainView render] blinkDots];
  167. NSTimeInterval in = [now timeIntervalSinceDate:self.alarmDatePicker.dateValue];
  168. if ((in >= -0.5) && (in <= 0.5)) {
  169. // We have reached the alarm time
  170. NSLog(@"Alarm time reached!");
  171. [self drawAnimation:[NSNumber numberWithInteger:1]];
  172. }
  173. self.mainView.needsDisplay = YES;
  174. }
  175. - (void)blink {
  176. if (currentlyBlinking == NO) {
  177. currentlyBlinking = YES;
  178. [[self.mainView render] drawWithEye:EYE_BLINK];
  179. self.mainView.needsDisplay = YES;
  180. }
  181. [self performSelector:@selector(unblink) withObject:nil afterDelay:UNBLINK_DELAY];
  182. }
  183. - (void)unblink {
  184. if (currentlyBlinking == YES) {
  185. currentlyBlinking = NO;
  186. [[self.mainView render] drawWithEye:lastEyeState];
  187. self.mainView.needsDisplay = YES;
  188. }
  189. [self performSelector:@selector(blink) withObject:nil afterDelay:(((float)rand() / RAND_MAX) * MAX_BLINK_DELAY)];
  190. }
  191. - (IBAction)alarmDateSelected:(id)sender {
  192. [[self.mainView render] drawAlarmDate:self.alarmDatePicker.dateValue];
  193. self.mainView.needsDisplay = YES;
  194. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  195. [defaults setObject:self.alarmDatePicker.dateValue forKey:CONFIG_ALARM_TIME];
  196. [defaults synchronize];
  197. }
  198. - (IBAction)showDate:(id)sender {
  199. if (showDate == YES) {
  200. showDate = NO;
  201. self.showDateItem.state = NSOffState;
  202. } else {
  203. showDate = YES;
  204. self.showDateItem.state = NSOnState;
  205. }
  206. [[self.mainView render] drawDate:showDate];
  207. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  208. [defaults setBool:showDate forKey:CONFIG_SHOW_DATE];
  209. [defaults synchronize];
  210. }
  211. - (IBAction)changeSize:(NSMenuItem *)sender {
  212. NSRect frame = [self frame];
  213. [self.changeSize1 setState:NSOffState];
  214. [self.changeSize2 setState:NSOffState];
  215. [self.changeSize3 setState:NSOffState];
  216. [self.changeSize4 setState:NSOffState];
  217. [self.changeSize5 setState:NSOffState];
  218. [self.changeSize6 setState:NSOffState];
  219. [self.changeSize7 setState:NSOffState];
  220. [self.changeSize8 setState:NSOffState];
  221. [self.changeSize9 setState:NSOffState];
  222. [self.changeSize10 setState:NSOffState];
  223. BOOL found = NO;
  224. for (int i = RESIZE_START; i <= RESIZE_END; i += RESIZE_STEP) {
  225. NSString *title = [NSString stringWithFormat:@"x%d", i];
  226. if ([[sender title] isEqualToString:title]) {
  227. [sender setState:NSOnState];
  228. NSSize newSize = defaultSize;
  229. newSize.height *= i;
  230. newSize.width *= i;
  231. frame.size = newSize;
  232. found = YES;
  233. startScale = i;
  234. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  235. [defaults setInteger:i forKey:CONFIG_START_SCALE];
  236. [defaults synchronize];
  237. break;
  238. }
  239. }
  240. if (found == NO) {
  241. NSLog(@"Unknown changeSize sender: %@", sender);
  242. } else {
  243. [self setFrame:frame display:YES];
  244. }
  245. }
  246. - (IBAction)lockPosition:(NSMenuItem *)sender {
  247. BOOL state = [sender state];
  248. if (state == NSOffState) {
  249. // Lock position
  250. state = NSOnState;
  251. [sender setState:state];
  252. self.keepPosition = YES;
  253. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  254. [defaults setBool:YES forKey:CONFIG_KEEP_POSITION];
  255. [defaults synchronize];
  256. } else {
  257. // Unlock position
  258. state = NSOffState;
  259. [sender setState:state];
  260. self.keepPosition = NO;
  261. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  262. [defaults setBool:NO forKey:CONFIG_KEEP_POSITION];
  263. [defaults synchronize];
  264. }
  265. }
  266. - (IBAction)keepOnTop:(NSMenuItem *)sender {
  267. BOOL state = [sender state];
  268. if (state == NSOffState) {
  269. // Keep window on top
  270. state = NSOnState;
  271. [sender setState:state];
  272. [self setLevel:NSFloatingWindowLevel];
  273. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  274. [defaults setBool:YES forKey:CONFIG_KEEP_ON_TOP];
  275. [defaults synchronize];
  276. } else {
  277. // Don't keep window on top
  278. state = NSOffState;
  279. [sender setState:state];
  280. [self setLevel:NSNormalWindowLevel];
  281. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  282. [defaults setBool:NO forKey:CONFIG_KEEP_ON_TOP];
  283. [defaults synchronize];
  284. }
  285. }
  286. - (BOOL)canBecomeKeyWindow {
  287. return YES;
  288. }
  289. - (void)mouseDown:(NSEvent *)theEvent {
  290. dragStart = [theEvent locationInWindow];
  291. }
  292. - (void)mouseDragged:(NSEvent *)theEvent {
  293. NSRect screenVisibleFrame = [[NSScreen mainScreen] visibleFrame];
  294. NSRect windowFrame = [self frame];
  295. NSPoint newOrigin = windowFrame.origin;
  296. if (self.keepPosition == NO) {
  297. NSPoint currentLocation = [theEvent locationInWindow];
  298. newOrigin.x += (currentLocation.x - dragStart.x);
  299. newOrigin.y += (currentLocation.y - dragStart.y);
  300. if ((newOrigin.y + windowFrame.size.height) > (screenVisibleFrame.origin.y + screenVisibleFrame.size.height)) {
  301. newOrigin.y = screenVisibleFrame.origin.y + (screenVisibleFrame.size.height - windowFrame.size.height);
  302. }
  303. [self setFrameOrigin:newOrigin];
  304. }
  305. }
  306. - (void)mouseMoved:(NSEvent *)theEvent {
  307. NSPoint mousePoint = [NSEvent mouseLocation];
  308. mousePoint.x -= [self frame].origin.x;
  309. mousePoint.y -= [self frame].origin.y;
  310. BOOL top = (mousePoint.y > (MOUSE_CENTER_Y * startScale));
  311. BOOL right = (mousePoint.x > (MOUSE_CENTER_X * startScale));
  312. NSInteger eyeState = EYE_BOTTOM_LEFT;
  313. if (top && right) {
  314. eyeState = EYE_TOP_RIGHT;
  315. } else if (top && (!right)) {
  316. eyeState = EYE_TOP_LEFT;
  317. } else if ((!top) && right) {
  318. eyeState = EYE_BOTTOM_RIGHT;
  319. }
  320. if (eyeState != lastEyeState) {
  321. lastEyeState = eyeState;
  322. if (currentlyBlinking == NO) {
  323. [[self.mainView render] drawWithEye:lastEyeState];
  324. self.mainView.needsDisplay = YES;
  325. }
  326. }
  327. }
  328. @end