1/******************************************************************************
2 * $Id: GroupsController.m 13340 2012-06-10 02:35:58Z livings124 $
3 *
4 * Copyright (c) 2007-2012 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import "GroupsController.h"
26#import "NSMutableArrayAdditions.h"
27
28#define ICON_WIDTH 16.0
29#define ICON_WIDTH_SMALL 12.0
30
31@interface GroupsController (Private)
32
33- (void) saveGroups;
34
35- (NSImage *) imageForGroup: (NSMutableDictionary *) dict;
36
37- (BOOL) torrent: (Torrent *) torrent doesMatchRulesForGroupAtIndex: (NSInteger) index;
38
39@end
40
41@implementation GroupsController
42
43GroupsController * fGroupsInstance = nil;
44+ (GroupsController *) groups
45{
46    if (!fGroupsInstance)
47        fGroupsInstance = [[GroupsController alloc] init];
48    return fGroupsInstance;
49}
50
51- (id) init
52{
53    if ((self = [super init]))
54    {
55        NSData * data;
56        if ((data = [[NSUserDefaults standardUserDefaults] dataForKey: @"GroupDicts"]))
57            fGroups = [[NSKeyedUnarchiver unarchiveObjectWithData: data] retain];
58        else if ((data = [[NSUserDefaults standardUserDefaults] dataForKey: @"Groups"])) //handle old groups
59        {
60            fGroups = [[NSUnarchiver unarchiveObjectWithData: data] retain];
61            [[NSUserDefaults standardUserDefaults] removeObjectForKey: @"Groups"];
62            [self saveGroups];
63        }
64        else
65        {
66            //default groups
67            NSMutableDictionary * red = [NSMutableDictionary dictionaryWithObjectsAndKeys:
68                                            [NSColor redColor], @"Color",
69                                            NSLocalizedString(@"Red", "Groups -> Name"), @"Name",
70                                            [NSNumber numberWithInteger: 0], @"Index", nil];
71            
72            NSMutableDictionary * orange = [NSMutableDictionary dictionaryWithObjectsAndKeys:
73                                            [NSColor orangeColor], @"Color",
74                                            NSLocalizedString(@"Orange", "Groups -> Name"), @"Name",
75                                            [NSNumber numberWithInteger: 1], @"Index", nil];
76            
77            NSMutableDictionary * yellow = [NSMutableDictionary dictionaryWithObjectsAndKeys:
78                                            [NSColor yellowColor], @"Color",
79                                            NSLocalizedString(@"Yellow", "Groups -> Name"), @"Name",
80                                            [NSNumber numberWithInteger: 2], @"Index", nil];
81            
82            NSMutableDictionary * green = [NSMutableDictionary dictionaryWithObjectsAndKeys:
83                                            [NSColor greenColor], @"Color",
84                                            NSLocalizedString(@"Green", "Groups -> Name"), @"Name",
85                                            [NSNumber numberWithInteger: 3], @"Index", nil];
86            
87            NSMutableDictionary * blue = [NSMutableDictionary dictionaryWithObjectsAndKeys:
88                                            [NSColor blueColor], @"Color",
89                                            NSLocalizedString(@"Blue", "Groups -> Name"), @"Name",
90                                            [NSNumber numberWithInteger: 4], @"Index", nil];
91            
92            NSMutableDictionary * purple = [NSMutableDictionary dictionaryWithObjectsAndKeys:
93                                            [NSColor purpleColor], @"Color",
94                                            NSLocalizedString(@"Purple", "Groups -> Name"), @"Name",
95                                            [NSNumber numberWithInteger: 5], @"Index", nil];
96            
97            NSMutableDictionary * gray = [NSMutableDictionary dictionaryWithObjectsAndKeys:
98                                            [NSColor grayColor], @"Color",
99                                            NSLocalizedString(@"Gray", "Groups -> Name"), @"Name",
100                                            [NSNumber numberWithInteger: 6], @"Index", nil];
101            
102            fGroups = [[NSMutableArray alloc] initWithObjects: red, orange, yellow, green, blue, purple, gray, nil];
103            [self saveGroups]; //make sure this is saved right away
104        }
105    }
106    
107    return self;
108}
109
110- (void) dealloc
111{
112    [fGroups release];
113    [super dealloc];
114}
115
116- (NSInteger) numberOfGroups
117{
118    return [fGroups count];
119}
120
121- (NSInteger) rowValueForIndex: (NSInteger) index
122{
123    if (index != -1)
124    {
125        for (NSInteger i = 0; i < [fGroups count]; i++)
126            if (index == [[[fGroups objectAtIndex: i] objectForKey: @"Index"] integerValue])
127                return i;
128    }
129    return -1;
130}
131
132- (NSInteger) indexForRow: (NSInteger) row
133{
134    return [[[fGroups objectAtIndex: row] objectForKey: @"Index"] integerValue];
135}
136
137- (NSString *) nameForIndex: (NSInteger) index
138{
139    NSInteger orderIndex = [self rowValueForIndex: index];
140    return orderIndex != -1 ? [[fGroups objectAtIndex: orderIndex] objectForKey: @"Name"] : nil;
141}
142
143- (void) setName: (NSString *) name forIndex: (NSInteger) index
144{
145    NSInteger orderIndex = [self rowValueForIndex: index];
146    [[fGroups objectAtIndex: orderIndex] setObject: name forKey: @"Name"];
147    [self saveGroups];
148    
149    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGroups" object: self];
150}
151
152- (NSImage *) imageForIndex: (NSInteger) index
153{
154    NSInteger orderIndex = [self rowValueForIndex: index];
155    return orderIndex != -1 ? [self imageForGroup: [fGroups objectAtIndex: orderIndex]]
156                            : [NSImage imageNamed: @"GroupsNoneTemplate"];
157}
158
159- (NSColor *) colorForIndex: (NSInteger) index
160{
161    NSInteger orderIndex = [self rowValueForIndex: index];
162    return orderIndex != -1 ? [[fGroups objectAtIndex: orderIndex] objectForKey: @"Color"] : nil;
163}
164
165- (void) setColor: (NSColor *) color forIndex: (NSInteger) index
166{
167    NSMutableDictionary * dict = [fGroups objectAtIndex: [self rowValueForIndex: index]];
168    [dict removeObjectForKey: @"Icon"];
169    
170    [dict setObject: color forKey: @"Color"];
171    
172    [[GroupsController groups] saveGroups];
173    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGroups" object: self];
174}
175
176- (BOOL) usesCustomDownloadLocationForIndex: (NSInteger) index
177{
178    if (![self customDownloadLocationForIndex: index])
179        return NO;
180
181    NSInteger orderIndex = [self rowValueForIndex: index];
182    return [[[fGroups objectAtIndex: orderIndex] objectForKey: @"UsesCustomDownloadLocation"] boolValue];
183}
184
185- (void) setUsesCustomDownloadLocation: (BOOL) useCustomLocation forIndex: (NSInteger) index
186{
187    NSMutableDictionary * dict = [fGroups objectAtIndex: [self rowValueForIndex: index]];
188    
189    [dict setObject: [NSNumber numberWithBool: useCustomLocation] forKey: @"UsesCustomDownloadLocation"];
190    
191    [[GroupsController groups] saveGroups];
192}
193
194- (NSString *) customDownloadLocationForIndex: (NSInteger) index
195{
196    NSInteger orderIndex = [self rowValueForIndex: index];
197    return orderIndex != -1 ? [[fGroups objectAtIndex: orderIndex] objectForKey: @"CustomDownloadLocation"] : nil;
198}
199
200- (void) setCustomDownloadLocation: (NSString *) location forIndex: (NSInteger) index
201{
202    NSMutableDictionary * dict = [fGroups objectAtIndex: [self rowValueForIndex: index]];
203    [dict setObject: location forKey: @"CustomDownloadLocation"];
204    
205    [[GroupsController groups] saveGroups];
206}
207
208- (BOOL) usesAutoAssignRulesForIndex: (NSInteger) index
209{
210    NSInteger orderIndex = [self rowValueForIndex: index];
211    if (orderIndex == -1)
212        return NO;
213    
214    NSNumber * assignRules = [[fGroups objectAtIndex: orderIndex] objectForKey: @"UsesAutoGroupRules"];
215    return assignRules && [assignRules boolValue];
216}
217
218- (void) setUsesAutoAssignRules: (BOOL) useAutoAssignRules forIndex: (NSInteger) index
219{
220    NSMutableDictionary * dict = [fGroups objectAtIndex: [self rowValueForIndex: index]];
221    
222    [dict setObject: [NSNumber numberWithBool: useAutoAssignRules] forKey: @"UsesAutoGroupRules"];
223    
224    [[GroupsController groups] saveGroups];
225}
226
227- (NSPredicate *) autoAssignRulesForIndex: (NSInteger) index
228{
229    NSInteger orderIndex = [self rowValueForIndex: index];
230    if (orderIndex == -1)
231		return nil;
232	
233	return [[fGroups objectAtIndex: orderIndex] objectForKey: @"AutoGroupRules"];
234}
235
236- (void) setAutoAssignRules: (NSPredicate *) predicate forIndex: (NSInteger) index
237{
238    NSMutableDictionary * dict = [fGroups objectAtIndex: [self rowValueForIndex: index]];
239    
240    if (predicate)
241    {
242        [dict setObject: predicate forKey: @"AutoGroupRules"];
243        [[GroupsController groups] saveGroups];
244    }
245    else
246    {
247        [dict removeObjectForKey: @"AutoGroupRules"];
248        [self setUsesAutoAssignRules: NO forIndex: index];
249    }
250}
251
252- (void) addNewGroup
253{
254    //find the lowest index
255    NSMutableIndexSet * candidates = [NSMutableIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fGroups count]+1)];
256    for (NSDictionary * dict in fGroups)
257        [candidates removeIndex: [[dict objectForKey: @"Index"] integerValue]];
258    
259    const NSInteger index = [candidates firstIndex];
260    
261    [fGroups addObject: [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInteger: index], @"Index",
262                            [NSColor colorWithCalibratedRed: 0.0 green: 0.65 blue: 1.0 alpha: 1.0], @"Color", @"", @"Name", nil]];
263    
264    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGroups" object: self];
265    [self saveGroups];
266}
267
268- (void) removeGroupWithRowIndex: (NSInteger) row
269{
270    NSInteger index = [[[fGroups objectAtIndex: row] objectForKey: @"Index"] integerValue];
271    [fGroups removeObjectAtIndex: row];
272    
273    [[NSNotificationCenter defaultCenter] postNotificationName: @"GroupValueRemoved" object: self userInfo:
274        [NSDictionary dictionaryWithObject: [NSNumber numberWithInteger: index] forKey: @"Index"]];
275    
276    if (index == [[NSUserDefaults standardUserDefaults] integerForKey: @"FilterGroup"])
277        [[NSUserDefaults standardUserDefaults] setInteger: -2 forKey: @"FilterGroup"];
278    
279    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGroups" object: self];
280    [self saveGroups];
281}
282
283- (void) moveGroupAtRow: (NSInteger) oldRow toRow: (NSInteger) newRow
284{
285    [fGroups moveObjectAtIndex: oldRow toIndex: newRow];
286    
287    [self saveGroups];
288    [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGroups" object: self];
289}
290
291- (NSMenu *) groupMenuWithTarget: (id) target action: (SEL) action isSmall: (BOOL) small
292{
293    NSMenu * menu = [[NSMenu alloc] initWithTitle: @"Groups"];
294    
295    NSMenuItem * item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"None", "Groups -> Menu") action: action
296                            keyEquivalent: @""];
297    [item setTarget: target];
298    [item setTag: -1];
299    
300    NSImage * icon = [NSImage imageNamed: @"GroupsNoneTemplate"];
301    if (small)
302    {
303        icon = [icon copy];
304        [icon setSize: NSMakeSize(ICON_WIDTH_SMALL, ICON_WIDTH_SMALL)];
305        
306        [item setImage: icon];
307        [icon release];
308    }
309    else
310        [item setImage: icon];
311    
312    [menu addItem: item];
313    [item release];
314    
315    for (NSMutableDictionary * dict in fGroups)
316    {
317        item = [[NSMenuItem alloc] initWithTitle: [dict objectForKey: @"Name"] action: action keyEquivalent: @""];
318        [item setTarget: target];
319        
320        [item setTag: [[dict objectForKey: @"Index"] integerValue]];
321        
322        NSImage * icon = [self imageForGroup: dict];
323        if (small)
324        {
325            icon = [icon copy];
326            [icon setSize: NSMakeSize(ICON_WIDTH_SMALL, ICON_WIDTH_SMALL)];
327            
328            [item setImage: icon];
329            [icon release];
330        }
331        else
332            [item setImage: icon];
333        
334        [menu addItem: item];
335        [item release];
336    }
337    
338    return [menu autorelease];
339}
340
341- (NSInteger) groupIndexForTorrent: (Torrent *) torrent;
342{
343    for (NSDictionary * group in fGroups)
344    {
345        NSInteger row = [[group objectForKey: @"Index"] integerValue];
346        if ([self torrent: torrent doesMatchRulesForGroupAtIndex: row])
347            return row;
348    }
349    return -1;
350}
351
352@end
353
354@implementation GroupsController (Private)
355
356- (void) saveGroups
357{
358    //don't archive the icon
359    NSMutableArray * groups = [NSMutableArray arrayWithCapacity: [fGroups count]];
360    for (NSDictionary * dict in fGroups)
361    {
362        NSMutableDictionary * tempDict = [dict mutableCopy];
363        [tempDict removeObjectForKey: @"Icon"];
364        [groups addObject: tempDict];
365        [tempDict release];
366    }
367    
368    [[NSUserDefaults standardUserDefaults] setObject: [NSKeyedArchiver archivedDataWithRootObject: groups] forKey: @"GroupDicts"];
369}
370
371- (NSImage *) imageForGroup: (NSMutableDictionary *) dict
372{
373    NSImage * image;
374    if ((image = [dict objectForKey: @"Icon"]))
375        return image;
376    
377    NSRect rect = NSMakeRect(0.0, 0.0, ICON_WIDTH, ICON_WIDTH);
378    
379    NSBezierPath * bp = [NSBezierPath bezierPathWithRoundedRect: rect xRadius: 3.0 yRadius: 3.0];
380    NSImage * icon = [[NSImage alloc] initWithSize: rect.size];
381    
382    NSColor * color = [dict objectForKey: @"Color"];
383    
384    [icon lockFocus];
385    
386    //border
387    NSGradient * gradient = [[NSGradient alloc] initWithStartingColor: [color blendedColorWithFraction: 0.45 ofColor:
388                                [NSColor whiteColor]] endingColor: color];
389    [gradient drawInBezierPath: bp angle: 270.0];
390    [gradient release];
391    
392    //inside
393    bp = [NSBezierPath bezierPathWithRoundedRect: NSInsetRect(rect, 1.0, 1.0) xRadius: 3.0 yRadius: 3.0];
394    gradient = [[NSGradient alloc] initWithStartingColor: [color blendedColorWithFraction: 0.75 ofColor: [NSColor whiteColor]]
395                endingColor: [color blendedColorWithFraction: 0.2 ofColor: [NSColor whiteColor]]];
396    [gradient drawInBezierPath: bp angle: 270.0];
397    [gradient release];
398    
399    [icon unlockFocus];
400    
401    [dict setObject: icon forKey: @"Icon"];
402    [icon release];
403    
404    return icon;
405}
406
407- (BOOL) torrent: (Torrent *) torrent doesMatchRulesForGroupAtIndex: (NSInteger) index
408{
409    if (![self usesAutoAssignRulesForIndex: index])
410        return NO;
411	
412    NSPredicate * predicate = [self autoAssignRulesForIndex: index];
413    BOOL eval = NO;
414    @try
415    {
416        eval = [predicate evaluateWithObject: torrent];
417    }
418    @catch (NSException * exception)
419    {
420        NSLog(@"Error when evaluating predicate (%@) - %@", predicate, exception);
421    }
422    @finally
423    {
424        return eval;
425    }
426}
427
428@end
429