/****************************************************************************** * Copyright (c) 2007-2012 Transmission authors and contributors * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #import "GroupsController.h" #import "NSMutableArrayAdditions.h" #define ICON_WIDTH 16.0 #define ICON_WIDTH_SMALL 12.0 @interface GroupsController (Private) - (void) saveGroups; - (NSImage *) imageForGroup: (NSMutableDictionary *) dict; - (BOOL) torrent: (Torrent *) torrent doesMatchRulesForGroupAtIndex: (NSInteger) index; @end @implementation GroupsController GroupsController * fGroupsInstance = nil; + (GroupsController *) groups { if (!fGroupsInstance) fGroupsInstance = [[GroupsController alloc] init]; return fGroupsInstance; } - (id) init { if ((self = [super init])) { NSData * data; if ((data = [[NSUserDefaults standardUserDefaults] dataForKey: @"GroupDicts"])) fGroups = [NSKeyedUnarchiver unarchiveObjectWithData: data]; else if ((data = [[NSUserDefaults standardUserDefaults] dataForKey: @"Groups"])) //handle old groups { fGroups = [NSUnarchiver unarchiveObjectWithData: data]; [[NSUserDefaults standardUserDefaults] removeObjectForKey: @"Groups"]; [self saveGroups]; } else { //default groups NSMutableDictionary * red = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSColor redColor], @"Color", NSLocalizedString(@"Red", "Groups -> Name"), @"Name", @0, @"Index", nil]; NSMutableDictionary * orange = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSColor orangeColor], @"Color", NSLocalizedString(@"Orange", "Groups -> Name"), @"Name", @1, @"Index", nil]; NSMutableDictionary * yellow = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSColor yellowColor], @"Color", NSLocalizedString(@"Yellow", "Groups -> Name"), @"Name", @2, @"Index", nil]; NSMutableDictionary * green = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSColor greenColor], @"Color", NSLocalizedString(@"Green", "Groups -> Name"), @"Name", @3, @"Index", nil]; NSMutableDictionary * blue = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSColor blueColor], @"Color", NSLocalizedString(@"Blue", "Groups -> Name"), @"Name", @4, @"Index", nil]; NSMutableDictionary * purple = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSColor purpleColor], @"Color", NSLocalizedString(@"Purple", "Groups -> Name"), @"Name", @5, @"Index", nil]; NSMutableDictionary * gray = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSColor grayColor], @"Color", NSLocalizedString(@"Gray", "Groups -> Name"), @"Name", @6, @"Index", nil]; fGroups = [[NSMutableArray alloc] initWithObjects: red, orange, yellow, green, blue, purple, gray, nil]; [self saveGroups]; //make sure this is saved right away } } return self; } - (NSInteger) numberOfGroups { return [fGroups count]; } - (NSInteger) rowValueForIndex: (NSInteger) index { if (index != -1) { for (NSUInteger i = 0; i < [fGroups count]; i++) if (index == [fGroups[i][@"Index"] integerValue]) return i; } return -1; } - (NSInteger) indexForRow: (NSInteger) row { return [fGroups[row][@"Index"] integerValue]; } - (NSString *) nameForIndex: (NSInteger) index { NSInteger orderIndex = [self rowValueForIndex: index]; return orderIndex != -1 ? fGroups[orderIndex][@"Name"] : nil; } - (void) setName: (NSString *) name forIndex: (NSInteger) index { NSInteger orderIndex = [self rowValueForIndex: index]; fGroups[orderIndex][@"Name"] = name; [self saveGroups]; [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGroups" object: self]; } - (NSImage *) imageForIndex: (NSInteger) index { NSInteger orderIndex = [self rowValueForIndex: index]; return orderIndex != -1 ? [self imageForGroup: fGroups[orderIndex]] : [NSImage imageNamed: @"GroupsNoneTemplate"]; } - (NSColor *) colorForIndex: (NSInteger) index { NSInteger orderIndex = [self rowValueForIndex: index]; return orderIndex != -1 ? fGroups[orderIndex][@"Color"] : nil; } - (void) setColor: (NSColor *) color forIndex: (NSInteger) index { NSMutableDictionary * dict = fGroups[[self rowValueForIndex: index]]; [dict removeObjectForKey: @"Icon"]; dict[@"Color"] = color; [[GroupsController groups] saveGroups]; [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGroups" object: self]; } - (BOOL) usesCustomDownloadLocationForIndex: (NSInteger) index { if (![self customDownloadLocationForIndex: index]) return NO; NSInteger orderIndex = [self rowValueForIndex: index]; return [fGroups[orderIndex][@"UsesCustomDownloadLocation"] boolValue]; } - (void) setUsesCustomDownloadLocation: (BOOL) useCustomLocation forIndex: (NSInteger) index { NSMutableDictionary * dict = fGroups[[self rowValueForIndex: index]]; dict[@"UsesCustomDownloadLocation"] = @(useCustomLocation); [[GroupsController groups] saveGroups]; } - (NSString *) customDownloadLocationForIndex: (NSInteger) index { NSInteger orderIndex = [self rowValueForIndex: index]; return orderIndex != -1 ? fGroups[orderIndex][@"CustomDownloadLocation"] : nil; } - (void) setCustomDownloadLocation: (NSString *) location forIndex: (NSInteger) index { NSMutableDictionary * dict = fGroups[[self rowValueForIndex: index]]; dict[@"CustomDownloadLocation"] = location; [[GroupsController groups] saveGroups]; } - (BOOL) usesAutoAssignRulesForIndex: (NSInteger) index { NSInteger orderIndex = [self rowValueForIndex: index]; if (orderIndex == -1) return NO; NSNumber * assignRules = fGroups[orderIndex][@"UsesAutoGroupRules"]; return assignRules && [assignRules boolValue]; } - (void) setUsesAutoAssignRules: (BOOL) useAutoAssignRules forIndex: (NSInteger) index { NSMutableDictionary * dict = fGroups[[self rowValueForIndex: index]]; dict[@"UsesAutoGroupRules"] = @(useAutoAssignRules); [[GroupsController groups] saveGroups]; } - (NSPredicate *) autoAssignRulesForIndex: (NSInteger) index { NSInteger orderIndex = [self rowValueForIndex: index]; if (orderIndex == -1) return nil; return fGroups[orderIndex][@"AutoGroupRules"]; } - (void) setAutoAssignRules: (NSPredicate *) predicate forIndex: (NSInteger) index { NSMutableDictionary * dict = fGroups[[self rowValueForIndex: index]]; if (predicate) { dict[@"AutoGroupRules"] = predicate; [[GroupsController groups] saveGroups]; } else { [dict removeObjectForKey: @"AutoGroupRules"]; [self setUsesAutoAssignRules: NO forIndex: index]; } } - (void) addNewGroup { //find the lowest index NSMutableIndexSet * candidates = [NSMutableIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fGroups count]+1)]; for (NSDictionary * dict in fGroups) [candidates removeIndex: [dict[@"Index"] integerValue]]; const NSInteger index = [candidates firstIndex]; [fGroups addObject: [NSMutableDictionary dictionaryWithObjectsAndKeys: @(index), @"Index", [NSColor colorWithCalibratedRed: 0.0 green: 0.65 blue: 1.0 alpha: 1.0], @"Color", @"", @"Name", nil]]; [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGroups" object: self]; [self saveGroups]; } - (void) removeGroupWithRowIndex: (NSInteger) row { NSInteger index = [fGroups[row][@"Index"] integerValue]; [fGroups removeObjectAtIndex: row]; [[NSNotificationCenter defaultCenter] postNotificationName: @"GroupValueRemoved" object: self userInfo: @{@"Index": @(index)}]; if (index == [[NSUserDefaults standardUserDefaults] integerForKey: @"FilterGroup"]) [[NSUserDefaults standardUserDefaults] setInteger: -2 forKey: @"FilterGroup"]; [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGroups" object: self]; [self saveGroups]; } - (void) moveGroupAtRow: (NSInteger) oldRow toRow: (NSInteger) newRow { [fGroups moveObjectAtIndex: oldRow toIndex: newRow]; [self saveGroups]; [[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGroups" object: self]; } - (NSMenu *) groupMenuWithTarget: (id) target action: (SEL) action isSmall: (BOOL) small { NSMenu * menu = [[NSMenu alloc] initWithTitle: @"Groups"]; NSMenuItem * item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"None", "Groups -> Menu") action: action keyEquivalent: @""]; [item setTarget: target]; [item setTag: -1]; NSImage * icon = [NSImage imageNamed: @"GroupsNoneTemplate"]; if (small) { icon = [icon copy]; [icon setSize: NSMakeSize(ICON_WIDTH_SMALL, ICON_WIDTH_SMALL)]; [item setImage: icon]; } else [item setImage: icon]; [menu addItem: item]; for (NSMutableDictionary * dict in fGroups) { item = [[NSMenuItem alloc] initWithTitle: dict[@"Name"] action: action keyEquivalent: @""]; [item setTarget: target]; [item setTag: [dict[@"Index"] integerValue]]; NSImage * icon = [self imageForGroup: dict]; if (small) { icon = [icon copy]; [icon setSize: NSMakeSize(ICON_WIDTH_SMALL, ICON_WIDTH_SMALL)]; [item setImage: icon]; } else [item setImage: icon]; [menu addItem: item]; } return menu; } - (NSInteger) groupIndexForTorrent: (Torrent *) torrent { for (NSDictionary * group in fGroups) { NSInteger row = [group[@"Index"] integerValue]; if ([self torrent: torrent doesMatchRulesForGroupAtIndex: row]) return row; } return -1; } @end @implementation GroupsController (Private) - (void) saveGroups { //don't archive the icon NSMutableArray * groups = [NSMutableArray arrayWithCapacity: [fGroups count]]; for (NSDictionary * dict in fGroups) { NSMutableDictionary * tempDict = [dict mutableCopy]; [tempDict removeObjectForKey: @"Icon"]; [groups addObject: tempDict]; } [[NSUserDefaults standardUserDefaults] setObject: [NSKeyedArchiver archivedDataWithRootObject: groups] forKey: @"GroupDicts"]; } - (NSImage *) imageForGroup: (NSMutableDictionary *) dict { NSImage * image; if ((image = dict[@"Icon"])) return image; NSRect rect = NSMakeRect(0.0, 0.0, ICON_WIDTH, ICON_WIDTH); NSBezierPath * bp = [NSBezierPath bezierPathWithRoundedRect: rect xRadius: 3.0 yRadius: 3.0]; NSImage * icon = [[NSImage alloc] initWithSize: rect.size]; NSColor * color = dict[@"Color"]; [icon lockFocus]; //border NSGradient * gradient = [[NSGradient alloc] initWithStartingColor: [color blendedColorWithFraction: 0.45 ofColor: [NSColor whiteColor]] endingColor: color]; [gradient drawInBezierPath: bp angle: 270.0]; //inside bp = [NSBezierPath bezierPathWithRoundedRect: NSInsetRect(rect, 1.0, 1.0) xRadius: 3.0 yRadius: 3.0]; gradient = [[NSGradient alloc] initWithStartingColor: [color blendedColorWithFraction: 0.75 ofColor: [NSColor whiteColor]] endingColor: [color blendedColorWithFraction: 0.2 ofColor: [NSColor whiteColor]]]; [gradient drawInBezierPath: bp angle: 270.0]; [icon unlockFocus]; dict[@"Icon"] = icon; return icon; } - (BOOL) torrent: (Torrent *) torrent doesMatchRulesForGroupAtIndex: (NSInteger) index { if (![self usesAutoAssignRulesForIndex: index]) return NO; NSPredicate * predicate = [self autoAssignRulesForIndex: index]; BOOL eval = NO; @try { eval = [predicate evaluateWithObject: torrent]; } @catch (NSException * exception) { NSLog(@"Error when evaluating predicate (%@) - %@", predicate, exception); } @finally { return eval; } } @end