source: trunk/macosx/Controller.m @ 3886

Last change on this file since 3886 was 3886, checked in by livings124, 15 years ago

add stats window...right now the values mean nothing

  • Property svn:keywords set to Date Rev Author Id
File size: 124.1 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 3886 2007-11-19 18:13:41Z livings124 $
3 *
4 * Copyright (c) 2005-2007 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import <IOKit/IOMessage.h>
26
27#import "Controller.h"
28#import "Torrent.h"
29#import "TorrentCell.h"
30#import "TorrentTableView.h"
31#import "CreatorWindowController.h"
32#import "StatsWindowController.h"
33#import "AboutWindowController.h"
34#import "NSApplicationAdditions.h"
35#import "NSStringAdditions.h"
36#import "UKKQueue.h"
37#import "ActionMenuSpeedToDisplayLimitTransformer.h"
38#import "ActionMenuRatioToDisplayRatioTransformer.h"
39#import "ExpandedPathToPathTransformer.h"
40#import "ExpandedPathToIconTransformer.h"
41#import "SpeedLimitToTurtleIconTransformer.h"
42
43#import <Sparkle/Sparkle.h>
44
45#define TOOLBAR_CREATE          @"Toolbar Create"
46#define TOOLBAR_OPEN            @"Toolbar Open"
47#define TOOLBAR_REMOVE          @"Toolbar Remove"
48#define TOOLBAR_INFO            @"Toolbar Info"
49#define TOOLBAR_PAUSE_ALL       @"Toolbar Pause All"
50#define TOOLBAR_RESUME_ALL      @"Toolbar Resume All"
51#define TOOLBAR_PAUSE_SELECTED  @"Toolbar Pause Selected"
52#define TOOLBAR_RESUME_SELECTED @"Toolbar Resume Selected"
53#define TOOLBAR_FILTER          @"Toolbar Toggle Filter"
54
55#define SORT_DATE       @"Date"
56#define SORT_NAME       @"Name"
57#define SORT_STATE      @"State"
58#define SORT_PROGRESS   @"Progress"
59#define SORT_TRACKER    @"Tracker"
60#define SORT_ORDER      @"Order"
61
62typedef enum
63{
64    SORT_ORDER_TAG = 0,
65    SORT_DATE_TAG = 1,
66    SORT_NAME_TAG = 2,
67    SORT_PROGRESS_TAG = 3,
68    SORT_STATE_TAG = 4,
69    SORT_TRACKER_TAG = 5
70} sortTag;
71
72#define FILTER_NONE     @"None"
73#define FILTER_DOWNLOAD @"Download"
74#define FILTER_SEED     @"Seed"
75#define FILTER_PAUSE    @"Pause"
76
77#define FILTER_TYPE_NAME    @"Name"
78#define FILTER_TYPE_TRACKER @"Tracker"
79
80#define FILTER_TYPE_TAG_NAME    401
81#define FILTER_TYPE_TAG_TRACKER 402
82
83#define GROWL_DOWNLOAD_COMPLETE @"Download Complete"
84#define GROWL_SEEDING_COMPLETE  @"Seeding Complete"
85#define GROWL_AUTO_ADD          @"Torrent Auto Added"
86#define GROWL_AUTO_SPEED_LIMIT  @"Speed Limit Auto Changed"
87
88#define TORRENT_TABLE_VIEW_DATA_TYPE    @"TorrentTableViewDataType"
89
90#define ROW_HEIGHT_REGULAR      65.0
91#define ROW_HEIGHT_SMALL        40.0
92#define WINDOW_REGULAR_WIDTH    468.0
93
94#define SEARCH_FILTER_MIN_WIDTH 55.0
95#define SEARCH_FILTER_MAX_WIDTH 115.0
96
97#define UPDATE_UI_SECONDS           1.0
98#define AUTO_SPEED_LIMIT_SECONDS    5.0
99
100#define DOCK_SEEDING_TAG        101
101#define DOCK_DOWNLOADING_TAG    102
102
103#define SUPPORT_FOLDER  @"/Library/Application Support/Transmission/Transfers.plist"
104
105#define WEBSITE_URL @"http://transmission.m0k.org/"
106#define FORUM_URL   @"http://transmission.m0k.org/forum/"
107#define DONATE_URL  @"http://transmission.m0k.org/donate.php"
108
109void sleepCallBack(void * controller, io_service_t y, natural_t messageType, void * messageArgument)
110{
111    [(Controller *)controller sleepCallBack: messageType argument: messageArgument];
112}
113
114@implementation Controller
115
116+ (void) initialize
117{
118    //make sure another Transmission.app isn't running already
119    NSString * bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
120    int processIdentifier = [[NSProcessInfo processInfo] processIdentifier];
121
122    NSDictionary * dic;
123    NSEnumerator * enumerator = [[[NSWorkspace sharedWorkspace] launchedApplications] objectEnumerator];
124    while ((dic = [enumerator nextObject]))
125    {
126        if ([[dic objectForKey: @"NSApplicationBundleIdentifier"] isEqualToString: bundleIdentifier]
127            && [[dic objectForKey: @"NSApplicationProcessIdentifier"] intValue] != processIdentifier)
128        {
129            NSAlert * alert = [[NSAlert alloc] init];
130            [alert addButtonWithTitle: NSLocalizedString(@"Quit", "Transmission already running alert -> button")];
131            [alert setMessageText: NSLocalizedString(@"Transmission is already running.",
132                                                    "Transmission already running alert -> title")];
133            [alert setInformativeText: NSLocalizedString(@"There is already a copy of Transmission running. "
134                "This copy cannot be opened until that instance is quit.", "Transmission already running alert -> message")];
135            [alert setAlertStyle: NSWarningAlertStyle];
136           
137            [alert runModal];
138            [alert release];
139           
140            //kill ourselves right away
141            exit(0);
142        }
143    }
144   
145    [[NSUserDefaults standardUserDefaults] registerDefaults: [NSDictionary dictionaryWithContentsOfFile:
146        [[NSBundle mainBundle] pathForResource: @"Defaults" ofType: @"plist"]]];
147   
148    //set custom value transformers
149    ActionMenuSpeedToDisplayLimitTransformer * limitTransformer =
150                        [[[ActionMenuSpeedToDisplayLimitTransformer alloc] init] autorelease];
151    [NSValueTransformer setValueTransformer: limitTransformer forName: @"ActionMenuSpeedToDisplayLimitTransformer"];
152   
153    ActionMenuRatioToDisplayRatioTransformer * ratioTransformer =
154                        [[[ActionMenuRatioToDisplayRatioTransformer alloc] init] autorelease];
155    [NSValueTransformer setValueTransformer: ratioTransformer forName: @"ActionMenuRatioToDisplayRatioTransformer"];
156   
157    ExpandedPathToPathTransformer * pathTransformer =
158                        [[[ExpandedPathToPathTransformer alloc] init] autorelease];
159    [NSValueTransformer setValueTransformer: pathTransformer forName: @"ExpandedPathToPathTransformer"];
160   
161    ExpandedPathToIconTransformer * iconTransformer =
162                        [[[ExpandedPathToIconTransformer alloc] init] autorelease];
163    [NSValueTransformer setValueTransformer: iconTransformer forName: @"ExpandedPathToIconTransformer"];
164   
165    SpeedLimitToTurtleIconTransformer * speedLimitIconTransformer =
166                        [[[SpeedLimitToTurtleIconTransformer alloc] init] autorelease];
167    [NSValueTransformer setValueTransformer: speedLimitIconTransformer forName: @"SpeedLimitToTurtleIconTransformer"];
168}
169
170- (id) init
171{
172    if ((self = [super init]))
173    {
174        fLib = tr_init("macosx");
175       
176        [NSApp setDelegate: self];
177       
178        fTorrents = [[NSMutableArray alloc] init];
179        fDisplayedTorrents = [[NSMutableArray alloc] init];
180       
181        fDefaults = [NSUserDefaults standardUserDefaults];
182       
183        fMessageController = [[MessageWindowController alloc] init];
184        fInfoController = [[InfoWindowController alloc] init];
185        fPrefsController = [[PrefsController alloc] initWithHandle: fLib];
186       
187        fBadger = [[Badger alloc] initWithLib: fLib];
188       
189        fIPCController = [[IPCController alloc] init];
190        [fIPCController setDelegate: self];
191        fRemoteQuit = NO;
192       
193        [GrowlApplicationBridge setGrowlDelegate: self];
194        [[UKKQueue sharedFileWatcher] setDelegate: self];
195    }
196    return self;
197}
198
199- (void) awakeFromNib
200{
201    NSToolbar * toolbar = [[NSToolbar alloc] initWithIdentifier: @"Transmission Toolbar"];
202    [toolbar setDelegate: self];
203    [toolbar setAllowsUserCustomization: YES];
204    [toolbar setAutosavesConfiguration: YES];
205    [fWindow setToolbar: toolbar];
206    [toolbar release];
207   
208    [fWindow setDelegate: self]; //do manually to avoid placement issue
209   
210    [fWindow makeFirstResponder: fTableView];
211    [fWindow setExcludedFromWindowsMenu: YES];
212   
213    //set table size
214    if ([fDefaults boolForKey: @"SmallView"])
215        [fTableView setRowHeight: ROW_HEIGHT_SMALL];
216   
217    //window min height
218    NSSize contentMinSize = [fWindow contentMinSize];
219    contentMinSize.height = [[fWindow contentView] frame].size.height - [fScrollView frame].size.height
220                                + [fTableView rowHeight] + [fTableView intercellSpacing].height;
221    [fWindow setContentMinSize: contentMinSize];
222   
223    //set info and filter keyboard shortcuts
224    unichar rightChar = NSRightArrowFunctionKey, leftChar = NSLeftArrowFunctionKey;
225    [fNextInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & rightChar length: 1]];
226    [fPrevInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & leftChar length: 1]];
227   
228    [fNextFilterItem setKeyEquivalent: [NSString stringWithCharacters: & rightChar length: 1]];
229    [fNextFilterItem setKeyEquivalentModifierMask: NSCommandKeyMask | NSAlternateKeyMask];
230    [fPrevFilterItem setKeyEquivalent: [NSString stringWithCharacters: & leftChar length: 1]];
231    [fPrevFilterItem setKeyEquivalentModifierMask: NSCommandKeyMask | NSAlternateKeyMask];
232   
233    //set up filter bar
234    NSView * contentView = [fWindow contentView];
235    NSSize windowSize = [contentView convertSize: [fWindow frame].size fromView: nil];
236    [fFilterBar setHidden: YES];
237   
238    NSRect filterBarFrame = [fFilterBar frame];
239    filterBarFrame.size.width = windowSize.width;
240    [fFilterBar setFrame: filterBarFrame];
241   
242    [contentView addSubview: fFilterBar];
243    [fFilterBar setFrameOrigin: NSMakePoint(0, NSMaxY([contentView frame]))];
244
245    [self showFilterBar: [fDefaults boolForKey: @"FilterBar"] animate: NO];
246   
247    //set up status bar
248    [fStatusBar setHidden: YES];
249   
250    NSRect statusBarFrame = [fStatusBar frame];
251    statusBarFrame.size.width = windowSize.width;
252    [fStatusBar setFrame: statusBarFrame];
253
254    [contentView addSubview: fStatusBar];
255    [fStatusBar setFrameOrigin: NSMakePoint(0, NSMaxY([contentView frame]))];
256    [self showStatusBar: [fDefaults boolForKey: @"StatusBar"] animate: NO];
257
258    [fActionButton setToolTip: NSLocalizedString(@"Shortcuts for changing global settings.",
259                                "Main window -> 1st bottom left button (action) tooltip")];
260    [fSpeedLimitButton setToolTip: NSLocalizedString(@"Speed Limit overrides the total bandwidth limits with its own limits.",
261                                "Main window -> 2nd bottom left button (turtle) tooltip")];
262   
263    [fPrefsController setUpdater: fUpdater];
264   
265    [fTableView setTorrents: fDisplayedTorrents];
266    [[fTableView tableColumnWithIdentifier: @"Torrent"] setDataCell: [[TorrentCell alloc] init]];
267   
268    [fTableView registerForDraggedTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE]];
269    [fWindow registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType, NSURLPboardType, nil]];
270
271    //register for sleep notifications
272    IONotificationPortRef notify;
273    io_object_t iterator;
274    if ((fRootPort = IORegisterForSystemPower(self, & notify, sleepCallBack, & iterator)) != 0)
275        CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify), kCFRunLoopCommonModes);
276    else
277        NSLog(@"Could not IORegisterForSystemPower");
278   
279    //load previous transfers
280    NSArray * history = [[NSArray alloc] initWithContentsOfFile: [NSHomeDirectory() stringByAppendingPathComponent: SUPPORT_FOLDER]];
281   
282    //old version saved transfer info in prefs file
283    if (!history)
284    {
285        if ((history = [fDefaults arrayForKey: @"History"]))
286            [history retain];
287        [fDefaults removeObjectForKey: @"History"];
288    }
289   
290    if (history)
291    {
292        Torrent * torrent;
293        NSDictionary * historyItem;
294        NSEnumerator * enumerator = [history objectEnumerator];
295        while ((historyItem = [enumerator nextObject]))
296            if ((torrent = [[Torrent alloc] initWithHistory: historyItem lib: fLib]))
297            {
298                [fTorrents addObject: torrent];
299                [torrent release];
300            }
301       
302        [history release];
303    }
304   
305    //set filter
306    NSString * filterType = [fDefaults stringForKey: @"Filter"];
307   
308    NSButton * currentFilterButton;
309    if ([filterType isEqualToString: FILTER_PAUSE])
310        currentFilterButton = fPauseFilterButton;
311    else if ([filterType isEqualToString: FILTER_SEED])
312        currentFilterButton = fSeedFilterButton;
313    else if ([filterType isEqualToString: FILTER_DOWNLOAD])
314        currentFilterButton = fDownloadFilterButton;
315    else
316    {
317        //safety
318        if (![filterType isEqualToString: FILTER_NONE])
319            [fDefaults setObject: FILTER_NONE forKey: @"Filter"];
320        currentFilterButton = fNoFilterButton;
321    }
322    [currentFilterButton setState: NSOnState];
323   
324    //set filter search type
325    NSString * filterSearchType = [fDefaults stringForKey: @"FilterSearchType"];
326   
327    NSMenu * filterSearchMenu = [[fSearchFilterField cell] searchMenuTemplate];
328    NSString * filterSearchTypeTitle;
329    if ([filterSearchType isEqualToString: FILTER_TYPE_TRACKER])
330        filterSearchTypeTitle = [[filterSearchMenu itemWithTag: FILTER_TYPE_TAG_TRACKER] title];
331    else
332    {
333        //safety
334        if (![filterType isEqualToString: FILTER_TYPE_NAME])
335            [fDefaults setObject: FILTER_TYPE_NAME forKey: @"FilterSearchType"];
336        filterSearchTypeTitle = [[filterSearchMenu itemWithTag: FILTER_TYPE_TAG_NAME] title];
337    }
338    [[fSearchFilterField cell] setPlaceholderString: filterSearchTypeTitle];
339   
340    //observe notifications
341    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
342   
343    [nc addObserver: self selector: @selector(updateUI)
344                    name: @"UpdateUI" object: nil];
345   
346    [nc addObserver: self selector: @selector(torrentFinishedDownloading:)
347                    name: @"TorrentFinishedDownloading" object: nil];
348   
349    [nc addObserver: self selector: @selector(torrentRestartedDownloading:)
350                    name: @"TorrentRestartedDownloading" object: nil];
351   
352    //avoids need of setting delegate
353    [nc addObserver: self selector: @selector(torrentTableViewSelectionDidChange:)
354                    name: NSTableViewSelectionDidChangeNotification object: fTableView];
355   
356    [nc addObserver: self selector: @selector(updateControlTint:)
357                    name: NSControlTintDidChangeNotification object: nil];
358   
359    [nc addObserver: self selector: @selector(prepareForUpdate:)
360                    name: SUUpdaterWillRestartNotification object: nil];
361    fUpdateInProgress = NO;
362   
363    [nc addObserver: self selector: @selector(autoSpeedLimitChange:)
364                    name: @"AutoSpeedLimitChange" object: nil];
365   
366    [nc addObserver: self selector: @selector(changeAutoImport)
367                    name: @"AutoImportSettingChange" object: nil];
368   
369    [nc addObserver: self selector: @selector(setWindowSizeToFit)
370                    name: @"AutoSizeSettingChange" object: nil];
371   
372    [nc addObserver: fWindow selector: @selector(makeKeyWindow)
373                    name: @"MakeWindowKey" object: nil];
374   
375    //check if torrent should now start
376    [nc addObserver: self selector: @selector(torrentStoppedForRatio:)
377                    name: @"TorrentStoppedForRatio" object: nil];
378   
379    [nc addObserver: self selector: @selector(updateTorrentsInQueue)
380                    name: @"UpdateQueue" object: nil];
381   
382    //open newly created torrent file
383    [nc addObserver: self selector: @selector(beginCreateFile:)
384                    name: @"BeginCreateTorrentFile" object: nil];
385   
386    //open newly created torrent file
387    [nc addObserver: self selector: @selector(openCreatedFile:)
388                    name: @"OpenCreatedTorrentFile" object: nil];
389
390    //timer to update the interface every second
391    [self updateUI];
392    fTimer = [NSTimer scheduledTimerWithTimeInterval: UPDATE_UI_SECONDS target: self
393        selector: @selector(updateUI) userInfo: nil repeats: YES];
394    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSModalPanelRunLoopMode];
395    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSEventTrackingRunLoopMode];
396   
397    [self applyFilter: nil];
398   
399    [fWindow makeKeyAndOrderFront: nil];
400   
401    if ([fDefaults boolForKey: @"InfoVisible"])
402        [self showInfo: nil];
403   
404    //timer to auto toggle speed limit
405    [self autoSpeedLimitChange: nil];
406    fSpeedLimitTimer = [NSTimer scheduledTimerWithTimeInterval: AUTO_SPEED_LIMIT_SECONDS target: self
407        selector: @selector(autoSpeedLimit) userInfo: nil repeats: YES];
408    [[NSRunLoop currentRunLoop] addTimer: fSpeedLimitTimer forMode: NSModalPanelRunLoopMode];
409    [[NSRunLoop currentRunLoop] addTimer: fSpeedLimitTimer forMode: NSEventTrackingRunLoopMode];
410}
411
412- (void) applicationDidFinishLaunching: (NSNotification *) notification
413{
414    [NSApp setServicesProvider: self];
415   
416    //register for dock icon drags
417    [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self
418        andSelector: @selector(handleOpenContentsEvent:replyEvent:)
419        forEventClass: kCoreEventClass andEventID: kAEOpenContents];
420   
421    //auto importing
422    [self checkAutoImportDirectory];
423}
424
425- (BOOL) applicationShouldHandleReopen: (NSApplication *) app hasVisibleWindows: (BOOL) visibleWindows
426{
427    if (![fWindow isVisible] && ![[fPrefsController window] isVisible])
428        [fWindow makeKeyAndOrderFront: nil];
429    return NO;
430}
431
432- (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication *) sender
433{
434    if (!fUpdateInProgress && !fRemoteQuit && [fDefaults boolForKey: @"CheckQuit"])
435    {
436        int active = 0, downloading = 0;
437        Torrent * torrent;
438        NSEnumerator * enumerator = [fTorrents objectEnumerator];
439        while ((torrent = [enumerator nextObject]))
440            if ([torrent isActive])
441            {
442                active++;
443                if (![torrent allDownloaded])
444                    downloading++;
445            }
446       
447        if ([fDefaults boolForKey: @"CheckQuitDownloading"] ? downloading > 0 : active > 0)
448        {
449            NSString * message = active == 1
450                ? NSLocalizedString(@"There is an active transfer. Do you really want to quit?",
451                    "Confirm Quit panel -> message")
452                : [NSString stringWithFormat: NSLocalizedString(@"There are %d active transfers. Do you really want to quit?",
453                    "Confirm Quit panel -> message"), active];
454
455            NSBeginAlertSheet(NSLocalizedString(@"Confirm Quit", "Confirm Quit panel -> title"),
456                                NSLocalizedString(@"Quit", "Confirm Quit panel -> button"),
457                                NSLocalizedString(@"Cancel", "Confirm Quit panel -> button"), nil, fWindow, self,
458                                @selector(quitSheetDidEnd:returnCode:contextInfo:), nil, nil, message);
459            return NSTerminateLater;
460        }
461    }
462
463    return NSTerminateNow;
464}
465
466- (void) quitSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo
467{
468    [NSApp replyToApplicationShouldTerminate: returnCode == NSAlertDefaultReturn];
469}
470
471- (void) applicationWillTerminate: (NSNotification *) notification
472{
473    //stop timers and notification checking
474    [[NSNotificationCenter defaultCenter] removeObserver: self];
475   
476    [fTimer invalidate];
477    [fSpeedLimitTimer invalidate];
478    if (fAutoImportTimer)
479    {   
480        if ([fAutoImportTimer isValid])
481            [fAutoImportTimer invalidate];
482        [fAutoImportTimer release];
483    }
484   
485    //remove all torrent downloads
486    if (fPendingTorrentDownloads)
487    {
488        NSEnumerator * downloadEnumerator = [[fPendingTorrentDownloads allValues] objectEnumerator];
489        NSDictionary * downloadDict;
490        NSURLDownload * download;
491        while ((downloadDict = [downloadEnumerator nextObject]))
492        {
493            download = [downloadDict objectForKey: @"Download"];
494            [download cancel];
495            [download release];
496        }
497        [fPendingTorrentDownloads removeAllObjects];
498    }
499   
500    //remove all remaining torrent files in the temporary directory
501    if (fTempTorrentFiles)
502    {
503        NSEnumerator * torrentEnumerator = [fTempTorrentFiles objectEnumerator];
504        NSString * path;
505        while ((path = [torrentEnumerator nextObject]))
506            [[NSFileManager defaultManager] removeFileAtPath: path handler: nil];
507    }
508   
509    //remember window states and close all windows
510    [fDefaults setBool: [[fInfoController window] isVisible] forKey: @"InfoVisible"];
511    [[NSApp windows] makeObjectsPerformSelector: @selector(close)];
512    [self showStatusBar: NO animate: NO];
513    [self showFilterBar: NO animate: NO];
514   
515    //save history
516    [self updateTorrentHistory];
517   
518    [fDisplayedTorrents removeAllObjects];
519    [fTorrents removeAllObjects];
520   
521    //clear badge
522    [fBadger clearBadge];
523   
524    //remaining calls the same as dealloc
525    [fInfoController release];
526    [fMessageController release];
527    [fPrefsController release];
528   
529    [fTorrents release];
530    [fDisplayedTorrents release];
531   
532    [fBadger release];
533    [fOverlayWindow release];
534    [fIPCController release];
535   
536    [fAutoImportedNames release];
537    [fPendingTorrentDownloads release];
538    [fTempTorrentFiles release];
539   
540    //complete cleanup
541    tr_close(fLib);
542}
543
544- (void) handleOpenContentsEvent: (NSAppleEventDescriptor *) event replyEvent: (NSAppleEventDescriptor *) replyEvent
545{
546    NSString * urlString = nil;
547
548    NSAppleEventDescriptor * directObject = [event paramDescriptorForKeyword: keyDirectObject];
549    if ([directObject descriptorType] == typeAEList)
550    {
551        unsigned i;
552        for (i = 1; i <= [directObject numberOfItems]; i++)
553            if ((urlString = [[directObject descriptorAtIndex: i] stringValue]))
554                break;
555    }
556    else
557        urlString = [directObject stringValue];
558   
559    if (urlString)
560        [self openURL: [[[NSURL alloc] initWithString: urlString] autorelease]];
561}
562
563- (void) download: (NSURLDownload *) download decideDestinationWithSuggestedFilename: (NSString *) suggestedName
564{
565    if ([[suggestedName pathExtension] caseInsensitiveCompare: @"torrent"] != NSOrderedSame)
566    {
567        [download cancel];
568       
569        NSRunAlertPanel(NSLocalizedString(@"Torrent download failed",
570            @"Download not a torrent -> title"), [NSString stringWithFormat:
571            NSLocalizedString(@"It appears that the file \"%@\" from %@ is not a torrent file.",
572            @"Download not a torrent -> message"), suggestedName,
573            [[[[download request] URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding]],
574            NSLocalizedString(@"OK", @"Download not a torrent -> button"), nil, nil);
575       
576        [download release];
577    }
578    else
579        [download setDestination: [NSTemporaryDirectory() stringByAppendingPathComponent: [suggestedName lastPathComponent]]
580                    allowOverwrite: NO];
581}
582
583-(void) download: (NSURLDownload *) download didCreateDestination: (NSString *) path
584{
585    if (!fPendingTorrentDownloads)
586        fPendingTorrentDownloads = [[NSMutableDictionary alloc] init];
587   
588    [fPendingTorrentDownloads setObject: [NSDictionary dictionaryWithObjectsAndKeys:
589                    path, @"Path", download, @"Download", nil] forKey: [[download request] URL]];
590}
591
592- (void) download: (NSURLDownload *) download didFailWithError: (NSError *) error
593{
594    NSRunAlertPanel(NSLocalizedString(@"Torrent download failed",
595        @"Torrent download error -> title"), [NSString stringWithFormat:
596        NSLocalizedString(@"The torrent could not be downloaded from %@ because an error occurred (%@).",
597        @"Torrent download failed -> message"),
598        [[[[download request] URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding],
599        [error localizedDescription]], NSLocalizedString(@"OK", @"Torrent download failed -> button"), nil, nil);
600   
601    [fPendingTorrentDownloads removeObjectForKey: [[download request] URL]];
602    [download release];
603}
604
605- (void) downloadDidFinish: (NSURLDownload *) download
606{
607    NSString * path = [[fPendingTorrentDownloads objectForKey: [[download request] URL]] objectForKey: @"Path"];
608   
609    [self openFiles: [NSArray arrayWithObject: path] forcePath: nil ignoreDownloadFolder:
610        ![[fDefaults stringForKey: @"DownloadChoice"] isEqualToString: @"Constant"] deleteTorrentFile: TORRENT_FILE_DELETE];
611   
612    [fPendingTorrentDownloads removeObjectForKey: [[download request] URL]];
613    [download release];
614   
615    //delete temp torrent file on quit
616    if (!fTempTorrentFiles)
617        fTempTorrentFiles = [[NSMutableArray alloc] init];
618    [fTempTorrentFiles addObject: path];
619}
620
621- (void) application: (NSApplication *) app openFiles: (NSArray *) filenames
622{
623    [self openFiles: filenames forcePath: nil ignoreDownloadFolder: NO deleteTorrentFile: TORRENT_FILE_DEFAULT];
624}
625
626- (void) openFiles: (NSArray *) filenames forcePath: (NSString *) path ignoreDownloadFolder: (BOOL) ignore
627        deleteTorrentFile: (torrentFileState) deleteTorrent
628{
629    NSString * downloadChoice = [fDefaults stringForKey: @"DownloadChoice"];
630    if (ignore || (!path && [downloadChoice isEqualToString: @"Ask"]))
631    {
632        [self openFilesAsk: [filenames mutableCopy] deleteTorrentFile: deleteTorrent];
633        return;
634    }
635   
636    #warning make submethod
637    if (!path && [fDefaults boolForKey: @"UseIncompleteDownloadFolder"]
638        && access([[[fDefaults stringForKey: @"IncompleteDownloadFolder"] stringByExpandingTildeInPath] UTF8String], 0))
639    {
640        NSOpenPanel * panel = [NSOpenPanel openPanel];
641       
642        [panel setPrompt: NSLocalizedString(@"Select", "Default incomplete folder cannot be used alert -> prompt")];
643        [panel setAllowsMultipleSelection: NO];
644        [panel setCanChooseFiles: NO];
645        [panel setCanChooseDirectories: YES];
646        [panel setCanCreateDirectories: YES];
647
648        [panel setMessage: NSLocalizedString(@"The incomplete folder cannot be used. Choose a new location or cancel for none.",
649                                        "Default incomplete folder cannot be used alert -> message")];
650       
651        NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
652                                        filenames, @"Filenames",
653                                        [NSNumber numberWithBool: ignore], @"Ignore",
654                                        [NSNumber numberWithInt: deleteTorrent], @"DeleteTorrent", nil];
655       
656        [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: fWindow modalDelegate: self
657                didEndSelector: @selector(incompleteChoiceClosed:returnCode:contextInfo:) contextInfo: dict];
658        return;
659    }
660    if (!path && [downloadChoice isEqualToString: @"Constant"]
661        && access([[[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath] UTF8String], 0))
662    {
663        NSOpenPanel * panel = [NSOpenPanel openPanel];
664       
665        [panel setPrompt: NSLocalizedString(@"Select", "Default folder cannot be used alert -> prompt")];
666        [panel setAllowsMultipleSelection: NO];
667        [panel setCanChooseFiles: NO];
668        [panel setCanChooseDirectories: YES];
669        [panel setCanCreateDirectories: YES];
670
671        [panel setMessage: NSLocalizedString(@"The download folder cannot be used. Choose a new location.",
672                                        "Default folder cannot be used alert -> message")];
673       
674        NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
675                                        filenames, @"Filenames",
676                                        [NSNumber numberWithBool: ignore], @"Ignore",
677                                        [NSNumber numberWithInt: deleteTorrent], @"DeleteTorrent", nil];
678       
679        [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: fWindow modalDelegate: self
680                didEndSelector: @selector(downloadChoiceClosed:returnCode:contextInfo:) contextInfo: dict];
681        return;
682    }
683   
684    Torrent * torrent;
685    NSString * torrentPath;
686    tr_info info;
687    NSEnumerator * enumerator = [filenames objectEnumerator];
688    while ((torrentPath = [enumerator nextObject]))
689    {
690        NSString * location;
691        if (path)
692            location = path;
693        else if ([downloadChoice isEqualToString: @"Constant"])
694            location = [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath];
695        else
696            location = [torrentPath stringByDeletingLastPathComponent];
697       
698        if (tr_torrentParse(fLib, [torrentPath UTF8String], NULL, &info) == TR_EDUPLICATE)
699        {
700            [self duplicateOpenAlert: [NSString stringWithUTF8String: info.name]];
701            tr_metainfoFree(&info);
702            continue;
703        }
704        tr_metainfoFree(&info);
705       
706        if (!(torrent = [[Torrent alloc] initWithPath: torrentPath location: location deleteTorrentFile: deleteTorrent lib: fLib]))
707            continue;
708       
709        //add it to the "File > Open Recent" menu
710        [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
711       
712        [torrent update];
713        [fTorrents addObject: torrent];
714        [torrent release];
715    }
716
717    [self updateTorrentsInQueue];
718}
719
720- (void) openCreatedFile: (NSNotification *) notification
721{
722    NSDictionary * dict = [notification userInfo];
723    [self openFiles: [NSArray arrayWithObject: [dict objectForKey: @"File"]] forcePath: [dict objectForKey: @"Path"]
724            ignoreDownloadFolder: NO deleteTorrentFile: TORRENT_FILE_SAVE];
725    [dict release];
726}
727
728- (void) incompleteChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (NSDictionary *) dictionary
729{
730    if (code == NSOKButton)
731        [fDefaults setObject: [[openPanel filenames] objectAtIndex: 0] forKey: @"IncompleteDownloadFolder"];
732    else
733        [fDefaults setBool: NO forKey: @"UseIncompleteDownloadFolder"];
734   
735    [self performSelectorOnMainThread: @selector(openFilesWithDict:) withObject: dictionary waitUntilDone: NO];
736}
737
738- (void) downloadChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (NSDictionary *) dictionary
739{
740    if (code == NSOKButton)
741    {
742        [fDefaults setObject: [[openPanel filenames] objectAtIndex: 0] forKey: @"DownloadFolder"];
743        [self performSelectorOnMainThread: @selector(openFilesWithDict:) withObject: dictionary waitUntilDone: NO];
744    }
745    else
746        [dictionary release];
747}
748
749- (void) openFilesWithDict: (NSDictionary *) dictionary
750{
751    [self openFiles: [dictionary objectForKey: @"Filenames"] forcePath: nil
752        ignoreDownloadFolder: [[dictionary objectForKey: @"Ignore"] boolValue]
753        deleteTorrentFile: [[dictionary objectForKey: @"DeleteTorrent"] intValue]];
754   
755    [dictionary release];
756}
757
758//called by the main open method to show sheet for choosing download location
759- (void) openFilesAsk: (NSMutableArray *) files deleteTorrentFile: (torrentFileState) deleteTorrent
760{
761    //determine the next file that can be opened
762    NSString * torrentPath;
763    int canAdd;
764    tr_info info;
765    while ([files count] > 0)
766    {
767        torrentPath = [[files objectAtIndex: 0] retain];
768        canAdd = tr_torrentParse(fLib, [torrentPath UTF8String], NULL, &info);
769        if (canAdd == TR_OK)
770            break;
771        else if (canAdd == TR_EDUPLICATE)
772            [self duplicateOpenAlert: [NSString stringWithUTF8String: info.name]];
773        else;
774       
775        tr_metainfoFree(&info);
776        [files removeObjectAtIndex: 0];
777    }
778   
779    //no files left to open
780    if ([files count] <= 0)
781    {
782        [files release];
783        [self updateTorrentHistory];
784        return;
785    }
786   
787    [files removeObjectAtIndex: 0];
788   
789    NSOpenPanel * panel = [NSOpenPanel openPanel];
790
791    [panel setPrompt: NSLocalizedString(@"Select", "Open torrent -> prompt")];
792    [panel setAllowsMultipleSelection: NO];
793    [panel setCanChooseFiles: NO];
794    [panel setCanChooseDirectories: YES];
795    [panel setCanCreateDirectories: YES];
796   
797    [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the download folder for \"%@\"",
798                        "Open torrent -> select destination folder"), [NSString stringWithUTF8String: info.name]]];
799    tr_metainfoFree(&info);
800   
801    NSDictionary * dictionary = [[NSDictionary alloc] initWithObjectsAndKeys: torrentPath, @"Path",
802                                    files, @"Files", [NSNumber numberWithInt: deleteTorrent], @"DeleteTorrent", nil];
803    [torrentPath release];
804   
805    [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: fWindow modalDelegate: self
806            didEndSelector: @selector(folderChoiceClosed:returnCode:contextInfo:) contextInfo: dictionary];
807}
808
809- (void) folderChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (NSDictionary *) dictionary
810{
811    if (code == NSOKButton)
812    {
813        NSString * torrentPath = [dictionary objectForKey: @"Path"];
814        Torrent * torrent = [[Torrent alloc] initWithPath: torrentPath location: [[openPanel filenames] objectAtIndex: 0]
815                            deleteTorrentFile: [[dictionary objectForKey: @"DeleteTorrent"] intValue] lib: fLib];
816       
817        //add it to the "File > Open Recent" menu
818        [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
819       
820        [torrent update];
821        [fTorrents addObject: torrent];
822        [torrent release];
823       
824        [self updateTorrentsInQueue];
825    }
826   
827    [self performSelectorOnMainThread: @selector(openFilesAskWithDict:) withObject: dictionary waitUntilDone: NO];
828}
829
830- (void) openFilesAskWithDict: (NSDictionary *) dictionary
831{
832    [self openFilesAsk: [dictionary objectForKey: @"Files"]
833            deleteTorrentFile: [[dictionary objectForKey: @"DeleteTorrent"] intValue]];
834    [dictionary release];
835}
836
837//called on by applescript
838- (void) open: (NSArray *) files
839{
840    [self performSelectorOnMainThread: @selector(openFiles:) withObject: files waitUntilDone: NO];
841}
842
843- (void) openFiles: (NSArray *) filenames
844{
845    [self openFiles: filenames forcePath: nil ignoreDownloadFolder: NO deleteTorrentFile: TORRENT_FILE_DEFAULT];
846}
847
848- (void) openShowSheet: (id) sender
849{
850    NSOpenPanel * panel = [NSOpenPanel openPanel];
851
852    [panel setAllowsMultipleSelection: YES];
853    [panel setCanChooseFiles: YES];
854    [panel setCanChooseDirectories: NO];
855
856    [panel beginSheetForDirectory: nil file: nil types: [NSArray arrayWithObject: @"torrent"]
857        modalForWindow: fWindow modalDelegate: self didEndSelector: @selector(openSheetClosed:returnCode:contextInfo:)
858        contextInfo: [NSNumber numberWithBool: sender == fOpenIgnoreDownloadFolder]];
859}
860
861- (void) openSheetClosed: (NSOpenPanel *) panel returnCode: (int) code contextInfo: (NSNumber *) ignore
862{
863    if (code == NSOKButton)
864    {
865        NSDictionary * dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:
866                                        [panel filenames], @"Files", ignore, @"Ignore", nil];
867        [self performSelectorOnMainThread: @selector(openFromSheet:) withObject: dictionary waitUntilDone: NO];
868    }
869}
870
871- (void) openFromSheet: (NSDictionary *) dictionary
872{
873    [self openFiles: [dictionary objectForKey: @"Files"] forcePath: nil
874        ignoreDownloadFolder: [[dictionary objectForKey: @"Ignore"] boolValue] deleteTorrentFile: TORRENT_FILE_DEFAULT];
875   
876    [dictionary release];
877}
878
879- (void) duplicateOpenAlert: (NSString *) name
880{
881    if (![fDefaults boolForKey: @"WarningDuplicate"])
882        return;
883   
884    NSAlert * alert = [[NSAlert alloc] init];
885    [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"A transfer of \"%@\" is already running.",
886                            "Open duplicate alert -> title"), name]];
887    [alert setInformativeText:
888            NSLocalizedString(@"The torrent file cannot be opened because it is a duplicate of an already running transfer.",
889                            "Open duplicate alert -> message")];
890    [alert setAlertStyle: NSWarningAlertStyle];
891    [alert addButtonWithTitle: NSLocalizedString(@"OK", "Open duplicate alert -> button")];
892   
893    BOOL onLeopard = [NSApp isOnLeopardOrBetter];
894    if (onLeopard)
895        [alert setShowsSuppressionButton: YES];
896    else
897        [alert addButtonWithTitle: NSLocalizedString(@"Don't Alert Again", "Open duplicate alert -> button")];
898   
899    NSInteger result = [alert runModal];
900    if ((onLeopard ? [[alert suppressionButton] state] == NSOnState : result == NSAlertSecondButtonReturn))
901        [fDefaults setBool: NO forKey: @"WarningDuplicate"];
902    [alert release];
903}
904
905- (void) openURL: (NSURL *) url
906{
907    [[NSURLDownload alloc] initWithRequest: [NSURLRequest requestWithURL: url] delegate: self];
908}
909
910- (void) openURLShowSheet: (id) sender
911{
912    [NSApp beginSheet: fURLSheetWindow modalForWindow: fWindow modalDelegate: self
913            didEndSelector: @selector(urlSheetDidEnd:returnCode:contextInfo:) contextInfo: nil];
914}
915
916- (void) openURLEndSheet: (id) sender
917{
918    [fURLSheetWindow orderOut: sender];
919    [NSApp endSheet: fURLSheetWindow returnCode: 1];
920}
921
922- (void) openURLCancelEndSheet: (id) sender
923{
924    [fURLSheetWindow orderOut: sender];
925    [NSApp endSheet: fURLSheetWindow returnCode: 0];
926}
927
928- (void) urlSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo
929{
930    [fURLSheetTextField selectText: self];
931    if (returnCode != 1)
932        return;
933   
934    NSString * urlString = [fURLSheetTextField stringValue];
935    if (![urlString isEqualToString: @""])
936    {
937        if ([urlString rangeOfString: @"://"].location == NSNotFound)
938        {
939            if ([urlString rangeOfString: @"."].location == NSNotFound)
940            {
941                int beforeCom;
942                if ((beforeCom = [urlString rangeOfString: @"/"].location) != NSNotFound)
943                    urlString = [NSString stringWithFormat: @"http://www.%@.com/%@",
944                                    [urlString substringToIndex: beforeCom],
945                                    [urlString substringFromIndex: beforeCom + 1]];
946                else
947                    urlString = [NSString stringWithFormat: @"http://www.%@.com", urlString];
948            }
949            else
950                urlString = [@"http://" stringByAppendingString: urlString];
951        }
952       
953        NSURL * url = [NSURL URLWithString: urlString];
954        [self performSelectorOnMainThread: @selector(openURL:) withObject: url waitUntilDone: NO];
955    }
956}
957
958- (void) createFile: (id) sender
959{
960    [CreatorWindowController createTorrentFile: fLib];
961}
962
963- (void) resumeSelectedTorrents: (id) sender
964{
965    [self resumeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
966}
967
968- (void) resumeAllTorrents: (id) sender
969{
970    [self resumeTorrents: fTorrents];
971}
972
973- (void) resumeTorrents: (NSArray *) torrents
974{
975    NSEnumerator * enumerator = [torrents objectEnumerator];
976    Torrent * torrent;
977    while ((torrent = [enumerator nextObject]))
978        [torrent setWaitToStart: YES];
979   
980    [self updateTorrentsInQueue];
981}
982
983- (void) resumeSelectedTorrentsNoWait:  (id) sender
984{
985    [self resumeTorrentsNoWait: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
986}
987
988- (void) resumeWaitingTorrents: (id) sender
989{
990    NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [fTorrents count]];
991   
992    NSEnumerator * enumerator = [fTorrents objectEnumerator];
993    Torrent * torrent;
994    while ((torrent = [enumerator nextObject]))
995        if (![torrent isActive] && [torrent waitingToStart])
996            [torrents addObject: torrent];
997   
998    [self resumeTorrentsNoWait: torrents];
999}
1000
1001- (void) resumeTorrentsNoWait: (NSArray *) torrents
1002{
1003    //iterate through instead of all at once to ensure no conflicts
1004    NSEnumerator * enumerator = [torrents objectEnumerator];
1005    Torrent * torrent;
1006    while ((torrent = [enumerator nextObject]))
1007        [torrent startTransfer];
1008   
1009    [self updateUI];
1010    [self applyFilter: nil];
1011    [self updateTorrentHistory];
1012}
1013
1014- (void) stopSelectedTorrents: (id) sender
1015{
1016    [self stopTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
1017}
1018
1019- (void) stopAllTorrents: (id) sender
1020{
1021    [self stopTorrents: fTorrents];
1022}
1023
1024- (void) stopTorrents: (NSArray *) torrents
1025{
1026    //don't want any of these starting then stopping
1027    NSEnumerator * enumerator = [torrents objectEnumerator];
1028    Torrent * torrent;
1029    while ((torrent = [enumerator nextObject]))
1030        [torrent setWaitToStart: NO];
1031
1032    [torrents makeObjectsPerformSelector: @selector(stopTransfer)];
1033   
1034    [self updateUI];
1035    [self applyFilter: nil];
1036    [self updateTorrentHistory];
1037}
1038
1039- (void) removeTorrents: (NSArray *) torrents deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
1040{
1041    [torrents retain];
1042    int active = 0, downloading = 0;
1043
1044    if ([fDefaults boolForKey: @"CheckRemove"])
1045    {
1046        Torrent * torrent;
1047        NSEnumerator * enumerator = [torrents objectEnumerator];
1048        while ((torrent = [enumerator nextObject]))
1049            if ([torrent isActive])
1050            {
1051                active++;
1052                if (![torrent isSeeding])
1053                    downloading++;
1054            }
1055
1056        if ([fDefaults boolForKey: @"CheckRemoveDownloading"] ? downloading > 0 : active > 0)
1057        {
1058            NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
1059                torrents, @"Torrents",
1060                [NSNumber numberWithBool: deleteData], @"DeleteData",
1061                [NSNumber numberWithBool: deleteTorrent], @"DeleteTorrent", nil];
1062
1063            NSString * title, * message;
1064           
1065            int selected = [fTableView numberOfSelectedRows];
1066            if (selected == 1)
1067            {
1068                NSString * torrentName = [[fDisplayedTorrents objectAtIndex: [fTableView selectedRow]] name];
1069               
1070                if (!deleteData && !deleteTorrent)
1071                    title = [NSString stringWithFormat: NSLocalizedString(@"Confirm removal of \"%@\" from the transfer list.",
1072                                "Removal confirm panel -> title"), torrentName];
1073                else if (deleteData && !deleteTorrent)
1074                    title = [NSString stringWithFormat: NSLocalizedString(@"Confirm removal of \"%@\" from the transfer list"
1075                                " and trash data file.", "Removal confirm panel -> title"), torrentName];
1076                else if (!deleteData && deleteTorrent)
1077                    title = [NSString stringWithFormat: NSLocalizedString(@"Confirm removal of \"%@\" from the transfer list"
1078                                " and trash torrent file.", "Removal confirm panel -> title"), torrentName];
1079                else
1080                    title = [NSString stringWithFormat: NSLocalizedString(@"Confirm removal of \"%@\" from the transfer list"
1081                            " and trash both data and torrent files.", "Removal confirm panel -> title"), torrentName];
1082               
1083                message = NSLocalizedString(@"This transfer is active."
1084                            " Once removed, continuing the transfer will require the torrent file.",
1085                            "Removal confirm panel -> message");
1086            }
1087            else
1088            {
1089                if (!deleteData && !deleteTorrent)
1090                    title = [NSString stringWithFormat: NSLocalizedString(@"Confirm removal of %d transfers"
1091                                " from the transfer list.", "Removal confirm panel -> title"), selected];
1092                else if (deleteData && !deleteTorrent)
1093                    title = [NSString stringWithFormat: NSLocalizedString(@"Confirm removal of %d transfers"
1094                                " from the transfer list and trash data file.", "Removal confirm panel -> title"), selected];
1095                else if (!deleteData && deleteTorrent)
1096                    title = [NSString stringWithFormat: NSLocalizedString(@"Confirm removal of %d transfers"
1097                                " from the transfer list and trash torrent file.", "Removal confirm panel -> title"), selected];
1098                else
1099                    title = [NSString stringWithFormat: NSLocalizedString(@"Confirm removal of %d transfers"
1100                                " from the transfer list and trash both data and torrent files.",
1101                                "Removal confirm panel -> title"), selected];
1102               
1103                if (selected == active)
1104                    message = [NSString stringWithFormat: NSLocalizedString(@"There are %d active transfers.",
1105                                "Removal confirm panel -> message part 1"), active];
1106                else
1107                    message = [NSString stringWithFormat: NSLocalizedString(@"There are %d transfers (%d active).",
1108                                "Removal confirm panel -> message part 1"), selected, active];
1109                message = [message stringByAppendingString:
1110                    NSLocalizedString(@" Once removed, continuing the transfers will require the torrent files.",
1111                                "Removal confirm panel -> message part 2")];
1112            }
1113
1114            NSBeginAlertSheet(title, NSLocalizedString(@"Remove", "Removal confirm panel -> button"),
1115                NSLocalizedString(@"Cancel", "Removal confirm panel -> button"), nil, fWindow, self,
1116                nil, @selector(removeSheetDidEnd:returnCode:contextInfo:), dict, message);
1117            return;
1118        }
1119    }
1120   
1121    [self confirmRemoveTorrents: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
1122}
1123
1124- (void) removeSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (NSDictionary *) dict
1125{
1126    NSArray * torrents = [dict objectForKey: @"Torrents"];
1127    BOOL deleteData = [[dict objectForKey: @"DeleteData"] boolValue],
1128        deleteTorrent = [[dict objectForKey: @"DeleteTorrent"] boolValue];
1129    [dict release];
1130   
1131    if (returnCode == NSAlertDefaultReturn)
1132        [self confirmRemoveTorrents: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
1133    else
1134        [torrents release];
1135}
1136
1137- (void) confirmRemoveTorrents: (NSArray *) torrents deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
1138{
1139    //don't want any of these starting then stopping
1140    NSEnumerator * enumerator = [torrents objectEnumerator];
1141    Torrent * torrent;
1142    while ((torrent = [enumerator nextObject]))
1143        [torrent setWaitToStart: NO];
1144
1145    NSNumber * lowestOrderValue = nil, * currentOrderValue;
1146
1147    enumerator = [torrents objectEnumerator];
1148    while ((torrent = [enumerator nextObject]))
1149    {
1150        [torrent stopTransfer];
1151
1152        if (deleteData)
1153            [torrent trashData];
1154        if (deleteTorrent)
1155            [torrent trashTorrent];
1156       
1157        //determine lowest order value
1158        currentOrderValue = [torrent orderValue];
1159        if (!lowestOrderValue || [lowestOrderValue compare: currentOrderValue] == NSOrderedDescending)
1160            lowestOrderValue = currentOrderValue;
1161       
1162        [torrent closeRemoveTorrent];
1163        [fTorrents removeObject: torrent];
1164        [fDisplayedTorrents removeObject: torrent];
1165    }
1166    [torrents release];
1167
1168    //reset the order values if necessary
1169    if ([lowestOrderValue intValue] < [fTorrents count])
1170    {
1171        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1172                                                @"orderValue" ascending: YES] autorelease];
1173        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1174
1175        NSArray * tempTorrents = [fTorrents sortedArrayUsingDescriptors: descriptors];
1176        [descriptors release];
1177
1178        int i;
1179        for (i = [lowestOrderValue intValue]; i < [tempTorrents count]; i++)
1180            [[tempTorrents objectAtIndex: i] setOrderValue: i];
1181    }
1182   
1183    [fTableView deselectAll: nil];
1184   
1185    [self updateUI];
1186    [self applyFilter: nil];
1187    [self updateTorrentHistory];
1188}
1189
1190- (void) removeNoDelete: (id) sender
1191{
1192    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1193                deleteData: NO deleteTorrent: NO];
1194}
1195
1196- (void) removeDeleteData: (id) sender
1197{
1198    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1199                deleteData: YES deleteTorrent: NO];
1200}
1201
1202- (void) removeDeleteTorrent: (id) sender
1203{
1204    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1205                deleteData: NO deleteTorrent: YES];
1206}
1207
1208- (void) removeDeleteDataAndTorrent: (id) sender
1209{
1210    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1211                deleteData: YES deleteTorrent: YES];
1212}
1213
1214- (void) moveDataFiles: (id) sender
1215{
1216    NSOpenPanel * panel = [NSOpenPanel openPanel];
1217    [panel setPrompt: NSLocalizedString(@"Select", "Move torrent -> prompt")];
1218    [panel setAllowsMultipleSelection: NO];
1219    [panel setCanChooseFiles: NO];
1220    [panel setCanChooseDirectories: YES];
1221    [panel setCanCreateDirectories: YES];
1222   
1223    NSArray * torrents = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] retain];
1224    int count = [torrents count];
1225    if (count == 1)
1226        [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the new folder for \"%@\".",
1227                            "Move torrent -> select destination folder"), [[torrents objectAtIndex: 0] name]]];
1228    else
1229        [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the new folder for %d data files.",
1230                            "Move torrent -> select destination folder"), count]];
1231       
1232    [panel beginSheetForDirectory: nil file: nil modalForWindow: fWindow modalDelegate: self
1233        didEndSelector: @selector(moveDataFileChoiceClosed:returnCode:contextInfo:) contextInfo: torrents];
1234}
1235
1236- (void) moveDataFileChoiceClosed: (NSOpenPanel *) panel returnCode: (int) code contextInfo: (NSArray *) torrents
1237{
1238    if (code == NSOKButton)
1239    {
1240        NSEnumerator * enumerator = [torrents objectEnumerator];
1241        Torrent * torrent;
1242        while ((torrent = [enumerator nextObject]))
1243            [torrent moveTorrentDataFileTo: [[panel filenames] objectAtIndex: 0]];
1244    }
1245   
1246    [torrents release];
1247}
1248
1249- (void) copyTorrentFiles: (id) sender
1250{
1251    [self copyTorrentFileForTorrents: [[NSMutableArray alloc] initWithArray:
1252            [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]]];
1253}
1254
1255- (void) copyTorrentFileForTorrents: (NSMutableArray *) torrents
1256{
1257    if ([torrents count] <= 0)
1258    {
1259        [torrents release];
1260        return;
1261    }
1262
1263    Torrent * torrent = [torrents objectAtIndex: 0];
1264
1265    //warn user if torrent file can't be found
1266    if (![[NSFileManager defaultManager] fileExistsAtPath: [torrent torrentLocation]])
1267    {
1268        NSAlert * alert = [[NSAlert alloc] init];
1269        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent file copy alert -> button")];
1270        [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Copy of \"%@\" Cannot Be Created",
1271                                "Torrent file copy alert -> title"), [torrent name]]];
1272        [alert setInformativeText: [NSString stringWithFormat:
1273                NSLocalizedString(@"The torrent file (%@) cannot be found.", "Torrent file copy alert -> message"),
1274                                    [torrent torrentLocation]]];
1275        [alert setAlertStyle: NSWarningAlertStyle];
1276       
1277        [alert runModal];
1278        [alert release];
1279       
1280        [torrents removeObjectAtIndex: 0];
1281        [self copyTorrentFileForTorrents: torrents];
1282    }
1283    else
1284    {
1285        NSSavePanel * panel = [NSSavePanel savePanel];
1286        [panel setRequiredFileType: @"torrent"];
1287        [panel setCanSelectHiddenExtension: YES];
1288       
1289        [panel beginSheetForDirectory: nil file: [torrent name] modalForWindow: fWindow modalDelegate: self
1290            didEndSelector: @selector(saveTorrentCopySheetClosed:returnCode:contextInfo:) contextInfo: torrents];
1291    }
1292}
1293
1294- (void) saveTorrentCopySheetClosed: (NSSavePanel *) panel returnCode: (int) code contextInfo: (NSMutableArray *) torrents
1295{
1296    //copy torrent to new location with name of data file
1297    if (code == NSOKButton)
1298        [[torrents objectAtIndex: 0] copyTorrentFileTo: [panel filename]];
1299   
1300    [torrents removeObjectAtIndex: 0];
1301    [self performSelectorOnMainThread: @selector(copyTorrentFileForTorrents:) withObject: torrents waitUntilDone: NO];
1302}
1303
1304- (void) revealFile: (id) sender
1305{
1306    NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
1307    Torrent * torrent;
1308    while ((torrent = [enumerator nextObject]))
1309        [torrent revealData];
1310}
1311
1312- (void) announceSelectedTorrents: (id) sender
1313{
1314    NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
1315    Torrent * torrent;
1316    while ((torrent = [enumerator nextObject]))
1317    {
1318        if ([torrent canManualAnnounce])
1319            [torrent manualAnnounce];
1320    }
1321}
1322
1323- (void) resetCacheForSelectedTorrents: (id) sender
1324{
1325    NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
1326    Torrent * torrent;
1327    while ((torrent = [enumerator nextObject]))
1328        [torrent resetCache];
1329   
1330    [self applyFilter: nil];
1331}
1332
1333- (void) showPreferenceWindow: (id) sender
1334{
1335    NSWindow * window = [fPrefsController window];
1336    if (![window isVisible])
1337        [window center];
1338
1339    [window makeKeyAndOrderFront: nil];
1340}
1341
1342- (void) showAboutWindow: (id) sender
1343{
1344    [[AboutWindowController aboutController] showWindow: nil];
1345}
1346
1347- (void) showInfo: (id) sender
1348{
1349    if ([[fInfoController window] isVisible])
1350        [fInfoController close];
1351    else
1352    {
1353        [fInfoController updateInfoStats];
1354        [[fInfoController window] orderFront: nil];
1355    }
1356}
1357
1358- (void) setInfoTab: (id) sender
1359{
1360    if (sender == fNextInfoTabItem)
1361        [fInfoController setNextTab];
1362    else
1363        [fInfoController setPreviousTab];
1364}
1365
1366- (void) showMessageWindow: (id) sender
1367{
1368    [fMessageController showWindow: nil];
1369}
1370
1371- (void) showStatsWindow: (id) sender
1372{
1373    [[StatsWindowController statsWindow: fLib] showWindow: nil];
1374}
1375
1376- (void) updateUI
1377{
1378    [fTorrents makeObjectsPerformSelector: @selector(update)];
1379   
1380    if (![NSApp isHidden])
1381    {
1382        if ([fWindow isVisible])
1383        {
1384            //resort if necessary or just update the table
1385            NSString * sortType = [fDefaults stringForKey: @"Sort"];
1386            if ([sortType isEqualToString: SORT_PROGRESS] || [sortType isEqualToString: SORT_STATE]
1387                    || [sortType isEqualToString: SORT_TRACKER])
1388                [self sortTorrents];
1389            else
1390                [fTableView reloadData];
1391           
1392            //update the global DL/UL rates
1393            if (![fStatusBar isHidden])
1394            {
1395                float downloadRate, uploadRate;
1396                tr_torrentRates(fLib, & downloadRate, & uploadRate);
1397               
1398                [fTotalDLField setStringValue: [NSLocalizedString(@"Total DL: ", "Status bar -> total download")
1399                                                stringByAppendingString: [NSString stringForSpeed: downloadRate]]];
1400                [fTotalULField setStringValue: [NSLocalizedString(@"Total UL: ", "Status bar -> total upload")
1401                                                stringByAppendingString: [NSString stringForSpeed: uploadRate]]];
1402            }
1403        }
1404
1405        //update non-constant parts of info window
1406        if ([[fInfoController window] isVisible])
1407            [fInfoController updateInfoStats];
1408    }
1409   
1410    //badge dock
1411    [fBadger updateBadge];
1412}
1413
1414- (void) updateTorrentsInQueue
1415{
1416    BOOL download = [fDefaults boolForKey: @"Queue"],
1417        seed = [fDefaults boolForKey: @"QueueSeed"];
1418   
1419    int desiredDownloadActive = [self numToStartFromQueue: YES],
1420        desiredSeedActive = [self numToStartFromQueue: NO];
1421   
1422    //sort torrents by order value
1423    NSArray * sortedTorrents;
1424    if ([fTorrents count] > 1 && (desiredDownloadActive > 0 || desiredSeedActive > 0))
1425    {
1426        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1427                                                @"orderValue" ascending: YES] autorelease];
1428        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1429       
1430        sortedTorrents = [fTorrents sortedArrayUsingDescriptors: descriptors];
1431        [descriptors release];
1432    }
1433    else
1434        sortedTorrents = fTorrents;
1435
1436    Torrent * torrent;
1437    NSEnumerator * enumerator = [sortedTorrents objectEnumerator];
1438    while ((torrent = [enumerator nextObject]))
1439    {
1440        if (![torrent isActive] && ![torrent isChecking] && [torrent waitingToStart])
1441        {
1442            if (![torrent allDownloaded])
1443            {
1444                if (!download || desiredDownloadActive > 0)
1445                {
1446                    [torrent startTransfer];
1447                    if ([torrent isActive])
1448                        desiredDownloadActive--;
1449                    [torrent update];
1450                }
1451            }
1452            else
1453            {
1454                if (!seed || desiredSeedActive > 0)
1455                {
1456                    [torrent startTransfer];
1457                    if ([torrent isActive])
1458                        desiredSeedActive--;
1459                    [torrent update];
1460                }
1461            }
1462        }
1463    }
1464   
1465    [self updateUI];
1466    [self applyFilter: nil];
1467    [self updateTorrentHistory];
1468}
1469
1470- (int) numToStartFromQueue: (BOOL) downloadQueue
1471{
1472    if (![fDefaults boolForKey: downloadQueue ? @"Queue" : @"QueueSeed"])
1473        return 0;
1474   
1475    int desired = [fDefaults integerForKey: downloadQueue ? @"QueueDownloadNumber" : @"QueueSeedNumber"];
1476       
1477    Torrent * torrent;
1478    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1479    while ((torrent = [enumerator nextObject]))
1480    {
1481        if ([torrent isChecking])
1482        {
1483            desired--;
1484            if (desired <= 0)
1485                return 0;
1486        }
1487        else if ([torrent isActive] && ![torrent isStalled] && ![torrent isError])
1488        {
1489            if ([torrent allDownloaded] != downloadQueue)
1490            {
1491                desired--;
1492                if (desired <= 0)
1493                    return 0;
1494            }
1495        }
1496        else;
1497    }
1498   
1499    return desired;
1500}
1501
1502- (void) torrentFinishedDownloading: (NSNotification *) notification
1503{
1504    Torrent * torrent = [notification object];
1505    if ([torrent isActive])
1506    {
1507        if ([fDefaults boolForKey: @"PlayDownloadSound"])
1508        {
1509            NSSound * sound;
1510            if ((sound = [NSSound soundNamed: [fDefaults stringForKey: @"DownloadSound"]]))
1511                [sound play];
1512        }
1513       
1514        NSDictionary * clickContext = [NSDictionary dictionaryWithObjectsAndKeys: GROWL_DOWNLOAD_COMPLETE, @"Type",
1515                                        [torrent dataLocation] , @"Location", nil];
1516        [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Download Complete", "Growl notification title")
1517                                    description: [torrent name] notificationName: GROWL_DOWNLOAD_COMPLETE
1518                                    iconData: nil priority: 0 isSticky: NO clickContext: clickContext];
1519       
1520        if (![fWindow isMainWindow])
1521            [fBadger incrementCompleted];
1522       
1523        if ([fDefaults boolForKey: @"QueueSeed"] && [self numToStartFromQueue: NO] <= 0)
1524        {
1525            [torrent stopTransfer];
1526            [torrent setWaitToStart: YES];
1527        }
1528    }
1529   
1530    [self updateTorrentsInQueue];
1531}
1532
1533- (void) torrentRestartedDownloading: (NSNotification *) notification
1534{
1535    Torrent * torrent = [notification object];
1536    if ([torrent isActive])
1537    {
1538        if ([fDefaults boolForKey: @"Queue"] && [self numToStartFromQueue: YES] <= 0)
1539        {
1540            [torrent stopTransfer];
1541            [torrent setWaitToStart: YES];
1542        }
1543    }
1544   
1545    [self updateTorrentsInQueue];
1546}
1547
1548- (void) updateTorrentHistory
1549{
1550    NSMutableArray * history = [NSMutableArray arrayWithCapacity: [fTorrents count]];
1551
1552    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1553    Torrent * torrent;
1554    while ((torrent = [enumerator nextObject]))
1555        [history addObject: [torrent history]];
1556
1557    [history writeToFile: [NSHomeDirectory() stringByAppendingPathComponent: SUPPORT_FOLDER] atomically: YES];
1558}
1559
1560- (void) sortTorrents
1561{
1562    //remember selected rows if needed
1563    NSArray * selectedTorrents = nil;
1564    int numSelected = [fTableView numberOfSelectedRows];
1565    if (numSelected > 0 && numSelected < [fDisplayedTorrents count])
1566        selectedTorrents = [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]];
1567
1568    [self sortTorrentsIgnoreSelected]; //actually sort
1569   
1570    //set selected rows if needed
1571    if (selectedTorrents)
1572    {
1573        Torrent * torrent;
1574        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1575        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1576        while ((torrent = [enumerator nextObject]))
1577            [indexSet addIndex: [fDisplayedTorrents indexOfObject: torrent]];
1578       
1579        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1580        [indexSet release];
1581    }
1582}
1583
1584//doesn't remember selected rows
1585- (void) sortTorrentsIgnoreSelected
1586{
1587    NSString * sortType = [fDefaults stringForKey: @"Sort"];
1588    BOOL asc = ![fDefaults boolForKey: @"SortReverse"];
1589   
1590    NSSortDescriptor * nameDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"name"
1591                                            ascending: asc selector: @selector(caseInsensitiveCompare:)] autorelease],
1592                    * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"orderValue"
1593                                            ascending: asc] autorelease];
1594   
1595    NSArray * descriptors;
1596    if ([sortType isEqualToString: SORT_NAME])
1597        descriptors = [[NSArray alloc] initWithObjects: nameDescriptor, orderDescriptor, nil];
1598    else if ([sortType isEqualToString: SORT_STATE])
1599    {
1600        NSSortDescriptor * stateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1601                                                @"stateSortKey" ascending: !asc] autorelease],
1602                        * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1603                                            @"progressSortKey" ascending: !asc] autorelease],
1604                        * ratioDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1605                                            @"ratioSortKey" ascending: !asc] autorelease];
1606       
1607        descriptors = [[NSArray alloc] initWithObjects: stateDescriptor, progressDescriptor, ratioDescriptor,
1608                                                            nameDescriptor, orderDescriptor, nil];
1609    }
1610    else if ([sortType isEqualToString: SORT_PROGRESS])
1611    {
1612        NSSortDescriptor * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1613                                            @"progressSortKey" ascending: asc] autorelease],
1614                        * ratioProgressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1615                                            @"ratioProgressSortKey" ascending: asc] autorelease],
1616                        * ratioDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1617                                            @"ratioSortKey" ascending: asc] autorelease];
1618       
1619        descriptors = [[NSArray alloc] initWithObjects: progressDescriptor, ratioProgressDescriptor, ratioDescriptor,
1620                                                            nameDescriptor, orderDescriptor, nil];
1621    }
1622    else if ([sortType isEqualToString: SORT_TRACKER])
1623    {
1624        NSSortDescriptor * trackerDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"trackerAddress"
1625                                                ascending: asc selector: @selector(caseInsensitiveCompare:)] autorelease];
1626       
1627        descriptors = [[NSArray alloc] initWithObjects: trackerDescriptor, nameDescriptor, orderDescriptor, nil];
1628    }
1629    else if ([sortType isEqualToString: SORT_ORDER])
1630        descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1631    else
1632    {
1633        NSSortDescriptor * dateDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"dateAdded" ascending: asc] autorelease];
1634   
1635        descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, orderDescriptor, nil];
1636    }
1637
1638    [fDisplayedTorrents sortUsingDescriptors: descriptors];
1639    [descriptors release];
1640   
1641    [fTableView reloadData];
1642}
1643
1644- (void) setSort: (id) sender
1645{
1646    NSString * sortType;
1647    switch ([sender tag])
1648    {
1649        case SORT_ORDER_TAG:
1650            sortType = SORT_ORDER;
1651            [fDefaults setBool: NO forKey: @"SortReverse"];
1652            break;
1653           
1654        case SORT_DATE_TAG:
1655            sortType = SORT_DATE;
1656            break;
1657       
1658        case SORT_NAME_TAG:
1659            sortType = SORT_NAME;
1660            break;
1661       
1662        case SORT_PROGRESS_TAG:
1663            sortType = SORT_PROGRESS;
1664            break;
1665       
1666        case SORT_STATE_TAG:
1667            sortType = SORT_STATE;
1668            break;
1669       
1670        case SORT_TRACKER_TAG:
1671            sortType = SORT_TRACKER;
1672            break;
1673       
1674        default:
1675            return;
1676    }
1677   
1678    [fDefaults setObject: sortType forKey: @"Sort"];
1679    [self sortTorrents];
1680}
1681
1682- (void) setSortReverse: (id) sender
1683{
1684    [fDefaults setBool: ![fDefaults boolForKey: @"SortReverse"] forKey: @"SortReverse"];
1685    [self sortTorrents];
1686}
1687
1688- (void) applyFilter: (id) sender
1689{
1690    //remember selected rows if needed
1691    NSArray * selectedTorrents = [fTableView numberOfSelectedRows] > 0
1692                ? [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] : nil;
1693
1694    NSMutableArray * tempTorrents = [NSMutableArray arrayWithCapacity: [fTorrents count]];
1695   
1696    NSString * filterType = [fDefaults stringForKey: @"Filter"];
1697   
1698    int downloading = 0, seeding = 0, paused = 0;
1699    BOOL isDownload = [filterType isEqualToString: FILTER_DOWNLOAD],
1700            isSeed = [filterType isEqualToString: FILTER_SEED],
1701            isPause = [filterType isEqualToString: FILTER_PAUSE];
1702    BOOL filtering = isDownload || isSeed || isPause;
1703   
1704    //get count of each type
1705    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1706    Torrent * torrent;
1707    while ((torrent = [enumerator nextObject]))
1708    {
1709        if ([torrent isActive])
1710        {
1711            if ([torrent isSeeding])
1712            {
1713                seeding++;
1714                if (isSeed)
1715                    [tempTorrents addObject: torrent];
1716            }
1717            else
1718            {
1719                downloading++;
1720                if (isDownload)
1721                    [tempTorrents addObject: torrent];
1722            }
1723        }
1724        else
1725        {
1726            paused++;
1727            if (isPause)
1728                [tempTorrents addObject: torrent];
1729        }
1730    }
1731   
1732    if (!filtering)
1733        [tempTorrents setArray: fTorrents];
1734   
1735    //set button tooltips
1736    [fNoFilterButton setCount: [fTorrents count]];
1737    [fDownloadFilterButton setCount: downloading];
1738    [fSeedFilterButton setCount: seeding];
1739    [fPauseFilterButton setCount: paused];
1740   
1741    NSString * searchString = [fSearchFilterField stringValue];
1742    if ([searchString length] > 0)
1743    {
1744        filtering = YES;
1745       
1746        Torrent * torrent;
1747        BOOL filterTracker = [[fDefaults stringForKey: @"FilterSearchType"] isEqualToString: FILTER_TYPE_TRACKER], remove;
1748       
1749        int i;
1750        for (i = [tempTorrents count]-1; i >= 0; i--)
1751        {
1752            torrent = [tempTorrents objectAtIndex: i];
1753            remove = YES;
1754           
1755            if (filterTracker)
1756            {
1757                NSEnumerator * trackerEnumerator = [[torrent allTrackers] objectEnumerator], * subTrackerEnumerator;
1758                NSArray * subTrackers;
1759                NSString * tracker;
1760                while (remove && (subTrackers = [trackerEnumerator nextObject]))
1761                {
1762                    subTrackerEnumerator = [subTrackers objectEnumerator];
1763                    while ((tracker = [subTrackerEnumerator nextObject]))
1764                    {
1765                        if ([tracker rangeOfString: searchString options: NSCaseInsensitiveSearch].location != NSNotFound)
1766                        {
1767                            remove = NO;
1768                            break;
1769                        }
1770                    }
1771                }
1772            }
1773            else
1774                remove = [[torrent name] rangeOfString: searchString options: NSCaseInsensitiveSearch].location == NSNotFound;
1775           
1776            if (remove)
1777                [tempTorrents removeObjectAtIndex: i];
1778        }
1779    }
1780   
1781    [fDisplayedTorrents setArray: tempTorrents];
1782   
1783    [self sortTorrentsIgnoreSelected];
1784   
1785    //set selected rows if needed
1786    if (selectedTorrents)
1787    {
1788        Torrent * torrent;
1789        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1790        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1791        unsigned index;
1792        while ((torrent = [enumerator nextObject]))
1793            if ((index = [fDisplayedTorrents indexOfObject: torrent]) != NSNotFound)
1794                [indexSet addIndex: index];
1795       
1796        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1797        [indexSet release];
1798    }
1799   
1800    //set status bar torrent count text
1801    NSMutableString * totalTorrentsString = [[NSMutableString alloc] initWithString: @""];
1802    if (filtering)
1803        [totalTorrentsString appendFormat: @"%d/", [fDisplayedTorrents count]];
1804   
1805    int totalCount = [fTorrents count];
1806    if (totalCount != 1)
1807        [totalTorrentsString appendFormat: NSLocalizedString(@"%d Transfers", "Status bar transfer count"), totalCount];
1808    else
1809        [totalTorrentsString appendString: NSLocalizedString(@"1 Transfer", "Status bar transfer count")];
1810   
1811    [fTotalTorrentsField setStringValue: totalTorrentsString];
1812    [totalTorrentsString release];
1813
1814    [self setWindowSizeToFit];
1815}
1816
1817//resets filter and sorts torrents
1818- (void) setFilter: (id) sender
1819{
1820    NSString * oldFilterType = [fDefaults stringForKey: @"Filter"];
1821   
1822    NSButton * prevFilterButton;
1823    if ([oldFilterType isEqualToString: FILTER_PAUSE])
1824        prevFilterButton = fPauseFilterButton;
1825    else if ([oldFilterType isEqualToString: FILTER_SEED])
1826        prevFilterButton = fSeedFilterButton;
1827    else if ([oldFilterType isEqualToString: FILTER_DOWNLOAD])
1828        prevFilterButton = fDownloadFilterButton;
1829    else
1830        prevFilterButton = fNoFilterButton;
1831   
1832    if (sender != prevFilterButton)
1833    {
1834        [prevFilterButton setState: NSOffState];
1835        [sender setState: NSOnState];
1836
1837        NSString * filterType;
1838        if (sender == fDownloadFilterButton)
1839            filterType = FILTER_DOWNLOAD;
1840        else if (sender == fPauseFilterButton)
1841            filterType = FILTER_PAUSE;
1842        else if (sender == fSeedFilterButton)
1843            filterType = FILTER_SEED;
1844        else
1845            filterType = FILTER_NONE;
1846
1847        [fDefaults setObject: filterType forKey: @"Filter"];
1848    }
1849    else
1850        [sender setState: NSOnState];
1851
1852    [self applyFilter: nil];
1853}
1854
1855- (void) setFilterSearchType: (id) sender
1856{
1857    NSString * oldFilterType = [fDefaults stringForKey: @"FilterSearchType"];
1858   
1859    int prevTag, currentTag = [sender tag];
1860    if ([oldFilterType isEqualToString: FILTER_TYPE_TRACKER])
1861        prevTag = FILTER_TYPE_TAG_TRACKER;
1862    else
1863        prevTag = FILTER_TYPE_TAG_NAME;
1864   
1865    if (currentTag != prevTag)
1866    {
1867        NSString * filterType;
1868        if (currentTag == FILTER_TYPE_TAG_TRACKER)
1869            filterType = FILTER_TYPE_TRACKER;
1870        else
1871            filterType = FILTER_TYPE_NAME;
1872       
1873        [fDefaults setObject: filterType forKey: @"FilterSearchType"];
1874       
1875        [[fSearchFilterField cell] setPlaceholderString: [sender title]];
1876    }
1877   
1878    [self applyFilter: nil];
1879}
1880
1881- (void) switchFilter: (id) sender
1882{
1883    NSString * filterType = [fDefaults stringForKey: @"Filter"];
1884   
1885    NSButton * button;
1886    if ([filterType isEqualToString: FILTER_NONE])
1887        button = sender == fNextFilterItem ? fDownloadFilterButton : fPauseFilterButton;
1888    else if ([filterType isEqualToString: FILTER_DOWNLOAD])
1889        button = sender == fNextFilterItem ? fSeedFilterButton : fNoFilterButton;
1890    else if ([filterType isEqualToString: FILTER_SEED])
1891        button = sender == fNextFilterItem ? fPauseFilterButton : fDownloadFilterButton;
1892    else if ([filterType isEqualToString: FILTER_PAUSE])
1893        button = sender == fNextFilterItem ? fNoFilterButton : fSeedFilterButton;
1894    else
1895        button = fNoFilterButton;
1896   
1897    [self setFilter: button];
1898}
1899
1900- (void) updateControlTint: (NSNotification *) notification
1901{
1902    if ([fDefaults boolForKey: @"SpeedLimit"])
1903        [fSpeedLimitButton setImage: [NSColor currentControlTint] == NSGraphiteControlTint
1904            ? [NSImage imageNamed: @"SpeedLimitButtonGraphite.png"] : [NSImage imageNamed: @"SpeedLimitButtonBlue.png"]];
1905}
1906
1907- (void) toggleSpeedLimit: (id) sender
1908{
1909    [fDefaults setBool: ![fDefaults boolForKey: @"SpeedLimit"] forKey: @"SpeedLimit"];
1910    [fPrefsController applySpeedSettings: nil];
1911}
1912
1913- (void) autoSpeedLimitChange: (NSNotification *) notification
1914{
1915    if (![fDefaults boolForKey: @"SpeedLimitAuto"])
1916        return;
1917 
1918    NSCalendarDate * onDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
1919                        [[fDefaults objectForKey: @"SpeedLimitAutoOnDate"] timeIntervalSinceReferenceDate]],
1920        * offDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
1921                        [[fDefaults objectForKey: @"SpeedLimitAutoOffDate"] timeIntervalSinceReferenceDate]],
1922        * nowDate = [NSCalendarDate calendarDate];
1923   
1924    //check if should be on if within range
1925    int onTime = [onDate hourOfDay] * 60 + [onDate minuteOfHour],
1926        offTime = [offDate hourOfDay] * 60 + [offDate minuteOfHour],
1927        nowTime = [nowDate hourOfDay] * 60 + [nowDate minuteOfHour];
1928   
1929    BOOL shouldBeOn = NO;
1930    if (onTime < offTime)
1931        shouldBeOn = onTime <= nowTime && nowTime < offTime;
1932    else if (onTime > offTime)
1933        shouldBeOn = onTime <= nowTime || nowTime < offTime;
1934    else;
1935   
1936    if ([fDefaults boolForKey: @"SpeedLimit"] != shouldBeOn)
1937        [self toggleSpeedLimit: nil];
1938}
1939
1940- (void) autoSpeedLimit
1941{
1942    if (![fDefaults boolForKey: @"SpeedLimitAuto"])
1943        return;
1944   
1945    //only toggle if within first few seconds of minutes
1946    NSCalendarDate * nowDate = [NSCalendarDate calendarDate];
1947    if ([nowDate secondOfMinute] > AUTO_SPEED_LIMIT_SECONDS)
1948        return;
1949   
1950    NSCalendarDate * offDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
1951                        [[fDefaults objectForKey: @"SpeedLimitAutoOffDate"] timeIntervalSinceReferenceDate]];
1952   
1953    BOOL toggle;
1954    if ([fDefaults boolForKey: @"SpeedLimit"])
1955        toggle = [nowDate hourOfDay] == [offDate hourOfDay] && [nowDate minuteOfHour] == [offDate minuteOfHour];
1956    else
1957    {
1958        NSCalendarDate * onDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
1959                        [[fDefaults objectForKey: @"SpeedLimitAutoOnDate"] timeIntervalSinceReferenceDate]];
1960        toggle = ([nowDate hourOfDay] == [onDate hourOfDay] && [nowDate minuteOfHour] == [onDate minuteOfHour])
1961                    && !([onDate hourOfDay] == [offDate hourOfDay] && [onDate minuteOfHour] == [offDate minuteOfHour]);
1962    }
1963   
1964    if (toggle)
1965    {
1966        [self toggleSpeedLimit: nil];
1967       
1968        [GrowlApplicationBridge notifyWithTitle: [fDefaults boolForKey: @"SpeedLimit"]
1969                ? NSLocalizedString(@"Speed Limit Auto Enabled", "Growl notification title")
1970                : NSLocalizedString(@"Speed Limit Auto Disabled", "Growl notification title")
1971            description: NSLocalizedString(@"Bandwidth settings changed", "Growl notification description")
1972            notificationName: GROWL_AUTO_SPEED_LIMIT iconData: nil priority: 0 isSticky: NO clickContext: nil];
1973    }
1974}
1975
1976- (void) setLimitGlobalEnabled: (id) sender
1977{
1978    [fPrefsController applySpeedSettings: nil];
1979}
1980
1981- (void) setQuickLimitGlobal: (id) sender
1982{
1983    [fDefaults setInteger: [[sender title] intValue] forKey: [sender menu] == fUploadMenu ? @"UploadLimit" : @"DownloadLimit"];
1984    [fDefaults setBool: YES forKey: [sender menu] == fUploadMenu ? @"CheckUpload" : @"CheckDownload"];
1985   
1986    [fPrefsController updateLimitFields];
1987    [fPrefsController applySpeedSettings: nil];
1988}
1989
1990- (void) setQuickRatioGlobal: (id) sender
1991{
1992    [fDefaults setBool: YES forKey: @"RatioCheck"];
1993    [fDefaults setFloat: [[sender title] floatValue] forKey: @"RatioLimit"];
1994   
1995    [fPrefsController updateRatioStopField];
1996}
1997
1998- (void) torrentStoppedForRatio: (NSNotification *) notification
1999{
2000    Torrent * torrent = [notification object];
2001   
2002    [self updateTorrentsInQueue];
2003    [fInfoController updateInfoStats];
2004    [fInfoController updateOptions];
2005   
2006    if ([fDefaults boolForKey: @"PlaySeedingSound"])
2007    {
2008        NSSound * sound;
2009        if ((sound = [NSSound soundNamed: [fDefaults stringForKey: @"SeedingSound"]]))
2010            [sound play];
2011    }
2012   
2013    NSDictionary * clickContext = [NSDictionary dictionaryWithObjectsAndKeys: GROWL_SEEDING_COMPLETE, @"Type",
2014                                    [torrent dataLocation], @"Location", nil];
2015    [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Seeding Complete", "Growl notification title")
2016                        description: [torrent name] notificationName: GROWL_SEEDING_COMPLETE
2017                        iconData: nil priority: 0 isSticky: NO clickContext: clickContext];
2018}
2019
2020-(void) watcher: (id<UKFileWatcher>) watcher receivedNotification: (NSString *) notification forPath: (NSString *) path
2021{
2022    if ([notification isEqualToString: UKFileWatcherWriteNotification])
2023    {
2024        if (![fDefaults boolForKey: @"AutoImport"] || ![fDefaults stringForKey: @"AutoImportDirectory"])
2025            return;
2026       
2027        if (fAutoImportTimer)
2028        {
2029            if ([fAutoImportTimer isValid])
2030                [fAutoImportTimer invalidate];
2031            [fAutoImportTimer release];
2032            fAutoImportTimer = nil;
2033        }
2034       
2035        //check again in 10 seconds in case torrent file wasn't complete
2036        fAutoImportTimer = [[NSTimer scheduledTimerWithTimeInterval: 10.0 target: self
2037            selector: @selector(checkAutoImportDirectory) userInfo: nil repeats: NO] retain];
2038       
2039        [self checkAutoImportDirectory];
2040    }
2041}
2042
2043- (void) changeAutoImport
2044{
2045    if (fAutoImportTimer)
2046    {
2047        if ([fAutoImportTimer isValid])
2048            [fAutoImportTimer invalidate];
2049        [fAutoImportTimer release];
2050        fAutoImportTimer = nil;
2051    }
2052   
2053    if (fAutoImportedNames)
2054        [fAutoImportedNames removeAllObjects];
2055    [self checkAutoImportDirectory];
2056}
2057
2058- (void) checkAutoImportDirectory
2059{
2060    NSString * path;
2061    if (![fDefaults boolForKey: @"AutoImport"] || !(path = [fDefaults stringForKey: @"AutoImportDirectory"]))
2062        return;
2063   
2064    path = [path stringByExpandingTildeInPath];
2065   
2066    NSArray * importedNames;
2067    if (!(importedNames = [[NSFileManager defaultManager] directoryContentsAtPath: path]))
2068        return;
2069   
2070    //only check files that have not been checked yet
2071    NSMutableArray * newNames = [importedNames mutableCopy];
2072   
2073    if (fAutoImportedNames)
2074        [newNames removeObjectsInArray: fAutoImportedNames];
2075    else
2076        fAutoImportedNames = [[NSMutableArray alloc] init];
2077    [fAutoImportedNames setArray: importedNames];
2078   
2079    NSString * file;
2080    int i;
2081    for (i = [newNames count] - 1; i >= 0; i--)
2082    {
2083        file = [newNames objectAtIndex: i];
2084        if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] != NSOrderedSame)
2085            [newNames removeObjectAtIndex: i];
2086        else
2087            [newNames replaceObjectAtIndex: i withObject: [path stringByAppendingPathComponent: file]];
2088    }
2089   
2090    BOOL ask = [[fDefaults stringForKey: @"DownloadChoice"] isEqualToString: @"Ask"];
2091   
2092    NSEnumerator * enumerator = [newNames objectEnumerator];
2093    int canAdd, count;
2094    while ((file = [enumerator nextObject]))
2095    {
2096        canAdd = tr_torrentParse(fLib, [file UTF8String], NULL, NULL);
2097        if (canAdd == TR_OK)
2098        {
2099            if (!ask)
2100                count = [fTorrents count];
2101            [self openFiles: [NSArray arrayWithObject: file]];
2102           
2103            //check if torrent was opened
2104            if (!ask && [fTorrents count] > count)
2105                [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Torrent File Auto Added",
2106                    "Growl notification title") description: [file lastPathComponent]
2107                    notificationName: GROWL_AUTO_ADD iconData: nil priority: 0 isSticky: NO clickContext: nil];
2108        }
2109        else if (canAdd == TR_EINVALID)
2110            [fAutoImportedNames removeObject: [file lastPathComponent]];
2111        else;
2112    }
2113   
2114    [newNames release];
2115}
2116
2117- (void) beginCreateFile: (NSNotification *) notification
2118{
2119    if (![fDefaults boolForKey: @"AutoImport"])
2120        return;
2121   
2122    NSString * location = [notification object],
2123            * path = [fDefaults stringForKey: @"AutoImportDirectory"];
2124   
2125    if (location && path && [[[location stringByDeletingLastPathComponent] stringByExpandingTildeInPath]
2126                                    isEqualToString: [path stringByExpandingTildeInPath]])
2127        [fAutoImportedNames addObject: [location lastPathComponent]];
2128}
2129
2130- (int) numberOfRowsInTableView: (NSTableView *) tableview
2131{
2132    return [fDisplayedTorrents count];
2133}
2134
2135- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) tableColumn row: (int) row
2136{
2137    return nil;
2138}
2139
2140- (BOOL) tableView: (NSTableView *) tableView writeRowsWithIndexes: (NSIndexSet *) indexes toPasteboard: (NSPasteboard *) pasteboard
2141{
2142    //only allow reordering of rows if sorting by order
2143    if ([[fDefaults stringForKey: @"Sort"] isEqualToString: SORT_ORDER])
2144    {
2145        [pasteboard declareTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE] owner: self];
2146        [pasteboard setData: [NSKeyedArchiver archivedDataWithRootObject: indexes] forType: TORRENT_TABLE_VIEW_DATA_TYPE];
2147        return YES;
2148    }
2149    return NO;
2150}
2151
2152- (NSDragOperation) tableView: (NSTableView *) tableView validateDrop: (id <NSDraggingInfo>) info
2153    proposedRow: (int) row proposedDropOperation: (NSTableViewDropOperation) operation
2154{
2155    NSPasteboard * pasteboard = [info draggingPasteboard];
2156    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2157    {
2158        [fTableView setDropRow: row dropOperation: NSTableViewDropAbove];
2159        return NSDragOperationGeneric;
2160    }
2161   
2162    return NSDragOperationNone;
2163}
2164
2165- (BOOL) tableView: (NSTableView *) t acceptDrop: (id <NSDraggingInfo>) info
2166    row: (int) newRow dropOperation: (NSTableViewDropOperation) operation
2167{
2168    NSPasteboard * pasteboard = [info draggingPasteboard];
2169    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2170    {
2171        //remember selected rows if needed
2172        NSArray * selectedTorrents = nil;
2173        int numSelected = [fTableView numberOfSelectedRows];
2174        if (numSelected > 0 && numSelected < [fDisplayedTorrents count])
2175            selectedTorrents = [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]];
2176   
2177        NSIndexSet * indexes = [NSKeyedUnarchiver unarchiveObjectWithData:
2178                                [pasteboard dataForType: TORRENT_TABLE_VIEW_DATA_TYPE]];
2179       
2180        //determine where to move them
2181        int i;
2182        for (i = [indexes firstIndex]; i < newRow && i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
2183            newRow--;
2184       
2185        //reinsert into array
2186        int insertIndex = newRow > 0 ? [[[fDisplayedTorrents objectAtIndex: newRow-1] orderValue] intValue] + 1 : 0;
2187       
2188        //get all torrents to reorder
2189        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
2190                                                @"orderValue" ascending: YES] autorelease];
2191        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
2192       
2193        NSMutableArray * sortedTorrents = [[fTorrents sortedArrayUsingDescriptors: descriptors] mutableCopy];
2194        [descriptors release];
2195       
2196        //remove objects to reinsert
2197        NSArray * movingTorrents = [[fDisplayedTorrents objectsAtIndexes: indexes] retain];
2198        [sortedTorrents removeObjectsInArray: movingTorrents];
2199       
2200        //insert objects at new location
2201        for (i = 0; i < [movingTorrents count]; i++)
2202            [sortedTorrents insertObject: [movingTorrents objectAtIndex: i] atIndex: insertIndex + i];
2203       
2204        [movingTorrents release];
2205       
2206        //redo order values
2207        i = 0;
2208        for (i = 0; i < [sortedTorrents count]; i++)
2209            [[sortedTorrents objectAtIndex: i] setOrderValue: i];
2210       
2211        [sortedTorrents release];
2212       
2213        [self applyFilter: nil];
2214       
2215        //set selected rows if needed
2216        if (selectedTorrents)
2217        {
2218            Torrent * torrent;
2219            NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
2220            NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
2221            while ((torrent = [enumerator nextObject]))
2222                [indexSet addIndex: [fDisplayedTorrents indexOfObject: torrent]];
2223           
2224            [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
2225            [indexSet release];
2226        }
2227    }
2228   
2229    return YES;
2230}
2231
2232- (void) torrentTableViewSelectionDidChange: (NSNotification *) notification
2233{
2234    [fInfoController setInfoForTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
2235}
2236
2237- (NSDragOperation) draggingEntered: (id <NSDraggingInfo>) info
2238{
2239    NSPasteboard * pasteboard = [info draggingPasteboard];
2240    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2241    {
2242        //check if any torrent files can be added
2243        NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
2244        NSEnumerator * enumerator = [files objectEnumerator];
2245        NSString * file;
2246        BOOL torrent = NO;
2247        int canAdd;
2248        while ((file = [enumerator nextObject]))
2249        {
2250            if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame)
2251            {
2252                switch (canAdd = tr_torrentParse(fLib, [file UTF8String], NULL, NULL))
2253                {
2254                    case TR_OK:
2255                        if (!fOverlayWindow)
2256                            fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2257                        [fOverlayWindow setTorrents: files];
2258                       
2259                        return NSDragOperationCopy;
2260                   
2261                    case TR_EDUPLICATE:
2262                        torrent = YES;
2263                }
2264            }
2265        }
2266       
2267        //create a torrent file if a single file
2268        if (!torrent && [files count] == 1)
2269        {
2270            if (!fOverlayWindow)
2271                fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2272            [fOverlayWindow setFile: [[files objectAtIndex: 0] lastPathComponent]];
2273           
2274            return NSDragOperationCopy;
2275        }
2276    }
2277    else if ([[pasteboard types] containsObject: NSURLPboardType])
2278    {
2279        if (!fOverlayWindow)
2280            fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2281        [fOverlayWindow setURL: [[NSURL URLFromPasteboard: pasteboard] relativeString]];
2282       
2283        return NSDragOperationCopy;
2284    }
2285    else;
2286   
2287    return NSDragOperationNone;
2288}
2289
2290- (void) draggingExited: (id <NSDraggingInfo>) info
2291{
2292    if (fOverlayWindow)
2293        [fOverlayWindow fadeOut];
2294}
2295
2296- (BOOL) performDragOperation: (id <NSDraggingInfo>) info
2297{
2298    if (fOverlayWindow)
2299        [fOverlayWindow fadeOut];
2300   
2301    NSPasteboard * pasteboard = [info draggingPasteboard];
2302    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2303    {
2304        BOOL torrent = NO, accept = YES;
2305       
2306        //create an array of files that can be opened
2307        NSMutableArray * filesToOpen = [[NSMutableArray alloc] init];
2308        NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
2309        NSEnumerator * enumerator = [files objectEnumerator];
2310        NSString * file;
2311        int canAdd;
2312        while ((file = [enumerator nextObject]))
2313        {
2314            if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame)
2315            {
2316                switch(tr_torrentParse(fLib, [file UTF8String], NULL, NULL))
2317                {
2318                    case TR_OK:
2319                        [filesToOpen addObject: file];
2320                        torrent = YES;
2321                        break;
2322                       
2323                    case TR_EDUPLICATE:
2324                        torrent = YES;
2325                }
2326            }
2327        }
2328       
2329        if ([filesToOpen count] > 0)
2330            [self application: NSApp openFiles: filesToOpen];
2331        else
2332        {
2333            if (!torrent && [files count] == 1)
2334                [CreatorWindowController createTorrentFile: fLib forFile: [files objectAtIndex: 0]];
2335            else
2336                accept = NO;
2337        }
2338        [filesToOpen release];
2339       
2340        return accept;
2341    }
2342    else if ([[pasteboard types] containsObject: NSURLPboardType])
2343    {
2344        NSURL * url;
2345        if ((url = [NSURL URLFromPasteboard: pasteboard]))
2346        {
2347            [self openURL: url];
2348            return YES;
2349        }
2350    }
2351    else;
2352   
2353    return NO;
2354}
2355
2356- (void) toggleSmallView: (id) sender
2357{
2358    BOOL makeSmall = ![fDefaults boolForKey: @"SmallView"];
2359    [fDefaults setBool: makeSmall forKey: @"SmallView"];
2360   
2361    [fTableView setRowHeight: makeSmall ? ROW_HEIGHT_SMALL : ROW_HEIGHT_REGULAR];
2362   
2363    //window min height
2364    NSSize contentMinSize = [fWindow contentMinSize],
2365            contentSize = [[fWindow contentView] frame].size;
2366    contentMinSize.height = contentSize.height - [fScrollView frame].size.height
2367                            + [fTableView rowHeight] + [fTableView intercellSpacing].height;
2368    [fWindow setContentMinSize: contentMinSize];
2369   
2370    //resize for larger min height if not set to auto size
2371    if (![fDefaults boolForKey: @"AutoSize"])
2372    {
2373        if (!makeSmall && contentSize.height < contentMinSize.height)
2374        {
2375            NSRect frame = [fWindow frame];
2376            float heightChange = contentMinSize.height - contentSize.height;
2377            frame.size.height += heightChange;
2378            frame.origin.y -= heightChange;
2379           
2380            [fWindow setFrame: frame display: YES];
2381            [fTableView reloadData];
2382        }
2383    }
2384    else
2385        [self setWindowSizeToFit];
2386}
2387
2388- (void) togglePiecesBar: (id) sender
2389{
2390    [fDefaults setBool: ![fDefaults boolForKey: @"PiecesBar"] forKey: @"PiecesBar"];
2391    [fTableView display];
2392}
2393
2394- (void) toggleAvailabilityBar: (id) sender
2395{
2396    [fDefaults setBool: ![fDefaults boolForKey: @"DisplayProgressBarAvailable"] forKey: @"DisplayProgressBarAvailable"];
2397    [fTableView display];
2398}
2399
2400- (void) toggleStatusBar: (id) sender
2401{
2402    [self showStatusBar: [fStatusBar isHidden] animate: YES];
2403    [fDefaults setBool: ![fStatusBar isHidden] forKey: @"StatusBar"];
2404}
2405
2406- (NSRect) windowFrameByAddingHeight: (float) height checkLimits: (BOOL) check
2407{
2408    //convert pixels to points
2409    NSRect windowFrame = [fWindow frame];
2410    NSSize windowSize = [fScrollView convertSize: windowFrame.size fromView: nil];
2411    windowSize.height += height;
2412   
2413    if (check)
2414    {
2415        NSSize minSize = [fScrollView convertSize: [fWindow minSize] fromView: nil];
2416       
2417        if (windowSize.height < minSize.height)
2418            windowSize.height = minSize.height;
2419        else
2420        {
2421            NSSize maxSize = [fScrollView convertSize: [[fWindow screen] visibleFrame].size fromView: nil];
2422            if ([fStatusBar isHidden])
2423                maxSize.height -= [fStatusBar frame].size.height;
2424            if ([fFilterBar isHidden])
2425                maxSize.height -= [fFilterBar frame].size.height;
2426            if (windowSize.height > maxSize.height)
2427                windowSize.height = maxSize.height;
2428        }
2429    }
2430
2431    //convert points to pixels
2432    windowSize = [fScrollView convertSize: windowSize toView: nil];
2433
2434    windowFrame.origin.y -= (windowSize.height - windowFrame.size.height);
2435    windowFrame.size.height = windowSize.height;
2436    return windowFrame;
2437}
2438
2439- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
2440{
2441    if (show != [fStatusBar isHidden])
2442        return;
2443
2444    if (show)
2445        [fStatusBar setHidden: NO];
2446
2447    NSRect frame;
2448    float heightChange = [fStatusBar frame].size.height;
2449    if (!show)
2450        heightChange *= -1;
2451   
2452    //allow bar to show even if not enough room
2453    if (show && ![fDefaults boolForKey: @"AutoSize"])
2454    {
2455        frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2456        float change = [[fWindow screen] visibleFrame].size.height - frame.size.height;
2457        if (change < 0.0)
2458        {
2459            frame = [fWindow frame];
2460            frame.size.height += change;
2461            frame.origin.y -= change;
2462            [fWindow setFrame: frame display: NO animate: NO];
2463        }
2464    }
2465
2466    [self updateUI];
2467   
2468    //set views to not autoresize
2469    unsigned int statsMask = [fStatusBar autoresizingMask];
2470    unsigned int filterMask = [fFilterBar autoresizingMask];
2471    unsigned int scrollMask = [fScrollView autoresizingMask];
2472    [fStatusBar setAutoresizingMask: NSViewNotSizable];
2473    [fFilterBar setAutoresizingMask: NSViewNotSizable];
2474    [fScrollView setAutoresizingMask: NSViewNotSizable];
2475   
2476    frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2477    [fWindow setFrame: frame display: YES animate: animate];
2478   
2479    //re-enable autoresize
2480    [fStatusBar setAutoresizingMask: statsMask];
2481    [fFilterBar setAutoresizingMask: filterMask];
2482    [fScrollView setAutoresizingMask: scrollMask];
2483   
2484    //change min size
2485    NSSize minSize = [fWindow contentMinSize];
2486    minSize.height += heightChange;
2487    [fWindow setContentMinSize: minSize];
2488   
2489    if (!show)
2490        [fStatusBar setHidden: YES];
2491}
2492
2493- (void) toggleFilterBar: (id) sender
2494{
2495    //disable filtering when hiding
2496    if (![fFilterBar isHidden])
2497    {
2498        [fSearchFilterField setStringValue: @""];
2499        [self setFilter: fNoFilterButton];
2500    }
2501
2502    [self showFilterBar: [fFilterBar isHidden] animate: YES];
2503    [fDefaults setBool: ![fFilterBar isHidden] forKey: @"FilterBar"];
2504}
2505
2506- (void) showFilterBar: (BOOL) show animate: (BOOL) animate
2507{
2508    if (show != [fFilterBar isHidden])
2509        return;
2510
2511    if (show)
2512        [fFilterBar setHidden: NO];
2513
2514    NSRect frame;
2515    float heightChange = [fFilterBar frame].size.height;
2516    if (!show)
2517        heightChange *= -1;
2518   
2519    //allow bar to show even if not enough room
2520    if (show && ![fDefaults boolForKey: @"AutoSize"])
2521    {
2522        frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2523        float change = [[fWindow screen] visibleFrame].size.height - frame.size.height;
2524        if (change < 0.0)
2525        {
2526            frame = [fWindow frame];
2527            frame.size.height += change;
2528            frame.origin.y -= change;
2529            [fWindow setFrame: frame display: NO animate: NO];
2530        }
2531    }
2532
2533    //set views to not autoresize
2534    unsigned int filterMask = [fFilterBar autoresizingMask];
2535    unsigned int scrollMask = [fScrollView autoresizingMask];
2536    [fFilterBar setAutoresizingMask: NSViewNotSizable];
2537    [fScrollView setAutoresizingMask: NSViewNotSizable];
2538   
2539    frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2540    [fWindow setFrame: frame display: YES animate: animate];
2541   
2542    //re-enable autoresize
2543    [fFilterBar setAutoresizingMask: filterMask];
2544    [fScrollView setAutoresizingMask: scrollMask];
2545   
2546    //change min size
2547    NSSize minSize = [fWindow contentMinSize];
2548    minSize.height += heightChange;
2549    [fWindow setContentMinSize: minSize];
2550   
2551    if (!show)
2552    {
2553        [fFilterBar setHidden: YES];
2554        [fWindow makeFirstResponder: fTableView];
2555    }
2556}
2557
2558- (void) doNothing: (id) sender {}
2559
2560- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier: (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
2561{
2562    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
2563
2564    if ([ident isEqualToString: TOOLBAR_CREATE])
2565    {
2566        [item setLabel: NSLocalizedString(@"Create", "Create toolbar item -> label")];
2567        [item setPaletteLabel: NSLocalizedString(@"Create Torrent File", "Create toolbar item -> palette label")];
2568        [item setToolTip: NSLocalizedString(@"Create torrent file", "Create toolbar item -> tooltip")];
2569        [item setImage: [NSImage imageNamed: @"Create.png"]];
2570        [item setTarget: self];
2571        [item setAction: @selector(createFile:)];
2572        [item setAutovalidates: NO];
2573    }
2574    else if ([ident isEqualToString: TOOLBAR_OPEN])
2575    {
2576        [item setLabel: NSLocalizedString(@"Open", "Open toolbar item -> label")];
2577        [item setPaletteLabel: NSLocalizedString(@"Open Torrent Files", "Open toolbar item -> palette label")];
2578        [item setToolTip: NSLocalizedString(@"Open torrent files", "Open toolbar item -> tooltip")];
2579        [item setImage: [NSImage imageNamed: @"Open.png"]];
2580        [item setTarget: self];
2581        [item setAction: @selector(openShowSheet:)];
2582        [item setAutovalidates: NO];
2583    }
2584    else if ([ident isEqualToString: TOOLBAR_REMOVE])
2585    {
2586        [item setLabel: NSLocalizedString(@"Remove", "Remove toolbar item -> label")];
2587        [item setPaletteLabel: NSLocalizedString(@"Remove Selected", "Remove toolbar item -> palette label")];
2588        [item setToolTip: NSLocalizedString(@"Remove selected transfers", "Remove toolbar item -> tooltip")];
2589        [item setImage: [NSImage imageNamed: @"Remove.png"]];
2590        [item setTarget: self];
2591        [item setAction: @selector(removeNoDelete:)];
2592    }
2593    else if ([ident isEqualToString: TOOLBAR_INFO])
2594    {
2595        [item setLabel: NSLocalizedString(@"Inspector", "Inspector toolbar item -> label")];
2596        [item setPaletteLabel: NSLocalizedString(@"Toggle Inspector", "Inspector toolbar item -> palette label")];
2597        [item setToolTip: NSLocalizedString(@"Toggle the torrent inspector", "Inspector toolbar item -> tooltip")];
2598        [item setImage: [NSImage imageNamed: @"Info.png"]];
2599        [item setTarget: self];
2600        [item setAction: @selector(showInfo:)];
2601        [item setAutovalidates: NO];
2602    }
2603    else if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
2604    {
2605        [item setLabel: NSLocalizedString(@"Pause All", "Pause All toolbar item -> label")];
2606        [item setPaletteLabel: [item label]];
2607        [item setToolTip: NSLocalizedString(@"Pause all transfers", "Pause All toolbar item -> tooltip")];
2608        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
2609        [item setTarget: self];
2610        [item setAction: @selector(stopAllTorrents:)];
2611    }
2612    else if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
2613    {
2614        [item setLabel: NSLocalizedString(@"Resume All", "Resume All toolbar item -> label")];
2615        [item setPaletteLabel: [item label]];
2616        [item setToolTip: NSLocalizedString(@"Resume all transfers", "Resume All toolbar item -> tooltip")];
2617        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
2618        [item setTarget: self];
2619        [item setAction: @selector(resumeAllTorrents:)];
2620    }
2621    else if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
2622    {
2623        [item setLabel: NSLocalizedString(@"Pause", "Pause toolbar item -> label")];
2624        [item setPaletteLabel: NSLocalizedString(@"Pause Selected", "Pause toolbar item -> palette label")];
2625        [item setToolTip: NSLocalizedString(@"Pause selected transfers", "Pause toolbar item -> tooltip")];
2626        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
2627        [item setTarget: self];
2628        [item setAction: @selector(stopSelectedTorrents:)];
2629    }
2630    else if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
2631    {
2632        [item setLabel: NSLocalizedString(@"Resume", "Resume toolbar item -> label")];
2633        [item setPaletteLabel: NSLocalizedString(@"Resume Selected", "Resume toolbar item -> palette label")];
2634        [item setToolTip: NSLocalizedString(@"Resume selected transfers", "Resume toolbar item -> tooltip")];
2635        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
2636        [item setTarget: self];
2637        [item setAction: @selector(resumeSelectedTorrents:)];
2638    }
2639    else if ([ident isEqualToString: TOOLBAR_FILTER])
2640    {
2641        [item setLabel: NSLocalizedString(@"Filter", "Filter toolbar item -> label")];
2642        [item setPaletteLabel: NSLocalizedString(@"Toggle Filter", "Filter toolbar item -> palette label")];
2643        [item setToolTip: NSLocalizedString(@"Toggle the filter bar", "Filter toolbar item -> tooltip")];
2644        [item setImage: [NSImage imageNamed: @"Filter.png"]];
2645        [item setTarget: self];
2646        [item setAction: @selector(toggleFilterBar:)];
2647        [item setAutovalidates: NO];
2648    }
2649    else
2650    {
2651        [item release];
2652        return nil;
2653    }
2654
2655    return [item autorelease];
2656}
2657
2658- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
2659{
2660    return [NSArray arrayWithObjects:
2661            TOOLBAR_CREATE, TOOLBAR_OPEN, TOOLBAR_REMOVE,
2662            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
2663            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL, TOOLBAR_FILTER, TOOLBAR_INFO,
2664            NSToolbarSeparatorItemIdentifier,
2665            NSToolbarSpaceItemIdentifier,
2666            NSToolbarFlexibleSpaceItemIdentifier,
2667            NSToolbarCustomizeToolbarItemIdentifier, nil];
2668}
2669
2670- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
2671{
2672    return [NSArray arrayWithObjects:
2673            TOOLBAR_CREATE, TOOLBAR_OPEN, TOOLBAR_REMOVE,
2674            NSToolbarSeparatorItemIdentifier,
2675            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
2676            NSToolbarFlexibleSpaceItemIdentifier,
2677            TOOLBAR_FILTER, TOOLBAR_INFO, nil];
2678}
2679
2680- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
2681{
2682    NSString * ident = [toolbarItem itemIdentifier];
2683
2684    //enable remove item
2685    if ([ident isEqualToString: TOOLBAR_REMOVE])
2686        return [fTableView numberOfSelectedRows] > 0;
2687
2688    //enable pause all item
2689    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
2690    {
2691        Torrent * torrent;
2692        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2693        while ((torrent = [enumerator nextObject]))
2694            if ([torrent isActive] || [torrent waitingToStart])
2695                return YES;
2696        return NO;
2697    }
2698
2699    //enable resume all item
2700    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
2701    {
2702        Torrent * torrent;
2703        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2704        while ((torrent = [enumerator nextObject]))
2705            if (![torrent isActive] && ![torrent waitingToStart])
2706                return YES;
2707        return NO;
2708    }
2709
2710    //enable pause item
2711    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
2712    {
2713        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2714        Torrent * torrent;
2715        while ((torrent = [enumerator nextObject]))
2716            if ([torrent isActive] || [torrent waitingToStart])
2717                return YES;
2718        return NO;
2719    }
2720   
2721    //enable resume item
2722    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
2723    {
2724        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2725        Torrent * torrent;
2726        while ((torrent = [enumerator nextObject]))
2727            if (![torrent isActive] && ![torrent waitingToStart])
2728                return YES;
2729        return NO;
2730    }
2731
2732    return YES;
2733}
2734
2735- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
2736{
2737    SEL action = [menuItem action];
2738   
2739    if (action == @selector(toggleSpeedLimit:))
2740    {
2741        [menuItem setState: [fDefaults boolForKey: @"SpeedLimit"] ? NSOnState : NSOffState];
2742        return YES;
2743    }
2744   
2745    //only enable some items if it is in a context menu or the window is useable
2746    BOOL canUseTable = [fWindow isKeyWindow] || [[menuItem menu] supermenu] != [NSApp mainMenu];
2747
2748    //enable open items
2749    if (action == @selector(openShowSheet:) || action == @selector(openURLShowSheet:))
2750        return [fWindow attachedSheet] == nil;
2751   
2752    //enable sort options
2753    if (action == @selector(setSort:))
2754    {
2755        NSString * sortType;
2756        switch ([menuItem tag])
2757        {
2758            case SORT_ORDER_TAG:
2759                sortType = SORT_ORDER;
2760                break;
2761            case SORT_DATE_TAG:
2762                sortType = SORT_DATE;
2763                break;
2764            case SORT_NAME_TAG:
2765                sortType = SORT_NAME;
2766                break;
2767            case SORT_PROGRESS_TAG:
2768                sortType = SORT_PROGRESS;
2769                break;
2770            case SORT_STATE_TAG:
2771                sortType = SORT_STATE;
2772                break;
2773            case SORT_TRACKER_TAG:
2774                sortType = SORT_TRACKER;
2775                break;
2776            default:
2777                sortType = @"";
2778        }
2779       
2780        [menuItem setState: [sortType isEqualToString: [fDefaults stringForKey: @"Sort"]] ? NSOnState : NSOffState];
2781        return [fWindow isVisible];
2782    }
2783   
2784    if (action == @selector(toggleSmallView:))
2785    {
2786        [menuItem setState: [fDefaults boolForKey: @"SmallView"] ? NSOnState : NSOffState];
2787        return [fWindow isVisible];
2788    }
2789   
2790    if (action == @selector(togglePiecesBar:))
2791    {
2792        [menuItem setState: [fDefaults boolForKey: @"PiecesBar"] ? NSOnState : NSOffState];
2793        return [fWindow isVisible];
2794    }
2795   
2796    if (action == @selector(toggleAvailabilityBar:))
2797    {
2798        [menuItem setState: [fDefaults boolForKey: @"DisplayProgressBarAvailable"] ? NSOnState : NSOffState];
2799        return [fWindow isVisible];
2800    }
2801
2802    //enable show info
2803    if (action == @selector(showInfo:))
2804    {
2805        NSString * title = [[fInfoController window] isVisible] ? NSLocalizedString(@"Hide Inspector", "View menu -> Inspector")
2806                            : NSLocalizedString(@"Show Inspector", "View menu -> Inspector");
2807        [menuItem setTitle: title];
2808
2809        return YES;
2810    }
2811   
2812    //enable prev/next inspector tab
2813    if (action == @selector(setInfoTab:))
2814        return [[fInfoController window] isVisible];
2815   
2816    //enable toggle status bar
2817    if (action == @selector(toggleStatusBar:))
2818    {
2819        NSString * title = [fStatusBar isHidden] ? NSLocalizedString(@"Show Status Bar", "View menu -> Status Bar")
2820                            : NSLocalizedString(@"Hide Status Bar", "View menu -> Status Bar");
2821        [menuItem setTitle: title];
2822
2823        return [fWindow isVisible];
2824    }
2825   
2826    //enable toggle filter bar
2827    if (action == @selector(toggleFilterBar:))
2828    {
2829        NSString * title = [fFilterBar isHidden] ? NSLocalizedString(@"Show Filter Bar", "View menu -> Filter Bar")
2830                            : NSLocalizedString(@"Hide Filter Bar", "View menu -> Filter Bar");
2831        [menuItem setTitle: title];
2832
2833        return [fWindow isVisible];
2834    }
2835   
2836    //enable prev/next filter button
2837    if (action == @selector(switchFilter:))
2838        return [fWindow isVisible] && ![fFilterBar isHidden];
2839
2840    //enable reveal in finder
2841    if (action == @selector(revealFile:))
2842        return canUseTable && [fTableView numberOfSelectedRows] > 0;
2843
2844    //enable remove items
2845    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
2846        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteDataAndTorrent:))
2847    {
2848        BOOL warning = NO,
2849            onlyDownloading = [fDefaults boolForKey: @"CheckRemoveDownloading"],
2850            canDelete = action != @selector(removeDeleteTorrent:) && action != @selector(removeDeleteDataAndTorrent:);
2851       
2852        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2853        Torrent * torrent;
2854        while ((torrent = [enumerator nextObject]))
2855        {
2856            if (!warning && [torrent isActive])
2857            {
2858                warning = onlyDownloading ? ![torrent isSeeding] : YES;
2859                if (warning && canDelete)
2860                    break;
2861            }
2862            if (!canDelete && [torrent publicTorrent])
2863            {
2864                canDelete = YES;
2865                if (warning)
2866                    break;
2867            }
2868        }
2869   
2870        //append or remove ellipsis when needed
2871        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
2872        if (warning && [fDefaults boolForKey: @"CheckRemove"])
2873        {
2874            if (![title hasSuffix: ellipsis])
2875                [menuItem setTitle: [title stringByAppendingEllipsis]];
2876        }
2877        else
2878        {
2879            if ([title hasSuffix: ellipsis])
2880                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
2881        }
2882       
2883        return canUseTable && canDelete && [fTableView numberOfSelectedRows] > 0;
2884    }
2885
2886    //enable pause all item
2887    if (action == @selector(stopAllTorrents:))
2888    {
2889        Torrent * torrent;
2890        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2891        while ((torrent = [enumerator nextObject]))
2892            if ([torrent isActive] || [torrent waitingToStart])
2893                return YES;
2894        return NO;
2895    }
2896   
2897    //enable resume all item
2898    if (action == @selector(resumeAllTorrents:))
2899    {
2900        Torrent * torrent;
2901        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2902        while ((torrent = [enumerator nextObject]))
2903            if (![torrent isActive] && ![torrent waitingToStart])
2904                return YES;
2905        return NO;
2906    }
2907   
2908    //enable resume all waiting item
2909    if (action == @selector(resumeWaitingTorrents:))
2910    {
2911        if (![fDefaults boolForKey: @"Queue"] && ![fDefaults boolForKey: @"QueueSeed"])
2912            return NO;
2913   
2914        Torrent * torrent;
2915        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2916        while ((torrent = [enumerator nextObject]))
2917            if (![torrent isActive] && [torrent waitingToStart])
2918                return YES;
2919        return NO;
2920    }
2921   
2922    //enable resume selected waiting item
2923    if (action == @selector(resumeSelectedTorrentsNoWait:))
2924    {
2925        if (![fDefaults boolForKey: @"Queue"] && ![fDefaults boolForKey: @"QueueSeed"])
2926            return NO;
2927   
2928        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2929        Torrent * torrent;
2930        while ((torrent = [enumerator nextObject]))
2931            if (![torrent isActive])
2932                return YES;
2933        return NO;
2934    }
2935
2936    //enable pause item
2937    if (action == @selector(stopSelectedTorrents:))
2938    {
2939        if (!canUseTable)
2940            return NO;
2941   
2942        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2943        Torrent * torrent;
2944        while ((torrent = [enumerator nextObject]))
2945            if ([torrent isActive] || [torrent waitingToStart])
2946                return YES;
2947        return NO;
2948    }
2949   
2950    //enable resume item
2951    if (action == @selector(resumeSelectedTorrents:))
2952    {
2953        if (!canUseTable)
2954            return NO;
2955   
2956        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2957        Torrent * torrent;
2958        while ((torrent = [enumerator nextObject]))
2959            if (![torrent isActive] && ![torrent waitingToStart])
2960                return YES;
2961        return NO;
2962    }
2963   
2964    //enable manual announce item
2965    if (action == @selector(announceSelectedTorrents:))
2966    {
2967        if (!canUseTable)
2968            return NO;
2969       
2970        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2971        Torrent * torrent;
2972        while ((torrent = [enumerator nextObject]))
2973            if ([torrent canManualAnnounce])
2974                return YES;
2975        return NO;
2976    }
2977   
2978    //enable reset cache item
2979    if (action == @selector(resetCacheForSelectedTorrents:))
2980        return canUseTable && [fTableView numberOfSelectedRows] > 0;
2981   
2982    //enable move torrent file item
2983    if (action == @selector(moveDataFiles:))
2984        return canUseTable && [fTableView numberOfSelectedRows] > 0;
2985   
2986    //enable copy torrent file item
2987    if (action == @selector(copyTorrentFiles:))
2988        return canUseTable && [fTableView numberOfSelectedRows] > 0;
2989   
2990    //enable reverse sort item
2991    if (action == @selector(setSortReverse:))
2992    {
2993        [menuItem setState: [fDefaults boolForKey: @"SortReverse"] ? NSOnState : NSOffState];
2994        return ![[fDefaults stringForKey: @"Sort"] isEqualToString: SORT_ORDER];
2995    }
2996   
2997    //check proper filter search item
2998    if (action == @selector(setFilterSearchType:))
2999    {
3000        NSString * filterType = [fDefaults stringForKey: @"FilterSearchType"];
3001       
3002        BOOL state;
3003        if ([menuItem tag] == FILTER_TYPE_TAG_TRACKER)
3004            state = [filterType isEqualToString: FILTER_TYPE_TRACKER];
3005        else
3006            state = [filterType isEqualToString: FILTER_TYPE_NAME];
3007       
3008        [menuItem setState: state ? NSOnState : NSOffState];
3009        return YES;
3010    }
3011   
3012    return YES;
3013}
3014
3015- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument
3016{
3017    NSEnumerator * enumerator;
3018    Torrent * torrent;
3019    BOOL allowSleep;
3020
3021    switch (messageType)
3022    {
3023        case kIOMessageSystemWillSleep:
3024            //close all connections before going to sleep and remember we should resume when we wake up
3025            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
3026
3027            //wait for running transfers to stop (5 second timeout)
3028            NSDate * start = [NSDate date];
3029            BOOL timeUp = NO;
3030           
3031            enumerator = [fTorrents objectEnumerator];
3032            while (!timeUp && (torrent = [enumerator nextObject]))
3033                while ([torrent isActive] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
3034                {
3035                    usleep(100000);
3036                    [torrent update];
3037                }
3038
3039            IOAllowPowerChange(fRootPort, (long) messageArgument);
3040            break;
3041
3042        case kIOMessageCanSystemSleep:
3043            allowSleep = YES;
3044            if ([fDefaults boolForKey: @"SleepPrevent"])
3045            {
3046                //prevent idle sleep unless no torrents are active
3047                enumerator = [fTorrents objectEnumerator];
3048                while ((torrent = [enumerator nextObject]))
3049                    if ([torrent isActive] && ![torrent isStalled] && ![torrent isError])
3050                    {
3051                        allowSleep = NO;
3052                        break;
3053                    }
3054            }
3055
3056            if (allowSleep)
3057                IOAllowPowerChange(fRootPort, (long) messageArgument);
3058            else
3059                IOCancelPowerChange(fRootPort, (long) messageArgument);
3060            break;
3061
3062        case kIOMessageSystemHasPoweredOn:
3063            //resume sleeping transfers after we wake up
3064            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
3065            [self autoSpeedLimitChange: nil];
3066            break;
3067    }
3068}
3069
3070- (NSMenu *) applicationDockMenu: (NSApplication *) sender
3071{
3072    int seeding = 0, downloading = 0;
3073    NSEnumerator * enumerator = [fTorrents objectEnumerator];
3074    Torrent * torrent;
3075    while ((torrent = [enumerator nextObject]))
3076    {
3077        if ([torrent isSeeding])
3078            seeding++;
3079        else if ([torrent isActive])
3080            downloading++;
3081        else;
3082    }
3083   
3084    NSMenuItem * seedingItem = [fDockMenu itemWithTag: DOCK_SEEDING_TAG],
3085            * downloadingItem = [fDockMenu itemWithTag: DOCK_DOWNLOADING_TAG];
3086   
3087    BOOL hasSeparator = seedingItem || downloadingItem;
3088   
3089    if (seeding > 0)
3090    {
3091        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Seeding",
3092                                                        "Dock item - Seeding"), seeding];
3093        if (!seedingItem)
3094        {
3095            seedingItem = [[[NSMenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""] autorelease];
3096            [seedingItem setTag: DOCK_SEEDING_TAG];
3097            [fDockMenu insertItem: seedingItem atIndex: 0];
3098        }
3099        else
3100            [seedingItem setTitle: title];
3101    }
3102    else
3103    {
3104        if (seedingItem)
3105            [fDockMenu removeItem: seedingItem];
3106    }
3107   
3108    if (downloading > 0)
3109    {
3110        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Downloading",
3111                                                        "Dock item - Downloading"), downloading];
3112        if (!downloadingItem)
3113        {
3114            downloadingItem = [[[NSMenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""] autorelease];
3115            [downloadingItem setTag: DOCK_DOWNLOADING_TAG];
3116            [fDockMenu insertItem: downloadingItem atIndex: seeding > 0 ? 1 : 0];
3117        }
3118        else
3119            [downloadingItem setTitle: title];
3120    }
3121    else
3122    {
3123        if (downloadingItem)
3124            [fDockMenu removeItem: downloadingItem];
3125    }
3126   
3127    if (seeding > 0 || downloading > 0)
3128    {
3129        if (!hasSeparator)
3130            [fDockMenu insertItem: [NSMenuItem separatorItem] atIndex: seeding > 0 && downloading > 0 ? 2 : 1];
3131    }
3132    else
3133    {
3134        if (hasSeparator)
3135            [fDockMenu removeItemAtIndex: 0];
3136    }
3137   
3138    return fDockMenu;
3139}
3140
3141- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
3142{
3143    //if auto size is enabled, the current frame shouldn't need to change
3144    NSRect frame = [fDefaults boolForKey: @"AutoSize"] ? [window frame] : [self sizedWindowFrame];
3145   
3146    frame.size.width = [fDefaults boolForKey: @"SmallView"] ? [fWindow minSize].width : WINDOW_REGULAR_WIDTH;
3147    return frame;
3148}
3149
3150- (void) setWindowSizeToFit
3151{
3152    if ([fDefaults boolForKey: @"AutoSize"])
3153    {
3154        [fScrollView setHasVerticalScroller: NO];
3155        [fWindow setFrame: [self sizedWindowFrame] display: YES animate: YES];
3156        [fScrollView setHasVerticalScroller: YES];
3157    }
3158}
3159
3160- (NSRect) sizedWindowFrame
3161{
3162    float heightChange = [fDisplayedTorrents count] * ([fTableView rowHeight] +
3163            [fTableView intercellSpacing].height) - [fScrollView frame].size.height;
3164    return [self windowFrameByAddingHeight: heightChange checkLimits: YES];
3165}
3166
3167- (void) showMainWindow: (id) sender
3168{
3169    [fWindow makeKeyAndOrderFront: nil];
3170}
3171
3172- (void) windowDidBecomeMain: (NSNotification *) notification
3173{
3174    [fBadger clearCompleted];
3175    [self updateUI];
3176}
3177
3178- (NSSize) windowWillResize: (NSWindow *) sender toSize: (NSSize) proposedFrameSize
3179{
3180    //only resize horizontally if autosize is enabled
3181    if ([fDefaults boolForKey: @"AutoSize"])
3182        proposedFrameSize.height = [fWindow frame].size.height;
3183    return proposedFrameSize;
3184}
3185
3186- (void) windowDidResize: (NSNotification *) notification
3187{
3188    //size search filter to not overlap buttons
3189    float pointX = NSMaxX([fPauseFilterButton frame]) + 5.0;
3190    NSRect oldFrame = [fSearchFilterField frame],
3191            frame = NSMakeRect(pointX, oldFrame.origin.y, NSMaxX(oldFrame) - pointX, oldFrame.size.height);
3192   
3193    BOOL show = frame.size.width >= SEARCH_FILTER_MIN_WIDTH;
3194    if (show)
3195    {
3196        //make sure it is not too long
3197        if (frame.size.width > SEARCH_FILTER_MAX_WIDTH)
3198        {
3199            float different = frame.size.width - SEARCH_FILTER_MAX_WIDTH;
3200            frame.origin.x += different;
3201            frame.size.width -= different;
3202        }
3203        [fSearchFilterField setFrame: frame];
3204    }
3205   
3206    //hide search filter if it overlaps filter buttons
3207    [fSearchFilterField setHidden: !show];
3208}
3209
3210- (void) applicationWillUnhide: (NSNotification *) notification
3211{
3212    [self updateUI];
3213}
3214
3215- (void) linkHomepage: (id) sender
3216{
3217    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
3218}
3219
3220- (void) linkForums: (id) sender
3221{
3222    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
3223}
3224
3225- (void) linkDonate: (id) sender
3226{
3227    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: DONATE_URL]];
3228}
3229
3230- (void) prepareForUpdate: (NSNotification *) notification
3231{
3232    fUpdateInProgress = YES;
3233}
3234
3235- (NSDictionary *) registrationDictionaryForGrowl
3236{
3237    NSArray * notifications = [NSArray arrayWithObjects: GROWL_DOWNLOAD_COMPLETE, GROWL_SEEDING_COMPLETE,
3238                                                            GROWL_AUTO_ADD, GROWL_AUTO_SPEED_LIMIT, nil];
3239    return [NSDictionary dictionaryWithObjectsAndKeys: notifications, GROWL_NOTIFICATIONS_ALL,
3240                                notifications, GROWL_NOTIFICATIONS_DEFAULT, nil];
3241}
3242
3243- (void) growlNotificationWasClicked: (id) clickContext
3244{
3245    if (!clickContext || ![clickContext isKindOfClass: [NSDictionary class]])
3246        return;
3247   
3248    NSString * type = [clickContext objectForKey: @"Type"], * location;
3249    if (([type isEqualToString: GROWL_DOWNLOAD_COMPLETE] || [type isEqualToString: GROWL_SEEDING_COMPLETE])
3250            && (location = [clickContext objectForKey: @"Location"]))
3251        [[NSWorkspace sharedWorkspace] selectFile: location inFileViewerRootedAtPath: nil];
3252}
3253
3254- (void) ipcQuit
3255{
3256    fRemoteQuit = YES;
3257    [NSApp terminate: self];
3258}
3259
3260- (NSArray *) ipcGetTorrentsByID: (NSArray *) idlist
3261{
3262    if (!idlist)
3263        return fTorrents;
3264   
3265    NSMutableArray * torrents = [NSMutableArray array];
3266   
3267    NSEnumerator * torrentEnum = [fTorrents objectEnumerator], * idEnum;
3268    int torId;
3269    Torrent * torrent;
3270    NSNumber * tempId;
3271    while ((torrent = [torrentEnum nextObject]))
3272    {
3273        torId = [torrent torrentID];
3274       
3275        idEnum = [idlist objectEnumerator];
3276        while ((tempId = [idEnum nextObject]))
3277        {
3278            if ([tempId intValue] == torId)
3279            {
3280                [torrents addObject: torrent];
3281                break;
3282            }
3283        }
3284    }
3285
3286    return torrents;
3287}
3288
3289- (NSArray *) ipcGetTorrentsByHash: (NSArray *) hashlist
3290{
3291    if (!hashlist)
3292        return fTorrents;
3293   
3294    NSMutableArray * torrents = [NSMutableArray array];
3295   
3296    NSEnumerator * torrentEnum = [fTorrents objectEnumerator], * hashEnum;
3297    NSString * torHash, * tempHash;
3298    Torrent * torrent;
3299    while ((torrent = [torrentEnum nextObject]))
3300    {
3301        torHash = [torrent hashString];
3302       
3303        hashEnum = [hashlist objectEnumerator];
3304        while ((tempHash = [hashEnum nextObject]))
3305        {
3306            if ([torHash caseInsensitiveCompare: tempHash] == NSOrderedSame)
3307            {
3308                [torrents addObject: torrent];
3309                break;
3310            }
3311        }
3312    }
3313   
3314    return torrents;
3315}
3316
3317- (BOOL) ipcAddTorrents: (NSArray *) torrents
3318{
3319    int oldCount = [fTorrents count];
3320   
3321    [self openFiles: torrents];
3322   
3323    return [fTorrents count] > oldCount;
3324}
3325
3326- (BOOL) ipcAddTorrentFile: (NSString *) path directory: (NSString *) directory
3327{
3328    int oldCount = [fTorrents count];
3329   
3330    [self openFiles: [NSArray arrayWithObject: path] forcePath: directory ignoreDownloadFolder: NO
3331            deleteTorrentFile: TORRENT_FILE_DEFAULT];
3332   
3333    return [fTorrents count] > oldCount;
3334}
3335
3336- (BOOL) ipcAddTorrentFileAutostart: (NSString *) path directory: (NSString *) directory autostart: (BOOL) autostart
3337{
3338    /* 'path' is path to torrent file, 'dir' is the directory it
3339       should download it's files to and may be nil, 'autostart' is a
3340       boolean indicating if the torrent should be automatically
3341       started (or queued to start, I guess), should return NO if
3342       torrent fails to load */
3343    return NO;
3344}
3345
3346- (BOOL) ipcAddTorrentData: (NSData *) data directory: (NSString *) directory
3347{
3348    /* 'data' is the contents of a torrent file, 'directory' is the
3349       directory it should download it's files to and may be nil,
3350       should return NO if torrent fails to load */
3351    return NO;
3352}
3353
3354- (BOOL) ipcAddTorrentDataAutostart: (NSData *) path directory: (NSString *) directory autostart: (BOOL) autostart
3355{
3356    /* 'data' is the contents of a torrent file, 'directory' is the
3357       directory it should download it's files to and may be nil,
3358       'autostart' is a boolean indicating if the torrent should be
3359       automatically started (or queued to start, I guess), should
3360       return NO if torrent fails to load */
3361    return NO;
3362}
3363
3364- (BOOL) ipcStartTorrents: (NSArray *) torrents
3365{
3366    if (!torrents)
3367        [self resumeAllTorrents: self];
3368    else
3369        [self resumeTorrents: torrents];
3370
3371    return YES;
3372}
3373
3374- (BOOL) ipcStopTorrents: (NSArray *) torrents
3375{
3376    if (!torrents)
3377        [self stopAllTorrents: self];
3378    else
3379        [self stopTorrents: torrents];
3380
3381    return YES;
3382}
3383
3384- (BOOL) ipcRemoveTorrents: (NSArray *) torrents
3385{
3386    if (!torrents)
3387        torrents = [NSArray arrayWithArray: fTorrents];
3388
3389    [self confirmRemoveTorrents: torrents deleteData: NO deleteTorrent: NO];
3390
3391    return YES;
3392}
3393
3394@end
Note: See TracBrowser for help on using the repository browser.