1/******************************************************************************
2 * Copyright (c) 2011-2012 Transmission authors and contributors
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
21 *****************************************************************************/
22
23#import "FilterBarController.h"
24#import "FilterButton.h"
25#import "GroupsController.h"
26#import "NSStringAdditions.h"
27
28#define FILTER_TYPE_TAG_NAME    401
29#define FILTER_TYPE_TAG_TRACKER 402
30
31#define SEARCH_MIN_WIDTH 48.0
32#define SEARCH_MAX_WIDTH 95.0
33
34@interface FilterBarController (Private)
35
36- (void) resizeBar;
37- (void) updateGroupsButton;
38- (void) updateGroups: (NSNotification *) notification;
39
40@end
41
42@implementation FilterBarController
43
44- (id) init
45{
46    return (self = [super initWithNibName: @"FilterBar" bundle: nil]);
47}
48
49- (void) awakeFromNib
50{
51    //localizations
52    [fNoFilterButton setTitle: NSLocalizedString(@"All", "Filter Bar -> filter button")];
53    [fActiveFilterButton setTitle: NSLocalizedString(@"Active", "Filter Bar -> filter button")];
54    [fDownloadFilterButton setTitle: NSLocalizedString(@"Downloading", "Filter Bar -> filter button")];
55    [fSeedFilterButton setTitle: NSLocalizedString(@"Seeding", "Filter Bar -> filter button")];
56    [fPauseFilterButton setTitle: NSLocalizedString(@"Paused", "Filter Bar -> filter button")];
57
58    [[fNoFilterButton cell] setBackgroundStyle: NSBackgroundStyleRaised];
59    [[fActiveFilterButton cell] setBackgroundStyle: NSBackgroundStyleRaised];
60    [[fDownloadFilterButton cell] setBackgroundStyle: NSBackgroundStyleRaised];
61    [[fSeedFilterButton cell] setBackgroundStyle: NSBackgroundStyleRaised];
62    [[fPauseFilterButton cell] setBackgroundStyle: NSBackgroundStyleRaised];
63
64    [[[[fSearchField cell] searchMenuTemplate] itemWithTag: FILTER_TYPE_TAG_NAME] setTitle:
65        NSLocalizedString(@"Name", "Filter Bar -> filter menu")];
66    [[[[fSearchField cell] searchMenuTemplate] itemWithTag: FILTER_TYPE_TAG_TRACKER] setTitle:
67        NSLocalizedString(@"Tracker", "Filter Bar -> filter menu")];
68
69    [[[fGroupsButton menu] itemWithTag: GROUP_FILTER_ALL_TAG] setTitle:
70        NSLocalizedString(@"All Groups", "Filter Bar -> group filter menu")];
71
72    [self resizeBar];
73
74    //set current filter
75    NSString * filterType = [[NSUserDefaults standardUserDefaults] stringForKey: @"Filter"];
76
77    NSButton * currentFilterButton;
78    if ([filterType isEqualToString: FILTER_ACTIVE])
79        currentFilterButton = fActiveFilterButton;
80    else if ([filterType isEqualToString: FILTER_PAUSE])
81        currentFilterButton = fPauseFilterButton;
82    else if ([filterType isEqualToString: FILTER_SEED])
83        currentFilterButton = fSeedFilterButton;
84    else if ([filterType isEqualToString: FILTER_DOWNLOAD])
85        currentFilterButton = fDownloadFilterButton;
86    else
87    {
88        //safety
89        if (![filterType isEqualToString: FILTER_NONE])
90            [[NSUserDefaults standardUserDefaults] setObject: FILTER_NONE forKey: @"Filter"];
91        currentFilterButton = fNoFilterButton;
92    }
93    [currentFilterButton setState: NSOnState];
94
95    //set filter search type
96    NSString * filterSearchType = [[NSUserDefaults standardUserDefaults] stringForKey: @"FilterSearchType"];
97
98    NSMenu * filterSearchMenu = [[fSearchField cell] searchMenuTemplate];
99    NSString * filterSearchTypeTitle;
100    if ([filterSearchType isEqualToString: FILTER_TYPE_TRACKER])
101        filterSearchTypeTitle = [[filterSearchMenu itemWithTag: FILTER_TYPE_TAG_TRACKER] title];
102    else
103    {
104        //safety
105        if (![filterType isEqualToString: FILTER_TYPE_NAME])
106            [[NSUserDefaults standardUserDefaults] setObject: FILTER_TYPE_NAME forKey: @"FilterSearchType"];
107        filterSearchTypeTitle = [[filterSearchMenu itemWithTag: FILTER_TYPE_TAG_NAME] title];
108    }
109    [[fSearchField cell] setPlaceholderString: filterSearchTypeTitle];
110
111    NSString * searchString;
112    if ((searchString = [[NSUserDefaults standardUserDefaults] stringForKey: @"FilterSearchString"]))
113        [fSearchField setStringValue: searchString];
114
115    [self updateGroupsButton];
116
117    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(resizeBar)
118        name: NSWindowDidResizeNotification object: [[self view] window]];
119
120    //update when groups change
121    [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateGroups:)
122        name: @"UpdateGroups" object: nil];
123}
124
125- (void) dealloc
126{
127    [[NSNotificationCenter defaultCenter] removeObserver: self];
128}
129
130- (void) setFilter: (id) sender
131{
132    NSString * oldFilterType = [[NSUserDefaults standardUserDefaults] stringForKey: @"Filter"];
133
134    NSButton * prevFilterButton;
135    if ([oldFilterType isEqualToString: FILTER_PAUSE])
136        prevFilterButton = fPauseFilterButton;
137    else if ([oldFilterType isEqualToString: FILTER_ACTIVE])
138        prevFilterButton = fActiveFilterButton;
139    else if ([oldFilterType isEqualToString: FILTER_SEED])
140        prevFilterButton = fSeedFilterButton;
141    else if ([oldFilterType isEqualToString: FILTER_DOWNLOAD])
142        prevFilterButton = fDownloadFilterButton;
143    else
144        prevFilterButton = fNoFilterButton;
145
146    if (sender != prevFilterButton)
147    {
148        [prevFilterButton setState: NSOffState];
149        [sender setState: NSOnState];
150
151        NSString * filterType;
152        if (sender == fActiveFilterButton)
153            filterType = FILTER_ACTIVE;
154        else if (sender == fDownloadFilterButton)
155            filterType = FILTER_DOWNLOAD;
156        else if (sender == fPauseFilterButton)
157            filterType = FILTER_PAUSE;
158        else if (sender == fSeedFilterButton)
159            filterType = FILTER_SEED;
160        else
161            filterType = FILTER_NONE;
162
163        [[NSUserDefaults standardUserDefaults] setObject: filterType forKey: @"Filter"];
164    }
165    else
166        [sender setState: NSOnState];
167
168    [[NSNotificationCenter defaultCenter] postNotificationName: @"ApplyFilter" object: nil];
169}
170
171- (void) switchFilter: (BOOL) right
172{
173    NSString * filterType = [[NSUserDefaults standardUserDefaults] stringForKey: @"Filter"];
174
175    NSButton * button;
176    if ([filterType isEqualToString: FILTER_NONE])
177        button = right ? fActiveFilterButton : fPauseFilterButton;
178    else if ([filterType isEqualToString: FILTER_ACTIVE])
179        button = right ? fDownloadFilterButton : fNoFilterButton;
180    else if ([filterType isEqualToString: FILTER_DOWNLOAD])
181        button = right ? fSeedFilterButton : fActiveFilterButton;
182    else if ([filterType isEqualToString: FILTER_SEED])
183        button = right ? fPauseFilterButton : fDownloadFilterButton;
184    else if ([filterType isEqualToString: FILTER_PAUSE])
185        button = right ? fNoFilterButton : fSeedFilterButton;
186    else
187        button = fNoFilterButton;
188
189    [self setFilter: button];
190}
191
192- (void) setSearchText: (id) sender
193{
194    [[NSUserDefaults standardUserDefaults] setObject: [fSearchField stringValue] forKey: @"FilterSearchString"];
195    [[NSNotificationCenter defaultCenter] postNotificationName: @"ApplyFilter" object: nil];
196}
197
198- (void) focusSearchField
199{
200    [[[self view] window] makeFirstResponder: fSearchField];
201}
202
203- (void) setSearchType: (id) sender
204{
205    NSString * oldFilterType = [[NSUserDefaults standardUserDefaults] stringForKey: @"FilterSearchType"];
206
207    NSInteger prevTag, currentTag = [sender tag];
208    if ([oldFilterType isEqualToString: FILTER_TYPE_TRACKER])
209        prevTag = FILTER_TYPE_TAG_TRACKER;
210    else
211        prevTag = FILTER_TYPE_TAG_NAME;
212
213    if (currentTag != prevTag)
214    {
215        NSString * filterType;
216        if (currentTag == FILTER_TYPE_TAG_TRACKER)
217            filterType = FILTER_TYPE_TRACKER;
218        else
219            filterType = FILTER_TYPE_NAME;
220
221        [[NSUserDefaults standardUserDefaults] setObject: filterType forKey: @"FilterSearchType"];
222
223        [[fSearchField cell] setPlaceholderString: [sender title]];
224    }
225
226    [[NSNotificationCenter defaultCenter] postNotificationName: @"ApplyFilter" object: nil];
227}
228
229- (void) setGroupFilter: (id) sender
230{
231    [[NSUserDefaults standardUserDefaults] setInteger: [sender tag] forKey: @"FilterGroup"];
232    [self updateGroupsButton];
233
234    [[NSNotificationCenter defaultCenter] postNotificationName: @"ApplyFilter" object: nil];
235}
236
237- (void) reset: (BOOL) updateUI
238{
239    [[NSUserDefaults standardUserDefaults] setInteger: GROUP_FILTER_ALL_TAG forKey: @"FilterGroup"];
240
241    if (updateUI)
242    {
243        [self updateGroupsButton];
244
245        [self setFilter: fNoFilterButton];
246
247        [fSearchField setStringValue: @""];
248        [self setSearchText: fSearchField];
249    }
250    else
251    {
252        [[NSUserDefaults standardUserDefaults] setObject: FILTER_NONE forKey: @"Filter"];
253        [[NSUserDefaults standardUserDefaults] removeObjectForKey: @"FilterSearchString"];
254    }
255}
256
257- (NSArray *) searchStrings
258{
259    return [[fSearchField stringValue] betterComponentsSeparatedByCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
260}
261
262- (void) setCountAll: (NSUInteger) all active: (NSUInteger) active downloading: (NSUInteger) downloading
263        seeding: (NSUInteger) seeding paused: (NSUInteger) paused
264{
265    [fNoFilterButton setCount: all];
266    [fActiveFilterButton setCount: active];
267    [fDownloadFilterButton setCount: downloading];
268    [fSeedFilterButton setCount: seeding];
269    [fPauseFilterButton setCount: paused];
270}
271
272- (void) menuNeedsUpdate: (NSMenu *) menu
273{
274    if (menu == [fGroupsButton menu])
275    {
276        for (NSInteger i = [menu numberOfItems]-1; i >= 3; i--)
277            [menu removeItemAtIndex: i];
278
279        NSMenu * groupMenu = [[GroupsController groups] groupMenuWithTarget: self action: @selector(setGroupFilter:) isSmall: YES];
280
281        const NSInteger groupMenuCount = [groupMenu numberOfItems];
282        for (NSInteger i = 0; i < groupMenuCount; i++)
283        {
284            NSMenuItem * item = [groupMenu itemAtIndex: 0];
285            [groupMenu removeItemAtIndex: 0];
286            [menu addItem: item];
287        }
288    }
289}
290
291- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
292{
293    const SEL action = [menuItem action];
294
295    //check proper filter search item
296    if (action == @selector(setSearchType:))
297    {
298        NSString * filterType = [[NSUserDefaults standardUserDefaults] stringForKey: @"FilterSearchType"];
299
300        BOOL state;
301        if ([menuItem tag] == FILTER_TYPE_TAG_TRACKER)
302            state = [filterType isEqualToString: FILTER_TYPE_TRACKER];
303        else
304            state = [filterType isEqualToString: FILTER_TYPE_NAME];
305
306        [menuItem setState: state ? NSOnState : NSOffState];
307        return YES;
308    }
309
310    if (action == @selector(setGroupFilter:))
311    {
312        [menuItem setState: [menuItem tag] == [[NSUserDefaults standardUserDefaults] integerForKey: @"FilterGroup"]
313                                                ? NSOnState : NSOffState];
314        return YES;
315    }
316
317    return YES;
318}
319
320@end
321
322@implementation FilterBarController (Private)
323
324- (void) resizeBar
325{
326    //replace all buttons
327    [fNoFilterButton sizeToFit];
328    [fActiveFilterButton sizeToFit];
329    [fDownloadFilterButton sizeToFit];
330    [fSeedFilterButton sizeToFit];
331    [fPauseFilterButton sizeToFit];
332
333    NSRect allRect = [fNoFilterButton frame];
334    NSRect activeRect = [fActiveFilterButton frame];
335    NSRect downloadRect = [fDownloadFilterButton frame];
336    NSRect seedRect = [fSeedFilterButton frame];
337    NSRect pauseRect = [fPauseFilterButton frame];
338
339    //size search filter to not overlap buttons
340    NSRect searchFrame = [fSearchField frame];
341    searchFrame.origin.x = NSMaxX(pauseRect) + 5.0;
342    searchFrame.size.width = NSWidth([[self view] frame]) - searchFrame.origin.x - 5.0;
343
344    //make sure it is not too long
345    if (NSWidth(searchFrame) > SEARCH_MAX_WIDTH)
346    {
347        searchFrame.origin.x += NSWidth(searchFrame) - SEARCH_MAX_WIDTH;
348        searchFrame.size.width = SEARCH_MAX_WIDTH;
349    }
350    else if (NSWidth(searchFrame) < SEARCH_MIN_WIDTH)
351    {
352        searchFrame.origin.x += NSWidth(searchFrame) - SEARCH_MIN_WIDTH;
353        searchFrame.size.width = SEARCH_MIN_WIDTH;
354
355        //calculate width the buttons can take up
356        const CGFloat allowedWidth = (searchFrame.origin.x - 5.0) - allRect.origin.x;
357        const CGFloat currentWidth = NSWidth(allRect) + NSWidth(activeRect) + NSWidth(downloadRect) + NSWidth(seedRect)
358                                        + NSWidth(pauseRect) + 4.0; //add 4 for space between buttons
359        const CGFloat ratio = allowedWidth / currentWidth;
360
361        //decrease button widths proportionally
362        allRect.size.width  = NSWidth(allRect) * ratio;
363        activeRect.size.width = NSWidth(activeRect) * ratio;
364        downloadRect.size.width = NSWidth(downloadRect) * ratio;
365        seedRect.size.width = NSWidth(seedRect) * ratio;
366        pauseRect.size.width = NSWidth(pauseRect) * ratio;
367    }
368    else;
369
370    activeRect.origin.x = NSMaxX(allRect) + 1.0;
371    downloadRect.origin.x = NSMaxX(activeRect) + 1.0;
372    seedRect.origin.x = NSMaxX(downloadRect) + 1.0;
373    pauseRect.origin.x = NSMaxX(seedRect) + 1.0;
374
375    [fNoFilterButton setFrame: allRect];
376    [fActiveFilterButton setFrame: activeRect];
377    [fDownloadFilterButton setFrame: downloadRect];
378    [fSeedFilterButton setFrame: seedRect];
379    [fPauseFilterButton setFrame: pauseRect];
380
381    [fSearchField setFrame: searchFrame];
382}
383
384- (void) updateGroupsButton
385{
386    const NSInteger groupIndex = [[NSUserDefaults standardUserDefaults] integerForKey: @"FilterGroup"];
387
388    NSImage * icon;
389    NSString * toolTip;
390    if (groupIndex == GROUP_FILTER_ALL_TAG)
391    {
392        icon = [NSImage imageNamed: @"PinTemplate"];
393        toolTip = NSLocalizedString(@"All Groups", "Groups -> Button");
394    }
395    else
396    {
397        icon = [[GroupsController groups] imageForIndex: groupIndex];
398        NSString * groupName = groupIndex != -1 ? [[GroupsController groups] nameForIndex: groupIndex]
399                                                : NSLocalizedString(@"None", "Groups -> Button");
400        toolTip = [NSLocalizedString(@"Group", "Groups -> Button") stringByAppendingFormat: @": %@", groupName];
401    }
402
403    [[[fGroupsButton menu] itemAtIndex: 0] setImage: icon];
404    [fGroupsButton setToolTip: toolTip];
405}
406
407- (void) updateGroups: (NSNotification *) notification
408{
409    [self updateGroupsButton];
410}
411
412@end
413