source: trunk/macosx/Controller.m @ 3730

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

clean up the pieces bar drawing to avoid putting it into an image, and use NSColor object for the piece colors

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