aboutsummaryrefslogtreecommitdiffstats
path: root/Source/SPGrowlController.m
diff options
context:
space:
mode:
authorrowanbeentje <rowan@beent.je>2009-10-03 21:47:55 +0000
committerrowanbeentje <rowan@beent.je>2009-10-03 21:47:55 +0000
commitd794d105545e92a75bdaf697580ee7f12b566531 (patch)
tree18a8cf4bc60380284d28b151eb89357cf0df76bd /Source/SPGrowlController.m
parent7abd1679730327d15a33cc516e84e0e368f53696 (diff)
downloadsequelpro-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.m105
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