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