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