source: trunk/macosx/Controller.m @ 3664

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

get reordering of torrent queue working again in trunk (thanks to cocoadev mailing list)

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