source: trunk/macosx/Controller.m @ 3485

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

eliminate the redundant isPaused, instead using !isActive

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