1/****************************************************************************** 2 * $Id: AddWindowController.m 13492 2012-09-10 02:37:29Z livings124 $ 3 * 4 * Copyright (c) 2008-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 "AddWindowController.h" 26#import "Controller.h" 27#import "ExpandedPathToIconTransformer.h" 28#import "FileOutlineController.h" 29#import "GroupsController.h" 30#import "NSStringAdditions.h" 31#import "Torrent.h" 32 33#define UPDATE_SECONDS 1.0 34 35#define POPUP_PRIORITY_HIGH 0 36#define POPUP_PRIORITY_NORMAL 1 37#define POPUP_PRIORITY_LOW 2 38 39@interface AddWindowController (Private) 40 41- (void) updateFiles; 42 43- (void) confirmAdd; 44 45- (void) setDestinationPath: (NSString *) destination; 46 47- (void) setGroupsMenu; 48- (void) changeGroupValue: (id) sender; 49 50- (void) sameNameAlertDidEnd: (NSAlert *) alert returnCode: (NSInteger) returnCode contextInfo: (void *) contextInfo; 51 52@end 53 54@implementation AddWindowController 55 56- (id) initWithTorrent: (Torrent *) torrent destination: (NSString *) path lockDestination: (BOOL) lockDestination 57 controller: (Controller *) controller torrentFile: (NSString *) torrentFile 58 deleteTorrentCheckEnableInitially: (BOOL) deleteTorrent canToggleDelete: (BOOL) canToggleDelete 59{ 60 if ((self = [super initWithWindowNibName: @"AddWindow"])) 61 { 62 fTorrent = torrent; 63 fDestination = [[path stringByExpandingTildeInPath] retain]; 64 fLockDestination = lockDestination; 65 66 fController = controller; 67 68 fTorrentFile = [[torrentFile stringByExpandingTildeInPath] retain]; 69 70 fDeleteTorrentEnableInitially = deleteTorrent; 71 fCanToggleDelete = canToggleDelete; 72 73 fGroupValue = [torrent groupValue]; 74 75 [fVerifyIndicator setUsesThreadedAnimation: YES]; 76 } 77 return self; 78} 79 80- (void) awakeFromNib 81{ 82 [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateCheckButtons:) name: @"TorrentFileCheckChange" object: fTorrent]; 83 84 [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateGroupMenu:) name: @"UpdateGroups" object: nil]; 85 86 [fFileController setTorrent: fTorrent]; 87 88 NSString * name = [fTorrent name]; 89 [[self window] setTitle: name]; 90 [fNameField setStringValue: name]; 91 [fNameField setToolTip: name]; 92 93 [fIconView setImage: [fTorrent icon]]; 94 95 if (![fTorrent isFolder]) 96 { 97 [fFileFilterField setHidden: YES]; 98 [fCheckAllButton setHidden: YES]; 99 [fUncheckAllButton setHidden: YES]; 100 101 NSRect scrollFrame = [fFileScrollView frame]; 102 const CGFloat diff = NSMinY([fFileScrollView frame]) - NSMinY([fFileFilterField frame]); 103 scrollFrame.origin.y -= diff; 104 scrollFrame.size.height += diff; 105 [fFileScrollView setFrame: scrollFrame]; 106 } 107 else 108 [self updateCheckButtons: nil]; 109 110 [self setGroupsMenu]; 111 [fGroupPopUp selectItemWithTag: fGroupValue]; 112 113 NSInteger priorityIndex; 114 switch ([fTorrent priority]) 115 { 116 case TR_PRI_HIGH: priorityIndex = POPUP_PRIORITY_HIGH; break; 117 case TR_PRI_NORMAL: priorityIndex = POPUP_PRIORITY_NORMAL; break; 118 case TR_PRI_LOW: priorityIndex = POPUP_PRIORITY_LOW; break; 119 default: NSAssert1(NO, @"Unknown priority for adding torrent: %d", [fTorrent priority]); 120 } 121 [fPriorityPopUp selectItemAtIndex: priorityIndex]; 122 123 [fStartCheck setState: [[NSUserDefaults standardUserDefaults] boolForKey: @"AutoStartDownload"] ? NSOnState : NSOffState]; 124 125 [fDeleteCheck setState: fDeleteTorrentEnableInitially ? NSOnState : NSOffState]; 126 [fDeleteCheck setEnabled: fCanToggleDelete]; 127 128 if (fDestination) 129 [self setDestinationPath: fDestination]; 130 else 131 { 132 [fLocationField setStringValue: @""]; 133 [fLocationImageView setImage: nil]; 134 } 135 136 fTimer = [[NSTimer scheduledTimerWithTimeInterval: UPDATE_SECONDS target: self 137 selector: @selector(updateFiles) userInfo: nil repeats: YES] retain]; 138 [self updateFiles]; 139} 140 141- (void) windowDidLoad 142{ 143 //if there is no destination, prompt for one right away 144 if (!fDestination) 145 [self setDestination: nil]; 146} 147 148- (void) dealloc 149{ 150 [[NSNotificationCenter defaultCenter] removeObserver: self]; 151 152 [fTimer invalidate]; 153 [fTimer release]; 154 155 [fDestination release]; 156 [fTorrentFile release]; 157 158 [super dealloc]; 159} 160 161- (Torrent *) torrent 162{ 163 return fTorrent; 164} 165 166- (void) setDestination: (id) sender 167{ 168 NSOpenPanel * panel = [NSOpenPanel openPanel]; 169 170 [panel setPrompt: NSLocalizedString(@"Select", "Open torrent -> prompt")]; 171 [panel setAllowsMultipleSelection: NO]; 172 [panel setCanChooseFiles: NO]; 173 [panel setCanChooseDirectories: YES]; 174 [panel setCanCreateDirectories: YES]; 175 176 [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the download folder for \"%@\"", 177 "Add -> select destination folder"), [fTorrent name]]]; 178 179 [panel beginSheetModalForWindow: [self window] completionHandler: ^(NSInteger result) { 180 if (result == NSFileHandlingPanelOKButton) 181 { 182 fLockDestination = NO; 183 [self setDestinationPath: [[[panel URLs] objectAtIndex: 0] path]]; 184 } 185 else 186 { 187 if (!fDestination) 188 [self performSelectorOnMainThread: @selector(cancelAdd:) withObject: nil waitUntilDone: NO]; 189 } 190 }]; 191} 192 193- (void) add: (id) sender 194{ 195 if ([[fDestination lastPathComponent] isEqualToString: [fTorrent name]] 196 && [[NSUserDefaults standardUserDefaults] boolForKey: @"WarningFolderDataSameName"]) 197 { 198 NSAlert * alert = [[NSAlert alloc] init]; 199 [alert setMessageText: NSLocalizedString(@"The destination directory and root data directory have the same name.", 200 "Add torrent -> same name -> title")]; 201 [alert setInformativeText: NSLocalizedString(@"If you are attempting to use already existing data," 202 " the root data directory should be inside the destination directory.", "Add torrent -> same name -> message")]; 203 [alert setAlertStyle: NSWarningAlertStyle]; 204 [alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Add torrent -> same name -> button")]; 205 [alert addButtonWithTitle: NSLocalizedString(@"Add", "Add torrent -> same name -> button")]; 206 [alert setShowsSuppressionButton: YES]; 207 208 [alert beginSheetModalForWindow: [self window] modalDelegate: self 209 didEndSelector: @selector(sameNameAlertDidEnd:returnCode:contextInfo:) contextInfo: nil]; 210 } 211 else 212 [self confirmAdd]; 213} 214 215- (void) cancelAdd: (id) sender 216{ 217 [[self window] performClose: sender]; 218} 219 220//only called on cancel 221- (BOOL) windowShouldClose: (id) window 222{ 223 [fTimer invalidate]; 224 [fTimer release]; 225 fTimer = nil; 226 227 [fFileController setTorrent: nil]; //avoid a crash when window tries to update 228 229 [fController askOpenConfirmed: self add: NO]; 230 return YES; 231} 232 233- (void) setFileFilterText: (id) sender 234{ 235 [fFileController setFilterText: [sender stringValue]]; 236} 237 238- (IBAction) checkAll: (id) sender 239{ 240 [fFileController checkAll]; 241} 242 243- (IBAction) uncheckAll: (id) sender 244{ 245 [fFileController uncheckAll]; 246} 247 248- (void) verifyLocalData: (id) sender 249{ 250 [fTorrent resetCache]; 251 [self updateFiles]; 252} 253 254- (void) changePriority: (id) sender 255{ 256 tr_priority_t priority; 257 switch ([sender indexOfSelectedItem]) 258 { 259 case POPUP_PRIORITY_HIGH: priority = TR_PRI_HIGH; break; 260 case POPUP_PRIORITY_NORMAL: priority = TR_PRI_NORMAL; break; 261 case POPUP_PRIORITY_LOW: priority = TR_PRI_LOW; break; 262 default: NSAssert1(NO, @"Unknown priority tag for adding torrent: %ld", [sender tag]); 263 } 264 [fTorrent setPriority: priority]; 265} 266 267- (void) updateCheckButtons: (NSNotification *) notification 268{ 269 NSString * statusString = [NSString stringForFileSize: [fTorrent size]]; 270 if ([fTorrent isFolder]) 271 { 272 //check buttons 273 //keep synced with identical code in InfoFileViewController.m 274 const NSInteger filesCheckState = [fTorrent checkForFiles: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTorrent fileCount])]]; 275 [fCheckAllButton setEnabled: filesCheckState != NSOnState]; //if anything is unchecked 276 [fUncheckAllButton setEnabled: ![fTorrent allDownloaded]]; //if there are any checked files that aren't finished 277 278 //status field 279 NSString * fileString; 280 NSInteger count = [fTorrent fileCount]; 281 if (count != 1) 282 fileString = [NSString stringWithFormat: NSLocalizedString(@"%@ files", "Add torrent -> info"), 283 [NSString formattedUInteger: count]]; 284 else 285 fileString = NSLocalizedString(@"1 file", "Add torrent -> info"); 286 287 NSString * selectedString = [NSString stringWithFormat: NSLocalizedString(@"%@ selected", "Add torrent -> info"), 288 [NSString stringForFileSize: [fTorrent totalSizeSelected]]]; 289 290 statusString = [NSString stringWithFormat: @"%@, %@ (%@)", fileString, statusString, selectedString]; 291 } 292 293 [fStatusField setStringValue: statusString]; 294} 295 296- (void) updateGroupMenu: (NSNotification *) notification 297{ 298 [self setGroupsMenu]; 299 if (![fGroupPopUp selectItemWithTag: fGroupValue]) 300 { 301 fGroupValue = -1; 302 [fGroupPopUp selectItemWithTag: fGroupValue]; 303 } 304} 305 306@end 307 308@implementation AddWindowController (Private) 309 310- (void) updateFiles 311{ 312 [fTorrent update]; 313 314 [fFileController refresh]; 315 316 [self updateCheckButtons: nil]; //call in case button state changed by checking 317 318 if ([fTorrent isChecking]) 319 { 320 const BOOL waiting = [fTorrent isCheckingWaiting]; 321 [fVerifyIndicator setIndeterminate: waiting]; 322 if (waiting) 323 [fVerifyIndicator startAnimation: self]; 324 else 325 [fVerifyIndicator setDoubleValue: [fTorrent checkingProgress]]; 326 } 327 else { 328 [fVerifyIndicator setIndeterminate: YES]; //we want to hide when stopped, which only applies when indeterminate 329 [fVerifyIndicator stopAnimation: self]; 330 } 331} 332 333- (void) confirmAdd 334{ 335 [fTimer invalidate]; 336 [fTimer release]; 337 fTimer = nil; 338 [fTorrent setGroupValue: fGroupValue]; 339 340 if (fTorrentFile && fCanToggleDelete && [fDeleteCheck state] == NSOnState) 341 [Torrent trashFile: fTorrentFile]; 342 343 if ([fStartCheck state] == NSOnState) 344 [fTorrent startTransfer]; 345 346 [fFileController setTorrent: nil]; //avoid a crash when window tries to update 347 348 [self close]; 349 [fController askOpenConfirmed: self add: YES]; //ensure last, since it releases this controller 350} 351 352- (void) setDestinationPath: (NSString *) destination 353{ 354 destination = [destination stringByExpandingTildeInPath]; 355 if (!fDestination || ![fDestination isEqualToString: destination]) 356 { 357 [fDestination release]; 358 fDestination = [destination retain]; 359 360 [fTorrent changeDownloadFolderBeforeUsing: fDestination]; 361 } 362 363 [fLocationField setStringValue: [fDestination stringByAbbreviatingWithTildeInPath]]; 364 [fLocationField setToolTip: fDestination]; 365 366 ExpandedPathToIconTransformer * iconTransformer = [[ExpandedPathToIconTransformer alloc] init]; 367 [fLocationImageView setImage: [iconTransformer transformedValue: fDestination]]; 368 [iconTransformer release]; 369} 370 371- (void) setGroupsMenu 372{ 373 NSMenu * groupMenu = [[GroupsController groups] groupMenuWithTarget: self action: @selector(changeGroupValue:) isSmall: NO]; 374 [fGroupPopUp setMenu: groupMenu]; 375} 376 377- (void) changeGroupValue: (id) sender 378{ 379 NSInteger previousGroup = fGroupValue; 380 fGroupValue = [sender tag]; 381 382 if (!fLockDestination) 383 { 384 if ([[GroupsController groups] usesCustomDownloadLocationForIndex: fGroupValue]) 385 [self setDestinationPath: [[GroupsController groups] customDownloadLocationForIndex: fGroupValue]]; 386 else if ([fDestination isEqualToString: [[GroupsController groups] customDownloadLocationForIndex: previousGroup]]) 387 [self setDestinationPath: [[NSUserDefaults standardUserDefaults] stringForKey: @"DownloadFolder"]]; 388 else; 389 } 390} 391 392- (void) sameNameAlertDidEnd: (NSAlert *) alert returnCode: (NSInteger) returnCode contextInfo: (void *) contextInfo 393{ 394 if ([[alert suppressionButton] state] == NSOnState) 395 [[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningFolderDataSameName"]; 396 397 [alert release]; 398 399 if (returnCode == NSAlertSecondButtonReturn) 400 [self performSelectorOnMainThread: @selector(confirmAdd) withObject: nil waitUntilDone: NO]; 401} 402 403@end 404