source: trunk/macosx/Controller.m @ 3714

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

move dock badging preference change notification into the Badger

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