├── .gitignore ├── .gitmodules ├── Credits.rtf ├── DDHotKeyCenter.h ├── DDHotKeyCenter.m ├── DDHotKeyUtilities.h ├── DDHotKeyUtilities.m ├── DataModel.xcdatamodel ├── elements └── layout ├── English.lproj └── InfoPlist.strings ├── Frameworks └── Growl.framework │ ├── Growl │ ├── Headers │ ├── Resources │ └── Versions │ ├── A │ ├── Growl │ ├── Headers │ │ ├── Growl.h │ │ ├── GrowlApplicationBridge.h │ │ └── GrowlDefines.h │ ├── Resources │ │ └── Info.plist │ └── _CodeSignature │ │ └── CodeResources │ └── Current ├── Growl Registration Ticket.growlRegDict ├── LICENSE ├── MainMenu.xib ├── PauseCommand.h ├── PauseCommand.m ├── PreferencesWindowController.h ├── PreferencesWindowController.m ├── PreferencesWindowController.xib ├── README.md ├── Session.h ├── Session.m ├── StartCommand.h ├── StartCommand.m ├── StopCommand.h ├── StopCommand.m ├── Stopwatch.h ├── Stopwatch.m ├── TagWindowController.h ├── TagWindowController.m ├── TagWindowController.xib ├── Thyme-Info.plist ├── Thyme.xcodeproj ├── joao.mode1v3 ├── joao.pbxuser ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── joao.xcuserdatad │ └── xcschemes │ ├── Thyme.xcscheme │ └── xcschememanagement.plist ├── ThymeAppDelegate.h ├── ThymeAppDelegate.m ├── Thyme_Prefix.pch ├── ToggleCommand.h ├── ToggleCommand.m ├── artwork ├── logo.ai └── logo.png ├── defaults.plist ├── logo.icns ├── logo_small.png ├── logo_small@2x.png ├── main.m └── scriptSupport.sdef /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .DS_Store 3 | *.pbxuser 4 | *.perspective 5 | *.perspectivev3 6 | *.mode1v3 7 | *.mode2v3 8 | *.moved-aside 9 | xcuserdata 10 | xcshareddata 11 | 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ShortcutRecorder"] 2 | path = ShortcutRecorder 3 | url = git@github.com:plopp/ShortcutRecorder.git 4 | -------------------------------------------------------------------------------- /Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1344\cocoasubrtf720 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;} 3 | {\colortbl;\red255\green255\blue255;} 4 | {\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid1\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid1}} 5 | {\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}} 6 | \paperw12240\paperh15840\vieww9000\viewh8400\viewkind0 7 | \deftab720 8 | \pard\pardeftab720\sl380\sa260 9 | 10 | \f0\fs26 \cf0 Thyme is on {\field{\*\fldinst{HYPERLINK "http://github.com/joaomoreno/Thyme"}}{\fldrslt Github}}. Made by {\field{\*\fldinst{HYPERLINK "https://github.com/joaomoreno"}}{\fldrslt Jo\'e3o Moreno}}.\ 11 | 12 | \b Contributors 13 | \b0 \ 14 | \pard\tx220\tx720\tx4493\pardeftab720\li720\fi-720 15 | \ls1\ilvl0\cf0 {\listtext \'95 }{\field{\*\fldinst{HYPERLINK "https://github.com/dimonzozo"}}{\fldrslt Dmitriy}}\ 16 | {\listtext \'95 }{\field{\*\fldinst{HYPERLINK "https://github.com/jaysonlane"}}{\fldrslt Jason Lane}}\ 17 | {\listtext \'95 }{\field{\*\fldinst{HYPERLINK "https://github.com/tilden"}}{\fldrslt Dan Tilden}}\ 18 | } -------------------------------------------------------------------------------- /DDHotKeyCenter.h: -------------------------------------------------------------------------------- 1 | /* 2 | DDHotKey -- DDHotKeyCenter.h 3 | 4 | Copyright (c) Dave DeLong 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 7 | 8 | The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the author(s) or copyright holder(s) be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. 9 | */ 10 | 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | //a convenient typedef for the required signature of a hotkey block callback 16 | typedef void (^DDHotKeyTask)(NSEvent*); 17 | 18 | @interface DDHotKey : NSObject 19 | 20 | // creates a new hotkey but does not register it 21 | + (instancetype)hotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags task:(DDHotKeyTask _Nullable)task; 22 | 23 | @property (nonatomic, assign, readonly, nullable) id target; 24 | @property (nonatomic, readonly, nullable) SEL action; 25 | @property (nonatomic, strong, readonly, nullable) id object; 26 | @property (nonatomic, copy, readonly, nullable) DDHotKeyTask task; 27 | 28 | @property (nonatomic, readonly) unsigned short keyCode; 29 | @property (nonatomic, readonly) NSUInteger modifierFlags; 30 | 31 | @end 32 | 33 | #pragma mark - 34 | 35 | @interface DDHotKeyCenter : NSObject 36 | 37 | @property (class, readonly, nonnull) DDHotKeyCenter *sharedHotKeyCenter; 38 | 39 | /** 40 | Register a hotkey. 41 | */ 42 | - (DDHotKey * _Nullable)registerHotKey:(DDHotKey *)hotKey withError:(NSError **)error; 43 | 44 | /** 45 | Register a target/action hotkey. 46 | The modifierFlags must be a bitwise OR of NSEventModifierFlagCommand, NSEventModifierFlagOption, NSEventModifierFlagControl, or NSEventModifierFlagShift; 47 | Returns the hotkey registered. If registration failed, returns nil. 48 | */ 49 | - (DDHotKey * _Nullable)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags target:(id)target action:(SEL)action object:(id)object error:(NSError **)error; 50 | 51 | /** 52 | Register a block callback hotkey. 53 | The modifierFlags must be a bitwise OR of NSEventModifierFlagCommand, NSEventModifierFlagOption, NSEventModifierFlagControl, or NSEventModifierFlagShift; 54 | Returns the hotkey registered. If registration failed, returns nil. 55 | */ 56 | - (DDHotKey * _Nullable)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags task:(DDHotKeyTask)task error:(NSError **)error; 57 | 58 | /** 59 | See if a hotkey exists with the specified keycode and modifier flags. 60 | NOTE: this will only check among hotkeys you have explicitly registered with DDHotKeyCenter. This does not check all globally registered hotkeys. 61 | */ 62 | - (BOOL)hasRegisteredHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags; 63 | 64 | /** 65 | Unregister a specific hotkey 66 | */ 67 | - (void)unregisterHotKey:(DDHotKey *)hotKey; 68 | 69 | /** 70 | Unregister all hotkeys 71 | */ 72 | - (void)unregisterAllHotKeys; 73 | 74 | /** 75 | Unregister all hotkeys with a specific target 76 | */ 77 | - (void)unregisterHotKeysWithTarget:(id)target; 78 | 79 | /** 80 | Unregister all hotkeys with a specific target and action 81 | */ 82 | - (void)unregisterHotKeysWithTarget:(id)target action:(SEL)action; 83 | 84 | /** 85 | Unregister a hotkey with a specific keycode and modifier flags 86 | */ 87 | - (void)unregisterHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags; 88 | 89 | /** 90 | Returns a set of currently registered hotkeys 91 | **/ 92 | - (NSSet *)registeredHotKeys; 93 | 94 | @end 95 | 96 | NS_ASSUME_NONNULL_END 97 | -------------------------------------------------------------------------------- /DDHotKeyCenter.m: -------------------------------------------------------------------------------- 1 | /* 2 | DDHotKey -- DDHotKeyCenter.m 3 | 4 | Copyright (c) Dave DeLong 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 7 | 8 | The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the author(s) or copyright holder(s) be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. 9 | */ 10 | 11 | #import 12 | #import 13 | 14 | #import "DDHotKeyCenter.h" 15 | #import "DDHotKeyUtilities.h" 16 | 17 | #pragma mark Private Global Declarations 18 | 19 | OSStatus dd_hotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData); 20 | 21 | #pragma mark DDHotKey 22 | 23 | @interface DDHotKey () 24 | 25 | @property (nonatomic, retain) NSValue *hotKeyRef; 26 | @property (nonatomic) UInt32 hotKeyID; 27 | 28 | 29 | @property (nonatomic, assign, setter = _setTarget:) id target; 30 | @property (nonatomic, setter = _setAction:) SEL action; 31 | @property (nonatomic, strong, setter = _setObject:) id object; 32 | @property (nonatomic, copy, setter = _setTask:) DDHotKeyTask task; 33 | 34 | @property (nonatomic, setter = _setKeyCode:) unsigned short keyCode; 35 | @property (nonatomic, setter = _setModifierFlags:) NSUInteger modifierFlags; 36 | 37 | @end 38 | 39 | @implementation DDHotKey 40 | 41 | + (instancetype)hotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags task:(DDHotKeyTask)task { 42 | DDHotKey *newHotKey = [[self alloc] init]; 43 | [newHotKey _setTask:task]; 44 | [newHotKey _setKeyCode:keyCode]; 45 | [newHotKey _setModifierFlags:flags]; 46 | return newHotKey; 47 | } 48 | 49 | - (void) dealloc { 50 | [[DDHotKeyCenter sharedHotKeyCenter] unregisterHotKey:self]; 51 | [super dealloc]; 52 | } 53 | 54 | - (NSUInteger)hash { 55 | return [self keyCode] ^ [self modifierFlags]; 56 | } 57 | 58 | - (BOOL)isEqual:(id)object { 59 | BOOL equal = NO; 60 | if ([object isKindOfClass:[DDHotKey class]]) { 61 | equal = ([object keyCode] == [self keyCode]); 62 | equal &= ([object modifierFlags] == [self modifierFlags]); 63 | } 64 | return equal; 65 | } 66 | 67 | - (NSString *)description { 68 | NSMutableArray *bits = [NSMutableArray array]; 69 | if ((_modifierFlags & NSEventModifierFlagControl) > 0) { [bits addObject:@"NSEventModifierFlagControl"]; } 70 | if ((_modifierFlags & NSEventModifierFlagCommand) > 0) { [bits addObject:@"NSEventModifierFlagCommand"]; } 71 | if ((_modifierFlags & NSEventModifierFlagShift) > 0) { [bits addObject:@"NSEventModifierFlagShift"]; } 72 | if ((_modifierFlags & NSEventModifierFlagOption) > 0) { [bits addObject:@"NSEventModifierFlagOption"]; } 73 | 74 | NSString *flags = [NSString stringWithFormat:@"(%@)", [bits componentsJoinedByString:@" | "]]; 75 | NSString *invokes = @"(block)"; 76 | if ([self target] != nil && [self action] != nil) { 77 | invokes = [NSString stringWithFormat:@"[%@ %@]", [self target], NSStringFromSelector([self action])]; 78 | } 79 | return [NSString stringWithFormat:@"%@\n\t(key: %hu\n\tflags: %@\n\tinvokes: %@)", [super description], [self keyCode], flags, invokes]; 80 | } 81 | 82 | - (void)invokeWithEvent:(NSEvent *)event { 83 | if (_target != nil && _action != nil && [_target respondsToSelector:_action]) { 84 | #pragma clang diagnostic push 85 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 86 | [_target performSelector:_action withObject:event withObject:_object]; 87 | #pragma clang diagnostic pop 88 | } else if (_task != nil) { 89 | _task(event); 90 | } 91 | } 92 | 93 | @end 94 | 95 | #pragma mark DDHotKeyCenter 96 | 97 | static DDHotKeyCenter *sharedHotKeyCenter = nil; 98 | 99 | @implementation DDHotKeyCenter { 100 | NSMutableSet *_registeredHotKeys; 101 | UInt32 _nextHotKeyID; 102 | } 103 | 104 | + (instancetype)sharedHotKeyCenter { 105 | static dispatch_once_t onceToken; 106 | dispatch_once(&onceToken, ^{ 107 | sharedHotKeyCenter = [super allocWithZone:nil]; 108 | sharedHotKeyCenter = [sharedHotKeyCenter init]; 109 | 110 | EventTypeSpec eventSpec; 111 | eventSpec.eventClass = kEventClassKeyboard; 112 | eventSpec.eventKind = kEventHotKeyReleased; 113 | InstallApplicationEventHandler(&dd_hotKeyHandler, 1, &eventSpec, NULL, NULL); 114 | }); 115 | return sharedHotKeyCenter; 116 | } 117 | 118 | + (id)allocWithZone:(NSZone *)zone { 119 | return sharedHotKeyCenter; 120 | } 121 | 122 | - (id)init { 123 | if (self != sharedHotKeyCenter) { return sharedHotKeyCenter; } 124 | 125 | self = [super init]; 126 | if (self) { 127 | _registeredHotKeys = [[NSMutableSet alloc] init]; 128 | _nextHotKeyID = 1; 129 | } 130 | return self; 131 | } 132 | 133 | - (NSSet *)hotKeysMatching:(BOOL(^)(DDHotKey *hotkey))matcher { 134 | NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { 135 | return matcher(evaluatedObject); 136 | }]; 137 | return [_registeredHotKeys filteredSetUsingPredicate:predicate]; 138 | } 139 | 140 | - (BOOL)hasRegisteredHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags { 141 | return [self hotKeysMatching:^BOOL(DDHotKey *hotkey) { 142 | return hotkey.keyCode == keyCode && hotkey.modifierFlags == flags; 143 | }].count > 0; 144 | } 145 | 146 | - (DDHotKey *)_registerHotKey:(DDHotKey *)hotKey withError:(NSError **)error { 147 | 148 | error = error ?: &(NSError * __autoreleasing){ nil }; 149 | 150 | if ([_registeredHotKeys containsObject:hotKey]) { 151 | return hotKey; 152 | } 153 | 154 | EventHotKeyID keyID; 155 | keyID.signature = 'htk1'; 156 | keyID.id = _nextHotKeyID; 157 | 158 | EventHotKeyRef carbonHotKey; 159 | UInt32 flags = DDCarbonModifierFlagsFromCocoaModifiers([hotKey modifierFlags]); 160 | OSStatus err = RegisterEventHotKey([hotKey keyCode], flags, keyID, GetEventDispatcherTarget(), 0, &carbonHotKey); 161 | 162 | //error registering hot key 163 | if (err != 0) { 164 | *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; 165 | return nil; 166 | } 167 | 168 | NSValue *refValue = [NSValue valueWithPointer:carbonHotKey]; 169 | [hotKey setHotKeyRef:refValue]; 170 | [hotKey setHotKeyID:_nextHotKeyID]; 171 | 172 | _nextHotKeyID++; 173 | [_registeredHotKeys addObject:hotKey]; 174 | 175 | return hotKey; 176 | } 177 | 178 | - (DDHotKey *)registerHotKey:(DDHotKey *)hotKey withError:(NSError **)error { 179 | return [self _registerHotKey:hotKey withError:error]; 180 | } 181 | 182 | - (void)unregisterHotKey:(DDHotKey *)hotKey { 183 | NSValue *hotKeyRef = [hotKey hotKeyRef]; 184 | if (hotKeyRef) { 185 | EventHotKeyRef carbonHotKey = (EventHotKeyRef)[hotKeyRef pointerValue]; 186 | UnregisterEventHotKey(carbonHotKey); 187 | [hotKey setHotKeyRef:nil]; 188 | } 189 | 190 | [_registeredHotKeys removeObject:hotKey]; 191 | } 192 | 193 | - (DDHotKey *)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags task:(DDHotKeyTask)task error:(NSError **)error { 194 | error = error ?: &(NSError * __autoreleasing){ nil }; 195 | 196 | //we can't add a new hotkey if something already has this combo 197 | if ([self hasRegisteredHotKeyWithKeyCode:keyCode modifierFlags:flags]) { return nil; } 198 | 199 | DDHotKey *newHotKey = [[DDHotKey alloc] init]; 200 | [newHotKey _setTask:task]; 201 | [newHotKey _setKeyCode:keyCode]; 202 | [newHotKey _setModifierFlags:flags]; 203 | 204 | return [self _registerHotKey:newHotKey withError:error]; 205 | } 206 | 207 | - (DDHotKey *)registerHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags target:(id)target action:(SEL)action object:(id)object error:(NSError **)error { 208 | 209 | error = error ?: &(NSError * __autoreleasing){ nil }; 210 | 211 | //we can't add a new hotkey if something already has this combo 212 | if ([self hasRegisteredHotKeyWithKeyCode:keyCode modifierFlags:flags]) { return nil; } 213 | 214 | //build the hotkey object: 215 | DDHotKey *newHotKey = [[DDHotKey alloc] init]; 216 | [newHotKey _setTarget:target]; 217 | [newHotKey _setAction:action]; 218 | [newHotKey _setObject:object]; 219 | [newHotKey _setKeyCode:keyCode]; 220 | [newHotKey _setModifierFlags:flags]; 221 | return [self _registerHotKey:newHotKey withError:error]; 222 | } 223 | 224 | - (void)unregisterHotKeysMatching:(BOOL(^)(DDHotKey *hotkey))matcher { 225 | //explicitly unregister the hotkey, since relying on the unregistration in -dealloc can be problematic 226 | @autoreleasepool { 227 | NSSet *matches = [self hotKeysMatching:matcher]; 228 | for (DDHotKey *hotKey in matches) { 229 | [self unregisterHotKey:hotKey]; 230 | } 231 | } 232 | } 233 | 234 | - (void)unregisterHotKeysWithTarget:(id)target { 235 | [self unregisterHotKeysMatching:^BOOL(DDHotKey *hotkey) { 236 | return hotkey.target == target; 237 | }]; 238 | } 239 | 240 | - (void)unregisterHotKeysWithTarget:(id)target action:(SEL)action { 241 | [self unregisterHotKeysMatching:^BOOL(DDHotKey *hotkey) { 242 | return hotkey.target == target && sel_isEqual(hotkey.action, action); 243 | }]; 244 | } 245 | 246 | - (void)unregisterHotKeyWithKeyCode:(unsigned short)keyCode modifierFlags:(NSUInteger)flags { 247 | [self unregisterHotKeysMatching:^BOOL(DDHotKey *hotkey) { 248 | return hotkey.keyCode == keyCode && hotkey.modifierFlags == flags; 249 | }]; 250 | } 251 | 252 | - (void)unregisterAllHotKeys { 253 | NSSet *keys = [_registeredHotKeys copy]; 254 | for (DDHotKey *key in keys) { 255 | [self unregisterHotKey:key]; 256 | } 257 | } 258 | 259 | - (NSSet *)registeredHotKeys { 260 | return [self hotKeysMatching:^BOOL(DDHotKey *hotkey) { 261 | return hotkey.hotKeyRef != NULL; 262 | }]; 263 | } 264 | 265 | @end 266 | 267 | OSStatus dd_hotKeyHandler(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) { 268 | @autoreleasepool { 269 | EventHotKeyID hotKeyID; 270 | GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hotKeyID), NULL, &hotKeyID); 271 | 272 | UInt32 keyID = hotKeyID.id; 273 | 274 | NSSet *matchingHotKeys = [[DDHotKeyCenter sharedHotKeyCenter] hotKeysMatching:^BOOL(DDHotKey *hotkey) { 275 | return hotkey.hotKeyID == keyID; 276 | }]; 277 | if ([matchingHotKeys count] > 1) { NSLog(@"ERROR!"); } 278 | DDHotKey *matchingHotKey = [matchingHotKeys anyObject]; 279 | 280 | NSEvent *event = [NSEvent eventWithEventRef:theEvent]; 281 | NSEvent *keyEvent = [NSEvent keyEventWithType:NSEventTypeKeyUp 282 | location:[event locationInWindow] 283 | modifierFlags:[event modifierFlags] 284 | timestamp:[event timestamp] 285 | windowNumber:-1 286 | context:nil 287 | characters:@"" 288 | charactersIgnoringModifiers:@"" 289 | isARepeat:NO 290 | keyCode:[matchingHotKey keyCode]]; 291 | 292 | [matchingHotKey invokeWithEvent:keyEvent]; 293 | } 294 | 295 | return noErr; 296 | } 297 | -------------------------------------------------------------------------------- /DDHotKeyUtilities.h: -------------------------------------------------------------------------------- 1 | /* 2 | DDHotKey -- DDHotKeyUtilities.h 3 | 4 | Copyright (c) Dave DeLong 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 7 | 8 | The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the author(s) or copyright holder(s) be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. 9 | */ 10 | 11 | #import 12 | 13 | extern NSString * _Nonnull DDStringFromKeyCode(unsigned short keyCode, NSUInteger modifiers); 14 | extern UInt32 DDCarbonModifierFlagsFromCocoaModifiers(NSUInteger flags); 15 | -------------------------------------------------------------------------------- /DDHotKeyUtilities.m: -------------------------------------------------------------------------------- 1 | /* 2 | DDHotKey -- DDHotKeyUtilities.m 3 | 4 | Copyright (c) Dave DeLong 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 7 | 8 | The software is provided "as is", without warranty of any kind, including all implied warranties of merchantability and fitness. In no event shall the author(s) or copyright holder(s) be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. 9 | */ 10 | 11 | #import "DDHotKeyUtilities.h" 12 | #import 13 | #import 14 | 15 | static NSDictionary *_DDKeyCodeToCharacterMap(void); 16 | static NSDictionary *_DDKeyCodeToCharacterMap(void) { 17 | static NSDictionary *keyCodeMap = nil; 18 | static dispatch_once_t onceToken; 19 | dispatch_once(&onceToken, ^{ 20 | keyCodeMap = @{ 21 | @(kVK_Return) : @"↩", 22 | @(kVK_Tab) : @"⇥", 23 | @(kVK_Space) : @"⎵", 24 | @(kVK_Delete) : @"⌫", 25 | @(kVK_Escape) : @"⎋", 26 | @(kVK_Command) : @"⌘", 27 | @(kVK_Shift) : @"⇧", 28 | @(kVK_CapsLock) : @"⇪", 29 | @(kVK_Option) : @"⌥", 30 | @(kVK_Control) : @"⌃", 31 | @(kVK_RightShift) : @"⇧", 32 | @(kVK_RightOption) : @"⌥", 33 | @(kVK_RightControl) : @"⌃", 34 | @(kVK_VolumeUp) : @"🔊", 35 | @(kVK_VolumeDown) : @"🔈", 36 | @(kVK_Mute) : @"🔇", 37 | @(kVK_Function) : @"\u2318", 38 | @(kVK_F1) : @"F1", 39 | @(kVK_F2) : @"F2", 40 | @(kVK_F3) : @"F3", 41 | @(kVK_F4) : @"F4", 42 | @(kVK_F5) : @"F5", 43 | @(kVK_F6) : @"F6", 44 | @(kVK_F7) : @"F7", 45 | @(kVK_F8) : @"F8", 46 | @(kVK_F9) : @"F9", 47 | @(kVK_F10) : @"F10", 48 | @(kVK_F11) : @"F11", 49 | @(kVK_F12) : @"F12", 50 | @(kVK_F13) : @"F13", 51 | @(kVK_F14) : @"F14", 52 | @(kVK_F15) : @"F15", 53 | @(kVK_F16) : @"F16", 54 | @(kVK_F17) : @"F17", 55 | @(kVK_F18) : @"F18", 56 | @(kVK_F19) : @"F19", 57 | @(kVK_F20) : @"F20", 58 | // @(kVK_Help) : @"", 59 | @(kVK_ForwardDelete) : @"⌦", 60 | @(kVK_Home) : @"↖", 61 | @(kVK_End) : @"↘", 62 | @(kVK_PageUp) : @"⇞", 63 | @(kVK_PageDown) : @"⇟", 64 | @(kVK_LeftArrow) : @"←", 65 | @(kVK_RightArrow) : @"→", 66 | @(kVK_DownArrow) : @"↓", 67 | @(kVK_UpArrow) : @"↑", 68 | }; 69 | }); 70 | return keyCodeMap; 71 | } 72 | 73 | NSString *DDStringFromKeyCode(unsigned short keyCode, NSUInteger modifiers) { 74 | NSMutableString *final = [NSMutableString stringWithString:@""]; 75 | NSDictionary *characterMap = _DDKeyCodeToCharacterMap(); 76 | 77 | if (modifiers & NSEventModifierFlagControl) { 78 | [final appendString:[characterMap objectForKey:@(kVK_Control)]]; 79 | } 80 | if (modifiers & NSEventModifierFlagOption) { 81 | [final appendString:[characterMap objectForKey:@(kVK_Option)]]; 82 | } 83 | if (modifiers & NSEventModifierFlagShift) { 84 | [final appendString:[characterMap objectForKey:@(kVK_Shift)]]; 85 | } 86 | if (modifiers & NSEventModifierFlagCommand) { 87 | [final appendString:[characterMap objectForKey:@(kVK_Command)]]; 88 | } 89 | 90 | if (keyCode == kVK_Control || keyCode == kVK_Option || keyCode == kVK_Shift || keyCode == kVK_Command) { 91 | return final; 92 | } 93 | 94 | NSString *mapped = [characterMap objectForKey:@(keyCode)]; 95 | if (mapped != nil) { 96 | [final appendString:mapped]; 97 | } else { 98 | 99 | TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); 100 | CFDataRef uchr = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); 101 | 102 | // Fix crash using non-unicode layouts, such as Chinese or Japanese. 103 | if (!uchr) { 104 | CFRelease(currentKeyboard); 105 | currentKeyboard = TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); 106 | uchr = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); 107 | } 108 | 109 | const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr); 110 | 111 | if (keyboardLayout) { 112 | UInt32 deadKeyState = 0; 113 | UniCharCount maxStringLength = 255; 114 | UniCharCount actualStringLength = 0; 115 | UniChar unicodeString[maxStringLength]; 116 | 117 | UInt32 keyModifiers = DDCarbonModifierFlagsFromCocoaModifiers(modifiers); 118 | 119 | OSStatus status = UCKeyTranslate(keyboardLayout, 120 | keyCode, kUCKeyActionDown, keyModifiers, 121 | LMGetKbdType(), 0, 122 | &deadKeyState, 123 | maxStringLength, 124 | &actualStringLength, unicodeString); 125 | 126 | if (actualStringLength > 0 && status == noErr) { 127 | NSString *characterString = [NSString stringWithCharacters:unicodeString length:(NSUInteger)actualStringLength]; 128 | 129 | [final appendString:characterString]; 130 | } 131 | } 132 | } 133 | 134 | return final; 135 | } 136 | 137 | UInt32 DDCarbonModifierFlagsFromCocoaModifiers(NSUInteger flags) { 138 | UInt32 newFlags = 0; 139 | if ((flags & NSEventModifierFlagControl) > 0) { newFlags |= controlKey; } 140 | if ((flags & NSEventModifierFlagCommand) > 0) { newFlags |= cmdKey; } 141 | if ((flags & NSEventModifierFlagShift) > 0) { newFlags |= shiftKey; } 142 | if ((flags & NSEventModifierFlagOption) > 0) { newFlags |= optionKey; } 143 | if ((flags & NSEventModifierFlagCapsLock) > 0) { newFlags |= alphaLock; } 144 | return newFlags; 145 | } 146 | -------------------------------------------------------------------------------- /DataModel.xcdatamodel/elements: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaomoreno/thyme/44ac7c0f89707ac97d043d7f3d87f4438190c4ba/DataModel.xcdatamodel/elements -------------------------------------------------------------------------------- /DataModel.xcdatamodel/layout: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaomoreno/thyme/44ac7c0f89707ac97d043d7f3d87f4438190c4ba/DataModel.xcdatamodel/layout -------------------------------------------------------------------------------- /English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Frameworks/Growl.framework/Growl: -------------------------------------------------------------------------------- 1 | Versions/Current/Growl -------------------------------------------------------------------------------- /Frameworks/Growl.framework/Headers: -------------------------------------------------------------------------------- 1 | Versions/Current/Headers -------------------------------------------------------------------------------- /Frameworks/Growl.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /Frameworks/Growl.framework/Versions/A/Growl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaomoreno/thyme/44ac7c0f89707ac97d043d7f3d87f4438190c4ba/Frameworks/Growl.framework/Versions/A/Growl -------------------------------------------------------------------------------- /Frameworks/Growl.framework/Versions/A/Headers/Growl.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef __OBJC__ 4 | # include 5 | #endif 6 | -------------------------------------------------------------------------------- /Frameworks/Growl.framework/Versions/A/Headers/GrowlApplicationBridge.h: -------------------------------------------------------------------------------- 1 | // 2 | // GrowlApplicationBridge.h 3 | // Growl 4 | // 5 | // Created by Evan Schoenberg on Wed Jun 16 2004. 6 | // Copyright 2004-2006 The Growl Project. All rights reserved. 7 | // 8 | 9 | /*! 10 | * @header GrowlApplicationBridge.h 11 | * @abstract Defines the GrowlApplicationBridge class. 12 | * @discussion This header defines the GrowlApplicationBridge class as well as 13 | * the GROWL_PREFPANE_BUNDLE_IDENTIFIER constant. 14 | */ 15 | 16 | #ifndef __GrowlApplicationBridge_h__ 17 | #define __GrowlApplicationBridge_h__ 18 | 19 | #import 20 | #import 21 | #import 22 | 23 | //Forward declarations 24 | @protocol GrowlApplicationBridgeDelegate; 25 | 26 | //------------------------------------------------------------------------------ 27 | #pragma mark - 28 | 29 | /*! 30 | * @class GrowlApplicationBridge 31 | * @abstract A class used to interface with Growl. 32 | * @discussion This class provides a means to interface with Growl. 33 | * 34 | * Currently it provides a way to detect if Growl is installed and launch the 35 | * GrowlHelperApp if it's not already running. 36 | */ 37 | @interface GrowlApplicationBridge : NSObject { 38 | 39 | } 40 | 41 | /*! 42 | * @method isGrowlInstalled 43 | * @abstract Detects whether Growl is installed. 44 | * @discussion Determines if the Growl prefpane and its helper app are installed. 45 | * @result this method will forever return YES. 46 | */ 47 | + (BOOL) isGrowlInstalled __attribute__((deprecated)); 48 | 49 | /*! 50 | * @method isGrowlRunning 51 | * @abstract Detects whether GrowlHelperApp is currently running. 52 | * @discussion Cycles through the process list to find whether GrowlHelperApp is running and returns its findings. 53 | * @result Returns YES if GrowlHelperApp is running, NO otherwise. 54 | */ 55 | + (BOOL) isGrowlRunning; 56 | 57 | 58 | /*! 59 | * @method isMistEnabled 60 | * @abstract Gives the caller a fairly good indication of whether or not built-in notifications(Mist) will be used. 61 | * @discussion since this call makes use of isGrowlRunning it is entirely possible for this value to change between call and 62 | * executing a notification dispatch 63 | * @result Returns YES if Growl isn't reachable and the developer has not opted-out of 64 | * Mist and the user hasn't set the global mist enable key to false. 65 | */ 66 | + (BOOL)isMistEnabled; 67 | 68 | /*! 69 | * @method setShouldUseBuiltInNotifications 70 | * @abstract opt-out mechanism for the mist notification style in the event growl can't be reached. 71 | * @discussion if growl is unavailable due to not being installed or as a result of being turned off then 72 | * this option can enable/disable a built-in fire and forget display style 73 | * @param should Specifies whether or not the developer wants to opt-in (default) or opt out 74 | * of the built-in Mist style in the event Growl is unreachable. 75 | */ 76 | + (void)setShouldUseBuiltInNotifications:(BOOL)should; 77 | 78 | /*! 79 | * @method shouldUseBuiltInNotifications 80 | * @abstract returns the current opt-in state of the framework's use of the Mist display style. 81 | * @result Returns NO if the developer opt-ed out of Mist, the default value is YES. 82 | */ 83 | + (BOOL)shouldUseBuiltInNotifications; 84 | 85 | #pragma mark - 86 | 87 | /*! 88 | * @method setGrowlDelegate: 89 | * @abstract Set the object which will be responsible for providing and receiving Growl information. 90 | * @discussion This must be called before using GrowlApplicationBridge. 91 | * 92 | * The methods in the GrowlApplicationBridgeDelegate protocol are required 93 | * and return the basic information needed to register with Growl. 94 | * 95 | * The methods in the GrowlApplicationBridgeDelegate_InformalProtocol 96 | * informal protocol are individually optional. They provide a greater 97 | * degree of interaction between the application and growl such as informing 98 | * the application when one of its Growl notifications is clicked by the user. 99 | * 100 | * The methods in the GrowlApplicationBridgeDelegate_Installation_InformalProtocol 101 | * informal protocol are individually optional and are only applicable when 102 | * using the Growl-WithInstaller.framework which allows for automated Growl 103 | * installation. 104 | * 105 | * When this method is called, data will be collected from inDelegate, Growl 106 | * will be launched if it is not already running, and the application will be 107 | * registered with Growl. 108 | * 109 | * If using the Growl-WithInstaller framework, if Growl is already installed 110 | * but this copy of the framework has an updated version of Growl, the user 111 | * will be prompted to update automatically. 112 | * 113 | * @param inDelegate The delegate for the GrowlApplicationBridge. It must conform to the GrowlApplicationBridgeDelegate protocol. 114 | */ 115 | + (void) setGrowlDelegate:(id)inDelegate; 116 | 117 | /*! 118 | * @method growlDelegate 119 | * @abstract Return the object responsible for providing and receiving Growl information. 120 | * @discussion See setGrowlDelegate: for details. 121 | * @result The Growl delegate. 122 | */ 123 | + (id) growlDelegate; 124 | 125 | #pragma mark - 126 | 127 | /*! 128 | * @method notifyWithTitle:description:notificationName:iconData:priority:isSticky:clickContext: 129 | * @abstract Send a Growl notification. 130 | * @discussion This is the preferred means for sending a Growl notification. 131 | * The notification name and at least one of the title and description are 132 | * required (all three are preferred). All other parameters may be 133 | * nil (or 0 or NO as appropriate) to accept default values. 134 | * 135 | * If using the Growl-WithInstaller framework, if Growl is not installed the 136 | * user will be prompted to install Growl. If the user cancels, this method 137 | * will have no effect until the next application session, at which time when 138 | * it is called the user will be prompted again. The user is also given the 139 | * option to not be prompted again. If the user does choose to install Growl, 140 | * the requested notification will be displayed once Growl is installed and 141 | * running. 142 | * 143 | * @param title The title of the notification displayed to the user. 144 | * @param description The full description of the notification displayed to the user. 145 | * @param notifName The internal name of the notification. Should be human-readable, as it will be displayed in the Growl preference pane. 146 | * @param iconData NSData object to show with the notification as its icon. If nil, the application's icon will be used instead. 147 | * @param priority The priority of the notification. The default value is 0; positive values are higher priority and negative values are lower priority. Not all Growl displays support priority. 148 | * @param isSticky If YES, the notification will remain on screen until clicked. Not all Growl displays support sticky notifications. 149 | * @param clickContext A context passed back to the Growl delegate if it implements -(void)growlNotificationWasClicked: and the notification is clicked. Not all display plugins support clicking. The clickContext must be plist-encodable (completely of NSString, NSArray, NSNumber, NSDictionary, and NSData types). 150 | */ 151 | + (void) notifyWithTitle:(NSString *)title 152 | description:(NSString *)description 153 | notificationName:(NSString *)notifName 154 | iconData:(NSData *)iconData 155 | priority:(signed int)priority 156 | isSticky:(BOOL)isSticky 157 | clickContext:(id)clickContext; 158 | 159 | /*! 160 | * @method notifyWithTitle:description:notificationName:iconData:priority:isSticky:clickContext:identifier: 161 | * @abstract Send a Growl notification. 162 | * @discussion This is the preferred means for sending a Growl notification. 163 | * The notification name and at least one of the title and description are 164 | * required (all three are preferred). All other parameters may be 165 | * nil (or 0 or NO as appropriate) to accept default values. 166 | * 167 | * If using the Growl-WithInstaller framework, if Growl is not installed the 168 | * user will be prompted to install Growl. If the user cancels, this method 169 | * will have no effect until the next application session, at which time when 170 | * it is called the user will be prompted again. The user is also given the 171 | * option to not be prompted again. If the user does choose to install Growl, 172 | * the requested notification will be displayed once Growl is installed and 173 | * running. 174 | * 175 | * @param title The title of the notification displayed to the user. 176 | * @param description The full description of the notification displayed to the user. 177 | * @param notifName The internal name of the notification. Should be human-readable, as it will be displayed in the Growl preference pane. 178 | * @param iconData NSData object to show with the notification as its icon. If nil, the application's icon will be used instead. 179 | * @param priority The priority of the notification. The default value is 0; positive values are higher priority and negative values are lower priority. Not all Growl displays support priority. 180 | * @param isSticky If YES, the notification will remain on screen until clicked. Not all Growl displays support sticky notifications. 181 | * @param clickContext A context passed back to the Growl delegate if it implements -(void)growlNotificationWasClicked: and the notification is clicked. Not all display plugins support clicking. The clickContext must be plist-encodable (completely of NSString, NSArray, NSNumber, NSDictionary, and NSData types). 182 | * @param identifier An identifier for this notification. Notifications with equal identifiers are coalesced. 183 | */ 184 | + (void) notifyWithTitle:(NSString *)title 185 | description:(NSString *)description 186 | notificationName:(NSString *)notifName 187 | iconData:(NSData *)iconData 188 | priority:(signed int)priority 189 | isSticky:(BOOL)isSticky 190 | clickContext:(id)clickContext 191 | identifier:(NSString *)identifier; 192 | 193 | /*! @method notifyWithDictionary: 194 | * @abstract Notifies using a userInfo dictionary suitable for passing to 195 | * NSDistributedNotificationCenter. 196 | * @param userInfo The dictionary to notify with. 197 | * @discussion Before Growl 0.6, your application would have posted 198 | * notifications using NSDistributedNotificationCenter by 199 | * creating a userInfo dictionary with the notification data. This had the 200 | * advantage of allowing you to add other data to the dictionary for programs 201 | * besides Growl that might be listening. 202 | * 203 | * This method allows you to use such dictionaries without being restricted 204 | * to using NSDistributedNotificationCenter. The keys for this dictionary 205 | * can be found in GrowlDefines.h. 206 | */ 207 | + (void) notifyWithDictionary:(NSDictionary *)userInfo; 208 | 209 | #pragma mark - 210 | 211 | /*! @method registerWithDictionary: 212 | * @abstract Register your application with Growl without setting a delegate. 213 | * @discussion When you call this method with a dictionary, 214 | * GrowlApplicationBridge registers your application using that dictionary. 215 | * If you pass nil, GrowlApplicationBridge will ask the delegate 216 | * (if there is one) for a dictionary, and if that doesn't work, it will look 217 | * in your application's bundle for an auto-discoverable plist. 218 | * (XXX refer to more information on that) 219 | * 220 | * If you pass a dictionary to this method, it must include the 221 | * GROWL_APP_NAME key, unless a delegate is set. 222 | * 223 | * This method is mainly an alternative to the delegate system introduced 224 | * with Growl 0.6. Without a delegate, you cannot receive callbacks such as 225 | * -growlIsReady (since they are sent to the delegate). You can, 226 | * however, set a delegate after registering without one. 227 | * 228 | * This method was introduced in Growl.framework 0.7. 229 | */ 230 | + (BOOL) registerWithDictionary:(NSDictionary *)regDict; 231 | 232 | /*! @method reregisterGrowlNotifications 233 | * @abstract Reregister the notifications for this application. 234 | * @discussion This method does not normally need to be called. If your 235 | * application changes what notifications it is registering with Growl, call 236 | * this method to have the Growl delegate's 237 | * -registrationDictionaryForGrowl method called again and the 238 | * Growl registration information updated. 239 | * 240 | * This method is now implemented using -registerWithDictionary:. 241 | */ 242 | + (void) reregisterGrowlNotifications; 243 | 244 | #pragma mark - 245 | 246 | /*! @method setWillRegisterWhenGrowlIsReady: 247 | * @abstract Tells GrowlApplicationBridge to register with Growl when Growl 248 | * launches (or not). 249 | * @discussion When Growl has started listening for notifications, it posts a 250 | * GROWL_IS_READY notification on the Distributed Notification 251 | * Center. GrowlApplicationBridge listens for this notification, using it to 252 | * perform various tasks (such as calling your delegate's 253 | * -growlIsReady method, if it has one). If this method is 254 | * called with YES, one of those tasks will be to reregister 255 | * with Growl (in the manner of -reregisterGrowlNotifications). 256 | * 257 | * This attribute is automatically set back to NO (the default) 258 | * after every GROWL_IS_READY notification. 259 | * @param flag YES if you want GrowlApplicationBridge to register with 260 | * Growl when next it is ready; NO if not. 261 | */ 262 | + (void) setWillRegisterWhenGrowlIsReady:(BOOL)flag; 263 | 264 | /*! @method willRegisterWhenGrowlIsReady 265 | * @abstract Reports whether GrowlApplicationBridge will register with Growl 266 | * when Growl next launches. 267 | * @result YES if GrowlApplicationBridge will register with Growl 268 | * when next it posts GROWL_IS_READY; NO if not. 269 | */ 270 | + (BOOL) willRegisterWhenGrowlIsReady; 271 | 272 | #pragma mark - 273 | 274 | /*! @method registrationDictionaryFromDelegate 275 | * @abstract Asks the delegate for a registration dictionary. 276 | * @discussion If no delegate is set, or if the delegate's 277 | * -registrationDictionaryForGrowl method returns 278 | * nil, this method returns nil. 279 | * 280 | * This method does not attempt to clean up the dictionary in any way - for 281 | * example, if it is missing the GROWL_APP_NAME key, the result 282 | * will be missing it too. Use +[GrowlApplicationBridge 283 | * registrationDictionaryByFillingInDictionary:] or 284 | * +[GrowlApplicationBridge 285 | * registrationDictionaryByFillingInDictionary:restrictToKeys:] to try 286 | * to fill in missing keys. 287 | * 288 | * This method was introduced in Growl.framework 0.7. 289 | * @result A registration dictionary. 290 | */ 291 | + (NSDictionary *) registrationDictionaryFromDelegate; 292 | 293 | /*! @method registrationDictionaryFromBundle: 294 | * @abstract Looks in a bundle for a registration dictionary. 295 | * @discussion This method looks in a bundle for an auto-discoverable 296 | * registration dictionary file using -[NSBundle 297 | * pathForResource:ofType:]. If it finds one, it loads the file using 298 | * +[NSDictionary dictionaryWithContentsOfFile:] and returns the 299 | * result. 300 | * 301 | * If you pass nil as the bundle, the main bundle is examined. 302 | * 303 | * This method does not attempt to clean up the dictionary in any way - for 304 | * example, if it is missing the GROWL_APP_NAME key, the result 305 | * will be missing it too. Use +[GrowlApplicationBridge 306 | * registrationDictionaryByFillingInDictionary:] or 307 | * +[GrowlApplicationBridge 308 | * registrationDictionaryByFillingInDictionary:restrictToKeys:] to try 309 | * to fill in missing keys. 310 | * 311 | * This method was introduced in Growl.framework 0.7. 312 | * @result A registration dictionary. 313 | */ 314 | + (NSDictionary *) registrationDictionaryFromBundle:(NSBundle *)bundle; 315 | 316 | /*! @method bestRegistrationDictionary 317 | * @abstract Obtains a registration dictionary, filled out to the best of 318 | * GrowlApplicationBridge's knowledge. 319 | * @discussion This method creates a registration dictionary as best 320 | * GrowlApplicationBridge knows how. 321 | * 322 | * First, GrowlApplicationBridge contacts the Growl delegate (if there is 323 | * one) and gets the registration dictionary from that. If no such dictionary 324 | * was obtained, GrowlApplicationBridge looks in your application's main 325 | * bundle for an auto-discoverable registration dictionary file. If that 326 | * doesn't exist either, this method returns nil. 327 | * 328 | * Second, GrowlApplicationBridge calls 329 | * +registrationDictionaryByFillingInDictionary: with whatever 330 | * dictionary was obtained. The result of that method is the result of this 331 | * method. 332 | * 333 | * GrowlApplicationBridge uses this method when you call 334 | * +setGrowlDelegate:, or when you call 335 | * +registerWithDictionary: with nil. 336 | * 337 | * This method was introduced in Growl.framework 0.7. 338 | * @result A registration dictionary. 339 | */ 340 | + (NSDictionary *) bestRegistrationDictionary; 341 | 342 | #pragma mark - 343 | 344 | /*! @method registrationDictionaryByFillingInDictionary: 345 | * @abstract Tries to fill in missing keys in a registration dictionary. 346 | * @discussion This method examines the passed-in dictionary for missing keys, 347 | * and tries to work out correct values for them. As of 0.7, it uses: 348 | * 349 | * Key Value 350 | * --- ----- 351 | * GROWL_APP_NAME CFBundleExecutableName 352 | * GROWL_APP_ICON_DATA The data of the icon of the application. 353 | * GROWL_APP_LOCATION The location of the application. 354 | * GROWL_NOTIFICATIONS_DEFAULT GROWL_NOTIFICATIONS_ALL 355 | * 356 | * Keys are only filled in if missing; if a key is present in the dictionary, 357 | * its value will not be changed. 358 | * 359 | * This method was introduced in Growl.framework 0.7. 360 | * @param regDict The dictionary to fill in. 361 | * @result The dictionary with the keys filled in. This is an autoreleased 362 | * copy of regDict. 363 | */ 364 | + (NSDictionary *) registrationDictionaryByFillingInDictionary:(NSDictionary *)regDict; 365 | 366 | /*! @method registrationDictionaryByFillingInDictionary:restrictToKeys: 367 | * @abstract Tries to fill in missing keys in a registration dictionary. 368 | * @discussion This method examines the passed-in dictionary for missing keys, 369 | * and tries to work out correct values for them. As of 0.7, it uses: 370 | * 371 | * Key Value 372 | * --- ----- 373 | * GROWL_APP_NAME CFBundleExecutableName 374 | * GROWL_APP_ICON_DATA The data of the icon of the application. 375 | * GROWL_APP_LOCATION The location of the application. 376 | * GROWL_NOTIFICATIONS_DEFAULT GROWL_NOTIFICATIONS_ALL 377 | * 378 | * Only those keys that are listed in keys will be filled in. 379 | * Other missing keys are ignored. Also, keys are only filled in if missing; 380 | * if a key is present in the dictionary, its value will not be changed. 381 | * 382 | * This method was introduced in Growl.framework 0.7. 383 | * @param regDict The dictionary to fill in. 384 | * @param keys The keys to fill in. If nil, any missing keys are filled in. 385 | * @result The dictionary with the keys filled in. This is an autoreleased 386 | * copy of regDict. 387 | */ 388 | + (NSDictionary *) registrationDictionaryByFillingInDictionary:(NSDictionary *)regDict restrictToKeys:(NSSet *)keys; 389 | 390 | /*! @brief Tries to fill in missing keys in a notification dictionary. 391 | * @param notifDict The dictionary to fill in. 392 | * @return The dictionary with the keys filled in. This will be a separate instance from \a notifDict. 393 | * @discussion This function examines the \a notifDict for missing keys, and 394 | * tries to get them from the last known registration dictionary. As of 1.1, 395 | * the keys that it will look for are: 396 | * 397 | * \li GROWL_APP_NAME 398 | * \li GROWL_APP_ICON_DATA 399 | * 400 | * @since Growl.framework 1.1 401 | */ 402 | + (NSDictionary *) notificationDictionaryByFillingInDictionary:(NSDictionary *)regDict; 403 | 404 | + (NSDictionary *) frameworkInfoDictionary; 405 | 406 | #pragma mark - 407 | 408 | /*! 409 | *@method growlURLSchemeAvailable 410 | *@abstract Lets the app know whether growl:// is registered on the system, used for certain methods below this 411 | *@return Returns whether growl:// is registered on the system 412 | *@discussion Methods such as openGrowlPreferences rely on the growl:// URL scheme to function 413 | * Further, this method can provide a check on whether Growl is installed, 414 | * however, the framework will not be relying on this method for choosing when/how to notify, 415 | * and it is not recommended that the app rely on it for other than whether to use growl:// methods 416 | *@since Growl.framework 1.4 417 | */ 418 | + (BOOL) isGrowlURLSchemeAvailable; 419 | 420 | /*! 421 | * @method openGrowlPreferences: 422 | * @abstract Open Growl preferences, optionally to this app's settings, growl:// method 423 | * @param showApp Whether to show the application's settings, otherwise just opens to the last position 424 | * @return Return's whether opening the URL was succesfull or not. 425 | * @discussion Will launch if Growl is installed, but not running, and open the preferences window 426 | * Uses growl:// URL scheme 427 | * @since Growl.framework 1.4 428 | */ 429 | + (BOOL) openGrowlPreferences:(BOOL)showApp; 430 | 431 | @end 432 | 433 | //------------------------------------------------------------------------------ 434 | #pragma mark - 435 | 436 | /*! 437 | * @protocol GrowlApplicationBridgeDelegate 438 | * @abstract Required protocol for the Growl delegate. 439 | * @discussion The methods in this protocol are optional and are called 440 | * automatically as needed by GrowlApplicationBridge. See 441 | * +[GrowlApplicationBridge setGrowlDelegate:]. 442 | * See also GrowlApplicationBridgeDelegate_InformalProtocol. 443 | */ 444 | 445 | @protocol GrowlApplicationBridgeDelegate 446 | 447 | @optional 448 | 449 | /*! 450 | * @method registrationDictionaryForGrowl 451 | * @abstract Return the dictionary used to register this application with Growl. 452 | * @discussion The returned dictionary gives Growl the complete list of 453 | * notifications this application will ever send, and it also specifies which 454 | * notifications should be enabled by default. Each is specified by an array 455 | * of NSString objects. 456 | * 457 | * For most applications, these two arrays can be the same (if all sent 458 | * notifications should be displayed by default). 459 | * 460 | * The NSString objects of these arrays will correspond to the 461 | * notificationName: parameter passed in 462 | * +[GrowlApplicationBridge 463 | * notifyWithTitle:description:notificationName:iconData:priority:isSticky:clickContext:] calls. 464 | * 465 | * The dictionary should have the required key object pairs: 466 | * key: GROWL_NOTIFICATIONS_ALL object: NSArray of NSString objects 467 | * key: GROWL_NOTIFICATIONS_DEFAULT object: NSArray of NSString objects 468 | * 469 | * The dictionary may have the following key object pairs: 470 | * key: GROWL_NOTIFICATIONS_HUMAN_READABLE_NAMES object: NSDictionary of key: notification name object: human-readable notification name 471 | * 472 | * You do not need to implement this method if you have an auto-discoverable 473 | * plist file in your app bundle. (XXX refer to more information on that) 474 | * 475 | * @result The NSDictionary to use for registration. 476 | */ 477 | - (NSDictionary *) registrationDictionaryForGrowl; 478 | 479 | /*! 480 | * @method applicationNameForGrowl 481 | * @abstract Return the name of this application which will be used for Growl bookkeeping. 482 | * @discussion This name is used both internally and in the Growl preferences. 483 | * 484 | * This should remain stable between different versions and incarnations of 485 | * your application. 486 | * For example, "SurfWriter" is a good app name, whereas "SurfWriter 2.0" and 487 | * "SurfWriter Lite" are not. 488 | * 489 | * You do not need to implement this method if you are providing the 490 | * application name elsewhere, meaning in an auto-discoverable plist file in 491 | * your app bundle (XXX refer to more information on that) or in the result 492 | * of -registrationDictionaryForGrowl. 493 | * 494 | * @result The name of the application using Growl. 495 | */ 496 | - (NSString *) applicationNameForGrowl; 497 | 498 | /*! 499 | * @method applicationIconForGrowl 500 | * @abstract Return the NSImage to treat as the application icon. 501 | * @discussion The delegate may optionally return an NSImage 502 | * object to use as the application icon. If this method is not implemented, 503 | * {{{-applicationIconDataForGrowl}}} is tried. If that method is not 504 | * implemented, the application's own icon is used. Neither method is 505 | * generally needed. 506 | * @result The NSImage to treat as the application icon. 507 | */ 508 | - (NSImage *) applicationIconForGrowl; 509 | 510 | /*! 511 | * @method applicationIconDataForGrowl 512 | * @abstract Return the NSData to treat as the application icon. 513 | * @discussion The delegate may optionally return an NSData 514 | * object to use as the application icon; if this is not implemented, the 515 | * application's own icon is used. This is not generally needed. 516 | * @result The NSData to treat as the application icon. 517 | * @deprecated In version 1.1, in favor of {{{-applicationIconForGrowl}}}. 518 | */ 519 | - (NSData *) applicationIconDataForGrowl; 520 | 521 | /*! 522 | * @method growlIsReady 523 | * @abstract Informs the delegate that Growl has launched. 524 | * @discussion Informs the delegate that Growl (specifically, the 525 | * GrowlHelperApp) was launched successfully. The application can take actions 526 | * with the knowledge that Growl is installed and functional. 527 | */ 528 | - (void) growlIsReady; 529 | 530 | /*! 531 | * @method growlNotificationWasClicked: 532 | * @abstract Informs the delegate that a Growl notification was clicked. 533 | * @discussion Informs the delegate that a Growl notification was clicked. It 534 | * is only sent for notifications sent with a non-nil 535 | * clickContext, so if you want to receive a message when a notification is 536 | * clicked, clickContext must not be nil when calling 537 | * +[GrowlApplicationBridge notifyWithTitle: description:notificationName:iconData:priority:isSticky:clickContext:]. 538 | * @param clickContext The clickContext passed when displaying the notification originally via +[GrowlApplicationBridge notifyWithTitle:description:notificationName:iconData:priority:isSticky:clickContext:]. 539 | */ 540 | - (void) growlNotificationWasClicked:(id)clickContext; 541 | 542 | /*! 543 | * @method growlNotificationTimedOut: 544 | * @abstract Informs the delegate that a Growl notification timed out. 545 | * @discussion Informs the delegate that a Growl notification timed out. It 546 | * is only sent for notifications sent with a non-nil 547 | * clickContext, so if you want to receive a message when a notification is 548 | * clicked, clickContext must not be nil when calling 549 | * +[GrowlApplicationBridge notifyWithTitle: description:notificationName:iconData:priority:isSticky:clickContext:]. 550 | * @param clickContext The clickContext passed when displaying the notification originally via +[GrowlApplicationBridge notifyWithTitle:description:notificationName:iconData:priority:isSticky:clickContext:]. 551 | */ 552 | - (void) growlNotificationTimedOut:(id)clickContext; 553 | 554 | 555 | /*! 556 | * @method hasNetworkClientEntitlement 557 | * @abstract Used only in sandboxed situations since we don't know whether the app has com.apple.security.network.client entitlement 558 | * @discussion GrowlDelegate calls to find out if we have the com.apple.security.network.client entitlement, 559 | * since we can't find this out without hitting the sandbox. We only call it if we detect that the application is sandboxed. 560 | */ 561 | - (BOOL) hasNetworkClientEntitlement; 562 | 563 | @end 564 | 565 | #pragma mark - 566 | 567 | #endif /* __GrowlApplicationBridge_h__ */ 568 | -------------------------------------------------------------------------------- /Frameworks/Growl.framework/Versions/A/Headers/GrowlDefines.h: -------------------------------------------------------------------------------- 1 | // 2 | // GrowlDefines.h 3 | // 4 | 5 | #ifndef _GROWLDEFINES_H 6 | #define _GROWLDEFINES_H 7 | 8 | #ifdef __OBJC__ 9 | #define XSTR(x) (@x) 10 | #else 11 | #define XSTR CFSTR 12 | #endif 13 | 14 | /*! @header GrowlDefines.h 15 | * @abstract Defines all the notification keys. 16 | * @discussion Defines all the keys used for registration with Growl and for 17 | * Growl notifications. 18 | * 19 | * Most applications should use the functions or methods of Growl.framework 20 | * instead of posting notifications such as those described here. 21 | * @updated 2004-01-25 22 | */ 23 | 24 | // UserInfo Keys for Registration 25 | #pragma mark UserInfo Keys for Registration 26 | 27 | /*! @group Registration userInfo keys */ 28 | /* @abstract Keys for the userInfo dictionary of a GROWL_APP_REGISTRATION distributed notification. 29 | * @discussion The values of these keys describe the application and the 30 | * notifications it may post. 31 | * 32 | * Your application must register with Growl before it can post Growl 33 | * notifications (and have them not be ignored). However, as of Growl 0.6, 34 | * posting GROWL_APP_REGISTRATION notifications directly is no longer the 35 | * preferred way to register your application. Your application should instead 36 | * use Growl.framework's delegate system. 37 | * See +[GrowlApplicationBridge setGrowlDelegate:] or Growl_SetDelegate for 38 | * more information. 39 | */ 40 | 41 | /*! @defined GROWL_APP_NAME 42 | * @abstract The name of your application. 43 | * @discussion The name of your application. This should remain stable between 44 | * different versions and incarnations of your application. 45 | * For example, "SurfWriter" is a good app name, whereas "SurfWriter 2.0" and 46 | * "SurfWriter Lite" are not. 47 | */ 48 | #define GROWL_APP_NAME XSTR("ApplicationName") 49 | /*! @defined GROWL_APP_ID 50 | * @abstract The bundle identifier of your application. 51 | * @discussion The bundle identifier of your application. This key should 52 | * be unique for your application while there may be several applications 53 | * with the same GROWL_APP_NAME. 54 | * This key is optional. 55 | */ 56 | #define GROWL_APP_ID XSTR("ApplicationId") 57 | /*! @defined GROWL_APP_ICON_DATA 58 | * @abstract The image data for your application's icon. 59 | * @discussion Image data representing your application's icon. This may be 60 | * superimposed on a notification icon as a badge, used as the notification 61 | * icon when a notification-specific icon is not supplied, or ignored 62 | * altogether, depending on the display. Must be in a format supported by 63 | * NSImage, such as TIFF, PNG, GIF, JPEG, BMP, PICT, or PDF. 64 | * 65 | * Optional. Not supported by all display plugins. 66 | */ 67 | #define GROWL_APP_ICON_DATA XSTR("ApplicationIcon") 68 | /*! @defined GROWL_NOTIFICATIONS_DEFAULT 69 | * @abstract The array of notifications to turn on by default. 70 | * @discussion These are the names of the notifications that should be enabled 71 | * by default when your application registers for the first time. If your 72 | * application reregisters, Growl will look here for any new notification 73 | * names found in GROWL_NOTIFICATIONS_ALL, but ignore any others. 74 | */ 75 | #define GROWL_NOTIFICATIONS_DEFAULT XSTR("DefaultNotifications") 76 | /*! @defined GROWL_NOTIFICATIONS_ALL 77 | * @abstract The array of all notifications your application can send. 78 | * @discussion These are the names of all of the notifications that your 79 | * application may post. See GROWL_NOTIFICATION_NAME for a discussion of good 80 | * notification names. 81 | */ 82 | #define GROWL_NOTIFICATIONS_ALL XSTR("AllNotifications") 83 | /*! @defined GROWL_NOTIFICATIONS_HUMAN_READABLE_DESCRIPTIONS 84 | * @abstract A dictionary of human-readable names for your notifications. 85 | * @discussion By default, the Growl UI will display notifications by the names given in GROWL_NOTIFICATIONS_ALL 86 | * which correspond to the GROWL_NOTIFICATION_NAME. This dictionary specifies the human-readable name to display. 87 | * The keys of the dictionary are GROWL_NOTIFICATION_NAME strings; the objects are the human-readable versions. 88 | * For any GROWL_NOTIFICATION_NAME not specific in this dictionary, the GROWL_NOTIFICATION_NAME will be displayed. 89 | * 90 | * This key is optional. 91 | */ 92 | #define GROWL_NOTIFICATIONS_HUMAN_READABLE_NAMES XSTR("HumanReadableNames") 93 | /*! @defined GROWL_NOTIFICATIONS_DESCRIPTIONS 94 | * @abstract A dictionary of descriptions of _when_ each notification occurs 95 | * @discussion This is an NSDictionary whose keys are GROWL_NOTIFICATION_NAME strings and whose objects are 96 | * descriptions of _when_ each notification occurs, such as "You received a new mail message" or 97 | * "A file finished downloading". 98 | * 99 | * This key is optional. 100 | */ 101 | #define GROWL_NOTIFICATIONS_DESCRIPTIONS XSTR("NotificationDescriptions") 102 | /*! @defined GROWL_NOTIFICATIONS_ICONS 103 | * @abstract A dictionary of icons for each notification 104 | * @discussion This is an NSDictionary whose keys are GROWL_NOTIFICATION_NAME strings and whose objects are 105 | * icons for each notification, for GNTP spec 106 | * 107 | * This key is optional. 108 | */ 109 | #define GROWL_NOTIFICATIONS_ICONS XSTR("NotificationIcons") 110 | 111 | /*! @defined GROWL_TICKET_VERSION 112 | * @abstract The version of your registration ticket. 113 | * @discussion Include this key in a ticket plist file that you put in your 114 | * application bundle for auto-discovery. The current ticket version is 1. 115 | */ 116 | #define GROWL_TICKET_VERSION XSTR("TicketVersion") 117 | // UserInfo Keys for Notifications 118 | #pragma mark UserInfo Keys for Notifications 119 | 120 | /*! @group Notification userInfo keys */ 121 | /* @abstract Keys for the userInfo dictionary of a GROWL_NOTIFICATION distributed notification. 122 | * @discussion The values of these keys describe the content of a Growl 123 | * notification. 124 | * 125 | * Not all of these keys are supported by all displays. Only the name, title, 126 | * and description of a notification are universal. Most of the built-in 127 | * displays do support all of these keys, and most other visual displays 128 | * probably will also. But, as of 0.6, the Log, MailMe, and Speech displays 129 | * support only textual data. 130 | */ 131 | 132 | /*! @defined GROWL_NOTIFICATION_NAME 133 | * @abstract The name of the notification. 134 | * @discussion The name of the notification. Note that if you do not define 135 | * GROWL_NOTIFICATIONS_HUMAN_READABLE_NAMES when registering your ticket originally this name 136 | * will the one displayed within the Growl preference pane and should be human-readable. 137 | */ 138 | #define GROWL_NOTIFICATION_NAME XSTR("NotificationName") 139 | /*! @defined GROWL_NOTIFICATION_TITLE 140 | * @abstract The title to display in the notification. 141 | * @discussion The title of the notification. Should be very brief. 142 | * The title usually says what happened, e.g. "Download complete". 143 | */ 144 | #define GROWL_NOTIFICATION_TITLE XSTR("NotificationTitle") 145 | /*! @defined GROWL_NOTIFICATION_DESCRIPTION 146 | * @abstract The description to display in the notification. 147 | * @discussion The description should be longer and more verbose than the title. 148 | * The description usually tells the subject of the action, 149 | * e.g. "Growl-0.6.dmg downloaded in 5.02 minutes". 150 | */ 151 | #define GROWL_NOTIFICATION_DESCRIPTION XSTR("NotificationDescription") 152 | /*! @defined GROWL_NOTIFICATION_ICON 153 | * @discussion Image data for the notification icon. Image data must be in a format 154 | * supported by NSImage, such as TIFF, PNG, GIF, JPEG, BMP, PICT, or PDF. 155 | * 156 | * Optional. Not supported by all display plugins. 157 | */ 158 | #define GROWL_NOTIFICATION_ICON_DATA XSTR("NotificationIcon") 159 | /*! @defined GROWL_NOTIFICATION_APP_ICON 160 | * @discussion Image data for the application icon, in case GROWL_APP_ICON does 161 | * not apply for some reason. Image data be in a format supported by NSImage, such 162 | * as TIFF, PNG, GIF, JPEG, BMP, PICT, or PDF. 163 | * 164 | * Optional. Not supported by all display plugins. 165 | */ 166 | #define GROWL_NOTIFICATION_APP_ICON_DATA XSTR("NotificationAppIcon") 167 | /*! @defined GROWL_NOTIFICATION_PRIORITY 168 | * @discussion The priority of the notification as an integer number from 169 | * -2 to +2 (+2 being highest). 170 | * 171 | * Optional. Not supported by all display plugins. 172 | */ 173 | #define GROWL_NOTIFICATION_PRIORITY XSTR("NotificationPriority") 174 | /*! @defined GROWL_NOTIFICATION_STICKY 175 | * @discussion A Boolean number controlling whether the notification is sticky. 176 | * 177 | * Optional. Not supported by all display plugins. 178 | */ 179 | #define GROWL_NOTIFICATION_STICKY XSTR("NotificationSticky") 180 | /*! @defined GROWL_NOTIFICATION_CLICK_CONTEXT 181 | * @abstract Identifies which notification was clicked. 182 | * @discussion An identifier for the notification for clicking purposes. 183 | * 184 | * This will be passed back to the application when the notification is 185 | * clicked. It must be plist-encodable (a data, dictionary, array, number, or 186 | * string object), and it should be unique for each notification you post. 187 | * A good click context would be a UUID string returned by NSProcessInfo or 188 | * CFUUID. 189 | * 190 | * Optional. Not supported by all display plugins. 191 | */ 192 | #define GROWL_NOTIFICATION_CLICK_CONTEXT XSTR("NotificationClickContext") 193 | 194 | /*! @defined GROWL_NOTIFICATION_IDENTIFIER 195 | * @abstract An identifier for the notification for coalescing purposes. 196 | * Notifications with the same identifier fall into the same class; only 197 | * the last notification of a class is displayed on the screen. If a 198 | * notification of the same class is currently being displayed, it is 199 | * replaced by this notification. 200 | * 201 | * Optional. Not supported by all display plugins. 202 | */ 203 | #define GROWL_NOTIFICATION_IDENTIFIER XSTR("GrowlNotificationIdentifier") 204 | 205 | /*! @defined GROWL_APP_PID 206 | * @abstract The process identifier of the process which sends this 207 | * notification. If this field is set, the application will only receive 208 | * clicked and timed out notifications which originate from this process. 209 | * 210 | * Optional. 211 | */ 212 | #define GROWL_APP_PID XSTR("ApplicationPID") 213 | 214 | /*! @defined GROWL_NOTIFICATION_PROGRESS 215 | * @abstract If this key is set, it should contain a double value wrapped 216 | * in a NSNumber which describes some sort of progress (from 0.0 to 100.0). 217 | * If this is key is not set, no progress bar is shown. 218 | * 219 | * Optional. Not supported by all display plugins. 220 | */ 221 | #define GROWL_NOTIFICATION_PROGRESS XSTR("NotificationProgress") 222 | 223 | /*! @defined GROWL_NOTIFICATION_ALREADY_SHOWN 224 | * @abstract If this key is set, it should contain a bool value wrapped 225 | * in a NSNumber which describes whether the notification has 226 | * already been displayed, for instance by built in Notification 227 | * Center support. This value can be used to allow display 228 | * plugins to skip a notification, while still allowing Growl 229 | * actions to run on them. 230 | * 231 | * Optional. Not supported by all display plugins. 232 | */ 233 | #define GROWL_NOTIFICATION_ALREADY_SHOWN XSTR("AlreadyShown") 234 | 235 | 236 | // Notifications 237 | #pragma mark Notifications 238 | 239 | /*! @group Notification names */ 240 | /* @abstract Names of distributed notifications used by Growl. 241 | * @discussion These are notifications used by applications (directly or 242 | * indirectly) to interact with Growl, and by Growl for interaction between 243 | * its components. 244 | * 245 | * Most of these should no longer be used in Growl 0.6 and later, in favor of 246 | * Growl.framework's GrowlApplicationBridge APIs. 247 | */ 248 | 249 | /*! @defined GROWL_APP_REGISTRATION 250 | * @abstract The distributed notification for registering your application. 251 | * @discussion This is the name of the distributed notification that can be 252 | * used to register applications with Growl. 253 | * 254 | * The userInfo dictionary for this notification can contain these keys: 255 | *
    256 | *
  • GROWL_APP_NAME
  • 257 | *
  • GROWL_APP_ICON_DATA
  • 258 | *
  • GROWL_NOTIFICATIONS_ALL
  • 259 | *
  • GROWL_NOTIFICATIONS_DEFAULT
  • 260 | *
261 | * 262 | * No longer recommended as of Growl 0.6. An alternate method of registering 263 | * is to use Growl.framework's delegate system. 264 | * See +[GrowlApplicationBridge setGrowlDelegate:] or Growl_SetDelegate for 265 | * more information. 266 | */ 267 | #define GROWL_APP_REGISTRATION XSTR("GrowlApplicationRegistrationNotification") 268 | /*! @defined GROWL_APP_REGISTRATION_CONF 269 | * @abstract The distributed notification for confirming registration. 270 | * @discussion The name of the distributed notification sent to confirm the 271 | * registration. Used by the Growl preference pane. Your application probably 272 | * does not need to use this notification. 273 | */ 274 | #define GROWL_APP_REGISTRATION_CONF XSTR("GrowlApplicationRegistrationConfirmationNotification") 275 | /*! @defined GROWL_NOTIFICATION 276 | * @abstract The distributed notification for Growl notifications. 277 | * @discussion This is what it all comes down to. This is the name of the 278 | * distributed notification that your application posts to actually send a 279 | * Growl notification. 280 | * 281 | * The userInfo dictionary for this notification can contain these keys: 282 | *
    283 | *
  • GROWL_NOTIFICATION_NAME (required)
  • 284 | *
  • GROWL_NOTIFICATION_TITLE (required)
  • 285 | *
  • GROWL_NOTIFICATION_DESCRIPTION (required)
  • 286 | *
  • GROWL_NOTIFICATION_ICON
  • 287 | *
  • GROWL_NOTIFICATION_APP_ICON
  • 288 | *
  • GROWL_NOTIFICATION_PRIORITY
  • 289 | *
  • GROWL_NOTIFICATION_STICKY
  • 290 | *
  • GROWL_NOTIFICATION_CLICK_CONTEXT
  • 291 | *
  • GROWL_APP_NAME (required)
  • 292 | *
293 | * 294 | * No longer recommended as of Growl 0.6. Three alternate methods of posting 295 | * notifications are +[GrowlApplicationBridge notifyWithTitle:description:notificationName:iconData:priority:isSticky:clickContext:], 296 | * Growl_NotifyWithTitleDescriptionNameIconPriorityStickyClickContext, and 297 | * Growl_PostNotification. 298 | */ 299 | #define GROWL_NOTIFICATION XSTR("GrowlNotification") 300 | /*! @defined GROWL_PING 301 | * @abstract A distributed notification to check whether Growl is running. 302 | * @discussion This is used by the Growl preference pane. If it receives a 303 | * GROWL_PONG, the preference pane takes this to mean that Growl is running. 304 | */ 305 | #define GROWL_PING XSTR("Honey, Mind Taking Out The Trash") 306 | /*! @defined GROWL_PONG 307 | * @abstract The distributed notification sent in reply to GROWL_PING. 308 | * @discussion GrowlHelperApp posts this in reply to GROWL_PING. 309 | */ 310 | #define GROWL_PONG XSTR("What Do You Want From Me, Woman") 311 | /*! @defined GROWL_IS_READY 312 | * @abstract The distributed notification sent when Growl starts up. 313 | * @discussion GrowlHelperApp posts this when it has begin listening on all of 314 | * its sources for new notifications. GrowlApplicationBridge (in 315 | * Growl.framework), upon receiving this notification, reregisters using the 316 | * registration dictionary supplied by its delegate. 317 | */ 318 | #define GROWL_IS_READY XSTR("Lend Me Some Sugar; I Am Your Neighbor!") 319 | 320 | 321 | /*! @defined GROWL_DISTRIBUTED_NOTIFICATION_CLICKED_SUFFIX 322 | * @abstract Part of the name of the distributed notification sent when a supported notification is clicked. 323 | * @discussion When a Growl notification with a click context is clicked on by 324 | * the user, Growl posts a distributed notification whose name is in the format: 325 | * [NSString stringWithFormat:@"%@-%d-%@", appName, pid, GROWL_DISTRIBUTED_NOTIFICATION_CLICKED_SUFFIX] 326 | * The GrowlApplicationBridge responds to this notification by calling a callback in its delegate. 327 | */ 328 | #define GROWL_DISTRIBUTED_NOTIFICATION_CLICKED_SUFFIX XSTR("GrowlClicked!") 329 | 330 | /*! @defined GROWL_DISTRIBUTED_NOTIFICATION_TIMED_OUT_SUFFIX 331 | * @abstract Part of the name of the distributed notification sent when a supported notification times out without being clicked. 332 | * @discussion When a Growl notification with a click context times out, Growl posts a distributed notification 333 | * whose name is in the format: 334 | * [NSString stringWithFormat:@"%@-%d-%@", appName, pid, GROWL_DISTRIBUTED_NOTIFICATION_TIMED_OUT_SUFFIX] 335 | * The GrowlApplicationBridge responds to this notification by calling a callback in its delegate. 336 | * NOTE: The user may have actually clicked the 'close' button; this triggers an *immediate* time-out of the notification. 337 | */ 338 | #define GROWL_DISTRIBUTED_NOTIFICATION_TIMED_OUT_SUFFIX XSTR("GrowlTimedOut!") 339 | 340 | /*! @defined GROWL_DISTRIBUTED_NOTIFICATION_NOTIFICATIONCENTER_ON 341 | * @abstract The distributed notification sent when the Notification Center support is toggled on in Growl 2.0 342 | * @discussion When the user enables Notification Center support in Growl 2.0, this notification is sent 343 | * to inform all running apps that they should now speak to Notification Center directly. 344 | */ 345 | #define GROWL_DISTRIBUTED_NOTIFICATION_NOTIFICATIONCENTER_ON XSTR("GrowlNotificationCenterOn!") 346 | 347 | /*! @defined GROWL_DISTRIBUTED_NOTIFICATION_NOTIFICATIONCENTER_OFF 348 | * @abstract The distributed notification sent when the Notification Center support is toggled off in Growl 2.0 349 | * @discussion When the user enables Notification Center support in Growl 2.0, this notification is sent 350 | * to inform all running apps that they should no longer speak to Notification Center directly. 351 | */ 352 | #define GROWL_DISTRIBUTED_NOTIFICATION_NOTIFICATIONCENTER_OFF XSTR("GrowlNotificationCenterOff!") 353 | 354 | /*! @defined GROWL_DISTRIBUTED_NOTIFICATION_NOTIFICATIONCENTER_QUERY 355 | * @abstract The distributed notification sent by an application to query Growl 2.0's notification center support. 356 | * @discussion When an app starts up, it will send this query to get Growl 2.0 to spit out whether notification 357 | * center support is on or off. 358 | */ 359 | #define GROWL_DISTRIBUTED_NOTIFICATION_NOTIFICATIONCENTER_QUERY XSTR("GrowlNotificationCenterYN?") 360 | 361 | 362 | /*! @group Other symbols */ 363 | /* Symbols which don't fit into any of the other categories. */ 364 | 365 | /*! @defined GROWL_KEY_CLICKED_CONTEXT 366 | * @abstract Used internally as the key for the clickedContext passed over DNC. 367 | * @discussion This key is used in GROWL_NOTIFICATION_CLICKED, and contains the 368 | * click context that was supplied in the original notification. 369 | */ 370 | #define GROWL_KEY_CLICKED_CONTEXT XSTR("ClickedContext") 371 | /*! @defined GROWL_REG_DICT_EXTENSION 372 | * @abstract The filename extension for registration dictionaries. 373 | * @discussion The GrowlApplicationBridge in Growl.framework registers with 374 | * Growl by creating a file with the extension of .(GROWL_REG_DICT_EXTENSION) 375 | * and opening it in the GrowlHelperApp. This happens whether or not Growl is 376 | * running; if it was stopped, it quits immediately without listening for 377 | * notifications. 378 | */ 379 | #define GROWL_REG_DICT_EXTENSION XSTR("growlRegDict") 380 | 381 | 382 | #define GROWL_POSITION_PREFERENCE_KEY @"GrowlSelectedPosition" 383 | 384 | #define GROWL_PLUGIN_CONFIG_ID XSTR("GrowlPluginConfigurationID") 385 | 386 | #endif //ndef _GROWLDEFINES_H 387 | -------------------------------------------------------------------------------- /Frameworks/Growl.framework/Versions/A/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 12C60 7 | CFBundleDevelopmentRegion 8 | English 9 | CFBundleExecutable 10 | Growl 11 | CFBundleIdentifier 12 | com.growl.growlframework 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 2.0.1 19 | CFBundleSignature 20 | GRRR 21 | CFBundleVersion 22 | 2.0.1 23 | DTCompiler 24 | com.apple.compilers.llvm.clang.1_0 25 | DTPlatformBuild 26 | 4G2008a 27 | DTPlatformVersion 28 | GM 29 | DTSDKBuild 30 | 12C37 31 | DTSDKName 32 | macosx10.8 33 | DTXcode 34 | 0452 35 | DTXcodeBuild 36 | 4G2008a 37 | NSPrincipalClass 38 | GrowlApplicationBridge 39 | 40 | 41 | -------------------------------------------------------------------------------- /Frameworks/Growl.framework/Versions/A/_CodeSignature/CodeResources: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | files 6 | 7 | Resources/Info.plist 8 | 9 | VZb3f8My4te/5JwcjfvotgCXTAs= 10 | 11 | 12 | rules 13 | 14 | ^Resources/ 15 | 16 | ^Resources/.*\.lproj/ 17 | 18 | optional 19 | 20 | weight 21 | 1000 22 | 23 | ^Resources/.*\.lproj/locversion.plist$ 24 | 25 | omit 26 | 27 | weight 28 | 1100 29 | 30 | ^version.plist$ 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Frameworks/Growl.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /Growl Registration Ticket.growlRegDict: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AllNotifications 6 | 7 | start 8 | pause 9 | stop 10 | 11 | TicketVersion 12 | 1 13 | 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 João Moreno 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 101000 5 | 14C1510 6 | 6751 7 | 1344.72 8 | 757.30 9 | 10 | com.apple.InterfaceBuilder.CocoaPlugin 11 | 6751 12 | 13 | 14 | NSCustomObject 15 | NSMenu 16 | NSMenuItem 17 | NSView 18 | NSWindowTemplate 19 | 20 | 21 | com.apple.InterfaceBuilder.CocoaPlugin 22 | 23 | 24 | PluginDependencyRecalculationVersion 25 | 26 | 27 | 28 | 29 | NSApplication 30 | 31 | 32 | FirstResponder 33 | 34 | 35 | NSApplication 36 | 37 | 38 | Thyme 39 | 40 | 41 | 42 | Start 43 | 44 | 2147483647 45 | 46 | NSImage 47 | NSMenuCheckmark 48 | 49 | 50 | NSImage 51 | NSMenuMixedState 52 | 53 | 54 | 55 | 56 | YES 57 | Restart 58 | 59 | 2147483647 60 | 61 | 62 | 63 | 64 | 65 | YES 66 | Finish 67 | 68 | 2147483647 69 | 70 | 71 | 72 | 73 | 74 | YES 75 | YES 76 | 77 | 78 | 1048576 79 | 2147483647 80 | 81 | 82 | 83 | 84 | 85 | Preferences... 86 | 87 | 2147483647 88 | 89 | 90 | 91 | 92 | 93 | YES 94 | YES 95 | 96 | 97 | 2147483647 98 | 99 | 100 | 101 | 102 | 103 | About Thyme 104 | 105 | 2147483647 106 | 107 | 108 | 109 | 110 | 111 | Quit 112 | 113 | 2147483647 114 | 115 | 116 | 117 | 118 | _NSAppleMenu 119 | YES 120 | 121 | 122 | 15 123 | 2 124 | {{335, 558}, {267, 192}} 125 | 1954021376 126 | Thyme 127 | NSWindow 128 | 129 | 130 | 131 | 132 | 256 133 | {267, 192} 134 | 135 | {{0, 0}, {1440, 877}} 136 | {10000000000000, 10000000000000} 137 | YES 138 | 139 | 140 | ThymeAppDelegate 141 | 142 | 143 | NSFontManager 144 | 145 | 146 | 147 | 148 | 149 | 150 | terminate: 151 | 152 | 153 | 154 | 449 155 | 156 | 157 | 158 | delegate 159 | 160 | 161 | 162 | 495 163 | 164 | 165 | 166 | window 167 | 168 | 169 | 170 | 532 171 | 172 | 173 | 174 | menu 175 | 176 | 177 | 178 | 534 179 | 180 | 181 | 182 | onStartPauseClick: 183 | 184 | 185 | 186 | 572 187 | 188 | 189 | 190 | onFinishClick: 191 | 192 | 193 | 194 | 573 195 | 196 | 197 | 198 | startPauseItem 199 | 200 | 201 | 202 | 574 203 | 204 | 205 | 206 | finishItem 207 | 208 | 209 | 210 | 575 211 | 212 | 213 | 214 | onPreferencesClick: 215 | 216 | 217 | 218 | 584 219 | 220 | 221 | 222 | onAboutClick: 223 | 224 | 225 | 226 | 586 227 | 228 | 229 | 230 | onRestartClick: 231 | 232 | 233 | 234 | 588 235 | 236 | 237 | 238 | restartItem 239 | 240 | 241 | 242 | 589 243 | 244 | 245 | 246 | 247 | 248 | 0 249 | 250 | 251 | 252 | 253 | 254 | -2 255 | 256 | 257 | File's Owner 258 | 259 | 260 | -1 261 | 262 | 263 | First Responder 264 | 265 | 266 | -3 267 | 268 | 269 | Application 270 | 271 | 272 | 371 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 372 281 | 282 | 283 | 284 | 285 | 420 286 | 287 | 288 | 289 | 290 | 494 291 | 292 | 293 | 294 | 295 | 57 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 143 311 | 312 | 313 | 314 | 315 | 129 316 | 317 | 318 | 319 | 320 | 136 321 | 322 | 323 | 324 | 325 | 580 326 | 327 | 328 | 329 | 330 | 583 331 | 332 | 333 | 334 | 335 | 585 336 | 337 | 338 | 339 | 340 | 533 341 | 342 | 343 | 344 | 345 | 587 346 | 347 | 348 | 349 | 350 | 351 | 352 | com.apple.InterfaceBuilder.CocoaPlugin 353 | com.apple.InterfaceBuilder.CocoaPlugin 354 | com.apple.InterfaceBuilder.CocoaPlugin 355 | com.apple.InterfaceBuilder.CocoaPlugin 356 | com.apple.InterfaceBuilder.CocoaPlugin 357 | com.apple.InterfaceBuilder.CocoaPlugin 358 | com.apple.InterfaceBuilder.CocoaPlugin 359 | {{245, 585}, {267, 192}} 360 | 361 | com.apple.InterfaceBuilder.CocoaPlugin 362 | com.apple.InterfaceBuilder.CocoaPlugin 363 | com.apple.InterfaceBuilder.CocoaPlugin 364 | com.apple.InterfaceBuilder.CocoaPlugin 365 | com.apple.InterfaceBuilder.CocoaPlugin 366 | com.apple.InterfaceBuilder.CocoaPlugin 367 | com.apple.InterfaceBuilder.CocoaPlugin 368 | com.apple.InterfaceBuilder.CocoaPlugin 369 | com.apple.InterfaceBuilder.CocoaPlugin 370 | 371 | 372 | 373 | 374 | 375 | 591 376 | 377 | 378 | 379 | 380 | ThymeAppDelegate 381 | NSObject 382 | 383 | id 384 | id 385 | id 386 | id 387 | id 388 | 389 | 390 | 391 | onAboutClick: 392 | id 393 | 394 | 395 | onExportClick: 396 | id 397 | 398 | 399 | onFinishClick: 400 | id 401 | 402 | 403 | onPreferencesClick: 404 | id 405 | 406 | 407 | onStartPauseClick: 408 | id 409 | 410 | 411 | 412 | NSMenuItem 413 | NSMenu 414 | NSMenuItem 415 | NSMenuItem 416 | NSWindow 417 | 418 | 419 | 420 | finishItem 421 | NSMenuItem 422 | 423 | 424 | menu 425 | NSMenu 426 | 427 | 428 | restartItem 429 | NSMenuItem 430 | 431 | 432 | startPauseItem 433 | NSMenuItem 434 | 435 | 436 | window 437 | NSWindow 438 | 439 | 440 | 441 | IBProjectSource 442 | ../ThymeAppDelegate.h 443 | 444 | 445 | 446 | ThymeAppDelegate 447 | 448 | id 449 | id 450 | id 451 | id 452 | id 453 | id 454 | id 455 | id 456 | 457 | 458 | 459 | clear: 460 | id 461 | 462 | 463 | onAboutClick: 464 | id 465 | 466 | 467 | onExportClick: 468 | id 469 | 470 | 471 | onFinishClick: 472 | id 473 | 474 | 475 | onPreferencesClick: 476 | id 477 | 478 | 479 | onRestartClick: 480 | id 481 | 482 | 483 | onStartPauseClick: 484 | id 485 | 486 | 487 | saveAction: 488 | id 489 | 490 | 491 | 492 | IBProjectSource 493 | ../ThymeAppDelegate.m 494 | 495 | 496 | 497 | 498 | 499 | NSApplication 500 | NSResponder 501 | 502 | IBFrameworkSource 503 | AppKit.framework/Headers/NSApplication.h 504 | 505 | 506 | 507 | NSFontManager 508 | NSObject 509 | 510 | IBFrameworkSource 511 | AppKit.framework/Headers/NSFontManager.h 512 | 513 | 514 | 515 | NSMenu 516 | NSObject 517 | 518 | IBFrameworkSource 519 | AppKit.framework/Headers/NSMenu.h 520 | 521 | 522 | 523 | NSMenuItem 524 | NSObject 525 | 526 | IBFrameworkSource 527 | AppKit.framework/Headers/NSMenuItem.h 528 | 529 | 530 | 531 | NSResponder 532 | NSObject 533 | 534 | IBFrameworkSource 535 | AppKit.framework/Headers/NSResponder.h 536 | 537 | 538 | 539 | NSView 540 | NSResponder 541 | 542 | IBFrameworkSource 543 | AppKit.framework/Headers/NSView.h 544 | 545 | 546 | 547 | NSWindow 548 | NSResponder 549 | 550 | IBFrameworkSource 551 | AppKit.framework/Headers/NSWindow.h 552 | 553 | 554 | 555 | 556 | 0 557 | IBCocoaFramework 558 | NO 559 | 560 | com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 561 | 562 | 563 | YES 564 | 3 565 | 566 | {12, 12} 567 | {10, 2} 568 | 569 | 570 | 571 | -------------------------------------------------------------------------------- /PauseCommand.h: -------------------------------------------------------------------------------- 1 | // 2 | // PauseCommand.h 3 | // Thyme 4 | // 5 | // Created by João Moreno on 28/10/12. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface PauseCommand : NSScriptCommand 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /PauseCommand.m: -------------------------------------------------------------------------------- 1 | // 2 | // PauseCommand.m 3 | // Thyme 4 | // 5 | // Created by João Moreno on 28/10/12. 6 | // 7 | // 8 | 9 | #import "PauseCommand.h" 10 | #import "ThymeAppDelegate.h" 11 | 12 | @implementation PauseCommand 13 | 14 | - (id) performDefaultImplementation { 15 | ThymeAppDelegate *appDelegate = (ThymeAppDelegate *) [[NSApplication sharedApplication] delegate]; 16 | [appDelegate pauseWithNotification:false]; 17 | return nil; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /PreferencesWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesWindowController.h 3 | // Thyme 4 | // 5 | // Created by João on 3/18/13. 6 | // 7 | // 8 | 9 | #import 10 | #import "ShortcutRecorder/ShortcutRecorder.h" 11 | 12 | @interface PreferencesWindowController : NSWindowController { 13 | SRRecorderControl *startPauseShortcutRecorder; 14 | SRRecorderControl *restartShortcutRecorder; 15 | SRRecorderControl *finishShortcutRecorder; 16 | NSButton *pauseOnSleepButton; 17 | NSButton *pauseOnScreensaverButton; 18 | NSButton *askForTagOnFinishButton; 19 | } 20 | 21 | @property (nonatomic, retain) IBOutlet SRRecorderControl *startPauseShortcutRecorder; 22 | @property (nonatomic, retain) IBOutlet SRRecorderControl *restartShortcutRecorder; 23 | @property (nonatomic, retain) IBOutlet SRRecorderControl *finishShortcutRecorder; 24 | @property (nonatomic, retain) IBOutlet NSButton *pauseOnSleepButton; 25 | @property (nonatomic, retain) IBOutlet NSButton *pauseOnScreensaverButton; 26 | @property (nonatomic, retain) IBOutlet NSButton *askForTagOnFinishButton; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /PreferencesWindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesWindowController.m 3 | // Thyme 4 | // 5 | // Created by João on 3/18/13. 6 | // 7 | // 8 | 9 | #import "PreferencesWindowController.h" 10 | 11 | @interface PreferencesWindowController () 12 | - (void)onWindowResignKey; 13 | @end 14 | 15 | @implementation PreferencesWindowController 16 | 17 | @synthesize startPauseShortcutRecorder; 18 | @synthesize restartShortcutRecorder; 19 | @synthesize finishShortcutRecorder; 20 | @synthesize pauseOnSleepButton; 21 | @synthesize pauseOnScreensaverButton; 22 | @synthesize askForTagOnFinishButton; 23 | 24 | - (id)initWithWindow:(NSWindow *)window 25 | { 26 | self = [super initWithWindow:window]; 27 | if (self) { 28 | // Initialization code here. 29 | } 30 | 31 | return self; 32 | 33 | } 34 | - (void)windowDidLoad 35 | { 36 | [super windowDidLoad]; 37 | 38 | NSUserDefaultsController *defaults = [NSUserDefaultsController sharedUserDefaultsController]; 39 | 40 | [self.startPauseShortcutRecorder bind:NSValueBinding toObject:defaults withKeyPath:@"values.startPause" options:nil]; 41 | [self.restartShortcutRecorder bind:NSValueBinding toObject:defaults withKeyPath:@"values.restart" options:nil]; 42 | [self.finishShortcutRecorder bind:NSValueBinding toObject:defaults withKeyPath:@"values.finish" options:nil]; 43 | 44 | [self.pauseOnSleepButton bind:NSValueBinding toObject:defaults withKeyPath:@"values.pauseOnSleep" options:nil]; 45 | [self.pauseOnScreensaverButton bind:NSValueBinding toObject:defaults withKeyPath:@"values.pauseOnScreensaver" options:nil]; 46 | [self.askForTagOnFinishButton bind:NSValueBinding toObject:defaults withKeyPath:@"values.askForTagOnFinishButton" options:nil]; 47 | 48 | [self.startPauseShortcutRecorder clearButtonRect]; 49 | 50 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onWindowResignKey) name:NSWindowDidResignKeyNotification object:nil]; 51 | } 52 | 53 | - (void)onWindowResignKey { 54 | [self.window close]; 55 | } 56 | 57 | #pragma mark SRRecorderControlDelegate 58 | 59 | - (BOOL)shortcutRecorder:(SRRecorderControl *)aRecorder canRecordShortcut:(NSDictionary *)aShortcut { 60 | return !SRShortcutEqualToShortcut([self.startPauseShortcutRecorder objectValue], aShortcut) && 61 | !SRShortcutEqualToShortcut([self.restartShortcutRecorder objectValue], aShortcut) && 62 | !SRShortcutEqualToShortcut([self.finishShortcutRecorder objectValue], aShortcut); 63 | } 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /PreferencesWindowController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 77 | 85 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Thyme 2 | 3 | Thyme is a timer for OS X. It's as simple as that. 4 | 5 | https://joaomoreno.github.io/thyme/ 6 | 7 | # Thyme Web 8 | 9 | Not using Mac OS? 10 | https://thymeweb.netlify.com/ 11 | -------------------------------------------------------------------------------- /Session.h: -------------------------------------------------------------------------------- 1 | // 2 | // Session.h 3 | // Thyme 4 | // 5 | // Created by João Moreno on 6/4/10. 6 | // 7 | 8 | #import 9 | 10 | 11 | @interface Session : NSManagedObject 12 | { 13 | } 14 | 15 | @property (nonatomic, retain) NSString * tag; 16 | @property (nonatomic, retain) NSNumber * hours; 17 | @property (nonatomic, retain) NSNumber * minutes; 18 | @property (nonatomic, retain) NSNumber * seconds; 19 | @property (nonatomic, retain) NSDate * date; 20 | 21 | + (NSArray*)allSessions; 22 | + (NSArray*)allSessionsAsDictionaries; 23 | + (Session*)sessionWithSeconds:(NSInteger)_seconds minutes:(NSInteger)_minutes hours:(NSInteger)_hours tag:(NSString*)_tag; 24 | - (NSString*)timeStringRepresentation; 25 | - (NSString*)stringRepresentation; 26 | - (NSDictionary *)asDictionary; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Session.m: -------------------------------------------------------------------------------- 1 | // 2 | // Session.m 3 | // Thyme 4 | // 5 | // Created by João Moreno on 6/4/10. 6 | // 7 | 8 | #import "Session.h" 9 | #import "ThymeAppDelegate.h" 10 | 11 | #define AppDelegate ((ThymeAppDelegate*) [[NSApplication sharedApplication] delegate]) 12 | 13 | @interface Session(hidden) 14 | - (NSString*)formattedDate; 15 | @end 16 | 17 | 18 | @implementation Session 19 | 20 | @dynamic tag; 21 | @dynamic hours; 22 | @dynamic minutes; 23 | @dynamic seconds; 24 | @dynamic date; 25 | 26 | - (NSString*)formatDate 27 | { 28 | static NSDateFormatter *dateFormatter = nil; 29 | 30 | if (dateFormatter == nil) 31 | { 32 | dateFormatter = [[NSDateFormatter alloc] init]; 33 | [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; 34 | [dateFormatter setDateStyle:NSDateFormatterMediumStyle]; 35 | } 36 | 37 | return [dateFormatter stringFromDate:self.date]; 38 | } 39 | 40 | + (NSArray*)allSessions 41 | { 42 | NSFetchRequest *request = [[NSFetchRequest alloc] init]; 43 | [request setEntity:[[AppDelegate.managedObjectModel entitiesByName] valueForKey:@"Session"]]; 44 | 45 | NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"date" 46 | ascending:YES 47 | comparator:^NSComparisonResult(NSDate* a, NSDate* b) { 48 | return [b compare:a]; 49 | }]; 50 | 51 | [request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]]; 52 | 53 | NSArray *result = [AppDelegate.managedObjectContext executeFetchRequest:request error:nil]; 54 | [request release]; 55 | 56 | return result; 57 | } 58 | 59 | + (NSArray*)allSessionsAsDictionaries 60 | { 61 | NSArray *sessions = [Session allSessions]; 62 | NSMutableArray *result = [NSMutableArray arrayWithCapacity:sessions.count]; 63 | 64 | [sessions enumerateObjectsUsingBlock:^(Session *session, NSUInteger index, BOOL *stop) { 65 | [result addObject:[session asDictionary]]; 66 | }]; 67 | 68 | return result; 69 | } 70 | 71 | + (Session*)sessionWithSeconds:(NSInteger)_seconds minutes:(NSInteger)_minutes hours:(NSInteger)_hours tag:(NSString*)_tag 72 | { 73 | Session* session = (Session*) [NSEntityDescription insertNewObjectForEntityForName:@"Session" 74 | inManagedObjectContext:AppDelegate.managedObjectContext]; 75 | 76 | session.tag = _tag; 77 | session.hours = [NSNumber numberWithInt:_hours]; 78 | session.minutes = [NSNumber numberWithInt:_minutes]; 79 | session.seconds = [NSNumber numberWithInt:_seconds]; 80 | session.date = [NSDate date]; 81 | 82 | return session; 83 | } 84 | 85 | - (NSString*)timeStringRepresentation 86 | { 87 | if ([self.hours intValue] > 0) 88 | return [NSString stringWithFormat:@"%02d:%02d:%02d", [self.hours intValue], [self.minutes intValue], [self.seconds intValue]]; 89 | else 90 | return [NSString stringWithFormat:@"%02d:%02d", [self.minutes intValue], [self.seconds intValue]]; 91 | } 92 | 93 | - (NSString*)stringRepresentation 94 | { 95 | NSString* formatString = @"%@ - %@ - %@"; 96 | if([self.tag isEqual: @""]){ 97 | formatString = @"%@ - %@"; 98 | } 99 | return [NSString stringWithFormat:formatString, [self timeStringRepresentation], [self formatDate], [self tag]]; 100 | } 101 | 102 | - (NSDictionary*)asDictionary 103 | { 104 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 105 | NSLocale *enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; 106 | [dateFormatter setLocale:enUSPOSIXLocale]; 107 | [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZZ"]; 108 | 109 | NSNumber *duration = [NSNumber numberWithUnsignedInteger:[self.seconds unsignedIntegerValue] 110 | + (([self.minutes unsignedIntegerValue] + ([self.hours unsignedIntegerValue] * 60)) * 60)]; 111 | 112 | NSDictionary *result = [NSDictionary dictionaryWithObjectsAndKeys: 113 | [dateFormatter stringFromDate:self.date], @"date", 114 | duration, @"duration", 115 | self.tag, @"tag", 116 | nil]; 117 | 118 | [dateFormatter release]; 119 | 120 | return result; 121 | } 122 | 123 | @end 124 | -------------------------------------------------------------------------------- /StartCommand.h: -------------------------------------------------------------------------------- 1 | // 2 | // StartCommand.h 3 | // Thyme 4 | // 5 | // Created by João Moreno on 28/10/12. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface StartCommand : NSScriptCommand 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /StartCommand.m: -------------------------------------------------------------------------------- 1 | // 2 | // StartCommand.m 3 | // Thyme 4 | // 5 | // Created by João Moreno on 28/10/12. 6 | // 7 | // 8 | 9 | #import "StartCommand.h" 10 | #import "ThymeAppDelegate.h" 11 | 12 | @implementation StartCommand 13 | 14 | - (id) performDefaultImplementation { 15 | ThymeAppDelegate *appDelegate = (ThymeAppDelegate *) [[NSApplication sharedApplication] delegate]; 16 | [appDelegate startWithNotification:false]; 17 | return nil; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /StopCommand.h: -------------------------------------------------------------------------------- 1 | // 2 | // StopCommand.h 3 | // Thyme 4 | // 5 | // Created by João Moreno on 28/10/12. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface StopCommand : NSScriptCommand 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /StopCommand.m: -------------------------------------------------------------------------------- 1 | // 2 | // StopCommand.m 3 | // Thyme 4 | // 5 | // Created by João Moreno on 28/10/12. 6 | // 7 | // 8 | 9 | #import "StopCommand.h" 10 | #import "ThymeAppDelegate.h" 11 | 12 | @implementation StopCommand 13 | 14 | - (id) performDefaultImplementation { 15 | ThymeAppDelegate *appDelegate = (ThymeAppDelegate *) [[NSApplication sharedApplication] delegate]; 16 | [appDelegate stopWithNotification:false]; 17 | return nil; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /Stopwatch.h: -------------------------------------------------------------------------------- 1 | // 2 | // Timer.h 3 | // Thyme 4 | // 5 | // Created by João on 3/16/13. 6 | // 7 | // 8 | 9 | #import 10 | 11 | // Delegate 12 | @protocol StopwatchDelegate 13 | - (void) didStart:(id)stopwatch; 14 | - (void) didPause:(id)stopwatch; 15 | - (void) didStop:(id)stopwatch withValue:(NSTimeInterval)value; 16 | - (void) didChange:(id)stopwatch; 17 | @end 18 | 19 | // Timer 20 | @interface Stopwatch : NSObject { 21 | id delegate; 22 | 23 | @private 24 | NSTimer* timer; 25 | NSDate* reference; 26 | NSTimeInterval accum; 27 | } 28 | 29 | @property (nonatomic, assign) id delegate; 30 | 31 | - (id) initWithDelegate:(id)delegate; 32 | - (NSString*) description; 33 | - (NSTimeInterval) value; 34 | 35 | - (BOOL) isActive; 36 | - (BOOL) isPaused; 37 | - (BOOL) isStopped; 38 | 39 | - (void) start; 40 | - (void) pause; 41 | - (void) reset:(NSTimeInterval) value; 42 | - (void) stop; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /Stopwatch.m: -------------------------------------------------------------------------------- 1 | // 2 | // Timer.m 3 | // Thyme 4 | // 5 | // Created by João on 3/16/13. 6 | // 7 | // 8 | 9 | #import "Stopwatch.h" 10 | 11 | @interface Stopwatch () 12 | @property (nonatomic, retain) NSTimer* timer; 13 | @property (nonatomic, retain) NSDate* reference; 14 | @property (nonatomic) NSTimeInterval accum; 15 | - (void) tick; 16 | @end 17 | 18 | @implementation Stopwatch 19 | 20 | @synthesize delegate; 21 | @synthesize timer; 22 | @synthesize reference; 23 | @synthesize accum; 24 | 25 | - (id)init { 26 | if (self = [super init]) { 27 | self.timer = nil; 28 | self.reference = [NSDate date]; 29 | self.accum = 0; 30 | } 31 | 32 | return self; 33 | } 34 | 35 | - (id)initWithDelegate:(id)aDelegate { 36 | if (self = [self init]) { 37 | self.delegate = aDelegate; 38 | } 39 | 40 | return self; 41 | } 42 | 43 | - (NSString*) description { 44 | long seconds = (long) floor([self value]); 45 | long hours = seconds / 3600; 46 | long minutes = (seconds / 60) % 60; 47 | seconds = seconds % 60; 48 | 49 | if (hours > 0) { 50 | return [NSString stringWithFormat:@"%02ld:%02ld:%02ld", hours, minutes, seconds]; 51 | } else { 52 | return [NSString stringWithFormat:@"%02ld:%02ld", minutes, seconds]; 53 | } 54 | } 55 | 56 | - (NSTimeInterval) value { 57 | if (!self.timer) { 58 | return self.accum; 59 | } 60 | 61 | return [[NSDate date] timeIntervalSinceDate:reference] + self.accum; 62 | } 63 | 64 | - (BOOL) isActive { 65 | return self.timer != nil; 66 | } 67 | 68 | - (BOOL) isPaused { 69 | return self.timer == nil && self.accum > 0; 70 | } 71 | 72 | - (BOOL) isStopped { 73 | return self.timer == nil && self.accum == 0; 74 | } 75 | 76 | - (void) start { 77 | if ([self isActive]) { 78 | return; 79 | } 80 | 81 | self.reference = [NSDate date]; 82 | 83 | self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(tick) userInfo:nil repeats:YES]; 84 | 85 | if (self.delegate) { 86 | [self.delegate didStart:self]; 87 | [self.delegate didChange:self]; 88 | } 89 | } 90 | 91 | - (void) pause { 92 | if ([self isPaused]) { 93 | return; 94 | } 95 | 96 | self.accum = [self value]; 97 | 98 | [self.timer invalidate]; 99 | self.timer = nil; 100 | 101 | if (self.delegate) { 102 | [self.delegate didPause:self]; 103 | } 104 | } 105 | 106 | - (void) reset:(NSTimeInterval) value { 107 | self.accum = value; 108 | self.reference = [NSDate date]; 109 | 110 | if (self.delegate) { 111 | [self.delegate didChange:self]; 112 | } 113 | } 114 | 115 | - (void) stop { 116 | if ([self isStopped]) { 117 | return; 118 | } 119 | 120 | NSTimeInterval value = [self value]; 121 | 122 | self.accum = 0; 123 | 124 | [self.timer invalidate]; 125 | self.timer = nil; 126 | 127 | if (self.delegate) { 128 | [self.delegate didStop:self withValue:value]; 129 | } 130 | } 131 | 132 | #pragma mark Private 133 | 134 | - (void) tick { 135 | if (self.delegate) { 136 | [self.delegate didChange:self]; 137 | } 138 | } 139 | 140 | @end 141 | -------------------------------------------------------------------------------- /TagWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TagWindowController.h 3 | // Thyme 4 | // 5 | // Created by Marcus Kempe on 2017-04-18. 6 | // 7 | // 8 | 9 | //#ifndef TagWindowController_h 10 | //#define TagWindowController_h 11 | 12 | 13 | 14 | //#endif /* TagWindowController_h */ 15 | 16 | #import 17 | #import "ShortcutRecorder/ShortcutRecorder.h" 18 | 19 | @interface TagWindowController : NSWindowController { 20 | NSButton *okButton; 21 | NSButton *cancelButton; 22 | NSTextField *tagField; 23 | } 24 | 25 | @property (nonatomic, retain) IBOutlet NSTextField *tagField; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /TagWindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TagWindowController.m 3 | // Thyme 4 | // 5 | // Created by Marcus Kempe on 2017-04-18. 6 | // 7 | // 8 | 9 | #import "TagWindowController.h" 10 | 11 | @interface TagWindowController () 12 | - (void)onWindowResignKey; 13 | @end 14 | 15 | @implementation TagWindowController 16 | 17 | @synthesize tagField; 18 | 19 | - (id)initWithWindow:(NSWindow *)window 20 | { 21 | self = [super initWithWindow:window]; 22 | if (self) { 23 | // Initialization code here. 24 | } 25 | 26 | return self; 27 | 28 | } 29 | - (void)windowDidLoad 30 | { 31 | [super windowDidLoad]; 32 | 33 | NSUserDefaultsController *defaults = [NSUserDefaultsController sharedUserDefaultsController]; 34 | 35 | //[self.okButton bind:NSValueBinding toObject:defaults withKeyPath:@"values.askForTagOnFinishButton" options:nil]; 36 | } 37 | 38 | #pragma mark SRRecorderControlDelegate 39 | 40 | @end 41 | 42 | -------------------------------------------------------------------------------- /TagWindowController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 56 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Thyme-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleDisplayName 8 | Thyme 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFile 12 | logo.icns 13 | CFBundleIdentifier 14 | com.joaomoreno.${PRODUCT_NAME:rfc1034identifier} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | ${PRODUCT_NAME} 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 0.5.1 23 | CFBundleSignature 24 | ???? 25 | LSMinimumSystemVersion 26 | 10.7 27 | LSUIElement 28 | 29 | NSAppleScriptEnabled 30 | 31 | NSHumanReadableCopyright 32 | Copyright 2010-2015 João Moreno 33 | NSMainNibFile 34 | MainMenu 35 | NSPrincipalClass 36 | NSApplication 37 | OSAScriptingDefinition 38 | scriptSupport.sdef 39 | 40 | 41 | -------------------------------------------------------------------------------- /Thyme.xcodeproj/joao.pbxuser: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | 089C165DFE840E0CC02AAC07 /* English */ = { 4 | uiCtxt = { 5 | sepNavIntBoundsRect = "{{0, 0}, {1110, 461}}"; 6 | sepNavSelRange = "{0, 0}"; 7 | sepNavVisRange = "{0, 45}"; 8 | }; 9 | }; 10 | 256AC3D80F4B6AC300CF3369 /* ThymeAppDelegate.h */ = { 11 | uiCtxt = { 12 | sepNavIntBoundsRect = "{{0, 0}, {1110, 1065}}"; 13 | sepNavSelRange = "{38, 0}"; 14 | sepNavVisRange = "{0, 1293}"; 15 | }; 16 | }; 17 | 256AC3D90F4B6AC300CF3369 /* ThymeAppDelegate.m */ = { 18 | uiCtxt = { 19 | sepNavIntBoundsRect = "{{0, 0}, {1110, 8610}}"; 20 | sepNavSelRange = "{3115, 0}"; 21 | sepNavVisRange = "{2881, 1290}"; 22 | sepNavWindowFrame = "{{15, 15}, {750, 558}}"; 23 | }; 24 | }; 25 | 256AC3F00F4B6AF500CF3369 /* Thyme_Prefix.pch */ = { 26 | uiCtxt = { 27 | sepNavIntBoundsRect = "{{0, 0}, {1110, 716}}"; 28 | sepNavSelRange = "{131, 0}"; 29 | sepNavVisRange = "{0, 139}"; 30 | }; 31 | }; 32 | 29B97313FDCFA39411CA2CEA /* Project object */ = { 33 | activeBuildConfigurationName = Release; 34 | activeExecutable = CF00AF0F1120CEEF00F3503E /* Tyme */; 35 | activeSDKPreference = macosx10.6; 36 | activeTarget = 8D1107260486CEB800E47090 /* Thyme */; 37 | addToTargets = ( 38 | 8D1107260486CEB800E47090 /* Thyme */, 39 | ); 40 | breakpoints = ( 41 | ); 42 | codeSenseManager = CF00AF231120CF0600F3503E /* Code sense */; 43 | executables = ( 44 | CF00AF0F1120CEEF00F3503E /* Tyme */, 45 | ); 46 | perUserDictionary = { 47 | PBXConfiguration.PBXFileTableDataSource3.PBXFileTableDataSource = { 48 | PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; 49 | PBXFileTableDataSourceColumnSortingKey = PBXFileDataSource_Filename_ColumnID; 50 | PBXFileTableDataSourceColumnWidthsKey = ( 51 | 20, 52 | 932, 53 | 20, 54 | 48, 55 | 43, 56 | 43, 57 | 20, 58 | ); 59 | PBXFileTableDataSourceColumnsKey = ( 60 | PBXFileDataSource_FiletypeID, 61 | PBXFileDataSource_Filename_ColumnID, 62 | PBXFileDataSource_Built_ColumnID, 63 | PBXFileDataSource_ObjectSize_ColumnID, 64 | PBXFileDataSource_Errors_ColumnID, 65 | PBXFileDataSource_Warnings_ColumnID, 66 | PBXFileDataSource_Target_ColumnID, 67 | ); 68 | }; 69 | PBXConfiguration.PBXTargetDataSource.PBXTargetDataSource = { 70 | PBXFileTableDataSourceColumnSortingDirectionKey = "-1"; 71 | PBXFileTableDataSourceColumnSortingKey = PBXFileDataSource_Filename_ColumnID; 72 | PBXFileTableDataSourceColumnWidthsKey = ( 73 | 20, 74 | 892, 75 | 60, 76 | 20, 77 | 48, 78 | 43, 79 | 43, 80 | ); 81 | PBXFileTableDataSourceColumnsKey = ( 82 | PBXFileDataSource_FiletypeID, 83 | PBXFileDataSource_Filename_ColumnID, 84 | PBXTargetDataSource_PrimaryAttribute, 85 | PBXFileDataSource_Built_ColumnID, 86 | PBXFileDataSource_ObjectSize_ColumnID, 87 | PBXFileDataSource_Errors_ColumnID, 88 | PBXFileDataSource_Warnings_ColumnID, 89 | ); 90 | }; 91 | PBXPerProjectTemplateStateSaveDate = 300662900; 92 | PBXWorkspaceStateSaveDate = 300662900; 93 | }; 94 | perUserProjectItems = { 95 | CF007EB5116E1BF30028AA45 /* PBXTextBookmark */ = CF007EB5116E1BF30028AA45 /* PBXTextBookmark */; 96 | CF007F99116E1E210028AA45 /* PBXTextBookmark */ = CF007F99116E1E210028AA45 /* PBXTextBookmark */; 97 | CF007F9B116E1E210028AA45 /* PBXTextBookmark */ = CF007F9B116E1E210028AA45 /* PBXTextBookmark */; 98 | CF008088116E78A00028AA45 /* PBXTextBookmark */ = CF008088116E78A00028AA45 /* PBXTextBookmark */; 99 | CF00B02D1120E57500F3503E /* PBXTextBookmark */ = CF00B02D1120E57500F3503E /* PBXTextBookmark */; 100 | CF077A3B11B95CC600439C51 /* XDModelBookmark */ = CF077A3B11B95CC600439C51 /* XDModelBookmark */; 101 | CF7CF6BB11E9E77800E67462 /* PlistBookmark */ = CF7CF6BB11E9E77800E67462 /* PlistBookmark */; 102 | CF7CF6BD11E9E77800E67462 /* PBXTextBookmark */ = CF7CF6BD11E9E77800E67462 /* PBXTextBookmark */; 103 | CF7FFBDA11B94238005A78A3 /* PBXTextBookmark */ = CF7FFBDA11B94238005A78A3 /* PBXTextBookmark */; 104 | CFB79C1611EBC07700CC2698 /* PBXTextBookmark */ = CFB79C1611EBC07700CC2698 /* PBXTextBookmark */; 105 | CFBC42FA11C2A79300136CB5 /* PBXTextBookmark */ = CFBC42FA11C2A79300136CB5 /* PBXTextBookmark */; 106 | CFD02ADB11C198B6005C866A /* PBXTextBookmark */ = CFD02ADB11C198B6005C866A /* PBXTextBookmark */; 107 | CFD02ADC11C198B6005C866A /* PBXTextBookmark */ = CFD02ADC11C198B6005C866A /* PBXTextBookmark */; 108 | CFE78F3511BA75AC00C2A003 /* PBXTextBookmark */ = CFE78F3511BA75AC00C2A003 /* PBXTextBookmark */; 109 | CFE78F3811BA75AC00C2A003 /* PBXTextBookmark */ = CFE78F3811BA75AC00C2A003 /* PBXTextBookmark */; 110 | CFE78F3B11BA75AC00C2A003 /* PBXTextBookmark */ = CFE78F3B11BA75AC00C2A003 /* PBXTextBookmark */; 111 | CFE78F3C11BA75AC00C2A003 /* PBXTextBookmark */ = CFE78F3C11BA75AC00C2A003 /* PBXTextBookmark */; 112 | CFE78F3E11BA75AC00C2A003 /* PBXTextBookmark */ = CFE78F3E11BA75AC00C2A003 /* PBXTextBookmark */; 113 | CFE78F7C11BA7D3500C2A003 /* PBXTextBookmark */ = CFE78F7C11BA7D3500C2A003 /* PBXTextBookmark */; 114 | CFE78F7D11BA7D3500C2A003 /* PBXTextBookmark */ = CFE78F7D11BA7D3500C2A003 /* PBXTextBookmark */; 115 | CFE78F8611BA7DAD00C2A003 /* PBXBookmark */ = CFE78F8611BA7DAD00C2A003 /* PBXBookmark */; 116 | CFE78F9211BA7E3700C2A003 /* PBXBookmark */ = CFE78F9211BA7E3700C2A003 /* PBXBookmark */; 117 | CFE78F9311BA7E3700C2A003 /* PBXTextBookmark */ = CFE78F9311BA7E3700C2A003 /* PBXTextBookmark */; 118 | }; 119 | sourceControlManager = CF00AF221120CF0600F3503E /* Source Control */; 120 | userBuildSettings = { 121 | }; 122 | }; 123 | 29B97316FDCFA39411CA2CEA /* main.m */ = { 124 | uiCtxt = { 125 | sepNavIntBoundsRect = "{{0, 0}, {1379, 240}}"; 126 | sepNavSelRange = "{27, 0}"; 127 | sepNavVisRange = "{0, 190}"; 128 | }; 129 | }; 130 | 8D1107260486CEB800E47090 /* Thyme */ = { 131 | activeExec = 0; 132 | executables = ( 133 | CF00AF0F1120CEEF00F3503E /* Tyme */, 134 | ); 135 | }; 136 | 8D1107310486CEB800E47090 /* Thyme-Info.plist */ = { 137 | uiCtxt = { 138 | sepNavIntBoundsRect = "{{0, 0}, {726, 455}}"; 139 | sepNavSelRange = "{493, 0}"; 140 | sepNavVisRange = "{0, 1026}"; 141 | sepNavWindowFrame = "{{15, 59}, {1170, 814}}"; 142 | }; 143 | }; 144 | CF007EB5116E1BF30028AA45 /* PBXTextBookmark */ = { 145 | isa = PBXTextBookmark; 146 | fRef = CF007EB6116E1BF30028AA45 /* NSApplication.h */; 147 | name = "NSApplication.h: 300"; 148 | rLen = 0; 149 | rLoc = 13488; 150 | rType = 0; 151 | vrLen = 1621; 152 | vrLoc = 16793; 153 | }; 154 | CF007EB6116E1BF30028AA45 /* NSApplication.h */ = { 155 | isa = PBXFileReference; 156 | lastKnownFileType = sourcecode.c.h; 157 | name = NSApplication.h; 158 | path = /Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/AppKit.framework/Versions/C/Headers/NSApplication.h; 159 | sourceTree = ""; 160 | }; 161 | CF007F99116E1E210028AA45 /* PBXTextBookmark */ = { 162 | isa = PBXTextBookmark; 163 | fRef = CF007F9A116E1E210028AA45 /* CarbonEventsCore.h */; 164 | name = "CarbonEventsCore.h: 2594"; 165 | rLen = 21; 166 | rLoc = 92036; 167 | rType = 0; 168 | vrLen = 1122; 169 | vrLoc = 91536; 170 | }; 171 | CF007F9A116E1E210028AA45 /* CarbonEventsCore.h */ = { 172 | isa = PBXFileReference; 173 | lastKnownFileType = sourcecode.c.h; 174 | name = CarbonEventsCore.h; 175 | path = /Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/CarbonEventsCore.h; 176 | sourceTree = ""; 177 | }; 178 | CF007F9B116E1E210028AA45 /* PBXTextBookmark */ = { 179 | isa = PBXTextBookmark; 180 | fRef = CF007F9C116E1E210028AA45 /* CarbonEvents.h */; 181 | name = "CarbonEvents.h: 14809"; 182 | rLen = 93; 183 | rLoc = 568394; 184 | rType = 0; 185 | vrLen = 1730; 186 | vrLoc = 567747; 187 | }; 188 | CF007F9C116E1E210028AA45 /* CarbonEvents.h */ = { 189 | isa = PBXFileReference; 190 | lastKnownFileType = sourcecode.c.h; 191 | name = CarbonEvents.h; 192 | path = /Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/CarbonEvents.h; 193 | sourceTree = ""; 194 | }; 195 | CF00807D116E75E80028AA45 /* DDHotKeyCenter.h */ = { 196 | uiCtxt = { 197 | sepNavIntBoundsRect = "{{0, 0}, {2784, 915}}"; 198 | sepNavSelRange = "{1015, 0}"; 199 | sepNavVisRange = "{309, 1986}"; 200 | }; 201 | }; 202 | CF00807E116E75E80028AA45 /* DDHotKeyCenter.m */ = { 203 | uiCtxt = { 204 | sepNavIntBoundsRect = "{{0, 0}, {2784, 3450}}"; 205 | sepNavSelRange = "{7057, 0}"; 206 | sepNavVisRange = "{35, 1667}"; 207 | }; 208 | }; 209 | CF008088116E78A00028AA45 /* PBXTextBookmark */ = { 210 | isa = PBXTextBookmark; 211 | fRef = CF008089116E78A00028AA45 /* NSEvent.h */; 212 | name = "NSEvent.h: 104"; 213 | rLen = 43; 214 | rLoc = 4207; 215 | rType = 0; 216 | vrLen = 1398; 217 | vrLoc = 3966; 218 | }; 219 | CF008089116E78A00028AA45 /* NSEvent.h */ = { 220 | isa = PBXFileReference; 221 | lastKnownFileType = sourcecode.c.h; 222 | name = NSEvent.h; 223 | path = /Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/AppKit.framework/Versions/C/Headers/NSEvent.h; 224 | sourceTree = ""; 225 | }; 226 | CF0080C2116E80820028AA45 /* Growl Registration Ticket.growlRegDict */ = { 227 | uiCtxt = { 228 | sepNavIntBoundsRect = "{{0, 0}, {1110, 509}}"; 229 | sepNavSelRange = "{360, 0}"; 230 | sepNavVisRange = "{0, 360}"; 231 | }; 232 | }; 233 | CF00AF0F1120CEEF00F3503E /* Tyme */ = { 234 | isa = PBXExecutable; 235 | activeArgIndices = ( 236 | ); 237 | argumentStrings = ( 238 | ); 239 | autoAttachOnCrash = 1; 240 | breakpointsEnabled = 1; 241 | configStateDict = { 242 | }; 243 | customDataFormattersEnabled = 1; 244 | dataTipCustomDataFormattersEnabled = 1; 245 | dataTipShowTypeColumn = 1; 246 | dataTipSortType = 0; 247 | debuggerPlugin = GDBDebugging; 248 | disassemblyDisplayState = 0; 249 | dylibVariantSuffix = ""; 250 | enableDebugStr = 1; 251 | environmentEntries = ( 252 | ); 253 | executableSystemSymbolLevel = 0; 254 | executableUserSymbolLevel = 0; 255 | libgmallocEnabled = 0; 256 | name = Tyme; 257 | savedGlobals = { 258 | }; 259 | showTypeColumn = 0; 260 | sourceDirectories = ( 261 | ); 262 | }; 263 | CF00AF221120CF0600F3503E /* Source Control */ = { 264 | isa = PBXSourceControlManager; 265 | fallbackIsa = XCSourceControlManager; 266 | isSCMEnabled = 0; 267 | scmConfiguration = { 268 | repositoryNamesForRoots = { 269 | "" = ""; 270 | }; 271 | }; 272 | }; 273 | CF00AF231120CF0600F3503E /* Code sense */ = { 274 | isa = PBXCodeSenseManager; 275 | indexTemplatePath = ""; 276 | }; 277 | CF00B02D1120E57500F3503E /* PBXTextBookmark */ = { 278 | isa = PBXTextBookmark; 279 | fRef = CF00B02E1120E57500F3503E /* NSRunLoop.h */; 280 | name = "NSRunLoop.h: 30"; 281 | rLen = 60; 282 | rLoc = 706; 283 | rType = 0; 284 | vrLen = 1016; 285 | vrLoc = 0; 286 | }; 287 | CF00B02E1120E57500F3503E /* NSRunLoop.h */ = { 288 | isa = PBXFileReference; 289 | lastKnownFileType = sourcecode.c.h; 290 | name = NSRunLoop.h; 291 | path = /Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/Foundation.framework/Versions/C/Headers/NSRunLoop.h; 292 | sourceTree = ""; 293 | }; 294 | CF0779E111B9568900439C51 /* Session.h */ = { 295 | uiCtxt = { 296 | sepNavIntBoundsRect = "{{0, 0}, {1110, 716}}"; 297 | sepNavSelRange = "{67, 0}"; 298 | sepNavVisRange = "{0, 552}"; 299 | }; 300 | }; 301 | CF0779E211B9568900439C51 /* Session.m */ = { 302 | uiCtxt = { 303 | sepNavIntBoundsRect = "{{0, 0}, {1110, 1125}}"; 304 | sepNavSelRange = "{667, 0}"; 305 | sepNavVisRange = "{391, 1691}"; 306 | }; 307 | }; 308 | CF077A3B11B95CC600439C51 /* XDModelBookmark */ = { 309 | isa = XDModelBookmark; 310 | fRef = CF7FFC0A11B942E4005A78A3 /* DataModel.xcdatamodel */; 311 | fallbackIsa = PBXBookmark; 312 | }; 313 | CF7CF6BB11E9E77800E67462 /* PlistBookmark */ = { 314 | isa = PlistBookmark; 315 | fRef = 8D1107310486CEB800E47090 /* Thyme-Info.plist */; 316 | fallbackIsa = PBXBookmark; 317 | isK = 0; 318 | kPath = ( 319 | ); 320 | name = "/Users/joao/Projects/Thyme/Thyme-Info.plist"; 321 | rLen = 0; 322 | rLoc = 9223372036854775808; 323 | }; 324 | CF7CF6BD11E9E77800E67462 /* PBXTextBookmark */ = { 325 | isa = PBXTextBookmark; 326 | fRef = 256AC3D90F4B6AC300CF3369 /* ThymeAppDelegate.m */; 327 | name = "ThymeAppDelegate.m: 154"; 328 | rLen = 0; 329 | rLoc = 3115; 330 | rType = 0; 331 | vrLen = 1331; 332 | vrLoc = 2881; 333 | }; 334 | CF7FFBDA11B94238005A78A3 /* PBXTextBookmark */ = { 335 | isa = PBXTextBookmark; 336 | fRef = CF0080C2116E80820028AA45 /* Growl Registration Ticket.growlRegDict */; 337 | name = "Growl Registration Ticket.growlRegDict: 1"; 338 | rLen = 0; 339 | rLoc = 0; 340 | rType = 0; 341 | vrLen = 360; 342 | vrLoc = 0; 343 | }; 344 | CF7FFC0A11B942E4005A78A3 /* DataModel.xcdatamodel */ = { 345 | uiCtxt = { 346 | "Xdesign Model Editor Bookmark Key" = { 347 | UIContextForDetailArea = { 348 | _indexOfSelectedPane = 0; 349 | _indexOfSelectedView = 0; 350 | }; 351 | UIContextForEntityArea = { 352 | _indexOfSelectedView = 0; 353 | chosenTableColumnns = ( 354 | "ET Entity", 355 | "ET Abstract", 356 | "ET Class", 357 | ); 358 | chosenTableColumnns_sortDirections = ( 359 | YES, 360 | ); 361 | chosenTableColumnns_sortKeys = ( 362 | name, 363 | ); 364 | chosenTableColumnns_sortSelectors = ( 365 | "_xdcaseInsensitiveNumericCompare:", 366 | ); 367 | chosenTableWidths = ( 368 | 103.000000, 369 | 22.000000, 370 | 100.080078, 371 | ); 372 | }; 373 | UIContextForLayoutManager = { 374 | detailSplitGeometry = { 375 | _collapsingFrameDimension = 0; 376 | _indexOfCollapsedView = 0; 377 | _percentageOfCollapsedView = 0; 378 | isCollapsed = yes; 379 | sizes = ( 380 | "{{0, 0}, {763, 218}}", 381 | "{{768, 0}, {401, 218}}", 382 | ); 383 | }; 384 | diagramSplitGeometry = { 385 | _collapsingFrameDimension = 0; 386 | _indexOfCollapsedView = 0; 387 | _percentageOfCollapsedView = 0; 388 | isCollapsed = yes; 389 | sizes = ( 390 | "{{0, 0}, {1169, 220}}", 391 | "{{0, 225}, {1169, 531}}", 392 | ); 393 | }; 394 | tableSplitGeometry = { 395 | _collapsingFrameDimension = 0; 396 | _indexOfCollapsedView = 0; 397 | _percentageOfCollapsedView = 0; 398 | isCollapsed = yes; 399 | sizes = ( 400 | "{{0, 0}, {250, 218}}", 401 | "{{255, 0}, {508, 218}}", 402 | ); 403 | }; 404 | }; 405 | UIContextForPropertyArea = { 406 | _indexOfSelectedView = 0; 407 | chosenTableColumnnFRTs = ( 408 | "FT Fetch Request", 409 | "FT Predicate", 410 | ); 411 | chosenTableColumnnFRTs_sortDirections = ( 412 | YES, 413 | ); 414 | chosenTableColumnnFRTs_sortKeys = ( 415 | name, 416 | ); 417 | chosenTableColumnnFRTs_sortSelectors = ( 418 | "_xdcaseInsensitiveNumericCompare:", 419 | ); 420 | chosenTableColumnnsAT = ( 421 | "AT Attribute", 422 | "AT Type", 423 | ); 424 | chosenTableColumnnsAT_sortDirections = ( 425 | YES, 426 | ); 427 | chosenTableColumnnsAT_sortKeys = ( 428 | name, 429 | ); 430 | chosenTableColumnnsAT_sortSelectors = ( 431 | "_xdcaseInsensitiveNumericCompare:", 432 | ); 433 | chosenTableColumnnsFPT = ( 434 | "FP FetchedProperty", 435 | "FP Destination", 436 | "FP Predicate", 437 | ); 438 | chosenTableColumnnsFPT_sortDirections = ( 439 | YES, 440 | ); 441 | chosenTableColumnnsFPT_sortKeys = ( 442 | name, 443 | ); 444 | chosenTableColumnnsFPT_sortSelectors = ( 445 | "_xdcaseInsensitiveNumericCompare:", 446 | ); 447 | chosenTableColumnnsPT = ( 448 | "PT Property", 449 | "PT Kind", 450 | "PT SharedKeyOne", 451 | ); 452 | chosenTableColumnnsPT_sortDirections = ( 453 | YES, 454 | ); 455 | chosenTableColumnnsPT_sortKeys = ( 456 | name, 457 | ); 458 | chosenTableColumnnsPT_sortSelectors = ( 459 | "_xdcaseInsensitiveNumericCompare:", 460 | ); 461 | chosenTableColumnnsRT = ( 462 | "RT Relationship", 463 | "RT Destination", 464 | ); 465 | chosenTableColumnnsRT_sortDirections = ( 466 | YES, 467 | ); 468 | chosenTableColumnnsRT_sortKeys = ( 469 | name, 470 | ); 471 | chosenTableColumnnsRT_sortSelectors = ( 472 | "_xdcaseInsensitiveNumericCompare:", 473 | ); 474 | chosenTableWidthsAT = ( 475 | 128.000000, 476 | 64.000000, 477 | ); 478 | chosenTableWidthsFPT = ( 479 | 128.000000, 480 | 64.000000, 481 | 144.764160, 482 | ); 483 | chosenTableWidthsFRT = ( 484 | 128.000000, 485 | 249.764160, 486 | ); 487 | chosenTableWidthsPT = ( 488 | 128.000000, 489 | 108.110840, 490 | 245.570801, 491 | ); 492 | chosenTableWidthsRT = ( 493 | 128.000000, 494 | 64.000000, 495 | ); 496 | }; 497 | "xdesign model browser state key - shown/hidden" = 1; 498 | "xdesign model diagram view visible rect key" = "{{-192, -11}, {1154, 516}}"; 499 | }; 500 | sepNavWindowFrame = "{{15, 59}, {1170, 814}}"; 501 | }; 502 | }; 503 | CFB79C1611EBC07700CC2698 /* PBXTextBookmark */ = { 504 | isa = PBXTextBookmark; 505 | fRef = 256AC3D90F4B6AC300CF3369 /* ThymeAppDelegate.m */; 506 | name = "ThymeAppDelegate.m: 154"; 507 | rLen = 0; 508 | rLoc = 3115; 509 | rType = 0; 510 | vrLen = 1290; 511 | vrLoc = 2881; 512 | }; 513 | CFBC42FA11C2A79300136CB5 /* PBXTextBookmark */ = { 514 | isa = PBXTextBookmark; 515 | fRef = 256AC3D80F4B6AC300CF3369 /* ThymeAppDelegate.h */; 516 | name = "ThymeAppDelegate.h: 10"; 517 | rLen = 0; 518 | rLoc = 150; 519 | rType = 0; 520 | vrLen = 1185; 521 | vrLoc = 0; 522 | }; 523 | CFD02ADB11C198B6005C866A /* PBXTextBookmark */ = { 524 | isa = PBXTextBookmark; 525 | fRef = CF0779E211B9568900439C51 /* Session.m */; 526 | name = "Session.m: 33"; 527 | rLen = 0; 528 | rLoc = 667; 529 | rType = 0; 530 | vrLen = 1691; 531 | vrLoc = 391; 532 | }; 533 | CFD02ADC11C198B6005C866A /* PBXTextBookmark */ = { 534 | isa = PBXTextBookmark; 535 | fRef = CFD02ADD11C198B6005C866A /* NSStatusBar.h */; 536 | name = "NSStatusBar.h: 30"; 537 | rLen = 12; 538 | rLoc = 563; 539 | rType = 0; 540 | vrLen = 710; 541 | vrLoc = 0; 542 | }; 543 | CFD02ADD11C198B6005C866A /* NSStatusBar.h */ = { 544 | isa = PBXFileReference; 545 | lastKnownFileType = sourcecode.c.h; 546 | name = NSStatusBar.h; 547 | path = /Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/AppKit.framework/Versions/C/Headers/NSStatusBar.h; 548 | sourceTree = ""; 549 | }; 550 | CFE78F3511BA75AC00C2A003 /* PBXTextBookmark */ = { 551 | isa = PBXTextBookmark; 552 | fRef = CF0779E111B9568900439C51 /* Session.h */; 553 | name = "Session.h: 5"; 554 | rLen = 0; 555 | rLoc = 67; 556 | rType = 0; 557 | vrLen = 552; 558 | vrLoc = 0; 559 | }; 560 | CFE78F3811BA75AC00C2A003 /* PBXTextBookmark */ = { 561 | isa = PBXTextBookmark; 562 | fRef = CF00807E116E75E80028AA45 /* DDHotKeyCenter.m */; 563 | name = "DDHotKeyCenter.m: 214"; 564 | rLen = 0; 565 | rLoc = 7057; 566 | rType = 0; 567 | vrLen = 1667; 568 | vrLoc = 35; 569 | }; 570 | CFE78F3B11BA75AC00C2A003 /* PBXTextBookmark */ = { 571 | isa = PBXTextBookmark; 572 | fRef = 256AC3F00F4B6AF500CF3369 /* Thyme_Prefix.pch */; 573 | name = "Thyme_Prefix.pch: 6"; 574 | rLen = 0; 575 | rLoc = 131; 576 | rType = 0; 577 | vrLen = 139; 578 | vrLoc = 0; 579 | }; 580 | CFE78F3C11BA75AC00C2A003 /* PBXTextBookmark */ = { 581 | isa = PBXTextBookmark; 582 | fRef = 29B97316FDCFA39411CA2CEA /* main.m */; 583 | name = "main.m: 5"; 584 | rLen = 0; 585 | rLoc = 64; 586 | rType = 0; 587 | vrLen = 190; 588 | vrLoc = 0; 589 | }; 590 | CFE78F3E11BA75AC00C2A003 /* PBXTextBookmark */ = { 591 | isa = PBXTextBookmark; 592 | fRef = 089C165DFE840E0CC02AAC07 /* English */; 593 | name = "InfoPlist.strings: 1"; 594 | rLen = 0; 595 | rLoc = 0; 596 | rType = 0; 597 | vrLen = 45; 598 | vrLoc = 0; 599 | }; 600 | CFE78F5811BA797A00C2A003 /* LICENSE */ = { 601 | uiCtxt = { 602 | sepNavIntBoundsRect = "{{0, 0}, {1110, 10140}}"; 603 | sepNavSelRange = "{30236, 0}"; 604 | sepNavVisRange = "{29777, 1581}"; 605 | }; 606 | }; 607 | CFE78F7C11BA7D3500C2A003 /* PBXTextBookmark */ = { 608 | isa = PBXTextBookmark; 609 | fRef = CFE78F5811BA797A00C2A003 /* LICENSE */; 610 | name = "LICENSE: 576"; 611 | rLen = 0; 612 | rLoc = 30236; 613 | rType = 0; 614 | vrLen = 1581; 615 | vrLoc = 29777; 616 | }; 617 | CFE78F7D11BA7D3500C2A003 /* PBXTextBookmark */ = { 618 | isa = PBXTextBookmark; 619 | fRef = CFE78F5411BA786A00C2A003 /* Credits.rtf */; 620 | name = "Credits.rtf: 3"; 621 | rLen = 0; 622 | rLoc = 102; 623 | rType = 0; 624 | vrLen = 102; 625 | vrLoc = 0; 626 | }; 627 | CFE78F8611BA7DAD00C2A003 /* PBXBookmark */ = { 628 | isa = PBXBookmark; 629 | fRef = CFE78F1411BA73B700C2A003 /* logo.icns */; 630 | }; 631 | CFE78F9211BA7E3700C2A003 /* PBXBookmark */ = { 632 | isa = PBXBookmark; 633 | fRef = CFE78F8111BA7D5D00C2A003 /* logo_small.png */; 634 | }; 635 | CFE78F9311BA7E3700C2A003 /* PBXTextBookmark */ = { 636 | isa = PBXTextBookmark; 637 | fRef = CF00807D116E75E80028AA45 /* DDHotKeyCenter.h */; 638 | name = "DDHotKeyCenter.h: 21"; 639 | rLen = 0; 640 | rLoc = 1015; 641 | rType = 0; 642 | vrLen = 1986; 643 | vrLoc = 309; 644 | }; 645 | } 646 | -------------------------------------------------------------------------------- /Thyme.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Thyme.xcodeproj/xcuserdata/joao.xcuserdatad/xcschemes/Thyme.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /Thyme.xcodeproj/xcuserdata/joao.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Thyme.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 8D1107260486CEB800E47090 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ThymeAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // ThymeAppDelegate.h 3 | // Thyme 4 | // 5 | // Created by João Moreno on 2/8/10. 6 | // 7 | 8 | #import 9 | #import 10 | #import "DDHotKeyCenter.h" 11 | #import "Stopwatch.h" 12 | #import "PreferencesWindowController.h" 13 | #import "TagWindowController.h" 14 | 15 | @interface ThymeAppDelegate : NSObject 16 | { 17 | Stopwatch* stopwatch; 18 | DDHotKeyCenter *hotKeyCenter; 19 | 20 | NSPersistentStoreCoordinator *persistentStoreCoordinator; 21 | NSManagedObjectModel *managedObjectModel; 22 | NSManagedObjectContext *managedObjectContext; 23 | 24 | NSWindow *window; 25 | 26 | NSStatusItem *statusItem; 27 | NSMenu *menu; 28 | NSMenuItem *startPauseItem; 29 | NSMenuItem *finishItem; 30 | 31 | PreferencesWindowController *preferencesWindowController; 32 | TagWindowController *tagWindowController; 33 | 34 | NSMenuItem *sessionsMenuSeparator; 35 | NSMenuItem *sessionsMenuExportItem; 36 | NSMenuItem *sessionsMenuClearItem; 37 | NSMutableArray *sessionsMenuItems; 38 | 39 | BOOL startOnWake; 40 | BOOL startOnScreensaverEnd; 41 | } 42 | 43 | @property(nonatomic, retain) Stopwatch *stopwatch; 44 | @property(nonatomic, retain) DDHotKeyCenter *hotKeyCenter; 45 | 46 | @property(nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator; 47 | @property(nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel; 48 | @property(nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext; 49 | 50 | @property(nonatomic, retain) IBOutlet NSWindow *window; 51 | 52 | @property(nonatomic, retain) NSStatusItem *statusItem; 53 | @property(nonatomic, retain) IBOutlet NSMenu *menu; 54 | @property(nonatomic, retain) IBOutlet NSMenuItem *startPauseItem; 55 | @property(nonatomic, retain) IBOutlet NSMenuItem *restartItem; 56 | @property(nonatomic, retain) IBOutlet NSMenuItem *finishItem; 57 | 58 | @property(nonatomic, retain) PreferencesWindowController *preferencesWindowController; 59 | @property(nonatomic, retain) TagWindowController *tagWindowController; 60 | 61 | @property(nonatomic, retain) NSMenuItem *sessionsMenuSeparator; 62 | @property(nonatomic, retain) NSMenuItem *sessionsMenuExportItem; 63 | @property(nonatomic, retain) NSMenuItem *sessionsMenuClearItem; 64 | @property(nonatomic, retain) NSMutableArray *sessionsMenuItems; 65 | 66 | @property(nonatomic) NSTimeInterval lastStopWatchValue; 67 | 68 | @property(nonatomic, retain) NSString *currentTag; 69 | 70 | - (void)export; 71 | 72 | - (void)startWithNotification:(Boolean)notification; 73 | - (void)pauseWithNotification:(Boolean)notification; 74 | - (void)toggleWithNotification:(Boolean)notification; 75 | - (void)stopWithNotification:(Boolean)notification; 76 | 77 | - (void)clearHotKeys; 78 | - (void)resetHotKeys; 79 | 80 | - (IBAction)onStartPauseClick:(id)sender; 81 | - (IBAction)onFinishClick:(id)sender; 82 | - (IBAction)onPreferencesClick:(id)sender; 83 | - (IBAction)onAboutClick:(id)sender; 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /ThymeAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // ThymeAppDelegate.m 3 | // Thyme 4 | // 5 | // Created by João Moreno on 2/8/10. 6 | // 7 | 8 | #import "ThymeAppDelegate.h" 9 | #import "Session.h" 10 | #import "ShortcutRecorder/PTHotKey/PTKeyCodeTranslator.h" 11 | 12 | #define KEYCODE_T 17 13 | #define KEYCODE_R 15 14 | #define ZERO_TIME (hours == 0 && minutes == 0 && seconds == 0) 15 | 16 | @interface ThymeAppDelegate(hidden) 17 | - (void)startTimer; 18 | - (void)resetTimer; 19 | 20 | - (void)notifyStart; 21 | - (void)notifyPauseWithDescription:(NSString*)description; 22 | - (void)notifyStopWithDescription:(NSString*)description; 23 | 24 | - (void)save:(NSTimeInterval)value : (NSString*)tag; 25 | 26 | - (void)updateStatusBar; 27 | - (void)clearSessionsFromMenu; 28 | - (void)addSessionToMenu:(Session*)session; 29 | 30 | - (IBAction)clear:(id)sender; 31 | - (IBAction)saveAction:(id)sender; 32 | - (IBAction)okButtonAction:(id)sender; 33 | @end 34 | 35 | 36 | @implementation ThymeAppDelegate 37 | 38 | @synthesize stopwatch; 39 | @synthesize hotKeyCenter; 40 | @synthesize statusItem; 41 | @synthesize window; 42 | @synthesize menu; 43 | @synthesize startPauseItem; 44 | @synthesize restartItem; 45 | @synthesize finishItem; 46 | @synthesize preferencesWindowController; 47 | @synthesize tagWindowController; 48 | @synthesize sessionsMenuSeparator; 49 | @synthesize sessionsMenuExportItem; 50 | @synthesize sessionsMenuClearItem; 51 | @synthesize sessionsMenuItems; 52 | @synthesize lastStopWatchValue; 53 | 54 | #pragma mark Controller 55 | 56 | - (void)startWithNotification:(Boolean)notification 57 | { 58 | if (![self.stopwatch isActive]) { 59 | [self.stopwatch start]; 60 | 61 | [startPauseItem setTitle:@"Pause"]; 62 | [restartItem setEnabled:YES]; 63 | [finishItem setEnabled:YES]; 64 | 65 | if (notification) { 66 | [self notifyStart]; 67 | } 68 | } 69 | } 70 | 71 | - (void)pauseWithNotification:(Boolean)notification 72 | { 73 | if ([self.stopwatch isActive]) { 74 | [self.stopwatch pause]; 75 | 76 | [startPauseItem setTitle:@"Continue"]; 77 | 78 | if (notification) { 79 | [self notifyPauseWithDescription:[self.stopwatch description]]; 80 | } 81 | } 82 | } 83 | 84 | - (void)toggleWithNotification:(Boolean)notification 85 | { 86 | if ([self.stopwatch isActive]) { 87 | [self pauseWithNotification:notification]; 88 | } else { 89 | [self startWithNotification:notification]; 90 | } 91 | } 92 | 93 | - (void)stopWithNotification:(Boolean)notification 94 | { 95 | if (![self.stopwatch isStopped]) { 96 | NSString* description = [self.stopwatch description]; 97 | [self resetWithNotification:NO]; 98 | 99 | if (notification) { 100 | [self notifyStopWithDescription:description]; 101 | } 102 | } 103 | } 104 | 105 | - (void)resetWithNotification:(Boolean)notification 106 | { 107 | if (![self.stopwatch isStopped]) { 108 | NSString* description = [self.stopwatch description]; 109 | [self.stopwatch stop]; 110 | 111 | [startPauseItem setTitle:@"Start"]; 112 | [restartItem setEnabled:NO]; 113 | [finishItem setEnabled:NO]; 114 | 115 | if ([[NSUserDefaults standardUserDefaults] boolForKey:@"askForTagOnFinishButton"]) { 116 | //Show tag window 117 | if (self.tagWindowController == nil) { 118 | TagWindowController* pwc = [[TagWindowController alloc] initWithWindowNibName:@"TagWindowController"]; 119 | self.tagWindowController = pwc; 120 | [pwc release]; 121 | } 122 | 123 | [self.tagWindowController showWindow:nil]; 124 | } 125 | else{ 126 | [self save: lastStopWatchValue : @""]; 127 | } 128 | 129 | [NSApp activateIgnoringOtherApps:YES]; 130 | 131 | if (notification) { 132 | [self notifyPauseWithDescription:description]; 133 | } 134 | } 135 | } 136 | 137 | - (void)restartWithNotification:(Boolean)notification 138 | { 139 | if (![self.stopwatch isStopped]) { 140 | [self resetWithNotification:notification]; 141 | [self startWithNotification:notification]; 142 | } 143 | } 144 | 145 | - (void)clearSessionsFromMenu 146 | { 147 | [menu removeItem:self.sessionsMenuSeparator]; 148 | [menu removeItem:self.sessionsMenuExportItem]; 149 | [menu removeItem:self.sessionsMenuClearItem]; 150 | 151 | for (NSMenuItem *item in self.sessionsMenuItems) { 152 | [menu removeItem:item]; 153 | } 154 | 155 | [self.sessionsMenuItems removeAllObjects]; 156 | } 157 | 158 | - (void)addSessionToMenu:(Session*)session 159 | { 160 | if ([self.sessionsMenuItems count] == 0) 161 | { 162 | [menu insertItem:self.sessionsMenuSeparator atIndex:3]; 163 | [menu insertItem:self.sessionsMenuExportItem atIndex:4]; 164 | [menu insertItem:self.sessionsMenuClearItem atIndex:5]; 165 | } 166 | 167 | NSInteger index = 4 + [self.sessionsMenuItems count]; 168 | 169 | NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[session stringRepresentation] action:@selector(lol) keyEquivalent:@""]; 170 | [item setEnabled:NO]; 171 | [menu insertItem:item atIndex:index]; 172 | [self.sessionsMenuItems addObject:item]; 173 | [item release]; 174 | } 175 | 176 | #pragma mark Model 177 | 178 | - (void)save:(NSTimeInterval)value :(NSString*)tag { 179 | 180 | long totalSeconds = (long) floor(value); 181 | long hours = totalSeconds / 3600; 182 | long minutes = (totalSeconds / 60) % 60; 183 | long seconds = totalSeconds % 60; 184 | NSString* defaultTag = @""; 185 | 186 | if (tag && tag.length > 0) 187 | { 188 | defaultTag = tag; 189 | } 190 | 191 | if (totalSeconds > 0) { 192 | Session *session = [Session sessionWithSeconds:seconds minutes:minutes hours:hours tag:defaultTag]; 193 | [self saveAction:self]; 194 | [self addSessionToMenu:session]; 195 | } 196 | } 197 | 198 | - (IBAction)okButtonAction:(id)sender 199 | { 200 | if([sender isKindOfClass:[NSButton class]]){ 201 | NSButton *button = (NSButton *)sender; 202 | if([button.identifier isEqual: @"tagWindowOkButton"]){ 203 | [self save: lastStopWatchValue : self.tagWindowController.tagField.stringValue]; 204 | } 205 | if (self.tagWindowController) { 206 | [self.tagWindowController close]; 207 | } 208 | } 209 | } 210 | 211 | - (IBAction)cancelButtonAction:(id)sender 212 | { 213 | if([sender isKindOfClass:[NSButton class]]){ 214 | NSButton *button = (NSButton *)sender; 215 | if([button.identifier isEqual: @"tagWindowCancelButton"]){ 216 | [self save: lastStopWatchValue : @""]; 217 | if (self.tagWindowController) { 218 | [self.tagWindowController close]; 219 | } 220 | } 221 | } 222 | } 223 | 224 | #pragma mark Status Bar 225 | 226 | - (IBAction)onStartPauseClick:(id)sender 227 | { 228 | [self toggleWithNotification:NO]; 229 | } 230 | 231 | - (IBAction)onRestartClick:(id)sender 232 | { 233 | [self restartWithNotification:NO]; 234 | } 235 | 236 | - (IBAction)onFinishClick:(id)sender 237 | { 238 | [self resetWithNotification:NO]; 239 | } 240 | 241 | - (IBAction)clear:(id)sender 242 | { 243 | for (Session *session in [Session allSessions]) { 244 | [self.managedObjectContext deleteObject:session]; 245 | } 246 | 247 | [self saveAction:self]; 248 | 249 | [self clearSessionsFromMenu]; 250 | } 251 | 252 | - (IBAction)onPreferencesClick:(id)sender { 253 | if (self.preferencesWindowController == nil) { 254 | PreferencesWindowController* pwc = [[PreferencesWindowController alloc] initWithWindowNibName:@"PreferencesWindowController"]; 255 | self.preferencesWindowController = pwc; 256 | [pwc release]; 257 | } 258 | 259 | [self.preferencesWindowController showWindow:nil]; 260 | [NSApp activateIgnoringOtherApps:YES]; 261 | } 262 | 263 | - (IBAction)onAboutClick:(id)sender { 264 | [[NSApplication sharedApplication] orderFrontStandardAboutPanel:nil]; 265 | [NSApp activateIgnoringOtherApps:YES]; 266 | } 267 | 268 | - (void)updateStatusBar { 269 | if ([self.stopwatch isStopped]) { 270 | [statusItem setLength:26.0]; 271 | [statusItem setTitle:@""]; 272 | 273 | NSImage *logo = [NSImage imageNamed:@"logo_small"]; 274 | [logo setTemplate:YES]; 275 | [statusItem setImage: logo]; 276 | } else { 277 | [statusItem setLength:[self.stopwatch value] > 3600 ? 72.0 : 46.0]; 278 | [statusItem setTitle:[self.stopwatch description]]; 279 | [statusItem setImage:nil]; 280 | } 281 | } 282 | 283 | #pragma mark Export 284 | 285 | - (void)export { 286 | NSError *error; 287 | 288 | NSData *jsonData = [NSJSONSerialization dataWithJSONObject:[Session allSessionsAsDictionaries] 289 | options:0 290 | error:&error]; 291 | 292 | NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 293 | 294 | NSSavePanel *panel = [NSSavePanel savePanel]; 295 | [panel setCanCreateDirectories:NO]; 296 | 297 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 298 | NSLocale *enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; 299 | [dateFormatter setLocale:enUSPOSIXLocale]; 300 | [dateFormatter setDateFormat:@"yyyy-MM-dd"]; 301 | 302 | [panel setNameFieldStringValue:[NSString stringWithFormat:@"thyme-%@.json", [dateFormatter stringFromDate:[NSDate date]]]]; 303 | [dateFormatter release]; 304 | 305 | if ([panel runModal] == NSModalResponseOK) { 306 | [jsonData writeToURL:[panel URL] atomically:YES]; 307 | } 308 | 309 | [jsonString release]; 310 | } 311 | 312 | #pragma mark Hot Key Handlers 313 | 314 | - (void)startTimer 315 | { 316 | if (self.preferencesWindowController != nil && [[self.preferencesWindowController window] isVisible]) { 317 | return; 318 | } 319 | 320 | [self toggleWithNotification:YES]; 321 | } 322 | 323 | - (void)restartTimer 324 | { 325 | if (self.preferencesWindowController != nil && [[self.preferencesWindowController window] isVisible]) { 326 | return; 327 | } 328 | 329 | [self restartWithNotification:YES]; 330 | } 331 | 332 | - (void)resetTimer 333 | { 334 | if (self.preferencesWindowController != nil && [[self.preferencesWindowController window] isVisible]) { 335 | return; 336 | } 337 | 338 | [self stopWithNotification:YES]; 339 | } 340 | 341 | #pragma mark Stopwatch Delegate 342 | 343 | - (void) didStart:(id)stopwatch { 344 | [self updateStatusBar]; 345 | } 346 | 347 | - (void) didPause:(id)_stopwatch { 348 | [self updateStatusBar]; 349 | } 350 | 351 | - (void) didStop:(id)stopwatch withValue:(NSTimeInterval)value { 352 | self.lastStopWatchValue = value; 353 | [self updateStatusBar]; 354 | } 355 | 356 | - (void) didChange:(id)stopwatch { 357 | [self updateStatusBar]; 358 | } 359 | 360 | #pragma mark Notifications 361 | 362 | - (void)notifyStart 363 | { 364 | [GrowlApplicationBridge notifyWithTitle:@"Thyme" description:@"Started" notificationName:@"start" iconData:nil priority:0 isSticky:NO clickContext:nil]; 365 | } 366 | 367 | - (void)notifyPauseWithDescription:(NSString*)description 368 | { 369 | [GrowlApplicationBridge notifyWithTitle:@"Thyme" description:[@"Paused at " stringByAppendingString:description] notificationName:@"pause" iconData:nil priority:0 isSticky:NO clickContext:nil]; 370 | } 371 | 372 | - (void)notifyStopWithDescription:(NSString*)description 373 | { 374 | [GrowlApplicationBridge notifyWithTitle:@"Thyme" description:[@"Stopped at " stringByAppendingString:description] notificationName:@"stop" iconData:nil priority:0 isSticky:NO clickContext:nil]; 375 | } 376 | 377 | #pragma mark NSUserDefaultsDidChangeNotification 378 | 379 | - (void)onUserDefaultsChange:(NSNotification*)notification { 380 | [self resetHotKeys]; 381 | } 382 | 383 | #pragma mark Hot Keys 384 | 385 | - (void)clearHotKeys { 386 | [self.hotKeyCenter unregisterHotKeysWithTarget:self]; 387 | 388 | self.startPauseItem.keyEquivalent = @""; 389 | self.startPauseItem.keyEquivalentModifierMask = 0; 390 | 391 | self.restartItem.keyEquivalent = @""; 392 | self.restartItem.keyEquivalentModifierMask = 0; 393 | 394 | self.finishItem.keyEquivalent = @""; 395 | self.finishItem.keyEquivalentModifierMask = 0; 396 | } 397 | 398 | - (void)resetHotKeys { 399 | [self clearHotKeys]; 400 | 401 | NSDictionary* combo; 402 | NSInteger keyCode; 403 | NSUInteger modifierKeys; 404 | 405 | // Start, pause 406 | if ((combo = [[NSUserDefaults standardUserDefaults] valueForKey:@"startPause"]) != nil) { 407 | keyCode = [[combo valueForKey:@"keyCode"] integerValue]; 408 | modifierKeys = [[combo valueForKey:@"modifierFlags"] unsignedIntegerValue]; 409 | 410 | [self.hotKeyCenter registerHotKeyWithKeyCode:keyCode modifierFlags:modifierKeys target:self action:@selector(startTimer) object:nil]; 411 | self.startPauseItem.keyEquivalent = [[PTKeyCodeTranslator currentTranslator] translateKeyCode:keyCode]; 412 | self.startPauseItem.keyEquivalentModifierMask = modifierKeys; 413 | } 414 | 415 | // Restart 416 | if ((combo = [[NSUserDefaults standardUserDefaults] valueForKey:@"restart"]) != nil) { 417 | keyCode = [[combo valueForKey:@"keyCode"] integerValue]; 418 | modifierKeys = [[combo valueForKey:@"modifierFlags"] unsignedIntegerValue]; 419 | 420 | [self.hotKeyCenter registerHotKeyWithKeyCode:keyCode modifierFlags:modifierKeys target:self action:@selector(restartTimer) object:nil]; 421 | self.restartItem.keyEquivalent = [[PTKeyCodeTranslator currentTranslator] translateKeyCode:keyCode]; 422 | self.restartItem.keyEquivalentModifierMask = modifierKeys; 423 | } 424 | 425 | // Finish 426 | if ((combo = [[NSUserDefaults standardUserDefaults] valueForKey:@"finish"]) != nil) { 427 | keyCode = [[combo valueForKey:@"keyCode"] integerValue]; 428 | modifierKeys = [[combo valueForKey:@"modifierFlags"] unsignedIntegerValue]; 429 | 430 | [self.hotKeyCenter registerHotKeyWithKeyCode:keyCode modifierFlags:modifierKeys target:self action:@selector(resetTimer) object:nil]; 431 | self.finishItem.keyEquivalent = [[PTKeyCodeTranslator currentTranslator] translateKeyCode:keyCode]; 432 | self.finishItem.keyEquivalentModifierMask = modifierKeys; 433 | } 434 | } 435 | 436 | #pragma mark Sleep/Wake 437 | 438 | - (void) onSleep: (NSNotification*) note 439 | { 440 | if ([[NSUserDefaults standardUserDefaults] boolForKey:@"pauseOnSleep"]) { 441 | startOnWake = [self.stopwatch isActive]; 442 | [self pauseWithNotification:NO]; 443 | } 444 | } 445 | 446 | - (void) onWake: (NSNotification*) note 447 | { 448 | if ([[NSUserDefaults standardUserDefaults] boolForKey:@"pauseOnSleep"] && startOnWake) { 449 | startOnWake = NO; 450 | [self startWithNotification:NO]; 451 | } 452 | } 453 | 454 | #pragma mark Screensaver 455 | 456 | - (void) onScreensaverStart: (NSNotification*) note 457 | { 458 | if ([[NSUserDefaults standardUserDefaults] boolForKey:@"pauseOnScreensaver"]) { 459 | startOnScreensaverEnd = [self.stopwatch isActive]; 460 | [self pauseWithNotification:NO]; 461 | } 462 | } 463 | 464 | - (void) onScreensaverStop: (NSNotification*) note 465 | { 466 | if ([[NSUserDefaults standardUserDefaults] boolForKey:@"pauseOnScreensaver"] && startOnScreensaverEnd) { 467 | startOnScreensaverEnd = NO; 468 | [self startWithNotification:NO]; 469 | } 470 | } 471 | 472 | #pragma mark NSApplication 473 | 474 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 475 | { 476 | [window close]; 477 | startOnWake = NO; 478 | startOnScreensaverEnd = NO; 479 | 480 | // Setup the hotkey center 481 | DDHotKeyCenter *center = [[DDHotKeyCenter alloc] init]; 482 | self.hotKeyCenter = center; 483 | [center release]; 484 | 485 | // Setup Growl 486 | [GrowlApplicationBridge setGrowlDelegate:self]; 487 | 488 | // Setup user defaults notification 489 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onUserDefaultsChange:) name:NSUserDefaultsDidChangeNotification object:nil]; 490 | 491 | // Setup defaults 492 | NSDictionary *defaults = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"defaults" ofType:@"plist"]]; 493 | 494 | [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; 495 | [[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:defaults]; 496 | 497 | // Listen to sleep/wake 498 | [[[NSWorkspace sharedWorkspace] notificationCenter] 499 | addObserver:self 500 | selector:@selector(onSleep:) 501 | name:NSWorkspaceWillSleepNotification 502 | object:nil]; 503 | 504 | [[[NSWorkspace sharedWorkspace] notificationCenter] 505 | addObserver:self 506 | selector:@selector(onWake:) 507 | name:NSWorkspaceDidWakeNotification 508 | object:nil]; 509 | 510 | // Listen to screensaver 511 | [[NSDistributedNotificationCenter defaultCenter] 512 | addObserver:self 513 | selector:@selector(onScreensaverStart:) 514 | name:@"com.apple.screensaver.didstart" 515 | object:nil]; 516 | 517 | [[NSDistributedNotificationCenter defaultCenter] 518 | addObserver:self 519 | selector:@selector(onScreensaverStop:) 520 | name:@"com.apple.screensaver.didstop" 521 | object:nil]; 522 | 523 | [[NSDistributedNotificationCenter defaultCenter] 524 | addObserver:self 525 | selector:@selector(onScreensaverStart:) 526 | name:@"com.apple.screenIsLocked" 527 | object:nil]; 528 | 529 | [[NSDistributedNotificationCenter defaultCenter] 530 | addObserver:self 531 | selector:@selector(onScreensaverStop:) 532 | name:@"com.apple.screenIsUnlocked" 533 | object:nil]; 534 | 535 | // Create class attributes 536 | NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:20]; 537 | self.sessionsMenuItems = array; 538 | [array release]; 539 | 540 | self.sessionsMenuSeparator = [NSMenuItem separatorItem]; 541 | 542 | NSMenuItem *clearMenuItem = [[NSMenuItem alloc] initWithTitle:@"Clear" action:@selector(clear:) keyEquivalent:@""]; 543 | self.sessionsMenuClearItem = clearMenuItem; 544 | [clearMenuItem release]; 545 | 546 | NSMenuItem *exportMenuItem = [[NSMenuItem alloc] initWithTitle:@"Export..." action:@selector(export) keyEquivalent:@""]; 547 | self.sessionsMenuExportItem = exportMenuItem; 548 | [exportMenuItem release]; 549 | 550 | self.stopwatch = [[Stopwatch alloc] initWithDelegate:self]; 551 | 552 | NSStatusBar *statusBar = [NSStatusBar systemStatusBar]; 553 | self.statusItem = [statusBar statusItemWithLength:46.0]; 554 | [statusItem setHighlightMode:YES]; 555 | [statusItem setMenu:menu]; 556 | 557 | [self updateStatusBar]; 558 | 559 | // Populate data 560 | for (Session *session in [Session allSessions]) { 561 | [self addSessionToMenu:session]; 562 | } 563 | 564 | // Start controller 565 | [self resetWithNotification:NO]; 566 | } 567 | 568 | /** 569 | Returns the support directory for the application, used to store the Core Data 570 | store file. This code uses a directory named "Lol" for 571 | the content, either in the NSApplicationSupportDirectory location or (if the 572 | former cannot be found), the system's temporary directory. 573 | */ 574 | 575 | - (NSString *)applicationSupportDirectory 576 | { 577 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); 578 | NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : NSTemporaryDirectory(); 579 | return [basePath stringByAppendingPathComponent:@"Thyme"]; 580 | } 581 | 582 | 583 | /** 584 | Creates, retains, and returns the managed object model for the application 585 | by merging all of the models found in the application bundle. 586 | */ 587 | 588 | - (NSManagedObjectModel *)managedObjectModel 589 | { 590 | if (managedObjectModel) 591 | return managedObjectModel; 592 | 593 | managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain]; 594 | return managedObjectModel; 595 | } 596 | 597 | 598 | /** 599 | Returns the persistent store coordinator for the application. This 600 | implementation will create and return a coordinator, having added the 601 | store for the application to it. (The directory for the store is created, 602 | if necessary.) 603 | */ 604 | 605 | - (NSPersistentStoreCoordinator *) persistentStoreCoordinator 606 | { 607 | if (persistentStoreCoordinator) 608 | return persistentStoreCoordinator; 609 | 610 | NSManagedObjectModel *mom = [self managedObjectModel]; 611 | 612 | if (!mom) 613 | { 614 | NSAssert(NO, @"Managed object model is nil"); 615 | NSLog(@"%@: No model to generate a store from", [self class]); 616 | return nil; 617 | } 618 | 619 | NSFileManager *fileManager = [NSFileManager defaultManager]; 620 | NSString *applicationSupportDirectory = [self applicationSupportDirectory]; 621 | NSError *error = nil; 622 | 623 | if ( ![fileManager fileExistsAtPath:applicationSupportDirectory isDirectory:NULL] ) 624 | { 625 | if (![fileManager createDirectoryAtPath:applicationSupportDirectory withIntermediateDirectories:NO attributes:nil error:&error]) 626 | { 627 | NSAssert(NO, ([NSString stringWithFormat:@"Failed to create App Support directory %@ : %@", applicationSupportDirectory,error])); 628 | NSLog(@"Error creating application support directory at %@ : %@",applicationSupportDirectory,error); 629 | return nil; 630 | } 631 | } 632 | 633 | NSURL *url = [NSURL fileURLWithPath: [applicationSupportDirectory stringByAppendingPathComponent: @"storedata"]]; 634 | persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: mom]; 635 | 636 | if (![persistentStoreCoordinator addPersistentStoreWithType:NSXMLStoreType 637 | configuration:nil 638 | URL:url 639 | options:nil 640 | error:&error]) 641 | { 642 | [[NSApplication sharedApplication] presentError:error]; 643 | [persistentStoreCoordinator release], persistentStoreCoordinator = nil; 644 | return nil; 645 | } 646 | 647 | return persistentStoreCoordinator; 648 | } 649 | 650 | /** 651 | Returns the managed object context for the application (which is already 652 | bound to the persistent store coordinator for the application.) 653 | */ 654 | 655 | - (NSManagedObjectContext *) managedObjectContext 656 | { 657 | if (managedObjectContext) 658 | return managedObjectContext; 659 | 660 | NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 661 | 662 | if (!coordinator) 663 | { 664 | NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 665 | [dict setValue:@"Failed to initialize the store" forKey:NSLocalizedDescriptionKey]; 666 | [dict setValue:@"There was an error building up the data file." forKey:NSLocalizedFailureReasonErrorKey]; 667 | NSError *error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict]; 668 | [[NSApplication sharedApplication] presentError:error]; 669 | return nil; 670 | } 671 | 672 | managedObjectContext = [[NSManagedObjectContext alloc] init]; 673 | [managedObjectContext setPersistentStoreCoordinator: coordinator]; 674 | 675 | return managedObjectContext; 676 | } 677 | 678 | /** 679 | Returns the NSUndoManager for the application. In this case, the manager 680 | returned is that of the managed object context for the application. 681 | */ 682 | 683 | - (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window 684 | { 685 | return [[self managedObjectContext] undoManager]; 686 | } 687 | 688 | 689 | /** 690 | Performs the save action for the application, which is to send the save: 691 | message to the application's managed object context. Any encountered errors 692 | are presented to the user. 693 | */ 694 | 695 | - (IBAction) saveAction:(id)sender 696 | { 697 | NSError *error = nil; 698 | 699 | if (![[self managedObjectContext] commitEditing]) 700 | { 701 | NSLog(@"%@: unable to commit editing before saving", [self class]); 702 | } 703 | 704 | if (![[self managedObjectContext] save:&error]) 705 | { 706 | [[NSApplication sharedApplication] presentError:error]; 707 | } 708 | } 709 | 710 | 711 | /** 712 | Implementation of the applicationShouldTerminate: method, used here to 713 | handle the saving of changes in the application managed object context 714 | before the application terminates. 715 | */ 716 | 717 | - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender 718 | { 719 | if (!managedObjectContext) 720 | return NSTerminateNow; 721 | 722 | [self.stopwatch stop]; 723 | 724 | if (![managedObjectContext commitEditing]) 725 | { 726 | NSLog(@"%@: unable to commit editing to terminate", [self class]); 727 | return NSTerminateCancel; 728 | } 729 | 730 | if (![managedObjectContext hasChanges]) 731 | return NSTerminateNow; 732 | 733 | NSError *error = nil; 734 | if (![managedObjectContext save:&error]) 735 | { 736 | // This error handling simply presents error information in a panel with an 737 | // "Ok" button, which does not include any attempt at error recovery (meaning, 738 | // attempting to fix the error.) As a result, this implementation will 739 | // present the information to the user and then follow up with a panel asking 740 | // if the user wishes to "Quit Anyway", without saving the changes. 741 | 742 | // Typically, this process should be altered to include application-specific 743 | // recovery steps. 744 | 745 | BOOL result = [sender presentError:error]; 746 | 747 | if (result) 748 | return NSTerminateCancel; 749 | 750 | NSString *question = NSLocalizedString(@"Could not save changes while quitting. Quit anyway?", @"Quit without saves error question message"); 751 | NSString *info = NSLocalizedString(@"Quitting now will lose any changes you have made since the last successful save", @"Quit without saves error question info"); 752 | NSString *quitButton = NSLocalizedString(@"Quit anyway", @"Quit anyway button title"); 753 | NSString *cancelButton = NSLocalizedString(@"Cancel", @"Cancel button title"); 754 | NSAlert *alert = [[NSAlert alloc] init]; 755 | [alert setMessageText:question]; 756 | [alert setInformativeText:info]; 757 | [alert addButtonWithTitle:quitButton]; 758 | [alert addButtonWithTitle:cancelButton]; 759 | 760 | NSInteger answer = [alert runModal]; 761 | [alert release]; 762 | alert = nil; 763 | 764 | if (answer == NSAlertSecondButtonReturn) 765 | return NSTerminateCancel; 766 | } 767 | 768 | return NSTerminateNow; 769 | } 770 | 771 | 772 | /** 773 | Implementation of dealloc, to release the retained variables. 774 | */ 775 | - (void)dealloc 776 | { 777 | [window release]; 778 | [managedObjectContext release]; 779 | [persistentStoreCoordinator release]; 780 | [managedObjectModel release]; 781 | 782 | self.stopwatch = nil; 783 | [statusItem release]; 784 | [hotKeyCenter release]; 785 | 786 | [sessionsMenuSeparator release]; 787 | [sessionsMenuExportItem release]; 788 | [sessionsMenuClearItem release]; 789 | [sessionsMenuItems release]; 790 | 791 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 792 | 793 | [super dealloc]; 794 | } 795 | 796 | @end 797 | -------------------------------------------------------------------------------- /Thyme_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'Tyme' target in the 'Tyme' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /ToggleCommand.h: -------------------------------------------------------------------------------- 1 | // 2 | // ToggleCommand.h 3 | // Thyme 4 | // 5 | // Created by João Moreno on 28/10/12. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface ToggleCommand : NSScriptCommand 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ToggleCommand.m: -------------------------------------------------------------------------------- 1 | // 2 | // ToggleCommand.m 3 | // Thyme 4 | // 5 | // Created by João Moreno on 28/10/12. 6 | // 7 | // 8 | 9 | #import "ToggleCommand.h" 10 | #import "ThymeAppDelegate.h" 11 | 12 | @implementation ToggleCommand 13 | 14 | - (id) performDefaultImplementation { 15 | ThymeAppDelegate *appDelegate = (ThymeAppDelegate *) [[NSApplication sharedApplication] delegate]; 16 | [appDelegate toggleWithNotification:false]; 17 | return nil; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /artwork/logo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaomoreno/thyme/44ac7c0f89707ac97d043d7f3d87f4438190c4ba/artwork/logo.ai -------------------------------------------------------------------------------- /artwork/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaomoreno/thyme/44ac7c0f89707ac97d043d7f3d87f4438190c4ba/artwork/logo.png -------------------------------------------------------------------------------- /defaults.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | pauseOnScreensaver 6 | 7 | pauseOnSleep 8 | 9 | askForTagOnFinish 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /logo.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaomoreno/thyme/44ac7c0f89707ac97d043d7f3d87f4438190c4ba/logo.icns -------------------------------------------------------------------------------- /logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaomoreno/thyme/44ac7c0f89707ac97d043d7f3d87f4438190c4ba/logo_small.png -------------------------------------------------------------------------------- /logo_small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaomoreno/thyme/44ac7c0f89707ac97d043d7f3d87f4438190c4ba/logo_small@2x.png -------------------------------------------------------------------------------- /main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Thyme 4 | // 5 | // Created by João Moreno on 2/8/10. 6 | // 7 | 8 | #import 9 | 10 | int main(int argc, char *argv[]) 11 | { 12 | return NSApplicationMain(argc, (const char **) argv); 13 | } 14 | -------------------------------------------------------------------------------- /scriptSupport.sdef: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | --------------------------------------------------------------------------------