491 lines
16 KiB
Plaintext
491 lines
16 KiB
Plaintext
// This file Copyright © 2011-2022 Transmission authors and contributors.
|
|
// It may be used under the MIT (SPDX: MIT) license.
|
|
// License text can be found in the licenses/ folder.
|
|
|
|
#import "FilterBarController.h"
|
|
#import "FilterButton.h"
|
|
#import "GroupsController.h"
|
|
#import "NSStringAdditions.h"
|
|
|
|
#define FILTER_TYPE_TAG_NAME 401
|
|
#define FILTER_TYPE_TAG_TRACKER 402
|
|
|
|
#define SEARCH_MIN_WIDTH 48.0
|
|
#define SEARCH_MAX_WIDTH 95.0
|
|
|
|
@interface FilterBarController ()
|
|
|
|
@property(nonatomic) IBOutlet FilterButton* fNoFilterButton;
|
|
@property(nonatomic) IBOutlet FilterButton* fActiveFilterButton;
|
|
@property(nonatomic) IBOutlet FilterButton* fDownloadFilterButton;
|
|
@property(nonatomic) IBOutlet FilterButton* fSeedFilterButton;
|
|
@property(nonatomic) IBOutlet FilterButton* fPauseFilterButton;
|
|
@property(nonatomic) IBOutlet FilterButton* fErrorFilterButton;
|
|
|
|
@property(nonatomic) IBOutlet NSSearchField* fSearchField;
|
|
|
|
@property(nonatomic) IBOutlet NSPopUpButton* fGroupsButton;
|
|
|
|
- (void)resizeBar;
|
|
- (void)updateGroupsButton;
|
|
- (void)updateGroups:(NSNotification*)notification;
|
|
|
|
@end
|
|
|
|
@implementation FilterBarController
|
|
|
|
- (instancetype)init
|
|
{
|
|
return (self = [super initWithNibName:@"FilterBar" bundle:nil]);
|
|
}
|
|
|
|
- (void)awakeFromNib
|
|
{
|
|
//localizations
|
|
self.fNoFilterButton.title = NSLocalizedString(@"All", "Filter Bar -> filter button");
|
|
self.fActiveFilterButton.title = NSLocalizedString(@"Active", "Filter Bar -> filter button");
|
|
self.fDownloadFilterButton.title = NSLocalizedString(@"Downloading", "Filter Bar -> filter button");
|
|
self.fSeedFilterButton.title = NSLocalizedString(@"Seeding", "Filter Bar -> filter button");
|
|
self.fPauseFilterButton.title = NSLocalizedString(@"Paused", "Filter Bar -> filter button");
|
|
self.fErrorFilterButton.title = NSLocalizedString(@"Error", "Filter Bar -> filter button");
|
|
|
|
self.fNoFilterButton.cell.backgroundStyle = NSBackgroundStyleRaised;
|
|
self.fActiveFilterButton.cell.backgroundStyle = NSBackgroundStyleRaised;
|
|
self.fDownloadFilterButton.cell.backgroundStyle = NSBackgroundStyleRaised;
|
|
self.fSeedFilterButton.cell.backgroundStyle = NSBackgroundStyleRaised;
|
|
self.fPauseFilterButton.cell.backgroundStyle = NSBackgroundStyleRaised;
|
|
self.fErrorFilterButton.cell.backgroundStyle = NSBackgroundStyleRaised;
|
|
|
|
[self.fSearchField.searchMenuTemplate itemWithTag:FILTER_TYPE_TAG_NAME].title = NSLocalizedString(@"Name", "Filter Bar -> filter menu");
|
|
[self.fSearchField.searchMenuTemplate itemWithTag:FILTER_TYPE_TAG_TRACKER].title = NSLocalizedString(@"Tracker", "Filter Bar -> filter menu");
|
|
|
|
[self.fGroupsButton.menu itemWithTag:GROUP_FILTER_ALL_TAG].title = NSLocalizedString(@"All Groups", "Filter Bar -> group filter menu");
|
|
|
|
[self resizeBar];
|
|
|
|
//set current filter
|
|
NSString* filterType = [NSUserDefaults.standardUserDefaults stringForKey:@"Filter"];
|
|
|
|
NSButton* currentFilterButton;
|
|
if ([filterType isEqualToString:FILTER_ACTIVE])
|
|
{
|
|
currentFilterButton = self.fActiveFilterButton;
|
|
}
|
|
else if ([filterType isEqualToString:FILTER_PAUSE])
|
|
{
|
|
currentFilterButton = self.fPauseFilterButton;
|
|
}
|
|
else if ([filterType isEqualToString:FILTER_SEED])
|
|
{
|
|
currentFilterButton = self.fSeedFilterButton;
|
|
}
|
|
else if ([filterType isEqualToString:FILTER_DOWNLOAD])
|
|
{
|
|
currentFilterButton = self.fDownloadFilterButton;
|
|
}
|
|
else if ([filterType isEqualToString:FILTER_ERROR])
|
|
{
|
|
currentFilterButton = self.fErrorFilterButton;
|
|
}
|
|
else
|
|
{
|
|
//safety
|
|
if (![filterType isEqualToString:FILTER_NONE])
|
|
{
|
|
[NSUserDefaults.standardUserDefaults setObject:FILTER_NONE forKey:@"Filter"];
|
|
}
|
|
currentFilterButton = self.fNoFilterButton;
|
|
}
|
|
currentFilterButton.state = NSControlStateValueOn;
|
|
|
|
//set filter search type
|
|
NSString* filterSearchType = [NSUserDefaults.standardUserDefaults stringForKey:@"FilterSearchType"];
|
|
|
|
NSMenu* filterSearchMenu = self.fSearchField.searchMenuTemplate;
|
|
NSString* filterSearchTypeTitle;
|
|
if ([filterSearchType isEqualToString:FILTER_TYPE_TRACKER])
|
|
{
|
|
filterSearchTypeTitle = [filterSearchMenu itemWithTag:FILTER_TYPE_TAG_TRACKER].title;
|
|
}
|
|
else
|
|
{
|
|
//safety
|
|
if (![filterType isEqualToString:FILTER_TYPE_NAME])
|
|
{
|
|
[NSUserDefaults.standardUserDefaults setObject:FILTER_TYPE_NAME forKey:@"FilterSearchType"];
|
|
}
|
|
filterSearchTypeTitle = [filterSearchMenu itemWithTag:FILTER_TYPE_TAG_NAME].title;
|
|
}
|
|
self.fSearchField.placeholderString = filterSearchTypeTitle;
|
|
|
|
NSString* searchString;
|
|
if ((searchString = [NSUserDefaults.standardUserDefaults stringForKey:@"FilterSearchString"]))
|
|
{
|
|
self.fSearchField.stringValue = searchString;
|
|
}
|
|
|
|
[self updateGroupsButton];
|
|
|
|
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(resizeBar) name:NSWindowDidResizeNotification
|
|
object:self.view.window];
|
|
|
|
//update when groups change
|
|
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(updateGroups:) name:@"UpdateGroups" object:nil];
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[NSNotificationCenter.defaultCenter removeObserver:self];
|
|
}
|
|
|
|
- (void)setFilter:(id)sender
|
|
{
|
|
NSString* oldFilterType = [NSUserDefaults.standardUserDefaults stringForKey:@"Filter"];
|
|
|
|
NSButton* prevFilterButton;
|
|
if ([oldFilterType isEqualToString:FILTER_PAUSE])
|
|
{
|
|
prevFilterButton = self.fPauseFilterButton;
|
|
}
|
|
else if ([oldFilterType isEqualToString:FILTER_ACTIVE])
|
|
{
|
|
prevFilterButton = self.fActiveFilterButton;
|
|
}
|
|
else if ([oldFilterType isEqualToString:FILTER_SEED])
|
|
{
|
|
prevFilterButton = self.fSeedFilterButton;
|
|
}
|
|
else if ([oldFilterType isEqualToString:FILTER_DOWNLOAD])
|
|
{
|
|
prevFilterButton = self.fDownloadFilterButton;
|
|
}
|
|
else if ([oldFilterType isEqualToString:FILTER_ERROR])
|
|
{
|
|
prevFilterButton = self.fErrorFilterButton;
|
|
}
|
|
else
|
|
{
|
|
prevFilterButton = self.fNoFilterButton;
|
|
}
|
|
|
|
if (sender != prevFilterButton)
|
|
{
|
|
prevFilterButton.state = NSControlStateValueOff;
|
|
[sender setState:NSControlStateValueOn];
|
|
|
|
NSString* filterType;
|
|
if (sender == self.fActiveFilterButton)
|
|
{
|
|
filterType = FILTER_ACTIVE;
|
|
}
|
|
else if (sender == self.fDownloadFilterButton)
|
|
{
|
|
filterType = FILTER_DOWNLOAD;
|
|
}
|
|
else if (sender == self.fPauseFilterButton)
|
|
{
|
|
filterType = FILTER_PAUSE;
|
|
}
|
|
else if (sender == self.fSeedFilterButton)
|
|
{
|
|
filterType = FILTER_SEED;
|
|
}
|
|
else if (sender == self.fErrorFilterButton)
|
|
{
|
|
filterType = FILTER_ERROR;
|
|
}
|
|
else
|
|
{
|
|
filterType = FILTER_NONE;
|
|
}
|
|
|
|
[NSUserDefaults.standardUserDefaults setObject:filterType forKey:@"Filter"];
|
|
}
|
|
else
|
|
{
|
|
[sender setState:NSControlStateValueOn];
|
|
}
|
|
|
|
[NSNotificationCenter.defaultCenter postNotificationName:@"ApplyFilter" object:nil];
|
|
}
|
|
|
|
- (void)switchFilter:(BOOL)right
|
|
{
|
|
NSString* filterType = [NSUserDefaults.standardUserDefaults stringForKey:@"Filter"];
|
|
|
|
NSButton* button;
|
|
if ([filterType isEqualToString:FILTER_NONE])
|
|
{
|
|
button = right ? self.fActiveFilterButton : self.fErrorFilterButton;
|
|
}
|
|
else if ([filterType isEqualToString:FILTER_ACTIVE])
|
|
{
|
|
button = right ? self.fDownloadFilterButton : self.fNoFilterButton;
|
|
}
|
|
else if ([filterType isEqualToString:FILTER_DOWNLOAD])
|
|
{
|
|
button = right ? self.fSeedFilterButton : self.fActiveFilterButton;
|
|
}
|
|
else if ([filterType isEqualToString:FILTER_SEED])
|
|
{
|
|
button = right ? self.fPauseFilterButton : self.fDownloadFilterButton;
|
|
}
|
|
else if ([filterType isEqualToString:FILTER_PAUSE])
|
|
{
|
|
button = right ? self.fErrorFilterButton : self.fSeedFilterButton;
|
|
}
|
|
else if ([filterType isEqualToString:FILTER_ERROR])
|
|
{
|
|
button = right ? self.fNoFilterButton : self.fPauseFilterButton;
|
|
}
|
|
else
|
|
{
|
|
button = self.fNoFilterButton;
|
|
}
|
|
|
|
[self setFilter:button];
|
|
}
|
|
|
|
- (void)setSearchText:(id)sender
|
|
{
|
|
[NSUserDefaults.standardUserDefaults setObject:self.fSearchField.stringValue forKey:@"FilterSearchString"];
|
|
[NSNotificationCenter.defaultCenter postNotificationName:@"ApplyFilter" object:nil];
|
|
}
|
|
|
|
- (void)focusSearchField
|
|
{
|
|
[self.view.window makeFirstResponder:self.fSearchField];
|
|
}
|
|
|
|
- (void)setSearchType:(id)sender
|
|
{
|
|
NSString* oldFilterType = [NSUserDefaults.standardUserDefaults stringForKey:@"FilterSearchType"];
|
|
|
|
NSInteger prevTag, currentTag = [sender tag];
|
|
if ([oldFilterType isEqualToString:FILTER_TYPE_TRACKER])
|
|
{
|
|
prevTag = FILTER_TYPE_TAG_TRACKER;
|
|
}
|
|
else
|
|
{
|
|
prevTag = FILTER_TYPE_TAG_NAME;
|
|
}
|
|
|
|
if (currentTag != prevTag)
|
|
{
|
|
NSString* filterType;
|
|
if (currentTag == FILTER_TYPE_TAG_TRACKER)
|
|
{
|
|
filterType = FILTER_TYPE_TRACKER;
|
|
}
|
|
else
|
|
{
|
|
filterType = FILTER_TYPE_NAME;
|
|
}
|
|
|
|
[NSUserDefaults.standardUserDefaults setObject:filterType forKey:@"FilterSearchType"];
|
|
|
|
self.fSearchField.placeholderString = [sender title];
|
|
}
|
|
|
|
[NSNotificationCenter.defaultCenter postNotificationName:@"ApplyFilter" object:nil];
|
|
}
|
|
|
|
- (void)setGroupFilter:(id)sender
|
|
{
|
|
[NSUserDefaults.standardUserDefaults setInteger:[sender tag] forKey:@"FilterGroup"];
|
|
[self updateGroupsButton];
|
|
|
|
[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:self.fNoFilterButton];
|
|
|
|
self.fSearchField.stringValue = @"";
|
|
[self setSearchText:self.fSearchField];
|
|
}
|
|
else
|
|
{
|
|
[NSUserDefaults.standardUserDefaults setObject:FILTER_NONE forKey:@"Filter"];
|
|
[NSUserDefaults.standardUserDefaults removeObjectForKey:@"FilterSearchString"];
|
|
}
|
|
}
|
|
|
|
- (NSArray*)searchStrings
|
|
{
|
|
return [self.fSearchField.stringValue betterComponentsSeparatedByCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
|
|
}
|
|
|
|
- (void)setCountAll:(NSUInteger)all
|
|
active:(NSUInteger)active
|
|
downloading:(NSUInteger)downloading
|
|
seeding:(NSUInteger)seeding
|
|
paused:(NSUInteger)paused
|
|
error:(NSUInteger)error
|
|
{
|
|
self.fNoFilterButton.count = all;
|
|
self.fActiveFilterButton.count = active;
|
|
self.fDownloadFilterButton.count = downloading;
|
|
self.fSeedFilterButton.count = seeding;
|
|
self.fPauseFilterButton.count = paused;
|
|
self.fErrorFilterButton.count = error;
|
|
}
|
|
|
|
- (void)menuNeedsUpdate:(NSMenu*)menu
|
|
{
|
|
if (menu == self.fGroupsButton.menu)
|
|
{
|
|
for (NSInteger i = menu.numberOfItems - 1; i >= 3; i--)
|
|
{
|
|
[menu removeItemAtIndex:i];
|
|
}
|
|
|
|
NSMenu* groupMenu = [GroupsController.groups groupMenuWithTarget:self action:@selector(setGroupFilter:) isSmall:YES];
|
|
|
|
NSInteger const groupMenuCount = groupMenu.numberOfItems;
|
|
for (NSInteger i = 0; i < groupMenuCount; i++)
|
|
{
|
|
NSMenuItem* item = [groupMenu itemAtIndex:0];
|
|
[groupMenu removeItemAtIndex:0];
|
|
[menu addItem:item];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (BOOL)validateMenuItem:(NSMenuItem*)menuItem
|
|
{
|
|
SEL const action = menuItem.action;
|
|
|
|
//check proper filter search item
|
|
if (action == @selector(setSearchType:))
|
|
{
|
|
NSString* filterType = [NSUserDefaults.standardUserDefaults stringForKey:@"FilterSearchType"];
|
|
|
|
BOOL state;
|
|
if (menuItem.tag == FILTER_TYPE_TAG_TRACKER)
|
|
{
|
|
state = [filterType isEqualToString:FILTER_TYPE_TRACKER];
|
|
}
|
|
else
|
|
{
|
|
state = [filterType isEqualToString:FILTER_TYPE_NAME];
|
|
}
|
|
|
|
menuItem.state = state ? NSControlStateValueOn : NSControlStateValueOff;
|
|
return YES;
|
|
}
|
|
|
|
if (action == @selector(setGroupFilter:))
|
|
{
|
|
menuItem.state = menuItem.tag == [NSUserDefaults.standardUserDefaults integerForKey:@"FilterGroup"] ? NSControlStateValueOn :
|
|
NSControlStateValueOff;
|
|
return YES;
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
#pragma mark - Private
|
|
|
|
- (void)resizeBar
|
|
{
|
|
//replace all buttons
|
|
[self.fNoFilterButton sizeToFit];
|
|
[self.fActiveFilterButton sizeToFit];
|
|
[self.fDownloadFilterButton sizeToFit];
|
|
[self.fSeedFilterButton sizeToFit];
|
|
[self.fPauseFilterButton sizeToFit];
|
|
[self.fErrorFilterButton sizeToFit];
|
|
|
|
NSRect allRect = self.fNoFilterButton.frame;
|
|
NSRect activeRect = self.fActiveFilterButton.frame;
|
|
NSRect downloadRect = self.fDownloadFilterButton.frame;
|
|
NSRect seedRect = self.fSeedFilterButton.frame;
|
|
NSRect pauseRect = self.fPauseFilterButton.frame;
|
|
NSRect errorRect = self.fErrorFilterButton.frame;
|
|
|
|
//size search filter to not overlap buttons
|
|
NSRect searchFrame = self.fSearchField.frame;
|
|
searchFrame.origin.x = NSMaxX(errorRect) + 5.0;
|
|
searchFrame.size.width = NSWidth(self.view.frame) - searchFrame.origin.x - 5.0;
|
|
|
|
//make sure it is not too long
|
|
if (NSWidth(searchFrame) > SEARCH_MAX_WIDTH)
|
|
{
|
|
searchFrame.origin.x += NSWidth(searchFrame) - SEARCH_MAX_WIDTH;
|
|
searchFrame.size.width = SEARCH_MAX_WIDTH;
|
|
}
|
|
else if (NSWidth(searchFrame) < SEARCH_MIN_WIDTH)
|
|
{
|
|
searchFrame.origin.x += NSWidth(searchFrame) - SEARCH_MIN_WIDTH;
|
|
searchFrame.size.width = SEARCH_MIN_WIDTH;
|
|
|
|
//calculate width the buttons can take up
|
|
CGFloat const allowedWidth = (searchFrame.origin.x - 5.0) - allRect.origin.x;
|
|
CGFloat const currentWidth = NSWidth(allRect) + NSWidth(activeRect) + NSWidth(downloadRect) + NSWidth(seedRect) +
|
|
NSWidth(pauseRect) + NSWidth(errorRect) + 4.0; //add 4 for space between buttons
|
|
CGFloat const ratio = allowedWidth / currentWidth;
|
|
|
|
//decrease button widths proportionally
|
|
allRect.size.width = NSWidth(allRect) * ratio;
|
|
activeRect.size.width = NSWidth(activeRect) * ratio;
|
|
downloadRect.size.width = NSWidth(downloadRect) * ratio;
|
|
seedRect.size.width = NSWidth(seedRect) * ratio;
|
|
pauseRect.size.width = NSWidth(pauseRect) * ratio;
|
|
errorRect.size.width = NSWidth(errorRect) * ratio;
|
|
}
|
|
|
|
activeRect.origin.x = NSMaxX(allRect) + 1.0;
|
|
downloadRect.origin.x = NSMaxX(activeRect) + 1.0;
|
|
seedRect.origin.x = NSMaxX(downloadRect) + 1.0;
|
|
pauseRect.origin.x = NSMaxX(seedRect) + 1.0;
|
|
errorRect.origin.x = NSMaxX(pauseRect) + 1.0;
|
|
|
|
self.fNoFilterButton.frame = allRect;
|
|
self.fActiveFilterButton.frame = activeRect;
|
|
self.fDownloadFilterButton.frame = downloadRect;
|
|
self.fSeedFilterButton.frame = seedRect;
|
|
self.fPauseFilterButton.frame = pauseRect;
|
|
self.fErrorFilterButton.frame = errorRect;
|
|
|
|
self.fSearchField.frame = searchFrame;
|
|
}
|
|
|
|
- (void)updateGroupsButton
|
|
{
|
|
NSInteger const groupIndex = [NSUserDefaults.standardUserDefaults integerForKey:@"FilterGroup"];
|
|
|
|
NSImage* icon;
|
|
NSString* toolTip;
|
|
if (groupIndex == GROUP_FILTER_ALL_TAG)
|
|
{
|
|
icon = [NSImage imageNamed:@"PinTemplate"];
|
|
toolTip = NSLocalizedString(@"All Groups", "Groups -> Button");
|
|
}
|
|
else
|
|
{
|
|
icon = [GroupsController.groups imageForIndex:groupIndex];
|
|
NSString* groupName = groupIndex != -1 ? [GroupsController.groups nameForIndex:groupIndex] :
|
|
NSLocalizedString(@"None", "Groups -> Button");
|
|
toolTip = [NSLocalizedString(@"Group", "Groups -> Button") stringByAppendingFormat:@": %@", groupName];
|
|
}
|
|
|
|
[self.fGroupsButton.menu itemAtIndex:0].image = icon;
|
|
self.fGroupsButton.toolTip = toolTip;
|
|
}
|
|
|
|
- (void)updateGroups:(NSNotification*)notification
|
|
{
|
|
[self updateGroupsButton];
|
|
}
|
|
|
|
@end
|