@interface AboutWindowController : NSWindowController
{
- IBOutlet NSTextView * fTextView, * fLicenseView;
+ IBOutlet NSTextView * fTextView, * fLicenseView;
IBOutlet NSTextField * fVersionField, * fCopyrightField;
IBOutlet NSButton * fLicenseButton, * fLicenseCloseButton;
IBOutlet NSPanel * fLicenseSheet;
NSDictionary * info = [[NSBundle mainBundle] infoDictionary];
[fVersionField setStringValue: [NSString stringWithFormat: @"%@ (%@)",
[info objectForKey: @"CFBundleShortVersionString"], [info objectForKey: (NSString *)kCFBundleVersionKey]]];
-
+
[fCopyrightField setStringValue: [[NSBundle mainBundle] localizedStringForKey: @"NSHumanReadableCopyright"
value: nil table: @"InfoPlist"]];
-
+
[[fTextView textStorage] setAttributedString: [[[NSAttributedString alloc] initWithPath:
[[NSBundle mainBundle] pathForResource: @"Credits" ofType: @"rtf"] documentAttributes: nil] autorelease]];
-
+
//size license button
const CGFloat oldButtonWidth = NSWidth([fLicenseButton frame]);
-
+
[fLicenseButton setTitle: NSLocalizedString(@"License", "About window -> license button")];
[fLicenseButton sizeToFit];
-
+
NSRect buttonFrame = [fLicenseButton frame];
buttonFrame.size.width += 10.0;
buttonFrame.origin.x -= NSWidth(buttonFrame) - oldButtonWidth;
- (void) windowWillClose: (id) sender
{
- [fAboutBoxInstance autorelease];
+ [fAboutBoxInstance autorelease];
fAboutBoxInstance = nil;
}
usedEncoding: nil error: NULL];
[fLicenseView setString: licenseText];
[fLicenseCloseButton setTitle: NSLocalizedString(@"OK", "About window -> license close button")];
-
- [NSApp beginSheet: fLicenseSheet modalForWindow: [self window] modalDelegate: nil didEndSelector: nil contextInfo: nil];
+
+ [NSApp beginSheet: fLicenseSheet modalForWindow: [self window] modalDelegate: nil didEndSelector: nil contextInfo: nil];
}
- (IBAction) hideLicense: (id) sender
IBOutlet NSTextField * fNameField, * fLocationField;
IBOutlet NSButton * fStartCheck;
IBOutlet NSPopUpButton * fGroupPopUp, * fPriorityPopUp;
-
+
//remove these when switching to auto layout
IBOutlet NSTextField * fMagnetLinkLabel;
IBOutlet NSTextField * fDownloadToLabel, * fGroupLabel, * fPriorityLabel;
IBOutlet NSButton * fChangeDestinationButton;
IBOutlet NSBox * fDownloadToBox;
IBOutlet NSButton * fAddButton, * fCancelButton;
-
+
Controller * fController;
-
+
Torrent * fTorrent;
NSString * fDestination;
-
+
NSInteger fGroupValue;
TorrentDeterminationType fGroupDeterminationType;
}
{
fTorrent = torrent;
fDestination = [[path stringByExpandingTildeInPath] retain];
-
+
fController = controller;
-
+
fGroupValue = [torrent groupValue];
fGroupDeterminationType = TorrentDeterminationAutomatic;
}
{
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateGroupMenu:)
name: @"UpdateGroups" object: nil];
-
+
NSString * name = [fTorrent name];
[[self window] setTitle: name];
[fNameField setStringValue: name];
[fNameField setToolTip: name];
-
+
[self setGroupsMenu];
[fGroupPopUp selectItemWithTag: fGroupValue];
-
+
NSInteger priorityIndex;
switch ([fTorrent priority])
{
priorityIndex = POPUP_PRIORITY_NORMAL;
}
[fPriorityPopUp selectItemAtIndex: priorityIndex];
-
+
[fStartCheck setState: [[NSUserDefaults standardUserDefaults] boolForKey: @"AutoStartDownload"] ? NSOnState : NSOffState];
-
+
if (fDestination)
[self setDestinationPath: fDestination determinationType: TorrentDeterminationAutomatic];
else
[fLocationField setStringValue: @""];
[fLocationImageView setImage: nil];
}
-
+
#warning when 10.7-only, switch to auto layout
[fMagnetLinkLabel sizeToFit];
-
+
const CGFloat downloadToLabelOldWidth = [fDownloadToLabel frame].size.width;
[fDownloadToLabel sizeToFit];
const CGFloat changeDestOldWidth = [fChangeDestinationButton frame].size.width;
NSRect changeDestFrame = [fChangeDestinationButton frame];
changeDestFrame.origin.x -= changeDestFrame.size.width - changeDestOldWidth;
[fChangeDestinationButton setFrame: changeDestFrame];
-
+
NSRect downloadToBoxFrame = [fDownloadToBox frame];
const CGFloat downloadToBoxSizeDiff = ([fDownloadToLabel frame].size.width - downloadToLabelOldWidth) + (changeDestFrame.size.width - changeDestOldWidth);
downloadToBoxFrame.size.width -= downloadToBoxSizeDiff;
downloadToBoxFrame.origin.x -= downloadToLabelOldWidth - [fDownloadToLabel frame].size.width;
[fDownloadToBox setFrame: downloadToBoxFrame];
-
+
NSRect groupPopUpFrame = [fGroupPopUp frame];
NSRect priorityPopUpFrame = [fPriorityPopUp frame];
const CGFloat popUpOffset = groupPopUpFrame.origin.x - NSMaxX([fGroupLabel frame]);
[fGroupPopUp setFrame: groupPopUpFrame];
[fPriorityLabel setFrame: priorityLabelFrame];
[fPriorityPopUp setFrame: priorityPopUpFrame];
-
+
const CGFloat minButtonWidth = 82.0;
const CGFloat oldAddButtonWidth = [fAddButton bounds].size.width;
const CGFloat oldCancelButtonWidth = [fCancelButton bounds].size.width;
cancelButtonFrame.origin.x -= addButtonWidthIncrease + (buttonWidth - oldCancelButtonWidth);
[fAddButton setFrame: addButtonFrame];
[fCancelButton setFrame: cancelButtonFrame];
-
+
[fStartCheck sizeToFit];
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
-
+
[fDestination release];
-
+
[super dealloc];
}
[panel setCanChooseFiles: NO];
[panel setCanChooseDirectories: YES];
[panel setCanCreateDirectories: YES];
-
+
[panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the download folder for \"%@\"",
"Add -> select destination folder"), [fTorrent name]]];
-
+
[panel beginSheetModalForWindow: [self window] completionHandler: ^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton)
[self setDestinationPath: [[[panel URLs] objectAtIndex: 0] path] determinationType:TorrentDeterminationUserSpecified];
[alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Add torrent -> same name -> button")];
[alert addButtonWithTitle: NSLocalizedString(@"Add", "Add torrent -> same name -> button")];
[alert setShowsSuppressionButton: YES];
-
+
[alert beginSheetModalForWindow: [self window] modalDelegate: self
didEndSelector: @selector(sameNameAlertDidEnd:returnCode:contextInfo:) contextInfo: nil];
}
if (![fGroupPopUp selectItemWithTag: fGroupValue])
{
fGroupValue = -1;
- fGroupDeterminationType = TorrentDeterminationAutomatic;
+ fGroupDeterminationType = TorrentDeterminationAutomatic;
[fGroupPopUp selectItemWithTag: fGroupValue];
}
}
- (void) confirmAdd
{
[fTorrent setGroupValue: fGroupValue determinationType: fGroupDeterminationType];
-
+
if ([fStartCheck state] == NSOnState)
[fTorrent startTransfer];
-
+
[self close];
[fController askOpenMagnetConfirmed: self add: YES]; //ensure last, since it releases this controller
}
{
destination = [destination stringByExpandingTildeInPath];
if (!fDestination || ![fDestination isEqualToString: destination])
- {
+ {
[fDestination release];
fDestination = [destination retain];
-
+
[fTorrent changeDownloadFolderBeforeUsing: fDestination determinationType: determinationType];
}
-
+
[fLocationField setStringValue: [fDestination stringByAbbreviatingWithTildeInPath]];
[fLocationField setToolTip: fDestination];
-
+
ExpandedPathToIconTransformer * iconTransformer = [[ExpandedPathToIconTransformer alloc] init];
[fLocationImageView setImage: [iconTransformer transformedValue: fDestination]];
[iconTransformer release];
- (void) changeGroupValue: (id) sender
{
NSInteger previousGroup = fGroupValue;
- fGroupValue = [sender tag];
+ fGroupValue = [sender tag];
fGroupDeterminationType = TorrentDeterminationUserSpecified;
if ([[GroupsController groups] usesCustomDownloadLocationForIndex: fGroupValue])
{
if ([[alert suppressionButton] state] == NSOnState)
[[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningFolderDataSameName"];
-
+
[alert release];
-
+
if (returnCode == NSAlertSecondButtonReturn)
[self performSelectorOnMainThread: @selector(confirmAdd) withObject: nil waitUntilDone: NO];
}
IBOutlet NSButton * fStartCheck, * fDeleteCheck;
IBOutlet NSPopUpButton * fGroupPopUp, * fPriorityPopUp;
IBOutlet NSProgressIndicator * fVerifyIndicator;
-
+
IBOutlet NSTextField * fFileFilterField;
IBOutlet NSButton * fCheckAllButton, *fUncheckAllButton;
-
+
IBOutlet FileOutlineController * fFileController;
IBOutlet NSScrollView * fFileScrollView;
-
+
Controller * fController;
-
+
Torrent * fTorrent;
NSString * fDestination, * fTorrentFile;
BOOL fLockDestination;
-
+
BOOL fDeleteTorrentEnableInitially, fCanToggleDelete;
NSInteger fGroupValue;
-
+
NSTimer * fTimer;
-
+
TorrentDeterminationType fGroupValueDetermination;
}
fTorrent = torrent;
fDestination = [[path stringByExpandingTildeInPath] retain];
fLockDestination = lockDestination;
-
+
fController = controller;
-
+
fTorrentFile = [[torrentFile stringByExpandingTildeInPath] retain];
-
+
fDeleteTorrentEnableInitially = deleteTorrent;
fCanToggleDelete = canToggleDelete;
-
+
fGroupValue = [torrent groupValue];
fGroupValueDetermination = TorrentDeterminationAutomatic;
- (void) awakeFromNib
{
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateCheckButtons:) name: @"TorrentFileCheckChange" object: fTorrent];
-
+
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateGroupMenu:) name: @"UpdateGroups" object: nil];
-
+
[fFileController setTorrent: fTorrent];
-
+
NSString * name = [fTorrent name];
[[self window] setTitle: name];
[fNameField setStringValue: name];
[fNameField setToolTip: name];
-
+
[fIconView setImage: [fTorrent icon]];
-
+
if (![fTorrent isFolder])
{
[fFileFilterField setHidden: YES];
[fCheckAllButton setHidden: YES];
[fUncheckAllButton setHidden: YES];
-
+
NSRect scrollFrame = [fFileScrollView frame];
const CGFloat diff = NSMinY([fFileScrollView frame]) - NSMinY([fFileFilterField frame]);
scrollFrame.origin.y -= diff;
}
else
[self updateCheckButtons: nil];
-
+
[self setGroupsMenu];
[fGroupPopUp selectItemWithTag: fGroupValue];
-
+
NSInteger priorityIndex;
switch ([fTorrent priority])
{
priorityIndex = POPUP_PRIORITY_NORMAL;
}
[fPriorityPopUp selectItemAtIndex: priorityIndex];
-
+
[fStartCheck setState: [[NSUserDefaults standardUserDefaults] boolForKey: @"AutoStartDownload"] ? NSOnState : NSOffState];
-
+
[fDeleteCheck setState: fDeleteTorrentEnableInitially ? NSOnState : NSOffState];
[fDeleteCheck setEnabled: fCanToggleDelete];
-
+
if (fDestination)
[self setDestinationPath: fDestination determinationType: (fLockDestination ? TorrentDeterminationUserSpecified : TorrentDeterminationAutomatic)];
else
[fLocationField setStringValue: @""];
[fLocationImageView setImage: nil];
}
-
+
fTimer = [[NSTimer scheduledTimerWithTimeInterval: UPDATE_SECONDS target: self
selector: @selector(updateFiles) userInfo: nil repeats: YES] retain];
[self updateFiles];
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
-
+
[fTimer invalidate];
[fTimer release];
-
+
[fDestination release];
[fTorrentFile release];
-
+
[super dealloc];
}
[panel setCanChooseFiles: NO];
[panel setCanChooseDirectories: YES];
[panel setCanCreateDirectories: YES];
-
+
[panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the download folder for \"%@\"",
"Add -> select destination folder"), [fTorrent name]]];
-
+
[panel beginSheetModalForWindow: [self window] completionHandler: ^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton)
{
[alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Add torrent -> same name -> button")];
[alert addButtonWithTitle: NSLocalizedString(@"Add", "Add torrent -> same name -> button")];
[alert setShowsSuppressionButton: YES];
-
+
[alert beginSheetModalForWindow: [self window] modalDelegate: self
didEndSelector: @selector(sameNameAlertDidEnd:returnCode:contextInfo:) contextInfo: nil];
}
[fTimer invalidate];
[fTimer release];
fTimer = nil;
-
+
[fFileController setTorrent: nil]; //avoid a crash when window tries to update
-
+
[fController askOpenConfirmed: self add: NO];
return YES;
}
const NSInteger filesCheckState = [fTorrent checkForFiles: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTorrent fileCount])]];
[fCheckAllButton setEnabled: filesCheckState != NSOnState]; //if anything is unchecked
[fUncheckAllButton setEnabled: ![fTorrent allDownloaded]]; //if there are any checked files that aren't finished
-
+
//status field
NSString * fileString;
NSInteger count = [fTorrent fileCount];
[NSString formattedUInteger: count]];
else
fileString = NSLocalizedString(@"1 file", "Add torrent -> info");
-
+
NSString * selectedString = [NSString stringWithFormat: NSLocalizedString(@"%@ selected", "Add torrent -> info"),
[NSString stringForFileSize: [fTorrent totalSizeSelected]]];
-
+
statusString = [NSString stringWithFormat: @"%@, %@ (%@)", fileString, statusString, selectedString];
}
-
+
[fStatusField setStringValue: statusString];
}
if (![fGroupPopUp selectItemWithTag: fGroupValue])
{
fGroupValue = -1;
- fGroupValueDetermination = TorrentDeterminationAutomatic;
+ fGroupValueDetermination = TorrentDeterminationAutomatic;
[fGroupPopUp selectItemWithTag: fGroupValue];
}
}
- (void) updateFiles
{
[fTorrent update];
-
+
[fFileController refresh];
-
+
[self updateCheckButtons: nil]; //call in case button state changed by checking
-
+
if ([fTorrent isChecking])
{
const BOOL waiting = [fTorrent isCheckingWaiting];
if (fTorrentFile && fCanToggleDelete && [fDeleteCheck state] == NSOnState)
[Torrent trashFile: fTorrentFile error: nil];
-
+
if ([fStartCheck state] == NSOnState)
[fTorrent startTransfer];
-
+
[fFileController setTorrent: nil]; //avoid a crash when window tries to update
-
+
[self close];
[fController askOpenConfirmed: self add: YES]; //ensure last, since it releases this controller
}
{
destination = [destination stringByExpandingTildeInPath];
if (!fDestination || ![fDestination isEqualToString: destination])
- {
+ {
[fDestination release];
fDestination = [destination retain];
-
+
[fTorrent changeDownloadFolderBeforeUsing: fDestination determinationType: determinationType];
}
-
+
[fLocationField setStringValue: [fDestination stringByAbbreviatingWithTildeInPath]];
[fLocationField setToolTip: fDestination];
-
+
ExpandedPathToIconTransformer * iconTransformer = [[ExpandedPathToIconTransformer alloc] init];
[fLocationImageView setImage: [iconTransformer transformedValue: fDestination]];
[iconTransformer release];
{
if ([[alert suppressionButton] state] == NSOnState)
[[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningFolderDataSameName"];
-
+
[alert release];
-
+
if (returnCode == NSAlertSecondButtonReturn)
[self performSelectorOnMainThread: @selector(confirmAdd) withObject: nil waitUntilDone: NO];
}
@interface BadgeView : NSView
{
tr_session * fLib;
-
+
NSMutableDictionary * fAttributes;
-
+
CGFloat fDownloadRate, fUploadRate;
BOOL fQuitting;
}
if ((self = [super init]))
{
fLib = lib;
-
+
fDownloadRate = 0.0;
fUploadRate = 0.0;
fQuitting = NO;
//only needs update if the badges were displayed or are displayed now
if (fDownloadRate == downloadRate && fUploadRate == uploadRate)
return NO;
-
+
fDownloadRate = downloadRate;
fUploadRate = uploadRate;
return YES;
- (void) drawRect: (NSRect) rect
{
[[NSApp applicationIconImage] drawInRect: rect fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0];
-
+
if (fQuitting)
{
NSImage * quitBadge = [NSImage imageNamed: @"QuitBadge"];
atHeight: (NSHeight(rect) - [quitBadge size].height) * 0.5 adjustForQuit: YES];
return;
}
-
+
const BOOL upload = fUploadRate >= 0.1,
download = fDownloadRate >= 0.1;
CGFloat bottom = 0.0;
NSShadow * stringShadow = [[NSShadow alloc] init];
[stringShadow setShadowOffset: NSMakeSize(2.0, -2.0)];
[stringShadow setShadowBlurRadius: 4.0];
-
+
fAttributes = [[NSMutableDictionary alloc] initWithCapacity: 3];
[fAttributes setObject: [NSColor whiteColor] forKey: NSForegroundColorAttributeName];
[fAttributes setObject: stringShadow forKey: NSShadowAttributeName];
-
+
[stringShadow release];
}
-
+
NSRect badgeRect;
badgeRect.size = [badge size];
badgeRect.origin.x = 0.0;
badgeRect.origin.y = height;
-
+
[badge drawInRect: badgeRect fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0];
-
+
//make sure text fits on the badge
CGFloat fontSize = 26.0;
NSSize stringSize;
stringSize = [string sizeWithAttributes: fAttributes];
fontSize -= 1.0;
} while (NSWidth(badgeRect) < stringSize.width);
-
+
//string is in center of image
NSRect stringRect;
stringRect.origin.x = NSMidX(badgeRect) - stringSize.width * 0.5;
stringRect.origin.y = NSMidY(badgeRect) - stringSize.height * 0.5 + (quit ? 2.0 : 1.0); //adjust for shadow, extra for quit
stringRect.size = stringSize;
-
+
[string drawInRect: stringRect withAttributes: fAttributes];
}
@interface Badger : NSObject
{
tr_session * fLib;
-
+
NSMutableSet * fHashes;
}
if ((self = [super init]))
{
fLib = lib;
-
+
BadgeView * view = [[BadgeView alloc] initWithLib: lib];
[[NSApp dockTile] setContentView: view];
[view release];
-
+
fHashes = [[NSMutableSet alloc] init];
}
-
+
return self;
}
? downloadRate : 0.0;
const CGFloat displayUlRate = [[NSUserDefaults standardUserDefaults] boolForKey: @"BadgeUploadRate"]
? uploadRate : 0.0;
-
+
//only update if the badged values change
if ([(BadgeView *)[[NSApp dockTile] contentView] setRatesWithDownload: displayDlRate upload: displayUlRate])
[[NSApp dockTile] display];
- (void) addCompletedTorrent: (Torrent *) torrent
{
NSParameterAssert(torrent != nil);
-
+
[fHashes addObject: [torrent hashString]];
[[NSApp dockTile] setBadgeLabel: [NSString formattedUInteger: [fHashes count]]];
}
@interface BlocklistDownloader : NSObject <NSURLDownloadDelegate>
{
NSURLDownload * fDownload;
-
+
BlocklistDownloaderViewController * fViewController;
-
+
NSString * fDestination;
NSUInteger fCurrentSize;
long long fExpectedSize;
-
+
blocklistDownloadState fState;
}
fBLDownloader = [[BlocklistDownloader alloc] init];
[fBLDownloader startDownload];
}
-
+
return fBLDownloader;
}
- (void) cancelDownload
{
[fViewController setFinished];
-
+
[fDownload cancel];
-
+
[[BlocklistScheduler scheduler] updateSchedule];
-
+
fBLDownloader = nil;
[self release];
}
- (void) download: (NSURLDownload *) download didReceiveResponse: (NSURLResponse *) response
{
fState = BLOCKLIST_DL_DOWNLOADING;
-
+
fCurrentSize = 0;
fExpectedSize = [response expectedContentLength];
-
+
[fViewController setStatusProgressForCurrentSize: fCurrentSize expectedSize: fExpectedSize];
}
- (void) download: (NSURLDownload *) download didFailWithError: (NSError *) error
{
[fViewController setFailed: [error localizedDescription]];
-
+
[[NSUserDefaults standardUserDefaults] setObject: [NSDate date] forKey: @"BlocklistNewLastUpdate"];
[[BlocklistScheduler scheduler] updateSchedule];
-
+
fBLDownloader = nil;
[self release];
}
- (void) downloadDidFinish: (NSURLDownload *) download
{
fState = BLOCKLIST_DL_PROCESSING;
-
+
[fViewController setStatusProcessing];
-
+
NSAssert(fDestination != nil, @"the blocklist file destination has not been specified");
-
+
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self decompressBlocklist];
-
+
dispatch_async(dispatch_get_main_queue(), ^{
const int count = tr_blocklistSetContent([(Controller *)[NSApp delegate] sessionHandle], [fDestination UTF8String]);
-
+
//delete downloaded file
[[NSFileManager defaultManager] removeItemAtPath: fDestination error: NULL];
-
+
if (count > 0)
[fViewController setFinished];
else
[fViewController setFailed: NSLocalizedString(@"The specified blocklist file did not contain any valid rules.",
"blocklist fail message")];
-
+
//update last updated date for schedule
NSDate * date = [NSDate date];
[[NSUserDefaults standardUserDefaults] setObject: date forKey: @"BlocklistNewLastUpdate"];
[[NSUserDefaults standardUserDefaults] setObject: date forKey: @"BlocklistNewLastUpdateSuccess"];
[[BlocklistScheduler scheduler] updateSchedule];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"BlocklistUpdated" object: nil];
-
+
fBLDownloader = nil;
[self release];
});
- (void) startDownload
{
fState = BLOCKLIST_DL_START;
-
+
[[BlocklistScheduler scheduler] cancelSchedule];
-
+
NSString * urlString = [[NSUserDefaults standardUserDefaults] stringForKey: @"BlocklistURL"];
if (!urlString)
urlString = @"";
else if (![urlString isEqualToString: @""] && [urlString rangeOfString: @"://"].location == NSNotFound)
urlString = [@"http://" stringByAppendingString: urlString];
-
+
NSURLRequest * request = [NSURLRequest requestWithURL: [NSURL URLWithString: urlString]];
-
+
fDownload = [[NSURLDownload alloc] initWithRequest: request delegate: self];
}
//.gz, .tar.gz, .tgz, and .bgz will be decompressed by NSURLDownload for us. However, we have to do .zip files manually.
- (void) decompressBlocklist
{
- if ([[[fDestination pathExtension] lowercaseString] isEqualToString: @"zip"]) {
- BOOL success = NO;
-
+ if ([[[fDestination pathExtension] lowercaseString] isEqualToString: @"zip"]) {
+ BOOL success = NO;
+
NSString * workingDirectory = [fDestination stringByDeletingLastPathComponent];
-
- //First, perform the actual unzipping
- NSTask * unzip = [[NSTask alloc] init];
- [unzip setLaunchPath: @"/usr/bin/unzip"];
- [unzip setCurrentDirectoryPath: workingDirectory];
- [unzip setArguments: [NSArray arrayWithObjects:
+
+ //First, perform the actual unzipping
+ NSTask * unzip = [[NSTask alloc] init];
+ [unzip setLaunchPath: @"/usr/bin/unzip"];
+ [unzip setCurrentDirectoryPath: workingDirectory];
+ [unzip setArguments: [NSArray arrayWithObjects:
@"-o", /* overwrite */
@"-q", /* quiet! */
fDestination, /* source zip file */
@"-d", workingDirectory, /*destination*/
nil]];
-
- @try
- {
- [unzip launch];
- [unzip waitUntilExit];
-
- if ([unzip terminationStatus] == 0)
- success = YES;
- }
- @catch(id exc)
- {
- success = NO;
- }
- [unzip release];
-
- if (success) {
- //Now find out what file we actually extracted; don't just assume it matches the zipfile's name
- NSTask *zipinfo;
-
- zipinfo = [[NSTask alloc] init];
- [zipinfo setLaunchPath: @"/usr/bin/zipinfo"];
- [zipinfo setArguments: [NSArray arrayWithObjects:
+
+ @try
+ {
+ [unzip launch];
+ [unzip waitUntilExit];
+
+ if ([unzip terminationStatus] == 0)
+ success = YES;
+ }
+ @catch(id exc)
+ {
+ success = NO;
+ }
+ [unzip release];
+
+ if (success) {
+ //Now find out what file we actually extracted; don't just assume it matches the zipfile's name
+ NSTask *zipinfo;
+
+ zipinfo = [[NSTask alloc] init];
+ [zipinfo setLaunchPath: @"/usr/bin/zipinfo"];
+ [zipinfo setArguments: [NSArray arrayWithObjects:
@"-1", /* just the filename */
fDestination, /* source zip file */
nil]];
- [zipinfo setStandardOutput: [NSPipe pipe]];
-
- @try
- {
- NSFileHandle * zipinfoOutput = [[zipinfo standardOutput] fileHandleForReading];
-
- [zipinfo launch];
- [zipinfo waitUntilExit];
-
- NSString * actualFilename = [[[NSString alloc] initWithData: [zipinfoOutput readDataToEndOfFile]
+ [zipinfo setStandardOutput: [NSPipe pipe]];
+
+ @try
+ {
+ NSFileHandle * zipinfoOutput = [[zipinfo standardOutput] fileHandleForReading];
+
+ [zipinfo launch];
+ [zipinfo waitUntilExit];
+
+ NSString * actualFilename = [[[NSString alloc] initWithData: [zipinfoOutput readDataToEndOfFile]
encoding: NSUTF8StringEncoding] autorelease];
- actualFilename = [actualFilename stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
- NSString * newBlocklistPath = [workingDirectory stringByAppendingPathComponent: actualFilename];
-
- //Finally, delete the ZIP file; we're done with it, and we'll return the unzipped blocklist
- [[NSFileManager defaultManager] removeItemAtPath: fDestination error: NULL];
-
+ actualFilename = [actualFilename stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ NSString * newBlocklistPath = [workingDirectory stringByAppendingPathComponent: actualFilename];
+
+ //Finally, delete the ZIP file; we're done with it, and we'll return the unzipped blocklist
+ [[NSFileManager defaultManager] removeItemAtPath: fDestination error: NULL];
+
[fDestination release];
fDestination = [newBlocklistPath retain];
- }
+ }
@catch(id exc) {}
- [zipinfo release];
- }
- }
+ [zipinfo release];
+ }
+ }
}
@end
@class PrefsController;
@interface BlocklistDownloaderViewController : NSObject
-{
+{
PrefsController * fPrefsController;
-
+
IBOutlet NSWindow * fStatusWindow;
IBOutlet NSProgressIndicator * fProgressBar;
IBOutlet NSTextField * fTextField;
- (void) awakeFromNib
{
[fButton setTitle: NSLocalizedString(@"Cancel", "Blocklist -> cancel button")];
-
+
const CGFloat oldWidth = NSWidth([fButton frame]);
[fButton sizeToFit];
NSRect buttonFrame = [fButton frame];
buttonFrame.size.width += 12.0; //sizeToFit sizes a bit too small
buttonFrame.origin.x -= NSWidth(buttonFrame) - oldWidth;
[fButton setFrame: buttonFrame];
-
+
[fProgressBar setUsesThreadedAnimation: YES];
[fProgressBar startAnimation: self];
}
if (expectedSize != NSURLResponseUnknownLength)
{
[fProgressBar setIndeterminate: NO];
-
+
NSString * substring = [NSString stringForFilePartialSize: currentSize fullSize: expectedSize];
string = [string stringByAppendingFormat: @" (%@)", substring];
[fProgressBar setDoubleValue: (double)currentSize / expectedSize];
}
else
string = [string stringByAppendingFormat: @" (%@)", [NSString stringForFileSize: currentSize]];
-
+
[fTextField setStringValue: string];
}
//change to indeterminate while processing
[fProgressBar setIndeterminate: YES];
[fProgressBar startAnimation: self];
-
+
[fTextField setStringValue: [NSLocalizedString(@"Processing blocklist", "Blocklist -> message") stringByAppendingEllipsis]];
[fButton setEnabled: NO];
}
{
[NSApp endSheet: fStatusWindow];
[fStatusWindow orderOut: self];
-
+
fBLViewController = nil;
[self release];
}
{
[NSApp endSheet: fStatusWindow];
[fStatusWindow orderOut: self];
-
+
NSAlert * alert = [[[NSAlert alloc] init] autorelease];
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Blocklist -> button")];
[alert setMessageText: NSLocalizedString(@"Download of the blocklist failed.", "Blocklist -> message")];
[alert setAlertStyle: NSWarningAlertStyle];
-
+
[alert setInformativeText: error];
-
+
[alert beginSheetModalForWindow: [fPrefsController window] modalDelegate: self
didEndSelector: @selector(failureSheetClosed:returnCode:contextInfo:) contextInfo: nil];
}
{
fPrefsController = prefsController;
}
-
+
return self;
}
{
//load window and show as sheet
[NSBundle loadNibNamed: @"BlocklistStatusWindow" owner: self];
-
+
BlocklistDownloader * downloader = [BlocklistDownloader downloader];
[downloader setViewController: self]; //do before showing the sheet to ensure it doesn't slide out with placeholder text
-
+
[NSApp beginSheet: fStatusWindow modalForWindow: [fPrefsController window] modalDelegate: nil didEndSelector: nil contextInfo: nil];
}
- (void) failureSheetClosed: (NSAlert *) alert returnCode: (NSInteger) code contextInfo: (void *) info
{
[[alert window] orderOut: self];
-
+
fBLViewController = nil;
[self release];
}
{
if (!fScheduler)
fScheduler = [[BlocklistScheduler alloc] init];
-
+
return fScheduler;
}
{
if ([BlocklistDownloader isRunning])
return;
-
+
[self cancelSchedule];
-
+
NSString * blocklistURL;
if (![[NSUserDefaults standardUserDefaults] boolForKey: @"BlocklistNew"]
|| !((blocklistURL = [[NSUserDefaults standardUserDefaults] stringForKey: @"BlocklistURL"]) &&
![blocklistURL isEqualToString: @""])
|| ![[NSUserDefaults standardUserDefaults] boolForKey: @"BlocklistAutoUpdate"])
return;
-
+
NSDate * lastUpdateDate = [[NSUserDefaults standardUserDefaults] objectForKey: @"BlocklistNewLastUpdate"];
if (lastUpdateDate)
lastUpdateDate = [lastUpdateDate dateByAddingTimeInterval: FULL_WAIT];
NSDate * closeDate = [NSDate dateWithTimeIntervalSinceNow: SMALL_DELAY];
-
+
NSDate * useDate = lastUpdateDate ? [lastUpdateDate laterDate: closeDate] : closeDate;
-
+
fTimer = [[NSTimer alloc] initWithFireDate: useDate interval: 0 target: self selector: @selector(runUpdater) userInfo: nil repeats: NO];
-
+
//current run loop usually means a second update won't work
NSRunLoop * loop = [NSRunLoop mainRunLoop];
[loop addTimer: fTimer forMode: NSDefaultRunLoopMode];
dispatch_once(&onceToken, ^{
fDefaultController = [[BonjourController alloc] init];
});
-
+
return fDefaultController;
}
- (void) startWithPort: (int) port
{
[self stop];
-
+
NSMutableString * serviceName = [NSMutableString stringWithFormat: @"Transmission (%@ - %@)", NSUserName(), [[NSHost currentHost] localizedName]];
if ([serviceName length] > BONJOUR_SERVICE_NAME_MAX_LENGTH)
[serviceName deleteCharactersInRange: NSMakeRange(BONJOUR_SERVICE_NAME_MAX_LENGTH, [serviceName length] - BONJOUR_SERVICE_NAME_MAX_LENGTH)];
-
+
fService = [[NSNetService alloc] initWithDomain: @"" type: @"_http._tcp." name: serviceName port: port];
[fService setDelegate: self];
-
+
[fService publish];
}
NSMenuItem * menuItem = [[NSMenuItem alloc] initWithTitle: [self label] action: [self action] keyEquivalent: @""];
[menuItem setTarget: [self target]];
[menuItem setEnabled: [[self target] validateToolbarItem: self]];
-
+
return [menuItem autorelease];
}
- (void) setEnabled: (BOOL) flag
{
[super setEnabled: flag];
-
+
NSColor * color = flag ? [NSColor controlTextColor] : [NSColor disabledControlTextColor];
[self setTextColor: color];
}
@interface Controller : NSObject <GrowlApplicationBridgeDelegate, NSURLDownloadDelegate, NSUserNotificationCenterDelegate, NSPopoverDelegate, NSSharingServiceDelegate, NSSharingServicePickerDelegate, NSSoundDelegate, NSToolbarDelegate, NSWindowDelegate, QLPreviewPanelDataSource, QLPreviewPanelDelegate, VDKQueueDelegate>
{
tr_session * fLib;
-
+
NSMutableArray * fTorrents, * fDisplayedTorrents;
-
+
PrefsController * fPrefsController;
InfoWindowController * fInfoController;
MessageWindowController * fMessageController;
-
+
NSUserDefaults * fDefaults;
-
+
NSString * fConfigDirectory;
-
+
IBOutlet NSWindow * fWindow;
DragOverlayWindow * fOverlayWindow;
IBOutlet TorrentTableView * fTableView;
io_connect_t fRootPort;
NSTimer * fTimer;
-
+
VDKQueue * fFileWatcherQueue;
-
+
IBOutlet NSMenuItem * fOpenIgnoreDownloadFolder;
IBOutlet NSButton * fActionButton, * fSpeedLimitButton, * fClearCompletedButton;
IBOutlet NSTextField * fTotalTorrentsField;
-
+
StatusBarController * fStatusBar;
-
+
FilterBarController * fFilterBar;
IBOutlet NSMenuItem * fNextFilterItem;
-
+
IBOutlet NSMenuItem * fNextInfoTabItem, * fPrevInfoTabItem;
-
+
IBOutlet NSMenu * fSortMenu;
-
+
IBOutlet NSMenu * fGroupsSetMenu, * fGroupsSetContextMenu;
-
+
IBOutlet NSMenu * fShareMenu, * fShareContextMenu;
IBOutlet NSMenuItem * fShareMenuItem, * fShareContextMenuItem; // remove when dropping 10.6
-
+
QLPreviewPanel * fPreviewPanel;
BOOL fQuitting;
BOOL fQuitRequested;
BOOL fPauseOnLaunch;
-
+
Badger * fBadger;
-
+
NSMutableArray * fAutoImportedNames;
NSTimer * fAutoImportTimer;
-
+
NSMutableDictionary * fPendingTorrentDownloads;
-
+
NSMutableSet * fAddingTransfers;
-
+
NSMutableSet * fAddWindows;
URLSheetWindowController * fUrlSheetController;
-
+
BOOL fGlobalPopoverShown;
BOOL fSoundPlaying;
}
{
if ([krFilePath length] == 0 || ![fileManager fileExistsAtPath: krFilePath])
continue;
-
+
if (![fileManager removeItemAtPath: krFilePath error: NULL])
NSLog(@"Unable to remove ransomware file at %@, please do so manually", krFilePath);
}
[alert setInformativeText: NSLocalizedString(@"There is already a copy of Transmission running. "
"This copy cannot be opened until that instance is quit.", "Transmission already running alert -> message")];
[alert setAlertStyle: NSCriticalAlertStyle];
-
+
[alert runModal];
[alert release];
-
+
//kill ourselves right away
exit(0);
}
-
+
[[NSUserDefaults standardUserDefaults] registerDefaults: [NSDictionary dictionaryWithContentsOfFile:
[[NSBundle mainBundle] pathForResource: @"Defaults" ofType: @"plist"]]];
-
+
//set custom value transformers
ExpandedPathToPathTransformer * pathTransformer = [[[ExpandedPathToPathTransformer alloc] init] autorelease];
[NSValueTransformer setValueTransformer: pathTransformer forName: @"ExpandedPathToPathTransformer"];
-
+
ExpandedPathToIconTransformer * iconTransformer = [[[ExpandedPathToIconTransformer alloc] init] autorelease];
[NSValueTransformer setValueTransformer: iconTransformer forName: @"ExpandedPathToIconTransformer"];
-
+
//cover our asses
if ([[NSUserDefaults standardUserDefaults] boolForKey: @"WarningLegal"])
{
" You and you alone are fully responsible for exercising proper judgement and abiding by your local laws.",
"Legal alert -> message")];
[alert setAlertStyle: NSInformationalAlertStyle];
-
+
if ([alert runModal] == NSAlertSecondButtonReturn)
exit(0);
[alert release];
-
+
[[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningLegal"];
}
}
if ((self = [super init]))
{
fDefaults = [NSUserDefaults standardUserDefaults];
-
+
//checks for old version speeds of -1
if ([fDefaults integerForKey: @"UploadLimit"] < 0)
{
[fDefaults removeObjectForKey: @"DownloadLimit"];
[fDefaults setBool: NO forKey: @"CheckDownload"];
}
-
+
//upgrading from versions < 2.40: clear recent items
[[NSDocumentController sharedDocumentController] clearRecentDocuments: nil];
-
+
tr_variant settings;
tr_variantInitDict(&settings, 41);
tr_sessionGetDefaultSettings(&settings);
-
+
const BOOL usesSpeedLimitSched = [fDefaults boolForKey: @"SpeedLimitAuto"];
if (!usesSpeedLimitSched)
tr_variantDictAddBool(&settings, TR_KEY_alt_speed_enabled, [fDefaults boolForKey: @"SpeedLimit"]);
-
+
tr_variantDictAddInt(&settings, TR_KEY_alt_speed_up, [fDefaults integerForKey: @"SpeedLimitUploadLimit"]);
tr_variantDictAddInt(&settings, TR_KEY_alt_speed_down, [fDefaults integerForKey: @"SpeedLimitDownloadLimit"]);
-
+
tr_variantDictAddBool(&settings, TR_KEY_alt_speed_time_enabled, [fDefaults boolForKey: @"SpeedLimitAuto"]);
tr_variantDictAddInt(&settings, TR_KEY_alt_speed_time_begin, [PrefsController dateToTimeSum:
[fDefaults objectForKey: @"SpeedLimitAutoOnDate"]]);
tr_variantDictAddInt(&settings, TR_KEY_alt_speed_time_end, [PrefsController dateToTimeSum:
[fDefaults objectForKey: @"SpeedLimitAutoOffDate"]]);
tr_variantDictAddInt(&settings, TR_KEY_alt_speed_time_day, [fDefaults integerForKey: @"SpeedLimitAutoDay"]);
-
+
tr_variantDictAddInt(&settings, TR_KEY_speed_limit_down, [fDefaults integerForKey: @"DownloadLimit"]);
tr_variantDictAddBool(&settings, TR_KEY_speed_limit_down_enabled, [fDefaults boolForKey: @"CheckDownload"]);
tr_variantDictAddInt(&settings, TR_KEY_speed_limit_up, [fDefaults integerForKey: @"UploadLimit"]);
tr_variantDictAddBool(&settings, TR_KEY_speed_limit_up_enabled, [fDefaults boolForKey: @"CheckUpload"]);
-
+
//hidden prefs
if ([fDefaults objectForKey: @"BindAddressIPv4"])
tr_variantDictAddStr(&settings, TR_KEY_bind_address_ipv4, [[fDefaults stringForKey: @"BindAddressIPv4"] UTF8String]);
if ([fDefaults objectForKey: @"BindAddressIPv6"])
tr_variantDictAddStr(&settings, TR_KEY_bind_address_ipv6, [[fDefaults stringForKey: @"BindAddressIPv6"] UTF8String]);
-
+
tr_variantDictAddBool(&settings, TR_KEY_blocklist_enabled, [fDefaults boolForKey: @"BlocklistNew"]);
if ([fDefaults objectForKey: @"BlocklistURL"])
tr_variantDictAddStr(&settings, TR_KEY_blocklist_url, [[fDefaults stringForKey: @"BlocklistURL"] UTF8String]);
tr_variantDictAddInt(&settings, TR_KEY_message_level, TR_LOG_DEBUG);
tr_variantDictAddInt(&settings, TR_KEY_peer_limit_global, [fDefaults integerForKey: @"PeersTotal"]);
tr_variantDictAddInt(&settings, TR_KEY_peer_limit_per_torrent, [fDefaults integerForKey: @"PeersTorrent"]);
-
+
const BOOL randomPort = [fDefaults boolForKey: @"RandomPort"];
tr_variantDictAddBool(&settings, TR_KEY_peer_port_random_on_start, randomPort);
if (!randomPort)
tr_variantDictAddInt(&settings, TR_KEY_peer_port, [fDefaults integerForKey: @"BindPort"]);
-
+
//hidden pref
if ([fDefaults objectForKey: @"PeerSocketTOS"])
tr_variantDictAddStr(&settings, TR_KEY_peer_socket_tos, [[fDefaults stringForKey: @"PeerSocketTOS"] UTF8String]);
-
+
tr_variantDictAddBool(&settings, TR_KEY_pex_enabled, [fDefaults boolForKey: @"PEXGlobal"]);
tr_variantDictAddBool(&settings, TR_KEY_port_forwarding_enabled, [fDefaults boolForKey: @"NatTraversal"]);
tr_variantDictAddBool(&settings, TR_KEY_queue_stalled_enabled, [fDefaults boolForKey: @"CheckStalled"]);
tr_variantDictAddBool(&settings, TR_KEY_script_torrent_done_enabled, [fDefaults boolForKey: @"DoneScriptEnabled"]);
tr_variantDictAddStr(&settings, TR_KEY_script_torrent_done_filename, [[fDefaults stringForKey: @"DoneScriptPath"] UTF8String]);
tr_variantDictAddBool(&settings, TR_KEY_utp_enabled, [fDefaults boolForKey: @"UTPGlobal"]);
-
-
+
+
NSString * kbString, * mbString, * gbString, * tbString;
if ([NSApp isOnMountainLionOrBetter])
{
NSByteCountFormatter * unitFormatter = [[NSByteCountFormatterMtLion alloc] init];
[unitFormatter setIncludesCount: NO];
[unitFormatter setAllowsNonnumericFormatting: NO];
-
+
[unitFormatter setAllowedUnits: NSByteCountFormatterUseKB];
kbString = [unitFormatter stringFromByteCount: 17]; //use a random value to avoid possible pluralization issues with 1 or 0 (an example is if we use 1 for bytes, we'd get "byte" when we'd want "bytes" for the generic libtransmission value at least)
-
+
[unitFormatter setAllowedUnits: NSByteCountFormatterUseMB];
mbString = [unitFormatter stringFromByteCount: 17];
-
+
[unitFormatter setAllowedUnits: NSByteCountFormatterUseGB];
gbString = [unitFormatter stringFromByteCount: 17];
-
+
[unitFormatter setAllowedUnits: NSByteCountFormatterUseTB];
tbString = [unitFormatter stringFromByteCount: 17];
-
+
[unitFormatter release];
}
else
gbString = NSLocalizedString(@"GB", "file/memory size - gigabytes");
tbString = NSLocalizedString(@"TB", "file/memory size - terabytes");
}
-
+
tr_formatter_size_init(1000, [kbString UTF8String],
[mbString UTF8String],
[gbString UTF8String],
[mbString UTF8String],
[gbString UTF8String],
[tbString UTF8String]);
-
+
const char * configDir = tr_getDefaultConfigDir("Transmission");
fLib = tr_sessionInit(configDir, YES, &settings);
tr_variantFree(&settings);
-
+
fConfigDirectory = [[NSString alloc] initWithUTF8String: configDir];
-
+
[NSApp setDelegate: self];
-
+
//register for magnet URLs (has to be in init)
[[NSAppleEventManager sharedAppleEventManager] setEventHandler: self andSelector: @selector(handleOpenContentsEvent:replyEvent:)
forEventClass: kInternetEventClass andEventID: kAEGetURL];
-
+
fTorrents = [[NSMutableArray alloc] init];
fDisplayedTorrents = [[NSMutableArray alloc] init];
-
+
fInfoController = [[InfoWindowController alloc] init];
-
+
//needs to be done before init-ing the prefs controller
fFileWatcherQueue = [[VDKQueue alloc] init];
[fFileWatcherQueue setDelegate: self];
-
+
fPrefsController = [[PrefsController alloc] initWithHandle: fLib];
-
+
fQuitting = NO;
fGlobalPopoverShown = NO;
fSoundPlaying = NO;
-
+
tr_sessionSetAltSpeedFunc(fLib, altSpeedToggledCallback, self);
if (usesSpeedLimitSched)
[fDefaults setBool: tr_sessionUsesAltSpeed(fLib) forKey: @"SpeedLimit"];
-
+
tr_sessionSetRPCCallback(fLib, rpcCallback, self);
-
+
[GrowlApplicationBridge setGrowlDelegate: self];
-
+
[[SUUpdater sharedUpdater] setDelegate: self];
fQuitRequested = NO;
-
+
fPauseOnLaunch = (GetCurrentKeyModifiers() & (optionKey | rightOptionKey)) != 0;
}
return self;
[toolbar setDisplayMode: NSToolbarDisplayModeIconOnly];
[fWindow setToolbar: toolbar];
[toolbar release];
-
+
[fWindow setDelegate: self]; //do manually to avoid placement issue
-
+
[fWindow makeFirstResponder: fTableView];
[fWindow setExcludedFromWindowsMenu: YES];
-
+
//set table size
const BOOL small = [fDefaults boolForKey: @"SmallView"];
if (small)
[fTableView setRowHeight: ROW_HEIGHT_SMALL];
[fTableView setUsesAlternatingRowBackgroundColors: !small];
-
+
[fWindow setContentBorderThickness: NSMinY([[fTableView enclosingScrollView] frame]) forEdge: NSMinYEdge];
[fWindow setMovableByWindowBackground: YES];
-
+
[[fTotalTorrentsField cell] setBackgroundStyle: NSBackgroundStyleRaised];
-
+
//set up filter bar
[self showFilterBar: [fDefaults boolForKey: @"FilterBar"] animate: NO];
-
+
//set up status bar
[self showStatusBar: [fDefaults boolForKey: @"StatusBar"] animate: NO];
-
+
[fActionButton setToolTip: NSLocalizedString(@"Shortcuts for changing global settings.",
"Main window -> 1st bottom left button (action) tooltip")];
[fSpeedLimitButton setToolTip: NSLocalizedString(@"Speed Limit overrides the total bandwidth limits with its own limits.",
"Main window -> 2nd bottom left button (turtle) tooltip")];
[fClearCompletedButton setToolTip: NSLocalizedString(@"Remove all transfers that have completed seeding.",
"Main window -> 3rd bottom left button (remove all) tooltip")];
-
+
[fTableView registerForDraggedTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE]];
[fWindow registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType, NSURLPboardType, nil]];
-
+
//sort the sort menu items (localization is from strings file)
NSMutableArray * sortMenuItems = [NSMutableArray arrayWithCapacity: 7];
NSUInteger sortMenuIndex = 0;
++sortMenuIndex;
}
}
-
+
[sortMenuItems sortUsingDescriptors: [NSArray arrayWithObject: [NSSortDescriptor sortDescriptorWithKey: @"title" ascending: YES selector: @selector(localizedCompare:)]]];
-
+
for (NSMenuItem * item in sortMenuItems)
[fSortMenu insertItem: item atIndex: sortMenuIndex++];
-
+
//you would think this would be called later in this method from updateUI, but it's not reached in awakeFromNib
//this must be called after showStatusBar:
[fStatusBar updateWithDownload: 0.0 upload: 0.0];
//this should also be after the rest of the setup
[self updateForAutoSize];
-
+
//register for sleep notifications
IONotificationPortRef notify;
io_object_t iterator;
CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify), kCFRunLoopCommonModes);
else
NSLog(@"Could not IORegisterForSystemPower");
-
+
//load previous transfers
NSString * historyFile = [fConfigDirectory stringByAppendingPathComponent: TRANSFER_PLIST];
NSArray * history = [NSArray arrayWithContentsOfFile: historyFile];
if ((history = [fDefaults arrayForKey: @"History"]))
[fDefaults removeObjectForKey: @"History"];
}
-
+
if (history)
{
NSMutableArray * waitToStartTorrents = [NSMutableArray arrayWithCapacity: (([history count] > 0 && !fPauseOnLaunch) ? [history count]-1 : 0)]; //theoretical max without doing a lot of work
-
+
for (NSDictionary * historyItem in history)
{
Torrent * torrent;
if ((torrent = [[Torrent alloc] initWithHistory: historyItem lib: fLib forcePause: fPauseOnLaunch]))
{
[fTorrents addObject: torrent];
-
+
NSNumber * waitToStart;
if (!fPauseOnLaunch && (waitToStart = [historyItem objectForKey: @"WaitToStart"]) && [waitToStart boolValue])
[waitToStartTorrents addObject: torrent];
-
+
[torrent release];
}
}
-
+
//now that all are loaded, let's set those in the queue to waiting
for (Torrent * torrent in waitToStartTorrents)
[torrent startTransfer];
}
-
+
fBadger = [[Badger alloc] initWithLib: fLib];
-
+
if ([NSApp isOnMountainLionOrBetter])
[[NSUserNotificationCenterMtLion defaultUserNotificationCenter] setDelegate: self];
-
+
// remove Share menu items
if (![NSApp isOnMountainLionOrBetter]) {
[[fShareMenuItem menu] removeItem:fShareMenuItem];
[[fShareContextMenuItem menu] removeItem:fShareContextMenuItem];
}
-
+
//observe notifications
NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
-
+
[nc addObserver: self selector: @selector(updateUI)
name: @"UpdateUI" object: nil];
-
+
[nc addObserver: self selector: @selector(torrentFinishedDownloading:)
name: @"TorrentFinishedDownloading" object: nil];
-
+
[nc addObserver: self selector: @selector(torrentRestartedDownloading:)
name: @"TorrentRestartedDownloading" object: nil];
-
+
[nc addObserver: self selector: @selector(torrentFinishedSeeding:)
name: @"TorrentFinishedSeeding" object: nil];
-
+
[nc addObserver: self selector: @selector(applyFilter)
name: kTorrentDidChangeGroupNotification object: nil];
-
+
//avoids need of setting delegate
[nc addObserver: self selector: @selector(torrentTableViewSelectionDidChange:)
name: NSOutlineViewSelectionDidChangeNotification object: fTableView];
-
+
[nc addObserver: self selector: @selector(changeAutoImport)
name: @"AutoImportSettingChange" object: nil];
-
+
[nc addObserver: self selector: @selector(updateForAutoSize)
name: @"AutoSizeSettingChange" object: nil];
-
+
[nc addObserver: self selector: @selector(updateForExpandCollape)
name: @"OutlineExpandCollapse" object: nil];
-
+
[nc addObserver: fWindow selector: @selector(makeKeyWindow)
name: @"MakeWindowKey" object: nil];
-
+
#warning rename
[nc addObserver: self selector: @selector(fullUpdateUI)
name: @"UpdateQueue" object: nil];
-
+
[nc addObserver: self selector: @selector(applyFilter)
name: @"ApplyFilter" object: nil];
-
+
//open newly created torrent file
[nc addObserver: self selector: @selector(beginCreateFile:)
name: @"BeginCreateTorrentFile" object: nil];
-
+
//open newly created torrent file
[nc addObserver: self selector: @selector(openCreatedFile:)
name: @"OpenCreatedTorrentFile" object: nil];
-
+
[nc addObserver: self selector: @selector(applyFilter)
name: @"UpdateGroups" object: nil];
selector: @selector(updateUI) userInfo: nil repeats: YES] retain];
[[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSModalPanelRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSEventTrackingRunLoopMode];
-
+
[self applyFilter];
-
+
[fWindow makeKeyAndOrderFront: nil];
-
+
if ([fDefaults boolForKey: @"InfoVisible"])
[self showInfo: nil];
}
- (void) applicationDidFinishLaunching: (NSNotification *) notification
{
[NSApp setServicesProvider: self];
-
+
//register for dock icon drags (has to be in applicationDidFinishLaunching: to work)
[[NSAppleEventManager sharedAppleEventManager] setEventHandler: self andSelector: @selector(handleOpenContentsEvent:replyEvent:)
forEventClass: kCoreEventClass andEventID: kAEOpenContents];
-
+
//if we were opened from a user notification, do the corresponding action
if ([NSApp isOnMountainLionOrBetter])
{
if (launchNotification)
[self userNotificationCenter: nil didActivateNotification: launchNotification];
}
-
+
//auto importing
[self checkAutoImportDirectory];
-
+
//registering the Web UI to Bonjour
if ([fDefaults boolForKey: @"RPC"] && [fDefaults boolForKey: @"RPCWebDiscovery"])
[[BonjourController defaultController] startWithPort: [fDefaults integerForKey: @"RPCPort"]];
-
+
//shamelessly ask for donations
if ([fDefaults boolForKey: @"WarningDonate"])
{
tr_session_stats stats;
tr_sessionGetCumulativeStats(fLib, &stats);
const BOOL firstLaunch = stats.sessionCount <= 1;
-
+
NSDate * lastDonateDate = [fDefaults objectForKey: @"DonateAskDate"];
const BOOL timePassed = !lastDonateDate || (-1 * [lastDonateDate timeIntervalSinceNow]) >= DONATE_NAG_TIME;
-
+
if (!firstLaunch && timePassed)
{
[fDefaults setObject: [NSDate date] forKey: @"DonateAskDate"];
-
+
NSAlert * alert = [[NSAlert alloc] init];
[alert setMessageText: NSLocalizedString(@"Support open-source indie software", "Donation beg -> title")];
-
+
NSString * donateMessage = [NSString stringWithFormat: @"%@\n\n%@",
NSLocalizedString(@"Transmission is a full-featured torrent application."
" A lot of time and effort have gone into development, coding, and refinement."
" If you enjoy using it, please consider showing your love with a donation.", "Donation beg -> message"),
NSLocalizedString(@"Donate or not, there will be no difference to your torrenting experience.", "Donation beg -> message")];
-
+
[alert setInformativeText: donateMessage];
[alert setAlertStyle: NSInformationalAlertStyle];
-
+
[alert addButtonWithTitle: [NSLocalizedString(@"Donate", "Donation beg -> button") stringByAppendingEllipsis]];
NSButton * noDonateButton = [alert addButtonWithTitle: NSLocalizedString(@"Nope", "Donation beg -> button")];
[noDonateButton setKeyEquivalent: @"\e"]; //escape key
-
+
const BOOL allowNeverAgain = lastDonateDate != nil; //hide the "don't show again" check the first time - give them time to try the app
[alert setShowsSuppressionButton: allowNeverAgain];
if (allowNeverAgain)
[[alert suppressionButton] setTitle: NSLocalizedString(@"Don't bug me about this ever again.", "Donation beg -> button")];
-
+
const NSInteger donateResult = [alert runModal];
if (donateResult == NSAlertFirstButtonReturn)
[self linkDonate: self];
-
+
if (allowNeverAgain)
[fDefaults setBool: ([[alert suppressionButton] state] != NSOnState) forKey: @"WarningDonate"];
-
+
[alert release];
}
}
NSWindow * mainWindow = [NSApp mainWindow];
if (!mainWindow || ![mainWindow isVisible])
[fWindow makeKeyAndOrderFront: nil];
-
+
return NO;
}
if (![torrent allDownloaded])
downloading++;
}
-
+
if ([fDefaults boolForKey: @"CheckQuitDownloading"] ? downloading > 0 : active > 0)
{
NSString * message = active == 1
return NSTerminateLater;
}
}
-
+
return NSTerminateNow;
}
- (void) applicationWillTerminate: (NSNotification *) notification
{
fQuitting = YES;
-
+
//stop the Bonjour service
if ([BonjourController defaultControllerExists])
[[BonjourController defaultController] stop];
//stop blocklist download
if ([BlocklistDownloader isRunning])
[[BlocklistDownloader downloader] cancelDownload];
-
+
//stop timers and notification checking
[[NSNotificationCenter defaultCenter] removeObserver: self];
-
+
[fTimer invalidate];
[fTimer release];
-
+
if (fAutoImportTimer)
- {
+ {
if ([fAutoImportTimer isValid])
[fAutoImportTimer invalidate];
[fAutoImportTimer release];
}
-
+
[fBadger setQuitting];
-
+
//remove all torrent downloads
if (fPendingTorrentDownloads)
{
}
[fPendingTorrentDownloads release];
}
-
+
//remember window states and close all windows
[fDefaults setBool: [[fInfoController window] isVisible] forKey: @"InfoVisible"];
-
+
if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible])
[[QLPreviewPanel sharedPreviewPanel] updateController];
-
+
for (NSWindow * window in [NSApp windows])
[window close];
-
+
[self showStatusBar: NO animate: NO];
[self showFilterBar: NO animate: NO];
-
+
//save history
[self updateTorrentHistory];
[fTableView saveCollapsedGroups];
-
- //remaining calls the same as dealloc
+
+ //remaining calls the same as dealloc
[fInfoController release];
[fMessageController release];
[fPrefsController release];
-
+
[fStatusBar release];
[fFilterBar release];
-
+
[fTorrents release];
[fDisplayedTorrents release];
-
+
[fAddWindows release];
[fAddingTransfers release];
-
+
[fOverlayWindow release];
[fBadger release];
-
+
[fAutoImportedNames release];
-
+
[fPreviewPanel release];
-
+
[fConfigDirectory release];
-
+
[fFileWatcherQueue release];
-
+
//complete cleanup
tr_sessionClose(fLib);
}
}
else
urlString = [directObject stringValue];
-
+
if (urlString)
[self openURL: urlString];
}
if ([[suggestedName pathExtension] caseInsensitiveCompare: @"torrent"] != NSOrderedSame)
{
[download cancel];
-
+
[fPendingTorrentDownloads removeObjectForKey: [[download request] URL]];
if ([fPendingTorrentDownloads count] == 0)
{
[fPendingTorrentDownloads release];
fPendingTorrentDownloads = nil;
}
-
+
NSRunAlertPanel(NSLocalizedString(@"Torrent download failed", "Download not a torrent -> title"),
[NSString stringWithFormat: NSLocalizedString(@"It appears that the file \"%@\" from %@ is not a torrent file.",
"Download not a torrent -> message"), suggestedName,
[[[[download request] URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding]],
NSLocalizedString(@"OK", "Download not a torrent -> button"), nil, nil);
-
+
[download release];
}
else
"Torrent download failed -> message"),
[[[[download request] URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding],
[error localizedDescription]], NSLocalizedString(@"OK", "Torrent download failed -> button"), nil, nil);
-
+
[fPendingTorrentDownloads removeObjectForKey: [[download request] URL]];
if ([fPendingTorrentDownloads count] == 0)
{
[fPendingTorrentDownloads release];
fPendingTorrentDownloads = nil;
}
-
+
[download release];
}
- (void) downloadDidFinish: (NSURLDownload *) download
{
NSString * path = [[fPendingTorrentDownloads objectForKey: [[download request] URL]] objectForKey: @"Path"];
-
+
[self openFiles: [NSArray arrayWithObject: path] addType: ADD_URL forcePath: nil];
-
+
//delete the torrent file after opening
[[NSFileManager defaultManager] removeItemAtPath: path error: NULL];
-
+
[fPendingTorrentDownloads removeObjectForKey: [[download request] URL]];
if ([fPendingTorrentDownloads count] == 0)
{
[fPendingTorrentDownloads release];
fPendingTorrentDownloads = nil;
}
-
+
[download release];
}
deleteTorrentFile = [fDefaults boolForKey: @"DeleteOriginalTorrent"];
canToggleDelete = YES;
}
-
+
for (NSString * torrentPath in filenames)
{
//ensure torrent doesn't already exist
tr_ctor * ctor = tr_ctorNew(fLib);
tr_ctorSetMetainfoFromFile(ctor, [torrentPath UTF8String]);
-
+
tr_info info;
const tr_parse_result result = tr_torrentParse(ctor, &info);
tr_ctorFree(ctor);
-
+
if (result != TR_PARSE_OK)
{
if (result == TR_PARSE_DUPLICATE)
}
else
NSAssert2(NO, @"Unknown error code (%d) when attempting to open \"%@\"", result, torrentPath);
-
+
tr_metainfoFree(&info);
continue;
}
-
+
//determine download location
NSString * location;
BOOL lockDestination = NO; //don't override the location with a group location if it has a hardcoded path
location = [torrentPath stringByDeletingLastPathComponent];
else
location = nil;
-
+
//determine to show the options window
const BOOL showWindow = type == ADD_SHOW_OPTIONS || ([fDefaults boolForKey: @"DownloadAsk"]
&& (info.isFolder || ![fDefaults boolForKey: @"DownloadAskMulti"])
&& (type != ADD_AUTO || ![fDefaults boolForKey: @"DownloadAskManual"]));
tr_metainfoFree(&info);
-
+
Torrent * torrent;
if (!(torrent = [[Torrent alloc] initWithPath: torrentPath location: location
deleteTorrentFile: showWindow ? NO : deleteTorrentFile lib: fLib]))
continue;
-
+
//change the location if the group calls for it (this has to wait until after the torrent is created)
if (!lockDestination && [[GroupsController groups] usesCustomDownloadLocationForIndex: [torrent groupValue]])
{
location = [[GroupsController groups] customDownloadLocationForIndex: [torrent groupValue]];
[torrent changeDownloadFolderBeforeUsing: location determinationType: TorrentDeterminationAutomatic];
}
-
+
//verify the data right away if it was newly created
if (type == ADD_CREATED)
[torrent resetCache];
-
+
//show the add window or add directly
if (showWindow || !location)
{
lockDestination: lockDestination controller: self torrentFile: torrentPath
deleteTorrentCheckEnableInitially: deleteTorrentFile canToggleDelete: canToggleDelete];
[addController showWindow: self];
-
+
if (!fAddWindows)
fAddWindows = [[NSMutableSet alloc] init];
[fAddWindows addObject: addController];
{
if ([fDefaults boolForKey: @"AutoStartDownload"])
[torrent startTransfer];
-
+
[torrent update];
[fTorrents addObject: torrent];
[torrent release];
-
+
if (!fAddingTransfers)
fAddingTransfers = [[NSMutableSet alloc] init];
[fAddingTransfers addObject: torrent];
- (void) askOpenConfirmed: (AddWindowController *) addController add: (BOOL) add
{
Torrent * torrent = [addController torrent];
-
+
if (add)
{
[torrent setQueuePosition: [fTorrents count]];
-
+
[torrent update];
[fTorrents addObject: torrent];
[torrent release];
-
+
if (!fAddingTransfers)
fAddingTransfers = [[NSMutableSet alloc] init];
[fAddingTransfers addObject: torrent];
-
+
[self fullUpdateUI];
}
else
[torrent closeRemoveTorrent: NO];
[torrent release];
}
-
+
[fAddWindows removeObject: addController];
if ([fAddWindows count] == 0)
{
[self duplicateOpenMagnetAlert: address transferName: name];
return;
}
-
+
//determine download location
NSString * location = nil;
if ([fDefaults boolForKey: @"DownloadLocationConstant"])
location = [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath];
-
+
Torrent * torrent;
if (!(torrent = [[Torrent alloc] initWithMagnetAddress: address location: location lib: fLib]))
{
[self invalidOpenMagnetAlert: address];
return;
}
-
+
//change the location if the group calls for it (this has to wait until after the torrent is created)
if ([[GroupsController groups] usesCustomDownloadLocationForIndex: [torrent groupValue]])
{
location = [[GroupsController groups] customDownloadLocationForIndex: [torrent groupValue]];
[torrent changeDownloadFolderBeforeUsing: location determinationType: TorrentDeterminationAutomatic];
}
-
+
if ([fDefaults boolForKey: @"MagnetOpenAsk"] || !location)
{
AddMagnetWindowController * addController = [[AddMagnetWindowController alloc] initWithTorrent: torrent destination: location
controller: self];
[addController showWindow: self];
-
+
if (!fAddWindows)
fAddWindows = [[NSMutableSet alloc] init];
[fAddWindows addObject: addController];
{
if ([fDefaults boolForKey: @"AutoStartDownload"])
[torrent startTransfer];
-
+
[torrent update];
[fTorrents addObject: torrent];
[torrent release];
-
+
if (!fAddingTransfers)
fAddingTransfers = [[NSMutableSet alloc] init];
[fAddingTransfers addObject: torrent];
- (void) askOpenMagnetConfirmed: (AddMagnetWindowController *) addController add: (BOOL) add
{
Torrent * torrent = [addController torrent];
-
+
if (add)
{
[torrent setQueuePosition: [fTorrents count]];
-
+
[torrent update];
[fTorrents addObject: torrent];
[torrent release];
-
+
if (!fAddingTransfers)
fAddingTransfers = [[NSMutableSet alloc] init];
[fAddingTransfers addObject: torrent];
-
+
[self fullUpdateUI];
}
else
[torrent closeRemoveTorrent: NO];
[torrent release];
}
-
+
[fAddWindows removeObject: addController];
if ([fAddWindows count] == 0)
{
- (void) openFilesWithDict: (NSDictionary *) dictionary
{
[self openFiles: [dictionary objectForKey: @"Filenames"] addType: [[dictionary objectForKey: @"AddType"] intValue] forcePath: nil];
-
+
[dictionary release];
}
- (void) openShowSheet: (id) sender
{
NSOpenPanel * panel = [NSOpenPanel openPanel];
-
+
[panel setAllowsMultipleSelection: YES];
[panel setCanChooseFiles: YES];
[panel setCanChooseDirectories: NO];
-
+
[panel setAllowedFileTypes: [NSArray arrayWithObjects: @"org.bittorrent.torrent", @"torrent", nil]];
-
+
[panel beginSheetModalForWindow: fWindow completionHandler: ^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton)
{
NSMutableArray * filenames = [NSMutableArray arrayWithCapacity: [[panel URLs] count]];
for (NSURL * url in [panel URLs])
[filenames addObject: [url path]];
-
+
NSDictionary * dictionary = [[NSDictionary alloc] initWithObjectsAndKeys: filenames, @"Filenames",
[NSNumber numberWithInt: sender == fOpenIgnoreDownloadFolder ? ADD_SHOW_OPTIONS : ADD_MANUAL], @"AddType", nil];
[self performSelectorOnMainThread: @selector(openFilesWithDict:) withObject: dictionary waitUntilDone: NO];
{
if (![fDefaults boolForKey: @"WarningInvalidOpen"])
return;
-
+
NSAlert * alert = [[NSAlert alloc] init];
[alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"\"%@\" is not a valid torrent file.",
"Open invalid alert -> title"), filename]];
"Open invalid alert -> message")];
[alert setAlertStyle: NSWarningAlertStyle];
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Open invalid alert -> button")];
-
+
[alert runModal];
if ([[alert suppressionButton] state] == NSOnState)
[fDefaults setBool: NO forKey: @"WarningInvalidOpen"];
{
if (![fDefaults boolForKey: @"WarningInvalidOpen"])
return;
-
+
NSAlert * alert = [[NSAlert alloc] init];
[alert setMessageText: NSLocalizedString(@"Adding magnetized transfer failed.", "Magnet link failed -> title")];
[alert setInformativeText: [NSString stringWithFormat: NSLocalizedString(@"There was an error when adding the magnet link \"%@\"."
" The transfer will not occur.", "Magnet link failed -> message"), address]];
[alert setAlertStyle: NSWarningAlertStyle];
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Magnet link failed -> button")];
-
+
[alert runModal];
if ([[alert suppressionButton] state] == NSOnState)
[fDefaults setBool: NO forKey: @"WarningInvalidOpen"];
{
if (![fDefaults boolForKey: @"WarningDuplicate"])
return;
-
+
NSAlert * alert = [[NSAlert alloc] init];
[alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"A transfer of \"%@\" already exists.",
"Open duplicate alert -> title"), name]];
[alert setAlertStyle: NSWarningAlertStyle];
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Open duplicate alert -> button")];
[alert setShowsSuppressionButton: YES];
-
+
[alert runModal];
if ([[alert suppressionButton] state])
[fDefaults setBool: NO forKey: @"WarningDuplicate"];
{
if (![fDefaults boolForKey: @"WarningDuplicate"])
return;
-
+
NSAlert * alert = [[NSAlert alloc] init];
if (name)
[alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"A transfer of \"%@\" already exists.",
[alert setAlertStyle: NSWarningAlertStyle];
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Open duplicate magnet alert -> button")];
[alert setShowsSuppressionButton: YES];
-
+
[alert runModal];
if ([[alert suppressionButton] state])
[fDefaults setBool: NO forKey: @"WarningDuplicate"];
else
urlString = [@"http://" stringByAppendingString: urlString];
}
-
+
NSURLRequest * request = [NSURLRequest requestWithURL: [NSURL URLWithString: urlString]
cachePolicy: NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval: 60];
-
+
if ([fPendingTorrentDownloads objectForKey: [request URL]])
{
NSLog(@"Already downloading %@", [request URL]);
return;
}
-
+
NSURLDownload * download = [[NSURLDownload alloc] initWithRequest: request delegate: self];
-
+
if (!fPendingTorrentDownloads)
fPendingTorrentDownloads = [[NSMutableDictionary alloc] init];
[fPendingTorrentDownloads setObject: [NSMutableDictionary dictionaryWithObject: download forKey: @"Download"] forKey: [request URL]];
if (!fUrlSheetController)
{
fUrlSheetController = [[URLSheetWindowController alloc] initWithController: self];
-
+
[NSApp beginSheet: [fUrlSheetController window] modalForWindow: fWindow modalDelegate: self didEndSelector: @selector(urlSheetDidEnd:returnCode:contextInfo:) contextInfo: nil];
}
}
NSString * urlString = [fUrlSheetController urlString];
[self performSelectorOnMainThread: @selector(openURL:) withObject: urlString waitUntilDone: NO];
}
-
+
[fUrlSheetController release];
fUrlSheetController = nil;
}
- (void) resumeAllTorrents: (id) sender
{
NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [fTorrents count]];
-
+
for (Torrent * torrent in fTorrents)
if (![torrent isFinishedSeeding])
[torrents addObject: torrent];
-
+
[self resumeTorrents: torrents];
}
{
for (Torrent * torrent in torrents)
[torrent startTransfer];
-
+
[self fullUpdateUI];
}
- (void) resumeWaitingTorrents: (id) sender
{
NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [fTorrents count]];
-
+
for (Torrent * torrent in fTorrents)
if ([torrent waitingToStart])
[torrents addObject: torrent];
-
+
[self resumeTorrentsNoWait: torrents];
}
//iterate through instead of all at once to ensure no conflicts
for (Torrent * torrent in torrents)
[torrent startTransferNoQueue];
-
+
[self fullUpdateUI];
}
for (Torrent * torrent in torrents)
if ([torrent waitingToStart])
[torrent stopTransfer];
-
+
for (Torrent * torrent in torrents)
[torrent stopTransfer];
-
+
[self fullUpdateUI];
}
{
NSDictionary * dict = @{ @"Torrents" : torrents,
@"DeleteData" : @(deleteData) };
-
+
NSString * title, * message;
-
+
const NSUInteger selected = [torrents count];
if (selected == 1)
{
NSString * torrentName = [(Torrent *)[torrents objectAtIndex: 0] name];
-
+
if (deleteData)
title = [NSString stringWithFormat:
NSLocalizedString(@"Are you sure you want to remove \"%@\" from the transfer list"
title = [NSString stringWithFormat:
NSLocalizedString(@"Are you sure you want to remove \"%@\" from the transfer list?",
"Removal confirm panel -> title"), torrentName];
-
+
message = NSLocalizedString(@"This transfer is active."
" Once removed, continuing the transfer will require the torrent file or magnet link.",
"Removal confirm panel -> message");
title = [NSString stringWithFormat:
NSLocalizedString(@"Are you sure you want to remove %@ transfers from the transfer list?",
"Removal confirm panel -> title"), [NSString formattedUInteger: selected]];
-
+
if (selected == active)
message = [NSString stringWithFormat: NSLocalizedString(@"There are %@ active transfers.",
"Removal confirm panel -> message part 1"), [NSString formattedUInteger: active]];
NSLocalizedString(@"Once removed, continuing the transfers will require the torrent files or magnet links.",
"Removal confirm panel -> message part 2")];
}
-
+
NSBeginAlertSheet(title, NSLocalizedString(@"Remove", "Removal confirm panel -> button"),
NSLocalizedString(@"Cancel", "Removal confirm panel -> button"), nil, fWindow, self,
nil, @selector(removeSheetDidEnd:returnCode:contextInfo:), [dict retain], @"%@", message);
return;
}
}
-
+
[self confirmRemoveTorrents: torrents deleteData: deleteData];
}
//don't want any of these starting then stopping
if ([torrent waitingToStart])
[torrent stopTransfer];
-
+
//let's expand all groups that have removed items - they either don't exist anymore, are already expanded, or are collapsed (rpc)
[fTableView removeCollapsedGroup: [torrent groupValue]];
-
+
//we can't assume the window is active - RPC removal, for example
[fBadger removeTorrent: torrent];
}
-
+
//#5106 - don't try to remove torrents that have already been removed (fix for a bug, but better safe than crash anyway)
NSIndexSet * indexesToRemove = [torrents indexesOfObjectsWithOptions: NSEnumerationConcurrent passingTest: ^BOOL(Torrent * torrent, NSUInteger idx, BOOL * stop) {
return [fTorrents indexOfObjectIdenticalTo: torrent] != NSNotFound;
{
NSLog(@"trying to remove %ld transfers, but %ld have already been removed", [torrents count], [torrents count] - [indexesToRemove count]);
torrents = [torrents objectsAtIndexes: indexesToRemove];
-
+
if ([indexesToRemove count] == 0)
{
[self fullUpdateUI];
return;
}
}
-
+
[fTorrents removeObjectsInArray: torrents];
-
+
//set up helpers to remove from the table
__block BOOL beganUpdate = NO;
-
+
void (^doTableRemoval)(NSMutableArray *, id) = ^(NSMutableArray * displayedTorrents, id parent) {
NSIndexSet * indexes = [displayedTorrents indexesOfObjectsWithOptions: NSEnumerationConcurrent passingTest: ^(id obj, NSUInteger idx, BOOL * stop) {
return [torrents containsObject: obj];
}];
-
+
if ([indexes count] > 0)
{
if (!beganUpdate)
{
[NSAnimationContext beginGrouping]; //this has to be before we set the completion handler (#4874)
-
+
//we can't closeRemoveTorrent: until it's no longer in the GUI at all
[[NSAnimationContext currentContext] setCompletionHandler: ^{
for (Torrent * torrent in torrents)
[torrent closeRemoveTorrent: deleteData];
}];
-
+
[fTableView beginUpdates];
beganUpdate = YES;
}
-
+
[fTableView removeItemsAtIndexes: indexes inParent: parent withAnimation: NSTableViewAnimationSlideLeft];
[displayedTorrents removeObjectsAtIndexes: indexes];
}
};
-
+
//if not removed from the displayed torrents here, fullUpdateUI might cause a crash
if ([fDisplayedTorrents count] > 0)
{
}
else
doTableRemoval(fDisplayedTorrents, nil);
-
+
if (beganUpdate)
{
[fTableView endUpdates];
[NSAnimationContext endGrouping];
}
}
-
+
if (!beganUpdate)
{
//do here if we're not doing it at the end of the animation
for (Torrent * torrent in torrents)
[torrent closeRemoveTorrent: deleteData];
}
-
+
[self fullUpdateUI];
}
- (void) clearCompleted: (id) sender
{
NSMutableArray * torrents = [NSMutableArray array];
-
+
for (Torrent * torrent in fTorrents)
if ([torrent isFinishedSeeding])
[torrents addObject: torrent];
-
+
if ([fDefaults boolForKey: @"WarningRemoveCompleted"])
{
NSString * message, * info;
NSString * torrentName = [(Torrent *)[torrents objectAtIndex: 0] name];
message = [NSString stringWithFormat: NSLocalizedString(@"Are you sure you want to remove \"%@\" from the transfer list?",
"Remove completed confirm panel -> title"), torrentName];
-
+
info = NSLocalizedString(@"Once removed, continuing the transfer will require the torrent file or magnet link.",
"Remove completed confirm panel -> message");
}
{
message = [NSString stringWithFormat: NSLocalizedString(@"Are you sure you want to remove %@ completed transfers from the transfer list?",
"Remove completed confirm panel -> title"), [NSString formattedUInteger: [torrents count]]];
-
+
info = NSLocalizedString(@"Once removed, continuing the transfers will require the torrent files or magnet links.",
"Remove completed confirm panel -> message");
}
-
+
NSAlert * alert = [[[NSAlert alloc] init] autorelease];
[alert setMessageText: message];
[alert setInformativeText: info];
[alert addButtonWithTitle: NSLocalizedString(@"Remove", "Remove completed confirm panel -> button")];
[alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Remove completed confirm panel -> button")];
[alert setShowsSuppressionButton: YES];
-
+
const NSInteger returnCode = [alert runModal];
if ([[alert suppressionButton] state])
[fDefaults setBool: NO forKey: @"WarningRemoveCompleted"];
-
+
if (returnCode != NSAlertFirstButtonReturn)
return;
}
-
+
[self confirmRemoveTorrents: torrents deleteData: NO];
}
[panel setCanChooseFiles: NO];
[panel setCanChooseDirectories: YES];
[panel setCanCreateDirectories: YES];
-
+
NSInteger count = [torrents count];
if (count == 1)
[panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the new folder for \"%@\".",
else
[panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the new folder for %d data files.",
"Move torrent -> select destination folder"), count]];
-
+
[panel beginSheetModalForWindow: fWindow completionHandler: ^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton)
{
[torrents release];
return;
}
-
+
Torrent * torrent = [torrents objectAtIndex: 0];
-
+
if (![torrent isMagnet] && [[NSFileManager defaultManager] fileExistsAtPath: [torrent torrentLocation]])
{
NSSavePanel * panel = [NSSavePanel savePanel];
[panel setAllowedFileTypes: [NSArray arrayWithObjects: @"org.bittorrent.torrent", @"torrent", nil]];
[panel setExtensionHidden: NO];
-
+
[panel setNameFieldStringValue: [torrent name]];
-
+
[panel beginSheetModalForWindow: fWindow completionHandler: ^(NSInteger result) {
//copy torrent to new location with name of data file
if (result == NSFileHandlingPanelOKButton)
[torrent copyTorrentFileTo: [[panel URL] path]];
-
+
[torrents removeObjectAtIndex: 0];
[self performSelectorOnMainThread: @selector(copyTorrentFileForTorrents:) withObject: torrents waitUntilDone: NO];
}];
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent file copy alert -> button")];
[alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Copy of \"%@\" Cannot Be Created",
"Torrent file copy alert -> title"), [torrent name]]];
- [alert setInformativeText: [NSString stringWithFormat:
+ [alert setInformativeText: [NSString stringWithFormat:
NSLocalizedString(@"The torrent file (%@) cannot be found.", "Torrent file copy alert -> message"),
[torrent torrentLocation]]];
[alert setAlertStyle: NSWarningAlertStyle];
-
+
[alert runModal];
[alert release];
}
-
+
[torrents removeObjectAtIndex: 0];
[self copyTorrentFileForTorrents: torrents];
}
- (void) copyMagnetLinks: (id) sender
{
NSArray * torrents = [fTableView selectedTorrents];
-
+
if ([torrents count] <= 0)
return;
-
+
NSMutableArray * links = [NSMutableArray arrayWithCapacity: [torrents count]];
for (Torrent * torrent in torrents)
[links addObject: [torrent magnetLink]];
-
+
NSString * text = [links componentsJoinedByString: @"\n"];
-
+
NSPasteboard * pb = [NSPasteboard generalPasteboard];
[pb clearContents];
[pb writeObjects: [NSArray arrayWithObject: text]];
if (location)
[paths addObject: [NSURL fileURLWithPath: location]];
}
-
+
if ([paths count] > 0)
[[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: paths];
}
NSArray * selected = [fTableView selectedTorrents];
NSAssert([selected count] == 1, @"1 transfer needs to be selected to rename, but %ld are selected", [selected count]);
Torrent * torrent = [selected objectAtIndex:0];
-
+
[FileRenameSheetController presentSheetForTorrent:torrent modalForWindow: fWindow completionHandler: ^(BOOL didRename) {
if (didRename)
{
dispatch_async(dispatch_get_main_queue(), ^{
[self fullUpdateUI];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"ResetInspector" object: self userInfo: @{ @"Torrent" : torrent }];
});
}
{
for (Torrent * torrent in torrents)
[torrent resetCache];
-
+
[self applyFilter];
}
{
[fInfoController updateInfoStats];
[[fInfoController window] orderFront: nil];
-
+
if ([fInfoController canQuickLook] && [QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible])
[[QLPreviewPanel sharedPreviewPanel] reloadData];
}
-
+
[[fWindow toolbar] validateVisibleItems];
}
- (void) resetInfo
{
[fInfoController setInfoForTorrents: [fTableView selectedTorrents]];
-
+
if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible])
[[QLPreviewPanel sharedPreviewPanel] reloadData];
}
{
if (!fMessageController)
fMessageController = [[MessageWindowController alloc] init];
-
+
return fMessageController;
}
for (Torrent * torrent in fTorrents)
{
[torrent update];
-
+
//pull the upload and download speeds - most consistent by using current stats
dlRate += [torrent downloadRate];
ulRate += [torrent uploadRate];
-
+
anyCompleted |= [torrent isFinishedSeeding];
}
-
+
if (![NSApp isHidden])
{
if ([fWindow isVisible])
{
[self sortTorrents: NO];
-
+
[fStatusBar updateWithDownload: dlRate upload: ulRate];
-
+
[fClearCompletedButton setHidden: !anyCompleted];
}
if ([[fInfoController window] isVisible])
[fInfoController updateInfoStats];
}
-
+
//badge dock
[fBadger updateBadgeWithDownload: dlRate upload: ulRate];
}
[NSString formattedUInteger: totalCount]];
else
totalTorrentsString = NSLocalizedString(@"1 transfer", "Status bar transfer count");
-
+
if (filtering)
{
NSUInteger count = [fTableView numberOfRows]; //have to factor in collapsed rows
if (count > 0 && ![[fDisplayedTorrents objectAtIndex: 0] isKindOfClass: [Torrent class]])
count -= [fDisplayedTorrents count];
-
+
totalTorrentsString = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@", "Status bar transfer count"),
[NSString formattedUInteger: count], totalTorrentsString];
}
-
+
[fTotalTorrentsField setStringValue: totalTorrentsString];
}
{
if (![notification userInfo])
return;
-
+
if ([notification activationType] == NSUserNotificationActivationTypeActionButtonClicked) //reveal
{
Torrent * torrent = [self torrentForHash: [[notification userInfo] objectForKey: @"Hash"]];
row = [fTableView rowForItem: torrent];
}
}
-
+
if (row == -1)
{
//not found - must be filtering
NSAssert([fDefaults boolForKey: @"FilterBar"], @"expected the filter to be enabled");
[fFilterBar reset: YES];
-
+
row = [fTableView rowForItem: torrent];
-
+
//if it's not shown, it has to be in a collapsed row...again
if ([fDefaults boolForKey: @"SortByGroup"])
{
}
}
}
-
+
NSAssert1(row != -1, @"expected a row to be found for torrent %@", torrent);
[self showMainWindow: nil];
- (Torrent *) torrentForHash: (NSString *) hash
{
NSParameterAssert(hash != nil);
-
+
__block Torrent * torrent = nil;
[fTorrents enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(id obj, NSUInteger idx, BOOL * stop) {
if ([[(Torrent *)obj hashString] isEqualToString: hash])
- (void) torrentFinishedDownloading: (NSNotification *) notification
{
Torrent * torrent = [notification object];
-
+
if ([[[notification userInfo] objectForKey: @"WasRunning"] boolValue])
{
if (!fSoundPlaying && [fDefaults boolForKey: @"PlayDownloadSound"])
[sound play];
}
}
-
+
NSString * location = [torrent dataLocation];
-
+
NSString * notificationTitle = NSLocalizedString(@"Download Complete", "notification title");
if ([NSApp isOnMountainLionOrBetter])
{
NSUserNotification * notification = [[NSUserNotificationMtLion alloc] init];
[notification setTitle: notificationTitle];
[notification setInformativeText: [torrent name]];
-
+
[notification setHasActionButton: YES];
[notification setActionButtonTitle: NSLocalizedString(@"Show", "notification button")];
-
+
NSMutableDictionary * userInfo = [NSMutableDictionary dictionaryWithObject: [torrent hashString] forKey: @"Hash"];
if (location)
[userInfo setObject: location forKey: @"Location"];
[notification setUserInfo: userInfo];
-
+
[[NSUserNotificationCenterMtLion defaultUserNotificationCenter] deliverNotification: notification];
[notification release];
}
-
+
NSMutableDictionary * clickContext = [NSMutableDictionary dictionaryWithObjectsAndKeys:
GROWL_DOWNLOAD_COMPLETE, @"Type", nil];
-
+
if (location)
[clickContext setObject: location forKey: @"Location"];
-
+
[GrowlApplicationBridge notifyWithTitle: notificationTitle
description: [torrent name] notificationName: GROWL_DOWNLOAD_COMPLETE
iconData: nil priority: 0 isSticky: NO clickContext: clickContext];
-
+
//NSLog(@"delegate: %@", [[NSUserNotificationCenterMtLion defaultUserNotificationCenter] delegate]);
-
+
if (![fWindow isMainWindow])
[fBadger addCompletedTorrent: torrent];
-
+
//bounce download stack
[[NSDistributedNotificationCenter defaultCenter] postNotificationName: @"com.apple.DownloadFileFinished"
object: [torrent dataLocation]];
}
-
+
[self fullUpdateUI];
}
- (void) torrentFinishedSeeding: (NSNotification *) notification
{
Torrent * torrent = [notification object];
-
+
if (!fSoundPlaying && [fDefaults boolForKey: @"PlaySeedingSound"])
{
NSSound * sound;
[sound play];
}
}
-
+
NSString * location = [torrent dataLocation];
-
+
NSString * notificationTitle = NSLocalizedString(@"Seeding Complete", "notification title");
if ([NSApp isOnMountainLionOrBetter])
{
NSUserNotification * notification = [[NSUserNotificationMtLion alloc] init];
[notification setTitle: notificationTitle];
[notification setInformativeText: [torrent name]];
-
+
[notification setHasActionButton: YES];
[notification setActionButtonTitle: NSLocalizedString(@"Show", "notification button")];
-
+
NSMutableDictionary * userInfo = [NSMutableDictionary dictionaryWithObject: [torrent hashString] forKey: @"Hash"];
if (location)
[userInfo setObject: location forKey: @"Location"];
[notification setUserInfo: userInfo];
-
+
[[NSUserNotificationCenterMtLion defaultUserNotificationCenter] deliverNotification: notification];
[notification release];
}
-
+
NSMutableDictionary * clickContext = [NSMutableDictionary dictionaryWithObject: GROWL_SEEDING_COMPLETE forKey: @"Type"];
-
+
if (location)
[clickContext setObject: location forKey: @"Location"];
-
+
[GrowlApplicationBridge notifyWithTitle: notificationTitle
description: [torrent name] notificationName: GROWL_SEEDING_COMPLETE
iconData: nil priority: 0 isSticky: NO clickContext: clickContext];
-
+
//removing from the list calls fullUpdateUI
if ([torrent removeWhenFinishSeeding])
[self confirmRemoveTorrents: @[ torrent ] deleteData: NO];
{
if (![fWindow isMainWindow])
[fBadger addCompletedTorrent: torrent];
-
+
[self fullUpdateUI];
-
+
if ([[fTableView selectedTorrents] containsObject: torrent])
{
[fInfoController updateInfoStats];
- (void) updateTorrentHistory
{
NSMutableArray * history = [NSMutableArray arrayWithCapacity: [fTorrents count]];
-
+
for (Torrent * torrent in fTorrents)
[history addObject: [torrent history]];
-
+
NSString * historyFile = [fConfigDirectory stringByAppendingPathComponent: TRANSFER_PLIST];
[history writeToFile: historyFile atomically: YES];
}
NSAssert1(NO, @"Unknown sort tag received: %ld", [(NSMenuItem *)sender tag]);
return;
}
-
+
[fDefaults setObject: sortType forKey: @"Sort"];
-
+
[self sortTorrents: YES];
}
{
BOOL sortByGroup = ![fDefaults boolForKey: @"SortByGroup"];
[fDefaults setBool: sortByGroup forKey: @"SortByGroup"];
-
+
[self applyFilter];
}
- (void) sortTorrentsCallUpdates: (BOOL) callUpdates includeQueueOrder: (BOOL) includeQueueOrder
{
const BOOL asc = ![fDefaults boolForKey: @"SortReverse"];
-
+
NSArray * descriptors;
NSSortDescriptor * nameDescriptor = [NSSortDescriptor sortDescriptorWithKey: @"name" ascending: asc selector: @selector(localizedStandardCompare:)];
-
+
NSString * sortType = [fDefaults stringForKey: @"Sort"];
if ([sortType isEqualToString: SORT_STATE])
{
NSSortDescriptor * stateDescriptor = [NSSortDescriptor sortDescriptorWithKey: @"stateSortKey" ascending: !asc],
* progressDescriptor = [NSSortDescriptor sortDescriptorWithKey: @"progress" ascending: !asc],
* ratioDescriptor = [NSSortDescriptor sortDescriptorWithKey: @"ratio" ascending: !asc];
-
+
descriptors = [NSArray arrayWithObjects: stateDescriptor, progressDescriptor, ratioDescriptor, nameDescriptor, nil];
}
else if ([sortType isEqualToString: SORT_PROGRESS])
NSSortDescriptor * progressDescriptor = [NSSortDescriptor sortDescriptorWithKey: @"progress" ascending: asc],
* ratioProgressDescriptor = [NSSortDescriptor sortDescriptorWithKey: @"progressStopRatio" ascending: asc],
* ratioDescriptor = [NSSortDescriptor sortDescriptorWithKey: @"ratio" ascending: asc];
-
+
descriptors = [NSArray arrayWithObjects: progressDescriptor, ratioProgressDescriptor, ratioDescriptor, nameDescriptor, nil];
}
else if ([sortType isEqualToString: SORT_TRACKER])
{
NSSortDescriptor * trackerDescriptor = [NSSortDescriptor sortDescriptorWithKey: @"trackerSortKey" ascending: asc selector: @selector(localizedCaseInsensitiveCompare:)];
-
+
descriptors = [NSArray arrayWithObjects: trackerDescriptor, nameDescriptor, nil];
}
else if ([sortType isEqualToString: SORT_ACTIVITY])
{
NSSortDescriptor * rateDescriptor = [NSSortDescriptor sortDescriptorWithKey: @"totalRate" ascending: !asc];
NSSortDescriptor * activityDescriptor = [NSSortDescriptor sortDescriptorWithKey: @"dateActivityOrAdd" ascending: !asc];
-
+
descriptors = [NSArray arrayWithObjects: rateDescriptor, activityDescriptor, nameDescriptor, nil];
}
else if ([sortType isEqualToString: SORT_DATE])
{
NSSortDescriptor * dateDescriptor = [NSSortDescriptor sortDescriptorWithKey: @"dateAdded" ascending: asc];
-
+
descriptors = [NSArray arrayWithObjects: dateDescriptor, nameDescriptor, nil];
}
else if ([sortType isEqualToString: SORT_SIZE])
{
NSSortDescriptor * sizeDescriptor = [NSSortDescriptor sortDescriptorWithKey: @"size" ascending: asc];
-
+
descriptors = [NSArray arrayWithObjects: sizeDescriptor, nameDescriptor, nil];
}
else if ([sortType isEqualToString: SORT_NAME])
else
{
NSAssert1([sortType isEqualToString: SORT_ORDER], @"Unknown sort type received: %@", sortType);
-
+
if (!includeQueueOrder)
return;
-
+
NSSortDescriptor * orderDescriptor = [NSSortDescriptor sortDescriptorWithKey: @"queuePosition" ascending: asc];
-
+
descriptors = [NSArray arrayWithObject: orderDescriptor];
}
-
+
BOOL beganTableUpdate = !callUpdates;
-
+
//actually sort
if ([fDefaults boolForKey: @"SortByGroup"])
{
}
else
[self rearrangeTorrentTableArray: fDisplayedTorrents forParent: nil withSortDescriptors: descriptors beganTableUpdate: &beganTableUpdate];
-
+
if (beganTableUpdate && callUpdates)
{
[fTableView endUpdates];
if (result != NSOrderedSame)
return result;
}
-
+
return NSOrderedSame;
}];
-
+
if (insertIndex != currentIndex)
{
if (!*beganTableUpdate)
*beganTableUpdate = YES;
[fTableView beginUpdates];
}
-
+
[rearrangeArray moveObjectAtIndex: currentIndex toIndex: insertIndex];
[fTableView moveItemAtIndex: currentIndex inParent: parent toIndex: insertIndex inParent: parent];
}
}
-
+
NSAssert2([rearrangeArray isEqualToArray: [rearrangeArray sortedArrayUsingDescriptors: descriptors]], @"Torrent rearranging didn't work! %@ %@", rearrangeArray, [rearrangeArray sortedArrayUsingDescriptors: descriptors]);
}
filterPause = YES;
else
filterStatus = NO;
-
+
const NSInteger groupFilterValue = [fDefaults integerForKey: @"FilterGroup"];
const BOOL filterGroup = groupFilterValue != GROUP_FILTER_ALL_TAG;
-
+
NSArray * searchStrings = [fFilterBar searchStrings];
if (searchStrings && [searchStrings count] == 0)
searchStrings = nil;
const BOOL filterTracker = searchStrings && [[fDefaults stringForKey: @"FilterSearchType"] isEqualToString: FILTER_TYPE_TRACKER];
-
+
//filter & get counts of each type
NSIndexSet * indexesOfNonFilteredTorrents = [fTorrents indexesOfObjectsWithOptions: NSEnumerationConcurrent passingTest: ^BOOL(Torrent * torrent, NSUInteger idx, BOOL * stop) {
//check status
const BOOL isActive = ![torrent isStalled];
if (isActive)
OSAtomicIncrement32(&active);
-
+
if ([torrent isSeeding])
{
OSAtomicIncrement32(&seeding);
if (filterStatus && !filterPause)
return NO;
}
-
+
//checkGroup
if (filterGroup)
if ([torrent groupValue] != groupFilterValue)
return NO;
-
+
//check text field
if (searchStrings)
{
if (filterTracker)
{
NSArray * trackers = [torrent allTrackersFlat];
-
+
//to count, we need each string in at least 1 tracker
[searchStrings enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(id searchString, NSUInteger idx, BOOL * stop) {
__block BOOL found = NO;
}
}];
}
-
+
if (removeTextField)
return NO;
}
-
+
return YES;
}];
-
+
NSArray * allTorrents = [fTorrents objectsAtIndexes: indexesOfNonFilteredTorrents];
-
+
//set button tooltips
if (fFilterBar)
[fFilterBar setCountAll: [fTorrents count] active: active downloading: downloading seeding: seeding paused: paused];
-
+
//if either the previous or current lists are blank, set its value to the other
const BOOL groupRows = [allTorrents count] > 0 ? [fDefaults boolForKey: @"SortByGroup"] : ([fDisplayedTorrents count] > 0 && [[fDisplayedTorrents objectAtIndex: 0] isKindOfClass: [TorrentGroup class]]);
const BOOL wasGroupRows = [fDisplayedTorrents count] > 0 ? [[fDisplayedTorrents objectAtIndex: 0] isKindOfClass: [TorrentGroup class]] : groupRows;
-
+
#warning could probably be merged with later code somehow
//clear display cache for not-shown torrents
if ([fDisplayedTorrents count] > 0)
if (![allTorrents containsObject: torrent])
[torrent setPreviousFinishedPieces: nil];
};
-
+
if (wasGroupRows)
[fDisplayedTorrents enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(id obj, NSUInteger idx, BOOL * stop) {
[[(TorrentGroup *)obj torrents] enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: removePreviousFinishedPieces];
else
[fDisplayedTorrents enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: removePreviousFinishedPieces];
}
-
+
BOOL beganUpdates = NO;
//don't animate torrents when first launching
[[NSAnimationContext currentContext] setDuration: 0];
});
[NSAnimationContext beginGrouping];
-
+
//add/remove torrents (and rearrange for groups), one by one
if (!groupRows && !wasGroupRows)
{
NSMutableIndexSet * addIndexes = [NSMutableIndexSet indexSet],
* removePreviousIndexes = [NSMutableIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fDisplayedTorrents count])];
-
+
//for each of the torrents to add, find if it already exists (and keep track of those we've already added & those we need to remove)
[allTorrents enumerateObjectsWithOptions: 0 usingBlock: ^(id objAll, NSUInteger previousIndex, BOOL * stop) {
const NSUInteger currentIndex = [fDisplayedTorrents indexOfObjectAtIndexes: removePreviousIndexes options: NSEnumerationConcurrent passingTest: ^(id objDisplay, NSUInteger idx, BOOL *stop) {
else
[removePreviousIndexes removeIndex: currentIndex];
}];
-
+
if ([addIndexes count] > 0 || [removePreviousIndexes count] > 0)
{
beganUpdates = YES;
[fTableView beginUpdates];
-
+
//remove torrents we didn't find
if ([removePreviousIndexes count] > 0)
{
[fDisplayedTorrents removeObjectsAtIndexes: removePreviousIndexes];
[fTableView removeItemsAtIndexes: removePreviousIndexes inParent: nil withAnimation: NSTableViewAnimationSlideDown];
}
-
+
//add new torrents
if ([addIndexes count] > 0)
{
NSIndexSet * newAddIndexes = [allTorrents indexesOfObjectsAtIndexes: addIndexes options: NSEnumerationConcurrent passingTest: ^BOOL(id obj, NSUInteger idx, BOOL * stop) {
return [fAddingTransfers containsObject: obj];
}];
-
+
[addIndexes removeIndexes: newAddIndexes];
-
+
[fDisplayedTorrents addObjectsFromArray: [allTorrents objectsAtIndexes: newAddIndexes]];
[fTableView insertItemsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange([fDisplayedTorrents count] - [newAddIndexes count], [newAddIndexes count])] inParent: nil withAnimation: NSTableViewAnimationSlideLeft];
}
-
+
[fDisplayedTorrents addObjectsFromArray: [allTorrents objectsAtIndexes: addIndexes]];
[fTableView insertItemsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange([fDisplayedTorrents count] - [addIndexes count], [addIndexes count])] inParent: nil withAnimation: NSTableViewAnimationSlideDown];
}
else if (groupRows && wasGroupRows)
{
NSAssert(groupRows && wasGroupRows, @"Should have had group rows and should remain with group rows");
-
+
#warning don't always do?
beganUpdates = YES;
[fTableView beginUpdates];
-
+
NSMutableIndexSet * unusedAllTorrentsIndexes = [NSMutableIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [allTorrents count])];
-
+
NSMutableDictionary * groupsByIndex = [NSMutableDictionary dictionaryWithCapacity: [fDisplayedTorrents count]];
for (TorrentGroup * group in fDisplayedTorrents)
[groupsByIndex setObject: group forKey: [NSNumber numberWithInteger: [group groupIndex]]];
-
+
const NSUInteger originalGroupCount = [fDisplayedTorrents count];
for (NSUInteger index = 0; index < originalGroupCount; ++index)
{
TorrentGroup * group = [fDisplayedTorrents objectAtIndex: index];
-
+
NSMutableIndexSet * removeIndexes = [NSMutableIndexSet indexSet];
-
+
//needs to be a signed integer
for (NSUInteger indexInGroup = 0; indexInGroup < [[group torrents] count]; ++indexInGroup)
{
else
{
BOOL markTorrentAsUsed = YES;
-
+
const NSInteger groupValue = [torrent groupValue];
if (groupValue != [group groupIndex])
{
if ([fDisplayedTorrents indexOfObject: newGroup inRange: NSMakeRange(index+1, originalGroupCount-(index+1))] != NSNotFound)
markTorrentAsUsed = NO;
}
-
+
[[group torrents] removeObjectAtIndex: indexInGroup];
[[newGroup torrents] addObject: torrent];
[fTableView moveItemAtIndex: indexInGroup inParent: group toIndex: [[newGroup torrents] count]-1 inParent: newGroup];
-
+
--indexInGroup;
}
-
+
if (markTorrentAsUsed)
[unusedAllTorrentsIndexes removeIndex: allIndex];
}
}
-
+
if ([removeIndexes count] > 0)
{
[[group torrents] removeObjectsAtIndexes: removeIndexes];
[fTableView removeItemsAtIndexes: removeIndexes inParent: group withAnimation: NSTableViewAnimationEffectFade];
}
}
-
+
//add remaining new torrents
for (Torrent * torrent in [allTorrents objectsAtIndexes: unusedAllTorrentsIndexes])
{
[fTableView insertItemsAtIndexes: [NSIndexSet indexSetWithIndex: [fDisplayedTorrents count]-1] inParent: nil withAnimation: NSTableViewAnimationEffectFade];
[fTableView isGroupCollapsed: groupValue] ? [fTableView collapseItem: group] : [fTableView expandItem: group];
}
-
+
[[group torrents] addObject: torrent];
const BOOL newTorrent = fAddingTransfers && [fAddingTransfers containsObject: torrent];
[fTableView insertItemsAtIndexes: [NSIndexSet indexSetWithIndex: [[group torrents] count]-1] inParent: group withAnimation: newTorrent ? NSTableViewAnimationSlideLeft : NSTableViewAnimationSlideDown];
}
-
+
//remove empty groups
NSIndexSet * removeGroupIndexes = [fDisplayedTorrents indexesOfObjectsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, originalGroupCount)] options: NSEnumerationConcurrent passingTest: ^BOOL(id obj, NSUInteger idx, BOOL * stop) {
return [[(TorrentGroup *)obj torrents] count] == 0;
}];
-
+
if ([removeGroupIndexes count] > 0)
{
[fDisplayedTorrents removeObjectsAtIndexes: removeGroupIndexes];
[fTableView removeItemsAtIndexes: removeGroupIndexes inParent: nil withAnimation: NSTableViewAnimationEffectFade];
}
-
+
//now that all groups are there, sort them - don't insert on the fly in case groups were reordered in prefs
NSSortDescriptor * groupDescriptor = [NSSortDescriptor sortDescriptorWithKey: @"groupOrderValue" ascending: YES];
[self rearrangeTorrentTableArray: fDisplayedTorrents forParent: nil withSortDescriptors: [NSArray arrayWithObject: groupDescriptor] beganTableUpdate: &beganUpdates];
else
{
NSAssert(groupRows != wasGroupRows, @"Trying toggling group-torrent reordering when we weren't expecting to.");
-
+
//set all groups as expanded
[fTableView removeAllCollapsedGroups];
-
+
//since we're not doing this the right way (boo buggy animation), we need to remember selected values
#warning when Lion-only and using views instead of cells, this likely won't be needed
NSArray * selectedValues = [fTableView selectedValues];
-
+
beganUpdates = YES;
[fTableView beginUpdates];
[fTableView removeItemsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fDisplayedTorrents count])] inParent: nil withAnimation: NSTableViewAnimationSlideDown];
-
+
if (groupRows)
{
//a map for quickly finding groups
group = [[[TorrentGroup alloc] initWithGroup: groupValue] autorelease];
[groupsByIndex setObject: group forKey: [NSNumber numberWithInteger: groupValue]];
}
-
+
[[group torrents] addObject: torrent];
}
-
+
[fDisplayedTorrents setArray: [groupsByIndex allValues]];
-
+
//we need the groups to be sorted, and we can do it without moving items in the table, too!
NSSortDescriptor * groupDescriptor = [NSSortDescriptor sortDescriptorWithKey: @"groupOrderValue" ascending: YES];
[fDisplayedTorrents sortUsingDescriptors: [NSArray arrayWithObject: groupDescriptor]];
[fDisplayedTorrents setArray: allTorrents];
[fTableView insertItemsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fDisplayedTorrents count])] inParent: nil withAnimation: NSTableViewAnimationEffectFade];
-
- if (groupRows)
- {
- //actually expand group rows
- for (TorrentGroup * group in fDisplayedTorrents)
- [fTableView expandItem: group];
- }
-
+
+ if (groupRows)
+ {
+ //actually expand group rows
+ for (TorrentGroup * group in fDisplayedTorrents)
+ [fTableView expandItem: group];
+ }
+
if (selectedValues)
[fTableView selectValues: selectedValues];
}
-
+
//sort the torrents (won't sort the groups, though)
[self sortTorrentsCallUpdates: !beganUpdates includeQueueOrder: YES];
if (beganUpdates)
[fTableView endUpdates];
[fTableView setNeedsDisplay: YES];
-
+
[NSAnimationContext endGrouping];
[self resetInfo]; //if group is already selected, but the torrents in it change
-
+
[self setBottomCountText: groupRows || filterStatus || filterGroup || searchStrings];
-
+
[self setWindowSizeToFit];
-
+
if (fAddingTransfers)
{
[fAddingTransfers release];
{
if (fGlobalPopoverShown)
return;
-
+
NSPopover * popover = [[NSPopover alloc] init];
[popover setBehavior: NSPopoverBehaviorTransient];
GlobalOptionsPopoverViewController * viewController = [[GlobalOptionsPopoverViewController alloc] initWithHandle: fLib];
[popover setContentViewController: viewController];
[popover setDelegate: self];
-
+
[popover showRelativeToRect: [sender frame] ofView: sender preferredEdge: NSMaxYEdge];
-
+
[viewController release];
[popover release];
}
{
for (NSInteger i = [menu numberOfItems]-1; i >= 0; i--)
[menu removeItemAtIndex: i];
-
+
NSMenu * groupMenu = [[GroupsController groups] groupMenuWithTarget: self action: @selector(setGroup:) isSmall: NO];
-
+
const NSInteger groupMenuCount = [groupMenu numberOfItems];
for (NSInteger i = 0; i < groupMenuCount; i++)
{
}
else if (menu == fShareMenu || menu == fShareContextMenu) {
[menu removeAllItems];
-
+
for (NSMenuItem * item in [[ShareTorrentFileHelper sharedHelper] menuItems])
{
[menu addItem:item];
for (Torrent * torrent in [fTableView selectedTorrents])
{
[fTableView removeCollapsedGroup: [torrent groupValue]]; //remove old collapsed group
-
+
[torrent setGroupValue: [(NSMenuItem *)sender tag] determinationType: TorrentDeterminationUserSpecified];
}
-
+
[self applyFilter];
[self updateUI];
[self updateTorrentHistory];
[fDefaults setBool: isLimited forKey: @"SpeedLimit"];
[fStatusBar updateSpeedFieldsToolTips];
-
+
if (![[dict objectForKey: @"ByUser"] boolValue])
[GrowlApplicationBridge notifyWithTitle: isLimited ? NSLocalizedString(@"Speed Limit Auto Enabled", "Growl notification title") : NSLocalizedString(@"Speed Limit Auto Disabled", "Growl notification title")
description: NSLocalizedString(@"Bandwidth settings changed", "Growl notification description")
isSticky: NO
clickContext: nil
identifier: GROWL_AUTO_SPEED_LIMIT];
-
+
[dict release];
}
-(void) VDKQueue: (VDKQueue *) queue receivedNotification: (NSString*) notification forPath: (NSString*) fpath
{
//don't assume that just because we're watching for write notification, we'll only receive write notifications
-
+
if (![fDefaults boolForKey: @"AutoImport"] || ![fDefaults stringForKey: @"AutoImportDirectory"])
return;
-
+
if ([fAutoImportTimer isValid])
[fAutoImportTimer invalidate];
[fAutoImportTimer release];
-
+
//check again in 10 seconds in case torrent file wasn't complete
- fAutoImportTimer = [[NSTimer scheduledTimerWithTimeInterval: 10.0 target: self
+ fAutoImportTimer = [[NSTimer scheduledTimerWithTimeInterval: 10.0 target: self
selector: @selector(checkAutoImportDirectory) userInfo: nil repeats: NO] retain];
-
+
[self checkAutoImportDirectory];
}
[fAutoImportTimer invalidate];
[fAutoImportTimer release];
fAutoImportTimer = nil;
-
+
[fAutoImportedNames release];
fAutoImportedNames = nil;
-
+
[self checkAutoImportDirectory];
}
NSString * path;
if (![fDefaults boolForKey: @"AutoImport"] || !(path = [fDefaults stringForKey: @"AutoImportDirectory"]))
return;
-
+
path = [path stringByExpandingTildeInPath];
-
+
NSArray * importedNames;
if (!(importedNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: path error: NULL]))
return;
-
+
//only check files that have not been checked yet
NSMutableArray * newNames = [importedNames mutableCopy];
-
+
if (fAutoImportedNames)
[newNames removeObjectsInArray: fAutoImportedNames];
else
fAutoImportedNames = [[NSMutableArray alloc] init];
[fAutoImportedNames setArray: importedNames];
-
+
for (NSString * file in newNames)
{
if ([file hasPrefix: @"."])
continue;
-
+
NSString * fullFile = [path stringByAppendingPathComponent: file];
-
+
if (!([[[NSWorkspace sharedWorkspace] typeOfFile: fullFile error: NULL] isEqualToString: @"org.bittorrent.torrent"]
|| [[fullFile pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame))
continue;
-
+
tr_ctor * ctor = tr_ctorNew(fLib);
tr_ctorSetMetainfoFromFile(ctor, [fullFile UTF8String]);
-
+
switch (tr_torrentParse(ctor, NULL))
{
case TR_PARSE_OK:
[self openFiles: [NSArray arrayWithObject: fullFile] addType: ADD_AUTO forcePath: nil];
-
+
NSString * notificationTitle = NSLocalizedString(@"Torrent File Auto Added", "notification title");
if ([NSApp isOnMountainLionOrBetter])
{
NSUserNotification* notification = [[NSUserNotificationMtLion alloc] init];
[notification setTitle: notificationTitle];
[notification setInformativeText: file];
-
+
[notification setHasActionButton: NO];
-
+
[[NSUserNotificationCenterMtLion defaultUserNotificationCenter] deliverNotification: notification];
[notification release];
}
-
+
[GrowlApplicationBridge notifyWithTitle: notificationTitle
description: file notificationName: GROWL_AUTO_ADD iconData: nil priority: 0 isSticky: NO
clickContext: nil];
break;
-
+
case TR_PARSE_ERR:
[fAutoImportedNames removeObject: file];
break;
-
+
case TR_PARSE_DUPLICATE: //let's ignore this (but silence a warning)
break;
}
-
+
tr_ctorFree(ctor);
}
-
+
[newNames release];
}
{
if (![fDefaults boolForKey: @"AutoImport"])
return;
-
+
NSString * location = [(NSURL *)[notification object] path],
* path = [fDefaults stringForKey: @"AutoImportDirectory"];
-
+
if (location && path && [[[location stringByDeletingLastPathComponent] stringByExpandingTildeInPath]
isEqualToString: [path stringByExpandingTildeInPath]])
[fAutoImportedNames addObject: [location lastPathComponent]];
else
{
TorrentGroup * group = (TorrentGroup *)item;
-
+
if ([fDefaults boolForKey: @"DisplayGroupRowRatio"])
return [NSString stringForRatio: [group ratio]];
else
{
if (![torrent isKindOfClass: [Torrent class]])
return NO;
-
+
[indexSet addIndex: [fTableView rowForItem: torrent]];
}
-
+
[pasteboard declareTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE] owner: self];
[pasteboard setData: [NSKeyedArchiver archivedDataWithRootObject: indexSet] forType: TORRENT_TABLE_VIEW_DATA_TYPE];
return YES;
{
if (!item)
return NSDragOperationNone;
-
+
if ([[fDefaults stringForKey: @"Sort"] isEqualToString: SORT_ORDER])
{
if ([item isKindOfClass: [Torrent class]])
{
if (index == NSOutlineViewDropOnItemIndex)
return NSDragOperationNone;
-
+
if (item)
{
index = [fTableView rowForItem: item] + 1;
item = nil;
}
}
-
+
[fTableView setDropItem: item dropChildIndex: index];
return NSDragOperationGeneric;
}
-
+
return NSDragOperationNone;
}
if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
{
NSIndexSet * indexes = [NSKeyedUnarchiver unarchiveObjectWithData: [pasteboard dataForType: TORRENT_TABLE_VIEW_DATA_TYPE]];
-
+
//get the torrents to move
NSMutableArray * movingTorrents = [NSMutableArray arrayWithCapacity: [indexes count]];
for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
{
Torrent * torrent = [fTableView itemAtRow: i];
[movingTorrents addObject: torrent];
-
+
//change groups
if (item)
[torrent setGroupValue: [item groupIndex] determinationType: TorrentDeterminationUserSpecified];
}
-
+
//reorder queue order
if (newRow != NSOutlineViewDropOnItemIndex)
{
break;
}
}
-
+
//remove objects to reinsert
[fTorrents removeObjectsInArray: movingTorrents];
-
+
//insert objects at new location
const NSUInteger insertIndex = topTorrent ? [fTorrents indexOfObject: topTorrent] + 1 : 0;
NSIndexSet * insertIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(insertIndex, [movingTorrents count])];
[fTorrents insertObjects: movingTorrents atIndexes: insertIndexes];
-
+
//we need to make sure the queue order is updated in the Torrent object before we sort - safest to just reset all queue positions
NSUInteger i = 0;
for (Torrent * torrent in fTorrents)
[torrent setQueuePosition: i++];
[torrent update];
}
-
+
//do the drag animation here so that the dragged torrents are the ones that are animated as moving, and not the torrents around them
[fTableView beginUpdates];
-
+
NSUInteger insertDisplayIndex = topTorrent ? [groupTorrents indexOfObject: topTorrent] + 1 : 0;
-
+
for (Torrent * torrent in movingTorrents)
{
TorrentGroup * oldParent = item ? [fTableView parentForItem: torrent] : nil;
NSMutableArray * oldTorrents = oldParent ? [oldParent torrents] : fDisplayedTorrents;
const NSUInteger oldIndex = [oldTorrents indexOfObject: torrent];
-
+
if (item == oldParent)
{
if (oldIndex < insertDisplayIndex)
else
{
NSAssert(item && oldParent, @"Expected to be dragging between group rows");
-
+
NSMutableArray * newTorrents = [(TorrentGroup *)item torrents];
[newTorrents insertObject: torrent atIndex: insertDisplayIndex];
[oldTorrents removeObjectAtIndex: oldIndex];
}
[fTableView moveItemAtIndex: oldIndex inParent: oldParent toIndex: insertDisplayIndex inParent: item];
-
+
++insertDisplayIndex;
}
[fTableView endUpdates];
}
-
+
[self applyFilter];
}
-
+
return YES;
}
if (!fOverlayWindow)
fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
[fOverlayWindow setTorrents: files];
-
+
return NSDragOperationCopy;
}
tr_ctorFree(ctor);
}
}
-
+
//create a torrent file if a single file
if (!torrent && [files count] == 1)
{
if (!fOverlayWindow)
fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
[fOverlayWindow setFile: [[files objectAtIndex: 0] lastPathComponent]];
-
+
return NSDragOperationCopy;
}
}
if (!fOverlayWindow)
fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
[fOverlayWindow setURL: [[NSURL URLFromPasteboard: pasteboard] relativeString]];
-
+
return NSDragOperationCopy;
}
else;
-
+
return NSDragOperationNone;
}
{
if (fOverlayWindow)
[fOverlayWindow fadeOut];
-
+
NSPasteboard * pasteboard = [info draggingPasteboard];
if ([[pasteboard types] containsObject: NSFilenamesPboardType])
{
BOOL torrent = NO, accept = YES;
-
+
//create an array of files that can be opened
NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
NSMutableArray * filesToOpen = [NSMutableArray arrayWithCapacity: [files count]];
tr_ctorFree(ctor);
}
}
-
+
if ([filesToOpen count] > 0)
[self application: NSApp openFiles: filesToOpen];
else
else
accept = NO;
}
-
+
return accept;
}
else if ([[pasteboard types] containsObject: NSURLPboardType])
}
}
else;
-
+
return NO;
}
{
BOOL makeSmall = ![fDefaults boolForKey: @"SmallView"];
[fDefaults setBool: makeSmall forKey: @"SmallView"];
-
+
[fTableView setUsesAlternatingRowBackgroundColors: !makeSmall];
-
+
[fTableView setRowHeight: makeSmall ? ROW_HEIGHT_SMALL : ROW_HEIGHT_REGULAR];
[fTableView beginUpdates];
[fTableView noteHeightOfRowsWithIndexesChanged: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTableView numberOfRows])]];
[fTableView endUpdates];
-
+
//resize for larger min height if not set to auto size
if (![fDefaults boolForKey: @"AutoSize"])
{
const NSSize contentSize = [[fWindow contentView] frame].size;
-
+
NSSize contentMinSize = [fWindow contentMinSize];
contentMinSize.height = [self minWindowContentSizeAllowed];
[fWindow setContentMinSize: contentMinSize];
-
+
//make sure the window already isn't too small
if (!makeSmall && contentSize.height < contentMinSize.height)
{
CGFloat heightChange = contentMinSize.height - contentSize.height;
frame.size.height += heightChange;
frame.origin.y -= heightChange;
-
+
[fWindow setFrame: frame display: YES];
}
}
- (NSRect) windowFrameByAddingHeight: (CGFloat) height checkLimits: (BOOL) check
{
NSScrollView * scrollView = [fTableView enclosingScrollView];
-
+
//convert pixels to points
NSRect windowFrame = [fWindow frame];
NSSize windowSize = [scrollView convertSize: windowFrame.size fromView: nil];
windowSize.height += height;
-
+
if (check)
{
//we can't call minSize, since it might be set to the current size (auto size)
const CGFloat minHeight = [self minWindowContentSizeAllowed]
+ (NSHeight([fWindow frame]) - NSHeight([[fWindow contentView] frame])); //contentView to window
-
+
if (windowSize.height <= minHeight)
windowSize.height = minHeight;
else
const BOOL prevShown = fStatusBar != nil;
if (show == prevShown)
return;
-
+
if (show)
{
fStatusBar = [[StatusBarController alloc] initWithLib: fLib];
-
+
NSView * contentView = [fWindow contentView];
const NSSize windowSize = [contentView convertSize: [fWindow frame].size fromView: nil];
-
+
NSRect statusBarFrame = [[fStatusBar view] frame];
statusBarFrame.size.width = windowSize.width;
[[fStatusBar view] setFrame: statusBarFrame];
-
+
[contentView addSubview: [fStatusBar view]];
[[fStatusBar view] setFrameOrigin: NSMakePoint(0.0, NSMaxY([contentView frame]))];
}
-
+
CGFloat heightChange = [[fStatusBar view] frame].size.height;
if (!show)
heightChange *= -1;
-
+
//allow bar to show even if not enough room
if (show && ![fDefaults boolForKey: @"AutoSize"])
{
NSRect frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
-
+
NSScreen * screen = [fWindow screen];
if (screen)
{
}
}
}
-
+
[self updateUI];
-
+
NSScrollView * scrollView = [fTableView enclosingScrollView];
-
+
//set views to not autoresize
const NSUInteger statsMask = [[fStatusBar view] autoresizingMask];
[[fStatusBar view] setAutoresizingMask: NSViewNotSizable];
}
const NSUInteger scrollMask = [scrollView autoresizingMask];
[scrollView setAutoresizingMask: NSViewNotSizable];
-
+
NSRect frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
- [fWindow setFrame: frame display: YES animate: animate];
-
+ [fWindow setFrame: frame display: YES animate: animate];
+
//re-enable autoresize
[[fStatusBar view] setAutoresizingMask: statsMask];
if (fFilterBar)
[[fFilterBar view] setAutoresizingMask: filterMask];
[scrollView setAutoresizingMask: scrollMask];
-
+
if (!show)
{
[[fStatusBar view] removeFromSuperviewWithoutNeedingDisplay];
[fStatusBar release];
fStatusBar = nil;
}
-
+
if ([fDefaults boolForKey: @"AutoSize"])
[self setWindowMinMaxToCurrent];
else
- (void) toggleFilterBar: (id) sender
{
const BOOL show = fFilterBar == nil;
-
+
//disable filtering when hiding (have to do before showFilterBar:animate:)
if (!show)
[fFilterBar reset: NO];
-
+
[self showFilterBar: show animate: YES];
[fDefaults setBool: show forKey: @"FilterBar"];
[[fWindow toolbar] validateVisibleItems];
-
+
[self applyFilter]; //do even if showing to ensure tooltips are updated
}
const BOOL prevShown = fFilterBar != nil;
if (show == prevShown)
return;
-
+
if (show)
{
fFilterBar = [[FilterBarController alloc] init];
-
+
NSView * contentView = [fWindow contentView];
const NSSize windowSize = [contentView convertSize: [fWindow frame].size fromView: nil];
-
+
NSRect filterBarFrame = [[fFilterBar view] frame];
filterBarFrame.size.width = windowSize.width;
[[fFilterBar view] setFrame: filterBarFrame];
-
+
if (fStatusBar)
[contentView addSubview: [fFilterBar view] positioned: NSWindowBelow relativeTo: [fStatusBar view]];
else
}
else
[fWindow makeFirstResponder: fTableView];
-
+
CGFloat heightChange = NSHeight([[fFilterBar view] frame]);
if (!show)
heightChange *= -1;
-
+
//allow bar to show even if not enough room
if (show && ![fDefaults boolForKey: @"AutoSize"])
{
NSRect frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
-
+
NSScreen * screen = [fWindow screen];
if (screen)
{
}
}
}
-
+
NSScrollView * scrollView = [fTableView enclosingScrollView];
//set views to not autoresize
const NSUInteger scrollMask = [scrollView autoresizingMask];
[[fFilterBar view] setAutoresizingMask: NSViewNotSizable];
[scrollView setAutoresizingMask: NSViewNotSizable];
-
+
const NSRect frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
[fWindow setFrame: frame display: YES animate: animate];
-
+
//re-enable autoresize
[[fFilterBar view] setAutoresizingMask: filterMask];
[scrollView setAutoresizingMask: scrollMask];
-
+
if (!show)
{
[[fFilterBar view] removeFromSuperviewWithoutNeedingDisplay];
[fFilterBar release];
fFilterBar = nil;
}
-
+
if ([fDefaults boolForKey: @"AutoSize"])
[self setWindowMinMaxToCurrent];
else
{
NSArray * selectedTorrents = [fTableView selectedTorrents];
NSMutableArray * qlArray = [NSMutableArray arrayWithCapacity: [selectedTorrents count]];
-
+
for (Torrent * torrent in selectedTorrents)
if (([torrent isFolder] || [torrent isComplete]) && [torrent dataLocation])
[qlArray addObject: torrent];
-
+
return qlArray;
}
[super keyDown: event];
return YES;
}*/
-
+
return NO;
}
{
if (![fWindow isVisible])
return NSZeroRect;
-
+
const NSInteger row = [fTableView rowForItem: item];
if (row == -1)
return NSZeroRect;
-
+
NSRect frame = [fTableView iconRectForRow: row];
-
+
if (!NSIntersectsRect([fTableView visibleRect], frame))
return NSZeroRect;
-
+
frame.origin = [fTableView convertPoint: frame.origin toView: nil];
frame = [fWindow convertRectToScreen: frame];
frame.origin.y -= frame.size.height;
- (void) showToolbarShare: (id) sender
{
NSParameterAssert([sender isKindOfClass:[NSButton class]]);
-
+
NSSharingServicePicker * picker = [[NSSharingServicePicker alloc] initWithItems: [[ShareTorrentFileHelper sharedHelper] shareTorrentURLs]];
picker.delegate = self;
-
+
[picker showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinYEdge];
}
- (id) toolbarButtonWithIdentifier: (NSString *) ident forToolbarButtonClass:(Class)class
{
ButtonToolbarItem * item = [[class alloc] initWithItemIdentifier: ident];
-
+
NSButton * button = [[NSButton alloc] init];
[button setBezelStyle: NSTexturedRoundedBezelStyle];
[button setStringValue: @""];
-
+
[item setView: button];
[button release];
-
+
const NSSize buttonSize = NSMakeSize(36.0, 25.0);
[item setMinSize: buttonSize];
[item setMaxSize: buttonSize];
-
+
return [item autorelease];
}
if ([ident isEqualToString: TOOLBAR_CREATE])
{
ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
-
+
[item setLabel: NSLocalizedString(@"Create", "Create toolbar item -> label")];
[item setPaletteLabel: NSLocalizedString(@"Create Torrent File", "Create toolbar item -> palette label")];
[item setToolTip: NSLocalizedString(@"Create torrent file", "Create toolbar item -> tooltip")];
[item setTarget: self];
[item setAction: @selector(createFile:)];
[item setAutovalidates: NO];
-
+
return item;
}
else if ([ident isEqualToString: TOOLBAR_OPEN_FILE])
{
ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
-
+
[item setLabel: NSLocalizedString(@"Open", "Open toolbar item -> label")];
[item setPaletteLabel: NSLocalizedString(@"Open Torrent Files", "Open toolbar item -> palette label")];
[item setToolTip: NSLocalizedString(@"Open torrent files", "Open toolbar item -> tooltip")];
[item setTarget: self];
[item setAction: @selector(openShowSheet:)];
[item setAutovalidates: NO];
-
+
return item;
}
else if ([ident isEqualToString: TOOLBAR_OPEN_WEB])
{
ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
-
+
[item setLabel: NSLocalizedString(@"Open Address", "Open address toolbar item -> label")];
[item setPaletteLabel: NSLocalizedString(@"Open Torrent Address", "Open address toolbar item -> palette label")];
[item setToolTip: NSLocalizedString(@"Open torrent web address", "Open address toolbar item -> tooltip")];
[item setTarget: self];
[item setAction: @selector(openURLShowSheet:)];
[item setAutovalidates: NO];
-
+
return item;
}
else if ([ident isEqualToString: TOOLBAR_REMOVE])
{
ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
-
+
[item setLabel: NSLocalizedString(@"Remove", "Remove toolbar item -> label")];
[item setPaletteLabel: NSLocalizedString(@"Remove Selected", "Remove toolbar item -> palette label")];
[item setToolTip: NSLocalizedString(@"Remove selected transfers", "Remove toolbar item -> tooltip")];
[item setTarget: self];
[item setAction: @selector(removeNoDelete:)];
[item setVisibilityPriority: NSToolbarItemVisibilityPriorityHigh];
-
+
return item;
}
else if ([ident isEqualToString: TOOLBAR_INFO])
{
ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
[[(NSButton *)[item view] cell] setShowsStateBy: NSContentsCellMask]; //blue when enabled
-
+
[item setLabel: NSLocalizedString(@"Inspector", "Inspector toolbar item -> label")];
[item setPaletteLabel: NSLocalizedString(@"Toggle Inspector", "Inspector toolbar item -> palette label")];
[item setToolTip: NSLocalizedString(@"Toggle the torrent inspector", "Inspector toolbar item -> tooltip")];
[item setImage: [NSImage imageNamed: @"ToolbarInfoTemplate"]];
[item setTarget: self];
[item setAction: @selector(showInfo:)];
-
+
return item;
}
else if ([ident isEqualToString: TOOLBAR_PAUSE_RESUME_ALL])
{
GroupToolbarItem * groupItem = [[GroupToolbarItem alloc] initWithItemIdentifier: ident];
-
+
NSSegmentedControl * segmentedControl = [[NSSegmentedControl alloc] initWithFrame: NSZeroRect];
[segmentedControl setCell: [[[ToolbarSegmentedCell alloc] init] autorelease]];
[groupItem setView: segmentedControl];
NSSegmentedCell * segmentedCell = (NSSegmentedCell *)[segmentedControl cell];
-
+
if ([NSApp isOnYosemiteOrBetter]) {
segmentedControl.segmentStyle = NSSegmentStyleSeparated;
}
-
+
[segmentedControl setSegmentCount: 2];
[segmentedCell setTrackingMode: NSSegmentSwitchTrackingMomentary];
-
+
const NSSize groupSize = NSMakeSize(72.0, 25.0);
[groupItem setMinSize: groupSize];
[groupItem setMaxSize: groupSize];
-
+
[groupItem setLabel: NSLocalizedString(@"Apply All", "All toolbar item -> label")];
[groupItem setPaletteLabel: NSLocalizedString(@"Pause / Resume All", "All toolbar item -> palette label")];
[groupItem setTarget: self];
[groupItem setAction: @selector(allToolbarClicked:)];
-
+
[groupItem setIdentifiers: [NSArray arrayWithObjects: TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL, nil]];
-
+
[segmentedCell setTag: TOOLBAR_PAUSE_TAG forSegment: TOOLBAR_PAUSE_TAG];
[segmentedControl setImage: [NSImage imageNamed: @"ToolbarPauseAllTemplate"] forSegment: TOOLBAR_PAUSE_TAG];
[segmentedCell setToolTip: NSLocalizedString(@"Pause all transfers",
"All toolbar item -> tooltip") forSegment: TOOLBAR_PAUSE_TAG];
-
+
[segmentedCell setTag: TOOLBAR_RESUME_TAG forSegment: TOOLBAR_RESUME_TAG];
[segmentedControl setImage: [NSImage imageNamed: @"ToolbarResumeAllTemplate"] forSegment: TOOLBAR_RESUME_TAG];
[segmentedCell setToolTip: NSLocalizedString(@"Resume all transfers",
"All toolbar item -> tooltip") forSegment: TOOLBAR_RESUME_TAG];
-
+
[groupItem createMenu: [NSArray arrayWithObjects: NSLocalizedString(@"Pause All", "All toolbar item -> label"),
NSLocalizedString(@"Resume All", "All toolbar item -> label"), nil]];
-
+
[segmentedControl release];
-
+
[groupItem setVisibilityPriority: NSToolbarItemVisibilityPriorityHigh];
-
+
return [groupItem autorelease];
}
else if ([ident isEqualToString: TOOLBAR_PAUSE_RESUME_SELECTED])
{
GroupToolbarItem * groupItem = [[GroupToolbarItem alloc] initWithItemIdentifier: ident];
-
+
NSSegmentedControl * segmentedControl = [[NSSegmentedControl alloc] initWithFrame: NSZeroRect];
[segmentedControl setCell: [[[ToolbarSegmentedCell alloc] init] autorelease]];
[groupItem setView: segmentedControl];
NSSegmentedCell * segmentedCell = (NSSegmentedCell *)[segmentedControl cell];
-
+
if ([NSApp isOnYosemiteOrBetter]) {
segmentedControl.segmentStyle = NSSegmentStyleSeparated;
}
-
+
[segmentedControl setSegmentCount: 2];
[segmentedCell setTrackingMode: NSSegmentSwitchTrackingMomentary];
-
+
const NSSize groupSize = NSMakeSize(72.0, 25.0);
[groupItem setMinSize: groupSize];
[groupItem setMaxSize: groupSize];
-
+
[groupItem setLabel: NSLocalizedString(@"Apply Selected", "Selected toolbar item -> label")];
[groupItem setPaletteLabel: NSLocalizedString(@"Pause / Resume Selected", "Selected toolbar item -> palette label")];
[groupItem setTarget: self];
[groupItem setAction: @selector(selectedToolbarClicked:)];
-
+
[groupItem setIdentifiers: [NSArray arrayWithObjects: TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED, nil]];
-
+
[segmentedCell setTag: TOOLBAR_PAUSE_TAG forSegment: TOOLBAR_PAUSE_TAG];
[segmentedControl setImage: [NSImage imageNamed: @"ToolbarPauseSelectedTemplate"] forSegment: TOOLBAR_PAUSE_TAG];
[segmentedCell setToolTip: NSLocalizedString(@"Pause selected transfers",
"Selected toolbar item -> tooltip") forSegment: TOOLBAR_PAUSE_TAG];
-
+
[segmentedCell setTag: TOOLBAR_RESUME_TAG forSegment: TOOLBAR_RESUME_TAG];
[segmentedControl setImage: [NSImage imageNamed: @"ToolbarResumeSelectedTemplate"] forSegment: TOOLBAR_RESUME_TAG];
[segmentedCell setToolTip: NSLocalizedString(@"Resume selected transfers",
"Selected toolbar item -> tooltip") forSegment: TOOLBAR_RESUME_TAG];
-
+
[groupItem createMenu: [NSArray arrayWithObjects: NSLocalizedString(@"Pause Selected", "Selected toolbar item -> label"),
NSLocalizedString(@"Resume Selected", "Selected toolbar item -> label"), nil]];
-
+
[segmentedControl release];
-
+
[groupItem setVisibilityPriority: NSToolbarItemVisibilityPriorityHigh];
-
+
return [groupItem autorelease];
}
else if ([ident isEqualToString: TOOLBAR_FILTER])
{
ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
[[(NSButton *)[item view] cell] setShowsStateBy: NSContentsCellMask]; //blue when enabled
-
+
[item setLabel: NSLocalizedString(@"Filter", "Filter toolbar item -> label")];
[item setPaletteLabel: NSLocalizedString(@"Toggle Filter", "Filter toolbar item -> palette label")];
[item setToolTip: NSLocalizedString(@"Toggle the filter bar", "Filter toolbar item -> tooltip")];
[item setImage: [NSImage imageNamed: @"ToolbarFilterTemplate"]];
[item setTarget: self];
[item setAction: @selector(toggleFilterBar:)];
-
+
return item;
}
else if ([ident isEqualToString: TOOLBAR_QUICKLOOK])
{
ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
[[(NSButton *)[item view] cell] setShowsStateBy: NSContentsCellMask]; //blue when enabled
-
+
[item setLabel: NSLocalizedString(@"Quick Look", "QuickLook toolbar item -> label")];
[item setPaletteLabel: NSLocalizedString(@"Quick Look", "QuickLook toolbar item -> palette label")];
[item setToolTip: NSLocalizedString(@"Quick Look", "QuickLook toolbar item -> tooltip")];
[item setTarget: self];
[item setAction: @selector(toggleQuickLook:)];
[item setVisibilityPriority: NSToolbarItemVisibilityPriorityLow];
-
+
return item;
}
else if ([ident isEqualToString: TOOLBAR_SHARE])
{
ShareToolbarItem * item = [self toolbarButtonWithIdentifier: ident forToolbarButtonClass: [ShareToolbarItem class]];
-
+
[item setLabel: NSLocalizedString(@"Share", "Share toolbar item -> label")];
[item setPaletteLabel: NSLocalizedString(@"Share", "Share toolbar item -> palette label")];
[item setToolTip: NSLocalizedString(@"Share torrent file", "Share toolbar item -> tooltip")];
[item setImage: [NSImage imageNamed: NSImageNameShareTemplate]];
[item setVisibilityPriority: NSToolbarItemVisibilityPriorityLow];
-
+
NSButton *itemButton = (NSButton *)[item view];
[itemButton setTarget: self];
[itemButton setAction: @selector(showToolbarShare:)];
[itemButton sendActionOn:NSLeftMouseDownMask];
-
+
return item;
}
else
- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
{
NSString * ident = [toolbarItem itemIdentifier];
-
+
//enable remove item
if ([ident isEqualToString: TOOLBAR_REMOVE])
return [fTableView numberOfSelectedRows] > 0;
return YES;
return NO;
}
-
+
//enable resume item
if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
{
return YES;
return NO;
}
-
+
//set info item
if ([ident isEqualToString: TOOLBAR_INFO])
{
[(NSButton *)[toolbarItem view] setState: [[fInfoController window] isVisible]];
return YES;
}
-
+
//set filter item
if ([ident isEqualToString: TOOLBAR_FILTER])
{
[(NSButton *)[toolbarItem view] setState: fFilterBar != nil];
return YES;
}
-
+
//set quick look item
if ([ident isEqualToString: TOOLBAR_QUICKLOOK])
{
[(NSButton *)[toolbarItem view] setState: [QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]];
return YES;
}
-
+
//enable share item
if ([ident isEqualToString: TOOLBAR_SHARE])
return [fTableView numberOfSelectedRows] > 0;
-
+
return YES;
}
- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
{
SEL action = [menuItem action];
-
+
if (action == @selector(toggleSpeedLimit:))
{
[menuItem setState: [fDefaults boolForKey: @"SpeedLimit"] ? NSOnState : NSOffState];
return YES;
}
-
+
//only enable some items if it is in a context menu or the window is useable
BOOL canUseTable = [fWindow isKeyWindow] || [[menuItem menu] supermenu] != [NSApp mainMenu];
//enable open items
if (action == @selector(openShowSheet:) || action == @selector(openURLShowSheet:))
return [fWindow attachedSheet] == nil;
-
+
//enable sort options
if (action == @selector(setSort:))
{
NSAssert1(NO, @"Unknown sort tag received: %ld", [menuItem tag]);
sortType = SORT_ORDER;
}
-
+
[menuItem setState: [sortType isEqualToString: [fDefaults stringForKey: @"Sort"]] ? NSOnState : NSOffState];
return [fWindow isVisible];
}
-
+
if (action == @selector(setGroup:))
{
BOOL checked = NO;
-
+
NSInteger index = [menuItem tag];
for (Torrent * torrent in [fTableView selectedTorrents])
if (index == [torrent groupValue])
checked = YES;
break;
}
-
+
[menuItem setState: checked ? NSOnState : NSOffState];
return canUseTable && [fTableView numberOfSelectedRows] > 0;
}
-
+
if (action == @selector(toggleSmallView:))
{
[menuItem setState: [fDefaults boolForKey: @"SmallView"] ? NSOnState : NSOffState];
return [fWindow isVisible];
}
-
+
if (action == @selector(togglePiecesBar:))
{
[menuItem setState: [fDefaults boolForKey: @"PiecesBar"] ? NSOnState : NSOffState];
return [fWindow isVisible];
}
-
+
if (action == @selector(toggleAvailabilityBar:))
{
[menuItem setState: [fDefaults boolForKey: @"DisplayProgressBarAvailable"] ? NSOnState : NSOffState];
return [fWindow isVisible];
}
-
+
//enable show info
if (action == @selector(showInfo:))
{
return YES;
}
-
+
//enable prev/next inspector tab
if (action == @selector(setInfoTab:))
return [[fInfoController window] isVisible];
-
+
//enable toggle status bar
if (action == @selector(toggleStatusBar:))
{
return [fWindow isVisible];
}
-
+
//enable toggle filter bar
if (action == @selector(toggleFilterBar:))
{
return [fWindow isVisible];
}
-
+
//enable prev/next filter button
if (action == @selector(switchFilter:))
return [fWindow isVisible] && fFilterBar;
-
+
//enable reveal in finder
if (action == @selector(revealFile:))
return canUseTable && [fTableView numberOfSelectedRows] > 0;
-
+
//enable renaming file/folder
if (action == @selector(renameSelected:))
return canUseTable && [fTableView numberOfSelectedRows] == 1;
if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:))
{
BOOL warning = NO;
-
+
for (Torrent * torrent in [fTableView selectedTorrents])
{
if ([torrent isActive])
}
}
}
-
+
//append or remove ellipsis when needed
NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
if (warning && [fDefaults boolForKey: @"CheckRemove"])
if ([title hasSuffix: ellipsis])
[menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
}
-
+
return canUseTable && [fTableView numberOfSelectedRows] > 0;
}
-
+
//remove all completed transfers item
if (action == @selector(clearCompleted:))
{
if ([title hasSuffix: ellipsis])
[menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
}
-
+
for (Torrent * torrent in fTorrents)
if ([torrent isFinishedSeeding])
return YES;
return YES;
return NO;
}
-
+
//enable resume all item
if (action == @selector(resumeAllTorrents:))
{
return YES;
return NO;
}
-
+
//enable resume all waiting item
if (action == @selector(resumeWaitingTorrents:))
{
if (![fDefaults boolForKey: @"Queue"] && ![fDefaults boolForKey: @"QueueSeed"])
return NO;
-
+
for (Torrent * torrent in fTorrents)
if ([torrent waitingToStart])
return YES;
return NO;
}
-
+
//enable resume selected waiting item
if (action == @selector(resumeSelectedTorrentsNoWait:))
{
if (!canUseTable)
return NO;
-
+
for (Torrent * torrent in [fTableView selectedTorrents])
if (![torrent isActive])
return YES;
{
if (!canUseTable)
return NO;
-
+
for (Torrent * torrent in [fTableView selectedTorrents])
if ([torrent isActive] || [torrent waitingToStart])
return YES;
return NO;
}
-
+
//enable resume item
if (action == @selector(resumeSelectedTorrents:))
{
if (!canUseTable)
return NO;
-
+
for (Torrent * torrent in [fTableView selectedTorrents])
if (![torrent isActive] && ![torrent waitingToStart])
return YES;
return NO;
}
-
+
//enable manual announce item
if (action == @selector(announceSelectedTorrents:))
{
if (!canUseTable)
return NO;
-
+
for (Torrent * torrent in [fTableView selectedTorrents])
if ([torrent canManualAnnounce])
return YES;
return NO;
}
-
+
//enable reset cache item
if (action == @selector(verifySelectedTorrents:))
{
if (!canUseTable)
return NO;
-
+
for (Torrent * torrent in [fTableView selectedTorrents])
if (![torrent isMagnet])
return YES;
return NO;
}
-
+
//enable move torrent file item
if (action == @selector(moveDataFilesSelected:))
return canUseTable && [fTableView numberOfSelectedRows] > 0;
-
+
//enable copy torrent file item
if (action == @selector(copyTorrentFiles:))
{
if (!canUseTable)
return NO;
-
+
for (Torrent * torrent in [fTableView selectedTorrents])
if (![torrent isMagnet])
return YES;
return NO;
}
-
+
//enable copy torrent file item
if (action == @selector(copyMagnetLinks:))
return canUseTable && [fTableView numberOfSelectedRows] > 0;
-
+
//enable reverse sort item
if (action == @selector(setSortReverse:))
{
[menuItem setState: (isReverse == [fDefaults boolForKey: @"SortReverse"]) ? NSOnState : NSOffState];
return ![[fDefaults stringForKey: @"Sort"] isEqualToString: SORT_ORDER];
}
-
+
//enable group sort item
if (action == @selector(setSortByGroup:))
{
[menuItem setState: [fDefaults boolForKey: @"SortByGroup"] ? NSOnState : NSOffState];
return YES;
}
-
+
if (action == @selector(toggleQuickLook:))
{
const BOOL visible =[QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible];
NSString * title = !visible ? NSLocalizedString(@"Quick Look", "View menu -> Quick Look")
: NSLocalizedString(@"Close Quick Look", "View menu -> Quick Look");
[menuItem setTitle: title];
-
+
return YES;
}
-
+
return YES;
}
anyActive = YES;
[torrent sleep]; //have to call on all, regardless if they are active
}
-
+
//if there are any running transfers, wait 15 seconds for them to stop
if (anyActive)
{
sleep(15);
}
-
+
IOAllowPowerChange(fRootPort, (long) messageArgument);
break;
}
return;
}
}
-
+
IOAllowPowerChange(fRootPort, (long) messageArgument);
break;
{
if (fQuitting)
return nil;
-
+
NSUInteger seeding = 0, downloading = 0;
for (Torrent * torrent in fTorrents)
{
downloading++;
else;
}
-
+
NSMenu * menu = [[NSMenu alloc] init];
-
+
if (seeding > 0)
{
NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Seeding", "Dock item - Seeding"), seeding];
[menu addItemWithTitle: title action: nil keyEquivalent: @""];
}
-
+
if (downloading > 0)
{
NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Downloading", "Dock item - Downloading"), downloading];
[menu addItemWithTitle: title action: nil keyEquivalent: @""];
}
-
+
if (seeding > 0 || downloading > 0)
[menu addItem: [NSMenuItem separatorItem]];
-
+
[menu addItemWithTitle: NSLocalizedString(@"Pause All", "Dock item") action: @selector(stopAllTorrents:) keyEquivalent: @""];
[menu addItemWithTitle: NSLocalizedString(@"Resume All", "Dock item") action: @selector(resumeAllTorrents:) keyEquivalent: @""];
[menu addItem: [NSMenuItem separatorItem]];
[menu addItemWithTitle: NSLocalizedString(@"Speed Limit", "Dock item") action: @selector(toggleSpeedLimit:) keyEquivalent: @""];
-
+
return [menu autorelease];
}
{
//if auto size is enabled, the current frame shouldn't need to change
NSRect frame = [fDefaults boolForKey: @"AutoSize"] ? [window frame] : [self sizedWindowFrame];
-
+
frame.size.width = [fDefaults boolForKey: @"SmallView"] ? [fWindow minSize].width : WINDOW_REGULAR_WIDTH;
return frame;
}
if ([fDefaults boolForKey: @"AutoSize"])
{
NSScrollView * scrollView = [fTableView enclosingScrollView];
-
+
[scrollView setHasVerticalScroller: NO];
[fWindow setFrame: [self sizedWindowFrame] display: YES animate: YES];
[scrollView setHasVerticalScroller: YES];
-
+
[self setWindowMinMaxToCurrent];
}
}
{
NSUInteger groups = ([fDisplayedTorrents count] > 0 && ![[fDisplayedTorrents objectAtIndex: 0] isKindOfClass: [Torrent class]])
? [fDisplayedTorrents count] : 0;
-
+
CGFloat heightChange = (GROUP_SEPARATOR_HEIGHT + [fTableView intercellSpacing].height) * groups
+ ([fTableView rowHeight] + [fTableView intercellSpacing].height) * ([fTableView numberOfRows] - groups)
- NSHeight([[fTableView enclosingScrollView] frame]);
-
+
return [self windowFrameByAddingHeight: heightChange checkLimits: YES];
}
{
NSSize contentMinSize = [fWindow contentMinSize];
contentMinSize.height = [self minWindowContentSizeAllowed];
-
+
[fWindow setContentMinSize: contentMinSize];
-
+
NSSize contentMaxSize = [fWindow contentMaxSize];
contentMaxSize.height = FLT_MAX;
[fWindow setContentMaxSize: contentMaxSize];
- (void) setWindowMinMaxToCurrent
{
const CGFloat height = NSHeight([[fWindow contentView] frame]);
-
+
NSSize minSize = [fWindow contentMinSize],
maxSize = [fWindow contentMaxSize];
minSize.height = height;
maxSize.height = height;
-
+
[fWindow setContentMinSize: minSize];
[fWindow setContentMaxSize: maxSize];
}
- (NSDictionary *) registrationDictionaryForGrowl
{
NSArray * notifications = @[GROWL_DOWNLOAD_COMPLETE, GROWL_SEEDING_COMPLETE, GROWL_AUTO_ADD, GROWL_AUTO_SPEED_LIMIT];
-
+
return @{GROWL_NOTIFICATIONS_ALL : notifications,
GROWL_NOTIFICATIONS_DEFAULT : notifications };
}
{
if (![clickContext isKindOfClass: [NSDictionary class]])
return;
-
+
NSString * type = [clickContext objectForKey: @"Type"], * location;
if (([type isEqualToString: GROWL_DOWNLOAD_COMPLETE] || [type isEqualToString: GROWL_SEEDING_COMPLETE])
&& (location = [clickContext objectForKey: @"Location"]))
*stop = YES;
}
}];
-
+
if (!torrent)
{
NSLog(@"No torrent found matching the given torrent struct from the RPC callback!");
return;
}
}
-
+
dispatch_async(dispatch_get_main_queue(), ^{
switch (type)
{
case TR_RPC_TORRENT_ADDED:
[self rpcAddTorrentStruct: torrentStruct];
break;
-
+
case TR_RPC_TORRENT_STARTED:
case TR_RPC_TORRENT_STOPPED:
[self rpcStartedStoppedTorrent: torrent];
break;
-
+
case TR_RPC_TORRENT_REMOVING:
[self rpcRemoveTorrent: torrent deleteData: NO];
break;
-
+
case TR_RPC_TORRENT_TRASHING:
[self rpcRemoveTorrent: torrent deleteData: YES];
break;
-
+
case TR_RPC_TORRENT_CHANGED:
[self rpcChangedTorrent: torrent];
break;
-
+
case TR_RPC_TORRENT_MOVED:
[self rpcMovedTorrent: torrent];
break;
-
+
case TR_RPC_SESSION_QUEUE_POSITIONS_CHANGED:
[self rpcUpdateQueue];
break;
-
+
case TR_RPC_SESSION_CHANGED:
[fPrefsController rpcUpdatePrefs];
break;
-
+
case TR_RPC_SESSION_CLOSE:
fQuitRequested = YES;
[NSApp terminate: self];
break;
-
+
default:
NSAssert1(NO, @"Unknown RPC command received: %d", type);
}
NSString * location = nil;
if (tr_torrentGetDownloadDir(torrentStruct) != NULL)
location = [NSString stringWithUTF8String: tr_torrentGetDownloadDir(torrentStruct)];
-
+
Torrent * torrent = [[Torrent alloc] initWithTorrentStruct: torrentStruct location: location lib: fLib];
-
+
//change the location if the group calls for it (this has to wait until after the torrent is created)
if ([[GroupsController groups] usesCustomDownloadLocationForIndex: [torrent groupValue]])
{
location = [[GroupsController groups] customDownloadLocationForIndex: [torrent groupValue]];
[torrent changeDownloadFolderBeforeUsing: location determinationType: TorrentDeterminationAutomatic];
}
-
+
[torrent update];
[fTorrents addObject: torrent];
[torrent release];
-
+
if (!fAddingTransfers)
fAddingTransfers = [[NSMutableSet alloc] init];
[fAddingTransfers addObject: torrent];
-
+
[self fullUpdateUI];
}
- (void) rpcStartedStoppedTorrent: (Torrent *) torrent
{
[torrent update];
-
+
[self updateUI];
[self applyFilter];
[self updateTorrentHistory];
- (void) rpcChangedTorrent: (Torrent *) torrent
{
[torrent update];
-
+
if ([[fTableView selectedTorrents] containsObject: torrent])
{
[fInfoController updateInfoStats]; //this will reload the file table
{
[torrent update];
[torrent updateTimeMachineExclude];
-
+
if ([[fTableView selectedTorrents] containsObject: torrent])
[fInfoController updateInfoStats];
}
{
for (Torrent * torrent in fTorrents)
[torrent update];
-
+
NSSortDescriptor * descriptor = [NSSortDescriptor sortDescriptorWithKey: @"queuePosition" ascending: YES];
NSArray * descriptors = [NSArray arrayWithObject: descriptor];
[fTorrents sortUsingDescriptors: descriptors];
-
+
[self sortTorrents: YES];
}
IBOutlet NSSegmentedControl * fTrackerAddRemoveControl;
IBOutlet NSTextView * fCommentView;
IBOutlet NSButton * fPrivateCheck, * fOpenCheck;
-
+
IBOutlet NSView * fProgressView;
IBOutlet NSProgressIndicator * fProgressIndicator;
-
+
tr_metainfo_builder * fInfo;
NSURL * fPath, * fLocation;
NSMutableArray * fTrackers;
-
+
NSTimer * fTimer;
BOOL fStarted, fOpenWhenCreated;
-
+
NSUserDefaults * fDefaults;
}
NSURL * path;
if (!(path = [CreatorWindowController chooseFile]))
return nil;
-
+
CreatorWindowController * creator = [[self alloc] initWithHandle: handle path: path];
[creator showWindow: nil];
return creator;
if ((self = [super initWithWindowNibName: @"Creator"]))
{
fStarted = NO;
-
+
fPath = [path retain];
fInfo = tr_metaInfoBuilderCreate([[fPath path] UTF8String]);
-
+
if (fInfo->fileCount == 0)
{
NSAlert * alert = [[NSAlert alloc] init];
[alert setInformativeText: NSLocalizedString(@"There must be at least one file in a folder to create a torrent file.",
"Create torrent -> no files -> warning")];
[alert setAlertStyle: NSWarningAlertStyle];
-
+
[alert runModal];
[alert release];
-
+
[self release];
return nil;
}
[alert setInformativeText: NSLocalizedString(@"A torrent file cannot be created for files with no size.",
"Create torrent -> zero size -> warning")];
[alert setAlertStyle: NSWarningAlertStyle];
-
+
[alert runModal];
[alert release];
-
+
[self release];
return nil;
}
-
+
fDefaults = [NSUserDefaults standardUserDefaults];
-
+
//get list of trackers
if (!(fTrackers = [[fDefaults arrayForKey: @"CreatorTrackers"] mutableCopy]))
{
fTrackers = [[NSMutableArray alloc] init];
-
+
//check for single tracker from versions before 1.3
NSString * tracker;
if ((tracker = [fDefaults stringForKey: @"CreatorTracker"]))
}
}
}
-
+
//remove potentially invalid addresses
for (NSInteger i = [fTrackers count]-1; i >= 0; i--)
{
- (void) awakeFromNib
{
[[self window] setRestorationClass: [self class]];
-
+
NSString * name = [fPath lastPathComponent];
-
+
[[self window] setTitle: name];
-
+
[fNameField setStringValue: name];
[fNameField setToolTip: [fPath path]];
-
+
const BOOL multifile = fInfo->isFolder;
-
+
NSImage * icon = [[NSWorkspace sharedWorkspace] iconForFileType: multifile
? NSFileTypeForHFSTypeCode(kGenericFolderIcon) : [fPath pathExtension]];
[icon setSize: [fIconView frame].size];
[fIconView setImage: icon];
-
+
NSString * statusString = [NSString stringForFileSize: fInfo->totalSize];
if (multifile)
{
statusString = [NSString stringWithFormat: @"%@, %@", fileString, statusString];
}
[fStatusField setStringValue: statusString];
-
+
if (fInfo->pieceCount == 1)
[fPiecesField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"1 piece, %@", "Create torrent -> info"),
[NSString stringForFileSize: fInfo->pieceSize]]];
else
[fPiecesField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%d pieces, %@ each", "Create torrent -> info"),
fInfo->pieceCount, [NSString stringForFileSize: fInfo->pieceSize]]];
-
+
fLocation = [[[fDefaults URLForKey: @"CreatorLocationURL"] URLByAppendingPathComponent: [name stringByAppendingPathExtension: @"torrent"]] retain];
if (!fLocation)
{
fLocation = [[NSURL alloc] initFileURLWithPath: [[location stringByExpandingTildeInPath] stringByAppendingPathComponent: [name stringByAppendingPathExtension: @"torrent"]]];
}
[self updateLocationField];
-
+
//set previously saved values
if ([fDefaults objectForKey: @"CreatorPrivate"])
[fPrivateCheck setState: [fDefaults boolForKey: @"CreatorPrivate"] ? NSOnState : NSOffState];
-
+
[fOpenCheck setState: [fDefaults boolForKey: @"CreatorOpen"] ? NSOnState : NSOffState];
}
{
[fPath release];
[fLocation release];
-
+
[fTrackers release];
-
+
if (fInfo)
tr_metaInfoBuilderFree(fInfo);
-
+
[fTimer invalidate];
[fTimer release];
-
+
[super dealloc];
}
completionHandler(nil, [NSError errorWithDomain: NSURLErrorDomain code: NSURLErrorCannotOpenFile userInfo: nil]);
return;
}
-
+
NSWindow * window = [[self createTorrentFile: [(Controller *)[NSApp delegate] sessionHandle] forFile: path] window];
completionHandler(window, nil);
}
[fLocation release];
fLocation = [[coder decodeObjectForKey: @"TRCreatorLocation"] retain];
[self updateLocationField];
-
+
[fTrackers release];
fTrackers = [[coder decodeObjectForKey: @"TRCreatorTrackers"] retain];
[fTrackerTable reloadData];
-
+
[fOpenCheck setState: [coder decodeIntegerForKey: @"TRCreatorOpenCheck"]];
[fPrivateCheck setState: [coder decodeIntegerForKey: @"TRCreatorPrivateCheck"]];
[fCommentView setString: [coder decodeObjectForKey: @"TRCreatorPrivateComment"]];
[panel setPrompt: NSLocalizedString(@"Select", "Create torrent -> location sheet -> button")];
[panel setMessage: NSLocalizedString(@"Select the name and location for the torrent file.",
- "Create torrent -> location sheet -> message")];
-
+ "Create torrent -> location sheet -> message")];
+
[panel setAllowedFileTypes: [NSArray arrayWithObjects: @"org.bittorrent.torrent", @"torrent", nil]];
[panel setCanSelectHiddenExtension: YES];
-
+
[panel setDirectoryURL: [fLocation URLByDeletingLastPathComponent]];
[panel setNameFieldStringValue: [fLocation lastPathComponent]];
-
+
[panel beginSheetModalForWindow: [self window] completionHandler: ^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton)
{
//make sure the trackers are no longer being verified
if ([fTrackerTable editedRow] != -1)
[[self window] endEditingFor: fTrackerTable];
-
+
const BOOL isPrivate = [fPrivateCheck state] == NSOnState;
if ([fTrackers count] == 0
&& [fDefaults boolForKey: isPrivate ? @"WarningCreatorPrivateBlankAddress" : @"WarningCreatorBlankAddress"])
{
NSAlert * alert = [[NSAlert alloc] init];
[alert setMessageText: NSLocalizedString(@"There are no tracker addresses.", "Create torrent -> blank address -> title")];
-
+
NSString * infoString = isPrivate
? NSLocalizedString(@"A transfer marked as private with no tracker addresses will be unable to connect to peers."
" The torrent file will only be useful if you plan to upload the file to a tracker website"
: NSLocalizedString(@"The transfer will not contact trackers for peers, and will have to rely solely on"
" non-tracker peer discovery methods such as PEX and DHT to download and seed.",
"Create torrent -> blank address -> message");
-
+
[alert setInformativeText: infoString];
[alert addButtonWithTitle: NSLocalizedString(@"Create", "Create torrent -> blank address -> button")];
[alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Create torrent -> blank address -> button")];
//don't allow add/remove when currently adding - it leads to weird results
if ([fTrackerTable editedRow] != -1)
return;
-
+
if ([[sender cell] tagForSegment: [sender selectedSegment]] == TRACKER_REMOVE_TAG)
{
[fTrackers removeObjectsAtIndexes: [fTrackerTable selectedRowIndexes]];
-
+
[fTrackerTable deselectAll: self];
[fTrackerTable reloadData];
}
{
[fTrackers addObject: @""];
[fTrackerTable reloadData];
-
+
const NSInteger row = [fTrackers count] - 1;
[fTrackerTable selectRowIndexes: [NSIndexSet indexSetWithIndex: row] byExtendingSelection: NO];
[fTrackerTable editColumn: 0 row: row withEvent: nil select: YES];
row: (NSInteger) row
{
NSString * tracker = (NSString *)object;
-
+
tracker = [tracker stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
-
+
if ([tracker rangeOfString: @"://"].location == NSNotFound)
tracker = [@"http://" stringByAppendingString: tracker];
-
+
if (!tr_urlIsValidTracker([tracker UTF8String]))
{
NSBeep();
}
else
[fTrackers replaceObjectAtIndex: row withObject: tracker];
-
+
[fTrackerTable deselectAll: self];
[fTrackerTable reloadData];
}
{
NSArray * addresses = [fTrackers objectsAtIndexes: [fTrackerTable selectedRowIndexes]];
NSString * text = [addresses componentsJoinedByString: @"\n"];
-
+
NSPasteboard * pb = [NSPasteboard generalPasteboard];
[pb clearContents];
[pb writeObjects: [NSArray arrayWithObject: text]];
- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
{
const SEL action = [menuItem action];
-
+
if (action == @selector(copy:))
return [[self window] firstResponder] == fTrackerTable && [fTrackerTable numberOfSelectedRows] > 0;
-
+
if (action == @selector(paste:))
return [[self window] firstResponder] == fTrackerTable
&& [[NSPasteboard generalPasteboard] canReadObjectForClasses: [NSArray arrayWithObject: [NSString class]] options: nil];
-
+
return YES;
}
- (void) paste: (id) sender
{
NSMutableArray * tempTrackers = [NSMutableArray array];
-
+
NSArray * items = [[NSPasteboard generalPasteboard] readObjectsForClasses: [NSArray arrayWithObject: [NSString class]] options: nil];
NSAssert(items != nil, @"no string items to paste; should not be able to call this method");
-
+
for (NSString * pbItem in items)
{
for (NSString * tracker in [pbItem componentsSeparatedByString: @"\n"])
[tempTrackers addObject: tracker];
}
-
+
BOOL added = NO;
-
+
for (NSString * tracker in tempTrackers)
{
tracker = [tracker stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
-
+
if ([tracker rangeOfString: @"://"].location == NSNotFound)
tracker = [@"http://" stringByAppendingString: tracker];
-
+
if (tr_urlIsValidTracker([tracker UTF8String]))
{
[fTrackers addObject: tracker];
added = YES;
}
}
-
+
if (added)
{
[fTrackerTable deselectAll: self];
+ (NSURL *) chooseFile
{
NSOpenPanel * panel = [NSOpenPanel openPanel];
-
+
[panel setTitle: NSLocalizedString(@"Create Torrent File", "Create torrent -> select file")];
[panel setPrompt: NSLocalizedString(@"Select", "Create torrent -> select file")];
[panel setAllowsMultipleSelection: NO];
[panel setCanCreateDirectories: NO];
[panel setMessage: NSLocalizedString(@"Select a file or folder for the torrent file.", "Create torrent -> select file")];
-
+
BOOL success = [panel runModal] == NSOKButton;
return success ? [[panel URLs] objectAtIndex: 0] : nil;
}
if ([fPrivateCheck state] == NSOnState)
[[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningCreatorPrivateBlankAddress"];
}
-
+
[alert release];
-
+
if (returnCode == NSAlertFirstButtonReturn)
[self performSelectorOnMainThread: @selector(createReal) withObject: nil waitUntilDone: NO];
}
"Create torrent -> directory doesn't exist warning -> warning"),
[[fLocation URLByDeletingLastPathComponent] path]]];
[alert setAlertStyle: NSWarningAlertStyle];
-
+
[alert beginSheetModalForWindow: [self window] modalDelegate: self didEndSelector: nil contextInfo: nil];
return;
}
-
+
//check if a file with the same name and location already exists
if ([fLocation checkResourceIsReachableAndReturnError: NULL])
{
NSArray * pathComponents = [fLocation pathComponents];
NSInteger count = [pathComponents count];
-
+
NSAlert * alert = [[[NSAlert alloc] init] autorelease];
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> file already exists warning -> button")];
[alert setMessageText: NSLocalizedString(@"A torrent file with this name and directory cannot be created.",
"Create torrent -> file already exists warning -> warning"),
[pathComponents objectAtIndex: count-1], [pathComponents objectAtIndex: count-2]]];
[alert setAlertStyle: NSWarningAlertStyle];
-
+
[alert beginSheetModalForWindow: [self window] modalDelegate: self didEndSelector: nil contextInfo: nil];
return;
}
-
+
//parse non-empty tracker strings
tr_tracker_info * trackerInfo = tr_new0(tr_tracker_info, [fTrackers count]);
-
+
for (NSUInteger i = 0; i < [fTrackers count]; i++)
{
trackerInfo[i].announce = (char *)[[fTrackers objectAtIndex: i] UTF8String];
trackerInfo[i].tier = i;
}
-
+
//store values
[fDefaults setObject: fTrackers forKey: @"CreatorTrackers"];
[fDefaults setBool: [fPrivateCheck state] == NSOnState forKey: @"CreatorPrivate"];
[fDefaults setURL: [fLocation URLByDeletingLastPathComponent] forKey: @"CreatorLocationURL"];
[[self window] setRestorable: NO];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"BeginCreateTorrentFile" object: fLocation userInfo: nil];
tr_makeMetaInfo(fInfo, [[fLocation path] UTF8String], trackerInfo, [fTrackers count], [[fCommentView string] UTF8String], [fPrivateCheck state] == NSOnState);
tr_free(trackerInfo);
-
+
fTimer = [[NSTimer scheduledTimerWithTimeInterval: 0.1 target: self selector: @selector(checkProgress) userInfo: nil repeats: YES] retain];
}
[fTimer invalidate];
[fTimer release];
fTimer = nil;
-
+
NSAlert * alert;
switch (fInfo->result)
{
[[fPath URLByDeletingLastPathComponent] path], @"Path", nil];
[[NSNotificationCenter defaultCenter] postNotificationName: @"OpenCreatedTorrentFile" object: self userInfo: dict];
}
-
+
[[self window] close];
break;
-
+
case TR_MAKEMETA_CANCELLED:
[[self window] close];
break;
-
+
default:
alert = [[[NSAlert alloc] init] autorelease];
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Create torrent -> failed -> button")];
[alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Creation of \"%@\" failed.",
"Create torrent -> failed -> title"), [fLocation lastPathComponent]]];
[alert setAlertStyle: NSWarningAlertStyle];
-
+
if (fInfo->result == TR_MAKEMETA_IO_READ)
[alert setInformativeText: [NSString stringWithFormat: NSLocalizedString(@"Could not read \"%s\": %s.",
"Create torrent -> failed -> warning"), fInfo->errfile, strerror(fInfo->my_errno)]];
else //invalid url should have been caught before creating
[alert setInformativeText: [NSString stringWithFormat: @"%@ (%d)",
NSLocalizedString(@"An unknown error has occurred.", "Create torrent -> failed -> warning"), fInfo->result]];
-
+
[alert beginSheetModalForWindow: [self window] modalDelegate: self
didEndSelector: @selector(failureSheetClosed:returnCode:contextInfo:) contextInfo: nil];
}
else
{
[fProgressIndicator setDoubleValue: (double)fInfo->pieceIndex / fInfo->pieceCount];
-
+
if (!fStarted)
{
fStarted = YES;
-
+
[fProgressView setHidden: YES];
-
+
NSWindow * window = [self window];
[window setFrameAutosaveName: @""];
-
+
NSRect windowRect = [window frame];
CGFloat difference = [fProgressView frame].size.height - [[window contentView] frame].size.height;
windowRect.origin.y -= difference;
windowRect.size.height += difference;
-
+
//don't allow vertical resizing
CGFloat height = windowRect.size.height;
[window setMinSize: NSMakeSize([window minSize].width, height)];
[window setMaxSize: NSMakeSize([window maxSize].width, height)];
-
+
[window setContentView: fProgressView];
[window setFrame: windowRect display: YES animate: YES];
[fProgressView setHidden: NO];
-
+
[[window standardWindowButton: NSWindowCloseButton] setEnabled: NO];
}
}
@interface DragOverlayView : NSView
{
NSImage * fBadge;
-
+
NSDictionary * fMainLineAttributes, * fSubLineAttributes;
}
NSShadow * stringShadow = [[NSShadow alloc] init];
[stringShadow setShadowOffset: NSMakeSize(2.0, -2.0)];
[stringShadow setShadowBlurRadius: 4.0];
-
+
NSFont * bigFont = [NSFont boldSystemFontOfSize: 18.0],
* smallFont = [NSFont systemFontOfSize: 14.0];
-
+
NSMutableParagraphStyle * paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[paragraphStyle setLineBreakMode: NSLineBreakByTruncatingMiddle];
-
+
fMainLineAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSColor whiteColor], NSForegroundColorAttributeName,
bigFont, NSFontAttributeName, stringShadow, NSShadowAttributeName,
paragraphStyle, NSParagraphStyleAttributeName, nil];
-
+
fSubLineAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSColor whiteColor], NSForegroundColorAttributeName,
smallFont, NSFontAttributeName, stringShadow, NSShadowAttributeName,
paragraphStyle, NSParagraphStyleAttributeName, nil];
-
+
[stringShadow release];
[paragraphStyle release];
}
- (void) dealloc
{
[fBadge release];
-
+
[fMainLineAttributes release];
[fSubLineAttributes release];
-
+
[super dealloc];
}
- (void) setOverlay: (NSImage *) icon mainLine: (NSString *) mainLine subLine: (NSString *) subLine
{
[fBadge release];
-
+
//create badge
const NSRect badgeRect = NSMakeRect(0.0, 0.0, 325.0, 84.0);
-
+
fBadge = [[NSImage alloc] initWithSize: badgeRect.size];
[fBadge lockFocus];
-
+
NSBezierPath * bp = [NSBezierPath bezierPathWithRoundedRect: badgeRect xRadius: 15.0 yRadius: 15.0];
[[NSColor colorWithCalibratedWhite: 0.0 alpha: 0.75] set];
[bp fill];
-
+
//place icon
[icon drawInRect: NSMakeRect(PADDING, (NSHeight(badgeRect) - ICON_WIDTH) * 0.5, ICON_WIDTH, ICON_WIDTH) fromRect: NSZeroRect
operation: NSCompositeSourceOver fraction: 1.0];
-
+
//place main text
const NSSize mainLineSize = [mainLine sizeWithAttributes: fMainLineAttributes];
const NSSize subLineSize = [subLine sizeWithAttributes: fSubLineAttributes];
-
+
NSRect lineRect = NSMakeRect(PADDING + ICON_WIDTH + 5.0,
(NSHeight(badgeRect) + (subLineSize.height + 2.0 - mainLineSize.height)) * 0.5,
NSWidth(badgeRect) - (PADDING + ICON_WIDTH + 2.0) - PADDING, mainLineSize.height);
[mainLine drawInRect: lineRect withAttributes: fMainLineAttributes];
-
+
//place sub text
lineRect.origin.y -= subLineSize.height + 2.0;
lineRect.size.height = subLineSize.height;
[subLine drawInRect: lineRect withAttributes: fSubLineAttributes];
-
+
[fBadge unlockFocus];
-
+
[self setNeedsDisplay: YES];
}
@interface DragOverlayWindow : NSWindow
{
tr_session * fLib;
-
+
NSViewAnimation * fFadeInAnimation, * fFadeOutAnimation;
}
backing: NSBackingStoreBuffered defer: NO])))
{
fLib = lib;
-
+
[self setBackgroundColor: [NSColor colorWithCalibratedWhite: 0.0 alpha: 0.5]];
[self setAlphaValue: 0.0];
[self setOpaque: NO];
[self setHasShadow: NO];
-
+
DragOverlayView * view = [[DragOverlayView alloc] initWithFrame: [self frame]];
[self setContentView: view];
[view release];
-
+
[self setReleasedWhenClosed: NO];
[self setIgnoresMouseEvents: YES];
-
+
fFadeInAnimation = [[NSViewAnimation alloc] initWithViewAnimations: [NSArray arrayWithObject:
[NSDictionary dictionaryWithObjectsAndKeys: self, NSViewAnimationTargetKey,
NSViewAnimationFadeInEffect, NSViewAnimationEffectKey, nil]]];
[fFadeInAnimation setDuration: 0.15];
[fFadeInAnimation setAnimationBlockingMode: NSAnimationNonblockingThreaded];
-
+
fFadeOutAnimation = [[NSViewAnimation alloc] initWithViewAnimations: [NSArray arrayWithObject:
[NSDictionary dictionaryWithObjectsAndKeys: self, NSViewAnimationTargetKey,
NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey, nil]]];
[fFadeOutAnimation setDuration: 0.5];
[fFadeOutAnimation setAnimationBlockingMode: NSAnimationNonblockingThreaded];
-
+
[window addChildWindow: self ordered: NSWindowAbove];
-
+
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(resizeWindow)
name: NSWindowDidResizeNotification object: window];
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
-
+
[fFadeInAnimation release];
[fFadeOutAnimation release];
-
+
[super dealloc];
}
{
uint64_t size = 0;
NSInteger count = 0;
-
+
NSString * name;
BOOL folder;
NSInteger fileCount = 0;
-
+
for (NSString * file in files)
{
if ([[[NSWorkspace sharedWorkspace] typeOfFile: file error: NULL] isEqualToString: @"org.bittorrent.torrent"]
count++;
size += info.totalSize;
fileCount += info.fileCount;
-
+
//only useful when one torrent
if (count == 1)
{
tr_ctorFree(ctor);
}
}
-
+
if (count <= 0)
return;
-
+
//set strings and icon
NSString * secondString = [NSString stringForFileSize: size];
if (count > 1 || folder)
[NSString formattedUInteger: fileCount]];
secondString = [NSString stringWithFormat: @"%@, %@", fileString, secondString];
}
-
+
NSImage * icon;
if (count == 1)
icon = [[NSWorkspace sharedWorkspace] iconForFileType: folder ? NSFileTypeForHFSTypeCode(kGenericFolderIcon) : [name pathExtension]];
secondString = [secondString stringByAppendingString: @" total"];
icon = [NSImage imageNamed: @"TransmissionDocument.icns"];
}
-
+
[[self contentView] setOverlay: icon mainLine: name subLine: secondString];
[self fadeIn];
}
{
if (!value)
return nil;
-
+
NSString * path = [value stringByExpandingTildeInPath];
NSImage * icon;
//show a folder icon if the folder doesn't exist
icon = [[NSWorkspace sharedWorkspace] iconForFileType: NSFileTypeForHFSTypeCode(kGenericFolderIcon)];
else
icon = [[NSWorkspace sharedWorkspace] iconForFile: path];
-
+
[icon setSize: NSMakeSize(16.0, 16.0)];
-
+
return icon;
}
@interface FileListNode : NSObject <NSCopying>
{
NSMutableIndexSet * fIndexes;
-
+
NSString * fName;
NSString * fPath;
Torrent * fTorrent;
fChildren = [[NSMutableArray alloc] init];
fSize = 0;
}
-
+
return self;
}
fSize = size;
[fIndexes addIndex: index];
}
-
+
return self;
}
- (void) insertChild: (FileListNode *) child
{
NSAssert(fIsFolder, @"method can only be invoked on folders");
-
+
[fChildren addObject: child];
}
- (void) insertIndex: (NSUInteger) index withSize: (uint64_t) size
{
NSAssert(fIsFolder, @"method can only be invoked on folders");
-
+
[fIndexes addIndex: index];
fSize += size;
}
- (NSMutableArray *) children
{
NSAssert(fIsFolder, @"method can only be invoked on folders");
-
+
return fChildren;
}
NSParameterAssert(oldName != nil);
NSParameterAssert(newName != nil);
NSParameterAssert(path != nil);
-
+
NSArray * lookupPathComponents = [path pathComponents];
NSArray * thesePathComponents = [self.path pathComponents];
-
+
if ([lookupPathComponents isEqualToArray: thesePathComponents]) //this node represents what's being renamed
{
if ([oldName isEqualToString: self.name])
const BOOL allSame = NSNotFound == [lookupPathComponents indexOfObjectWithOptions: NSEnumerationConcurrent passingTest: ^BOOL(NSString * name, NSUInteger idx, BOOL * stop) {
return ![name isEqualToString: [thesePathComponents objectAtIndex: idx]];
}];
-
+
if (allSame)
{
NSString * oldPathPrefix = [path stringByAppendingPathComponent: oldName];
NSString * newPathPrefix = [path stringByAppendingPathComponent: newName];
-
+
[fPath autorelease];
fPath = [[fPath stringByReplacingCharactersInRange: NSMakeRange(0, [oldPathPrefix length]) withString: newPathPrefix] retain];
return YES;
}
}
-
+
return NO;
}
fIsFolder = isFolder;
fName = [name copy];
fPath = [path copy];
-
+
fIndexes = [[NSMutableIndexSet alloc] init];
-
+
fTorrent = torrent;
}
-
+
return self;
}
{
NSMutableParagraphStyle * paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[paragraphStyle setLineBreakMode: NSLineBreakByTruncatingMiddle];
-
+
fTitleAttributes = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
[NSFont messageFontOfSize: 12.0], NSFontAttributeName,
paragraphStyle, NSParagraphStyleAttributeName, nil];
-
+
NSMutableParagraphStyle * statusParagraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[statusParagraphStyle setLineBreakMode: NSLineBreakByTruncatingTail];
-
+
fStatusAttributes = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
[NSFont messageFontOfSize: 9.0], NSFontAttributeName,
statusParagraphStyle, NSParagraphStyleAttributeName, nil];
-
+
[paragraphStyle release];
[statusParagraphStyle release];
}
{
[fTitleAttributes release];
[fStatusAttributes release];
-
+
[super dealloc];
}
- (id) copyWithZone: (NSZone *) zone
{
FileNameCell * copy = [super copyWithZone: zone];
-
+
copy->fTitleAttributes = [fTitleAttributes retain];
copy->fStatusAttributes = [fStatusAttributes retain];
-
+
return copy;
}
- (NSRect) imageRectForBounds: (NSRect) bounds
{
NSRect result = bounds;
-
+
result.origin.x += PADDING_HORIZONAL;
-
+
const CGFloat IMAGE_SIZE = [(FileListNode *)[self objectValue] isFolder] ? IMAGE_FOLDER_SIZE : IMAGE_ICON_SIZE;
result.origin.y += (result.size.height - IMAGE_SIZE) * 0.5;
result.size = NSMakeSize(IMAGE_SIZE, IMAGE_SIZE);
-
+
return result;
}
{
//icon
[[self image] drawInRect: [self imageRectForBounds: cellFrame] fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0 respectFlipped: YES hints: nil];
-
+
NSColor * titleColor, * statusColor;
if ([self backgroundStyle] == NSBackgroundStyleDark)
titleColor = statusColor = [NSColor whiteColor];
titleColor = [NSColor controlTextColor];
statusColor = [NSColor darkGrayColor];
}
-
+
[fTitleAttributes setObject: titleColor forKey: NSForegroundColorAttributeName];
[fStatusAttributes setObject: statusColor forKey: NSForegroundColorAttributeName];
-
+
//title
NSAttributedString * titleString = [self attributedTitle];
NSRect titleRect = [self rectForTitleWithString: titleString inBounds: cellFrame];
[titleString drawInRect: titleRect];
-
+
//status
NSAttributedString * statusString = [self attributedStatus];
NSRect statusRect = [self rectForStatusWithString: statusString withTitleRect: titleRect inBounds: cellFrame];
{
NSAttributedString * titleString = [self attributedTitle];
NSRect realRect = [self rectForTitleWithString: titleString inBounds: cellFrame];
-
+
if ([titleString size].width > NSWidth(realRect)
&& NSMouseInRect([view convertPoint: [[view window] mouseLocationOutsideOfEventStream] fromView: nil], realRect, [view isFlipped]))
{
realRect.size.width = [titleString size].width;
return NSInsetRect(realRect, -PADDING_EXPANSION_FRAME, -PADDING_EXPANSION_FRAME);
}
-
+
return NSZeroRect;
}
{
cellFrame.origin.x += PADDING_EXPANSION_FRAME;
cellFrame.origin.y += PADDING_EXPANSION_FRAME;
-
+
[fTitleAttributes setObject: [NSColor controlTextColor] forKey: NSForegroundColorAttributeName];
NSAttributedString * titleString = [self attributedTitle];
[titleString drawInRect: cellFrame];
- (NSRect) rectForTitleWithString: (NSAttributedString *) string inBounds: (NSRect) bounds
{
const NSSize titleSize = [string size];
-
+
//no right padding, so that there's not too much space between this and the priority image
NSRect result;
if (![(FileListNode *)[self objectValue] isFolder])
result.size.width = MIN(titleSize.width, NSMaxX(bounds) - NSMinX(result));
}
result.size.height = titleSize.height;
-
+
return result;
}
- (NSRect) rectForStatusWithString: (NSAttributedString *) string withTitleRect: (NSRect) titleRect inBounds: (NSRect) bounds
{
const NSSize statusSize = [string size];
-
+
NSRect result;
if (![(FileListNode *)[self objectValue] isFolder])
{
result.size.width = NSMaxX(bounds) - NSMaxX(titleRect);
}
result.size.height = statusSize.height;
-
+
return result;
}
{
FileListNode * node = (FileListNode *)[self objectValue];
Torrent * torrent = [node torrent];
-
+
const CGFloat progress = [torrent fileProgress: node];
NSString * percentString = [NSString percentString: progress longDecimals: YES];
-
+
NSString * status = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@",
"Inspector -> Files tab -> file status string"), percentString, [NSString stringForFileSize: [node size]]];
-
+
return [[[NSAttributedString alloc] initWithString: status attributes: fStatusAttributes] autorelease];
}
{
Torrent * fTorrent;
NSMutableArray * fFileList;
-
+
IBOutlet FileOutlineView * fOutline;
-
+
NSString * fFilterText;
}
- (void) awakeFromNib
{
fFileList = [[NSMutableArray alloc] init];
-
+
[fOutline setDoubleAction: @selector(revealFile:)];
[fOutline setTarget: self];
-
+
//set table header tool tips
[[fOutline tableColumnWithIdentifier: @"Check"] setHeaderToolTip: NSLocalizedString(@"Download", "file table -> header tool tip")];
[[fOutline tableColumnWithIdentifier: @"Priority"] setHeaderToolTip: NSLocalizedString(@"Priority", "file table -> header tool tip")];
-
+
[fOutline setMenu: [self menu]];
-
+
[self setTorrent: nil];
}
{
[fFileList release];
[fFilterText release];
-
+
[super dealloc];
}
- (void) setTorrent: (Torrent *) torrent
{
fTorrent = torrent;
-
+
[fFileList setArray: [fTorrent fileList]];
-
+
[fFilterText release];
fFilterText = nil;
-
+
[fOutline reloadData];
[fOutline deselectAll: nil]; //do this after reloading the data #4575
}
text = nil;
components = nil;
}
-
+
if ((!text && !fFilterText) || (text && fFilterText && [text isEqualToString: fFilterText]))
return;
[fOutline beginUpdates];
-
+
NSUInteger currentIndex = 0, totalCount = 0;
NSMutableArray * itemsToAdd = [NSMutableArray array];
NSMutableIndexSet * itemsToAddIndexes = [NSMutableIndexSet indexSet];
-
+
NSMutableDictionary * removedIndexesForParents = nil; //ugly, but we can't modify the actual file nodes
-
+
NSArray * tempList = !text ? [fTorrent fileList] : [fTorrent flatFileList];
for (FileListNode * item in tempList)
{
}
}];
}
-
+
if (!filter)
{
FileListNode * parent = nil;
NSUInteger previousIndex = ![item isFolder] ? [self findFileNode: item inList: fFileList atIndexes: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(currentIndex, [fFileList count]-currentIndex)] currentParent: nil finalParent: &parent] : NSNotFound;
-
+
if (previousIndex == NSNotFound)
{
[itemsToAdd addObject: item];
else
{
[fFileList insertObject: item atIndex: currentIndex];
-
+
//figure out the index within the semi-edited table - UGLY
if (!removedIndexesForParents)
removedIndexesForParents = [NSMutableDictionary dictionary];
-
+
NSMutableIndexSet * removedIndexes = [removedIndexesForParents objectForKey: parent];
if (!removedIndexes)
{
previousIndex -= [removedIndexes countOfIndexesInRange: NSMakeRange(0, previousIndex)];
}
}
-
+
if (move)
[fOutline moveItemAtIndex: previousIndex inParent: parent toIndex: currentIndex inParent: nil];
-
+
++currentIndex;
}
-
+
++totalCount;
}
}
-
+
//remove trailing items - those are the unused
if (currentIndex < [fFileList count])
{
[fFileList removeObjectsInRange: removeRange];
[fOutline removeItemsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: removeRange] inParent: nil withAnimation: NSTableViewAnimationSlideDown];
}
-
+
//add new items
[fFileList insertObjects: itemsToAdd atIndexes: itemsToAddIndexes];
[fOutline insertItemsAtIndexes: itemsToAddIndexes inParent: nil withAnimation: NSTableViewAnimationSlideUp];
[fOutline endUpdates];
-
+
[fFilterText release];
fFilterText = [text retain];
}
- (void) refresh
{
[fTorrent updateFileStat];
-
+
[fOutline setNeedsDisplay: YES];
}
}
}
-- (BOOL) outlineView: (NSOutlineView *) outlineView isItemExpandable: (id) item
+- (BOOL) outlineView: (NSOutlineView *) outlineView isItemExpandable: (id) item
{
return [(FileListNode *)item isFolder];
}
else if ([identifier isEqualToString: @"Priority"])
{
[cell setRepresentedObject: item];
-
+
NSInteger hoveredRow = [fOutline hoveredRow];
[(FilePriorityCell *)cell setHovered: hoveredRow != -1 && hoveredRow == [fOutline rowForItem: item]];
}
indexSet = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTorrent fileCount])];
else
indexSet = [(FileListNode *)item indexes];
-
+
[fTorrent setFileCheckState: [object intValue] != NSOffState ? NSOnState : NSOffState forIndexes: indexSet];
[fOutline setNeedsDisplay: YES];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
}
}
}
}
else;
-
+
return nil;
}
- (void) setCheck: (id) sender
{
NSInteger state = [sender tag] == FILE_UNCHECK_TAG ? NSOffState : NSOnState;
-
+
NSIndexSet * indexSet = [fOutline selectedRowIndexes];
NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet];
for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
[itemIndexes addIndexes: [[fOutline itemAtRow: i] indexes]];
-
+
[fTorrent setFileCheckState: state forIndexes: itemIndexes];
[fOutline setNeedsDisplay: YES];
}
NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet];
for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
[itemIndexes addIndexes: [[fOutline itemAtRow: i] indexes]];
-
+
[fTorrent setFileCheckState: NSOnState forIndexes: itemIndexes];
-
+
NSMutableIndexSet * remainingItemIndexes = [NSMutableIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTorrent fileCount])];
[remainingItemIndexes removeIndexes: itemIndexes];
[fTorrent setFileCheckState: NSOffState forIndexes: remainingItemIndexes];
-
+
[fOutline setNeedsDisplay: YES];
}
case FILE_PRIORITY_LOW_TAG:
priority = TR_PRI_LOW;
}
-
+
NSIndexSet * indexSet = [fOutline selectedRowIndexes];
NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet];
for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
[itemIndexes addIndexes: [[fOutline itemAtRow: i] indexes]];
-
+
[fTorrent setFilePriority: priority forIndexes: itemIndexes];
[fOutline setNeedsDisplay: YES];
}
if (path)
[paths addObject: [NSURL fileURLWithPath: path]];
}
-
+
if ([paths count] > 0)
[[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: paths];
}
{
NSIndexSet * indexes = [fOutline selectedRowIndexes];
NSAssert([indexes count] == 1, @"1 file needs to be selected to rename, but %ld are selected", [indexes count]);
-
+
FileListNode * node = [fOutline itemAtRow: [indexes firstIndex]];
Torrent * torrent = [node torrent];
if (![torrent isFolder])
{
if (!fTorrent)
return NO;
-
+
SEL action = [menuItem action];
-
+
if (action == @selector(revealFile:))
{
NSIndexSet * indexSet = [fOutline selectedRowIndexes];
return YES;
return NO;
}
-
+
if (action == @selector(setCheck:))
{
if ([fOutline numberOfSelectedRows] == 0)
return NO;
-
+
NSIndexSet * indexSet = [fOutline selectedRowIndexes];
NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet];
for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
[itemIndexes addIndexes: [[fOutline itemAtRow: i] indexes]];
-
+
NSInteger state = ([menuItem tag] == FILE_CHECK_TAG) ? NSOnState : NSOffState;
return [fTorrent checkForFiles: itemIndexes] != state && [fTorrent canChangeDownloadCheckForFiles: itemIndexes];
}
-
+
if (action == @selector(setOnlySelectedCheck:))
{
if ([fOutline numberOfSelectedRows] == 0)
return NO;
-
+
NSIndexSet * indexSet = [fOutline selectedRowIndexes];
NSMutableIndexSet * itemIndexes = [NSMutableIndexSet indexSet];
for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
[itemIndexes addIndexes: [[fOutline itemAtRow: i] indexes]];
-
+
return [fTorrent canChangeDownloadCheckForFiles: itemIndexes];
}
-
+
if (action == @selector(setPriority:))
{
if ([fOutline numberOfSelectedRows] == 0)
[menuItem setState: NSOffState];
return NO;
}
-
+
//determine which priorities are checked
NSIndexSet * indexSet = [fOutline selectedRowIndexes];
tr_priority_t priority;
priority = TR_PRI_LOW;
break;
}
-
+
BOOL current = NO, canChange = NO;
for (NSInteger i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
{
NSIndexSet * fileIndexSet = [[fOutline itemAtRow: i] indexes];
if (![fTorrent canChangeDownloadCheckForFiles: fileIndexSet])
continue;
-
+
canChange = YES;
if ([fTorrent hasFilePriority: priority forIndexes: fileIndexSet])
{
break;
}
}
-
+
[menuItem setState: current ? NSOnState : NSOffState];
return canChange;
}
-
+
if (action == @selector(renameSelected:))
{
return [fOutline numberOfSelectedRows] == 1;
}
-
+
return YES;
}
- (NSMenu *) menu
{
NSMenu * menu = [[NSMenu alloc] initWithTitle: @"File Outline Menu"];
-
+
//check and uncheck
NSMenuItem * item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Check Selected", "File Outline -> Menu")
action: @selector(setCheck:) keyEquivalent: @""];
[item setTag: FILE_CHECK_TAG];
[menu addItem: item];
[item release];
-
+
item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Uncheck Selected", "File Outline -> Menu")
action: @selector(setCheck:) keyEquivalent: @""];
[item setTarget: self];
[item setTag: FILE_UNCHECK_TAG];
[menu addItem: item];
[item release];
-
+
//only check selected
item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Only Check Selected", "File Outline -> Menu")
action: @selector(setOnlySelectedCheck:) keyEquivalent: @""];
[item setTarget: self];
[menu addItem: item];
[item release];
-
+
[menu addItem: [NSMenuItem separatorItem]];
-
+
//priority
item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Priority", "File Outline -> Menu") action: NULL keyEquivalent: @""];
NSMenu * priorityMenu = [[NSMenu alloc] initWithTitle: @"File Priority Menu"];
[item setSubmenu: priorityMenu];
[menu addItem: item];
[item release];
-
+
item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"High", "File Outline -> Priority Menu")
action: @selector(setPriority:) keyEquivalent: @""];
[item setTarget: self];
[item setImage: [NSImage imageNamed: @"PriorityHighTemplate"]];
[priorityMenu addItem: item];
[item release];
-
+
item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Normal", "File Outline -> Priority Menu")
action: @selector(setPriority:) keyEquivalent: @""];
[item setTarget: self];
[item setImage: [NSImage imageNamed: @"PriorityNormalTemplate"]];
[priorityMenu addItem: item];
[item release];
-
+
item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Low", "File Outline -> Priority Menu")
action: @selector(setPriority:) keyEquivalent: @""];
[item setTarget: self];
[item setImage: [NSImage imageNamed: @"PriorityLowTemplate"]];
[priorityMenu addItem: item];
[item release];
-
+
[priorityMenu release];
-
+
[menu addItem: [NSMenuItem separatorItem]];
-
+
//reveal in finder
item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString(@"Show in Finder", "File Outline -> Menu")
action: @selector(revealFile:) keyEquivalent: @""];
[item setTarget: self];
[menu addItem: item];
[item release];
-
+
[menu addItem: [NSMenuItem separatorItem]];
-
+
//rename
item = [[NSMenuItem alloc] initWithTitle: [NSLocalizedString(@"Rename File", "File Outline -> Menu") stringByAppendingEllipsis]
action: @selector(renameSelected:) keyEquivalent: @""];
[item setTarget: self];
[menu addItem: item];
[item release];
-
+
return [menu autorelease];
}
- (NSUInteger) findFileNode: (FileListNode *) node inList: (NSArray *) list atIndexes: (NSIndexSet *) indexes currentParent: (FileListNode *) currentParent finalParent: (FileListNode **) parent
{
NSAssert(![node isFolder], @"Looking up folder node!");
-
+
__block NSUInteger retIndex = NSNotFound;
-
+
[list enumerateObjectsAtIndexes: indexes options: NSEnumerationConcurrent usingBlock: ^(id checkNode, NSUInteger index, BOOL * stop) {
if ([[checkNode indexes] containsIndex: [[node indexes] firstIndex]])
{
if (![checkNode isFolder])
{
NSAssert2([checkNode isEqualTo: node], @"Expected file nodes to be equal: %@ %@", checkNode, node);
-
+
*parent = currentParent;
retIndex = index;
}
NSAssert(subIndex != NSNotFound, @"We didn't find an expected file node.");
retIndex = subIndex;
}
-
+
*stop = YES;
}
}];
-
+
return retIndex;
}
FileNameCell * nameCell = [[FileNameCell alloc] init];
[[self tableColumnWithIdentifier: @"Name"] setDataCell: nameCell];
[nameCell release];
-
+
FilePriorityCell * priorityCell = [[FilePriorityCell alloc] init];
[[self tableColumnWithIdentifier: @"Priority"] setDataCell: priorityCell];
[priorityCell release];
-
+
[self setAutoresizesOutlineColumn: NO];
[self setIndentationPerLevel: 14.0];
-
+
fMouseRow = -1;
}
- (NSMenu *) menuForEvent: (NSEvent *) event
{
const NSInteger row = [self rowAtPoint: [self convertPoint: [event locationInWindow] fromView: nil]];
-
+
if (row >= 0)
{
if (![self isRowSelected: row])
}
else
[self deselectAll: self];
-
+
return [self menu];
}
{
FileNameCell * cell = (FileNameCell *)[self preparedCellAtColumn: [self columnWithIdentifier: @"Name"] row: row];
NSRect iconRect = [cell imageRectForBounds: [self rectOfRow: row]];
-
+
iconRect.origin.x += [self indentationPerLevel] * (CGFloat)([self levelForRow: row] + 1);
return iconRect;
}
- (void) updateTrackingAreas
{
[super updateTrackingAreas];
-
+
for (NSTrackingArea * area in [self trackingAreas])
{
if ([area owner] == self && [[area userInfo] objectForKey: @"Row"])
[self removeTrackingArea: area];
}
-
+
NSRange visibleRows = [self rowsInRect: [self visibleRect]];
if (visibleRows.length == 0)
return;
-
+
NSPoint mouseLocation = [self convertPoint: [[self window] mouseLocationOutsideOfEventStream] fromView: nil];
-
+
for (NSInteger row = visibleRows.location, col = [self columnWithIdentifier: @"Priority"]; (NSUInteger)row < NSMaxRange(visibleRows); row++)
{
FilePriorityCell * cell = (FilePriorityCell *)[self preparedCellAtColumn: col row: row];
-
+
NSDictionary * userInfo = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: row] forKey: @"Row"];
[cell addTrackingAreasForView: self inRect: [self frameOfCellAtColumn: col row: row] withUserInfo: userInfo
mouseLocation: mouseLocation];
[self setTrackingMode: NSSegmentSwitchTrackingSelectAny];
[self setControlSize: NSMiniControlSize];
[self setSegmentCount: 3];
-
+
for (NSInteger i = 0; i < [self segmentCount]; i++)
{
[self setLabel: @"" forSegment: i];
[self setWidth: 9.0f forSegment: i]; //9 is minimum size to get proper look
}
-
+
[self setImage: [NSImage imageNamed: @"PriorityControlLow"] forSegment: 0];
[self setImage: [NSImage imageNamed: @"PriorityControlNormal"] forSegment: 1];
[self setImage: [NSImage imageNamed: @"PriorityControlHigh"] forSegment: 2];
-
+
fHoverRow = NO;
}
return self;
- (void) setSelected: (BOOL) flag forSegment: (NSInteger) segment
{
[super setSelected: flag forSegment: segment];
-
+
//only for when clicking manually
NSInteger priority;
switch (segment)
priority = TR_PRI_HIGH;
break;
}
-
+
Torrent * torrent = [(FileListNode *)[self representedObject] torrent];
[torrent setFilePriority: priority forIndexes: [(FileListNode *)[self representedObject] indexes]];
-
+
FileOutlineView * controlView = (FileOutlineView *)[self controlView];
[controlView setNeedsDisplay: YES];
}
mouseLocation: (NSPoint) mouseLocation
{
NSTrackingAreaOptions options = NSTrackingEnabledDuringMouseDrag | NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways;
-
+
if (NSMouseInRect(mouseLocation, cellFrame, [controlView isFlipped]))
{
options |= NSTrackingAssumeInside;
[controlView setNeedsDisplayInRect: cellFrame];
}
-
+
NSTrackingArea * area = [[NSTrackingArea alloc] initWithRect: cellFrame options: options owner: controlView userInfo: userInfo];
[controlView addTrackingArea: area];
[area release];
FileListNode * node = [self representedObject];
Torrent * torrent = [node torrent];
NSSet * priorities = [torrent filePrioritiesForIndexes: [node indexes]];
-
+
const NSUInteger count = [priorities count];
if (fHoverRow && count > 0)
{
[super setSelected: [priorities containsObject: [NSNumber numberWithInteger: TR_PRI_LOW]] forSegment: 0];
[super setSelected: [priorities containsObject: [NSNumber numberWithInteger: TR_PRI_NORMAL]] forSegment: 1];
[super setSelected: [priorities containsObject: [NSNumber numberWithInteger: TR_PRI_HIGH]] forSegment: 2];
-
+
[super drawWithFrame: cellFrame inView: controlView];
}
else
{
NSMutableArray * images = [NSMutableArray arrayWithCapacity: MAX(count, 1u)];
CGFloat totalWidth;
-
+
if (count == 0)
{
//if ([self backgroundStyle] != NSBackgroundStyleDark)
else
{
NSColor * priorityColor = [self backgroundStyle] == NSBackgroundStyleDark ? [NSColor whiteColor] : [NSColor darkGrayColor];
-
+
totalWidth = 0.0;
if ([priorities containsObject: [NSNumber numberWithInteger: TR_PRI_LOW]])
{
totalWidth += [image size].width;
}
}
-
+
if (count > 1)
totalWidth -= IMAGE_OVERLAP * (count-1);
-
+
CGFloat currentWidth = floor(NSMidX(cellFrame) - totalWidth * 0.5);
-
+
for (NSImage * image in images)
{
const NSSize imageSize = [image size];
const NSRect imageRect = NSMakeRect(currentWidth, floor(NSMidY(cellFrame) - imageSize.height * 0.5), imageSize.width, imageSize.height);
-
+
[image drawInRect: imageRect fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0 respectFlipped: YES hints: nil];
-
+
currentWidth += imageSize.width - IMAGE_OVERLAP;
}
}
FileListNode * _node;
void (^_completionHandler)(BOOL);
NSString * _originalName;
-
+
IBOutlet NSTextField * _labelField;
IBOutlet NSTextField * _inputField;
IBOutlet NSButton * _renameButton;
{
NSParameterAssert(torrent != nil);
NSParameterAssert(window != nil);
-
+
FileRenameSheetController * renamer = [[FileRenameSheetController alloc] initWithWindowNibName: @"FileRenameSheetController"];
-
+
renamer.torrent = torrent;
renamer.completionHandler = completionHandler;
-
+
[NSApp beginSheet: [renamer window] modalForWindow: window modalDelegate: self didEndSelector: @selector(sheetDidEnd:returnCode:contextInfo:) contextInfo: renamer];
}
{
NSParameterAssert(node != nil);
NSParameterAssert(window != nil);
-
+
FileRenameSheetController * renamer = [[FileRenameSheetController alloc] initWithWindowNibName: @"FileRenameSheetController"];
-
+
renamer.torrent = [node torrent];
renamer.node = node;
renamer.completionHandler = completionHandler;
-
+
[NSApp beginSheet: [renamer window] modalForWindow: window modalDelegate: self didEndSelector: @selector(sheetDidEnd:returnCode:contextInfo:) contextInfo: renamer];
}
{
FileRenameSheetController * renamer = contextInfo;
NSParameterAssert([renamer isKindOfClass:[FileRenameSheetController class]]);
-
+
renamer.completionHandler(returnCode == NSOKButton);
-
+
//TODO: retain/release logic needs to be figured out for ARC (when ARC is enabled)
[renamer release];
[sheet orderOut: self];
- (void) windowDidLoad
{
[super windowDidLoad];
-
+
self.originalName = [self.node name] ?: [self.torrent name];
NSString * label = [NSString stringWithFormat: NSLocalizedString(@"Rename the file \"%@\":", "rename sheet label"), self.originalName];
[self.labelField setStringValue: label];
-
+
[self.inputField setStringValue: self.originalName];
[self.renameButton setEnabled: NO];
-
+
//resize the buttons so that they're long enough and the same width
const NSRect oldRenameFrame = [self.renameButton frame];
const NSRect oldCancelFrame = [self.cancelButton frame];
-
+
//get the extra width of the rename button from the English xib - the width from sizeToFit is too squished
[self.renameButton sizeToFit];
const CGFloat extra = NSWidth(oldRenameFrame) - NSWidth([self.renameButton frame]);
-
+
[self.renameButton setTitle: NSLocalizedString(@"Rename", "rename sheet button")];
[self.cancelButton setTitle: NSLocalizedString(@"Cancel", "rename sheet button")];
-
+
[self.renameButton sizeToFit];
[self.cancelButton sizeToFit];
NSRect newRenameFrame = [self.renameButton frame];
NSRect newCancelFrame = [self.cancelButton frame];
newRenameFrame.size.width = MAX(NSWidth(newRenameFrame), NSWidth(newCancelFrame)) + extra;
newCancelFrame.size.width = MAX(NSWidth(newRenameFrame), NSWidth(newCancelFrame)) + extra;
-
+
const CGFloat renameWidthIncrease = NSWidth(newRenameFrame) - NSWidth(oldRenameFrame);
newRenameFrame.origin.x -= renameWidthIncrease;
[self.renameButton setFrame:newRenameFrame];
-
+
const CGFloat cancelWidthIncrease = NSWidth(newCancelFrame) - NSWidth(oldCancelFrame);
newCancelFrame.origin.x -= renameWidthIncrease + cancelWidthIncrease;
[self.cancelButton setFrame:newCancelFrame];
NSBeep();
}
};
-
+
if (self.node)
[self.torrent renameFileNode: self.node withName: [self.inputField stringValue] completionHandler: completionHandler];
else
{
IBOutlet FilterButton * fNoFilterButton, * fActiveFilterButton, * fDownloadFilterButton,
* fSeedFilterButton, * fPauseFilterButton;
-
+
IBOutlet NSSearchField * fSearchField;
-
+
IBOutlet NSPopUpButton * fGroupsButton;
}
[fDownloadFilterButton setTitle: NSLocalizedString(@"Downloading", "Filter Bar -> filter button")];
[fSeedFilterButton setTitle: NSLocalizedString(@"Seeding", "Filter Bar -> filter button")];
[fPauseFilterButton setTitle: NSLocalizedString(@"Paused", "Filter Bar -> filter button")];
-
+
[[fNoFilterButton cell] setBackgroundStyle: NSBackgroundStyleRaised];
[[fActiveFilterButton cell] setBackgroundStyle: NSBackgroundStyleRaised];
[[fDownloadFilterButton cell] setBackgroundStyle: NSBackgroundStyleRaised];
[[fSeedFilterButton cell] setBackgroundStyle: NSBackgroundStyleRaised];
[[fPauseFilterButton cell] setBackgroundStyle: NSBackgroundStyleRaised];
-
+
[[[[fSearchField cell] searchMenuTemplate] itemWithTag: FILTER_TYPE_TAG_NAME] setTitle:
NSLocalizedString(@"Name", "Filter Bar -> filter menu")];
[[[[fSearchField cell] searchMenuTemplate] itemWithTag: FILTER_TYPE_TAG_TRACKER] setTitle:
NSLocalizedString(@"Tracker", "Filter Bar -> filter menu")];
-
+
[[[fGroupsButton menu] itemWithTag: GROUP_FILTER_ALL_TAG] setTitle:
NSLocalizedString(@"All Groups", "Filter Bar -> group filter menu")];
-
+
[self resizeBar];
-
+
//set current filter
NSString * filterType = [[NSUserDefaults standardUserDefaults] stringForKey: @"Filter"];
-
+
NSButton * currentFilterButton;
if ([filterType isEqualToString: FILTER_ACTIVE])
currentFilterButton = fActiveFilterButton;
currentFilterButton = fNoFilterButton;
}
[currentFilterButton setState: NSOnState];
-
+
//set filter search type
NSString * filterSearchType = [[NSUserDefaults standardUserDefaults] stringForKey: @"FilterSearchType"];
-
+
NSMenu * filterSearchMenu = [[fSearchField cell] searchMenuTemplate];
NSString * filterSearchTypeTitle;
if ([filterSearchType isEqualToString: FILTER_TYPE_TRACKER])
filterSearchTypeTitle = [[filterSearchMenu itemWithTag: FILTER_TYPE_TAG_NAME] title];
}
[[fSearchField cell] setPlaceholderString: filterSearchTypeTitle];
-
+
NSString * searchString;
if ((searchString = [[NSUserDefaults standardUserDefaults] stringForKey: @"FilterSearchString"]))
[fSearchField setStringValue: searchString];
-
+
[self updateGroupsButton];
-
+
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(resizeBar)
name: NSWindowDidResizeNotification object: [[self view] window]];
-
+
//update when groups change
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateGroups:)
name: @"UpdateGroups" object: nil];
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
-
+
[super dealloc];
}
- (void) setFilter: (id) sender
{
NSString * oldFilterType = [[NSUserDefaults standardUserDefaults] stringForKey: @"Filter"];
-
+
NSButton * prevFilterButton;
if ([oldFilterType isEqualToString: FILTER_PAUSE])
prevFilterButton = fPauseFilterButton;
prevFilterButton = fDownloadFilterButton;
else
prevFilterButton = fNoFilterButton;
-
+
if (sender != prevFilterButton)
{
[prevFilterButton setState: NSOffState];
}
else
[sender setState: NSOnState];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"ApplyFilter" object: nil];
}
- (void) switchFilter: (BOOL) right
{
NSString * filterType = [[NSUserDefaults standardUserDefaults] stringForKey: @"Filter"];
-
+
NSButton * button;
if ([filterType isEqualToString: FILTER_NONE])
button = right ? fActiveFilterButton : fPauseFilterButton;
button = right ? fNoFilterButton : fSeedFilterButton;
else
button = fNoFilterButton;
-
+
[self setFilter: button];
}
- (void) setSearchType: (id) sender
{
NSString * oldFilterType = [[NSUserDefaults standardUserDefaults] stringForKey: @"FilterSearchType"];
-
+
NSInteger prevTag, currentTag = [sender tag];
if ([oldFilterType isEqualToString: FILTER_TYPE_TRACKER])
prevTag = FILTER_TYPE_TAG_TRACKER;
else
prevTag = FILTER_TYPE_TAG_NAME;
-
+
if (currentTag != prevTag)
{
NSString * filterType;
filterType = FILTER_TYPE_TRACKER;
else
filterType = FILTER_TYPE_NAME;
-
+
[[NSUserDefaults standardUserDefaults] setObject: filterType forKey: @"FilterSearchType"];
-
+
[[fSearchField cell] setPlaceholderString: [sender title]];
}
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"ApplyFilter" object: nil];
}
{
[[NSUserDefaults standardUserDefaults] setInteger: [sender tag] forKey: @"FilterGroup"];
[self updateGroupsButton];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"ApplyFilter" object: nil];
}
- (void) reset: (BOOL) updateUI
{
[[NSUserDefaults standardUserDefaults] setInteger: GROUP_FILTER_ALL_TAG forKey: @"FilterGroup"];
-
+
if (updateUI)
- {
+ {
[self updateGroupsButton];
-
+
[self setFilter: fNoFilterButton];
-
+
[fSearchField setStringValue: @""];
[self setSearchText: fSearchField];
}
{
for (NSInteger i = [menu numberOfItems]-1; i >= 3; i--)
[menu removeItemAtIndex: i];
-
+
NSMenu * groupMenu = [[GroupsController groups] groupMenuWithTarget: self action: @selector(setGroupFilter:) isSmall: YES];
-
+
const NSInteger groupMenuCount = [groupMenu numberOfItems];
for (NSInteger i = 0; i < groupMenuCount; i++)
{
- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
{
const SEL action = [menuItem action];
-
+
//check proper filter search item
if (action == @selector(setSearchType:))
{
NSString * filterType = [[NSUserDefaults standardUserDefaults] stringForKey: @"FilterSearchType"];
-
+
BOOL state;
if ([menuItem tag] == FILTER_TYPE_TAG_TRACKER)
state = [filterType isEqualToString: FILTER_TYPE_TRACKER];
else
state = [filterType isEqualToString: FILTER_TYPE_NAME];
-
+
[menuItem setState: state ? NSOnState : NSOffState];
return YES;
}
-
+
if (action == @selector(setGroupFilter:))
{
[menuItem setState: [menuItem tag] == [[NSUserDefaults standardUserDefaults] integerForKey: @"FilterGroup"]
? NSOnState : NSOffState];
return YES;
}
-
+
return YES;
}
[fDownloadFilterButton sizeToFit];
[fSeedFilterButton sizeToFit];
[fPauseFilterButton sizeToFit];
-
+
NSRect allRect = [fNoFilterButton frame];
NSRect activeRect = [fActiveFilterButton frame];
NSRect downloadRect = [fDownloadFilterButton frame];
NSRect seedRect = [fSeedFilterButton frame];
NSRect pauseRect = [fPauseFilterButton frame];
-
+
//size search filter to not overlap buttons
NSRect searchFrame = [fSearchField frame];
searchFrame.origin.x = NSMaxX(pauseRect) + 5.0;
searchFrame.size.width = NSWidth([[self view] frame]) - searchFrame.origin.x - 5.0;
-
+
//make sure it is not too long
if (NSWidth(searchFrame) > SEARCH_MAX_WIDTH)
{
{
searchFrame.origin.x += NSWidth(searchFrame) - SEARCH_MIN_WIDTH;
searchFrame.size.width = SEARCH_MIN_WIDTH;
-
+
//calculate width the buttons can take up
const CGFloat allowedWidth = (searchFrame.origin.x - 5.0) - allRect.origin.x;
const CGFloat currentWidth = NSWidth(allRect) + NSWidth(activeRect) + NSWidth(downloadRect) + NSWidth(seedRect)
+ NSWidth(pauseRect) + 4.0; //add 4 for space between buttons
const CGFloat ratio = allowedWidth / currentWidth;
-
+
//decrease button widths proportionally
allRect.size.width = NSWidth(allRect) * ratio;
activeRect.size.width = NSWidth(activeRect) * ratio;
pauseRect.size.width = NSWidth(pauseRect) * ratio;
}
else;
-
+
activeRect.origin.x = NSMaxX(allRect) + 1.0;
downloadRect.origin.x = NSMaxX(activeRect) + 1.0;
seedRect.origin.x = NSMaxX(downloadRect) + 1.0;
pauseRect.origin.x = NSMaxX(seedRect) + 1.0;
-
+
[fNoFilterButton setFrame: allRect];
[fActiveFilterButton setFrame: activeRect];
[fDownloadFilterButton setFrame: downloadRect];
[fSeedFilterButton setFrame: seedRect];
[fPauseFilterButton setFrame: pauseRect];
-
+
[fSearchField setFrame: searchFrame];
}
- (void) updateGroupsButton
{
const NSInteger groupIndex = [[NSUserDefaults standardUserDefaults] integerForKey: @"FilterGroup"];
-
+
NSImage * icon;
NSString * toolTip;
if (groupIndex == GROUP_FILTER_ALL_TAG)
: NSLocalizedString(@"None", "Groups -> Button");
toolTip = [NSLocalizedString(@"Group", "Groups -> Button") stringByAppendingFormat: @": %@", groupName];
}
-
+
[[[fGroupsButton menu] itemAtIndex: 0] setImage: icon];
[fGroupsButton setToolTip: toolTip];
}
if ([NSApp isOnYosemiteOrBetter]) {
[[NSColor windowBackgroundColor] setFill];
NSRectFill(rect);
-
+
const NSRect lineBorderRect = NSMakeRect(NSMinX(rect), 0.0, NSWidth(rect), 1.0);
if (NSIntersectsRect(lineBorderRect, rect))
{
NSInteger count = 0;
NSRect gridRects[2];
NSColor * colorRects[2];
-
+
NSRect lineBorderRect = NSMakeRect(NSMinX(rect), NSHeight([self bounds]) - 1.0, NSWidth(rect), 1.0);
if (NSIntersectsRect(lineBorderRect, rect))
{
gridRects[count] = lineBorderRect;
colorRects[count] = [NSColor whiteColor];
++count;
-
+
rect.size.height -= 1.0;
}
-
+
lineBorderRect.origin.y = 0.0;
if (NSIntersectsRect(lineBorderRect, rect))
{
gridRects[count] = lineBorderRect;
colorRects[count] = [NSColor colorWithCalibratedWhite: 0.65 alpha: 1.0];
++count;
-
+
rect.origin.y += 1.0;
rect.size.height -= 1.0;
}
-
+
if (!NSIsEmptyRect(rect))
{
const NSRect gradientRect = NSMakeRect(NSMinX(rect), 1.0, NSWidth(rect), NSHeight([self bounds]) - 1.0 - 1.0); //proper gradient requires the full height of the bar
[fGradient drawInRect: gradientRect angle: 270.0];
}
-
+
NSRectFillListWithColors(gridRects, colorRects, count);
}
}
{
if (count == fCount)
return;
-
+
fCount = count;
-
+
[self setToolTip: fCount == 1 ? NSLocalizedString(@"1 transfer", "Filter Button -> tool tip")
: [NSString stringWithFormat: NSLocalizedString(@"%@ transfers", "Filter Bar Button -> tool tip"),
[NSString formattedUInteger: fCount]]];
{
tr_session * fHandle;
NSUserDefaults * fDefaults;
-
+
IBOutlet NSTextField * fUploadLimitField, * fDownloadLimitField;
-
+
IBOutlet NSTextField * fRatioStopField, * fIdleStopField;
-
+
NSString * fInitialString;
}
if ((self = [super initWithNibName: @"GlobalOptionsPopover" bundle: nil]))
{
fHandle = handle;
-
+
fDefaults = [NSUserDefaults standardUserDefaults];
}
-
+
return self;
}
{
[fUploadLimitField setIntValue: [fDefaults integerForKey: @"UploadLimit"]];
[fDownloadLimitField setIntValue: [fDefaults integerForKey: @"DownloadLimit"]];
-
+
[fRatioStopField setFloatValue: [fDefaults floatForKey: @"RatioLimit"]];
[fIdleStopField setIntegerValue: [fDefaults integerForKey: @"IdleLimitMinutes"]];
- (IBAction) setDownSpeedSetting: (id) sender
{
tr_sessionLimitSpeed(fHandle, TR_DOWN, [fDefaults boolForKey: @"CheckDownload"]);
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"SpeedLimitUpdate" object: nil];
}
const NSInteger limit = [sender integerValue];
[fDefaults setInteger: limit forKey: @"DownloadLimit"];
tr_sessionSetSpeedLimit_KBps(fHandle, TR_DOWN, limit);
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateSpeedLimitValuesOutsidePrefs" object: nil];
[[NSNotificationCenter defaultCenter] postNotificationName: @"SpeedLimitUpdate" object: nil];
}
- (IBAction) setUpSpeedSetting: (id) sender
{
tr_sessionLimitSpeed(fHandle, TR_UP, [fDefaults boolForKey: @"CheckUpload"]);
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateSpeedLimitValuesOutsidePrefs" object: nil];
[[NSNotificationCenter defaultCenter] postNotificationName: @"SpeedLimitUpdate" object: nil];
}
const NSInteger limit = [sender integerValue];
[fDefaults setInteger: limit forKey: @"UploadLimit"];
tr_sessionSetSpeedLimit_KBps(fHandle, TR_UP, limit);
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"SpeedLimitUpdate" object: nil];
}
- (IBAction) setRatioStopSetting: (id) sender
{
tr_sessionSetRatioLimited(fHandle, [fDefaults boolForKey: @"RatioCheck"]);
-
+
//reload main table for seeding progress
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
-
+
//reload global settings in inspector
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGlobalOptions" object: nil];
}
const CGFloat value = [sender floatValue];
[fDefaults setFloat: value forKey: @"RatioLimit"];
tr_sessionSetRatioLimit(fHandle, value);
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateRatioStopValueOutsidePrefs" object: nil];
-
+
//reload main table for seeding progress
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
-
+
//reload global settings in inspector
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGlobalOptions" object: nil];
}
- (IBAction) setIdleStopSetting: (id) sender
{
tr_sessionSetIdleLimited(fHandle, [fDefaults boolForKey: @"IdleLimitCheck"]);
-
+
//reload main table for remaining seeding time
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
-
+
//reload global settings in inspector
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGlobalOptions" object: nil];
}
const NSInteger value = [sender integerValue];
[fDefaults setInteger: value forKey: @"IdleLimitMinutes"];
tr_sessionSetIdleLimit(fHandle, value);
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateIdleStopValueOutsidePrefs" object: nil];
-
+
//reload main table for remaining seeding time
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
-
+
//reload global settings in inspector
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGlobalOptions" object: nil];
}
{
[fInitialString release];
fInitialString = [[control stringValue] retain];
-
+
return YES;
}
- (void) validate
{
NSSegmentedControl * control = (NSSegmentedControl *)[self view];
-
+
for (NSInteger i = 0; i < [control segmentCount]; i++)
[control setEnabled: [[self target] validateToolbarItem:
[[[NSToolbarItem alloc] initWithItemIdentifier: [fIdentifiers objectAtIndex: i]] autorelease]] forSegment: i];
NSMenuItem * menuItem = [[NSMenuItem alloc] initWithTitle: [self label] action: NULL keyEquivalent: @""];
NSMenu * menu = [[NSMenu alloc] initWithTitle: [self label]];
[menuItem setSubmenu: menu];
-
+
[menu setAutoenablesItems: NO];
-
+
const NSInteger count = [(NSSegmentedControl *)[self view] segmentCount];
for (NSInteger i = 0; i < count; i++)
{
NSMenuItem * addItem = [[NSMenuItem alloc] initWithTitle: [labels objectAtIndex: i] action: [self action] keyEquivalent: @""];
[addItem setTarget: [self target]];
[addItem setTag: i];
-
+
[menu addItem: addItem];
[addItem release];
}
-
+
[menu release];
[self setMenuFormRepresentation: menuItem];
[menuItem release];
- (NSMenuItem *) menuFormRepresentation
{
NSMenuItem * menuItem = [super menuFormRepresentation];
-
+
const NSInteger count = [(NSSegmentedControl *)[self view] segmentCount];
for (NSInteger i = 0; i < count; i++)
[[[menuItem submenu] itemAtIndex: i] setEnabled: [[self target] validateToolbarItem:
[[[NSToolbarItem alloc] initWithItemIdentifier: [fIdentifiers objectAtIndex: i]] autorelease]]];
-
+
return menuItem;
}
[NSColor redColor], @"Color",
NSLocalizedString(@"Red", "Groups -> Name"), @"Name",
[NSNumber numberWithInteger: 0], @"Index", nil];
-
+
NSMutableDictionary * orange = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSColor orangeColor], @"Color",
NSLocalizedString(@"Orange", "Groups -> Name"), @"Name",
[NSNumber numberWithInteger: 1], @"Index", nil];
-
+
NSMutableDictionary * yellow = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSColor yellowColor], @"Color",
NSLocalizedString(@"Yellow", "Groups -> Name"), @"Name",
[NSNumber numberWithInteger: 2], @"Index", nil];
-
+
NSMutableDictionary * green = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSColor greenColor], @"Color",
NSLocalizedString(@"Green", "Groups -> Name"), @"Name",
[NSNumber numberWithInteger: 3], @"Index", nil];
-
+
NSMutableDictionary * blue = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSColor blueColor], @"Color",
NSLocalizedString(@"Blue", "Groups -> Name"), @"Name",
[NSNumber numberWithInteger: 4], @"Index", nil];
-
+
NSMutableDictionary * purple = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSColor purpleColor], @"Color",
NSLocalizedString(@"Purple", "Groups -> Name"), @"Name",
[NSNumber numberWithInteger: 5], @"Index", nil];
-
+
NSMutableDictionary * gray = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSColor grayColor], @"Color",
NSLocalizedString(@"Gray", "Groups -> Name"), @"Name",
[NSNumber numberWithInteger: 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 orderIndex = [self rowValueForIndex: index];
[[fGroups objectAtIndex: orderIndex] setObject: name forKey: @"Name"];
[self saveGroups];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGroups" object: self];
}
{
NSMutableDictionary * dict = [fGroups objectAtIndex: [self rowValueForIndex: index]];
[dict removeObjectForKey: @"Icon"];
-
+
[dict setObject: color forKey: @"Color"];
-
+
[[GroupsController groups] saveGroups];
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGroups" object: self];
}
- (void) setUsesCustomDownloadLocation: (BOOL) useCustomLocation forIndex: (NSInteger) index
{
NSMutableDictionary * dict = [fGroups objectAtIndex: [self rowValueForIndex: index]];
-
+
[dict setObject: [NSNumber numberWithBool: useCustomLocation] forKey: @"UsesCustomDownloadLocation"];
-
+
[[GroupsController groups] saveGroups];
}
{
NSMutableDictionary * dict = [fGroups objectAtIndex: [self rowValueForIndex: index]];
[dict setObject: location forKey: @"CustomDownloadLocation"];
-
+
[[GroupsController groups] saveGroups];
}
NSInteger orderIndex = [self rowValueForIndex: index];
if (orderIndex == -1)
return NO;
-
+
NSNumber * assignRules = [[fGroups objectAtIndex: orderIndex] objectForKey: @"UsesAutoGroupRules"];
return assignRules && [assignRules boolValue];
}
- (void) setUsesAutoAssignRules: (BOOL) useAutoAssignRules forIndex: (NSInteger) index
{
NSMutableDictionary * dict = [fGroups objectAtIndex: [self rowValueForIndex: index]];
-
+
[dict setObject: [NSNumber numberWithBool: useAutoAssignRules] forKey: @"UsesAutoGroupRules"];
-
+
[[GroupsController groups] saveGroups];
}
{
NSInteger orderIndex = [self rowValueForIndex: index];
if (orderIndex == -1)
- return nil;
-
- return [[fGroups objectAtIndex: orderIndex] objectForKey: @"AutoGroupRules"];
+ return nil;
+
+ return [[fGroups objectAtIndex: orderIndex] objectForKey: @"AutoGroupRules"];
}
- (void) setAutoAssignRules: (NSPredicate *) predicate forIndex: (NSInteger) index
{
NSMutableDictionary * dict = [fGroups objectAtIndex: [self rowValueForIndex: index]];
-
+
if (predicate)
{
[dict setObject: predicate forKey: @"AutoGroupRules"];
NSMutableIndexSet * candidates = [NSMutableIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fGroups count]+1)];
for (NSDictionary * dict in fGroups)
[candidates removeIndex: [[dict objectForKey: @"Index"] integerValue]];
-
+
const NSInteger index = [candidates firstIndex];
-
+
[fGroups addObject: [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInteger: 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];
}
{
NSInteger index = [[[fGroups objectAtIndex: row] objectForKey: @"Index"] integerValue];
[fGroups removeObjectAtIndex: row];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"GroupValueRemoved" object: self userInfo:
[NSDictionary dictionaryWithObject: [NSNumber numberWithInteger: index] forKey: @"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];
[icon release];
}
else
[item setImage: icon];
-
+
[menu addItem: item];
[item release];
-
+
for (NSMutableDictionary * dict in fGroups)
{
item = [[NSMenuItem alloc] initWithTitle: [dict objectForKey: @"Name"] action: action keyEquivalent: @""];
[item setTarget: target];
-
+
[item setTag: [[dict objectForKey: @"Index"] integerValue]];
-
+
NSImage * icon = [self imageForGroup: dict];
if (small)
{
icon = [icon copy];
[icon setSize: NSMakeSize(ICON_WIDTH_SMALL, ICON_WIDTH_SMALL)];
-
+
[item setImage: icon];
[icon release];
}
else
[item setImage: icon];
-
+
[menu addItem: item];
[item release];
}
-
+
return [menu autorelease];
}
[groups addObject: tempDict];
[tempDict release];
}
-
+
[[NSUserDefaults standardUserDefaults] setObject: [NSKeyedArchiver archivedDataWithRootObject: groups] forKey: @"GroupDicts"];
}
NSImage * image;
if ((image = [dict objectForKey: @"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 objectForKey: @"Color"];
-
+
[icon lockFocus];
-
+
//border
NSGradient * gradient = [[NSGradient alloc] initWithStartingColor: [color blendedColorWithFraction: 0.45 ofColor:
[NSColor whiteColor]] endingColor: color];
[gradient drawInBezierPath: bp angle: 270.0];
[gradient release];
-
+
//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];
[gradient release];
-
+
[icon unlockFocus];
-
+
[dict setObject: icon forKey: @"Icon"];
[icon release];
-
+
return icon;
}
{
if (![self usesAutoAssignRulesForIndex: index])
return NO;
-
+
NSPredicate * predicate = [self autoAssignRulesForIndex: index];
BOOL eval = NO;
@try
{
IBOutlet NSTableView * fTableView;
IBOutlet NSSegmentedControl * fAddRemoveControl;
-
+
IBOutlet NSColorWell * fSelectedColorView;
IBOutlet NSTextField * fSelectedColorNameField;
IBOutlet NSButton * fCustomLocationEnableCheck;
IBOutlet NSPopUpButton * fCustomLocationPopUp;
-
+
IBOutlet NSButton * fAutoAssignRulesEnableCheck;
IBOutlet NSButton * fAutoAssignRulesEditButton;
-
+
IBOutlet NSWindow * fGroupRulesSheetWindow;
IBOutlet NSPredicateEditor * fRuleEditor;
- (void) awakeFromNib
{
[fTableView registerForDraggedTypes: [NSArray arrayWithObject: GROUP_TABLE_VIEW_DATA_TYPE]];
-
+
[fSelectedColorView addObserver: self forKeyPath: @"color" options: 0 context: NULL];
-
+
[self updateSelectedGroup];
}
{
GroupsController * groupsController = [GroupsController groups];
NSInteger groupsIndex = [groupsController indexForRow: row];
-
+
NSString * identifier = [tableColumn identifier];
if ([identifier isEqualToString: @"Color"])
return [groupsController imageForIndex: groupsIndex];
[fTableView setDropRow: row dropOperation: NSTableViewDropAbove];
return NSDragOperationGeneric;
}
-
+
return NSDragOperationNone;
}
{
NSIndexSet * indexes = [NSKeyedUnarchiver unarchiveObjectWithData: [pasteboard dataForType: GROUP_TABLE_VIEW_DATA_TYPE]];
NSInteger oldRow = [indexes firstIndex];
-
+
if (oldRow < newRow)
newRow--;
[fTableView beginUpdates];
-
+
[[GroupsController groups] moveGroupAtRow: oldRow toRow: newRow];
[fTableView moveRowAtIndex: oldRow toIndex: newRow];
[fTableView endUpdates];
}
-
+
return YES;
}
[[NSColorPanel sharedColorPanel] close];
NSInteger row;
-
+
switch ([[sender cell] tagForSegment: [sender selectedSegment]])
{
case ADD_TAG:
[fTableView beginUpdates];
-
+
[[GroupsController groups] addNewGroup];
-
+
row = [fTableView numberOfRows];
[fTableView insertRowsAtIndexes: [NSIndexSet indexSetWithIndex: row] withAnimation: NSTableViewAnimationSlideUp];
[fTableView endUpdates];
-
+
[fTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: row] byExtendingSelection: NO];
[fTableView scrollRowToVisible: row];
-
+
[[fSelectedColorNameField window] makeFirstResponder: fSelectedColorNameField];
-
+
break;
-
+
case REMOVE_TAG:
row = [fTableView selectedRow];
-
+
[fTableView beginUpdates];
-
- [[GroupsController groups] removeGroupWithRowIndex: row];
+
+ [[GroupsController groups] removeGroupWithRowIndex: row];
[fTableView removeRowsAtIndexes: [NSIndexSet indexSetWithIndex: row] withAnimation: NSTableViewAnimationSlideUp];
[fTableView endUpdates];
-
+
if ([fTableView numberOfRows] > 0)
{
if (row == [fTableView numberOfRows])
[fTableView selectRowIndexes: [NSIndexSet indexSetWithIndex: row] byExtendingSelection: NO];
[fTableView scrollRowToVisible: row];
}
-
+
break;
}
-
+
[self updateSelectedGroup];
}
[panel setCanChooseFiles: NO];
[panel setCanChooseDirectories: YES];
[panel setCanCreateDirectories: YES];
-
+
[panel beginSheetModalForWindow: [fCustomLocationPopUp window] completionHandler: ^(NSInteger result) {
const NSInteger index = [[GroupsController groups] indexForRow: [fTableView selectedRow]];
if (result == NSFileHandlingPanelOKButton)
if (![[GroupsController groups] customDownloadLocationForIndex: index])
[[GroupsController groups] setUsesCustomDownloadLocation: NO forIndex: index];
}
-
+
[self refreshCustomLocationWithSingleGroup];
-
+
[fCustomLocationPopUp selectItemAtIndex: 0];
}];
}
[NSBundle loadNibNamed: @"GroupRules" owner: self];
NSInteger index = [[GroupsController groups] indexForRow: [fTableView selectedRow]];
- NSPredicate *predicate = [[GroupsController groups] autoAssignRulesForIndex: index];
- [fRuleEditor setObjectValue: predicate];
-
+ NSPredicate *predicate = [[GroupsController groups] autoAssignRulesForIndex: index];
+ [fRuleEditor setObjectValue: predicate];
+
if ([fRuleEditor numberOfRows] == 0)
[fRuleEditor addRow: nil];
-
+
[NSApp beginSheet: fGroupRulesSheetWindow modalForWindow: [fTableView window] modalDelegate: nil didEndSelector: NULL
contextInfo: NULL];
}
{
[fGroupRulesSheetWindow orderOut: nil];
[NSApp endSheet: fGroupRulesSheetWindow];
-
+
NSInteger index = [[GroupsController groups] indexForRow: [fTableView selectedRow]];
if (![[GroupsController groups] autoAssignRulesForIndex: index])
{
{
[fGroupRulesSheetWindow orderOut: nil];
[NSApp endSheet: fGroupRulesSheetWindow];
-
+
NSInteger index = [[GroupsController groups] indexForRow: [fTableView selectedRow]];
[[GroupsController groups] setUsesAutoAssignRules: YES forIndex: index];
-
+
NSPredicate * predicate = [fRuleEditor objectValue];
[[GroupsController groups] setAutoAssignRules: predicate forIndex: index];
-
+
[fAutoAssignRulesEnableCheck setState: [[GroupsController groups] usesAutoAssignRulesForIndex: index]];
[fAutoAssignRulesEditButton setEnabled: [fAutoAssignRulesEnableCheck state] == NSOnState];
}
- (void) ruleEditorRowsDidChange: (NSNotification *) notification
{
NSScrollView * ruleEditorScrollView = [fRuleEditor enclosingScrollView];
-
+
const CGFloat rowHeight = [fRuleEditor rowHeight];
const CGFloat bordersHeight = [ruleEditorScrollView frame].size.height - [ruleEditorScrollView contentSize].height;
const CGFloat requiredRowCount = [fRuleEditor numberOfRows];
const CGFloat maxVisibleRowCount = (long)((NSHeight([[[fRuleEditor window] screen] visibleFrame]) * 2 / 3) / rowHeight);
-
+
[fRuleEditorHeightConstraint setConstant: MIN(requiredRowCount, maxVisibleRowCount) * rowHeight + bordersHeight];
[ruleEditorScrollView setHasVerticalScroller: requiredRowCount > maxVisibleRowCount];
}
[fSelectedColorView setEnabled: YES];
[fSelectedColorNameField setStringValue: [[GroupsController groups] nameForIndex: index]];
[fSelectedColorNameField setEnabled: YES];
-
+
[self refreshCustomLocationWithSingleGroup];
[fAutoAssignRulesEnableCheck setState: [[GroupsController groups] usesAutoAssignRulesForIndex: index]];
- (void) refreshCustomLocationWithSingleGroup
{
const NSInteger index = [[GroupsController groups] indexForRow: [fTableView selectedRow]];
-
+
const BOOL hasCustomLocation = [[GroupsController groups] usesCustomDownloadLocationForIndex: index];
[fCustomLocationEnableCheck setState: hasCustomLocation];
[fCustomLocationEnableCheck setEnabled: YES];
[fCustomLocationPopUp setEnabled: hasCustomLocation];
-
+
NSString * location = [[GroupsController groups] customDownloadLocationForIndex: index];
if (location)
{
@interface InfoActivityViewController : NSViewController <InfoViewController>
{
NSArray * fTorrents;
-
+
BOOL fSet;
-
+
IBOutlet NSTextField * fDateAddedField, * fDateCompletedField, * fDateActivityField,
* fStateField, * fProgressField,
* fHaveField, * fDownloadedTotalField, * fUploadedTotalField, * fFailedHashField,
* fRatioField,
* fDownloadTimeField, * fSeedTimeField;
IBOutlet NSTextView * fErrorMessageView;
-
+
IBOutlet PiecesView * fPiecesView;
IBOutlet NSSegmentedControl * fPiecesControl;
-
+
//remove when we switch to auto layout on 10.7
IBOutlet NSTextField * fTransferSectionLabel, * fDatesSectionLabel, * fTimeSectionLabel;
IBOutlet NSTextField * fStateLabel, * fProgressLabel, * fHaveLabel, * fDownloadedLabel, * fUploadedLabel,
{
[self setTitle: NSLocalizedString(@"Activity", "Inspector view -> title")];
}
-
+
return self;
}
[fTransferSectionLabel sizeToFit];
[fDatesSectionLabel sizeToFit];
[fTimeSectionLabel sizeToFit];
-
+
NSArray * labels = @[ fStateLabel, fProgressLabel, fHaveLabel, fDownloadedLabel, fUploadedLabel, fFailedDLLabel, fRatioLabel, fErrorLabel, fDateAddedLabel, fDateCompletedLabel, fDateActivityLabel, fDownloadTimeLabel, fSeedTimeLabel ];
-
+
CGFloat oldMaxWidth = 0.0, originX, newMaxWidth = 0.0;
for (NSTextField * label in labels)
{
oldMaxWidth = oldFrame.size.width;
originX = oldFrame.origin.x;
}
-
+
[label sizeToFit];
const CGFloat newWidth = [label bounds].size.width;
if (newWidth > newMaxWidth)
newMaxWidth = newWidth;
}
-
+
for (NSTextField * label in labels)
{
NSRect frame = [label frame];
frame.origin.x = originX + (newMaxWidth - frame.size.width);
[label setFrame: frame];
}
-
+
NSArray * fields = @[ fDateAddedField, fDateCompletedField, fDateActivityField, fStateField, fProgressField, fHaveField, fDownloadedTotalField, fUploadedTotalField, fFailedHashField, fRatioField, fDownloadTimeField, fSeedTimeField, fErrorScrollView ];
-
+
const CGFloat widthIncrease = newMaxWidth - oldMaxWidth;
for (NSView * field in fields) {
NSRect frame = [field frame];
frame.size.width -= widthIncrease;
[field setFrame: frame];
}
-
+
//set the click action of the pieces view
#warning after 2.8 just hook this up in the xib
[fPiecesView setAction:@selector(updatePiecesView:)];
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
-
+
[fTorrents release];
-
+
[super dealloc];
}
//don't check if it's the same in case the metadata changed
[fTorrents release];
fTorrents = [torrents retain];
-
+
fSet = NO;
}
{
if (!fSet)
[self setupInfo];
-
+
const NSInteger numberSelected = [fTorrents count];
if (numberSelected == 0)
return;
-
+
uint64_t have = 0, haveVerified = 0, downloadedTotal = 0, uploadedTotal = 0, failedHash = 0;
NSDate * lastActivity = nil;
for (Torrent * torrent in fTorrents)
downloadedTotal += [torrent downloadedTotal];
uploadedTotal += [torrent uploadedTotal];
failedHash += [torrent failedHash];
-
+
NSDate * nextLastActivity;
if ((nextLastActivity = [torrent dateActivity]))
lastActivity = lastActivity ? [lastActivity laterDate: nextLastActivity] : nextLastActivity;
}
-
+
if (have == 0)
[fHaveField setStringValue: [NSString stringForFileSize: 0]];
else
else
[fHaveField setStringValue: [NSString stringWithFormat: @"%@ (%@)", [NSString stringForFileSize: have], verifiedString]];
}
-
+
[fDownloadedTotalField setStringValue: [NSString stringForFileSize: downloadedTotal]];
[fUploadedTotalField setStringValue: [NSString stringForFileSize: uploadedTotal]];
[fFailedHashField setStringValue: [NSString stringForFileSize: failedHash]];
-
+
[fDateActivityField setObjectValue: lastActivity];
-
+
if (numberSelected == 1)
{
Torrent * torrent = [fTorrents objectAtIndex: 0];
-
+
[fStateField setStringValue: [torrent stateString]];
-
+
NSString * progressString = [NSString percentString: [torrent progress] longDecimals: YES];
if ([torrent isFolder])
{
progressString = [progressString stringByAppendingFormat: @" (%@)", progressSelectedString];
}
[fProgressField setStringValue: progressString];
-
+
[fRatioField setStringValue: [NSString stringForRatio: [torrent ratio]]];
-
+
NSString * errorMessage = [torrent errorMessage];
if (![errorMessage isEqualToString: [fErrorMessageView string]])
[fErrorMessageView setString: errorMessage];
-
+
[fDateCompletedField setObjectValue: [torrent dateCompleted]];
-
+
//uses a relative date, so can't be set once
[fDateAddedField setObjectValue: [torrent dateAdded]];
-
+
if ([NSApp isOnYosemiteOrBetter]) {
static NSDateComponentsFormatter *timeFormatter;
static dispatch_once_t onceToken;
timeFormatter.allowedUnits = NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
timeFormatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorDropLeading;
});
-
+
[fDownloadTimeField setStringValue: [timeFormatter stringFromTimeInterval:[torrent secondsDownloading]]];
[fSeedTimeField setStringValue: [timeFormatter stringFromTimeInterval:[torrent secondsSeeding]]];
}
[fDownloadTimeField setStringValue: [NSString timeString: [torrent secondsDownloading] includesTimeRemainingPhrase:NO showSeconds: YES]];
[fSeedTimeField setStringValue: [NSString timeString: [torrent secondsSeeding] includesTimeRemainingPhrase:NO showSeconds: YES]];
}
-
+
[fPiecesView updateView];
}
else if (numberSelected > 1)
- (void) updatePiecesView: (id) sender
{
const BOOL piecesAvailableSegment = [[NSUserDefaults standardUserDefaults] boolForKey: @"PiecesViewShowAvailability"];
-
+
[fPiecesControl setSelected: piecesAvailableSegment forSegment: PIECES_CONTROL_AVAILABLE];
[fPiecesControl setSelected: !piecesAvailableSegment forSegment: PIECES_CONTROL_PROGRESS];
-
+
[fPiecesView updateView];
}
[fDateActivityField setObjectValue: @""]; //using [field setStringValue: @""] causes "December 31, 1969 7:00 PM" to be displayed, at least on 10.7.3
[fRatioField setStringValue: @""];
}
-
+
[fStateField setStringValue: @""];
[fProgressField setStringValue: @""];
-
+
[fErrorMessageView setString: @""];
-
+
//using [field setStringValue: @""] causes "December 31, 1969 7:00 PM" to be displayed, at least on 10.7.3
[fDateAddedField setObjectValue: @""];
[fDateCompletedField setObjectValue: @""];
-
+
[fDownloadTimeField setStringValue: @""];
[fSeedTimeField setStringValue: @""];
-
+
[fPiecesControl setSelected: NO forSegment: PIECES_CONTROL_AVAILABLE];
[fPiecesControl setSelected: NO forSegment: PIECES_CONTROL_PROGRESS];
[fPiecesControl setEnabled: NO];
else
{
Torrent * torrent = [fTorrents objectAtIndex: 0];
-
+
const BOOL piecesAvailableSegment = [[NSUserDefaults standardUserDefaults] boolForKey: @"PiecesViewShowAvailability"];
[fPiecesControl setSelected: piecesAvailableSegment forSegment: PIECES_CONTROL_AVAILABLE];
[fPiecesControl setSelected: !piecesAvailableSegment forSegment: PIECES_CONTROL_PROGRESS];
[fPiecesControl setEnabled: YES];
-
+
[fPiecesView setTorrent: torrent];
}
-
+
fSet = YES;
}
@interface InfoFileViewController : NSViewController <InfoViewController>
{
NSArray * fTorrents;
-
+
BOOL fSet;
-
+
IBOutlet FileOutlineController * fFileController;
-
+
IBOutlet NSSearchField * fFileFilterField;
IBOutlet NSButton * fCheckAllButton, *fUncheckAllButton;
}
{
[self setTitle: NSLocalizedString(@"Files", "Inspector view -> title")];
}
-
+
return self;
}
viewRect.size.height = height;
[[self view] setFrame: viewRect];
}
-
+
[[fFileFilterField cell] setPlaceholderString: NSLocalizedString(@"Filter", "inspector -> file filter")];
-
+
//localize and place all and none buttons
[fCheckAllButton setTitle: NSLocalizedString(@"All", "inspector -> check all")];
[fUncheckAllButton setTitle: NSLocalizedString(@"None", "inspector -> check all")];
-
+
NSRect checkAllFrame = [fCheckAllButton frame];
NSRect uncheckAllFrame = [fUncheckAllButton frame];
const CGFloat oldAllWidth = checkAllFrame.size.width;
const CGFloat oldNoneWidth = uncheckAllFrame.size.width;
-
+
[fCheckAllButton sizeToFit];
[fUncheckAllButton sizeToFit];
const CGFloat newWidth = MAX([fCheckAllButton bounds].size.width, [fUncheckAllButton bounds].size.width);
-
+
const CGFloat uncheckAllChange = newWidth - oldNoneWidth;
uncheckAllFrame.size.width = newWidth;
uncheckAllFrame.origin.x -= uncheckAllChange;
[fUncheckAllButton setFrame: uncheckAllFrame];
-
+
const CGFloat checkAllChange = newWidth - oldAllWidth;
checkAllFrame.size.width = newWidth;
checkAllFrame.origin.x -= (checkAllChange + uncheckAllChange);
- (void) dealloc
{
[fTorrents release];
-
+
[super dealloc];
}
//don't check if it's the same in case the metadata changed
[fTorrents release];
fTorrents = [torrents retain];
-
+
fSet = NO;
}
{
if (!fSet)
[self setupInfo];
-
+
if ([fTorrents count] == 1)
{
[fFileController refresh];
-
+
#warning use TorrentFileCheckChange notification as well
Torrent * torrent = [fTorrents objectAtIndex: 0];
if ([torrent isFolder])
Torrent * torrent = [fTorrents objectAtIndex: 0];
NSIndexSet * indexes = [fileOutlineView selectedRowIndexes];
NSMutableArray * urlArray = [NSMutableArray arrayWithCapacity: [indexes count]];
-
+
for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
{
FileListNode * item = [fileOutlineView itemAtRow: i];
if ([self canQuickLookFile: item])
[urlArray addObject: [NSURL fileURLWithPath: [torrent fileLocation: item]]];
}
-
+
return urlArray;
}
{
if ([fTorrents count] != 1)
return NO;
-
+
Torrent * torrent = [fTorrents objectAtIndex: 0];
if (![torrent isFolder])
return NO;
-
+
FileOutlineView * fileOutlineView = [fFileController outlineView];
NSIndexSet * indexes = [fileOutlineView selectedRowIndexes];
-
+
for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
if ([self canQuickLookFile: [fileOutlineView itemAtRow: i]])
return YES;
-
+
return NO;
}
- (NSRect) quickLookSourceFrameForPreviewItem: (id <QLPreviewItem>) item
{
FileOutlineView * fileOutlineView = [fFileController outlineView];
-
+
NSString * fullPath = [(NSURL *)item path];
Torrent * torrent = [fTorrents objectAtIndex: 0];
NSRange visibleRows = [fileOutlineView rowsInRect: [fileOutlineView bounds]];
-
+
for (NSUInteger row = visibleRows.location; row < NSMaxRange(visibleRows); row++)
{
FileListNode * rowItem = [fileOutlineView itemAtRow: row];
if ([[torrent fileLocation: rowItem] isEqualToString: fullPath])
{
NSRect frame = [fileOutlineView iconRectForRow: row];
-
+
if (!NSIntersectsRect([fileOutlineView visibleRect], frame))
return NSZeroRect;
-
+
frame.origin = [fileOutlineView convertPoint: frame.origin toView: nil];
frame = [[[self view] window] convertRectToScreen: frame];
frame.origin.y -= frame.size.height;
return frame;
}
}
-
+
return NSZeroRect;
}
- (void) setupInfo
{
[fFileFilterField setStringValue: @""];
-
+
if ([fTorrents count] == 1)
{
Torrent * torrent = [fTorrents objectAtIndex: 0];
-
+
[fFileController setTorrent: torrent];
-
+
const BOOL isFolder = [torrent isFolder];
[fFileFilterField setEnabled: isFolder];
-
+
if (!isFolder)
{
[fCheckAllButton setEnabled: NO];
else
{
[fFileController setTorrent: nil];
-
+
[fFileFilterField setEnabled: NO];
-
+
[fCheckAllButton setEnabled: NO];
[fUncheckAllButton setEnabled: NO];
}
-
+
fSet = YES;
}
@interface InfoGeneralViewController : NSViewController <InfoViewController>
{
NSArray * fTorrents;
-
+
BOOL fSet;
-
+
IBOutlet NSTextField * fPiecesField, * fHashField, * fSecureField,
* fDataLocationField,
* fCreatorField, * fDateCreatedField;
-
+
IBOutlet NSTextView * fCommentView;
-
+
IBOutlet NSButton * fRevealDataButton;
-
+
//remove when we switch to auto layout on 10.7
IBOutlet NSTextField * fPiecesLabel, * fHashLabel, * fSecureLabel,
* fCreatorLabel, * fDateCreatedLabel,
{
[self setTitle: NSLocalizedString(@"General Info", "Inspector view -> title")];
}
-
+
return self;
}
- (void) dealloc
{
[fTorrents release];
-
+
[super dealloc];
}
#warning remove when 10.7-only with auto layout
[fInfoSectionLabel sizeToFit];
[fWhereSectionLabel sizeToFit];
-
+
NSArray * labels = @[ fPiecesLabel, fHashLabel, fSecureLabel, fCreatorLabel, fDateCreatedLabel, fCommentLabel, fDataLocationLabel ];
-
+
CGFloat oldMaxWidth = 0.0, originX, newMaxWidth = 0.0;
for (NSTextField * label in labels)
{
oldMaxWidth = oldFrame.size.width;
originX = oldFrame.origin.x;
}
-
+
[label sizeToFit];
const CGFloat newWidth = [label bounds].size.width;
if (newWidth > newMaxWidth)
newMaxWidth = newWidth;
}
-
+
for (NSTextField * label in labels)
{
NSRect frame = [label frame];
frame.origin.x = originX + (newMaxWidth - frame.size.width);
[label setFrame: frame];
}
-
+
NSArray * fields = @[ fPiecesField, fHashField, fSecureField, fCreatorField, fDateCreatedField, fCommentScrollView, fDataLocationField ];
-
+
const CGFloat widthIncrease = newMaxWidth - oldMaxWidth;
for (NSView * field in fields) {
NSRect frame = [field frame];
//don't check if it's the same in case the metadata changed
[fTorrents release];
fTorrents = [torrents retain];
-
+
fSet = NO;
}
{
if (!fSet)
[self setupInfo];
-
+
if ([fTorrents count] != 1)
return;
-
+
Torrent * torrent = [fTorrents objectAtIndex: 0];
-
+
NSString * location = [torrent dataLocation];
[fDataLocationField setStringValue: location ? [location stringByAbbreviatingWithTildeInPath] : @""];
[fDataLocationField setToolTip: location ? location : @""];
-
+
[fRevealDataButton setHidden: !location];
}
NSString * location = [torrent dataLocation];
if (!location)
return;
-
+
NSURL * file = [NSURL fileURLWithPath: location];
[[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: [NSArray arrayWithObject: file]];
}
if ([fTorrents count] == 1)
{
Torrent * torrent = [fTorrents objectAtIndex: 0];
-
+
#warning candidate for localizedStringWithFormat (although then we'll get two commas)
NSString * piecesString = ![torrent isMagnet] ? [NSString stringWithFormat: @"%ld, %@", [torrent pieceCount],
[NSString stringForFileSize: [torrent pieceSize]]] : @"";
[fPiecesField setStringValue: piecesString];
-
+
NSString * hashString = [torrent hashString];
[fHashField setStringValue: hashString];
[fHashField setToolTip: hashString];
[fSecureField setStringValue: [torrent privateTorrent]
? NSLocalizedString(@"Private Torrent, non-tracker peer discovery disabled", "Inspector -> private torrent")
: NSLocalizedString(@"Public Torrent", "Inspector -> private torrent")];
-
+
NSString * commentString = [torrent comment];
[fCommentView setString: commentString];
-
+
NSString * creatorString = [torrent creator];
[fCreatorField setStringValue: creatorString];
[fDateCreatedField setObjectValue: [torrent dateCreated]];
[fHashField setToolTip: nil];
[fSecureField setStringValue: @""];
[fCommentView setString: @""];
-
+
[fCreatorField setStringValue: @""];
[fDateCreatedField setStringValue: @""];
-
+
[fDataLocationField setStringValue: @""];
[fDataLocationField setToolTip: nil];
-
+
[fRevealDataButton setHidden: YES];
}
-
+
fSet = YES;
}
@interface InfoOptionsViewController : NSViewController <InfoViewController>
{
NSArray * fTorrents;
-
+
BOOL fSet;
-
+
IBOutlet NSPopUpButton * fPriorityPopUp, * fRatioPopUp, * fIdlePopUp;
IBOutlet NSButton * fUploadLimitCheck, * fDownloadLimitCheck, * fGlobalLimitCheck, * fRemoveSeedingCompleteCheck;
IBOutlet NSTextField * fUploadLimitField, * fDownloadLimitField, * fRatioLimitField, * fIdleLimitField,
* fUploadLimitLabel, * fDownloadLimitLabel, * fIdleLimitLabel,
* fRatioLimitGlobalLabel, * fIdleLimitGlobalLabel,
* fPeersConnectLabel, * fPeersConnectField;
-
+
//remove when we switch to auto layout on 10.7
IBOutlet NSTextField * fTransferBandwidthSectionLabel, * fPrioritySectionLabel, * fPriorityLabel;
IBOutlet NSTextField * fSeedingLimitsSectionLabel, * fRatioLabel, * fInactivityLabel;
IBOutlet NSTextField * fAdvancedSectionLabel, * fMaxConnectionsLabel;
-
+
NSString * fInitialString;
}
{
[self setTitle: NSLocalizedString(@"Options", "Inspector view -> title")];
}
-
+
return self;
}
- (void) awakeFromNib
{
[self setGlobalLabels];
-
+
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(setGlobalLabels) name: @"UpdateGlobalOptions" object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateOptionsNotification:) name: @"UpdateOptionsNotification" object: nil];
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
-
+
[fTorrents release];
-
+
[super dealloc];
}
//don't check if it's the same in case the metadata changed
[fTorrents release];
fTorrents = [torrents retain];
-
+
fSet = NO;
}
{
if (!fSet)
[self setupInfo];
-
+
fSet = YES;
}
{
if ([fTorrents count] == 0)
return;
-
+
//get bandwidth info
NSEnumerator * enumerator = [fTorrents objectEnumerator];
Torrent * torrent = [enumerator nextObject]; //first torrent
-
+
NSInteger uploadUseSpeedLimit = [torrent usesSpeedLimit: YES] ? NSOnState : NSOffState,
uploadSpeedLimit = [torrent speedLimit: YES],
downloadUseSpeedLimit = [torrent usesSpeedLimit: NO] ? NSOnState : NSOffState,
downloadSpeedLimit = [torrent speedLimit: NO],
globalUseSpeedLimit = [torrent usesGlobalSpeedLimit] ? NSOnState : NSOffState;
-
+
while ((torrent = [enumerator nextObject])
&& (uploadUseSpeedLimit != NSMixedState || uploadSpeedLimit != INVALID
|| downloadUseSpeedLimit != NSMixedState || downloadSpeedLimit != INVALID
{
if (uploadUseSpeedLimit != NSMixedState && uploadUseSpeedLimit != ([torrent usesSpeedLimit: YES] ? NSOnState : NSOffState))
uploadUseSpeedLimit = NSMixedState;
-
+
if (uploadSpeedLimit != INVALID && uploadSpeedLimit != [torrent speedLimit: YES])
uploadSpeedLimit = INVALID;
-
+
if (downloadUseSpeedLimit != NSMixedState && downloadUseSpeedLimit != ([torrent usesSpeedLimit: NO] ? NSOnState : NSOffState))
downloadUseSpeedLimit = NSMixedState;
-
+
if (downloadSpeedLimit != INVALID && downloadSpeedLimit != [torrent speedLimit: NO])
downloadSpeedLimit = INVALID;
-
+
if (globalUseSpeedLimit != NSMixedState && globalUseSpeedLimit != ([torrent usesGlobalSpeedLimit] ? NSOnState : NSOffState))
globalUseSpeedLimit = NSMixedState;
}
-
+
//set upload view
[fUploadLimitCheck setState: uploadUseSpeedLimit];
[fUploadLimitCheck setEnabled: YES];
-
+
[fUploadLimitLabel setEnabled: uploadUseSpeedLimit == NSOnState];
[fUploadLimitField setEnabled: uploadUseSpeedLimit == NSOnState];
if (uploadSpeedLimit != INVALID)
[fUploadLimitField setIntValue: uploadSpeedLimit];
else
[fUploadLimitField setStringValue: @""];
-
+
//set download view
[fDownloadLimitCheck setState: downloadUseSpeedLimit];
[fDownloadLimitCheck setEnabled: YES];
-
+
[fDownloadLimitLabel setEnabled: downloadUseSpeedLimit == NSOnState];
[fDownloadLimitField setEnabled: downloadUseSpeedLimit == NSOnState];
if (downloadSpeedLimit != INVALID)
[fDownloadLimitField setIntValue: downloadSpeedLimit];
else
[fDownloadLimitField setStringValue: @""];
-
+
//set global check
[fGlobalLimitCheck setState: globalUseSpeedLimit];
[fGlobalLimitCheck setEnabled: YES];
-
+
//get ratio and idle info
enumerator = [fTorrents objectEnumerator];
torrent = [enumerator nextObject]; //first torrent
-
+
NSInteger checkRatio = [torrent ratioSetting], checkIdle = [torrent idleSetting],
removeWhenFinishSeeding = [torrent removeWhenFinishSeeding] ? NSOnState : NSOffState;
CGFloat ratioLimit = [torrent ratioLimit];
NSUInteger idleLimit = [torrent idleLimitMinutes];
-
+
while ((torrent = [enumerator nextObject])
&& (checkRatio != INVALID || ratioLimit != INVALID || checkIdle != INVALID || idleLimit != INVALID))
{
if (checkRatio != INVALID && checkRatio != [torrent ratioSetting])
checkRatio = INVALID;
-
+
if (ratioLimit != INVALID && ratioLimit != [torrent ratioLimit])
ratioLimit = INVALID;
-
+
if (checkIdle != INVALID && checkIdle != [torrent idleSetting])
checkIdle = INVALID;
-
+
if (idleLimit != INVALID && idleLimit != [torrent idleLimitMinutes])
idleLimit = INVALID;
-
+
if (removeWhenFinishSeeding != NSMixedState && removeWhenFinishSeeding != ([torrent removeWhenFinishSeeding] ? NSOnState : NSOffState))
removeWhenFinishSeeding = NSMixedState;
}
-
+
//set ratio view
NSInteger index;
if (checkRatio == TR_RATIOLIMIT_SINGLE)
index = -1;
[fRatioPopUp selectItemAtIndex: index];
[fRatioPopUp setEnabled: YES];
-
+
[fRatioLimitField setHidden: checkRatio != TR_RATIOLIMIT_SINGLE];
if (ratioLimit != INVALID)
[fRatioLimitField setFloatValue: ratioLimit];
else
[fRatioLimitField setStringValue: @""];
-
+
[fRatioLimitGlobalLabel setHidden: checkRatio != TR_RATIOLIMIT_GLOBAL];
-
+
//set idle view
if (checkIdle == TR_IDLELIMIT_SINGLE)
index = OPTION_POPUP_LIMIT;
index = -1;
[fIdlePopUp selectItemAtIndex: index];
[fIdlePopUp setEnabled: YES];
-
+
[fIdleLimitField setHidden: checkIdle != TR_IDLELIMIT_SINGLE];
if (idleLimit != INVALID)
[fIdleLimitField setIntegerValue: idleLimit];
else
[fIdleLimitField setStringValue: @""];
[fIdleLimitLabel setHidden: checkIdle != TR_IDLELIMIT_SINGLE];
-
+
[fIdleLimitGlobalLabel setHidden: checkIdle != TR_IDLELIMIT_GLOBAL];
-
+
//set remove transfer when seeding finishes
[fRemoveSeedingCompleteCheck setState: removeWhenFinishSeeding];
[fRemoveSeedingCompleteCheck setEnabled: YES];
-
+
//get priority info
enumerator = [fTorrents objectEnumerator];
torrent = [enumerator nextObject]; //first torrent
-
+
NSInteger priority = [torrent priority];
-
+
while ((torrent = [enumerator nextObject]) && priority != INVALID)
{
if (priority != INVALID && priority != [torrent priority])
priority = INVALID;
}
-
+
//set priority view
if (priority == TR_PRI_HIGH)
index = OPTION_POPUP_PRIORITY_HIGH;
index = -1;
[fPriorityPopUp selectItemAtIndex: index];
[fPriorityPopUp setEnabled: YES];
-
+
//get peer info
enumerator = [fTorrents objectEnumerator];
torrent = [enumerator nextObject]; //first torrent
-
+
NSInteger maxPeers = [torrent maxPeerConnect];
-
+
while ((torrent = [enumerator nextObject]))
{
if (maxPeers != [torrent maxPeerConnect])
break;
}
}
-
+
//set peer view
[fPeersConnectField setEnabled: YES];
[fPeersConnectLabel setEnabled: YES];
- (void) setUseSpeedLimit: (id) sender
{
const BOOL upload = sender == fUploadLimitCheck;
-
+
if ([(NSButton *)sender state] == NSMixedState)
[sender setState: NSOnState];
const BOOL limit = [(NSButton *)sender state] == NSOnState;
-
+
for (Torrent * torrent in fTorrents)
[torrent setUseSpeedLimit: limit upload: upload];
-
+
NSTextField * field = upload ? fUploadLimitField : fDownloadLimitField;
[field setEnabled: limit];
if (limit)
[field selectText: self];
[[[self view] window] makeKeyAndOrderFront: self];
}
-
+
NSTextField * label = upload ? fUploadLimitLabel : fDownloadLimitLabel;
[label setEnabled: limit];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
}
if ([(NSButton *)sender state] == NSMixedState)
[sender setState: NSOnState];
const BOOL limit = [(NSButton *)sender state] == NSOnState;
-
+
for (Torrent * torrent in fTorrents)
[torrent setUseGlobalSpeedLimit: limit];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
}
{
const BOOL upload = sender == fUploadLimitField;
const NSInteger limit = [sender intValue];
-
+
for (Torrent * torrent in fTorrents)
[torrent setSpeedLimit: limit upload: upload];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
}
NSAssert1(NO, @"Unknown option selected in ratio popup: %ld", [sender indexOfSelectedItem]);
return;
}
-
+
for (Torrent * torrent in fTorrents)
[torrent setRatioSetting: setting];
-
+
[fRatioLimitField setHidden: !single];
if (single)
{
[fRatioLimitField selectText: self];
[[[self view] window] makeKeyAndOrderFront: self];
}
-
+
[fRatioLimitGlobalLabel setHidden: setting != TR_RATIOLIMIT_GLOBAL];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
}
- (void) setRatioLimit: (id) sender
{
const CGFloat limit = [sender floatValue];
-
+
for (Torrent * torrent in fTorrents)
[torrent setRatioLimit: limit];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
}
NSAssert1(NO, @"Unknown option selected in idle popup: %ld", [sender indexOfSelectedItem]);
return;
}
-
+
for (Torrent * torrent in fTorrents)
[torrent setIdleSetting: setting];
-
+
[fIdleLimitField setHidden: !single];
[fIdleLimitLabel setHidden: !single];
if (single)
[fIdleLimitField selectText: self];
[[[self view] window] makeKeyAndOrderFront: self];
}
-
+
[fIdleLimitGlobalLabel setHidden: setting != TR_IDLELIMIT_GLOBAL];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
}
- (void) setIdleLimit: (id) sender
{
const NSUInteger limit = [sender integerValue];
-
+
for (Torrent * torrent in fTorrents)
[torrent setIdleLimitMinutes: limit];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
}
if ([(NSButton *)sender state] == NSMixedState)
[sender setState: NSOnState];
const BOOL enable = [(NSButton *)sender state] == NSOnState;
-
+
for (Torrent * torrent in fTorrents)
[torrent setRemoveWhenFinishSeeding: enable];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
}
NSAssert1(NO, @"Unknown option selected in priority popup: %ld", [sender indexOfSelectedItem]);
return;
}
-
+
for (Torrent * torrent in fTorrents)
[torrent setPriority: priority];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
}
- (void) setPeersConnectLimit: (id) sender
{
NSInteger limit = [sender intValue];
-
+
for (Torrent * torrent in fTorrents)
[torrent setMaxPeerConnect: limit];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptionsNotification" object: self];
}
{
[fInitialString release];
fInitialString = [[control stringValue] retain];
-
+
return YES;
}
[fUploadLimitField setEnabled: NO];
[fUploadLimitLabel setEnabled: NO];
[fUploadLimitField setStringValue: @""];
-
+
[fDownloadLimitCheck setEnabled: NO];
[fDownloadLimitCheck setState: NSOffState];
[fDownloadLimitField setEnabled: NO];
[fDownloadLimitLabel setEnabled: NO];
[fDownloadLimitField setStringValue: @""];
-
+
[fGlobalLimitCheck setEnabled: NO];
[fGlobalLimitCheck setState: NSOffState];
-
+
[fPriorityPopUp setEnabled: NO];
[fPriorityPopUp selectItemAtIndex: -1];
-
+
[fRatioPopUp setEnabled: NO];
[fRatioPopUp selectItemAtIndex: -1];
[fRatioLimitField setHidden: YES];
[fRatioLimitField setStringValue: @""];
[fRatioLimitGlobalLabel setHidden: YES];
-
+
[fIdlePopUp setEnabled: NO];
[fIdlePopUp selectItemAtIndex: -1];
[fIdleLimitField setHidden: YES];
[fIdleLimitField setStringValue: @""];
[fIdleLimitLabel setHidden: YES];
[fIdleLimitGlobalLabel setHidden: YES];
-
+
[fRemoveSeedingCompleteCheck setEnabled: NO];
[fRemoveSeedingCompleteCheck setState: NSOffState];
-
+
[fPeersConnectField setEnabled: NO];
[fPeersConnectField setStringValue: @""];
[fPeersConnectLabel setEnabled: NO];
? [NSString stringForRatio: [[NSUserDefaults standardUserDefaults] floatForKey: @"RatioLimit"]]
: NSLocalizedString(@"disabled", "Info options -> global setting");
[fRatioLimitGlobalLabel setStringValue: global];
-
+
//idle field
NSString * globalIdle;
if ([[NSUserDefaults standardUserDefaults] boolForKey: @"IdleLimitCheck"])
@interface InfoPeersViewController : NSViewController <InfoViewController, NSAnimationDelegate>
{
NSArray * fTorrents;
-
+
BOOL fSet;
-
+
NSMutableArray * fPeers, * fWebSeeds;
-
+
IBOutlet NSTableView * fPeerTable;
IBOutlet WebSeedTableView * fWebSeedTable;
-
+
IBOutlet NSTextField * fConnectedPeersField;
CGFloat fViewTopMargin;
{
[self setTitle: NSLocalizedString(@"Peers", "Inspector view -> title")];
}
-
+
return self;
}
viewRect.size.height = height;
[[self view] setFrame: viewRect];
}
-
+
//set table header text
[[[fPeerTable tableColumnWithIdentifier: @"IP"] headerCell] setStringValue: NSLocalizedString(@"IP Address",
"inspector -> peer table -> header")];
"inspector -> peer table -> header")];
[[[fPeerTable tableColumnWithIdentifier: @"UL To"] headerCell] setStringValue: NSLocalizedString(@"UL",
"inspector -> peer table -> header")];
-
+
[[[fWebSeedTable tableColumnWithIdentifier: @"Address"] headerCell] setStringValue: NSLocalizedString(@"Web Seeds",
"inspector -> web seed table -> header")];
[[[fWebSeedTable tableColumnWithIdentifier: @"DL From"] headerCell] setStringValue: NSLocalizedString(@"DL",
"inspector -> web seed table -> header")];
-
+
//set table header tool tips
[[fPeerTable tableColumnWithIdentifier: @"Encryption"] setHeaderToolTip: NSLocalizedString(@"Encrypted Connection",
"inspector -> peer table -> header tool tip")];
"inspector -> peer table -> header tool tip")];
[[fPeerTable tableColumnWithIdentifier: @"UL To"] setHeaderToolTip: NSLocalizedString(@"Uploading To Peer",
"inspector -> peer table -> header tool tip")];
-
+
[[fWebSeedTable tableColumnWithIdentifier: @"DL From"] setHeaderToolTip: NSLocalizedString(@"Downloading From Web Seed",
"inspector -> web seed table -> header tool tip")];
-
+
//prepare for animating peer table and web seed table
fViewTopMargin = fWebSeedTableTopConstraint.constant;
-
+
CABasicAnimation * webSeedTableAnimation = [CABasicAnimation animation];
[webSeedTableAnimation setTimingFunction: [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionLinear]];
[webSeedTableAnimation setDuration: 0.125];
[webSeedTableAnimation setDelegate: self];
[webSeedTableAnimation setValue: WEB_SEED_ANIMATION_ID forKey: ANIMATION_ID_KEY];
[fWebSeedTableTopConstraint setAnimations: @{ @"constant": webSeedTableAnimation }];
-
+
[self setWebSeedTableHidden: YES animate: NO];
}
- (void) dealloc
{
[fTorrents release];
-
+
[fPeers release];
[fWebSeeds release];
-
+
[super dealloc];
}
//don't check if it's the same in case the metadata changed
[fTorrents release];
fTorrents = [torrents retain];
-
+
fSet = NO;
}
{
if (!fSet)
[self setupInfo];
-
+
if ([fTorrents count] == 0)
return;
-
+
if (!fPeers)
fPeers = [[NSMutableArray alloc] init];
else
[fPeers removeAllObjects];
-
+
if (!fWebSeeds)
fWebSeeds = [[NSMutableArray alloc] init];
else
[fWebSeeds removeAllObjects];
-
+
NSUInteger connected = 0, tracker = 0, incoming = 0, cache = 0, lpd = 0, pex = 0, dht = 0, ltep = 0,
toUs = 0, fromUs = 0;
BOOL anyActive = false;
{
if ([torrent webSeedCount] > 0)
[fWebSeeds addObjectsFromArray: [torrent webSeeds]];
-
+
if ([torrent isActive])
{
anyActive = YES;
[fPeers addObjectsFromArray: [torrent peers]];
-
+
const NSUInteger connectedThis = [torrent totalPeersConnected];
if (connectedThis > 0)
{
pex += [torrent totalPeersPex];
dht += [torrent totalPeersDHT];
ltep += [torrent totalPeersLTEP];
-
+
toUs += [torrent peersSendingToUs];
fromUs += [torrent peersGettingFromUs];
}
}
}
-
+
[fPeers sortUsingDescriptors: [self peerSortDescriptors]];
[fPeerTable reloadData];
-
+
[fWebSeeds sortUsingDescriptors: [fWebSeedTable sortDescriptors]];
[fWebSeedTable reloadData];
[fWebSeedTable setWebSeeds: fWebSeeds];
-
+
if (anyActive)
{
NSString * connectedText = [NSString stringWithFormat: NSLocalizedString(@"%d Connected", "Inspector -> Peers tab -> peers"),
connected];
-
+
if (connected > 0)
{
NSMutableArray * upDownComponents = [NSMutableArray arrayWithCapacity: 2];
NSLocalizedString(@"UL to %d", "Inspector -> Peers tab -> peers"), fromUs]];
if ([upDownComponents count] > 0)
connectedText = [connectedText stringByAppendingFormat: @": %@", [upDownComponents componentsJoinedByString: @", "]];
-
+
NSMutableArray * fromComponents = [NSMutableArray arrayWithCapacity: 7];
if (tracker > 0)
[fromComponents addObject: [NSString stringWithFormat:
if (ltep > 0)
[fromComponents addObject: [NSString stringWithFormat:
NSLocalizedString(@"%d LTEP", "Inspector -> Peers tab -> peers"), ltep]];
-
+
connectedText = [connectedText stringByAppendingFormat: @"\n%@", [fromComponents componentsJoinedByString: @", "]];
}
-
+
[fConnectedPeersField setStringValue: connectedText];
}
else
notActiveString = NSLocalizedString(@"Transfer Not Active", "Inspector -> Peers tab -> peers");
else
notActiveString = NSLocalizedString(@"Transfers Not Active", "Inspector -> Peers tab -> peers");
-
+
[fConnectedPeersField setStringValue: notActiveString];
}
}
{
NSString * ident = [column identifier];
NSDictionary * webSeed = [fWebSeeds objectAtIndex: row];
-
+
if ([ident isEqualToString: @"DL From"])
{
NSNumber * rate;
{
NSString * ident = [column identifier];
NSDictionary * peer = [fPeers objectAtIndex: row];
-
+
if ([ident isEqualToString: @"Encryption"])
return [[peer objectForKey: @"Encryption"] boolValue] ? [NSImage imageNamed: @"Lock"] : nil;
else if ([ident isEqualToString: @"Client"])
if (tableView == fPeerTable)
{
NSString * ident = [tableColumn identifier];
-
+
if ([ident isEqualToString: @"Progress"])
{
NSDictionary * peer = [fPeers objectAtIndex: row];
if (tableView == fPeerTable)
{
const BOOL multiple = [fTorrents count] > 1;
-
+
NSDictionary * peer = [fPeers objectAtIndex: row];
NSMutableArray * components = [NSMutableArray arrayWithCapacity: multiple ? 6 : 5];
-
+
if (multiple)
[components addObject: [peer objectForKey: @"Name"]];
-
+
const CGFloat progress = [[peer objectForKey: @"Progress"] floatValue];
NSString * progressString = [NSString stringWithFormat: NSLocalizedString(@"Progress: %@",
"Inspector -> Peers tab -> table row tooltip"),
progressString = [progressString stringByAppendingFormat: @" (%@)", NSLocalizedString(@"Partial Seed",
"Inspector -> Peers tab -> table row tooltip")];
[components addObject: progressString];
-
+
NSString * protocolString = [[peer objectForKey: @"uTP"] boolValue] ? @"\u00b5TP" : @"TCP";
if ([[peer objectForKey: @"Encryption"] boolValue])
protocolString = [protocolString stringByAppendingFormat: @" (%@)",
[components addObject: [NSString stringWithFormat:
NSLocalizedString(@"Protocol: %@", "Inspector -> Peers tab -> table row tooltip"),
protocolString]];
-
+
NSString * portString;
NSInteger port;
if ((port = [[peer objectForKey: @"Port"] intValue]) > 0)
portString = NSLocalizedString(@"N/A", "Inspector -> Peers tab -> table row tooltip");
[components addObject: [NSString stringWithFormat: @"%@: %@", NSLocalizedString(@"Port",
"Inspector -> Peers tab -> table row tooltip"), portString]];
-
+
const NSInteger peerFrom = [[peer objectForKey: @"From"] integerValue];
switch (peerFrom)
{
default:
NSAssert1(NO, @"Peer from unknown source: %ld", peerFrom);
}
-
+
//determing status strings from flags
NSMutableArray * statusArray = [NSMutableArray arrayWithCapacity: 6];
NSString * flags = [peer objectForKey: @"Flags"];
-
+
if ([flags rangeOfString: @"D"].location != NSNotFound)
[statusArray addObject: NSLocalizedString(@"Currently downloading (interested and not choked)",
"Inspector -> peer -> status")];
if ([flags rangeOfString: @"?"].location != NSNotFound)
[statusArray addObject: NSLocalizedString(@"You unchoked the peer, but the peer is not interested",
"Inspector -> peer -> status")];
-
+
if ([statusArray count] > 0)
{
NSString * statusStrings = [statusArray componentsJoinedByString: @"\n\n"];
[components addObject: [@"\n" stringByAppendingString: statusStrings]];
}
-
+
return [components componentsJoinedByString: @"\n"];
}
else
if ([fTorrents count] > 1)
return [[fWebSeeds objectAtIndex: row] objectForKey: @"Name"];
}
-
+
return nil;
}
- (void) setupInfo
{
__block BOOL hasWebSeeds = NO;
-
+
if ([fTorrents count] == 0)
{
[fPeers release];
fPeers = nil;
[fPeerTable reloadData];
-
+
[fConnectedPeersField setStringValue: @""];
}
else
}
}];
}
-
+
if (!hasWebSeeds)
{
[fWebSeeds release];
else
[fWebSeedTable deselectAll: self];
[self setWebSeedTableHidden: !hasWebSeeds animate: YES];
-
+
fSet = YES;
}
animate = NO;
const CGFloat webSeedTableTopMargin = hide ? -NSHeight([[fWebSeedTable enclosingScrollView] frame]) : fViewTopMargin;
-
+
[(animate ? [fWebSeedTableTopConstraint animator] : fWebSeedTableTopConstraint) setConstant: webSeedTableTopMargin];
}
- (NSArray *) peerSortDescriptors
{
NSMutableArray * descriptors = [NSMutableArray arrayWithCapacity: 2];
-
+
NSArray * oldDescriptors = [fPeerTable sortDescriptors];
BOOL useSecond = YES, asc = YES;
if ([oldDescriptors count] > 0)
{
NSSortDescriptor * descriptor = [oldDescriptors objectAtIndex: 0];
[descriptors addObject: descriptor];
-
+
if ((useSecond = ![[descriptor key] isEqualToString: @"IP"]))
asc = [descriptor ascending];
}
-
+
//sort by IP after primary sort
if (useSecond)
{
NSSortDescriptor * secondDescriptor = [NSSortDescriptor sortDescriptorWithKey: @"IP" ascending: asc selector: @selector(compareNumeric:)];
[descriptors addObject: secondDescriptor];
}
-
+
return descriptors;
}
NSInteger count = 0;
NSRect gridRects[2];
NSColor * colorRects[2];
-
+
NSRect lineBorderRect = NSMakeRect(NSMinX(rect), NSHeight([self bounds]) - 1.0, NSWidth(rect), 1.0);
if (NSIntersectsRect(lineBorderRect, rect))
{
gridRects[count] = lineBorderRect;
colorRects[count] = [NSColor grayColor];
++count;
-
+
rect.size.height -= 1.0;
}
-
+
lineBorderRect.origin.y = 0.0;
if (NSIntersectsRect(lineBorderRect, rect))
{
gridRects[count] = lineBorderRect;
colorRects[count] = [NSColor grayColor];
++count;
-
+
rect.origin.y += 1.0;
rect.size.height -= 1.0;
}
-
+
NSRectFillListWithColors(gridRects, colorRects, count);
-
+
[fGradient drawInRect: rect angle: 270.0];
}
@interface InfoTabButtonCell : NSButtonCell
{
NSImage * fIcon;
-
+
BOOL fSelected;
}
NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
[nc addObserver: self selector: @selector(updateControlTint:)
name: NSControlTintDidChangeNotification object: NSApp];
-
+
fSelected = NO;
-
+
//expects the icon to currently be set as the image
fIcon = [[self image] retain];
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
-
+
[fIcon release];
[super dealloc];
}
- (void) setSelectedTab: (BOOL) selected
{
fSelected = selected;
-
+
if ([self controlView] == nil)
return;
NSRect tabRect = [(NSMatrix *)[self controlView] cellFrameAtRow: row column: col];
tabRect.origin.x = 0.0;
tabRect.origin.y = 0.0;
-
+
NSImage * tabImage = [[NSImage alloc] initWithSize: tabRect.size];
-
+
[tabImage lockFocus];
-
+
NSGradient * gradient;
if (fSelected)
{
NSColor * darkColor = [NSColor colorWithCalibratedRed: 215.0/255.0 green: 215.0/255.0 blue: 215.0/255.0 alpha: 1.0];
gradient = [[NSGradient alloc] initWithStartingColor: lightColor endingColor: darkColor];
}
-
+
[[NSColor grayColor] set];
NSRectFill(NSMakeRect(0.0, 0.0, NSWidth(tabRect), 1.0));
NSRectFill(NSMakeRect(0.0, NSHeight(tabRect) - 1.0, NSWidth(tabRect), 1.0));
NSRectFill(NSMakeRect(NSWidth(tabRect) - 1.0, 1.0, NSWidth(tabRect) - 1.0, NSHeight(tabRect) - 2.0));
-
+
tabRect = NSMakeRect(0.0, 1.0, NSWidth(tabRect) - 1.0, NSHeight(tabRect) - 2.0);
-
+
[gradient drawInRect: tabRect angle: 270.0];
[gradient release];
-
+
if (fIcon)
{
const NSSize iconSize = [fIcon size];
-
+
const NSRect iconRect = NSMakeRect(NSMinX(tabRect) + floor((NSWidth(tabRect) - iconSize.width) * 0.5),
NSMinY(tabRect) + floor((NSHeight(tabRect) - iconSize.height) * 0.5),
iconSize.width, iconSize.height);
-
+
[fIcon drawInRect: iconRect fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0];
}
-
+
[tabImage unlockFocus];
-
+
[self setImage: tabImage];
[tabImage release];
}
- (void) setStringValue: (NSString *) string
{
[super setStringValue: string];
-
+
[self setSelectable: ![[self stringValue] isEqualToString: @""]];
}
- (void) setObjectValue: (id <NSCopying>) object
{
[super setObjectValue: object];
-
+
[self setSelectable: ![[self stringValue] isEqualToString: @""]];
}
@interface InfoTrackersViewController : NSViewController <InfoViewController>
{
NSArray * fTorrents;
-
+
BOOL fSet;
-
+
NSMutableArray * fTrackers;
-
+
IBOutlet TrackerTableView * fTrackerTable;
TrackerCell * fTrackerCell;
-
+
IBOutlet NSSegmentedControl * fTrackerAddRemoveControl;
}
if ((self = [super initWithNibName: @"InfoTrackersView" bundle: nil]))
{
[self setTitle: NSLocalizedString(@"Trackers", "Inspector view -> title")];
-
+
fTrackerCell = [[TrackerCell alloc] init];
}
-
+
return self;
}
forSegment: TRACKER_ADD_TAG];
[[fTrackerAddRemoveControl cell] setToolTip: NSLocalizedString(@"Remove selected trackers", "Inspector view -> tracker buttons")
forSegment: TRACKER_REMOVE_TAG];
-
+
const CGFloat height = [[NSUserDefaults standardUserDefaults] floatForKey: @"InspectorContentHeightTracker"];
if (height != 0.0)
{
[fTorrents release];
[fTrackers release];
[fTrackerCell release];
-
+
[super dealloc];
}
//don't check if it's the same in case the metadata changed
[fTorrents release];
fTorrents = [torrents retain];
-
+
fSet = NO;
}
{
if (!fSet)
[self setupInfo];
-
+
if ([fTorrents count] == 0)
return;
-
+
//get updated tracker stats
if ([fTrackerTable editedRow] == -1)
{
NSArray * oldTrackers = fTrackers;
-
+
if ([fTorrents count] == 1)
fTrackers = [[[fTorrents objectAtIndex: 0] allTrackerStats] retain];
else
for (Torrent * torrent in fTorrents)
[fTrackers addObjectsFromArray: [torrent allTrackerStats]];
}
-
+
[fTrackerTable setTrackers: fTrackers];
-
+
if (oldTrackers && [fTrackers isEqualToArray: oldTrackers])
[fTrackerTable setNeedsDisplay: YES];
else
[fTrackerTable reloadData];
-
+
[oldTrackers release];
}
else
{
NSAssert1([fTorrents count] == 1, @"Attempting to add tracker with %ld transfers selected", [fTorrents count]);
-
+
NSIndexSet * addedIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange([fTrackers count]-2, 2)];
NSArray * tierAndTrackerBeingAdded = [fTrackers objectsAtIndexes: addedIndexes];
-
+
[fTrackers release];
fTrackers = [[[fTorrents objectAtIndex: 0] allTrackerStats] retain];
[fTrackers addObjectsFromArray: tierAndTrackerBeingAdded];
-
+
[fTrackerTable setTrackers: fTrackers];
-
+
NSIndexSet * updateIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTrackers count]-2)],
* columnIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [[fTrackerTable tableColumns] count])];
[fTrackerTable reloadDataForRowIndexes: updateIndexes columnIndexes: columnIndexes];
- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) column row: (NSInteger) row
{
- id item = [fTrackers objectAtIndex: row];
-
+ id item = [fTrackers objectAtIndex: row];
+
if ([item isKindOfClass: [NSDictionary class]])
{
const NSInteger tier = [[item objectForKey: @"Tier"] integerValue];
NSString * tierString = tier == -1 ? NSLocalizedString(@"New Tier", "Inspector -> tracker table")
: [NSString stringWithFormat: NSLocalizedString(@"Tier %d", "Inspector -> tracker table"), tier];
-
+
if ([fTorrents count] > 1)
tierString = [tierString stringByAppendingFormat: @" - %@", [item objectForKey: @"Name"]];
return tierString;
row: (NSInteger) row
{
Torrent * torrent= [fTorrents objectAtIndex: 0];
-
+
BOOL added = NO;
for (NSString * tracker in [object componentsSeparatedByString: @"\n"])
if ([torrent addTrackerToNewTier: tracker])
added = YES;
-
+
if (!added)
NSBeep();
-
+
//reset table with either new or old value
[fTrackers release];
fTrackers = [[torrent allTrackerStats] retain];
-
+
[fTrackerTable setTrackers: fTrackers];
[fTrackerTable reloadData];
[fTrackerTable deselectAll: self];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil]; //incase sort by tracker
}
//don't allow add/remove when currently adding - it leads to weird results
if ([fTrackerTable editedRow] != -1)
return;
-
+
[self updateInfo];
-
+
if ([[sender cell] tagForSegment: [sender selectedSegment]] == TRACKER_REMOVE_TAG)
[self removeTrackers];
else
{
[fTrackers release];
fTrackers = nil;
-
+
[fTrackerTable setTrackers: nil];
[fTrackerTable reloadData];
}
-
+
[fTrackerTable setTorrent: nil];
-
+
[fTrackerAddRemoveControl setEnabled: NO forSegment: TRACKER_ADD_TAG];
[fTrackerAddRemoveControl setEnabled: NO forSegment: TRACKER_REMOVE_TAG];
}
else
{
[fTrackerTable setTorrent: [fTorrents objectAtIndex: 0]];
-
+
[fTrackerAddRemoveControl setEnabled: YES forSegment: TRACKER_ADD_TAG];
[fTrackerAddRemoveControl setEnabled: NO forSegment: TRACKER_REMOVE_TAG];
}
-
+
[fTrackerTable deselectAll: self];
-
+
fSet = YES;
}
- (void) addTrackers
{
[[[self view] window] makeKeyWindow];
-
+
NSAssert1([fTorrents count] == 1, @"Attempting to add tracker with %ld transfers selected", [fTorrents count]);
-
+
[fTrackers addObject: [NSDictionary dictionaryWithObject: [NSNumber numberWithInteger: -1] forKey: @"Tier"]];
[fTrackers addObject: @""];
-
+
[fTrackerTable setTrackers: fTrackers];
[fTrackerTable reloadData];
[fTrackerTable selectRowIndexes: [NSIndexSet indexSetWithIndex: [fTrackers count]-1] byExtendingSelection: NO];
{
NSMutableDictionary * removeIdentifiers = [NSMutableDictionary dictionaryWithCapacity: [fTorrents count]];
NSUInteger removeTrackerCount = 0;
-
+
NSIndexSet * selectedIndexes = [fTrackerTable selectedRowIndexes];
BOOL groupSelected = NO;
NSUInteger groupRowIndex = NSNotFound;
removeSet = [NSMutableSet set];
[removeIdentifiers setObject: removeSet forKey: torrent];
}
-
+
[removeSet addObject: [(TrackerNode *)object fullAnnounceAddress]];
++removeTrackerCount;
-
+
[removeIndexes addIndex: i];
}
else
//mark the previous group row for removal, if necessary
if (groupRowIndex != NSNotFound)
[removeIndexes addIndex: groupRowIndex];
-
+
groupSelected = [selectedIndexes containsIndex: i];
if (!groupSelected && i > [selectedIndexes lastIndex])
{
groupRowIndex = NSNotFound;
break;
}
-
+
groupRowIndex = i;
}
}
-
+
//mark the last group for removal, too
if (groupRowIndex != NSNotFound)
[removeIndexes addIndex: groupRowIndex];
-
+
NSAssert2(removeTrackerCount <= [removeIndexes count], @"Marked %ld trackers to remove, but only removing %ld rows", removeTrackerCount, [removeIndexes count]);
-
+
//we might have no trackers if remove right after a failed add (race condition ftw)
#warning look into having a failed add apply right away, so that this can become an assert
if (removeTrackerCount == 0)
return;
-
+
if ([[NSUserDefaults standardUserDefaults] boolForKey: @"WarningRemoveTrackers"])
{
NSAlert * alert = [[NSAlert alloc] init];
-
+
if (removeTrackerCount > 1)
{
[alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Are you sure you want to remove %d trackers?",
[alert setInformativeText: NSLocalizedString(@"Once removed, Transmission will no longer attempt to contact it."
" This cannot be undone.", "Remove trackers alert -> message")];
}
-
+
[alert addButtonWithTitle: NSLocalizedString(@"Remove", "Remove trackers alert -> button")];
[alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Remove trackers alert -> button")];
-
+
[alert setShowsSuppressionButton: YES];
NSInteger result = [alert runModal];
if ([[alert suppressionButton] state] == NSOnState)
[[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningRemoveTrackers"];
[alert release];
-
+
if (result != NSAlertFirstButtonReturn)
return;
}
-
+
[fTrackerTable beginUpdates];
-
+
for (Torrent * torrent in removeIdentifiers)
[torrent removeTrackers: [removeIdentifiers objectForKey: torrent]];
-
+
//reset table with either new or old value
[fTrackers release];
fTrackers = [[NSMutableArray alloc] init];
[fTrackers addObjectsFromArray: [torrent allTrackerStats]];
[fTrackerTable removeRowsAtIndexes: removeIndexes withAnimation: NSTableViewAnimationSlideLeft];
-
+
[fTrackerTable setTrackers: fTrackers];
-
+
[fTrackerTable endUpdates];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil]; //incase sort by tracker
}
@interface InfoWindowController : NSWindowController
{
NSArray * fTorrents;
-
+
CGFloat fMinWindowWidth;
-
+
NSViewController <InfoViewController> * fViewController;
NSInteger fCurrentTabTag;
IBOutlet NSMatrix * fTabMatrix;
-
+
InfoGeneralViewController * fGeneralViewController;
InfoActivityViewController * fActivityViewController;
InfoTrackersViewController * fTrackersViewController;
- (void) awakeFromNib
{
[fNoneSelectedField setStringValue: NSLocalizedString(@"No Torrents Selected", "Inspector -> selected torrents")];
-
+
//window location and size
NSPanel * window = (NSPanel *)[self window];
-
+
[window setFloatingPanel: NO];
-
+
const CGFloat windowHeight = NSHeight([window frame]);
fMinWindowWidth = [window minSize].width;
-
+
[window setFrameAutosaveName: @"InspectorWindow"];
[window setFrameUsingName: @"InspectorWindow"];
-
+
NSRect windowRect = [window frame];
windowRect.origin.y -= windowHeight - NSHeight(windowRect);
windowRect.size.height = windowHeight;
[window setFrame: windowRect display: NO];
-
+
[window setBecomesKeyOnlyIfNeeded: YES];
-
+
//set tab tooltips
[fTabMatrix setToolTip: NSLocalizedString(@"General Info", "Inspector -> tab") forCell: [fTabMatrix cellWithTag: TAB_GENERAL_TAG]];
[fTabMatrix setToolTip: NSLocalizedString(@"Activity", "Inspector -> tab") forCell: [fTabMatrix cellWithTag: TAB_ACTIVITY_TAG]];
[fTabMatrix setToolTip: NSLocalizedString(@"Peers", "Inspector -> tab") forCell: [fTabMatrix cellWithTag: TAB_PEERS_TAG]];
[fTabMatrix setToolTip: NSLocalizedString(@"Files", "Inspector -> tab") forCell: [fTabMatrix cellWithTag: TAB_FILE_TAG]];
[fTabMatrix setToolTip: NSLocalizedString(@"Options", "Inspector -> tab") forCell: [fTabMatrix cellWithTag: TAB_OPTIONS_TAG]];
-
+
//set selected tab
fCurrentTabTag = INVALID;
NSString * identifier = [[NSUserDefaults standardUserDefaults] stringForKey: @"InspectorSelected"];
}
[fTabMatrix selectCellWithTag: tag];
[self setTab: nil];
-
+
//set blank inspector
[self setInfoForTorrents: [NSArray array]];
-
+
//allow for update notifications
NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
[nc addObserver: self selector: @selector(resetInfoForTorrent:) name: @"ResetInspector" object: nil];
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
-
+
if ([fViewController respondsToSelector: @selector(saveViewSize)])
[fViewController saveViewSize];
-
+
[fGeneralViewController release];
[fActivityViewController release];
[fTrackersViewController release];
[fPeersViewController release];
[fFileViewController release];
[fOptionsViewController release];
-
+
[fTorrents release];
-
+
[super dealloc];
}
{
if (fTorrents && [fTorrents isEqualToArray: torrents])
return;
-
+
[fTorrents release];
fTorrents = [torrents retain];
-
+
[self resetInfo];
}
fCurrentTabTag = [fTabMatrix selectedTag];
if (fCurrentTabTag == oldTabTag)
return;
-
+
//take care of old view
CGFloat oldHeight = 0;
if (oldTabTag != INVALID)
{
//deselect old tab item
[(InfoTabButtonCell *)[fTabMatrix cellWithTag: oldTabTag] setSelectedTab: NO];
-
+
if ([fViewController respondsToSelector: @selector(saveViewSize)])
[fViewController saveViewSize];
-
+
if ([fViewController respondsToSelector: @selector(clearView)])
[fViewController clearView];
-
+
NSView * oldView = [fViewController view];
oldHeight = NSHeight([oldView frame]);
-
+
//remove old view
[oldView removeFromSuperview];
}
-
+
//set new tab item
NSString * identifier;
switch (fCurrentTabTag)
fGeneralViewController = [[InfoGeneralViewController alloc] init];
[fGeneralViewController setInfoForTorrents: fTorrents];
}
-
+
fViewController = fGeneralViewController;
identifier = TAB_INFO_IDENT;
break;
fActivityViewController = [[InfoActivityViewController alloc] init];
[fActivityViewController setInfoForTorrents: fTorrents];
}
-
+
fViewController = fActivityViewController;
identifier = TAB_ACTIVITY_IDENT;
break;
fTrackersViewController = [[InfoTrackersViewController alloc] init];
[fTrackersViewController setInfoForTorrents: fTorrents];
}
-
+
fViewController = fTrackersViewController;
identifier = TAB_TRACKER_IDENT;
break;
fPeersViewController = [[InfoPeersViewController alloc] init];
[fPeersViewController setInfoForTorrents: fTorrents];
}
-
+
fViewController = fPeersViewController;
identifier = TAB_PEERS_IDENT;
break;
fFileViewController = [[InfoFileViewController alloc] init];
[fFileViewController setInfoForTorrents: fTorrents];
}
-
+
fViewController = fFileViewController;
identifier = TAB_FILES_IDENT;
break;
fOptionsViewController = [[InfoOptionsViewController alloc] init];
[fOptionsViewController setInfoForTorrents: fTorrents];
}
-
+
fViewController = fOptionsViewController;
identifier = TAB_OPTIONS_IDENT;
break;
NSAssert1(NO, @"Unknown info tab selected: %ld", fCurrentTabTag);
return;
}
-
+
[[NSUserDefaults standardUserDefaults] setObject: identifier forKey: @"InspectorSelected"];
-
+
NSWindow * window = [self window];
-
+
[window setTitle: [NSString stringWithFormat: @"%@ - %@", [fViewController title],
NSLocalizedString(@"Torrent Inspector", "Inspector -> title")]];
-
+
//selected tab item
[(InfoTabButtonCell *)[fTabMatrix selectedCell] setSelectedTab: YES];
-
+
NSView * view = [fViewController view];
-
+
[fViewController updateInfo];
-
+
NSRect windowRect = [window frame], viewRect = [view frame];
-
+
const CGFloat difference = NSHeight(viewRect) - oldHeight;
windowRect.origin.y -= difference;
windowRect.size.height += difference;
-
+
const CGFloat minWindowWidth = MAX(fMinWindowWidth, [view fittingSize].width);
windowRect.size.width = MAX(NSWidth(windowRect), minWindowWidth);
-
+
if ([fViewController respondsToSelector: @selector(saveViewSize)]) //a little bit hacky, but avoids requiring an extra method
{
if ([window screen])
const CGFloat difference = screenHeight - NSHeight(windowRect);
windowRect.origin.y -= difference;
windowRect.size.height += difference;
-
+
viewRect.size.height += difference;
}
}
-
+
[window setMinSize: NSMakeSize(minWindowWidth, NSHeight(windowRect) - NSHeight(viewRect) + TAB_MIN_HEIGHT)];
[window setMaxSize: NSMakeSize(FLT_MAX, FLT_MAX)];
}
[window setMinSize: NSMakeSize(minWindowWidth, NSHeight(windowRect))];
[window setMaxSize: NSMakeSize(FLT_MAX, NSHeight(windowRect))];
}
-
+
viewRect.size.width = NSWidth(windowRect);
[view setFrame: viewRect];
-
+
[window setFrame: windowRect display: YES animate: oldTabTag != INVALID];
[[window contentView] addSubview: view];
-
+
[[window contentView] addConstraints: [NSLayoutConstraint constraintsWithVisualFormat: @"H:|-0-[view]-0-|"
options: 0
metrics: nil
options: 0
metrics: nil
views: @{ @"tabs": fTabMatrix, @"view": view }]];
-
+
if ((fCurrentTabTag == TAB_FILE_TAG || oldTabTag == TAB_FILE_TAG)
&& ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]))
[[QLPreviewPanel sharedPreviewPanel] reloadData];
NSInteger tag = [fTabMatrix selectedTag]+1;
if (tag >= [fTabMatrix numberOfColumns])
tag = 0;
-
+
[fTabMatrix selectCellWithTag: tag];
[self setTab: nil];
}
NSInteger tag = [fTabMatrix selectedTag]-1;
if (tag < 0)
tag = [fTabMatrix numberOfColumns]-1;
-
+
[fTabMatrix selectCellWithTag: tag];
[self setTab: nil];
}
{
if (fCurrentTabTag != TAB_FILE_TAG || ![[self window] isVisible])
return NO;
-
+
return [fFileViewController canQuickLook];
}
if (numberSelected > 0)
{
[fImageView setImage: [NSImage imageNamed: NSImageNameMultipleDocuments]];
-
+
[fNameField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@ Torrents Selected",
"Inspector -> selected torrents"),
[NSString formattedUInteger: numberSelected]]];
[fNameField setHidden: NO];
-
+
uint64_t size = 0;
NSUInteger fileCount = 0, magnetCount = 0;
for (Torrent * torrent in fTorrents)
if ([torrent isMagnet])
++magnetCount;
}
-
+
NSMutableArray * fileStrings = [NSMutableArray arrayWithCapacity: 2];
if (fileCount > 0)
{
"Inspector -> selected torrents"), [NSString formattedUInteger: magnetCount]];
[fileStrings addObject: magnetString];
}
-
+
NSString * fileString = [fileStrings componentsJoinedByString: @" + "];
-
+
if (magnetCount < numberSelected)
{
[fBasicInfoField setStringValue: [NSString stringWithFormat: @"%@, %@", fileString,
[NSString stringWithFormat: NSLocalizedString(@"%@ total", "Inspector -> selected torrents"),
[NSString stringForFileSize: size]]]];
-
+
NSString * byteString;
if ([NSApp isOnMountainLionOrBetter])
{
[fBasicInfoField setToolTip: nil];
}
[fBasicInfoField setHidden: NO];
-
+
[fNoneSelectedField setHidden: YES];
}
else
{
[fImageView setImage: [NSImage imageNamed: NSImageNameApplicationIcon]];
[fNoneSelectedField setHidden: NO];
-
+
[fNameField setHidden: YES];
[fBasicInfoField setHidden: YES];
}
-
+
[fNameField setToolTip: nil];
}
else
{
Torrent * torrent = [fTorrents objectAtIndex: 0];
-
+
[fImageView setImage: [torrent icon]];
-
+
NSString * name = [torrent name];
[fNameField setStringValue: name];
[fNameField setToolTip: name];
[fNameField setHidden: NO];
-
+
if (![torrent isMagnet])
{
NSString * basicString = [NSString stringForFileSize: [torrent size]];
basicString = [NSString stringWithFormat: @"%@, %@", fileString, basicString];
}
[fBasicInfoField setStringValue: basicString];
-
+
NSString * byteString;
if ([NSApp isOnMountainLionOrBetter])
{
[fBasicInfoField setToolTip: nil];
}
[fBasicInfoField setHidden: NO];
-
+
[fNoneSelectedField setHidden: YES];
}
-
+
[fGeneralViewController setInfoForTorrents: fTorrents];
[fActivityViewController setInfoForTorrents: fTorrents];
[fTrackersViewController setInfoForTorrents: fTorrents];
[fPeersViewController setInfoForTorrents: fTorrents];
[fFileViewController setInfoForTorrents: fTorrents];
[fOptionsViewController setInfoForTorrents: fTorrents];
-
+
[fViewController updateInfo];
}
@interface MessageWindowController : NSWindowController
{
IBOutlet NSTableView * fMessageTable;
-
+
IBOutlet NSPopUpButton * fLevelButton;
IBOutlet NSButton * fSaveButton, * fClearButton;
IBOutlet NSSearchField * fFilterField;
-
+
NSMutableArray * fMessages, * fDisplayedMessages;
-
+
NSDictionary * fAttributes;
-
+
NSTimer * fTimer;
-
+
NSLock * fLock;
}
[window setFrameAutosaveName: @"MessageWindowFrame"];
[window setFrameUsingName: @"MessageWindowFrame"];
[window setRestorationClass: [self class]];
-
+
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(resizeColumn)
name: NSTableViewColumnDidResizeNotification object: fMessageTable];
-
+
[window setContentBorderThickness: NSMinY([[fMessageTable enclosingScrollView] frame]) forEdge: NSMinYEdge];
-
+
[[self window] setTitle: NSLocalizedString(@"Message Log", "Message window -> title")];
-
+
//set images and text for popup button items
[[fLevelButton itemAtIndex: LEVEL_ERROR] setTitle: NSLocalizedString(@"Error", "Message window -> level string")];
[[fLevelButton itemAtIndex: LEVEL_INFO] setTitle: NSLocalizedString(@"Info", "Message window -> level string")];
const CGFloat levelButtonOldWidth = NSWidth([fLevelButton frame]);
[fLevelButton sizeToFit];
-
+
//set table column text
[[[fMessageTable tableColumnWithIdentifier: @"Date"] headerCell] setTitle: NSLocalizedString(@"Date",
"Message window -> table column")];
"Message window -> table column")];
[[[fMessageTable tableColumnWithIdentifier: @"Message"] headerCell] setTitle: NSLocalizedString(@"Message",
"Message window -> table column")];
-
+
//set and size buttons
[fSaveButton setTitle: [NSLocalizedString(@"Save", "Message window -> save button") stringByAppendingEllipsis]];
[fSaveButton sizeToFit];
-
+
NSRect saveButtonFrame = [fSaveButton frame];
saveButtonFrame.size.width += 10.0;
saveButtonFrame.origin.x += NSWidth([fLevelButton frame]) - levelButtonOldWidth;
[fSaveButton setFrame: saveButtonFrame];
-
+
const CGFloat oldClearButtonWidth = [fClearButton frame].size.width;
-
+
[fClearButton setTitle: NSLocalizedString(@"Clear", "Message window -> save button")];
[fClearButton sizeToFit];
-
+
NSRect clearButtonFrame = [fClearButton frame];
clearButtonFrame.size.width = MAX(clearButtonFrame.size.width + 10.0, saveButtonFrame.size.width);
clearButtonFrame.origin.x -= NSWidth(clearButtonFrame) - oldClearButtonWidth;
[fClearButton setFrame: clearButtonFrame];
-
+
[[fFilterField cell] setPlaceholderString: NSLocalizedString(@"Filter", "Message window -> filter field")];
NSRect filterButtonFrame = [fFilterField frame];
filterButtonFrame.origin.x -= NSWidth(clearButtonFrame) - oldClearButtonWidth;
[fFilterField setFrame: filterButtonFrame];
-
+
fAttributes = [[[[[fMessageTable tableColumnWithIdentifier: @"Message"] dataCell] attributedStringValue]
attributesAtIndex: 0 effectiveRange: NULL] retain];
-
+
//select proper level in popup button
switch ([[NSUserDefaults standardUserDefaults] integerForKey: @"MessageLevel"])
{
[[NSUserDefaults standardUserDefaults] setInteger: TR_LOG_ERROR forKey: @"MessageLevel"];
[fLevelButton selectItemAtIndex: LEVEL_ERROR];
}
-
+
fMessages = [[NSMutableArray alloc] init];
fDisplayedMessages = [[NSMutableArray alloc] init];
-
+
fLock = [[NSLock alloc] init];
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
-
+
[fTimer invalidate];
[fTimer release];
[fLock release];
-
+
[fMessages release];
[fDisplayedMessages release];
-
+
[fAttributes release];
-
+
[super dealloc];
}
+ (void) restoreWindowWithIdentifier: (NSString *) identifier state: (NSCoder *) state completionHandler: (void (^)(NSWindow *, NSError *)) completionHandler
{
NSAssert1([identifier isEqualToString: @"MessageWindow"], @"Trying to restore unexpected identifier %@", identifier);
-
+
NSWindow * window = [[(Controller *)[NSApp delegate] messageWindowController] window];
completionHandler(window, nil);
}
tr_log_message * messages;
if ((messages = tr_logGetQueue()) == NULL)
return;
-
+
[fLock lock];
-
+
static NSUInteger currentIndex = 0;
-
+
NSScroller * scroller = [[fMessageTable enclosingScrollView] verticalScroller];
const BOOL shouldScroll = currentIndex == 0 || [scroller floatValue] == 1.0 || [scroller isHidden]
|| [scroller knobProportion] == 1.0;
-
+
const NSInteger maxLevel = [[NSUserDefaults standardUserDefaults] integerForKey: @"MessageLevel"];
NSString * filterString = [fFilterField stringValue];
-
+
BOOL changed = NO;
-
+
for (tr_log_message * currentMessage = messages; currentMessage != NULL; currentMessage = currentMessage->next)
{
NSString * name = currentMessage->name != NULL ? [NSString stringWithUTF8String: currentMessage->name]
: [[NSProcessInfo processInfo] processName];
-
+
NSString * file = [[[NSString stringWithUTF8String: currentMessage->file] lastPathComponent] stringByAppendingFormat: @":%d",
currentMessage->line];
-
+
NSDictionary * message = [NSDictionary dictionaryWithObjectsAndKeys:
[NSString stringWithUTF8String: currentMessage->message], @"Message",
[NSDate dateWithTimeIntervalSince1970: currentMessage->when], @"Date",
[NSNumber numberWithInteger: currentMessage->level], @"Level",
name, @"Name",
file, @"File", nil];
-
+
[fMessages addObject: message];
-
+
if (currentMessage->level <= maxLevel && [self shouldIncludeMessageForFilter: filterString message: message])
{
[fDisplayedMessages addObject: message];
changed = YES;
}
}
-
+
if ([fMessages count] > TR_LOG_MAX_QUEUE_LENGTH)
{
const NSUInteger oldCount = [fDisplayedMessages count];
-
+
NSIndexSet * removeIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fMessages count]-TR_LOG_MAX_QUEUE_LENGTH)];
NSArray * itemsToRemove = [fMessages objectsAtIndexes: removeIndexes];
-
+
[fMessages removeObjectsAtIndexes: removeIndexes];
[fDisplayedMessages removeObjectsInArray: itemsToRemove];
-
+
changed |= oldCount > [fDisplayedMessages count];
}
-
+
if (changed)
{
[fDisplayedMessages sortUsingDescriptors: [fMessageTable sortDescriptors]];
-
+
[fMessageTable reloadData];
if (shouldScroll)
[fMessageTable scrollRowToVisible: [fMessageTable numberOfRows]-1];
}
-
+
[fLock unlock];
-
+
tr_logFreeQueue (messages);
}
- (CGFloat) tableView: (NSTableView *) tableView heightOfRow: (NSInteger) row
{
NSString * message = [[fDisplayedMessages objectAtIndex: row] objectForKey: @"Message"];
-
+
NSTableColumn * column = [tableView tableColumnWithIdentifier: @"Message"];
const CGFloat count = floorf([message sizeWithAttributes: fAttributes].width / [column width]);
-
+
return [tableView rowHeight] * (count + 1.0);
}
{
NSIndexSet * indexes = [fMessageTable selectedRowIndexes];
NSMutableArray * messageStrings = [NSMutableArray arrayWithCapacity: [indexes count]];
-
+
for (NSDictionary * message in [fDisplayedMessages objectsAtIndexes: indexes])
[messageStrings addObject: [self stringForMessage: message]];
-
+
NSString * messageString = [messageStrings componentsJoinedByString: @"\n"];
-
+
NSPasteboard * pb = [NSPasteboard generalPasteboard];
[pb clearContents];
[pb writeObjects: [NSArray arrayWithObject: messageString]];
- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
{
SEL action = [menuItem action];
-
+
if (action == @selector(copy:))
return [fMessageTable numberOfSelectedRows] > 0;
-
+
return YES;
}
NSAssert1(NO, @"Unknown message log level: %ld", [fLevelButton indexOfSelectedItem]);
level = TR_LOG_INFO;
}
-
+
if ([[NSUserDefaults standardUserDefaults] integerForKey: @"MessageLevel"] == level)
return;
-
+
[[NSUserDefaults standardUserDefaults] setInteger: level forKey: @"MessageLevel"];
-
+
[fLock lock];
-
+
[self updateListForFilter];
-
+
[fLock unlock];
}
- (void) changeFilter: (id) sender
{
[fLock lock];
-
+
[self updateListForFilter];
-
+
[fLock unlock];
}
- (void) clearLog: (id) sender
{
[fLock lock];
-
+
[fMessages removeAllObjects];
[fMessageTable beginUpdates];
[fDisplayedMessages removeAllObjects];
[fMessageTable endUpdates];
-
+
[fLock unlock];
}
NSSavePanel * panel = [NSSavePanel savePanel];
[panel setAllowedFileTypes: [NSArray arrayWithObject: @"txt"]];
[panel setCanSelectHiddenExtension: YES];
-
+
[panel setNameFieldStringValue: NSLocalizedString(@"untitled", "Save log panel -> default file name")];
-
+
[panel beginSheetModalForWindow: [self window] completionHandler: ^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton)
{
NSArray * descriptors = [[NSArray alloc] initWithObjects: descriptor, nil];
NSArray * sortedMessages = [fDisplayedMessages sortedArrayUsingDescriptors: descriptors];
[descriptors release];
-
+
//create the text to output
NSMutableArray * messageStrings = [NSMutableArray arrayWithCapacity: [sortedMessages count]];
for (NSDictionary * message in sortedMessages)
[messageStrings addObject: [self stringForMessage: message]];
-
+
NSString * fileString = [messageStrings componentsJoinedByString: @"\n"];
-
+
if (![fileString writeToFile: [[panel URL] path] atomically: YES encoding: NSUTF8StringEncoding error: nil])
{
NSAlert * alert = [[NSAlert alloc] init];
NSLocalizedString(@"There was a problem creating the file \"%@\".",
"Save log alert panel -> message"), [[[panel URL] path] lastPathComponent]]];
[alert setAlertStyle: NSWarningAlertStyle];
-
+
[alert runModal];
[alert release];
}
{
if ([filterString isEqualToString: @""])
return YES;
-
+
const NSStringCompareOptions searchOptions = NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch;
return [[message objectForKey: @"Name"] rangeOfString: filterString options: searchOptions].location != NSNotFound
|| [[message objectForKey: @"Message"] rangeOfString: filterString options: searchOptions].location != NSNotFound;
{
const NSInteger level = [[NSUserDefaults standardUserDefaults] integerForKey: @"MessageLevel"];
NSString * filterString = [fFilterField stringValue];
-
+
NSIndexSet * indexes = [fMessages indexesOfObjectsWithOptions: NSEnumerationConcurrent passingTest: ^BOOL(id message, NSUInteger idx, BOOL * stop) {
return [[(NSDictionary *)message objectForKey: @"Level"] integerValue] <= level && [self shouldIncludeMessageForFilter: filterString message: message];
}];
-
+
NSArray * tempMessages = [[fMessages objectsAtIndexes: indexes] sortedArrayUsingDescriptors: [fMessageTable sortDescriptors]];
[fMessageTable beginUpdates];
-
+
//figure out which rows were added/moved
NSUInteger currentIndex = 0, totalCount = 0;
NSMutableArray * itemsToAdd = [NSMutableArray array];
NSMutableIndexSet * itemsToAddIndexes = [NSMutableIndexSet indexSet];
-
+
for (NSDictionary * message in tempMessages)
{
const NSUInteger previousIndex = [fDisplayedMessages indexOfObject: message inRange: NSMakeRange(currentIndex, [fDisplayedMessages count]-currentIndex)];
}
++currentIndex;
}
-
+
++totalCount;
}
-
+
//remove trailing items - those are the unused
if (currentIndex < [fDisplayedMessages count])
{
[fDisplayedMessages removeObjectsInRange: removeRange];
[fMessageTable removeRowsAtIndexes: [NSIndexSet indexSetWithIndexesInRange: removeRange] withAnimation: NSTableViewAnimationSlideDown];
}
-
+
//add new items
[fDisplayedMessages insertObjects: itemsToAdd atIndexes: itemsToAddIndexes];
[fMessageTable insertRowsAtIndexes: itemsToAddIndexes withAnimation: NSTableViewAnimationSlideUp];
[fMessageTable endUpdates];
-
+
NSAssert2([fDisplayedMessages isEqualToArray: tempMessages], @"Inconsistency between message arrays! %@ %@", fDisplayedMessages, tempMessages);
}
NSAssert1(NO, @"Unknown message log level: %ld", level);
levelString = @"?";
}
-
+
return [NSString stringWithFormat: @"%@ %@ [%@] %@: %@", [message objectForKey: @"Date"],
[message objectForKey: @"File"], levelString,
[message objectForKey: @"Name"], [message objectForKey: @"Message"], nil];
- (NSImage *) imageWithColor: (NSColor *) color
{
NSImage * coloredImage = [self copy];
-
+
[coloredImage lockFocus];
-
+
[color set];
-
+
const NSSize size = [coloredImage size];
NSRectFillUsingOperation(NSMakeRect(0.0, 0.0, size.width, size.height), NSCompositeSourceAtop);
-
+
[coloredImage unlockFocus];
-
+
return [coloredImage autorelease];
}
{
if (fromIndex == toIndex)
return;
-
+
id object = [[self objectAtIndex: fromIndex] retain];
-
+
//shift objects - more efficient than simply removing the object and re-inserting the object
if (fromIndex < toIndex)
{
[self replaceObjectAtIndex: i withObject: [self objectAtIndex: i-1]];
}
[self replaceObjectAtIndex: toIndex withObject: object];
-
+
[object release];
}
+ (NSString *) ellipsis
{
- return [NSString stringWithUTF8String: "\xE2\x80\xA6"];
+ return [NSString stringWithUTF8String: "\xE2\x80\xA6"];
}
- (NSString *) stringByAppendingEllipsis
{
- return [self stringByAppendingString: [NSString ellipsis]];
+ return [self stringByAppendingString: [NSString ellipsis]];
}
#warning use localizedStringWithFormat: directly when 10.8-only
[numberFormatter setNumberStyle: NSNumberFormatterDecimalStyle];
[numberFormatter setMaximumFractionDigits: 0];
});
-
+
return [numberFormatter stringFromNumber: [NSNumber numberWithUnsignedInteger: value]];
}
}
if ([NSApp isOnMountainLionOrBetter])
{
NSByteCountFormatter * fileSizeFormatter = [[NSByteCountFormatterMtLion alloc] init];
-
+
fullString = [fileSizeFormatter stringFromByteCount: fullSize];
-
+
//figure out the magniture of the two, since we can't rely on comparing the units because of localization and pluralization issues (for example, "1 byte of 2 bytes")
BOOL partialUnitsSame;
if (partialSize == 0)
const unsigned int magnitudeFull = fullSize < 1000 ? 0 : log(fullSize)/log(1000); //we have to catch 0 with a special case, so might as well avoid the math for all of magnitude 0
partialUnitsSame = magnitudePartial == magnitudeFull;
}
-
+
[fileSizeFormatter setIncludesUnit: !partialUnitsSame];
partialString = [fileSizeFormatter stringFromByteCount: partialSize];
-
+
[fileSizeFormatter release];
}
else
fullString = [self stringForFileSizeLion: fullSize showUnitUnless: nil unitsUsed: &units];
partialString = [self stringForFileSizeLion: partialSize showUnitUnless: units unitsUsed: nil];
}
-
+
return [NSString stringWithFormat: NSLocalizedString(@"%@ of %@", "file size string"), partialString, fullString];
}
{
NSAssert(![NSApp isOnYosemiteOrBetter], @"you should be using NSDateComponentsFormatter on >= 10.10");
NSParameterAssert(max > 0);
-
+
NSMutableArray * timeArray = [NSMutableArray arrayWithCapacity: MIN(max, 5u)];
NSUInteger remaining = seconds; //causes problems for some users when it's a uint64_t
-
+
if (seconds >= 31557600) //official amount of seconds in one year
{
const NSUInteger years = remaining / 31557600;
}
if (max > 0 && showSeconds)
[timeArray addObject: [NSString stringWithFormat: NSLocalizedString(@"%u sec", "time string"), remaining]];
-
+
NSString * timeString = [timeArray componentsJoinedByString: @" "];
-
+
if (includesTimeRemainingPhrase) {
timeString = [NSString stringWithFormat: NSLocalizedString(@"%@ remaining", "time remaining string"), timeString];
}
-
+
return timeString;
}
- (NSArray *) betterComponentsSeparatedByCharactersInSet: (NSCharacterSet *) separators
{
NSMutableArray * components = [NSMutableArray array];
-
+
NSCharacterSet * includededCharSet = [separators invertedSet];
NSUInteger index = 0;
const NSUInteger fullLength = [self length];
const NSUInteger start = [self rangeOfCharacterFromSet: includededCharSet options: 0 range: NSMakeRange(index, fullLength - index)].location;
if (start == NSNotFound)
break;
-
+
const NSRange endRange = [self rangeOfCharacterFromSet: separators options: 0 range: NSMakeRange(start, fullLength - start)];
if (endRange.location == NSNotFound)
{
[components addObject: [self substringFromIndex: start]];
break;
}
-
+
[components addObject: [self substringWithRange: NSMakeRange(start, endRange.location - start)]];
-
+
index = NSMaxRange(endRange);
}
while (YES);
-
+
return components;
}
unit = NSLocalizedString(@"TB", "File size - terabytes");
decimals = 2;
}
-
+
//match Finder's behavior
NSNumberFormatter * numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle: NSNumberFormatterDecimalStyle];
[numberFormatter setMinimumFractionDigits: 0];
[numberFormatter setMaximumFractionDigits: decimals];
-
+
NSString * fileSizeString = [numberFormatter stringFromNumber: [NSNumber numberWithFloat: convertedSize]];
[numberFormatter release];
-
+
if (!notAllowedUnit || ![unit isEqualToString: notAllowedUnit])
fileSizeString = [fileSizeString stringByAppendingFormat: @" %@", unit];
-
+
if (unitUsed)
*unitUsed = unit;
-
+
return fileSizeString;
}
{
if (speed <= 999.95) //0.0 KB/s to 999.9 KB/s
return [NSString localizedStringWithFormat: @"%.1f %@", speed, kb];
-
+
speed /= 1000.0;
-
+
if (speed <= 99.995) //1.00 MB/s to 99.99 MB/s
return [NSString localizedStringWithFormat: @"%.2f %@", speed, mb];
else if (speed <= 999.95) //100.0 MB/s to 999.9 MB/s
{
PeerProgressIndicatorCell * copy = [super copyWithZone: zone];
copy->fAttributes = [fAttributes retain];
-
+
return copy;
}
{
NSMutableParagraphStyle * paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[paragraphStyle setAlignment: NSRightTextAlignment];
-
+
fAttributes = [[NSDictionary alloc] initWithObjectsAndKeys: [NSFont systemFontOfSize: 11.0], NSFontAttributeName,
paragraphStyle, NSParagraphStyleAttributeName, nil];
[paragraphStyle release];
}
-
+
[[NSString percentString: [self floatValue] longDecimals: NO] drawInRect: cellFrame withAttributes: fAttributes];
}
else
[fAttributes release];
fAttributes = nil;
}
-
+
[super drawWithFrame: cellFrame inView: controlView];
if (fSeed)
{
NSImage * checkImage = [NSImage imageNamed: @"CompleteCheck"];
-
+
const NSSize imageSize = [checkImage size];
const NSRect rect = NSMakeRect(floor(NSMidX(cellFrame) - imageSize.width * 0.5),
floor(NSMidY(cellFrame) - imageSize.height * 0.5),
imageSize.width, imageSize.height);
-
+
[checkImage drawInRect: rect fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0 respectFlipped: YES hints: nil];
}
}
{
[[NSUserDefaults standardUserDefaults] setBool: ![[NSUserDefaults standardUserDefaults]
boolForKey: @"DisplayPeerProgressBarNumber"] forKey: @"DisplayPeerProgressBarNumber"];
-
+
NSIndexSet * rowIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [self numberOfRows])],
* columnIndexes = [NSIndexSet indexSetWithIndex: [self columnAtPoint: point]];
[self reloadDataForRowIndexes: rowIndexes columnIndexes: columnIndexes];
@interface PiecesView : NSImageView
{
int8_t * fPieces;
-
+
NSColor * fGreenAvailabilityColor, * fBluePieceColor;
-
+
Torrent * fTorrent;
NSInteger fNumPieces, fAcross, fWidth, fExtraBorder;
}
//store box colors
fGreenAvailabilityColor = [[NSColor colorWithCalibratedRed: 0.0 green: 1.0 blue: 0.4 alpha: 1.0] retain];
fBluePieceColor = [[NSColor colorWithCalibratedRed: 0.0 green: 0.4 blue: 0.8 alpha: 1.0] retain];
-
+
//actually draw the box
[self setTorrent: nil];
}
- (void) dealloc
{
tr_free(fPieces);
-
+
[fGreenAvailabilityColor release];
[fBluePieceColor release];
-
+
[super dealloc];
}
- (void) setTorrent: (Torrent *) torrent
{
[self clearView];
-
+
fTorrent = (torrent && ![torrent isMagnet]) ? torrent : nil;
if (fTorrent)
{
//determine relevant values
fNumPieces = MIN([fTorrent pieceCount], MAX_ACROSS * MAX_ACROSS);
fAcross = ceil(sqrt(fNumPieces));
-
+
const CGFloat width = [self bounds].size.width;
fWidth = (width - (fAcross + 1) * BETWEEN) / fAcross;
fExtraBorder = (width - ((fWidth + BETWEEN) * fAcross + BETWEEN)) / 2;
}
-
+
NSImage * back = [[NSImage alloc] initWithSize: [self bounds].size];
[back lockFocus];
-
+
NSGradient * gradient = [[NSGradient alloc] initWithStartingColor: [NSColor colorWithCalibratedWhite: 0.0 alpha: 0.4]
endingColor: [NSColor colorWithCalibratedWhite: 0.2 alpha: 0.4]];
[gradient drawInRect: [self bounds] angle: 90.0];
[gradient release];
[back unlockFocus];
-
+
[self setImage: back];
[back release];
-
+
[self setNeedsDisplay];
}
{
if (!fTorrent)
return;
-
+
//determine if first time
const BOOL first = fPieces == NULL;
if (first)
int8_t * pieces = NULL;
float * piecesPercent = NULL;
-
+
const BOOL showAvailablity = [[NSUserDefaults standardUserDefaults] boolForKey: @"PiecesViewShowAvailability"];
if (showAvailablity)
- {
+ {
pieces = (int8_t *)tr_malloc(fNumPieces * sizeof(int8_t));
[fTorrent getAvailability: pieces size: fNumPieces];
}
else
- {
+ {
piecesPercent = (float *)tr_malloc(fNumPieces * sizeof(float));
[fTorrent getAmountFinished: piecesPercent size: fNumPieces];
}
-
+
NSImage * image = [self image];
-
+
NSRect fillRects[fNumPieces];
NSColor * fillColors[fNumPieces];
-
+
NSInteger usedCount = 0;
-
+
for (NSInteger index = 0; index < fNumPieces; index++)
{
NSColor * pieceColor = nil;
-
+
if (showAvailablity ? pieces[index] == -1 : piecesPercent[index] == 1.0)
{
if (first || fPieces[index] != PIECE_FINISHED)
pieceColor = [[NSColor whiteColor] blendedColorWithFraction: percent ofColor: fullColor];
fPieces[index] = PIECE_SOME;
}
-
+
if (pieceColor)
{
const NSInteger across = index % fAcross,
[image size].width - (down + 1) * (fWidth + BETWEEN) - fExtraBorder,
fWidth, fWidth);
fillColors[usedCount] = pieceColor;
-
+
usedCount++;
}
}
-
+
if (usedCount > 0)
{
[image lockFocus];
[image unlockFocus];
[self setNeedsDisplay];
}
-
+
tr_free(pieces);
tr_free(piecesPercent);
}
{
const BOOL availability = ![[NSUserDefaults standardUserDefaults] boolForKey: @"PiecesViewShowAvailability"];
[[NSUserDefaults standardUserDefaults] setBool: availability forKey: @"PiecesViewShowAvailability"];
-
+
[self sendAction:[self action] to:[self target]];
}
-
+
[super mouseDown: event];
}
} port_status_t;
@interface PortChecker : NSObject
-{
+{
id fDelegate;
port_status_t fStatus;
-
+
NSURLConnection * fConnection;
NSMutableData * fPortProbeData;
-
+
NSTimer * fTimer;
}
if ((self = [super init]))
{
fDelegate = delegate;
-
+
fStatus = PORT_STATUS_CHECKING;
-
+
fTimer = [[NSTimer scheduledTimerWithTimeInterval: CHECK_FIRE target: self selector: @selector(startProbe:) userInfo: [NSNumber numberWithInteger: portNumber] repeats: NO] retain];
if (!delay)
[fTimer fire];
}
-
+
return self;
}
{
[fTimer invalidate];
[fTimer release];
-
+
[fConnection release];
[fPortProbeData release];
[super dealloc];
[fTimer invalidate];
[fTimer release];
fTimer = nil;
-
+
[fConnection cancel];
}
NSString * probeString = [[NSString alloc] initWithData: fPortProbeData encoding: NSUTF8StringEncoding];
[fPortProbeData release];
fPortProbeData = nil;
-
+
if (probeString)
{
if ([probeString isEqualToString: @"1"])
{
[fTimer release];
fTimer = nil;
-
+
NSURLRequest * portProbeRequest = [NSURLRequest requestWithURL: [NSURL URLWithString: CHECKER_URL([[timer userInfo] integerValue])]
cachePolicy: NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval: 15.0];
-
+
if ((fConnection = [[NSURLConnection alloc] initWithRequest: portProbeRequest delegate: self]))
fPortProbeData = [[NSMutableData alloc] init];
else
- (void) callBackWithStatus: (port_status_t) status
{
fStatus = status;
-
+
if (fDelegate && [fDelegate respondsToSelector: @selector(portCheckerDidFinishProbing:)])
[fDelegate performSelectorOnMainThread: @selector(portCheckerDidFinishProbing:) withObject: self waitUntilDone: NO];
}
{
//we only make NSComparisonPredicates
NSComparisonPredicate * predicate = (NSComparisonPredicate *)[super predicateWithSubpredicates: subpredicates];
-
+
//construct a near-identical predicate
return [NSComparisonPredicate predicateWithLeftExpression: [predicate leftExpression]
- rightExpression: [predicate rightExpression]
- modifier: NSAnyPredicateModifier
- type: [predicate predicateOperatorType]
- options: [predicate options]];
+ rightExpression: [predicate rightExpression]
+ modifier: NSAnyPredicateModifier
+ type: [predicate predicateOperatorType]
+ options: [predicate options]];
}
@end
tr_session * fHandle;
NSUserDefaults * fDefaults;
BOOL fHasLoaded;
-
+
IBOutlet NSView * fGeneralView, * fTransfersView, * fBandwidthView, * fPeersView, * fNetworkView, * fRemoteView, * fGroupsView;
-
+
NSString * fInitialString;
-
+
IBOutlet NSButton * fBuiltInGrowlButton, *fGrowlAppButton;
IBOutlet NSTextField * fCheckForUpdatesLabel;
IBOutlet NSButton * fCheckForUpdatesButton, * fCheckForUpdatesBetaButton;
-
+
IBOutlet NSPopUpButton * fFolderPopUp, * fIncompleteFolderPopUp, * fImportFolderPopUp, * fDoneScriptPopUp;
IBOutlet NSButton * fShowMagnetAddWindowCheck;
IBOutlet NSTextField * fRatioStopField, * fIdleStopField, * fQueueDownloadField, * fQueueSeedField, * fStalledField;
IBOutlet NSTextField * fUploadField, * fDownloadField,
* fSpeedLimitUploadField, * fSpeedLimitDownloadField;
IBOutlet NSPopUpButton * fAutoSpeedDayTypePopUp;
-
+
IBOutlet NSTextField * fPeersGlobalField, * fPeersTorrentField,
* fBlocklistURLField, * fBlocklistMessageField, * fBlocklistDateField;
IBOutlet NSButton * fBlocklistButton;
-
+
PortChecker * fPortChecker;
IBOutlet NSTextField * fPortField, * fPortStatusField;
IBOutlet NSButton * fNatCheck;
IBOutlet NSProgressIndicator * fPortStatusProgress;
NSTimer * fPortStatusTimer;
int fPeerPort, fNatStatus;
-
+
IBOutlet NSTextField * fRPCPortField, * fRPCPasswordField;
IBOutlet NSTableView * fRPCWhitelistTable;
NSMutableArray * fRPCWhitelistArray;
if ((self = [super initWithWindowNibName: @"PrefsWindow"]))
{
fHandle = handle;
-
+
fDefaults = [NSUserDefaults standardUserDefaults];
-
+
//check for old version download location (before 1.1)
NSString * choice;
if ((choice = [fDefaults stringForKey: @"DownloadChoice"]))
{
[fDefaults setBool: [choice isEqualToString: @"Constant"] forKey: @"DownloadLocationConstant"];
[fDefaults setBool: YES forKey: @"DownloadAsk"];
-
+
[fDefaults removeObjectForKey: @"DownloadChoice"];
}
-
+
//check for old version blocklist (before 2.12)
NSDate * blocklistDate;
if ((blocklistDate = [fDefaults objectForKey: @"BlocklistLastUpdate"]))
[fDefaults setObject: blocklistDate forKey: @"BlocklistNewLastUpdateSuccess"];
[fDefaults setObject: blocklistDate forKey: @"BlocklistNewLastUpdate"];
[fDefaults removeObjectForKey: @"BlocklistLastUpdate"];
-
+
NSURL * blocklistDir = [[[[NSFileManager defaultManager] URLsForDirectory: NSApplicationDirectory inDomains: NSUserDomainMask] objectAtIndex: 0] URLByAppendingPathComponent: @"Transmission/blocklists/"];
[[NSFileManager defaultManager] moveItemAtURL: [blocklistDir URLByAppendingPathComponent: @"level1.bin"]
toURL: [blocklistDir URLByAppendingPathComponent: [NSString stringWithUTF8String: DEFAULT_BLOCKLIST_FILENAME]]
error: nil];
}
-
+
//save a new random port
if ([fDefaults boolForKey: @"RandomPort"])
[fDefaults setInteger: tr_sessionGetPeerPort(fHandle) forKey: @"BindPort"];
-
+
//set auto import
NSString * autoPath;
if ([fDefaults boolForKey: @"AutoImport"] && (autoPath = [fDefaults stringForKey: @"AutoImportDirectory"]))
[[(Controller *)[NSApp delegate] fileWatcherQueue] addPath: [autoPath stringByExpandingTildeInPath] notifyingAbout: VDKQueueNotifyAboutWrite];
-
+
//set special-handling of magnet link add window checkbox
[self updateShowAddMagnetWindowField];
-
+
//set blocklist scheduler
[[BlocklistScheduler scheduler] updateSchedule];
-
+
//set encryption
[self setEncryptionMode: nil];
-
+
//update rpc whitelist
[self updateRPCPassword];
-
+
fRPCWhitelistArray = [[fDefaults arrayForKey: @"RPCWhitelist"] mutableCopy];
if (!fRPCWhitelistArray)
fRPCWhitelistArray = [[NSMutableArray arrayWithObject: @"127.0.0.1"] retain];
[self updateRPCWhitelist];
-
+
//reset old Sparkle settings from previous versions
[fDefaults removeObjectForKey: @"SUScheduledCheckInterval"];
if ([fDefaults objectForKey: @"CheckForUpdates"])
[[SUUpdater sharedUpdater] setAutomaticallyChecksForUpdates: [fDefaults boolForKey: @"CheckForUpdates"]];
[fDefaults removeObjectForKey: @"CheckForUpdates"];
}
-
+
//set built-in Growl
[GrowlApplicationBridge setShouldUseBuiltInNotifications: ![NSApp isOnMountainLionOrBetter] && [fDefaults boolForKey: @"DisplayNotifications"]];
-
+
[self setAutoUpdateToBeta: nil];
}
-
+
return self;
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
-
+
[fPortStatusTimer invalidate];
[fPortStatusTimer release];
if (fPortChecker)
[fPortChecker cancelProbe];
[fPortChecker release];
}
-
+
[fRPCWhitelistArray release];
-
+
[fRPCPassword release];
-
+
[super dealloc];
}
fHasLoaded = YES;
[[self window] setRestorationClass: [self class]];
-
+
NSToolbar * toolbar = [[NSToolbar alloc] initWithIdentifier: @"Preferences Toolbar"];
[toolbar setDelegate: self];
[toolbar setAllowsUserCustomization: NO];
[toolbar setSelectedItemIdentifier: TOOLBAR_GENERAL];
[[self window] setToolbar: toolbar];
[toolbar release];
-
+
[self setPrefView: nil];
-
+
//make sure proper notification settings are shown
[self updateGrowlButton];
-
+
//set download folder
[fFolderPopUp selectItemAtIndex: [fDefaults boolForKey: @"DownloadLocationConstant"] ? DOWNLOAD_FOLDER : DOWNLOAD_TORRENT];
-
+
//set stop ratio
[fRatioStopField setFloatValue: [fDefaults floatForKey: @"RatioLimit"]];
-
+
//set idle seeding minutes
[fIdleStopField setIntegerValue: [fDefaults integerForKey: @"IdleLimitMinutes"]];
-
+
//set limits
[self updateLimitFields];
-
+
//set speed limit
[fSpeedLimitUploadField setIntValue: [fDefaults integerForKey: @"SpeedLimitUploadLimit"]];
[fSpeedLimitDownloadField setIntValue: [fDefaults integerForKey: @"SpeedLimitDownloadLimit"]];
-
+
//set port
[fPortField setIntValue: [fDefaults integerForKey: @"BindPort"]];
fNatStatus = -1;
-
+
[self updatePortStatus];
fPortStatusTimer = [[NSTimer scheduledTimerWithTimeInterval: 5.0 target: self selector: @selector(updatePortStatus) userInfo: nil repeats: YES] retain];
-
+
//set peer connections
[fPeersGlobalField setIntValue: [fDefaults integerForKey: @"PeersTotal"]];
[fPeersTorrentField setIntValue: [fDefaults integerForKey: @"PeersTorrent"]];
-
+
//set queue values
[fQueueDownloadField setIntValue: [fDefaults integerForKey: @"QueueDownloadNumber"]];
[fQueueSeedField setIntValue: [fDefaults integerForKey: @"QueueSeedNumber"]];
[fStalledField setIntValue: [fDefaults integerForKey: @"StalledMinutes"]];
-
+
//set blocklist
NSString * blocklistURL = [fDefaults stringForKey: @"BlocklistURL"];
if (blocklistURL)
[fBlocklistURLField setStringValue: blocklistURL];
-
+
[self updateBlocklistButton];
[self updateBlocklistFields];
-
+
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateLimitFields)
name: @"UpdateSpeedLimitValuesOutsidePrefs" object: nil];
-
+
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateRatioStopField)
name: @"UpdateRatioStopValueOutsidePrefs" object: nil];
-
+
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateLimitStopField)
name: @"UpdateIdleStopValueOutsidePrefs" object: nil];
-
+
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateBlocklistFields)
name: @"BlocklistUpdated" object: nil];
-
+
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateBlocklistURLField)
name: NSControlTextDidChangeNotification object: fBlocklistURLField];
-
+
//set rpc port
[fRPCPortField setIntValue: [fDefaults integerForKey: @"RPCPort"]];
-
+
//set rpc password
if (fRPCPassword)
[fRPCPasswordField setStringValue: fRPCPassword];
const tr_port port = [sender intValue];
[fDefaults setInteger: port forKey: @"BindPort"];
tr_sessionSetPeerPort(fHandle, port);
-
+
fPeerPort = -1;
[self updatePortStatus];
}
const tr_port port = tr_sessionSetPeerPortRandom(fHandle);
[fDefaults setInteger: port forKey: @"BindPort"];
[fPortField setIntValue: port];
-
+
fPeerPort = -1;
[self updatePortStatus];
}
- (void) setNat: (id) sender
{
tr_sessionSetPortForwardingEnabled(fHandle, [fDefaults boolForKey: @"NatTraversal"]);
-
+
fNatStatus = -1;
[self updatePortStatus];
}
{
fNatStatus = fwd;
fPeerPort = port;
-
+
[fPortStatusField setStringValue: @""];
[fPortStatusImage setImage: nil];
[fPortStatusProgress startAnimation: self];
-
+
if (fPortChecker)
{
[fPortChecker cancelProbe];
- (NSArray *) sounds
{
NSMutableArray * sounds = [NSMutableArray array];
-
+
NSArray * directories = NSSearchPathForDirectoriesInDomains(NSAllLibrariesDirectory, NSUserDomainMask | NSLocalDomainMask | NSSystemDomainMask, YES);
-
+
for (NSString * directory in directories)
{
directory = [directory stringByAppendingPathComponent: @"Sounds"];
-
+
BOOL isDirectory;
if ([[NSFileManager defaultManager] fileExistsAtPath: directory isDirectory: &isDirectory] && isDirectory)
{
}
}
}
-
+
return sounds;
}
- (void) setEncryptionMode: (id) sender
{
- const tr_encryption_mode mode = [fDefaults boolForKey: @"EncryptionPrefer"] ?
+ const tr_encryption_mode mode = [fDefaults boolForKey: @"EncryptionPrefer"] ?
([fDefaults boolForKey: @"EncryptionRequire"] ? TR_ENCRYPTION_REQUIRED : TR_ENCRYPTION_PREFERRED) : TR_CLEAR_PREFERRED;
tr_sessionSetEncryption(fHandle, mode);
}
- (void) setBlocklistEnabled: (id) sender
{
tr_blocklistSetEnabled(fHandle, [fDefaults boolForKey: @"BlocklistNew"]);
-
+
[[BlocklistScheduler scheduler] updateSchedule];
-
+
[self updateBlocklistButton];
}
- (void) updateBlocklistFields
{
const BOOL exists = tr_blocklistExists(fHandle);
-
+
if (exists)
{
NSString * countString = [NSString formattedUInteger: tr_blocklistGetRuleCount(fHandle)];
[fBlocklistMessageField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@ IP address rules in list",
"Prefs -> blocklist -> message"), countString]];
}
- else
+ else
[fBlocklistMessageField setStringValue: NSLocalizedString(@"A blocklist must first be downloaded",
"Prefs -> blocklist -> message")];
-
+
NSString * updatedDateString;
if (exists)
{
NSDate * updatedDate = [fDefaults objectForKey: @"BlocklistNewLastUpdateSuccess"];
-
+
if (updatedDate)
updatedDateString = [NSDateFormatter localizedStringFromDate: updatedDate dateStyle: NSDateFormatterFullStyle timeStyle: NSDateFormatterShortStyle];
else
}
else
updatedDateString = NSLocalizedString(@"Never", "Prefs -> blocklist -> message");
-
+
[fBlocklistDateField setStringValue: [NSString stringWithFormat: @"%@: %@",
NSLocalizedString(@"Last updated", "Prefs -> blocklist -> message"), updatedDateString]];
}
- (void) updateBlocklistURLField
{
NSString * blocklistString = [fBlocklistURLField stringValue];
-
+
[fDefaults setObject: blocklistString forKey: @"BlocklistURL"];
tr_blocklistSetURL(fHandle, [blocklistString UTF8String]);
-
+
[self updateBlocklistButton];
}
{
tr_sessionLimitSpeed(fHandle, TR_UP, [fDefaults boolForKey: @"CheckUpload"]);
tr_sessionSetSpeedLimit_KBps(fHandle, TR_UP, [fDefaults integerForKey: @"UploadLimit"]);
-
+
tr_sessionLimitSpeed(fHandle, TR_DOWN, [fDefaults boolForKey: @"CheckDownload"]);
tr_sessionSetSpeedLimit_KBps(fHandle, TR_DOWN, [fDefaults integerForKey: @"DownloadLimit"]);
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"SpeedLimitUpdate" object: nil];
}
{
tr_sessionSetAltSpeed_KBps(fHandle, TR_UP, [fDefaults integerForKey: @"SpeedLimitUploadLimit"]);
tr_sessionSetAltSpeed_KBps(fHandle, TR_DOWN, [fDefaults integerForKey: @"SpeedLimitDownloadLimit"]);
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"SpeedLimitUpdate" object: nil];
}
{
tr_sessionSetRatioLimited(fHandle, [fDefaults boolForKey: @"RatioCheck"]);
tr_sessionSetRatioLimit(fHandle, [fDefaults floatForKey: @"RatioLimit"]);
-
+
//reload main table for seeding progress
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
-
+
//reload global settings in inspector
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGlobalOptions" object: nil];
}
- (void) setRatioStop: (id) sender
{
[fDefaults setFloat: [sender floatValue] forKey: @"RatioLimit"];
-
+
[self applyRatioSetting: nil];
}
- (void) updateRatioStopFieldOld
{
[self updateRatioStopField];
-
+
[self applyRatioSetting: nil];
}
{
tr_sessionSetIdleLimited(fHandle, [fDefaults boolForKey: @"IdleLimitCheck"]);
tr_sessionSetIdleLimit(fHandle, [fDefaults integerForKey: @"IdleLimitMinutes"]);
-
+
//reload main table for remaining seeding time
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
-
+
//reload global settings in inspector
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGlobalOptions" object: nil];
}
- (void) setIdleStop: (id) sender
{
[fDefaults setInteger: [sender integerValue] forKey: @"IdleLimitMinutes"];
-
+
[self applyIdleStopSetting: nil];
}
{
if (!fHasLoaded)
return;
-
+
[fUploadField setIntValue: [fDefaults integerForKey: @"UploadLimit"]];
[fDownloadField setIntValue: [fDefaults integerForKey: @"DownloadLimit"]];
}
NSDateComponents * comps = [[[NSDateComponents alloc] init] autorelease];
[comps setHour: sum / 60];
[comps setMinute: sum % 60];
-
+
return [[NSCalendar currentCalendar] dateFromComponents: comps];
}
{
[fInitialString release];
fInitialString = [[control stringValue] retain];
-
+
return YES;
}
//let's just do both - easier that way
tr_sessionSetQueueEnabled(fHandle, TR_DOWN, [fDefaults boolForKey: @"Queue"]);
tr_sessionSetQueueEnabled(fHandle, TR_UP, [fDefaults boolForKey: @"QueueSeed"]);
-
+
//handle if any transfers switch from queued to paused
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateQueue" object: self];
}
{
const NSInteger number = [sender intValue];
const BOOL seed = sender == fQueueSeedField;
-
+
[fDefaults setInteger: number forKey: seed ? @"QueueSeedNumber" : @"QueueDownloadNumber"];
-
+
tr_sessionSetQueueSize(fHandle, seed ? TR_UP : TR_DOWN, number);
}
- (void) setStalled: (id) sender
{
tr_sessionSetQueueStalledEnabled(fHandle, [fDefaults boolForKey: @"CheckStalled"]);
-
+
//reload main table for stalled status
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
}
const NSInteger min = [sender intValue];
[fDefaults setInteger: min forKey: @"StalledMinutes"];
tr_sessionSetQueueStalledMinutes(fHandle, min);
-
+
//reload main table for stalled status
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: self];
}
[panel setCanChooseFiles: NO];
[panel setCanChooseDirectories: YES];
[panel setCanCreateDirectories: YES];
-
+
[panel beginSheetModalForWindow: [self window] completionHandler: ^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton)
{
[fFolderPopUp selectItemAtIndex: DOWNLOAD_FOLDER];
-
+
NSString * folder = [[[panel URLs] objectAtIndex: 0] path];
[fDefaults setObject: folder forKey: @"DownloadFolder"];
[fDefaults setBool: YES forKey: @"DownloadLocationConstant"];
[self updateShowAddMagnetWindowField];
-
+
assert(folder.length > 0);
tr_sessionSetDownloadDir(fHandle, [folder fileSystemRepresentation]);
}
[panel setCanChooseFiles: NO];
[panel setCanChooseDirectories: YES];
[panel setCanCreateDirectories: YES];
-
+
[panel beginSheetModalForWindow: [self window] completionHandler: ^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton)
{
NSString * folder = [[[panel URLs] objectAtIndex: 0] path];
[fDefaults setObject: folder forKey: @"IncompleteDownloadFolder"];
-
+
assert(folder.length > 0);
tr_sessionSetIncompleteDir(fHandle, [folder fileSystemRepresentation]);
}
- (void) doneScriptSheetShow:(id)sender
{
NSOpenPanel * panel = [NSOpenPanel openPanel];
-
+
[panel setPrompt: NSLocalizedString(@"Select", "Preferences -> Open panel prompt")];
[panel setAllowsMultipleSelection: NO];
[panel setCanChooseFiles: YES];
[panel setCanChooseDirectories: NO];
[panel setCanCreateDirectories: NO];
-
+
[panel beginSheetModalForWindow: [self window] completionHandler: ^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton)
{
NSString * filePath = [[[panel URLs] objectAtIndex: 0] path];
-
+
assert(filePath.length > 0);
-
+
[fDefaults setObject: filePath forKey: @"DoneScriptPath"];
tr_sessionSetTorrentDoneScript(fHandle, [filePath fileSystemRepresentation]);
-
+
[fDefaults setBool: YES forKey: @"DoneScriptEnabled"];
tr_sessionSetTorrentDoneScriptEnabled(fHandle, YES);
}
}
else
[watcherQueue removeAllPaths];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"AutoImportSettingChange" object: self];
}
else
{
VDKQueue * watcherQueue = [(Controller *)[NSApp delegate] fileWatcherQueue];
[watcherQueue removeAllPaths];
-
+
NSString * path = [[[panel URLs] objectAtIndex: 0] path];
[fDefaults setObject: path forKey: @"AutoImportDirectory"];
[watcherQueue addPath: [path stringByExpandingTildeInPath] notifyingAbout: VDKQueueNotifyAboutWrite];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"AutoImportSettingChange" object: self];
}
else
if (!path)
[fDefaults setBool: NO forKey: @"AutoImport"];
}
-
+
[fImportFolderPopUp selectItemAtIndex: 0];
}];
}
{
BOOL enable = [fDefaults boolForKey: @"RPC"];
tr_sessionSetRPCEnabled(fHandle, enable);
-
+
[self setRPCWebUIDiscovery: nil];
}
{
[fRPCPassword release];
fRPCPassword = [[sender stringValue] copy];
-
+
const char * password = [[sender stringValue] UTF8String];
[self setKeychainPassword: password forService: RPC_KEYCHAIN_SERVICE username: RPC_KEYCHAIN_NAME];
-
+
tr_sessionSetRPCPassword(fHandle, password);
}
const char * password = nil;
SecKeychainFindGenericPassword(NULL, strlen(RPC_KEYCHAIN_SERVICE), RPC_KEYCHAIN_SERVICE,
strlen(RPC_KEYCHAIN_NAME), RPC_KEYCHAIN_NAME, &passwordLength, (void **)&password, NULL);
-
+
[fRPCPassword release];
if (password != NULL)
{
strncpy(fullPassword, password, passwordLength);
fullPassword[passwordLength] = '\0';
SecKeychainItemFreeContent(NULL, (void *)password);
-
+
tr_sessionSetRPCPassword(fHandle, fullPassword);
-
+
fRPCPassword = [[NSString alloc] initWithUTF8String: fullPassword];
[fRPCPasswordField setStringValue: fRPCPassword];
}
int port = [sender intValue];
[fDefaults setInteger: port forKey: @"RPCPort"];
tr_sessionSetRPCPort(fHandle, port);
-
+
[self setRPCWebUIDiscovery: nil];
}
//don't allow add/remove when currently adding - it leads to weird results
if ([fRPCWhitelistTable editedRow] != -1)
return;
-
+
if ([[sender cell] tagForSegment: [sender selectedSegment]] == RPC_IP_REMOVE_TAG)
{
[fRPCWhitelistArray removeObjectsAtIndexes: [fRPCWhitelistTable selectedRowIndexes]];
[fRPCWhitelistTable deselectAll: self];
[fRPCWhitelistTable reloadData];
-
+
[fDefaults setObject: fRPCWhitelistArray forKey: @"RPCWhitelist"];
[self updateRPCWhitelist];
}
{
[fRPCWhitelistArray addObject: @""];
[fRPCWhitelistTable reloadData];
-
+
const int row = [fRPCWhitelistArray count] - 1;
[fRPCWhitelistTable selectRowIndexes: [NSIndexSet indexSetWithIndex: row] byExtendingSelection: NO];
[fRPCWhitelistTable editColumn: 0 row: row withEvent: nil select: YES];
{
NSArray * components = [object componentsSeparatedByString: @"."];
NSMutableArray * newComponents = [NSMutableArray arrayWithCapacity: 4];
-
+
//create better-formatted ip string
BOOL valid = false;
if ([components count] == 4)
}
}
}
-
+
NSString * newIP;
if (valid)
{
newIP = [newComponents componentsJoinedByString: @"."];
-
+
//don't allow the same ip address
if ([fRPCWhitelistArray containsObject: newIP] && ![[fRPCWhitelistArray objectAtIndex: row] isEqualToString: newIP])
valid = false;
}
-
+
if (valid)
{
[fRPCWhitelistArray replaceObjectAtIndex: row withObject: newIP];
if ([[fRPCWhitelistArray objectAtIndex: row] isEqualToString: @""])
[fRPCWhitelistArray removeObjectAtIndex: row];
}
-
+
[fRPCWhitelistTable deselectAll: self];
[fRPCWhitelistTable reloadData];
-
+
[fDefaults setObject: fRPCWhitelistArray forKey: @"RPCWhitelist"];
[self updateRPCWhitelist];
}
const tr_encryption_mode encryptionMode = tr_sessionGetEncryption(fHandle);
[fDefaults setBool: encryptionMode != TR_CLEAR_PREFERRED forKey: @"EncryptionPrefer"];
[fDefaults setBool: encryptionMode == TR_ENCRYPTION_REQUIRED forKey: @"EncryptionRequire"];
-
+
//download directory
NSString * downloadLocation = [[NSString stringWithUTF8String: tr_sessionGetDownloadDir(fHandle)] stringByStandardizingPath];
[fDefaults setObject: downloadLocation forKey: @"DownloadFolder"];
-
+
NSString * incompleteLocation = [[NSString stringWithUTF8String: tr_sessionGetIncompleteDir(fHandle)] stringByStandardizingPath];
[fDefaults setObject: incompleteLocation forKey: @"IncompleteDownloadFolder"];
-
+
const BOOL useIncomplete = tr_sessionIsIncompleteDirEnabled(fHandle);
[fDefaults setBool: useIncomplete forKey: @"UseIncompleteDownloadFolder"];
-
+
const BOOL usePartialFileRanaming = tr_sessionIsIncompleteFileNamingEnabled(fHandle);
[fDefaults setBool: usePartialFileRanaming forKey: @"RenamePartialFiles"];
-
+
//utp
const BOOL utp = tr_sessionIsUTPEnabled(fHandle);
[fDefaults setBool: utp forKey: @"UTPGlobal"];
-
+
//peers
const uint16_t peersTotal = tr_sessionGetPeerLimit(fHandle);
[fDefaults setInteger: peersTotal forKey: @"PeersTotal"];
-
+
const uint16_t peersTorrent = tr_sessionGetPeerLimitPerTorrent(fHandle);
[fDefaults setInteger: peersTorrent forKey: @"PeersTorrent"];
-
+
//pex
const BOOL pex = tr_sessionIsPexEnabled(fHandle);
[fDefaults setBool: pex forKey: @"PEXGlobal"];
-
+
//dht
const BOOL dht = tr_sessionIsDHTEnabled(fHandle);
[fDefaults setBool: dht forKey: @"DHTGlobal"];
-
+
//lpd
const BOOL lpd = tr_sessionIsLPDEnabled(fHandle);
[fDefaults setBool: lpd forKey: @"LocalPeerDiscoveryGlobal"];
-
+
//auto start
const BOOL autoStart = !tr_sessionGetPaused(fHandle);
[fDefaults setBool: autoStart forKey: @"AutoStartDownload"];
-
+
//port
const tr_port port = tr_sessionGetPeerPort(fHandle);
[fDefaults setInteger: port forKey: @"BindPort"];
-
+
const BOOL nat = tr_sessionIsPortForwardingEnabled(fHandle);
[fDefaults setBool: nat forKey: @"NatTraversal"];
-
+
fPeerPort = -1;
fNatStatus = -1;
[self updatePortStatus];
-
+
const BOOL randomPort = tr_sessionGetPeerPortRandomOnStart(fHandle);
[fDefaults setBool: randomPort forKey: @"RandomPort"];
-
+
//speed limit - down
const BOOL downLimitEnabled = tr_sessionIsSpeedLimited(fHandle, TR_DOWN);
[fDefaults setBool: downLimitEnabled forKey: @"CheckDownload"];
-
+
const int downLimit = tr_sessionGetSpeedLimit_KBps(fHandle, TR_DOWN);
[fDefaults setInteger: downLimit forKey: @"DownloadLimit"];
-
+
//speed limit - up
const BOOL upLimitEnabled = tr_sessionIsSpeedLimited(fHandle, TR_UP);
[fDefaults setBool: upLimitEnabled forKey: @"CheckUpload"];
-
+
const int upLimit = tr_sessionGetSpeedLimit_KBps(fHandle, TR_UP);
[fDefaults setInteger: upLimit forKey: @"UploadLimit"];
-
+
//alt speed limit enabled
const BOOL useAltSpeed = tr_sessionUsesAltSpeed(fHandle);
[fDefaults setBool: useAltSpeed forKey: @"SpeedLimit"];
-
+
//alt speed limit - down
const int downLimitAlt = tr_sessionGetAltSpeed_KBps(fHandle, TR_DOWN);
[fDefaults setInteger: downLimitAlt forKey: @"SpeedLimitDownloadLimit"];
-
+
//alt speed limit - up
const int upLimitAlt = tr_sessionGetAltSpeed_KBps(fHandle, TR_UP);
[fDefaults setInteger: upLimitAlt forKey: @"SpeedLimitUploadLimit"];
-
+
//alt speed limit schedule
const BOOL useAltSpeedSched = tr_sessionUsesAltSpeedTime(fHandle);
[fDefaults setBool: useAltSpeedSched forKey: @"SpeedLimitAuto"];
-
+
NSDate * limitStartDate = [PrefsController timeSumToDate: tr_sessionGetAltSpeedBegin(fHandle)];
[fDefaults setObject: limitStartDate forKey: @"SpeedLimitAutoOnDate"];
-
+
NSDate * limitEndDate = [PrefsController timeSumToDate: tr_sessionGetAltSpeedEnd(fHandle)];
[fDefaults setObject: limitEndDate forKey: @"SpeedLimitAutoOffDate"];
-
+
const int limitDay = tr_sessionGetAltSpeedDay(fHandle);
[fDefaults setInteger: limitDay forKey: @"SpeedLimitAutoDay"];
-
+
//blocklist
const BOOL blocklist = tr_blocklistIsEnabled(fHandle);
[fDefaults setBool: blocklist forKey: @"BlocklistNew"];
-
+
NSString * blocklistURL = [NSString stringWithUTF8String: tr_blocklistGetURL(fHandle)];
[fDefaults setObject: blocklistURL forKey: @"BlocklistURL"];
-
+
//seed ratio
const BOOL ratioLimited = tr_sessionIsRatioLimited(fHandle);
[fDefaults setBool: ratioLimited forKey: @"RatioCheck"];
-
+
const float ratioLimit = tr_sessionGetRatioLimit(fHandle);
[fDefaults setFloat: ratioLimit forKey: @"RatioLimit"];
-
+
//idle seed limit
const BOOL idleLimited = tr_sessionIsIdleLimited(fHandle);
[fDefaults setBool: idleLimited forKey: @"IdleLimitCheck"];
-
+
const NSUInteger idleLimitMin = tr_sessionGetIdleLimit(fHandle);
[fDefaults setInteger: idleLimitMin forKey: @"IdleLimitMinutes"];
-
+
//queue
const BOOL downloadQueue = tr_sessionGetQueueEnabled(fHandle, TR_DOWN);
[fDefaults setBool: downloadQueue forKey: @"Queue"];
-
+
const int downloadQueueNum = tr_sessionGetQueueSize(fHandle, TR_DOWN);
[fDefaults setInteger: downloadQueueNum forKey: @"QueueDownloadNumber"];
-
+
const BOOL seedQueue = tr_sessionGetQueueEnabled(fHandle, TR_UP);
[fDefaults setBool: seedQueue forKey: @"QueueSeed"];
-
+
const int seedQueueNum = tr_sessionGetQueueSize(fHandle, TR_UP);
[fDefaults setInteger: seedQueueNum forKey: @"QueueSeedNumber"];
-
+
const BOOL checkStalled = tr_sessionGetQueueStalledEnabled(fHandle);
[fDefaults setBool: checkStalled forKey: @"CheckStalled"];
-
+
const int stalledMinutes = tr_sessionGetQueueStalledMinutes(fHandle);
[fDefaults setInteger: stalledMinutes forKey: @"StalledMinutes"];
-
+
//done script
const BOOL doneScriptEnabled = tr_sessionIsTorrentDoneScriptEnabled(fHandle);
[fDefaults setBool: doneScriptEnabled forKey: @"DoneScriptEnabled"];
-
+
NSString * doneScriptPath = [NSString stringWithUTF8String: tr_sessionGetTorrentDoneScript(fHandle)];
[fDefaults setObject: doneScriptPath forKey: @"DoneScriptPath"];
-
+
//update gui if loaded
if (fHasLoaded)
{
//encryption handled by bindings
-
+
//download directory handled by bindings
-
+
//utp handled by bindings
-
+
[fPeersGlobalField setIntValue: peersTotal];
[fPeersTorrentField setIntValue: peersTorrent];
-
+
//pex handled by bindings
-
+
//dht handled by bindings
-
+
//lpd handled by bindings
-
+
[fPortField setIntValue: port];
//port forwarding (nat) handled by bindings
//random port handled by bindings
-
+
//limit check handled by bindings
[fDownloadField setIntValue: downLimit];
-
+
//limit check handled by bindings
[fUploadField setIntValue: upLimit];
-
+
[fSpeedLimitDownloadField setIntValue: downLimitAlt];
-
+
[fSpeedLimitUploadField setIntValue: upLimitAlt];
-
+
//speed limit schedule handled by bindings
-
+
//speed limit schedule times and day handled by bindings
-
+
[fBlocklistURLField setStringValue: blocklistURL];
[self updateBlocklistButton];
[self updateBlocklistFields];
-
+
//ratio limit enabled handled by bindings
[fRatioStopField setFloatValue: ratioLimit];
-
+
//idle limit enabled handled by bindings
[fIdleStopField setIntegerValue: idleLimitMin];
-
+
//queues enabled handled by bindings
[fQueueDownloadField setIntValue: downloadQueueNum];
[fQueueSeedField setIntValue: seedQueueNum];
-
+
//check stalled handled by bindings
[fStalledField setIntValue: stalledMinutes];
}
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"SpeedLimitUpdate" object: nil];
-
+
//reload global settings in inspector
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateGlobalOptions" object: nil];
}
}
else
identifier = [[NSUserDefaults standardUserDefaults] stringForKey: @"SelectedPrefView"];
-
+
NSView * view;
if ([identifier isEqualToString: TOOLBAR_TRANSFERS])
view = fTransfersView;
identifier = TOOLBAR_GENERAL; //general view is the default selected
view = fGeneralView;
}
-
+
[[[self window] toolbar] setSelectedItemIdentifier: identifier];
-
+
NSWindow * window = [self window];
if ([window contentView] == view)
return;
-
+
NSRect windowRect = [window frame];
const CGFloat difference = NSHeight([view frame]) - NSHeight([[window contentView] frame]);
windowRect.origin.y -= difference;
windowRect.size.height += difference;
-
+
[view setHidden: YES];
[window setContentView: view];
[window setFrame: windowRect display: YES animate: YES];
[view setHidden: NO];
-
+
//set title label
if (sender)
[window setTitle: [sender label]];
{
[fBuiltInGrowlButton setHidden: YES];
[fGrowlAppButton setHidden: NO];
-
+
#warning remove NO
[fGrowlAppButton setEnabled: NO && [GrowlApplicationBridge isGrowlURLSchemeAvailable]];
[fGrowlAppButton setTitle: NSLocalizedString(@"Configure In Growl", "Prefs -> Notifications")];
[fGrowlAppButton sizeToFit];
-
+
[fGrowlAppButton setTarget: self];
[fGrowlAppButton setAction: @selector(openGrowlApp:)];
}
{
[fBuiltInGrowlButton setHidden: YES];
[fGrowlAppButton setHidden: NO];
-
+
[fGrowlAppButton setEnabled: YES];
[fGrowlAppButton setTitle: NSLocalizedString(@"Configure In System Preferences", "Prefs -> Notifications")];
[fGrowlAppButton sizeToFit];
-
+
[fGrowlAppButton setTarget: self];
[fGrowlAppButton setAction: @selector(openNotificationSystemPrefs:)];
}
{
[fBuiltInGrowlButton setHidden: NO];
[fGrowlAppButton setHidden: YES];
-
+
[fBuiltInGrowlButton setState: [fDefaults boolForKey: @"DisplayNotifications"]];
}
}
{
SecKeychainItemRef item = NULL;
NSUInteger passwordLength = strlen(password);
-
+
OSStatus result = SecKeychainFindGenericPassword(NULL, strlen(service), service, strlen(username), username, NULL, NULL, &item);
if (result == noErr && item)
{
+ (NSGradient *) progressGradientForRed: (CGFloat) redComponent green: (CGFloat) greenComponent blue: (CGFloat) blueComponent
{
const CGFloat alpha = [[NSUserDefaults standardUserDefaults] boolForKey: @"SmallView"] ? 0.27 : 1.0;
-
+
NSColor * baseColor = [NSColor colorWithCalibratedRed: redComponent green: greenComponent blue: blueComponent alpha: alpha];
-
+
NSColor * color2 = [NSColor colorWithCalibratedRed: redComponent * 0.95 green: greenComponent * 0.95 blue: blueComponent * 0.95
alpha: alpha];
-
+
NSColor * color3 = [NSColor colorWithCalibratedRed: redComponent * 0.85 green: greenComponent * 0.85 blue: blueComponent * 0.85
alpha: alpha];
-
+
return [[[NSGradient alloc] initWithColorsAndLocations: baseColor, 0.0, color2, 0.5, color3, 0.5, baseColor, 1.0, nil] autorelease];
}
{
NSString * rawFilename = ![fileExtension isEqualToString: @""] ? fileExtension : @"blank_file_name_transmission";
NSString * iconFileName = [NSString stringWithFormat: @"%ldx%@.tiff", width, rawFilename]; //we need to do this once per file extension, per size
-
+
if (![allImgProps objectForKey: iconFileName])
{
NSImage * icon = [[NSWorkspace sharedWorkspace] iconForFileType: fileExtension];
-
+
const NSRect iconFrame = NSMakeRect(0.0, 0.0, width, width);
NSImage * renderedIcon = [[NSImage alloc] initWithSize: iconFrame.size];
[renderedIcon lockFocus];
[icon drawInRect: iconFrame fromRect: NSZeroRect operation: NSCompositeCopy fraction: 1.0];
[renderedIcon unlockFocus];
-
+
NSData * iconData = [renderedIcon TIFFRepresentation];
[renderedIcon release];
-
+
NSDictionary * imgProps = @{
(NSString *)kQLPreviewPropertyMIMETypeKey : @"image/png",
(NSString *)kQLPreviewPropertyAttachmentDataKey : iconData };
[allImgProps setObject: imgProps forKey: iconFileName];
}
-
+
return [@"cid:" stringByAppendingString: iconFileName];
}
// Before proceeding make sure the user didn't cancel the request
if (QLPreviewRequestIsCancelled(preview))
return noErr;
-
+
//we need this call to ensure NSApp is initialized (not done automatically for plugins)
[NSApplication sharedApplication];
-
+
//try to parse the torrent file
tr_info inf;
tr_ctor * ctor = tr_ctorNew(NULL);
tr_ctorFree(ctor);
if (err)
return noErr;
-
+
NSBundle * bundle = [NSBundle bundleWithIdentifier: @"org.m0k.transmission.QuickLookPlugin"];
-
+
NSURL * styleURL = [bundle URLForResource: @"style" withExtension: @"css"];
NSString * styleContents = [NSString stringWithContentsOfURL: styleURL encoding: NSUTF8StringEncoding error: NULL];
-
+
NSMutableString * htmlString = [NSMutableString string];
[htmlString appendFormat: @"<html><style type=\"text/css\">%@</style><body>", styleContents];
-
+
NSMutableDictionary * allImgProps = [NSMutableDictionary dictionary];
-
+
NSString * name = [NSString stringWithUTF8String: inf.name];
NSString * fileTypeString = inf.isFolder ? NSFileTypeForHFSTypeCode(kGenericFolderIcon) : [name pathExtension];
-
+
const NSUInteger width = 32;
[htmlString appendFormat: @"<h2><img class=\"icon\" src=\"%@\" width=\"%ld\" height=\"%ld\" />%@</h2>", generateIconData(fileTypeString, width, allImgProps), width, width, name];
-
+
NSString * fileSizeString = [NSString stringForFileSize: inf.totalSize];
if (inf.isFolder)
{
fileSizeString = [NSString stringWithFormat: @"%@, %@", fileCountString, fileSizeString];
}
[htmlString appendFormat: @"<p>%@</p>", fileSizeString];
-
+
NSString * dateCreatedString = inf.dateCreated > 0 ? [NSDateFormatter localizedStringFromDate: [NSDate dateWithTimeIntervalSince1970: inf.dateCreated] dateStyle: NSDateFormatterLongStyle timeStyle: NSDateFormatterShortStyle] : nil;
NSString * creatorString = inf.creator ? [NSString stringWithUTF8String: inf.creator] : nil;
if ([creatorString isEqualToString: @""]) creatorString = nil;
creationString = [NSString stringWithFormat: NSLocalizedStringFromTableInBundle(@"Created with %@", nil, bundle, "quicklook creation info"), creatorString];
if (creationString)
[htmlString appendFormat: @"<p>%@</p>", creationString];
-
+
if (inf.comment)
{
NSString * comment = [NSString stringWithUTF8String: inf.comment];
if (![comment isEqualToString: @""])
[htmlString appendFormat: @"<p>%@</p>", comment];
}
-
+
NSMutableArray * lists = [NSMutableArray array];
-
+
if (inf.webseedCount > 0)
{
NSMutableString * listSection = [NSMutableString string];
[listSection appendString: @"<table>"];
-
+
NSString * headerTitleString = inf.webseedCount == 1 ? NSLocalizedStringFromTableInBundle(@"1 Web Seed", nil, bundle, "quicklook web seed header") : [NSString stringWithFormat: NSLocalizedStringFromTableInBundle(@"%@ Web Seeds", nil, bundle, "quicklook web seed header"), [NSString formattedUInteger: inf.webseedCount]];
[listSection appendFormat: @"<tr><th>%@</th></tr>", headerTitleString];
-
+
for (int i = 0; i < inf.webseedCount; ++i)
[listSection appendFormat: @"<tr><td>%s<td></tr>", inf.webseeds[i]];
-
+
[listSection appendString:@"</table>"];
-
+
[lists addObject: listSection];
}
-
+
if (inf.trackerCount > 0)
{
NSMutableString * listSection = [NSMutableString string];
[listSection appendString: @"<table>"];
-
+
NSString * headerTitleString = inf.trackerCount == 1 ? NSLocalizedStringFromTableInBundle(@"1 Tracker", nil, bundle, "quicklook tracker header") : [NSString stringWithFormat: NSLocalizedStringFromTableInBundle(@"%@ Trackers", nil, bundle, "quicklook tracker header"), [NSString formattedUInteger: inf.trackerCount]];
[listSection appendFormat: @"<tr><th>%@</th></tr>", headerTitleString];
-
+
#warning handle tiers?
for (int i = 0; i < inf.trackerCount; ++i)
[listSection appendFormat: @"<tr><td>%s<td></tr>", inf.trackers[i].announce];
-
+
[listSection appendString:@"</table>"];
-
+
[lists addObject: listSection];
}
-
+
if (inf.isFolder)
{
NSMutableString * listSection = [NSMutableString string];
[listSection appendString: @"<table>"];
-
+
NSString * fileTitleString = inf.fileCount == 1 ? NSLocalizedStringFromTableInBundle(@"1 File", nil, bundle, "quicklook file header") : [NSString stringWithFormat: NSLocalizedStringFromTableInBundle(@"%@ Files", nil, bundle, "quicklook file header"), [NSString formattedUInteger: inf.fileCount]];
[listSection appendFormat: @"<tr><th>%@</th></tr>", fileTitleString];
-
+
#warning display size?
#warning display folders?
for (int i = 0; i < inf.fileCount; ++i)
{
NSString * fullFilePath = [NSString stringWithUTF8String: inf.files[i].name];
NSCAssert([fullFilePath hasPrefix: [name stringByAppendingString: @"/"]], @"Expected file path %@ to begin with %@/", fullFilePath, name);
-
+
NSString * shortenedFilePath = [fullFilePath substringFromIndex: [name length]+1];
-
+
const NSUInteger width = 16;
[listSection appendFormat: @"<tr><td><img class=\"icon\" src=\"%@\" width=\"%ld\" height=\"%ld\" />%@<td></tr>", generateIconData([shortenedFilePath pathExtension], width, allImgProps), width, width, shortenedFilePath];
}
-
+
[listSection appendString:@"</table>"];
-
+
[lists addObject: listSection];
}
-
+
if ([lists count] > 0)
[htmlString appendFormat: @"<hr/><br>%@", [lists componentsJoinedByString: @"<br>"]];
-
+
[htmlString appendString: @"</body></html>"];
-
+
tr_metainfoFree(&inf);
-
+
NSDictionary * props = @{ (NSString *)kQLPreviewPropertyTextEncodingNameKey : @"UTF-8",
(NSString *)kQLPreviewPropertyMIMETypeKey : @"text/html",
(NSString *)kQLPreviewPropertyAttachmentsKey : allImgProps };
-
+
QLPreviewRequestSetDataRepresentation(preview, (CFDataRef)[htmlString dataUsingEncoding: NSUTF8StringEncoding], kUTTypeHTML, (CFDictionaryRef)props);
-
+
return noErr;
}
{
NSMenuItem * menuItem = [[NSMenuItem alloc] initWithTitle: [self label] action: nil keyEquivalent: @""];
[menuItem setEnabled: [[self target] validateToolbarItem: self]];
-
+
if ([menuItem isEnabled]) {
NSMenu *servicesMenu = [[NSMenu alloc] initWithTitle: @""];
for (NSMenuItem * item in [[ShareTorrentFileHelper sharedHelper] menuItems])
{
[servicesMenu addItem:item];
}
-
+
[menuItem setSubmenu:servicesMenu];
[servicesMenu release]; // can't believe we're not using ARC yet!
}
-
+
return menuItem;
}
[items addObject: item];
[item release];
}
-
+
return items;
}
- (void) awakeFromNib
{
[self updateStats];
-
+
fTimer = [[NSTimer scheduledTimerWithTimeInterval: UPDATE_SECONDS target: self selector: @selector(updateStats) userInfo: nil repeats: YES] retain];
[[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSModalPanelRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSEventTrackingRunLoopMode];
[[self window] setRestorationClass: [self class]];
-
+
[[self window] setTitle: NSLocalizedString(@"Statistics", "Stats window -> title")];
-
+
//set label text
[fUploadedLabelField setStringValue: [NSLocalizedString(@"Uploaded", "Stats window -> label") stringByAppendingString: @":"]];
[fDownloadedLabelField setStringValue: [NSLocalizedString(@"Downloaded", "Stats window -> label") stringByAppendingString: @":"]];
[fRatioLabelField setStringValue: [NSLocalizedString(@"Ratio", "Stats window -> label") stringByAppendingString: @":"]];
[fTimeLabelField setStringValue: [NSLocalizedString(@"Running Time", "Stats window -> label") stringByAppendingString: @":"]];
[fNumOpenedLabelField setStringValue: [NSLocalizedString(@"Program Started", "Stats window -> label") stringByAppendingString: @":"]];
-
+
//size of all labels
const CGFloat oldWidth = [fUploadedLabelField frame].size.width;
-
+
NSArray * labels = @[fUploadedLabelField, fDownloadedLabelField, fRatioLabelField, fTimeLabelField, fNumOpenedLabelField];
-
+
CGFloat maxWidth = CGFLOAT_MIN;
for (NSTextField * label in labels)
{
[label sizeToFit];
-
+
const CGFloat width = [label frame].size.width;
maxWidth = MAX(maxWidth, width);
}
-
+
for (NSTextField * label in labels)
{
NSRect frame = [label frame];
frame.size.width = maxWidth;
[label setFrame: frame];
}
-
+
//resize window for new label width - fields are set in nib to adjust correctly
NSRect windowRect = [[self window] frame];
windowRect.size.width += maxWidth - oldWidth;
[[self window] setFrame: windowRect display: YES];
-
+
//resize reset button
const CGFloat oldButtonWidth = [fResetButton frame].size.width;
-
+
[fResetButton setTitle: NSLocalizedString(@"Reset", "Stats window -> reset button")];
[fResetButton sizeToFit];
-
+
NSRect buttonFrame = [fResetButton frame];
buttonFrame.size.width += 10.0;
buttonFrame.origin.x -= buttonFrame.size.width - oldButtonWidth;
[fTimer invalidate];
[fTimer release];
fTimer = nil;
-
+
[fStatsWindowInstance autorelease];
fStatsWindowInstance = nil;
}
+ (void) restoreWindowWithIdentifier: (NSString *) identifier state: (NSCoder *) state completionHandler: (void (^)(NSWindow *, NSError *)) completionHandler
{
NSAssert1([identifier isEqualToString: @"StatsWindow"], @"Trying to restore unexpected identifier %@", identifier);
-
+
completionHandler([[StatsWindowController statsWindow] window], nil);
}
[self performResetStats];
return;
}
-
+
NSAlert * alert = [[NSAlert alloc] init];
[alert setMessageText: NSLocalizedString(@"Are you sure you want to reset usage statistics?", "Stats reset -> title")];
[alert setInformativeText: NSLocalizedString(@"This will clear the global statistics displayed by Transmission."
[alert addButtonWithTitle: NSLocalizedString(@"Reset", "Stats reset -> button")];
[alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Stats reset -> button")];
[alert setShowsSuppressionButton: YES];
-
+
[alert beginSheetModalForWindow: [self window] modalDelegate: self
didEndSelector: @selector(resetSheetClosed:returnCode:contextInfo:) contextInfo: nil];
}
tr_session_stats statsAll, statsSession;
tr_sessionGetCumulativeStats(fLib, &statsAll);
tr_sessionGetStats(fLib, &statsSession);
-
+
NSByteCountFormatter * byteFormatter = nil;
if ([NSApp isOnMountainLionOrBetter])
{
byteFormatter = [[NSByteCountFormatterMtLion alloc] init];
[byteFormatter setAllowedUnits: NSByteCountFormatterUseBytes];
}
-
+
[fUploadedField setStringValue: [NSString stringForFileSize: statsSession.uploadedBytes]];
[fUploadedField setToolTip: [NSApp isOnMountainLionOrBetter] ? [byteFormatter stringFromByteCount: statsSession.uploadedBytes] : [NSString stringWithFormat: NSLocalizedString(@"%@ bytes", "stats -> bytes"), [NSString formattedUInteger: statsSession.uploadedBytes]]];
[fUploadedAllField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@ total", "stats total"), [NSString stringForFileSize: statsAll.uploadedBytes]]];
[fUploadedAllField setToolTip: [NSApp isOnMountainLionOrBetter] ? [byteFormatter stringFromByteCount: statsAll.uploadedBytes] : [NSString stringWithFormat: NSLocalizedString(@"%@ bytes", "stats -> bytes"), [NSString formattedUInteger: statsAll.uploadedBytes]]];
-
+
[fDownloadedField setStringValue: [NSString stringForFileSize: statsSession.downloadedBytes]];
[fDownloadedField setToolTip: [NSApp isOnMountainLionOrBetter] ? [byteFormatter stringFromByteCount: statsSession.downloadedBytes] : [NSString stringWithFormat: NSLocalizedString(@"%@ bytes", "stats -> bytes"), [NSString formattedUInteger: statsSession.downloadedBytes]]];
[fDownloadedAllField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@ total", "stats total"), [NSString stringForFileSize: statsAll.downloadedBytes]]];
[fDownloadedAllField setToolTip: [NSApp isOnMountainLionOrBetter] ? [byteFormatter stringFromByteCount: statsAll.downloadedBytes] : [NSString stringWithFormat: NSLocalizedString(@"%@ bytes", "stats -> bytes"), [NSString formattedUInteger: statsAll.downloadedBytes]]];
-
+
[byteFormatter release];
-
+
[fRatioField setStringValue: [NSString stringForRatio: statsSession.ratio]];
-
+
NSString * totalRatioString = statsAll.ratio != TR_RATIO_NA
? [NSString stringWithFormat: NSLocalizedString(@"%@ total", "stats total"), [NSString stringForRatio: statsAll.ratio]]
: NSLocalizedString(@"Total N/A", "stats total");
[fRatioAllField setStringValue: totalRatioString];
-
+
if ([NSApp isOnYosemiteOrBetter]) {
static NSDateComponentsFormatter *timeFormatter;
static dispatch_once_t onceToken;
timeFormatter.maximumUnitCount = 3;
timeFormatter.allowedUnits = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitWeekOfMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute;
});
-
+
[fTimeField setStringValue: [timeFormatter stringFromTimeInterval:statsSession.secondsActive]];
[fTimeAllField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@ total", "stats total"), [timeFormatter stringFromTimeInterval:statsAll.secondsActive]]];
}
[fTimeField setStringValue: [NSString timeString: statsSession.secondsActive includesTimeRemainingPhrase:NO showSeconds: NO]];
[fTimeAllField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@ total", "stats total"), [NSString timeString: statsAll.secondsActive includesTimeRemainingPhrase:NO showSeconds: NO]]];
}
-
+
if (statsAll.sessionCount == 1)
[fNumOpenedField setStringValue: NSLocalizedString(@"1 time", "stats window -> times opened")];
else
- (void) resetSheetClosed: (NSAlert *) alert returnCode: (NSInteger) code contextInfo: (void *) info
{
[[alert window] orderOut: nil];
-
+
if ([[alert suppressionButton] state] == NSOnState)
[[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningResetStats"];
-
+
if (code == NSAlertFirstButtonReturn)
[self performResetStats];
}
IBOutlet NSButton * fStatusButton;
IBOutlet NSTextField * fTotalDLField, * fTotalULField;
IBOutlet NSImageView * fTotalDLImageView, * fTotalULImageView;
-
+
tr_session * fLib;
-
+
CGFloat fPreviousDownloadRate, fPreviousUploadRate;
}
if ((self = [super initWithNibName: @"StatusBar" bundle: nil]))
{
fLib = lib;
-
+
fPreviousDownloadRate = -1.0;
fPreviousUploadRate = -1.0;
}
-
+
return self;
}
"Status Bar -> status menu")];
[[[fStatusButton menu] itemWithTag: STATUS_TRANSFER_SESSION_TAG] setTitle: NSLocalizedString(@"Session Transfer",
"Status Bar -> status menu")];
-
+
[[fStatusButton cell] setBackgroundStyle: NSBackgroundStyleRaised];
[[fTotalDLField cell] setBackgroundStyle: NSBackgroundStyleRaised];
[[fTotalULField cell] setBackgroundStyle: NSBackgroundStyleRaised];
[[fTotalDLImageView cell] setBackgroundStyle: NSBackgroundStyleRaised];
[[fTotalULImageView cell] setBackgroundStyle: NSBackgroundStyleRaised];
-
+
[self updateSpeedFieldsToolTips];
-
+
//update when speed limits are changed
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(updateSpeedFieldsToolTips)
name: @"SpeedLimitUpdate" object: nil];
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
-
+
[super dealloc];
}
[fTotalDLField setStringValue: [NSString stringForSpeed: dlRate]];
fPreviousDownloadRate = dlRate;
}
-
+
if (ulRate != fPreviousUploadRate)
{
[fTotalULField setStringValue: [NSString stringForSpeed: ulRate]];
fPreviousUploadRate = ulRate;
}
-
+
//set status button text
NSString * statusLabel = [[NSUserDefaults standardUserDefaults] stringForKey: @"StatusLabel"], * statusString;
BOOL total;
tr_sessionGetCumulativeStats(fLib, &stats);
else
tr_sessionGetStats(fLib, &stats);
-
+
statusString = [NSLocalizedString(@"Ratio", "status bar -> status label") stringByAppendingFormat: @": %@",
[NSString stringForRatio: stats.ratio]];
}
else //STATUS_TRANSFER_TOTAL or STATUS_TRANSFER_SESSION
{
total = [statusLabel isEqualToString: STATUS_TRANSFER_TOTAL];
-
+
tr_session_stats stats;
if (total)
tr_sessionGetCumulativeStats(fLib, &stats);
else
tr_sessionGetStats(fLib, &stats);
-
+
statusString = [NSString stringWithFormat: @"%@: %@ %@: %@",
NSLocalizedString(@"DL", "status bar -> status label"), [NSString stringForFileSize: stats.downloadedBytes],
NSLocalizedString(@"UL", "status bar -> status label"), [NSString stringForFileSize: stats.uploadedBytes]];
}
-
-
+
+
if (![[fStatusButton title] isEqualToString: statusString])
{
[fStatusButton setTitle: statusString];
NSAssert1(NO, @"Unknown status label tag received: %ld", [sender tag]);
return;
}
-
+
[[NSUserDefaults standardUserDefaults] setObject: statusLabel forKey: @"StatusLabel"];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
}
- (void) updateSpeedFieldsToolTips
{
NSString * uploadText, * downloadText;
-
+
if ([[NSUserDefaults standardUserDefaults] boolForKey: @"SpeedLimit"])
{
NSString * speedString = [NSString stringWithFormat: @"%@ (%@)", NSLocalizedString(@"%d KB/s", "Status Bar -> speed tooltip"),
NSLocalizedString(@"Speed Limit", "Status Bar -> speed tooltip")];
-
+
uploadText = [NSString stringWithFormat: speedString,
[[NSUserDefaults standardUserDefaults] integerForKey: @"SpeedLimitUploadLimit"]];
downloadText = [NSString stringWithFormat: speedString,
[[NSUserDefaults standardUserDefaults] integerForKey: @"UploadLimit"]];
else
uploadText = NSLocalizedString(@"unlimited", "Status Bar -> speed tooltip");
-
+
if ([[NSUserDefaults standardUserDefaults] boolForKey: @"CheckDownload"])
downloadText = [NSString stringWithFormat: NSLocalizedString(@"%d KB/s", "Status Bar -> speed tooltip"),
[[NSUserDefaults standardUserDefaults] integerForKey: @"DownloadLimit"]];
else
downloadText = NSLocalizedString(@"unlimited", "Status Bar -> speed tooltip");
}
-
+
uploadText = [NSLocalizedString(@"Global upload limit", "Status Bar -> speed tooltip")
stringByAppendingFormat: @": %@", uploadText];
downloadText = [NSLocalizedString(@"Global download limit", "Status Bar -> speed tooltip")
stringByAppendingFormat: @": %@", downloadText];
-
+
[fTotalULField setToolTip: uploadText];
[fTotalDLField setToolTip: downloadText];
}
- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
{
const SEL action = [menuItem action];
-
+
//enable sort options
if (action == @selector(setStatusLabel:))
{
NSAssert1(NO, @"Unknown status label tag received: %ld", [menuItem tag]);
statusLabel = STATUS_RATIO_TOTAL;
}
-
+
[menuItem setState: [statusLabel isEqualToString: [[NSUserDefaults standardUserDefaults] stringForKey: @"StatusLabel"]]
? NSOnState : NSOffState];
return YES;
}
-
+
return YES;
}
- (void) resizeStatusButton
{
[fStatusButton sizeToFit];
-
+
//width ends up being too long
NSRect statusFrame = [fStatusButton frame];
statusFrame.size.width -= 25.0;
-
+
const CGFloat difference = NSMaxX(statusFrame) + 5.0 - NSMinX([fTotalDLImageView frame]);
if (difference > 0.0)
statusFrame.size.width -= difference;
-
+
[fStatusButton setFrame: statusFrame];
}
NSColor * lightColor = [NSColor colorWithCalibratedRed: 160.0/255.0 green: 160.0/255.0 blue: 160.0/255.0 alpha: 1.0];
NSColor * darkColor = [NSColor colorWithCalibratedRed: 155.0/255.0 green: 155.0/255.0 blue: 155.0/255.0 alpha: 1.0];
fGradient = [[NSGradient alloc] initWithStartingColor: lightColor endingColor: darkColor];
-
+
if (![NSApp isOnYosemiteOrBetter])
{
CIFilter * randomFilter = [CIFilter filterWithName: @"CIRandomGenerator"];
[randomFilter setDefaults];
-
+
fNoiseImage = [randomFilter valueForKey: @"outputImage"];
-
+
CIFilter * monochromeFilter = [CIFilter filterWithName: @"CIColorMonochrome"];
[monochromeFilter setDefaults];
[monochromeFilter setValue: fNoiseImage forKey: @"inputImage"];
}
else
fNoiseImage = nil;
-
+
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(reload) name: NSWindowDidBecomeMainNotification object: [self window]];
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(reload) name: NSWindowDidResignMainNotification object: [self window]];
}
if ([NSApp isOnYosemiteOrBetter]) {
[[NSColor windowBackgroundColor] setFill];
NSRectFill(rect);
-
+
const NSRect lineBorderRect = NSMakeRect(NSMinX(rect), 0.0, NSWidth(rect), 1.0);
if (NSIntersectsRect(lineBorderRect, rect))
{
}
else {
const BOOL active = [[self window] isMainWindow];
-
+
NSInteger count = 0;
NSRect gridRects[active ? 2 : 3];
NSColor * colorRects[active ? 2 : 3];
-
+
//bottom line
NSRect lineBorderRect = NSMakeRect(NSMinX(rect), 0.0, NSWidth(rect), 1.0);
NSRect intersectLineBorderRect = NSIntersectionRect(lineBorderRect, rect);
colorRects[count] = active ? [NSColor colorWithCalibratedWhite: 0.25 alpha: 1.0]
: [NSColor colorWithCalibratedWhite: 0.5 alpha: 1.0];
++count;
-
+
rect.origin.y += intersectLineBorderRect.size.height;
rect.size.height -= intersectLineBorderRect.size.height;
}
-
-
+
+
//top line
if (active)
{
gridRects[count] = intersectLineBorderRect;
colorRects[count] = [NSColor colorWithCalibratedWhite: 0.75 alpha: 1.0];
++count;
-
+
rect.size.height -= intersectLineBorderRect.size.height;
}
}
-
+
if (!NSIsEmptyRect(rect))
{
if (active)
++count;
}
}
-
+
NSRectFillListWithColors(gridRects, colorRects, count);
-
+
if (fNoiseImage) {
[fNoiseImage drawInRect: rect
fromRect: [self convertRectToBacking: rect]
@class FileListNode;
typedef enum {
- TorrentDeterminationAutomatic = 0,
- TorrentDeterminationUserSpecified
+ TorrentDeterminationAutomatic = 0,
+ TorrentDeterminationUserSpecified
} TorrentDeterminationType;
#define kTorrentDidChangeGroupNotification @"TorrentDidChangeGroup"
tr_torrent * fHandle;
const tr_info * fInfo;
const tr_stat * fStat;
-
+
NSUserDefaults * fDefaults;
NSImage * fIcon;
-
+
NSString * fHashString;
-
+
tr_file_stat * fFileStat;
NSArray * fFileList, * fFlatFileList;
-
+
NSIndexSet * fPreviousFinishedIndexes;
NSDate * fPreviousFinishedIndexesDate;
-
+
BOOL fRemoveWhenFinishSeeding;
-
+
NSInteger fGroupValue;
- TorrentDeterminationType fGroupValueDetermination;
-
- TorrentDeterminationType fDownloadFolderDetermination;
-
+ TorrentDeterminationType fGroupValueDetermination;
+
+ TorrentDeterminationType fDownloadFolderDetermination;
+
BOOL fResumeOnWake;
-
+
BOOL fTimeMachineExcludeInitialized;
}
@autoreleasepool {
NSString * oldPath = [NSString stringWithUTF8String: oldPathCharString];
NSString * newName = [NSString stringWithUTF8String: newNameCharString];
-
+
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary * contextDict = [(NSDictionary *)contextInfo autorelease];
Torrent * torrentObject = [contextDict objectForKey: @"Torrent"];
removeWhenFinishSeeding: nil
downloadFolder: location
legacyIncompleteFolder: nil];
-
+
if (self)
{
if (torrentDelete && ![[self torrentLocation] isEqualToString: path])
removeWhenFinishSeeding: nil
downloadFolder: location
legacyIncompleteFolder: nil];
-
+
return self;
}
lib: lib groupValue: nil
removeWhenFinishSeeding: nil
downloadFolder: location legacyIncompleteFolder: nil];
-
+
return self;
}
downloadFolder: [history objectForKey: @"DownloadFolder"] //upgrading from versions < 1.80
legacyIncompleteFolder: [[history objectForKey: @"UseIncompleteFolder"] boolValue] //upgrading from versions < 1.80
? [history objectForKey: @"IncompleteFolder"] : nil];
-
+
if (self)
{
//start transfer
fStat = tr_torrentStat(fHandle);
[self startTransferNoQueue];
}
-
+
//upgrading from versions < 1.30: get old added, activity, and done dates
NSDate * date;
if ((date = [history objectForKey: @"Date"]))
tr_torrentSetActivityDate(fHandle, [date timeIntervalSince1970]);
if ((date = [history objectForKey: @"DateCompleted"]))
tr_torrentSetDoneDate(fHandle, [date timeIntervalSince1970]);
-
+
//upgrading from versions < 1.60: get old stop ratio settings
NSNumber * ratioSetting;
if ((ratioSetting = [history objectForKey: @"RatioSetting"]))
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
-
+
if (fFileStat)
tr_torrentFilesFree(fFileStat, [self fileCount]);
-
+
[fPreviousFinishedIndexes release];
[fPreviousFinishedIndexesDate release];
-
+
[fHashString release];
-
+
[fIcon release];
-
+
[fFileList release];
[fFlatFileList release];
-
+
[super dealloc];
}
{
//allow the file to be indexed by Time Machine
[self setTimeMachineExclude: NO];
-
+
tr_torrentRemove(fHandle, trashFiles, trashDataFile);
}
{
//if data existed in original download location, unexclude it before changing the location
[self setTimeMachineExclude: NO];
-
+
tr_torrentSetDownloadDir(fHandle, [folder UTF8String]);
-
+
fDownloadFolderDetermination = determinationType;
}
{
[fPreviousFinishedIndexes release];
fPreviousFinishedIndexes = [indexes retain];
-
+
[fPreviousFinishedIndexesDate release];
fPreviousFinishedIndexesDate = indexes != nil ? [[NSDate alloc] init] : nil;
}
{
//get previous stalled value before update
const BOOL wasStalled = fStat != NULL && [self isStalled];
-
+
fStat = tr_torrentStat(fHandle);
-
+
//make sure the "active" filter is updated when stalled-ness changes
if (wasStalled != [self isStalled])
//posting asynchronously with coalescing to prevent stack overflow on lots of torrents changing state at the same time
{
ignoreQueue ? tr_torrentStartNow(fHandle) : tr_torrentStart(fHandle);
[self update];
-
+
//capture, specifically, stop-seeding settings changing to unlimited
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
}
- (void) setRatioLimit: (CGFloat) limit
{
NSParameterAssert(limit >= 0);
-
+
tr_torrentSetRatioLimit(fHandle, limit);
}
- (void) setIdleLimitMinutes: (NSUInteger) limit
{
NSParameterAssert(limit > 0);
-
+
tr_torrentSetIdleLimit(fHandle, limit);
}
- (void) setMaxPeerConnect: (uint16_t) count
{
NSParameterAssert(count > 0);
-
+
tr_torrentSetPeerLimit(fHandle, count);
}
NSString * oldFolder = [self currentDirectory];
if ([oldFolder isEqualToString: folder])
return;
-
+
//check if moving inside itself
NSArray * oldComponents = [oldFolder pathComponents],
* newComponents = [folder pathComponents];
const NSUInteger oldCount = [oldComponents count];
-
+
if (oldCount < [newComponents count] && [[newComponents objectAtIndex: oldCount] isEqualToString: [self name]]
&& [folder hasPrefix: oldFolder])
{
NSLocalizedString(@"The move operation of \"%@\" cannot be done.",
"Move inside itself alert -> message"), [self name]]];
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Move inside itself alert -> button")];
-
+
[alert runModal];
[alert release];
-
+
return;
}
-
+
volatile int status;
tr_torrentSetLocation(fHandle, [folder UTF8String], YES, NULL, &status);
-
+
while (status == TR_LOC_MOVING) //block while moving (for now)
[NSThread sleepForTimeInterval: 0.05];
-
+
if (status == TR_LOC_DONE)
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateStats" object: nil];
else
[alert setInformativeText: [NSString stringWithFormat:
NSLocalizedString(@"The move operation of \"%@\" cannot be done.", "Move error alert -> message"), [self name]]];
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Move error alert -> button")];
-
+
[alert runModal];
[alert release];
}
-
+
[self updateTimeMachineExclude];
}
{
if ([self allDownloaded] || ![fDefaults boolForKey: @"WarningRemainingSpace"])
return YES;
-
+
NSString * downloadFolder = [self currentDirectory];
NSDictionary * systemAttributes;
if ((systemAttributes = [[NSFileManager defaultManager] attributesOfFileSystemForPath: downloadFolder error: NULL]))
{
const uint64_t remainingSpace = [[systemAttributes objectForKey: NSFileSystemFreeSize] unsignedLongLongValue];
-
+
//if the remaining space is greater than the size left, then there is enough space regardless of preallocation
if (remainingSpace < [self sizeLeft] && remainingSpace < tr_torrentGetBytesLeftToAllocate(fHandle))
{
NSString * volumeName = [[[NSFileManager defaultManager] componentsToDisplayForPath: downloadFolder] objectAtIndex: 0];
-
+
NSAlert * alert = [[NSAlert alloc] init];
[alert setMessageText: [NSString stringWithFormat:
NSLocalizedString(@"Not enough remaining disk space to download \"%@\" completely.",
"Torrent disk space alert -> message"), volumeName]];
[alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent disk space alert -> button")];
[alert addButtonWithTitle: NSLocalizedString(@"Download Anyway", "Torrent disk space alert -> button")];
-
+
[alert setShowsSuppressionButton: YES];
[[alert suppressionButton] setTitle: NSLocalizedString(@"Do not check disk space again",
"Torrent disk space alert -> button")];
if ([[alert suppressionButton] state] == NSOnState)
[fDefaults setBool: NO forKey: @"WarningRemainingSpace"];
[alert release];
-
+
return result != NSAlertFirstButtonReturn;
}
}
{
if ([self isMagnet])
return [NSImage imageNamed: @"Magnet"];
-
+
if (!fIcon)
fIcon = [self isFolder] ? [[NSImage imageNamed: NSImageNameFolder] retain]
: [[[NSWorkspace sharedWorkspace] iconForFileType: [[self name] pathExtension]] retain];
{
int count;
tr_tracker_stat * stats = tr_torrentTrackers(fHandle, &count);
-
+
NSMutableArray * trackers = [NSMutableArray arrayWithCapacity: (count > 0 ? count + (stats[count-1].tier + 1) : 0)];
-
+
int prevTier = -1;
for (int i=0; i < count; ++i)
{
[trackers addObject: @{ @"Tier" : @(stats[i].tier + 1), @"Name" : [self name] }];
prevTier = stats[i].tier;
}
-
+
TrackerNode * tracker = [[TrackerNode alloc] initWithTrackerStat: &stats[i] torrent: self];
[trackers addObject: tracker];
[tracker release];
}
-
+
tr_torrentTrackersFree(stats, count);
return trackers;
}
- (NSArray *) allTrackersFlat
{
NSMutableArray * allTrackers = [NSMutableArray arrayWithCapacity: fInfo->trackerCount];
-
+
for (NSInteger i=0; i < fInfo->trackerCount; i++)
[allTrackers addObject: [NSString stringWithUTF8String: fInfo->trackers[i].announce]];
-
+
return allTrackers;
}
- (BOOL) addTrackerToNewTier: (NSString *) tracker
{
tracker = [tracker stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];
-
+
if ([tracker rangeOfString: @"://"].location == NSNotFound)
tracker = [@"http://" stringByAppendingString: tracker];
-
+
//recreate the tracker structure
const int oldTrackerCount = fInfo->trackerCount;
tr_tracker_info * trackerStructs = tr_new(tr_tracker_info, oldTrackerCount+1);
for (int i = 0; i < oldTrackerCount; ++i)
trackerStructs[i] = fInfo->trackers[i];
-
+
trackerStructs[oldTrackerCount].announce = (char *)[tracker UTF8String];
trackerStructs[oldTrackerCount].tier = trackerStructs[oldTrackerCount-1].tier + 1;
trackerStructs[oldTrackerCount].id = oldTrackerCount;
-
+
const BOOL success = tr_torrentSetAnnounceList(fHandle, trackerStructs, oldTrackerCount+1);
tr_free(trackerStructs);
-
+
return success;
}
{
//recreate the tracker structure
tr_tracker_info * trackerStructs = tr_new(tr_tracker_info, fInfo->trackerCount);
-
+
NSUInteger newCount = 0;
for (NSUInteger i = 0; i < fInfo->trackerCount; i++)
{
if (![trackers containsObject: [NSString stringWithUTF8String: fInfo->trackers[i].announce]])
trackerStructs[newCount++] = fInfo->trackers[i];
}
-
+
const BOOL success = tr_torrentSetAnnounceList(fHandle, trackerStructs, newCount);
NSAssert(success, @"Removing tracker addresses failed");
-
+
tr_free(trackerStructs);
}
{
if ([self isMagnet])
return nil;
-
+
if ([self isFolder])
{
NSString * dataLocation = [[self currentDirectory] stringByAppendingPathComponent: [self name]];
-
+
if (![[NSFileManager defaultManager] fileExistsAtPath: dataLocation])
return nil;
-
+
return dataLocation;
}
else
char * location = tr_torrentFindFile(fHandle, 0);
if (location == NULL)
return nil;
-
+
NSString * dataLocation = [NSString stringWithUTF8String: location];
free(location);
-
+
return dataLocation;
}
}
{
NSString * basePath = [[node path] stringByAppendingPathComponent: [node name]];
NSString * dataLocation = [[self currentDirectory] stringByAppendingPathComponent: basePath];
-
+
if (![[NSFileManager defaultManager] fileExistsAtPath: dataLocation])
return nil;
-
+
return dataLocation;
}
else
char * location = tr_torrentFindFile(fHandle, [[node indexes] firstIndex]);
if (location == NULL)
return nil;
-
+
NSString * dataLocation = [NSString stringWithUTF8String: location];
free(location);
-
+
return dataLocation;
}
}
{
NSParameterAssert(newName != nil);
NSParameterAssert(![newName isEqualToString: @""]);
-
+
NSDictionary * contextInfo = [@{ @"Torrent" : self, @"CompletionHandler" : [[completionHandler copy] autorelease] } retain];
-
+
tr_torrentRenamePath(fHandle, fInfo->name, [newName UTF8String], renameCallback, contextInfo);
}
NSParameterAssert([node torrent] == self);
NSParameterAssert(newName != nil);
NSParameterAssert(![newName isEqualToString: @""]);
-
+
NSDictionary * contextInfo = [@{ @"Torrent" : self, @"Nodes" : @[ node ], @"CompletionHandler" : [[completionHandler copy] autorelease] } retain];
-
+
NSString * oldPath = [[node path] stringByAppendingPathComponent: [node name]];
tr_torrentRenamePath(fHandle, [oldPath UTF8String], [newName UTF8String], renameCallback, contextInfo);
}
{
if ([self size] == 0) //magnet links
return 0.0;
-
+
return (CGFloat)[self sizeLeft] / [self size];
}
{
if (![self isAnyErrorOrWarning])
return @"";
-
+
NSString * error;
if (!(error = [NSString stringWithUTF8String: fStat->errorString])
&& !(error = [NSString stringWithCString: fStat->errorString encoding: NSISOLatin1StringEncoding]))
error = [NSString stringWithFormat: @"(%@)", NSLocalizedString(@"unreadable error", "Torrent -> error string unreadable")];
-
+
//libtransmission uses "Set Location", Mac client uses "Move data file to..." - very hacky!
error = [error stringByReplacingOccurrencesOfString: @"Set Location" withString: [@"Move Data File To" stringByAppendingEllipsis]];
-
+
return error;
}
{
int totalPeers;
tr_peer_stat * peers = tr_torrentPeers(fHandle, &totalPeers);
-
+
NSMutableArray * peerDicts = [NSMutableArray arrayWithCapacity: totalPeers];
-
+
for (int i = 0; i < totalPeers; i++)
{
tr_peer_stat * peer = &peers[i];
NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithCapacity: 12];
-
+
[dict setObject: [self name] forKey: @"Name"];
[dict setObject: [NSNumber numberWithInt: peer->from] forKey: @"From"];
[dict setObject: [NSString stringWithUTF8String: peer->addr] forKey: @"IP"];
[dict setObject: [NSNumber numberWithBool: peer->isUTP] forKey: @"uTP"];
[dict setObject: [NSString stringWithUTF8String: peer->client] forKey: @"Client"];
[dict setObject: [NSString stringWithUTF8String: peer->flagStr] forKey: @"Flags"];
-
+
if (peer->isUploadingTo)
[dict setObject: [NSNumber numberWithDouble: peer->rateToPeer_KBps] forKey: @"UL To Rate"];
if (peer->isDownloadingFrom)
[dict setObject: [NSNumber numberWithDouble: peer->rateToClient_KBps] forKey: @"DL From Rate"];
-
+
[peerDicts addObject: dict];
}
-
+
tr_torrentPeersFree(peers, totalPeers);
-
+
return peerDicts;
}
- (NSArray *) webSeeds
{
NSMutableArray * webSeeds = [NSMutableArray arrayWithCapacity: fInfo->webseedCount];
-
+
double * dlSpeeds = tr_torrentWebSpeeds_KBps(fHandle);
-
+
for (NSInteger i = 0; i < fInfo->webseedCount; i++)
{
NSMutableDictionary * dict = [NSMutableDictionary dictionaryWithCapacity: 3];
-
+
[dict setObject: [self name] forKey: @"Name"];
[dict setObject: [NSString stringWithUTF8String: fInfo->webseeds[i]] forKey: @"Address"];
-
+
if (dlSpeeds[i] != -1.0)
[dict setObject: [NSNumber numberWithDouble: dlSpeeds[i]] forKey: @"DL From Rate"];
-
+
[webSeeds addObject: dict];
}
-
+
tr_free(dlSpeeds);
-
+
return webSeeds;
}
? [NSString stringWithFormat: NSLocalizedString(@"%@ of torrent metadata retrieved",
"Torrent -> progress string"), [NSString percentString: fStat->metadataPercentComplete longDecimals: YES]]
: NSLocalizedString(@"torrent metadata needed", "Torrent -> progress string");
-
+
return [NSString stringWithFormat: @"%@ - %@", NSLocalizedString(@"Magnetized transfer", "Torrent -> progress string"),
progressString];
}
-
+
NSString * string;
-
+
if (![self allDownloaded])
{
CGFloat progress;
string = [NSString stringForFilePartialSize: [self haveTotal] fullSize: [self size]];
progress = [self progress];
}
-
+
string = [string stringByAppendingFormat: @" (%@)", [NSString percentString: progress longDecimals: YES]];
}
else
}
else
downloadString = [NSString stringForFileSize: [self size]];
-
+
NSString * uploadString = [NSString stringWithFormat: NSLocalizedString(@"uploaded %@ (Ratio: %@)",
"Torrent -> progress string"), [NSString stringForFileSize: [self uploadedTotal]],
[NSString stringForRatio: [self ratio]]];
-
+
string = [downloadString stringByAppendingFormat: @", %@", uploadString];
}
-
+
//add time when downloading or seed limit set
if ([self shouldShowEta])
string = [string stringByAppendingFormat: @" - %@", [self etaString]];
-
+
return string;
}
- (NSString *) statusString
{
NSString * string;
-
+
if ([self isAnyErrorOrWarning])
{
switch (fStat->error)
case TR_STAT_TRACKER_WARNING: string = NSLocalizedString(@"Tracker returned warning", "Torrent -> status string"); break;
default: NSAssert(NO, @"unknown error state");
}
-
+
NSString * errorString = [self errorMessage];
if (errorString && ![errorString isEqualToString: @""])
string = [string stringByAppendingFormat: @": %@", errorString];
else
string = NSLocalizedString(@"Paused", "Torrent -> status string");
break;
-
+
case TR_STATUS_DOWNLOAD_WAIT:
string = [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis];
break;
-
+
case TR_STATUS_SEED_WAIT:
string = [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
break;
-
+
case TR_STATUS_CHECK_WAIT:
string = [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
break;
else
string = [NSString stringWithFormat: NSLocalizedString(@"Downloading from %d of 1 peer",
"Torrent -> status string"), [self peersSendingToUs]];
-
+
const NSInteger webSeedCount = fStat->webseedsSendingToUs;
if (webSeedCount > 0)
{
else
webSeedString = [NSString stringWithFormat: NSLocalizedString(@"%d web seeds", "Torrent -> status string"),
webSeedCount];
-
+
string = [string stringByAppendingFormat: @" + %@", webSeedString];
}
-
+
break;
case TR_STATUS_SEED:
string = [NSString stringWithFormat: NSLocalizedString(@"Seeding to %d of 1 peer", "Torrent -> status string"),
[self peersGettingFromUs]];
}
-
+
if ([self isStalled])
string = [NSLocalizedString(@"Stalled", "Torrent -> status string") stringByAppendingFormat: @", %@", string];
}
-
+
//append even if error
if ([self isActive] && ![self isChecking])
{
string = [string stringByAppendingFormat: @" - %@: %@",
NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
}
-
+
return string;
}
- (NSString *) shortStatusString
{
NSString * string;
-
+
switch (fStat->activity)
{
case TR_STATUS_STOPPED:
else
string = NSLocalizedString(@"Paused", "Torrent -> status string");
break;
-
+
case TR_STATUS_DOWNLOAD_WAIT:
string = [NSLocalizedString(@"Waiting to download", "Torrent -> status string") stringByAppendingEllipsis];
break;
-
+
case TR_STATUS_SEED_WAIT:
string = [NSLocalizedString(@"Waiting to seed", "Torrent -> status string") stringByAppendingEllipsis];
break;
NSLocalizedString(@"Checking existing data", "Torrent -> status string"),
[NSString percentString: [self checkingProgress] longDecimals: YES]];
break;
-
+
case TR_STATUS_DOWNLOAD:
string = [NSString stringWithFormat: @"%@: %@, %@: %@",
NSLocalizedString(@"DL", "Torrent -> status string"), [NSString stringForSpeed: [self downloadRate]],
NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
break;
-
+
case TR_STATUS_SEED:
string = [NSString stringWithFormat: @"%@: %@, %@: %@",
NSLocalizedString(@"Ratio", "Torrent -> status string"), [NSString stringForRatio: [self ratio]],
NSLocalizedString(@"UL", "Torrent -> status string"), [NSString stringForSpeed: [self uploadRate]]];
}
-
+
return string;
}
case TR_STATUS_SEED_WAIT:
{
NSString * string = NSLocalizedString(@"Paused", "Torrent -> status string");
-
+
NSString * extra = nil;
if ([self waitingToStart])
{
- extra = fStat->activity == TR_STATUS_DOWNLOAD_WAIT
+ extra = fStat->activity == TR_STATUS_DOWNLOAD_WAIT
? NSLocalizedString(@"Waiting to download", "Torrent -> status string")
: NSLocalizedString(@"Waiting to seed", "Torrent -> status string");
}
else if ([self isFinishedSeeding])
extra = NSLocalizedString(@"Seeding complete", "Torrent -> status string");
else;
-
+
return extra ? [string stringByAppendingFormat: @" (%@)", extra] : string;
}
-
+
case TR_STATUS_CHECK_WAIT:
return [NSLocalizedString(@"Waiting to check existing data", "Torrent -> status string") stringByAppendingEllipsis];
{
fGroupValue = groupValue;
[[NSNotificationCenter defaultCenter] postNotificationName: kTorrentDidChangeGroupNotification object: self];
- }
+ }
fGroupValueDetermination = determinationType;
}
{
if (fFileStat)
tr_torrentFilesFree(fFileStat, [self fileCount]);
-
+
fFileStat = tr_torrentFiles(fHandle, NULL);
}
{
if ([self fileCount] == 1 || [self isComplete])
return [self progress];
-
+
if (!fFileStat)
[self updateFileStat];
-
+
// #5501
if ([node size] == 0) {
return 1.0;
}
-
+
NSIndexSet * indexSet = [node indexes];
-
+
if ([indexSet count] == 1)
return fFileStat[[indexSet firstIndex]].progress;
-
+
uint64_t have = 0;
for (NSInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
have += fFileStat[index].bytesCompleted;
-
+
return (CGFloat)have / [node size];
}
- (BOOL) canChangeDownloadCheckForFile: (NSUInteger) index
{
NSAssert2((NSInteger)index < [self fileCount], @"Index %ld is greater than file count %ld", index, [self fileCount]);
-
+
return [self canChangeDownloadCheckForFiles: [NSIndexSet indexSetWithIndex: index]];
}
{
if ([self fileCount] == 1 || [self isComplete])
return NO;
-
+
if (!fFileStat)
[self updateFileStat];
-
+
__block BOOL canChange = NO;
[indexSet enumerateIndexesWithOptions: NSEnumerationConcurrent usingBlock: ^(NSUInteger index, BOOL *stop) {
if (fFileStat[index].progress < 1.0)
onState = YES;
else
offState = YES;
-
+
if (onState && offState)
return NSMixedState;
}
tr_file_index_t * files = malloc(count * sizeof(tr_file_index_t));
for (NSUInteger index = [indexSet firstIndex], i = 0; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index], i++)
files[i] = index;
-
+
tr_torrentSetFileDLs(fHandle, files, count, state != NSOffState);
free(files);
-
+
[self update];
[[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFileCheckChange" object: self];
}
tr_file_index_t * files = tr_malloc(count * sizeof(tr_file_index_t));
for (NSUInteger index = [indexSet firstIndex], i = 0; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index], i++)
files[i] = index;
-
+
tr_torrentSetFilePriorities(fHandle, files, count, priority);
tr_free(files);
}
{
BOOL low = NO, normal = NO, high = NO;
NSMutableSet * priorities = [NSMutableSet setWithCapacity: MIN([indexSet count], 3u)];
-
+
for (NSUInteger index = [indexSet firstIndex]; index != NSNotFound; index = [indexSet indexGreaterThanIndex: index])
{
if (![self canChangeDownloadCheckForFile: index])
continue;
-
+
const tr_priority_t priority = fInfo->files[index].priority;
switch (priority)
{
default:
NSAssert2(NO, @"Unknown priority %d for file index %ld", priority, index);
}
-
+
[priorities addObject: [NSNumber numberWithInteger: priority]];
if (low && normal && high)
break;
{
if (fStat->idleSecs == -1)
return -1;
-
+
return fStat->idleSecs / 60;
}
{
int count;
tr_tracker_stat * stats = tr_torrentTrackers(fHandle, &count);
-
+
NSString * best = nil;
-
+
for (int i=0; i < count; ++i)
{
NSString * tracker = [NSString stringWithUTF8String: stats[i].host];
if (!best || [tracker localizedCaseInsensitiveCompare: best] == NSOrderedAscending)
best = tracker;
}
-
+
tr_torrentTrackersFree(stats, count);
return best;
}
{
if (!(self = [super init]))
return nil;
-
+
fDefaults = [NSUserDefaults standardUserDefaults];
-
+
if (torrentStruct)
fHandle = torrentStruct;
else
{
//set libtransmission settings for initialization
tr_ctor * ctor = tr_ctorNew(lib);
-
+
tr_ctorSetPaused(ctor, TR_FORCE, YES);
if (downloadFolder)
tr_ctorSetDownloadDir(ctor, TR_FORCE, [downloadFolder UTF8String]);
if (incompleteFolder)
tr_ctorSetIncompleteDir(ctor, [incompleteFolder UTF8String]);
-
+
tr_parse_result result = TR_PARSE_ERR;
if (path)
result = tr_ctorSetMetainfoFromFile(ctor, [path UTF8String]);
-
+
if (result != TR_PARSE_OK && magnetAddress)
result = tr_ctorSetMetainfoFromMagnetLink(ctor, [magnetAddress UTF8String]);
-
+
//backup - shouldn't be needed after upgrade to 1.70
if (result != TR_PARSE_OK && hashString)
result = tr_ctorSetMetainfoFromHash(ctor, [hashString UTF8String]);
-
+
if (result == TR_PARSE_OK)
fHandle = tr_torrentNew(ctor, NULL, NULL);
-
+
tr_ctorFree(ctor);
-
+
if (!fHandle)
{
[self release];
}
fInfo = tr_torrentInfo(fHandle);
-
+
tr_torrentSetQueueStartCallback(fHandle, startQueueCallback, self);
tr_torrentSetCompletenessCallback(fHandle, completenessChangeCallback, self);
tr_torrentSetRatioLimitHitCallback(fHandle, ratioLimitHitCallback, self);
tr_torrentSetIdleLimitHitCallback(fHandle, idleLimitHitCallback, self);
tr_torrentSetMetadataCallback(fHandle, metadataCallback, self);
-
+
fHashString = [[NSString alloc] initWithUTF8String: fInfo->hashString];
-
+
fResumeOnWake = NO;
-
+
//don't do after this point - it messes with auto-group functionality
if (![self isMagnet])
[self createFileList];
-
+
fDownloadFolderDetermination = TorrentDeterminationAutomatic;
-
+
if (groupValue)
{
fGroupValueDetermination = TorrentDeterminationUserSpecified;
fGroupValueDetermination = TorrentDeterminationAutomatic;
fGroupValue = [[GroupsController groups] groupIndexForTorrent: self];
}
-
+
fRemoveWhenFinishSeeding = removeWhenFinishSeeding ? [removeWhenFinishSeeding boolValue] : [fDefaults boolForKey: @"RemoveWhenFinishSeeding"];
-
+
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(checkGroupValueForRemoval:)
name: @"GroupValueRemoved" object: nil];
-
+
fTimeMachineExcludeInitialized = NO;
[self update];
-
+
return self;
}
- (void) createFileList
{
NSAssert(![self isMagnet], @"Cannot create a file list until the torrent is demagnetized");
-
+
if ([self isFolder])
{
const NSInteger count = [self fileCount];
NSMutableArray * flatFileList = [NSMutableArray arrayWithCapacity: count];
-
+
FileListNode * tempNode = nil;
-
+
for (NSInteger i = 0; i < count; i++)
{
tr_file * file = &fInfo->files[i];
-
+
NSString * fullPath = [NSString stringWithUTF8String: file->name];
NSArray * pathComponents = [fullPath pathComponents];
-
+
if (!tempNode)
tempNode = [[FileListNode alloc] initWithFolderName:[pathComponents objectAtIndex: 0] path:@"" torrent:self];
-
+
[self insertPathForComponents: pathComponents withComponentIndex: 1 forParent: tempNode fileSize: file->length index: i flatList: flatFileList];
}
-
+
[self sortFileList: [tempNode children]];
[self sortFileList: flatFileList];
-
+
fFileList = [[NSArray alloc] initWithArray: [tempNode children]];
fFlatFileList = [[NSArray alloc] initWithArray: flatFileList];
[tempNode release];
{
NSParameterAssert([components count] > 0);
NSParameterAssert(componentIndex < [components count]);
-
+
NSString * name = [components objectAtIndex: componentIndex];
const BOOL isFolder = componentIndex < ([components count]-1);
-
+
//determine if folder node already exists
__block FileListNode * node = nil;
if (isFolder)
}
}];
}
-
+
//create new folder or file if it doesn't already exist
if (!node)
{
node = [[[FileListNode alloc] initWithFileName: name path: path size: size index: index torrent: self] autorelease];
[flatFileList addObject: node];
}
-
+
[parent insertChild: node];
}
-
+
if (isFolder)
{
[node insertIndex: index withSize: size];
-
+
[self insertPathForComponents: components withComponentIndex: (componentIndex+1) forParent: node fileSize: size index: index flatList: flatFileList];
}
}
{
NSSortDescriptor * descriptor = [NSSortDescriptor sortDescriptorWithKey: @"name" ascending: YES selector: @selector(localizedStandardCompare:)];
[fileNodes sortUsingDescriptors: [NSArray arrayWithObject: descriptor]];
-
+
[fileNodes enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(FileListNode * node, NSUInteger idx, BOOL * stop) {
if ([node isFolder])
[self sortFileList: [node children]];
- (void) completenessChange: (tr_completeness) status wasRunning: (BOOL) wasRunning
{
fStat = tr_torrentStat(fHandle); //don't call update yet to avoid auto-stop
-
+
switch (status)
{
case TR_SEED:
{
NSDictionary * statusInfo = @{ @"Status" : @(status), @"WasRunning" : @(wasRunning) };
[[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedDownloading" object: self userInfo: statusInfo];
-
+
//quarantine the finished data
NSString * dataLocation = [[self currentDirectory] stringByAppendingPathComponent: [self name]];
FSRef ref;
}
else
NSLog(@"Could not find file to quarantine: %@", dataLocation);
-
+
break;
}
case TR_LEECH:
[[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentRestartedDownloading" object: self];
break;
}
-
+
[self update];
[self updateTimeMachineExclude];
}
- (void) ratioLimitHit
{
fStat = tr_torrentStat(fHandle);
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedSeeding" object: self];
}
- (void) idleLimitHit
{
fStat = tr_torrentStat(fHandle);
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"TorrentFinishedSeeding" object: self];
}
fStat = tr_torrentStat(fHandle);
[self createFileList];
-
+
/* If the torrent is in no group, or the group was automatically determined based on criteria evaluated
* before we had metadata for this torrent, redetermine the group
*/
if ((fGroupValueDetermination == TorrentDeterminationAutomatic) || ([self groupValue] == -1))
[self setGroupValue: [[GroupsController groups] groupIndexForTorrent: self] determinationType: TorrentDeterminationAutomatic];
-
+
//change the location if the group calls for it and it's either not already set or was set automatically before
if (((fDownloadFolderDetermination == TorrentDeterminationAutomatic) || !tr_torrentGetCurrentDir(fHandle)) &&
[[GroupsController groups] usesCustomDownloadLocationForIndex: [self groupValue]])
NSString *location = [[GroupsController groups] customDownloadLocationForIndex: [self groupValue]];
[self changeDownloadFolderBeforeUsing: location determinationType:TorrentDeterminationAutomatic];
}
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"ResetInspector" object: self userInfo: @{ @"Torrent" : self }];
}
NSParameterAssert(completionHandler != nil);
NSParameterAssert(oldPath != nil);
NSParameterAssert(newName != nil);
-
+
NSString * path = [oldPath stringByDeletingLastPathComponent];
-
+
if (success)
{
NSString * oldName = [oldPath lastPathComponent];
void (^__block updateNodeAndChildrenForRename)(FileListNode *) = ^(FileListNode * node) {
[node updateFromOldName: oldName toNewName: newName inPath: path];
-
+
if ([node isFolder]) {
[[node children] enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(FileListNode * childNode, NSUInteger idx, BOOL * stop) {
updateNodeAndChildrenForRename(childNode);
}];
}
};
-
+
if (!nodes)
nodes = fFlatFileList;
[nodes enumerateObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(FileListNode * node, NSUInteger idx, BOOL *stop) {
updateNodeAndChildrenForRename(node);
}];
-
+
//resort lists
NSMutableArray * fileList = [fFileList mutableCopy];
[fFileList release];
[self sortFileList: fileList];
fFileList = fileList;
-
+
NSMutableArray * flatFileList = [fFlatFileList mutableCopy];
[fFlatFileList release];
[self sortFileList: flatFileList];
}
else
NSLog(@"Error renaming %@ to %@", oldPath, [path stringByAppendingPathComponent: newName]);
-
+
completionHandler(success);
}
//ratio: show if it's set at all
if (tr_torrentGetSeedRatio(fHandle, NULL))
return YES;
-
+
//idle: show only if remaining time is less than cap
if (fStat->etaIdle != TR_ETA_NOT_AVAIL && fStat->etaIdle < ETA_IDLE_DISPLAY_SEC)
return YES;
}
-
+
return NO;
}
}
else
return NSLocalizedString(@"remaining time unknown", "Torrent -> eta string");
-
+
NSString * idleString;
-
+
if ([NSApp isOnYosemiteOrBetter]) {
static NSDateComponentsFormatter *formatter;
static dispatch_once_t onceToken;
formatter.collapsesLargestUnit = YES;
formatter.includesTimeRemainingPhrase = YES;
});
-
+
idleString = [formatter stringFromTimeInterval: eta];
}
else {
idleString = [NSString timeString: eta includesTimeRemainingPhrase: YES showSeconds: YES maxFields: 2];
}
-
+
if (fromIdle) {
idleString = [idleString stringByAppendingFormat: @" (%@)", NSLocalizedString(@"inactive", "Torrent -> eta string")];
}
-
+
return idleString;
}
@interface TorrentCell : NSActionCell
{
NSUserDefaults * fDefaults;
-
+
NSMutableDictionary * fTitleAttributes, * fStatusAttributes;
-
+
BOOL fTracking, fMouseDownControlButton, fMouseDownRevealButton, fMouseDownActionButton,
fHover, fHoverControl, fHoverReveal, fHoverAction;
-
+
NSColor * fBarBorderColor, * fBluePieceColor, * fBarMinimalBorderColor;
}
- (id) init
{
if ((self = [super init]))
- {
+ {
fDefaults = [NSUserDefaults standardUserDefaults];
-
+
NSMutableParagraphStyle * paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[paragraphStyle setLineBreakMode: NSLineBreakByTruncatingMiddle];
-
+
fTitleAttributes = [[NSMutableDictionary alloc] initWithCapacity: 3];
[fTitleAttributes setObject: [NSFont messageFontOfSize: 12.0] forKey: NSFontAttributeName];
[fTitleAttributes setObject: paragraphStyle forKey: NSParagraphStyleAttributeName];
-
+
fStatusAttributes = [[NSMutableDictionary alloc] initWithCapacity: 3];
[fStatusAttributes setObject: [NSFont messageFontOfSize: 9.0] forKey: NSFontAttributeName];
[fStatusAttributes setObject: paragraphStyle forKey: NSParagraphStyleAttributeName];
-
+
[paragraphStyle release];
-
+
fBluePieceColor = [[NSColor colorWithCalibratedRed: 0.0 green: 0.4 blue: 0.8 alpha: 1.0] retain];
fBarBorderColor = [[NSColor colorWithCalibratedWhite: 0.0 alpha: 0.2] retain];
fBarMinimalBorderColor = [[NSColor colorWithCalibratedWhite: 0.0 alpha: 0.015] retain];
}
- return self;
+ return self;
}
- (id) copyWithZone: (NSZone *) zone
- (NSRect) iconRectForBounds: (NSRect) bounds
{
const CGFloat imageSize = [fDefaults boolForKey: @"SmallView"] ? IMAGE_SIZE_MIN : IMAGE_SIZE_REG;
-
+
return NSMakeRect(NSMinX(bounds) + PADDING_HORIZONTAL, ceil(NSMidY(bounds) - imageSize * 0.5),
imageSize, imageSize);
}
- (NSCellHitResult) hitTestForEvent: (NSEvent *) event inRect: (NSRect) cellFrame ofView: (NSView *) controlView
{
NSPoint point = [controlView convertPoint: [event locationInWindow] fromView: nil];
-
+
if (NSMouseInRect(point, [self controlButtonRectForBounds: cellFrame], [controlView isFlipped])
|| NSMouseInRect(point, [self revealButtonRectForBounds: cellFrame], [controlView isFlipped]))
return NSCellHitContentArea | NSCellHitTrackableArea;
-
+
return NSCellHitContentArea;
}
- (BOOL) trackMouse: (NSEvent *) event inRect: (NSRect) cellFrame ofView: (NSView *) controlView untilMouseUp: (BOOL) flag
{
fTracking = YES;
-
+
[self setControlView: controlView];
-
+
NSPoint point = [controlView convertPoint: [event locationInWindow] fromView: nil];
-
+
const NSRect controlRect = [self controlButtonRectForBounds: cellFrame];
const BOOL checkControl = NSMouseInRect(point, controlRect, [controlView isFlipped]);
-
+
const NSRect revealRect = [self revealButtonRectForBounds: cellFrame];
const BOOL checkReveal = NSMouseInRect(point, revealRect, [controlView isFlipped]);
-
+
[(TorrentTableView *)controlView removeTrackingAreas];
-
+
while ([event type] != NSLeftMouseUp)
{
point = [controlView convertPoint: [event locationInWindow] fromView: nil];
-
+
if (checkControl)
{
const BOOL inControlButton = NSMouseInRect(point, controlRect, [controlView isFlipped]);
}
}
else;
-
+
//send events to where necessary
if ([event type] == NSMouseEntered || [event type] == NSMouseExited)
[NSApp sendEvent: event];
event = [[controlView window] nextEventMatchingMask:
(NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSMouseEnteredMask | NSMouseExitedMask)];
}
-
+
fTracking = NO;
if (fMouseDownControlButton)
{
fMouseDownControlButton = NO;
-
+
[(TorrentTableView *)controlView toggleControlForTorrent: [self representedObject]];
}
else if (fMouseDownRevealButton)
{
fMouseDownRevealButton = NO;
[controlView setNeedsDisplayInRect: cellFrame];
-
+
NSString * location = [[self representedObject] dataLocation];
if (location)
{
}
}
else;
-
+
[controlView updateTrackingAreas];
-
+
return YES;
}
mouseLocation: (NSPoint) mouseLocation
{
const NSTrackingAreaOptions options = NSTrackingEnabledDuringMouseDrag | NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways;
-
+
//whole row
if ([fDefaults boolForKey: @"SmallView"])
{
rowOptions |= NSTrackingAssumeInside;
[(TorrentTableView *)controlView setRowHover: [[userInfo objectForKey: @"Row"] integerValue]];
}
-
+
NSMutableDictionary * rowInfo = [userInfo mutableCopy];
[rowInfo setObject: @"Row" forKey: @"Type"];
NSTrackingArea * area = [[NSTrackingArea alloc] initWithRect: cellFrame options: rowOptions owner: controlView userInfo: rowInfo];
[rowInfo release];
[area release];
}
-
+
//control button
NSRect controlButtonRect = [self controlButtonRectForBounds: cellFrame];
NSTrackingAreaOptions controlOptions = options;
controlOptions |= NSTrackingAssumeInside;
[(TorrentTableView *)controlView setControlButtonHover: [[userInfo objectForKey: @"Row"] integerValue]];
}
-
+
NSMutableDictionary * controlInfo = [userInfo mutableCopy];
[controlInfo setObject: @"Control" forKey: @"Type"];
NSTrackingArea * area = [[NSTrackingArea alloc] initWithRect: controlButtonRect options: controlOptions owner: controlView
[controlView addTrackingArea: area];
[controlInfo release];
[area release];
-
+
//reveal button
NSRect revealButtonRect = [self revealButtonRectForBounds: cellFrame];
NSTrackingAreaOptions revealOptions = options;
revealOptions |= NSTrackingAssumeInside;
[(TorrentTableView *)controlView setRevealButtonHover: [[userInfo objectForKey: @"Row"] integerValue]];
}
-
+
NSMutableDictionary * revealInfo = [userInfo mutableCopy];
[revealInfo setObject: @"Reveal" forKey: @"Type"];
area = [[NSTrackingArea alloc] initWithRect: revealButtonRect options: revealOptions owner: controlView
[controlView addTrackingArea: area];
[revealInfo release];
[area release];
-
+
//action button
NSRect actionButtonRect = [self iconRectForBounds: cellFrame]; //use the whole icon
NSTrackingAreaOptions actionOptions = options;
actionOptions |= NSTrackingAssumeInside;
[(TorrentTableView *)controlView setActionButtonHover: [[userInfo objectForKey: @"Row"] integerValue]];
}
-
+
NSMutableDictionary * actionInfo = [userInfo mutableCopy];
[actionInfo setObject: @"Action" forKey: @"Type"];
area = [[NSTrackingArea alloc] initWithRect: actionButtonRect options: actionOptions owner: controlView userInfo: actionInfo];
{
Torrent * torrent = [self representedObject];
NSAssert(torrent != nil, @"can't have a TorrentCell without a Torrent");
-
+
const BOOL minimal = [fDefaults boolForKey: @"SmallView"];
-
+
//bar
[self drawBar: minimal ? [self barRectMinForBounds: cellFrame] : [self barRectRegForBounds: cellFrame]];
-
+
//group coloring
const NSRect iconRect = [self iconRectForBounds: cellFrame];
-
+
const NSInteger groupValue = [torrent groupValue];
if (groupValue != -1)
{
groupRect.origin.y -= 1.0;
}
const CGFloat radius = minimal ? 3.0 : 6.0;
-
+
NSColor * groupColor = [[GroupsController groups] colorForIndex: groupValue],
* darkGroupColor = [groupColor blendedColorWithFraction: 0.2 ofColor: [NSColor whiteColor]];
-
+
//border
NSBezierPath * bp = [NSBezierPath bezierPathWithRoundedRect: groupRect xRadius: radius yRadius: radius];
[darkGroupColor set];
[bp setLineWidth: 2.0];
[bp stroke];
-
+
//inside
bp = [NSBezierPath bezierPathWithRoundedRect: groupRect xRadius: radius yRadius: radius];
NSGradient * gradient = [[NSGradient alloc] initWithStartingColor: [groupColor blendedColorWithFraction: 0.7
[gradient drawInBezierPath: bp angle: 90.0];
[gradient release];
}
-
+
const BOOL error = [torrent isAnyErrorOrWarning];
-
+
//icon
if (!minimal || !(!fTracking && fHoverAction)) //don't show in minimal mode when hovered over
{
: [torrent icon];
[icon drawInRect: iconRect fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0 respectFlipped: YES hints: nil];
}
-
+
//error badge
if (error && !minimal)
{
const NSRect errorRect = NSMakeRect(NSMaxX(iconRect) - ERROR_IMAGE_SIZE, NSMaxY(iconRect) - ERROR_IMAGE_SIZE, ERROR_IMAGE_SIZE, ERROR_IMAGE_SIZE);
[errorImage drawInRect: errorRect fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0 respectFlipped: YES hints: nil];
}
-
+
//text color
NSColor * titleColor, * statusColor;
if ([self backgroundStyle] == NSBackgroundStyleDark)
titleColor = [NSColor controlTextColor];
statusColor = [NSColor darkGrayColor];
}
-
+
[fTitleAttributes setObject: titleColor forKey: NSForegroundColorAttributeName];
[fStatusAttributes setObject: statusColor forKey: NSForegroundColorAttributeName];
-
+
//minimal status
CGFloat minimalTitleRightBound;
if (minimal)
{
NSAttributedString * minimalString = [self attributedStatusString: [self minimalStatusString]];
NSRect minimalStatusRect = [self rectForMinimalStatusWithString: minimalString inBounds: cellFrame];
-
+
if (!fHover)
[minimalString drawInRect: minimalStatusRect];
-
+
minimalTitleRightBound = NSMinX(minimalStatusRect);
}
-
+
//progress
if (!minimal)
{
NSAttributedString * progressString = [self attributedStatusString: [torrent progressString]];
NSRect progressRect = [self rectForProgressWithStringInBounds: cellFrame];
-
+
[progressString drawInRect: progressRect];
}
-
+
if (!minimal || fHover)
{
//control button
controlImageSuffix = @"Hover";
else
controlImageSuffix = @"Off";
-
+
NSImage * controlImage;
if ([torrent isActive])
controlImage = [NSImage imageNamed: [@"Pause" stringByAppendingString: controlImageSuffix]];
else
controlImage = [NSImage imageNamed: [@"Resume" stringByAppendingString: controlImageSuffix]];
}
-
+
const NSRect controlRect = [self controlButtonRectForBounds: cellFrame];
[controlImage drawInRect: controlRect fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0 respectFlipped: YES hints: nil];
minimalTitleRightBound = MIN(minimalTitleRightBound, NSMinX(controlRect));
-
+
//reveal button
NSString * revealImageString;
if (fMouseDownRevealButton)
revealImageString = @"RevealHover";
else
revealImageString = @"RevealOff";
-
+
NSImage * revealImage = [NSImage imageNamed: revealImageString];
[revealImage drawInRect: [self revealButtonRectForBounds: cellFrame] fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0 respectFlipped: YES hints: nil];
-
+
//action button
#warning image should use new gear
NSString * actionImageString;
actionImageString = @"ActionHover";
else
actionImageString = nil;
-
+
if (actionImageString)
{
NSImage * actionImage = [NSImage imageNamed: actionImageString];
[actionImage drawInRect: [self actionButtonRectForBounds: cellFrame] fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0 respectFlipped: YES hints: nil];
}
}
-
+
//title
NSAttributedString * titleString = [self attributedTitle];
NSRect titleRect = [self rectForTitleWithString: titleString withRightBound: minimalTitleRightBound inBounds: cellFrame];
[titleString drawInRect: titleRect];
-
+
//priority icon
if ([torrent priority] != TR_PRI_NORMAL)
{
const NSRect priorityRect = NSMakeRect(NSMaxX(titleRect) + PADDING_BETWEEN_TITLE_AND_PRIORITY,
NSMidY(titleRect) - PRIORITY_ICON_HEIGHT * 0.5,
PRIORITY_ICON_WIDTH, PRIORITY_ICON_HEIGHT);
-
+
NSColor * priorityColor = [self backgroundStyle] == NSBackgroundStyleDark ? [NSColor whiteColor] : [NSColor darkGrayColor];
NSImage * priorityImage = [[NSImage imageNamed: ([torrent priority] == TR_PRI_HIGH ? @"PriorityHighTemplate" : @"PriorityLowTemplate")] imageWithColor: priorityColor];
[priorityImage drawInRect: priorityRect fromRect: NSZeroRect operation: NSCompositeSourceOver fraction: 1.0 respectFlipped: YES hints: nil];
}
-
+
//status
if (!minimal)
{
- (NSRect) expansionFrameWithFrame: (NSRect) cellFrame inView: (NSView *) view
{
BOOL minimal = [fDefaults boolForKey: @"SmallView"];
-
+
//this code needs to match the code in drawInteriorWithFrame:withView:
CGFloat minimalTitleRightBound;
if (minimal)
{
NSAttributedString * minimalString = [self attributedStatusString: [self minimalStatusString]];
NSRect minimalStatusRect = [self rectForMinimalStatusWithString: minimalString inBounds: cellFrame];
-
+
minimalTitleRightBound = NSMinX(minimalStatusRect);
}
-
+
if (!minimal || fHover)
{
const NSRect controlRect = [self controlButtonRectForBounds: cellFrame];
minimalTitleRightBound = MIN(minimalTitleRightBound, NSMinX(controlRect));
}
-
+
NSAttributedString * titleString = [self attributedTitle];
NSRect realRect = [self rectForTitleWithString: titleString withRightBound: minimalTitleRightBound inBounds: cellFrame];
-
+
NSAssert([titleString size].width >= NSWidth(realRect), @"Full rect width should not be less than the used title rect width!");
-
+
if ([titleString size].width > NSWidth(realRect)
&& NSMouseInRect([view convertPoint: [[view window] mouseLocationOutsideOfEventStream] fromView: nil], realRect, [view isFlipped]))
{
realRect.size.width = [titleString size].width;
return NSInsetRect(realRect, -PADDING_EXPANSION_FRAME, -PADDING_EXPANSION_FRAME);
}
-
+
return NSZeroRect;
}
{
cellFrame.origin.x += PADDING_EXPANSION_FRAME;
cellFrame.origin.y += PADDING_EXPANSION_FRAME;
-
+
[fTitleAttributes setObject: [NSColor controlTextColor] forKey: NSForegroundColorAttributeName];
NSAttributedString * titleString = [self attributedTitle];
[titleString drawInRect: cellFrame];
- (void) drawBar: (NSRect) barRect
{
const BOOL minimal = [fDefaults boolForKey: @"SmallView"];
-
+
const CGFloat piecesBarPercent = [(TorrentTableView *)[self controlView] piecesBarPercent];
if (piecesBarPercent > 0.0)
{
NSRect piecesBarRect, regularBarRect;
NSDivideRect(barRect, &piecesBarRect, ®ularBarRect, floor(NSHeight(barRect) * PIECES_TOTAL_PERCENT * piecesBarPercent),
NSMaxYEdge);
-
+
[self drawRegularBar: regularBarRect];
[self drawPiecesBar: piecesBarRect];
}
else
{
[[self representedObject] setPreviousFinishedPieces: nil];
-
+
[self drawRegularBar: barRect];
}
-
+
NSColor * borderColor = minimal ? fBarMinimalBorderColor : fBarBorderColor;
[borderColor set];
[NSBezierPath strokeRect: NSInsetRect(barRect, 0.5, 0.5)];
- (void) drawRegularBar: (NSRect) barRect
{
Torrent * torrent = [self representedObject];
-
+
NSRect haveRect, missingRect;
NSDivideRect(barRect, &haveRect, &missingRect, round([torrent progress] * NSWidth(barRect)), NSMinXEdge);
-
+
if (!NSIsEmptyRect(haveRect))
{
if ([torrent isActive])
NSRect ratioHaveRect, ratioRemainingRect;
NSDivideRect(haveRect, &ratioHaveRect, &ratioRemainingRect, round([torrent progressStopRatio] * NSWidth(haveRect)),
NSMinXEdge);
-
+
[[ProgressGradients progressGreenGradient] drawInRect: ratioHaveRect angle: 90];
[[ProgressGradients progressLightGreenGradient] drawInRect: ratioRemainingRect angle: 90];
}
[[ProgressGradients progressGrayGradient] drawInRect: haveRect angle: 90];
}
}
-
+
if (![torrent allDownloaded])
{
const CGFloat widthRemaining = round(NSWidth(barRect) * [torrent progressLeft]);
-
+
NSRect wantedRect;
NSDivideRect(missingRect, &wantedRect, &missingRect, widthRemaining, NSMinXEdge);
-
+
//not-available section
if ([torrent isActive] && ![torrent isChecking] && [torrent availableDesired] < 1.0
&& [fDefaults boolForKey: @"DisplayProgressBarAvailable"])
NSRect unavailableRect;
NSDivideRect(wantedRect, &wantedRect, &unavailableRect, round(NSWidth(wantedRect) * [torrent availableDesired]),
NSMinXEdge);
-
+
[[ProgressGradients progressRedGradient] drawInRect: unavailableRect angle: 90];
}
-
+
//remaining section
[[ProgressGradients progressWhiteGradient] drawInRect: wantedRect angle: 90];
}
-
+
//unwanted section
if (!NSIsEmptyRect(missingRect))
{
- (void) drawPiecesBar: (NSRect) barRect
{
Torrent * torrent = [self representedObject];
-
+
//fill an all-white bar for magnet links
if ([torrent isMagnet])
{
NSRectFillUsingOperation(barRect, NSCompositeSourceOver);
return;
}
-
+
NSInteger pieceCount = MIN([torrent pieceCount], MAX_PIECES);
float * piecesPercent = malloc(pieceCount * sizeof(float));
[torrent getAmountFinished: piecesPercent size: pieceCount];
-
+
NSBitmapImageRep * bitmap = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes: nil
pixelsWide: pieceCount pixelsHigh: 1 bitsPerSample: 8 samplesPerPixel: 4 hasAlpha: YES
isPlanar: NO colorSpaceName: NSCalibratedRGBColorSpace bytesPerRow: 0 bitsPerPixel: 0];
-
+
NSIndexSet * previousFinishedIndexes = [torrent previousFinishedPieces];
NSMutableIndexSet * finishedIndexes = [NSMutableIndexSet indexSet];
-
+
for (NSInteger i = 0; i < pieceCount; i++)
{
NSColor * pieceColor;
}
else
pieceColor = [[NSColor whiteColor] blendedColorWithFraction: piecesPercent[i] ofColor: fBluePieceColor];
-
+
//it's faster to just set color instead of checking previous color
[bitmap setColor: pieceColor atX: i y: 0];
}
-
+
free(piecesPercent);
-
+
[torrent setPreviousFinishedPieces: [finishedIndexes count] > 0 ? finishedIndexes : nil]; //don't bother saving if none are complete
-
+
//actually draw image
[bitmap drawInRect: barRect fromRect: NSZeroRect operation: NSCompositeSourceOver
fraction: ([fDefaults boolForKey: @"SmallView"] ? 0.25 : 1.0) respectFlipped: YES hints: nil];
{
NSRect result;
result.size = [string size];
-
+
result.origin.x = NSMaxX(bounds) - (PADDING_HORIZONTAL + NSWidth(result));
result.origin.y = ceil(NSMidY(bounds) - NSHeight(result) * 0.5);
-
+
return result;
}
- (NSRect) rectForTitleWithString: (NSAttributedString *) string withRightBound: (CGFloat) rightBound inBounds: (NSRect) bounds
{
const BOOL minimal = [fDefaults boolForKey: @"SmallView"];
-
+
NSRect result;
result.origin.x = NSMinX(bounds) + PADDING_HORIZONTAL
+ (minimal ? IMAGE_SIZE_MIN : IMAGE_SIZE_REG) + PADDING_BETWEEN_IMAGE_AND_TITLE;
result.size.height = HEIGHT_TITLE;
-
+
if (minimal)
{
result.origin.y = ceil(NSMidY(bounds) - NSHeight(result) * 0.5);
result.origin.y = NSMinY(bounds) + PADDING_ABOVE_TITLE;
result.size.width = NSMaxX(bounds) - NSMinX(result) - PADDING_HORIZONTAL;
}
-
+
if ([(Torrent *)[self representedObject] priority] != TR_PRI_NORMAL)
result.size.width -= PRIORITY_ICON_WIDTH + PADDING_BETWEEN_TITLE_AND_PRIORITY;
result.size.width = MIN(NSWidth(result), [string size].width);
-
+
return result;
}
NSRect result;
result.origin.y = NSMinY(bounds) + PADDING_ABOVE_TITLE + HEIGHT_TITLE + PADDING_BETWEEN_TITLE_AND_PROGRESS;
result.origin.x = NSMinX(bounds) + PADDING_HORIZONTAL + IMAGE_SIZE_REG + PADDING_BETWEEN_IMAGE_AND_TITLE;
-
+
result.size.height = HEIGHT_STATUS;
result.size.width = NSMaxX(bounds) - NSMinX(result) - PADDING_HORIZONTAL;
-
+
return result;
}
result.origin.y = NSMinY(bounds) + PADDING_ABOVE_TITLE + HEIGHT_TITLE + PADDING_BETWEEN_TITLE_AND_PROGRESS + HEIGHT_STATUS
+ PADDING_BETWEEN_PROGRESS_AND_BAR + BAR_HEIGHT + PADDING_BETWEEN_BAR_AND_STATUS;
result.origin.x = NSMinX(bounds) + PADDING_HORIZONTAL + IMAGE_SIZE_REG + PADDING_BETWEEN_IMAGE_AND_TITLE;
-
+
result.size.height = HEIGHT_STATUS;
result.size.width = NSMaxX(bounds) - NSMinX(result) - PADDING_HORIZONTAL;
-
+
return result;
}
result.origin.x = NSMinX(bounds) + PADDING_HORIZONTAL + IMAGE_SIZE_REG + PADDING_BETWEEN_IMAGE_AND_BAR;
result.origin.y = NSMinY(bounds) + PADDING_ABOVE_TITLE + HEIGHT_TITLE + PADDING_BETWEEN_TITLE_AND_PROGRESS
+ HEIGHT_STATUS + PADDING_BETWEEN_PROGRESS_AND_BAR;
-
+
result.size.width = floor(NSMaxX(bounds) - NSMinX(result) - PADDING_HORIZONTAL
- 2.0 * (PADDING_BETWEEN_BUTTONS + NORMAL_BUTTON_WIDTH));
-
+
return result;
}
result.origin.y = NSMinY(bounds) + PADDING_BETWEEN_BAR_AND_EDGE_MIN;
result.size.height = NSHeight(bounds) - 2.0 * PADDING_BETWEEN_BAR_AND_EDGE_MIN;
result.size.width = NSMaxX(bounds) - NSMinX(result) - PADDING_BETWEEN_BAR_AND_EDGE_MIN;
-
+
return result;
}
result.size.height = NORMAL_BUTTON_WIDTH;
result.size.width = NORMAL_BUTTON_WIDTH;
result.origin.x = NSMaxX(bounds) - (PADDING_HORIZONTAL + NORMAL_BUTTON_WIDTH + PADDING_BETWEEN_BUTTONS + NORMAL_BUTTON_WIDTH);
-
+
if (![fDefaults boolForKey: @"SmallView"])
result.origin.y = NSMinY(bounds) + PADDING_ABOVE_TITLE + HEIGHT_TITLE - (NORMAL_BUTTON_WIDTH - BAR_HEIGHT) * 0.5
+ PADDING_BETWEEN_TITLE_AND_PROGRESS + HEIGHT_STATUS + PADDING_BETWEEN_PROGRESS_AND_BAR;
else
result.origin.y = ceil(NSMidY(bounds) - NSHeight(result) * 0.5);
-
+
return result;
}
result.size.height = NORMAL_BUTTON_WIDTH;
result.size.width = NORMAL_BUTTON_WIDTH;
result.origin.x = NSMaxX(bounds) - (PADDING_HORIZONTAL + NORMAL_BUTTON_WIDTH);
-
+
if (![fDefaults boolForKey: @"SmallView"])
result.origin.y = NSMinY(bounds) + PADDING_ABOVE_TITLE + HEIGHT_TITLE - (NORMAL_BUTTON_WIDTH - BAR_HEIGHT) * 0.5
+ PADDING_BETWEEN_TITLE_AND_PROGRESS + HEIGHT_STATUS + PADDING_BETWEEN_PROGRESS_AND_BAR;
else
result.origin.y = ceil(NSMidY(bounds) - NSHeight(result) * 0.5);
-
+
return result;
}
- (NSRect) actionButtonRectForBounds: (NSRect) bounds
{
const NSRect iconRect = [self iconRectForBounds: bounds];
-
+
//in minimal view the rect will be the icon rect, but avoid the extra defaults lookup with some cheap math
return NSMakeRect(NSMidX(iconRect) - ACTION_BUTTON_WIDTH * 0.5, NSMidY(iconRect) - ACTION_BUTTON_WIDTH * 0.5,
ACTION_BUTTON_WIDTH, ACTION_BUTTON_WIDTH);
uploaded += [torrent uploadedTotal];
downloaded += [torrent downloadedTotal];
}
-
+
return tr_getRatio(uploaded, downloaded);
}
CGFloat rate = 0.0;
for (Torrent * torrent in fTorrents)
rate += [torrent uploadRate];
-
+
return rate;
}
CGFloat rate = 0.0;
for (Torrent * torrent in fTorrents)
rate += [torrent downloadRate];
-
+
return rate;
}
@interface TorrentTableView : NSOutlineView <NSOutlineViewDelegate, NSAnimationDelegate, NSPopoverDelegate>
{
IBOutlet Controller * fController;
-
+
TorrentCell * fTorrentCell;
-
+
NSUserDefaults * fDefaults;
-
+
NSMutableIndexSet * fCollapsedGroups;
-
+
IBOutlet NSMenu * fContextRow, * fContextNoRow;
-
+
NSInteger fMouseRow, fMouseControlRow, fMouseRevealRow, fMouseActionRow;
NSArray * fSelectedValues;
-
+
IBOutlet NSMenu * fActionMenu, * fUploadMenu, * fDownloadMenu, * fRatioMenu, * fPriorityMenu;
IBOutlet NSMenuItem * fGlobalLimitItem;
Torrent * fMenuTorrent;
-
+
CGFloat fPiecesBarPercent;
NSAnimation * fPiecesBarAnimation;
-
+
BOOL fActionPopoverShown;
}
if ((self = [super initWithCoder: decoder]))
{
fDefaults = [NSUserDefaults standardUserDefaults];
-
+
fTorrentCell = [[TorrentCell alloc] init];
-
+
NSData * groupData = [fDefaults dataForKey: @"CollapsedGroups"];
if (groupData)
fCollapsedGroups = [[NSUnarchiver unarchiveObjectWithData: groupData] mutableCopy];
else
fCollapsedGroups = [[NSMutableIndexSet alloc] init];
-
+
fMouseRow = -1;
fMouseControlRow = -1;
fMouseRevealRow = -1;
fMouseActionRow = -1;
-
+
fActionPopoverShown = NO;
-
+
[self setDelegate: self];
-
+
fPiecesBarPercent = [fDefaults boolForKey: @"PiecesBar"] ? 1.0 : 0.0;
}
-
+
return self;
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
-
+
[fCollapsedGroups release];
-
+
[fPiecesBarAnimation release];
[fMenuTorrent release];
-
+
[fSelectedValues release];
-
+
[fTorrentCell release];
-
+
[super dealloc];
}
{
//set group columns to show ratio, needs to be in awakeFromNib to size columns correctly
[self setGroupStatusColumns];
-
+
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(setNeedsDisplay) name: @"RefreshTorrentTable" object: nil];
}
{
if (value == -1)
value = MAX_GROUP;
-
+
return [fCollapsedGroups containsIndex: value];
}
{
if (value == -1)
value = MAX_GROUP;
-
+
[fCollapsedGroups removeIndex: value];
}
if (!tableColumn)
{
[cell setRepresentedObject: item];
-
+
const NSInteger row = [self rowForItem: item];
[cell setHover: row == fMouseRow];
[cell setControlHover: row == fMouseControlRow];
else
{
NSRect rect = [super frameOfCellAtColumn: column row: row];
-
+
//adjust placement for proper vertical alignment
if (column == [self columnWithIdentifier: @"Group"])
rect.size.height -= 1.0f;
-
+
return rect;
}
}
{
[super updateTrackingAreas];
[self removeTrackingAreas];
-
+
const NSRange rows = [self rowsInRect: [self visibleRect]];
if (rows.length == 0)
return;
-
+
NSPoint mouseLocation = [self convertPoint: [[self window] mouseLocationOutsideOfEventStream] fromView: nil];
for (NSUInteger row = rows.location; row < NSMaxRange(rows); row++)
{
if (![[self itemAtRow: row] isKindOfClass: [Torrent class]])
continue;
-
+
NSDictionary * userInfo = [NSDictionary dictionaryWithObject: [NSNumber numberWithInteger: row] forKey: @"Row"];
TorrentCell * cell = (TorrentCell *)[self preparedCellAtColumn: -1 row: row];
[cell addTrackingAreasForView: self inRect: [self rectOfRow: row] withUserInfo: userInfo mouseLocation: mouseLocation];
fMouseControlRow = -1;
fMouseRevealRow = -1;
fMouseActionRow = -1;
-
+
for (NSTrackingArea * area in [self trackingAreas])
{
if ([area owner] == self && [[area userInfo] objectForKey: @"Row"])
- (void) setRowHover: (NSInteger) row
{
NSAssert([fDefaults boolForKey: @"SmallView"], @"cannot set a hover row when not in compact view");
-
+
fMouseRow = row;
if (row >= 0)
[self setNeedsDisplayInRect: [self rectOfRow: row]];
- (void) mouseEntered: (NSEvent *) event
{
NSDictionary * dict = (NSDictionary *)[event userData];
-
+
NSNumber * row;
if ((row = [dict objectForKey: @"Row"]))
{
if (![fDefaults boolForKey: @"SmallView"])
return;
}
-
+
[self setNeedsDisplayInRect: [self rectOfRow: rowVal]];
}
}
- (void) mouseExited: (NSEvent *) event
{
NSDictionary * dict = (NSDictionary *)[event userData];
-
+
NSNumber * row;
if ((row = [dict objectForKey: @"Row"]))
{
if (![fDefaults boolForKey: @"SmallView"])
return;
}
-
+
[self setNeedsDisplayInRect: [self rectOfRow: [row integerValue]]];
}
}
NSInteger value = [[[notification userInfo] objectForKey: @"NSObject"] groupIndex];
if (value < 0)
value = MAX_GROUP;
-
+
if ([fCollapsedGroups containsIndex: value])
{
[fCollapsedGroups removeIndex: value];
NSInteger value = [[[notification userInfo] objectForKey: @"NSObject"] groupIndex];
if (value < 0)
value = MAX_GROUP;
-
+
[fCollapsedGroups addIndex: value];
[[NSNotificationCenter defaultCenter] postNotificationName: @"OutlineExpandCollapse" object: self];
}
{
NSPoint point = [self convertPoint: [event locationInWindow] fromView: nil];
const NSInteger row = [self rowAtPoint: point];
-
+
//check to toggle group status before anything else
if ([self pointInGroupStatusRect: point])
{
[fDefaults setBool: ![fDefaults boolForKey: @"DisplayGroupRowRatio"] forKey: @"DisplayGroupRowRatio"];
[self setGroupStatusColumns];
-
+
return;
}
-
+
const BOOL pushed = row != -1 && (fMouseActionRow == row || fMouseRevealRow == row || fMouseControlRow == row);
-
+
//if pushing a button, don't change the selected rows
if (pushed)
fSelectedValues = [[self selectedValues] retain];
-
+
[super mouseDown: event];
-
+
[fSelectedValues release];
fSelectedValues = nil;
-
+
//avoid weird behavior when showing menu by doing this after mouse down
if (row != -1 && fMouseActionRow == row)
{
id item = nil;
if (row != -1)
item = [self itemAtRow: row];
-
+
if (!item || [item isKindOfClass: [Torrent class]])
[fController showInfo: nil];
else
- (void) selectValues: (NSArray *) values
{
NSMutableIndexSet * indexSet = [NSMutableIndexSet indexSet];
-
+
for (id item in values)
{
if ([item isKindOfClass: [Torrent class]])
}
}
}
-
+
[self selectRowIndexes: indexSet byExtendingSelection: NO];
}
{
NSIndexSet * selectedIndexes = [self selectedRowIndexes];
NSMutableArray * values = [NSMutableArray arrayWithCapacity: [selectedIndexes count]];
-
+
for (NSUInteger i = [selectedIndexes firstIndex]; i != NSNotFound; i = [selectedIndexes indexGreaterThanIndex: i])
[values addObject: [self itemAtRow: i]];
-
+
return values;
}
{
NSIndexSet * selectedIndexes = [self selectedRowIndexes];
NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [selectedIndexes count]]; //take a shot at guessing capacity
-
+
for (NSUInteger i = [selectedIndexes firstIndex]; i != NSNotFound; i = [selectedIndexes indexGreaterThanIndex: i])
{
id item = [self itemAtRow: i];
i +=[groupTorrents count];
}
}
-
+
return torrents;
}
- (void) keyDown: (NSEvent *) event
{
const unichar firstChar = [[event charactersIgnoringModifiers] characterAtIndex: 0];
-
+
if (firstChar == 'f' && [event modifierFlags] & NSAlternateKeyMask && [event modifierFlags] & NSCommandKeyMask)
[fController focusFilterField];
else if (firstChar == ' ')
- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
{
SEL action = [menuItem action];
-
+
if (action == @selector(paste:))
{
if ([[[NSPasteboard generalPasteboard] types] containsObject: NSURLPboardType])
return YES;
}
}
-
+
return NO;
}
-
+
return YES;
}
const NSInteger row = [self rowAtPoint: [self convertPoint: [event locationInWindow] fromView: nil]];
if (row < 0)
return;
-
+
const NSRect rect = [fTorrentCell iconRectForBounds: [self rectOfRow: row]];
if (fActionPopoverShown)
return;
-
+
Torrent * torrent = [self itemAtRow: row];
-
+
NSPopover * popover = [[NSPopover alloc] init];
[popover setBehavior: NSPopoverBehaviorTransient];
InfoOptionsViewController * infoViewController = [[InfoOptionsViewController alloc] init];
[popover setContentViewController: infoViewController];
[popover setDelegate: self];
-
+
[popover showRelativeToRect: rect ofView: self preferredEdge: NSMaxYEdge];
[infoViewController setInfoForTorrents: [NSArray arrayWithObject: torrent]];
[infoViewController updateInfo];
-
+
[infoViewController release];
[popover release];
}
//this method seems to be called when it shouldn't be
if (!fMenuTorrent || ![menu supermenu])
return;
-
+
if (menu == fUploadMenu || menu == fDownloadMenu)
{
NSMenuItem * item;
{
const NSInteger speedLimitActionValue[] = { 0, 5, 10, 20, 30, 40, 50, 75, 100, 150, 200, 250, 500,
750, 1000, 1500, 2000, -1 };
-
+
for (NSInteger i = 0; speedLimitActionValue[i] != -1; i++)
{
item = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: NSLocalizedString(@"%d KB/s",
[item release];
}
}
-
+
const BOOL upload = menu == fUploadMenu;
const BOOL limit = [fMenuTorrent usesSpeedLimit: upload];
-
+
item = [menu itemWithTag: ACTION_MENU_LIMIT_TAG];
[item setState: limit ? NSOnState : NSOffState];
[item setTitle: [NSString stringWithFormat: NSLocalizedString(@"Limit (%d KB/s)",
"torrent action menu -> upload/download limit"), [fMenuTorrent speedLimit: upload]]];
-
+
item = [menu itemWithTag: ACTION_MENU_UNLIMITED_TAG];
[item setState: !limit ? NSOnState : NSOffState];
}
if ([menu numberOfItems] == 4)
{
const float ratioLimitActionValue[] = { 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, -1.0 };
-
+
for (NSInteger i = 0; ratioLimitActionValue[i] != -1.0; i++)
{
item = [[NSMenuItem alloc] initWithTitle: [NSString localizedStringWithFormat: @"%.2f", ratioLimitActionValue[i]]
[item release];
}
}
-
+
const tr_ratiolimit mode = [fMenuTorrent ratioSetting];
-
+
item = [menu itemWithTag: ACTION_MENU_LIMIT_TAG];
[item setState: mode == TR_RATIOLIMIT_SINGLE ? NSOnState : NSOffState];
[item setTitle: [NSString localizedStringWithFormat: NSLocalizedString(@"Stop at Ratio (%.2f)",
"torrent action menu -> ratio stop"), [fMenuTorrent ratioLimit]]];
-
+
item = [menu itemWithTag: ACTION_MENU_UNLIMITED_TAG];
[item setState: mode == TR_RATIOLIMIT_UNLIMITED ? NSOnState : NSOffState];
-
+
item = [menu itemWithTag: ACTION_MENU_GLOBAL_TAG];
[item setState: mode == TR_RATIOLIMIT_GLOBAL ? NSOnState : NSOffState];
}
else if (menu == fPriorityMenu)
{
const tr_priority_t priority = [fMenuTorrent priority];
-
+
NSMenuItem * item = [menu itemWithTag: ACTION_MENU_PRIORITY_HIGH_TAG];
[item setState: priority == TR_PRI_HIGH ? NSOnState : NSOffState];
-
+
item = [menu itemWithTag: ACTION_MENU_PRIORITY_NORMAL_TAG];
[item setState: priority == TR_PRI_NORMAL ? NSOnState : NSOffState];
-
+
item = [menu itemWithTag: ACTION_MENU_PRIORITY_LOW_TAG];
[item setState: priority == TR_PRI_LOW ? NSOnState : NSOffState];
}
{
const BOOL limit = [sender tag] == ACTION_MENU_LIMIT_TAG;
[fMenuTorrent setUseSpeedLimit: limit upload: [sender menu] == fUploadMenu];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
}
const BOOL upload = [sender menu] == fUploadMenu;
[fMenuTorrent setUseSpeedLimit: YES upload: upload];
[fMenuTorrent setSpeedLimit: [[sender representedObject] intValue] upload: upload];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
}
- (void) setGlobalLimit: (id) sender
{
[fMenuTorrent setUseGlobalSpeedLimit: [(NSButton *)sender state] != NSOnState];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
}
default:
return;
}
-
+
[fMenuTorrent setRatioSetting: mode];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
}
{
[fMenuTorrent setRatioSetting: TR_RATIOLIMIT_SINGLE];
[fMenuTorrent setRatioLimit: [[sender representedObject] floatValue]];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateOptions" object: nil];
}
NSAssert1(NO, @"Unknown priority: %ld", [sender tag]);
priority = TR_PRI_NORMAL;
}
-
+
[fMenuTorrent setPriority: priority];
-
+
[[NSNotificationCenter defaultCenter] postNotificationName: @"UpdateUI" object: nil];
}
NSMutableArray * progressMarks = [NSMutableArray arrayWithCapacity: 16];
for (NSAnimationProgress i = 0.0625; i <= 1.0; i += 0.0625)
[progressMarks addObject: [NSNumber numberWithFloat: i]];
-
+
//this stops a previous animation
[fPiecesBarAnimation release];
fPiecesBarAnimation = [[NSAnimation alloc] initWithDuration: TOGGLE_PROGRESS_SECONDS animationCurve: NSAnimationEaseIn];
[fPiecesBarAnimation setAnimationBlockingMode: NSAnimationNonblocking];
[fPiecesBarAnimation setProgressMarks: progressMarks];
[fPiecesBarAnimation setDelegate: self];
-
+
[fPiecesBarAnimation startAnimation];
}
fPiecesBarPercent = progress;
else
fPiecesBarPercent = 1.0 - progress;
-
+
[self setNeedsDisplay: YES];
}
}
{
NSParameterAssert(row >= 0);
NSParameterAssert(row < [self numberOfRows]);
-
+
[self selectRowIndexes: [NSIndexSet indexSetWithIndex: row] byExtendingSelection: NO];
-
+
const NSRect rowRect = [self rectOfRow: row];
const NSRect viewRect = [[self superview] frame];
NSInteger row = [self rowAtPoint: point];
if (row < 0 || [[self itemAtRow: row] isKindOfClass: [Torrent class]])
return NO;
-
+
NSString * ident = [[[self tableColumns] objectAtIndex: [self columnAtPoint: point]] identifier];
return [ident isEqualToString: @"UL"] || [ident isEqualToString: @"UL Image"]
|| [ident isEqualToString: @"DL"] || [ident isEqualToString: @"DL Image"];
- (void) setGroupStatusColumns
{
const BOOL ratio = [fDefaults boolForKey: @"DisplayGroupRowRatio"];
-
+
[[self tableColumnWithIdentifier: @"DL"] setHidden: ratio];
[[self tableColumnWithIdentifier: @"DL Image"] setHidden: ratio];
}
{
NSMutableParagraphStyle * paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[paragraphStyle setLineBreakMode: NSLineBreakByTruncatingTail];
-
+
fNameAttributes = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
[NSFont messageFontOfSize: 12.0], NSFontAttributeName,
paragraphStyle, NSParagraphStyleAttributeName, nil];
-
+
fStatusAttributes = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
[NSFont messageFontOfSize: 9.0], NSFontAttributeName,
paragraphStyle, NSParagraphStyleAttributeName, nil];
-
+
[paragraphStyle release];
}
return self;
{
[fNameAttributes release];
[fStatusAttributes release];
-
+
[super dealloc];
}
- (id) copyWithZone: (NSZone *) zone
{
TrackerCell * copy = [super copyWithZone: zone];
-
+
copy->fNameAttributes = [fNameAttributes retain];
copy->fStatusAttributes = [fStatusAttributes retain];
-
+
return copy;
}
nameColor = [NSColor controlTextColor];
statusColor = [NSColor darkGrayColor];
}
-
+
[fNameAttributes setObject: nameColor forKey: NSForegroundColorAttributeName];
[fStatusAttributes setObject: statusColor forKey: NSForegroundColorAttributeName];
-
+
TrackerNode * node = (TrackerNode *)[self objectValue];
-
+
//name
NSAttributedString * nameString = [self attributedName];
const NSRect nameRect = [self rectForNameWithString: nameString inBounds: cellFrame];
[nameString drawInRect: nameRect];
-
+
//count strings
NSAttributedString * seederString = [self attributedCount: [node totalSeeders]];
const NSRect seederRect = [self rectForCountWithString: seederString withAboveRect: nameRect inBounds: cellFrame];
[seederString drawInRect: seederRect];
-
+
NSAttributedString * leecherString = [self attributedCount: [node totalLeechers]];
const NSRect leecherRect = [self rectForCountWithString: leecherString withAboveRect: seederRect inBounds: cellFrame];
[leecherString drawInRect: leecherRect];
-
+
NSAttributedString * downloadedString = [self attributedCount: [node totalDownloaded]];
const NSRect downloadedRect = [self rectForCountWithString: downloadedString withAboveRect: leecherRect inBounds: cellFrame];
[downloadedString drawInRect: downloadedRect];
-
+
//count label strings
NSString * seederLabelBaseString = [NSLocalizedString(@"Seeders", "tracker peer stat") stringByAppendingFormat: @": "];
NSAttributedString * seederLabelString = [self attributedStatusWithString: seederLabelBaseString];
const NSRect seederLabelRect = [self rectForCountLabelWithString: seederLabelString withRightRect: seederRect
inBounds: cellFrame];
[seederLabelString drawInRect: seederLabelRect];
-
+
NSString * leecherLabelBaseString = [NSLocalizedString(@"Leechers", "tracker peer stat") stringByAppendingFormat: @": "];
NSAttributedString * leecherLabelString = [self attributedStatusWithString: leecherLabelBaseString];
const NSRect leecherLabelRect = [self rectForCountLabelWithString: leecherLabelString withRightRect: leecherRect
inBounds: cellFrame];
[leecherLabelString drawInRect: leecherLabelRect];
-
+
NSString * downloadedLabelBaseString = [NSLocalizedString(@"Downloaded", "tracker peer stat") stringByAppendingFormat: @": "];
NSAttributedString * downloadedLabelString = [self attributedStatusWithString: downloadedLabelBaseString];
const NSRect downloadedLabelRect = [self rectForCountLabelWithString: downloadedLabelString withRightRect: downloadedRect
inBounds: cellFrame];
[downloadedLabelString drawInRect: downloadedLabelRect];
-
+
//status strings
NSAttributedString * lastAnnounceString = [self attributedStatusWithString: [node lastAnnounceStatusString]];
const NSRect lastAnnounceRect = [self rectForStatusWithString: lastAnnounceString withAboveRect: nameRect
withRightRect: seederLabelRect inBounds: cellFrame];
[lastAnnounceString drawInRect: lastAnnounceRect];
-
+
NSAttributedString * nextAnnounceString = [self attributedStatusWithString: [node nextAnnounceStatusString]];
const NSRect nextAnnounceRect = [self rectForStatusWithString: nextAnnounceString withAboveRect: lastAnnounceRect
withRightRect: leecherLabelRect inBounds: cellFrame];
[nextAnnounceString drawInRect: nextAnnounceRect];
-
+
NSAttributedString * lastScrapeString = [self attributedStatusWithString: [node lastScrapeStatusString]];
const NSRect lastScrapeRect = [self rectForStatusWithString: lastScrapeString withAboveRect: nextAnnounceRect
withRightRect: downloadedLabelRect inBounds: cellFrame];
{
//don't try to parse ip address
const BOOL separable = !tr_addressIsIP([host UTF8String]);
-
+
NSArray * hostComponents = separable ? [host componentsSeparatedByString: @"."] : nil;
-
+
//let's try getting the tracker address without using any subdomains
NSString * baseAddress;
if (separable && [hostComponents count] > 1)
[hostComponents objectAtIndex: [hostComponents count]-2], [hostComponents lastObject]];
else
baseAddress = [NSString stringWithFormat: @"http://%@", host];
-
+
icon = [fTrackerIconCache objectForKey: baseAddress];
if (!icon && ![fTrackerIconLoading containsObject: baseAddress])
{
[NSThread detachNewThreadSelector: @selector(loadTrackerIcon:) toTarget: self withObject: baseAddress];
}
}
-
+
return (icon && icon != [NSNull null]) ? icon : [NSImage imageNamed: @"FavIcon"];
}
{
//try favicon.png
NSURL * favIconUrl = [NSURL URLWithString: [baseAddress stringByAppendingPathComponent: @"favicon.png"]];
-
+
NSURLRequest * request = [NSURLRequest requestWithURL: favIconUrl cachePolicy: NSURLRequestUseProtocolCachePolicy
timeoutInterval: 30.0];
NSData * iconData = [NSURLConnection sendSynchronousRequest: request returningResponse: NULL error: NULL];
NSImage * icon = [[NSImage alloc] initWithData: iconData];
-
+
//try favicon.ico
if (!icon)
{
favIconUrl = [NSURL URLWithString: [baseAddress stringByAppendingPathComponent: @"favicon.ico"]];
-
+
request = [NSURLRequest requestWithURL: favIconUrl cachePolicy: NSURLRequestUseProtocolCachePolicy
timeoutInterval: 30.0];
iconData = [NSURLConnection sendSynchronousRequest: request returningResponse: NULL error: NULL];
icon = [[NSImage alloc] initWithData: iconData];
}
-
+
if (icon)
{
[fTrackerIconCache setObject: icon forKey: baseAddress];
[icon release];
-
+
[[self controlView] setNeedsDisplay: YES];
}
else
[fTrackerIconCache setObject: [NSNull null] forKey: baseAddress];
-
+
[fTrackerIconLoading removeObject: baseAddress];
}
}
NSRect result;
result.origin.x = NSMinX(bounds) + PADDING_HORIZONAL + ICON_SIZE + PADDING_BETWEEN_ICON_AND_NAME;
result.origin.y = NSMinY(bounds) + PADDING_ABOVE_NAME;
-
+
result.size.height = [string size].height;
result.size.width = NSMaxX(bounds) - NSMinX(result) - PADDING_HORIZONAL;
-
+
return result;
}
NSRect result = rightRect;
result.size.width = [string size].width;
result.origin.x -= NSWidth(result);
-
+
return result;
}
NSRect result;
result.origin.x = NSMinX(bounds) + PADDING_STATUS_HORIZONAL;
result.origin.y = NSMaxY(aboveRect) + PADDING_BETWEEN_LINES;
-
+
result.size.height = [string size].height;
result.size.width = NSMinX(rightRect) - PADDING_BETWEEN_LINES_ON_SAME_LINE - NSMinX(result);
-
+
return result;
}
@interface TrackerNode : NSObject
{
tr_tracker_stat fStat;
-
+
Torrent * fTorrent;
}
fStat = *stat;
fTorrent = torrent; //weak reference
}
-
+
return self;
}
{
if (self == object)
return YES;
-
+
if (![object isKindOfClass: [self class]])
return NO;
-
+
if ([self torrent] != [object torrent])
return NO;
-
+
return [self tier] == [object tier] && [[self fullAnnounceAddress] isEqualToString: [object fullAnnounceAddress]];
}
[dateFormatter setDateStyle: NSDateFormatterFullStyle];
[dateFormatter setTimeStyle: NSDateFormatterShortStyle];
[dateFormatter setDoesRelativeDateFormatting: YES];
-
+
dateString = [dateFormatter stringFromDate: [NSDate dateWithTimeIntervalSince1970: fStat.lastAnnounceTime]];
[dateFormatter release];
}
else
dateString = NSLocalizedString(@"N/A", "Tracker last announce");
-
+
NSString * baseString;
if (fStat.hasAnnounced && fStat.lastAnnounceTimedOut)
baseString = [NSLocalizedString(@"Announce timed out", "Tracker last announce") stringByAppendingFormat: @": %@", dateString];
else if (fStat.hasAnnounced && !fStat.lastAnnounceSucceeded)
{
baseString = NSLocalizedString(@"Announce error", "Tracker last announce");
-
+
NSString * errorString = [NSString stringWithUTF8String: fStat.lastAnnounceResult];
if ([errorString isEqualToString: @""])
baseString = [baseString stringByAppendingFormat: @": %@", dateString];
baseString = [baseString stringByAppendingFormat: @" (%@)", peerString];
}
}
-
+
return baseString;
}
{
case TR_TRACKER_ACTIVE:
return [NSLocalizedString(@"Announce in progress", "Tracker next announce") stringByAppendingEllipsis];
-
+
case TR_TRACKER_WAITING:
{
const NSTimeInterval nextAnnounceTimeLeft = fStat.nextAnnounceTime - [[NSDate date] timeIntervalSince1970];
-
+
NSString *timeString;
if ([NSApp isOnYosemiteOrBetter]) {
static NSDateComponentsFormatter *formatter;
formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorDropLeading;
formatter.collapsesLargestUnit = YES;
});
-
+
timeString = [formatter stringFromTimeInterval: nextAnnounceTimeLeft];
}
else {
}
case TR_TRACKER_QUEUED:
return [NSLocalizedString(@"Announce is queued", "Tracker next announce") stringByAppendingEllipsis];
-
+
case TR_TRACKER_INACTIVE:
return fStat.isBackup ? NSLocalizedString(@"Tracker will be used as a backup", "Tracker next announce")
: NSLocalizedString(@"Announce not scheduled", "Tracker next announce");
-
+
default:
NSAssert1(NO, @"unknown announce state: %d", fStat.announceState);
return nil;
[dateFormatter setDateStyle: NSDateFormatterFullStyle];
[dateFormatter setTimeStyle: NSDateFormatterShortStyle];
[dateFormatter setDoesRelativeDateFormatting: YES];
-
+
dateString = [dateFormatter stringFromDate: [NSDate dateWithTimeIntervalSince1970: fStat.lastScrapeTime]];
[dateFormatter release];
}
else
dateString = NSLocalizedString(@"N/A", "Tracker last scrape");
-
+
NSString * baseString;
if (fStat.hasScraped && fStat.lastScrapeTimedOut)
baseString = [NSLocalizedString(@"Scrape timed out", "Tracker last scrape") stringByAppendingFormat: @": %@", dateString];
else if (fStat.hasScraped && !fStat.lastScrapeSucceeded)
{
baseString = NSLocalizedString(@"Scrape error", "Tracker last scrape");
-
+
NSString * errorString = [NSString stringWithUTF8String: fStat.lastScrapeResult];
if ([errorString isEqualToString: @""])
baseString = [baseString stringByAppendingFormat: @": %@", dateString];
}
else
baseString = [NSLocalizedString(@"Last Scrape", "Tracker last scrape") stringByAppendingFormat: @": %@", dateString];
-
+
return baseString;
}
else
[addresses addObject: [(TrackerNode *)item fullAnnounceAddress]];
}
-
+
NSString * text = [addresses componentsJoinedByString: @"\n"];
-
+
NSPasteboard * pb = [NSPasteboard generalPasteboard];
[pb clearContents];
[pb writeObjects: [NSArray arrayWithObject: text]];
- (void) paste: (id) sender
{
NSAssert(fTorrent != nil, @"no torrent but trying to paste; should not be able to call this method");
-
+
BOOL added = NO;
-
+
NSArray * items = [[NSPasteboard generalPasteboard] readObjectsForClasses: [NSArray arrayWithObject: [NSString class]] options: nil];
NSAssert(items != nil, @"no string items to paste; should not be able to call this method");
-
+
for (NSString * pbItem in items)
{
for (NSString * item in [pbItem componentsSeparatedByString: @"\n"])
if ([fTorrent addTrackerToNewTier: item])
added = YES;
}
-
+
//none added
if (!added)
NSBeep();
- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
{
const SEL action = [menuItem action];
-
+
if (action == @selector(copy:))
return [self numberOfSelectedRows] > 0;
-
+
if (action == @selector(paste:))
return fTorrent && [[NSPasteboard generalPasteboard] canReadObjectForClasses: [NSArray arrayWithObject: [NSString class]] options: nil];
-
+
return YES;
}
@interface URLSheetWindowController : NSWindowController
{
- IBOutlet NSTextField * fLabelField;
+ IBOutlet NSTextField * fLabelField;
IBOutlet NSTextField * fTextField;
IBOutlet NSButton * fOpenButton, * fCancelButton;
-
+
Controller * fController;
}
- (void) awakeFromNib
{
[fLabelField setStringValue: NSLocalizedString(@"Internet address of torrent file:", "URL sheet label")];
-
+
if (urlString)
{
[fTextField setStringValue: urlString];
[fTextField selectText: self];
-
+
[self updateOpenButtonForURL: urlString];
}
-
+
[fOpenButton setTitle: NSLocalizedString(@"Open", "URL sheet button")];
[fCancelButton setTitle: NSLocalizedString(@"Cancel", "URL sheet button")];
-
+
[fOpenButton sizeToFit];
[fCancelButton sizeToFit];
-
+
//size the two buttons the same
NSRect openFrame = [fOpenButton frame];
openFrame.size.width += 10.0;
NSRect cancelFrame = [fCancelButton frame];
cancelFrame.size.width += 10.0;
-
+
if (NSWidth(openFrame) > NSWidth(cancelFrame))
cancelFrame.size.width = NSWidth(openFrame);
else
openFrame.size.width = NSWidth(cancelFrame);
-
+
openFrame.origin.x = NSWidth([[self window] frame]) - NSWidth(openFrame) - 20.0 + 6.0; //I don't know why the extra 6.0 is needed
[fOpenButton setFrame: openFrame];
-
+
cancelFrame.origin.x = NSMinX(openFrame) - NSWidth(cancelFrame);
[fCancelButton setFrame: cancelFrame];
}
if (prefixRange.location != NSNotFound && [string length] == NSMaxRange(prefixRange))
enable = NO;
}
-
+
[fOpenButton setEnabled: enable];
}
[fWebSeeds enumerateObjectsAtIndexes: indexes options: 0 usingBlock: ^(NSDictionary * webSeed, NSUInteger idx, BOOL * stop) {
[addresses addObject: [webSeed objectForKey: @"Address"]];
}];
-
+
NSString * text = [addresses componentsJoinedByString: @"\n"];
-
+
NSPasteboard * pb = [NSPasteboard generalPasteboard];
[pb clearContents];
[pb writeObjects: [NSArray arrayWithObject: text]];
- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
{
const SEL action = [menuItem action];
-
+
if (action == @selector(copy:))
return [self numberOfSelectedRows] > 0;
-
+
return YES;
}