diff --git a/macosx/Controller.h b/macosx/Controller.h index a83f2386e..f08050c6e 100644 --- a/macosx/Controller.h +++ b/macosx/Controller.h @@ -49,7 +49,7 @@ typedef enum ADD_CREATED } addType; -@interface Controller : NSObject +@interface Controller : NSObject { tr_session * fLib; @@ -193,6 +193,8 @@ typedef enum - (void) setBottomCountText: (BOOL) filtering; +- (Torrent *) torrentForHash: (NSString *) hash; + - (void) torrentFinishedDownloading: (NSNotification *) notification; - (void) torrentRestartedDownloading: (NSNotification *) notification; - (void) torrentFinishedSeeding: (NSNotification *) notification; diff --git a/macosx/Controller.m b/macosx/Controller.m index 98502c771..18ea83d0d 100644 --- a/macosx/Controller.m +++ b/macosx/Controller.m @@ -484,6 +484,9 @@ static void sleepCallback(void * controller, io_service_t y, natural_t messageTy fBadger = [[Badger alloc] initWithLib: fLib]; + if ([NSApp isOnMountainLionOrBetter]) + [[NSUserNotificationCenterMtLion defaultUserNotificationCenter] setDelegate: self]; + //observe notifications NSNotificationCenter * nc = [NSNotificationCenter defaultCenter]; @@ -555,6 +558,14 @@ static void sleepCallback(void * controller, io_service_t y, natural_t messageTy [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self andSelector: @selector(handleOpenContentsEvent:replyEvent:) forEventClass: kCoreEventClass andEventID: kAEOpenContents]; + //if we were opened from a user notification, do the corresponding action + if ([NSApp isOnMountainLionOrBetter]) + { + NSUserNotification * launchNotification = [[notification userInfo] objectForKey: NSApplicationLaunchUserNotificationKey]; + if (launchNotification) + [self userNotificationCenter: nil didActivateNotification: launchNotification]; + } + //auto importing [self checkAutoImportDirectory]; @@ -1836,6 +1847,101 @@ static void sleepCallback(void * controller, io_service_t y, natural_t messageTy [fTotalTorrentsField setStringValue: totalTorrentsString]; } +- (BOOL) userNotificationCenter: (NSUserNotificationCenter *) center shouldPresentNotification:(NSUserNotification *) notification +{ + return YES; +} + +- (void) userNotificationCenter: (NSUserNotificationCenter *) center didActivateNotification: (NSUserNotification *) notification +{ + if (![notification userInfo]) + return; + + if ([notification activationType] == NSUserNotificationActivationTypeActionButtonClicked) //reveal + { + Torrent * torrent = [self torrentForHash: [[notification userInfo] objectForKey: @"Hash"]]; + NSString * location = [torrent dataLocation]; + if (!location) + location = [[notification userInfo] objectForKey: @"Location"]; + if (location) + [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: @[[NSURL fileURLWithPath: location]]]; + } + else if ([notification activationType] == NSUserNotificationActivationTypeContentsClicked) + { + Torrent * torrent = [self torrentForHash: [[notification userInfo] objectForKey: @"Hash"]]; + if (torrent) + { + //select in the table - first see if it's already shown + NSInteger row = [fTableView rowForItem: torrent]; + if (row == -1) + { + //if it's not shown, see if it's in a collapsed row + if ([fDefaults boolForKey: @"SortByGroup"]) + { + __block TorrentGroup * parent = nil; + [fDisplayedTorrents enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(TorrentGroup * group, NSUInteger idx, BOOL *stop) { + if ([[group torrents] containsObject: torrent]) + { + parent = group; + *stop = YES; + } + }]; + if (parent) + { + [[fTableView animator] expandItem: parent]; + row = [fTableView rowForItem: torrent]; + } + } + + if (row == -1) + { + //not found - must be filtering + NSAssert([fDefaults boolForKey: @"FilterBar"], @"expected the filter to be enabled"); + [fFilterBar reset: YES]; + + row = [fTableView rowForItem: torrent]; + + //if it's not shown, it has to be in a collapsed row...again + if ([fDefaults boolForKey: @"SortByGroup"]) + { + __block TorrentGroup * parent = nil; + [fDisplayedTorrents enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(TorrentGroup * group, NSUInteger idx, BOOL *stop) { + if ([[group torrents] containsObject: torrent]) + { + parent = group; + *stop = YES; + } + }]; + if (parent) + { + [[fTableView animator] expandItem: parent]; + row = [fTableView rowForItem: torrent]; + } + } + } + } + + NSAssert1(row != -1, @"expected a row to be found for torrent %@", torrent); + [fTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: row] byExtendingSelection:NO]; + } + } +} + +- (Torrent *) torrentForHash: (NSString *) hash +{ + NSParameterAssert(hash != nil); + + __block Torrent * torrent = nil; + [fTorrents enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(id obj, NSUInteger idx, BOOL * stop) { + if ([[(Torrent *)obj hashString] isEqualToString: hash]) + { + torrent = obj; + *stop = YES; + } + }]; + return torrent; +} + - (void) torrentFinishedDownloading: (NSNotification *) notification { Torrent * torrent = [notification object]; @@ -1853,9 +1959,28 @@ static void sleepCallback(void * controller, io_service_t y, natural_t messageTy } } + NSString * location = [torrent dataLocation]; + + if ([NSApp isOnMountainLionOrBetter]) + { + NSUserNotification * notification = [[NSUserNotificationMtLion alloc] init]; + [notification setTitle: NSLocalizedString(@"Download Complete", "notification title")]; + [notification setSubtitle: [torrent name]]; + + [notification setHasActionButton: YES]; + [notification setActionButtonTitle: NSLocalizedString(@"Reveal", "notification button")]; + + NSMutableDictionary * userInfo = [NSMutableDictionary dictionaryWithObject: [torrent hashString] forKey: @"Hash"]; + if (location) + [userInfo setObject: location forKey: @"Location"]; + [notification setUserInfo: userInfo]; + + [[NSUserNotificationCenterMtLion defaultUserNotificationCenter] deliverNotification: notification]; + [notification release]; + } + NSMutableDictionary * clickContext = [NSMutableDictionary dictionaryWithObject: GROWL_DOWNLOAD_COMPLETE forKey: @"Type"]; - NSString * location = [torrent dataLocation]; if (location) [clickContext setObject: location forKey: @"Location"]; @@ -1894,9 +2019,28 @@ static void sleepCallback(void * controller, io_service_t y, natural_t messageTy } } + NSString * location = [torrent dataLocation]; + + if ([NSApp isOnMountainLionOrBetter]) + { + NSUserNotification * notification = [[NSUserNotificationMtLion alloc] init]; + [notification setTitle: NSLocalizedString(@"Seeding Complete", "notification title")]; + [notification setSubtitle: [torrent name]]; + + [notification setHasActionButton: YES]; + [notification setActionButtonTitle: NSLocalizedString(@"Reveal", "notification button")]; + + NSMutableDictionary * userInfo = [NSMutableDictionary dictionaryWithObject: [torrent hashString] forKey: @"Hash"]; + if (location) + [userInfo setObject: location forKey: @"Location"]; + [notification setUserInfo: userInfo]; + + [[NSUserNotificationCenterMtLion defaultUserNotificationCenter] deliverNotification: notification]; + [notification release]; + } + NSMutableDictionary * clickContext = [NSMutableDictionary dictionaryWithObject: GROWL_SEEDING_COMPLETE forKey: @"Type"]; - NSString * location = [torrent dataLocation]; if (location) [clickContext setObject: location forKey: @"Location"]; @@ -2815,6 +2959,18 @@ static void sleepCallback(void * controller, io_service_t y, natural_t messageTy case TR_PARSE_OK: [self openFiles: [NSArray arrayWithObject: fullFile] addType: ADD_AUTO forcePath: nil]; + if ([NSApp isOnMountainLionOrBetter]) + { + NSUserNotification* notification = [[NSUserNotificationMtLion alloc] init]; + [notification setTitle: NSLocalizedString(@"Torrent File Auto Added", "notification title")]; + [notification setSubtitle: file]; + + [notification setHasActionButton: NO]; + + [[NSUserNotificationCenterMtLion defaultUserNotificationCenter] deliverNotification: notification]; + [notification release]; + } + [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Torrent File Auto Added", "Growl notification title") description: file notificationName: GROWL_AUTO_ADD iconData: nil priority: 0 isSticky: NO clickContext: nil]; @@ -3402,11 +3558,7 @@ static void sleepCallback(void * controller, io_service_t y, natural_t messageTy //disable filtering when hiding if (!show) - { - [[NSUserDefaults standardUserDefaults] setObject: FILTER_NONE forKey: @"Filter"]; - [[NSUserDefaults standardUserDefaults] setInteger: GROUP_FILTER_ALL_TAG forKey: @"FilterGroup"]; - [[NSUserDefaults standardUserDefaults] removeObjectForKey: @"FilterSearchString"]; - } + [fFilterBar reset: NO]; [self applyFilter]; //do even if showing to ensure tooltips are updated } @@ -4494,7 +4646,7 @@ static void sleepCallback(void * controller, io_service_t y, natural_t messageTy - (void) growlNotificationWasClicked: (id) clickContext { - if (!clickContext || ![clickContext isKindOfClass: [NSDictionary class]]) + if (![clickContext isKindOfClass: [NSDictionary class]]) return; NSString * type = [clickContext objectForKey: @"Type"], * location; diff --git a/macosx/FilterBarController.h b/macosx/FilterBarController.h index eeef1207f..45d57a329 100644 --- a/macosx/FilterBarController.h +++ b/macosx/FilterBarController.h @@ -54,6 +54,7 @@ - (void) setSearchText: (id) sender; - (void) setSearchType: (id) sender; - (void) setGroupFilter: (id) sender; +- (void) reset: (BOOL) updateUI; - (NSArray *) searchStrings; - (void) focusSearchField; @@ -61,4 +62,5 @@ - (void) setCountAll: (NSUInteger) all active: (NSUInteger) active downloading: (NSUInteger) downloading seeding: (NSUInteger) seeding paused: (NSUInteger) paused; + @end diff --git a/macosx/FilterBarController.m b/macosx/FilterBarController.m index 82fd0ae3f..229769996 100644 --- a/macosx/FilterBarController.m +++ b/macosx/FilterBarController.m @@ -238,6 +238,26 @@ [[NSNotificationCenter defaultCenter] postNotificationName: @"ApplyFilter" object: nil]; } +- (void) reset: (BOOL) updateUI +{ + [[NSUserDefaults standardUserDefaults] setInteger: GROUP_FILTER_ALL_TAG forKey: @"FilterGroup"]; + + if (updateUI) + { + [self updateGroupsButton]; + + [self setFilter: fNoFilterButton]; + + [fSearchField setStringValue: @""]; + [self setSearchText: fSearchField]; + } + else + { + [[NSUserDefaults standardUserDefaults] setObject: FILTER_NONE forKey: @"Filter"]; + [[NSUserDefaults standardUserDefaults] removeObjectForKey: @"FilterSearchString"]; + } +} + - (NSArray *) searchStrings { return [[fSearchField stringValue] betterComponentsSeparatedByCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];