source: trunk/macosx/Controller.m @ 3868

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

get rid of the Debug warning (it's not as relevant as it was back in the olden days)

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