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

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

(0.8x) when automatically opening a newly created torrent file, don't trash it regardless of prefs setting

  • Property svn:keywords set to Date Rev Author Id
File size: 120.6 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 2758 2007-08-14 21:11:17Z 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"] deleteTorrentFile: TORRENT_FILE_DELETE];
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 deleteTorrentFile: TORRENT_FILE_DEFAULT];
673}
674
675- (void) openFiles: (NSArray *) filenames forcePath: (NSString *) path ignoreDownloadFolder: (BOOL) ignore
676        deleteTorrentFile: (torrentFileState) deleteTorrent
677{
678    NSString * downloadChoice = [fDefaults stringForKey: @"DownloadChoice"];
679    if (ignore || (!path && [downloadChoice isEqualToString: @"Ask"]))
680    {
681        [self openFilesAsk: [filenames mutableCopy] deleteTorrentFile: deleteTorrent];
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 numberWithInt: deleteTorrent], @"DeleteTorrent", 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 numberWithInt: deleteTorrent], @"DeleteTorrent", 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 deleteTorrentFile: deleteTorrent 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 deleteTorrentFile: TORRENT_FILE_SAVE];
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        deleteTorrentFile: [[dictionary objectForKey: @"DeleteTorrent"] intValue]];
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 deleteTorrentFile: (torrentFileState) deleteTorrent
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 numberWithInt: deleteTorrent], @"DeleteTorrent", 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                            deleteTorrentFile: [[dictionary objectForKey: @"DeleteTorrent"] intValue] 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            deleteTorrentFile: [[dictionary objectForKey: @"DeleteTorrent"] intValue]];
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 deleteTorrentFile: TORRENT_FILE_DEFAULT];
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] deleteTorrentFile: TORRENT_FILE_DEFAULT];
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 deleteTorrentFile: 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.