source: branches/0.8x/macosx/Controller.m @ 2728

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

fix bug where downloads would not be added when dragging the url onto the window without a constant download location

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