diff options
author | rowanbeentje <rowan@beent.je> | 2009-10-03 21:47:55 +0000 |
---|---|---|
committer | rowanbeentje <rowan@beent.je> | 2009-10-03 21:47:55 +0000 |
commit | d794d105545e92a75bdaf697580ee7f12b566531 (patch) | |
tree | 18a8cf4bc60380284d28b151eb89357cf0df76bd /Source/SPGrowlController.m | |
parent | 7abd1679730327d15a33cc516e84e0e368f53696 (diff) | |
download | sequelpro-d794d105545e92a75bdaf697580ee7f12b566531.tar.gz sequelpro-d794d105545e92a75bdaf697580ee7f12b566531.tar.bz2 sequelpro-d794d105545e92a75bdaf697580ee7f12b566531.zip |
Improve Growl interaction to reduce general Growl spammage and improve functionality:
- Growls are now only shown by default if they are not fired from the frontmost window
- Long-running tasks (>3 secs) will still Growl
- Clicking on a Growl will now bring the associated window to the front
This addresses the original concerns of Issue #98.
Diffstat (limited to 'Source/SPGrowlController.m')
-rw-r--r-- | Source/SPGrowlController.m | 105 |
1 files changed, 92 insertions, 13 deletions
diff --git a/Source/SPGrowlController.m b/Source/SPGrowlController.m index 13e1698a..b939d878 100644 --- a/Source/SPGrowlController.m +++ b/Source/SPGrowlController.m @@ -24,6 +24,7 @@ // More info at <http://code.google.com/p/sequel-pro/> #import "SPGrowlController.h" +#include <mach/mach_time.h> static SPGrowlController *sharedGrowlController = nil; @@ -60,6 +61,8 @@ static SPGrowlController *sharedGrowlController = nil; { if ((self = [super init])) { [GrowlApplicationBridge setGrowlDelegate:self]; + timingNotificationName = nil; + timingNotificationStart = 0; } return self; @@ -77,29 +80,68 @@ static SPGrowlController *sharedGrowlController = nil; - (id)autorelease { return self; } -- (void)release { } +- (void)release +{ + if (timingNotificationName) [timingNotificationName release]; +} /** * Posts a Growl notification using the supplied details and default values. + * Calls the notification after a tiny delay to allow isKeyWindow to have updated + * after tasks. */ -- (void)notifyWithTitle:(NSString *)title description:(NSString *)description notificationName:(NSString *)name +- (void)notifyWithTitle:(NSString *)title description:(NSString *)description window:(NSWindow *)window notificationName:(NSString *)name { - [self notifyWithTitle:title - description:description - notificationName:name - iconData:nil - priority:0 - isSticky:NO - clickContext:nil]; + NSMutableDictionary *notificationDictionary = [NSMutableDictionary dictionary]; + [notificationDictionary setObject:title forKey:@"title"]; + [notificationDictionary setObject:description forKey:@"description"]; + [notificationDictionary setObject:window forKey:@"window"]; + [notificationDictionary setObject:name forKey:@"name"]; + [notificationDictionary setObject:[NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:[window windowNumber]] forKey:@"notificationWindow"] forKey:@"clickContext"]; + + [self performSelector:@selector(notifyWithObject:) withObject:notificationDictionary afterDelay:0.1]; } - + +/** + * Posts a Growl notification, using a NSDictionary to contain all arguments. + * Allows calling either with an NSThread or afterDelay as it only accepts a + * single argument. + */ +- (void)notifyWithObject:(NSDictionary *)notificationDictionary +{ + [self notifyWithTitle:[notificationDictionary objectForKey:@"title"] + description:[notificationDictionary objectForKey:@"description"] + window:[notificationDictionary objectForKey:@"window"] + notificationName:[notificationDictionary objectForKey:@"name"] + iconData:nil + priority:0 + isSticky:NO + clickContext:[notificationDictionary objectForKey:@"clickContext"]]; +} + /** * Posts a Growl notification using the supplied details and effectively ignoring the default values. */ -- (void)notifyWithTitle:(NSString *)title description:(NSString *)description notificationName:(NSString *)name iconData:(NSData *)data priority:(int)priority isSticky:(BOOL)sticky clickContext:(id)clickContext +- (void)notifyWithTitle:(NSString *)title description:(NSString *)description window:(NSWindow *)window notificationName:(NSString *)name iconData:(NSData *)data priority:(int)priority isSticky:(BOOL)sticky clickContext:(id)clickContext { - // Post notification only if preference is set - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"GrowlEnabled"]) { + BOOL postNotification = YES; + + // Don't post the notification if the notification window is key and order front + // as that suggests the user is already viewing the notification result. + if ([window isKeyWindow]) postNotification = NO; + + // If a timing notification name exists, check to see if it matches the notification name; + // if it does, and the time exceeds the threshold, display the notification even for + // frontmost windows to provide feedback for long-running tasks. + if (timingNotificationName && [timingNotificationName isEqualToString:name]) { + if ([self milliTime] > (SP_LONGRUNNING_NOTIFICATION_TIME * 1000) + timingNotificationStart) { + postNotification = YES; + } + [timingNotificationName release], timingNotificationName = nil; + } + + // Post notification only if preference is set and visibility has been confirmed + if (postNotification && [[NSUserDefaults standardUserDefaults] boolForKey:@"GrowlEnabled"]) { [GrowlApplicationBridge notifyWithTitle:title description:description notificationName:name @@ -110,4 +152,41 @@ static SPGrowlController *sharedGrowlController = nil; } } +/** + * React to a click on the notification. + */ +- (void) growlNotificationWasClicked:(NSDictionary *)clickContext +{ + if (clickContext && [clickContext objectForKey:@"notificationWindow"]) { + NSWindow *targetWindow = [NSApp windowWithWindowNumber:[[clickContext objectForKey:@"notificationWindow"] integerValue]]; + if (targetWindow) { + [NSApp activateIgnoringOtherApps:YES]; + [targetWindow makeKeyAndOrderFront:self]; + } + } +} + +/** + * Start the notification timer for a specific notification name. Only one notification + * timer can run at once, and tracks the time between this start and the notification + * being posted; if the notification is posted after the header-defined boundary, the + * notification will then be shown even if the app is frontmost. + */ +- (void) setVisibilityForNotificationName:(NSString *)name +{ + if (timingNotificationName) [timingNotificationName release], timingNotificationName = nil; + timingNotificationName = [[NSString alloc] initWithString:name]; + timingNotificationStart = [self milliTime]; +} + +/** + * Get a monotonically increasing time, in milliseconds. + */ +- (double) milliTime +{ + uint64_t currentTime_t = mach_absolute_time(); + Nanoseconds elapsedTime = AbsoluteToNanoseconds(*(AbsoluteTime *)&(currentTime_t)); + + return (((double)UnsignedWideToUInt64(elapsedTime)) * 1e-6); +} @end |