source: trunk/macosx/Controller.m @ 2266

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

a little code cleanup

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