source: trunk/macosx/Controller.m @ 2727

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

when opening multiple copies of the app, after the warning don't make the first copy active (in case that copy has been quit in between checking and responding to the dialog)

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