source: trunk/macosx/Controller.m @ 3453

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

generate the info tab button images when needed, add backend support for different colored tabs depending on aqua/graphite

  • Property svn:keywords set to Date Rev Author Id
File size: 126.2 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 3453 2007-10-18 00:04:01Z 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    [fTorrents makeObjectsPerformSelector: @selector(update)];
1436   
1437    if (![NSApp isHidden])
1438    {
1439        if ([fWindow isVisible])
1440        {
1441            //resort if necessary or just update the table
1442            NSString * sortType = [fDefaults stringForKey: @"Sort"];
1443            if ([sortType isEqualToString: SORT_PROGRESS] || [sortType isEqualToString: SORT_STATE]
1444                    || [sortType isEqualToString: SORT_TRACKER])
1445                [self sortTorrents];
1446            else
1447                [fTableView reloadData];
1448           
1449            //update the global DL/UL rates
1450            if (![fStatusBar isHidden])
1451            {
1452                float downloadRate, uploadRate;
1453                tr_torrentRates(fLib, & downloadRate, & uploadRate);
1454               
1455                [fTotalDLField setStringValue: [NSLocalizedString(@"Total DL: ", "Status bar -> total download")
1456                                                stringByAppendingString: [NSString stringForSpeed: downloadRate]]];
1457                [fTotalULField setStringValue: [NSLocalizedString(@"Total UL: ", "Status bar -> total upload")
1458                                                stringByAppendingString: [NSString stringForSpeed: uploadRate]]];
1459            }
1460        }
1461
1462        //update non-constant parts of info window
1463        if ([[fInfoController window] isVisible])
1464            [fInfoController updateInfoStats];
1465    }
1466
1467    //badge dock
1468    [fBadger updateBadge];
1469}
1470
1471- (void) updateTorrentsInQueue
1472{
1473    BOOL download = [fDefaults boolForKey: @"Queue"],
1474        seed = [fDefaults boolForKey: @"QueueSeed"];
1475   
1476    int desiredDownloadActive = [self numToStartFromQueue: YES],
1477        desiredSeedActive = [self numToStartFromQueue: NO];
1478   
1479    //sort torrents by order value
1480    NSArray * sortedTorrents;
1481    if ([fTorrents count] > 1 && (desiredDownloadActive > 0 || desiredSeedActive > 0))
1482    {
1483        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1484                                                @"orderValue" ascending: YES] autorelease];
1485        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1486       
1487        sortedTorrents = [fTorrents sortedArrayUsingDescriptors: descriptors];
1488        [descriptors release];
1489    }
1490    else
1491        sortedTorrents = fTorrents;
1492
1493    Torrent * torrent;
1494    NSEnumerator * enumerator = [sortedTorrents objectEnumerator];
1495    while ((torrent = [enumerator nextObject]))
1496    {
1497        if (![torrent isActive] && ![torrent isChecking] && [torrent waitingToStart])
1498        {
1499            if (![torrent allDownloaded])
1500            {
1501                if (!download || desiredDownloadActive > 0)
1502                {
1503                    [torrent startTransfer];
1504                    if ([torrent isActive])
1505                        desiredDownloadActive--;
1506                    [torrent update];
1507                }
1508            }
1509            else
1510            {
1511                if (!seed || desiredSeedActive > 0)
1512                {
1513                    [torrent startTransfer];
1514                    if ([torrent isActive])
1515                        desiredSeedActive--;
1516                    [torrent update];
1517                }
1518            }
1519        }
1520    }
1521   
1522    [self updateUI];
1523    [self applyFilter: nil];
1524    [self updateTorrentHistory];
1525}
1526
1527- (int) numToStartFromQueue: (BOOL) downloadQueue
1528{
1529    if (![fDefaults boolForKey: downloadQueue ? @"Queue" : @"QueueSeed"])
1530        return 0;
1531   
1532    int desired = [fDefaults integerForKey: downloadQueue ? @"QueueDownloadNumber" : @"QueueSeedNumber"];
1533       
1534    Torrent * torrent;
1535    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1536    while ((torrent = [enumerator nextObject]))
1537    {
1538        if ([torrent isChecking])
1539        {
1540            desired--;
1541            if (desired <= 0)
1542                return 0;
1543        }
1544        else if ([torrent isActive] && ![torrent isStalled] && ![torrent isError])
1545        {
1546            if ([torrent allDownloaded] != downloadQueue)
1547            {
1548                desired--;
1549                if (desired <= 0)
1550                    return 0;
1551            }
1552        }
1553        else;
1554    }
1555   
1556    return desired;
1557}
1558
1559- (void) torrentFinishedDownloading: (NSNotification *) notification
1560{
1561    Torrent * torrent = [notification object];
1562    if ([torrent isActive])
1563    {
1564        if ([fDefaults boolForKey: @"PlayDownloadSound"])
1565        {
1566            NSSound * sound;
1567            if ((sound = [NSSound soundNamed: [fDefaults stringForKey: @"DownloadSound"]]))
1568                [sound play];
1569        }
1570       
1571        NSDictionary * clickContext = [NSDictionary dictionaryWithObjectsAndKeys: GROWL_DOWNLOAD_COMPLETE, @"Type",
1572                                        [torrent dataLocation] , @"Location", nil];
1573        [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Download Complete", "Growl notification title")
1574                                    description: [torrent name] notificationName: GROWL_DOWNLOAD_COMPLETE
1575                                    iconData: nil priority: 0 isSticky: NO clickContext: clickContext];
1576       
1577        if (![fWindow isKeyWindow])
1578            [fBadger incrementCompleted];
1579       
1580        if ([fDefaults boolForKey: @"QueueSeed"] && [self numToStartFromQueue: NO] <= 0)
1581        {
1582            [torrent stopTransfer];
1583            [torrent setWaitToStart: YES];
1584        }
1585    }
1586   
1587    [self updateTorrentsInQueue];
1588}
1589
1590- (void) torrentRestartedDownloading: (NSNotification *) notification
1591{
1592    Torrent * torrent = [notification object];
1593    if ([torrent isActive])
1594    {
1595        if ([fDefaults boolForKey: @"Queue"] && [self numToStartFromQueue: YES] <= 0)
1596        {
1597            [torrent stopTransfer];
1598            [torrent setWaitToStart: YES];
1599        }
1600    }
1601   
1602    [self updateTorrentsInQueue];
1603}
1604
1605- (void) updateTorrentHistory
1606{
1607    NSMutableArray * history = [NSMutableArray arrayWithCapacity: [fTorrents count]];
1608
1609    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1610    Torrent * torrent;
1611    while ((torrent = [enumerator nextObject]))
1612        [history addObject: [torrent history]];
1613
1614    [history writeToFile: [NSHomeDirectory() stringByAppendingPathComponent:
1615        @"/Library/Application Support/Transmission/Transfers.plist"] atomically: YES];
1616}
1617
1618- (void) sortTorrents
1619{
1620    //remember selected rows if needed
1621    NSArray * selectedTorrents = nil;
1622    int numSelected = [fTableView numberOfSelectedRows];
1623    if (numSelected > 0 && numSelected < [fDisplayedTorrents count])
1624        selectedTorrents = [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]];
1625
1626    [self sortTorrentsIgnoreSelected]; //actually sort
1627   
1628    //set selected rows if needed
1629    if (selectedTorrents)
1630    {
1631        Torrent * torrent;
1632        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1633        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1634        while ((torrent = [enumerator nextObject]))
1635            [indexSet addIndex: [fDisplayedTorrents indexOfObject: torrent]];
1636       
1637        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1638        [indexSet release];
1639    }
1640}
1641
1642//doesn't remember selected rows
1643- (void) sortTorrentsIgnoreSelected
1644{
1645    NSString * sortType = [fDefaults stringForKey: @"Sort"];
1646    BOOL asc = ![fDefaults boolForKey: @"SortReverse"];
1647   
1648    NSSortDescriptor * nameDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"name"
1649                                            ascending: asc selector: @selector(caseInsensitiveCompare:)] autorelease],
1650                    * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"orderValue"
1651                                            ascending: asc] autorelease];
1652   
1653    NSArray * descriptors;
1654    if ([sortType isEqualToString: SORT_NAME])
1655        descriptors = [[NSArray alloc] initWithObjects: nameDescriptor, orderDescriptor, nil];
1656    else if ([sortType isEqualToString: SORT_STATE])
1657    {
1658        NSSortDescriptor * stateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1659                                                @"stateSortKey" ascending: !asc] autorelease],
1660                        * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1661                                            @"progressSortKey" ascending: !asc] autorelease],
1662                        * ratioDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1663                                            @"ratioSortKey" ascending: !asc] autorelease];
1664       
1665        descriptors = [[NSArray alloc] initWithObjects: stateDescriptor, progressDescriptor, ratioDescriptor,
1666                                                            nameDescriptor, orderDescriptor, nil];
1667    }
1668    else if ([sortType isEqualToString: SORT_PROGRESS])
1669    {
1670        NSSortDescriptor * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1671                                            @"progressSortKey" ascending: asc] autorelease],
1672                        * ratioProgressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1673                                            @"ratioProgressSortKey" ascending: asc] autorelease],
1674                        * ratioDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1675                                            @"ratioSortKey" ascending: asc] autorelease];
1676       
1677        descriptors = [[NSArray alloc] initWithObjects: progressDescriptor, ratioProgressDescriptor, ratioDescriptor,
1678                                                            nameDescriptor, orderDescriptor, nil];
1679    }
1680    else if ([sortType isEqualToString: SORT_TRACKER])
1681    {
1682        NSSortDescriptor * trackerDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"trackerAddress"
1683                                                ascending: asc selector: @selector(caseInsensitiveCompare:)] autorelease];
1684       
1685        descriptors = [[NSArray alloc] initWithObjects: trackerDescriptor, nameDescriptor, orderDescriptor, nil];
1686    }
1687    else if ([sortType isEqualToString: SORT_ORDER])
1688        descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1689    else
1690    {
1691        NSSortDescriptor * dateDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"dateAdded" ascending: asc] autorelease];
1692   
1693        descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, orderDescriptor, nil];
1694    }
1695
1696    [fDisplayedTorrents sortUsingDescriptors: descriptors];
1697    [descriptors release];
1698   
1699    [fTableView reloadData];
1700}
1701
1702- (void) setSort: (id) sender
1703{
1704    NSString * oldSortType = [fDefaults stringForKey: @"Sort"];
1705   
1706    //get checked items
1707    NSMenuItem * prevSortItem, * prevSortActionItem;
1708    if ([oldSortType isEqualToString: SORT_NAME])
1709    {
1710        prevSortItem = fNameSortItem;
1711        prevSortActionItem = fNameSortActionItem;
1712    }
1713    else if ([oldSortType isEqualToString: SORT_STATE])
1714    {
1715        prevSortItem = fStateSortItem;
1716        prevSortActionItem = fStateSortActionItem;
1717    }
1718    else if ([oldSortType isEqualToString: SORT_PROGRESS])
1719    {
1720        prevSortItem = fProgressSortItem;
1721        prevSortActionItem = fProgressSortActionItem;
1722    }
1723    else if ([oldSortType isEqualToString: SORT_TRACKER])
1724    {
1725        prevSortItem = fTrackerSortItem;
1726        prevSortActionItem = fTrackerSortActionItem;
1727    }
1728    else if ([oldSortType isEqualToString: SORT_ORDER])
1729    {
1730        prevSortItem = fOrderSortItem;
1731        prevSortActionItem = fOrderSortActionItem;
1732    }
1733    else
1734    {
1735        prevSortItem = fDateSortItem;
1736        prevSortActionItem = fDateSortActionItem;
1737    }
1738   
1739    if (sender != prevSortItem && sender != prevSortActionItem)
1740    {
1741        //get new items to check
1742        NSMenuItem * currentSortItem, * currentSortActionItem;
1743        NSString * sortType;
1744        if (sender == fNameSortItem || sender == fNameSortActionItem)
1745        {
1746            currentSortItem = fNameSortItem;
1747            currentSortActionItem = fNameSortActionItem;
1748            sortType = SORT_NAME;
1749        }
1750        else if (sender == fStateSortItem || sender == fStateSortActionItem)
1751        {
1752            currentSortItem = fStateSortItem;
1753            currentSortActionItem = fStateSortActionItem;
1754            sortType = SORT_STATE;
1755        }
1756        else if (sender == fProgressSortItem || sender == fProgressSortActionItem)
1757        {
1758            currentSortItem = fProgressSortItem;
1759            currentSortActionItem = fProgressSortActionItem;
1760            sortType = SORT_PROGRESS;
1761        }
1762        else if (sender == fTrackerSortItem || sender == fTrackerSortActionItem)
1763        {
1764            currentSortItem = fTrackerSortItem;
1765            currentSortActionItem = fTrackerSortActionItem;
1766            sortType = SORT_TRACKER;
1767        }
1768        else if (sender == fOrderSortItem || sender == fOrderSortActionItem)
1769        {
1770            currentSortItem = fOrderSortItem;
1771            currentSortActionItem = fOrderSortActionItem;
1772            sortType = SORT_ORDER;
1773           
1774            [fDefaults setBool: NO forKey: @"SortReverse"];
1775        }
1776        else
1777        {
1778            currentSortItem = fDateSortItem;
1779            currentSortActionItem = fDateSortActionItem;
1780            sortType = SORT_DATE;
1781        }
1782       
1783        [fDefaults setObject: sortType forKey: @"Sort"];
1784   
1785        [prevSortItem setState: NSOffState];
1786        [prevSortActionItem setState: NSOffState];
1787        [currentSortItem setState: NSOnState];
1788        [currentSortActionItem setState: NSOnState];
1789    }
1790
1791    [self sortTorrents];
1792}
1793
1794- (void) setSortReverse: (id) sender
1795{
1796    [self sortTorrents];
1797}
1798
1799- (void) applyFilter: (id) sender
1800{
1801    //remember selected rows if needed
1802    NSArray * selectedTorrents = [fTableView numberOfSelectedRows] > 0
1803                ? [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] : nil;
1804
1805    NSMutableArray * tempTorrents = [NSMutableArray arrayWithCapacity: [fTorrents count]];
1806   
1807    NSString * filterType = [fDefaults stringForKey: @"Filter"];
1808   
1809    int downloading = 0, seeding = 0, paused = 0;
1810    BOOL isDownload = [filterType isEqualToString: FILTER_DOWNLOAD],
1811            isSeed = [filterType isEqualToString: FILTER_SEED],
1812            isPause = [filterType isEqualToString: FILTER_PAUSE];
1813    BOOL filtering = isDownload || isSeed || isPause;
1814   
1815    //get count of each type
1816    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1817    Torrent * torrent;
1818    while ((torrent = [enumerator nextObject]))
1819    {
1820        if ([torrent isActive])
1821        {
1822            if ([torrent isSeeding])
1823            {
1824                seeding++;
1825                if (isSeed)
1826                    [tempTorrents addObject: torrent];
1827            }
1828            else
1829            {
1830                downloading++;
1831                if (isDownload)
1832                    [tempTorrents addObject: torrent];
1833            }
1834        }
1835        else
1836        {
1837            paused++;
1838            if (isPause)
1839                [tempTorrents addObject: torrent];
1840        }
1841    }
1842   
1843    if (!filtering)
1844        [tempTorrents setArray: fTorrents];
1845   
1846    //set buttons with counts
1847    [fNoFilterButton setCount: [fTorrents count]];
1848    [fDownloadFilterButton setCount: downloading];
1849    [fSeedFilterButton setCount: seeding];
1850    [fPauseFilterButton setCount: paused];
1851   
1852    NSString * searchString = [fSearchFilterField stringValue];
1853    if ([searchString length] > 0)
1854    {
1855        filtering = YES;
1856       
1857        #warning check multiple trackers
1858        NSString * filterType = [fDefaults stringForKey: @"FilterSearchType"];
1859        NSString * fullString;
1860        Torrent * torrent;
1861       
1862        int i;
1863        for (i = [tempTorrents count]-1; i >= 0; i--)
1864        {
1865            torrent = [tempTorrents objectAtIndex: i];
1866            if ([filterType isEqualToString: FILTER_TYPE_TRACKER])
1867                fullString = [torrent trackerAddress];
1868            else
1869                fullString = [torrent name];
1870           
1871            if ([fullString rangeOfString: searchString options: NSCaseInsensitiveSearch].location == NSNotFound)
1872                [tempTorrents removeObjectAtIndex: i];
1873        }
1874    }
1875   
1876    [fDisplayedTorrents setArray: tempTorrents];
1877   
1878    [self sortTorrentsIgnoreSelected];
1879   
1880    //set selected rows if needed
1881    if (selectedTorrents)
1882    {
1883        Torrent * torrent;
1884        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1885        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1886        unsigned index;
1887        while ((torrent = [enumerator nextObject]))
1888            if ((index = [fDisplayedTorrents indexOfObject: torrent]) != NSNotFound)
1889                [indexSet addIndex: index];
1890       
1891        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1892        [indexSet release];
1893    }
1894   
1895    //set status bar torrent count text
1896    NSMutableString * totalTorrentsString = [[NSMutableString alloc] initWithString: @""];
1897    if (filtering)
1898        [totalTorrentsString appendFormat: @"%d/", [fDisplayedTorrents count]];
1899   
1900    int totalCount = [fTorrents count];
1901    if (totalCount != 1)
1902        [totalTorrentsString appendFormat: NSLocalizedString(@"%d Transfers", "Status bar transfer count"), totalCount];
1903    else
1904        [totalTorrentsString appendString: NSLocalizedString(@"1 Transfer", "Status bar transfer count")];
1905   
1906    [fTotalTorrentsField setStringValue: totalTorrentsString];
1907    [totalTorrentsString release];
1908
1909    [self setWindowSizeToFit];
1910}
1911
1912//resets filter and sorts torrents
1913- (void) setFilter: (id) sender
1914{
1915    NSString * oldFilterType = [fDefaults stringForKey: @"Filter"];
1916   
1917    FilterBarButton * prevFilterButton;
1918    if ([oldFilterType isEqualToString: FILTER_PAUSE])
1919        prevFilterButton = fPauseFilterButton;
1920    else if ([oldFilterType isEqualToString: FILTER_SEED])
1921        prevFilterButton = fSeedFilterButton;
1922    else if ([oldFilterType isEqualToString: FILTER_DOWNLOAD])
1923        prevFilterButton = fDownloadFilterButton;
1924    else
1925        prevFilterButton = fNoFilterButton;
1926   
1927    if (sender != prevFilterButton)
1928    {
1929        [prevFilterButton setState: NSOffState];
1930        [sender setState: NSOnState];
1931
1932        NSString * filterType;
1933        if (sender == fDownloadFilterButton)
1934            filterType = FILTER_DOWNLOAD;
1935        else if (sender == fPauseFilterButton)
1936            filterType = FILTER_PAUSE;
1937        else if (sender == fSeedFilterButton)
1938            filterType = FILTER_SEED;
1939        else
1940            filterType = FILTER_NONE;
1941
1942        [fDefaults setObject: filterType forKey: @"Filter"];
1943    }
1944
1945    [self applyFilter: nil];
1946}
1947
1948- (void) setFilterSearchType: (id) sender
1949{
1950    NSString * oldFilterType = [fDefaults stringForKey: @"FilterSearchType"];
1951   
1952    int prevTag, currentTag = [sender tag];
1953    if ([oldFilterType isEqualToString: FILTER_TYPE_TRACKER])
1954        prevTag = FILTER_TYPE_TAG_TRACKER;
1955    else
1956        prevTag = FILTER_TYPE_TAG_NAME;
1957   
1958    if (currentTag != prevTag)
1959    {
1960        NSString * filterType;
1961        if (currentTag == FILTER_TYPE_TAG_TRACKER)
1962            filterType = FILTER_TYPE_TRACKER;
1963        else
1964            filterType = FILTER_TYPE_NAME;
1965       
1966        [fDefaults setObject: filterType forKey: @"FilterSearchType"];
1967       
1968        [[fSearchFilterField cell] setPlaceholderString: [sender title]];
1969    }
1970   
1971    [self applyFilter: nil];
1972}
1973
1974- (void) switchFilter: (id) sender
1975{
1976    NSString * filterType = [fDefaults stringForKey: @"Filter"];
1977   
1978    FilterBarButton * button;
1979    if ([filterType isEqualToString: FILTER_NONE])
1980        button = sender == fNextFilterItem ? fDownloadFilterButton : fPauseFilterButton;
1981    else if ([filterType isEqualToString: FILTER_DOWNLOAD])
1982        button = sender == fNextFilterItem ? fSeedFilterButton : fNoFilterButton;
1983    else if ([filterType isEqualToString: FILTER_SEED])
1984        button = sender == fNextFilterItem ? fPauseFilterButton : fDownloadFilterButton;
1985    else if ([filterType isEqualToString: FILTER_PAUSE])
1986        button = sender == fNextFilterItem ? fNoFilterButton : fSeedFilterButton;
1987    else
1988        button = fNoFilterButton;
1989   
1990    [self setFilter: button];
1991}
1992
1993- (void) updateControlTint: (NSNotification *) notification
1994{
1995    if ([fDefaults boolForKey: @"SpeedLimit"])
1996        [fSpeedLimitButton setImage: [NSColor currentControlTint] == NSGraphiteControlTint
1997            ? [NSImage imageNamed: @"SpeedLimitButtonGraphite.png"] : [NSImage imageNamed: @"SpeedLimitButtonBlue.png"]];
1998}
1999
2000- (void) applySpeedLimit: (id) sender
2001{
2002    [fPrefsController applySpeedSettings: nil];
2003}
2004
2005- (void) toggleSpeedLimit: (id) sender
2006{
2007    [fDefaults setBool: ![fDefaults boolForKey: @"SpeedLimit"] forKey: @"SpeedLimit"];
2008    [self applySpeedLimit: nil];
2009}
2010
2011- (void) autoSpeedLimitChange: (NSNotification *) notification
2012{
2013    if (![fDefaults boolForKey: @"SpeedLimitAuto"])
2014        return;
2015 
2016    NSCalendarDate * onDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
2017                        [[fDefaults objectForKey: @"SpeedLimitAutoOnDate"] timeIntervalSinceReferenceDate]],
2018        * offDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
2019                        [[fDefaults objectForKey: @"SpeedLimitAutoOffDate"] timeIntervalSinceReferenceDate]],
2020        * nowDate = [NSCalendarDate calendarDate];
2021   
2022    //check if should be on if within range
2023    BOOL shouldBeOn;
2024   
2025    int onTime = [onDate hourOfDay] * 60 + [onDate minuteOfHour],
2026        offTime = [offDate hourOfDay] * 60 + [offDate minuteOfHour],
2027        nowTime = [nowDate hourOfDay] * 60 + [nowDate minuteOfHour];
2028   
2029    if (onTime == offTime)
2030        shouldBeOn = NO;
2031    else if (onTime < offTime)
2032        shouldBeOn = onTime <= nowTime && nowTime < offTime;
2033    else
2034        shouldBeOn = onTime <= nowTime || nowTime < offTime;
2035   
2036    if ([fDefaults boolForKey: @"SpeedLimit"] != shouldBeOn)
2037        [self toggleSpeedLimit: nil];
2038}
2039
2040- (void) autoSpeedLimit
2041{
2042    if (![fDefaults boolForKey: @"SpeedLimitAuto"])
2043        return;
2044   
2045    //only toggle if within first few seconds of minutes
2046    NSCalendarDate * nowDate = [NSCalendarDate calendarDate];
2047    if ([nowDate secondOfMinute] > AUTO_SPEED_LIMIT_SECONDS)
2048        return;
2049   
2050    NSCalendarDate * offDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
2051                        [[fDefaults objectForKey: @"SpeedLimitAutoOffDate"] timeIntervalSinceReferenceDate]];
2052   
2053    BOOL toggle;
2054    if ([fDefaults boolForKey: @"SpeedLimit"])
2055        toggle = [nowDate hourOfDay] == [offDate hourOfDay] && [nowDate minuteOfHour] == [offDate minuteOfHour];
2056    else
2057    {
2058        NSCalendarDate * onDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
2059                        [[fDefaults objectForKey: @"SpeedLimitAutoOnDate"] timeIntervalSinceReferenceDate]];
2060        toggle = ([nowDate hourOfDay] == [onDate hourOfDay] && [nowDate minuteOfHour] == [onDate minuteOfHour])
2061                    && !([onDate hourOfDay] == [offDate hourOfDay] && [onDate minuteOfHour] == [offDate minuteOfHour]);
2062    }
2063   
2064    if (toggle)
2065    {
2066        [self toggleSpeedLimit: nil];
2067       
2068        [GrowlApplicationBridge notifyWithTitle: [fDefaults boolForKey: @"SpeedLimit"]
2069                ? NSLocalizedString(@"Speed Limit Auto Enabled", "Growl notification title")
2070                : NSLocalizedString(@"Speed Limit Auto Disabled", "Growl notification title")
2071            description: NSLocalizedString(@"Bandwidth settings changed", "Growl notification description")
2072            notificationName: GROWL_AUTO_SPEED_LIMIT iconData: nil priority: 0 isSticky: NO clickContext: nil];
2073    }
2074}
2075
2076- (void) setLimitGlobalEnabled: (id) sender
2077{
2078    [fPrefsController applySpeedSettings: nil];
2079}
2080
2081- (void) setQuickLimitGlobal: (id) sender
2082{
2083    [fDefaults setInteger: [[sender title] intValue] forKey: [sender menu] == fUploadMenu ? @"UploadLimit" : @"DownloadLimit"];
2084    [fDefaults setBool: YES forKey: [sender menu] == fUploadMenu ? @"CheckUpload" : @"CheckDownload"];
2085   
2086    [fPrefsController updateLimitFields];
2087    [fPrefsController applySpeedSettings: nil];
2088}
2089
2090- (void) setQuickRatioGlobal: (id) sender
2091{
2092    [fDefaults setBool: YES forKey: @"RatioCheck"];
2093    [fDefaults setFloat: [[sender title] floatValue] forKey: @"RatioLimit"];
2094   
2095    [fPrefsController updateRatioStopField];
2096}
2097
2098- (void) torrentStoppedForRatio: (NSNotification *) notification
2099{
2100    Torrent * torrent = [notification object];
2101   
2102    [self updateTorrentsInQueue];
2103    [fInfoController updateInfoStats];
2104   
2105    if ([fDefaults boolForKey: @"PlaySeedingSound"])
2106    {
2107        NSSound * sound;
2108        if ((sound = [NSSound soundNamed: [fDefaults stringForKey: @"SeedingSound"]]))
2109            [sound play];
2110    }
2111   
2112    NSDictionary * clickContext = [NSDictionary dictionaryWithObjectsAndKeys: GROWL_SEEDING_COMPLETE, @"Type",
2113                                    [torrent dataLocation], @"Location", nil];
2114    [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Seeding Complete", "Growl notification title")
2115                        description: [torrent name] notificationName: GROWL_SEEDING_COMPLETE
2116                        iconData: nil priority: 0 isSticky: NO clickContext: clickContext];
2117}
2118
2119-(void) watcher: (id<UKFileWatcher>) watcher receivedNotification: (NSString *) notification forPath: (NSString *) path
2120{
2121    if ([notification isEqualToString: UKFileWatcherWriteNotification])
2122    {
2123        if (![fDefaults boolForKey: @"AutoImport"] || ![fDefaults stringForKey: @"AutoImportDirectory"])
2124            return;
2125       
2126        if (fAutoImportTimer)
2127        {
2128            if ([fAutoImportTimer isValid])
2129                [fAutoImportTimer invalidate];
2130            [fAutoImportTimer release];
2131            fAutoImportTimer = nil;
2132        }
2133       
2134        //check again in 10 seconds in case torrent file wasn't complete
2135        fAutoImportTimer = [[NSTimer scheduledTimerWithTimeInterval: 10.0 target: self
2136            selector: @selector(checkAutoImportDirectory) userInfo: nil repeats: NO] retain];
2137       
2138        [self checkAutoImportDirectory];
2139    }
2140}
2141
2142- (void) changeAutoImport
2143{
2144    if (fAutoImportTimer)
2145    {
2146        if ([fAutoImportTimer isValid])
2147            [fAutoImportTimer invalidate];
2148        [fAutoImportTimer release];
2149        fAutoImportTimer = nil;
2150    }
2151   
2152    if (fAutoImportedNames)
2153        [fAutoImportedNames removeAllObjects];
2154    [self checkAutoImportDirectory];
2155}
2156
2157- (void) checkAutoImportDirectory
2158{
2159    NSString * path;
2160    if (![fDefaults boolForKey: @"AutoImport"] || !(path = [fDefaults stringForKey: @"AutoImportDirectory"]))
2161        return;
2162   
2163    path = [path stringByExpandingTildeInPath];
2164   
2165    NSArray * importedNames;
2166    if (!(importedNames = [[NSFileManager defaultManager] directoryContentsAtPath: path]))
2167        return;
2168   
2169    //only check files that have not been checked yet
2170    NSMutableArray * newNames = [importedNames mutableCopy];
2171   
2172    if (fAutoImportedNames)
2173        [newNames removeObjectsInArray: fAutoImportedNames];
2174    else
2175        fAutoImportedNames = [[NSMutableArray alloc] init];
2176    [fAutoImportedNames setArray: importedNames];
2177   
2178    NSString * file;
2179    int i;
2180    for (i = [newNames count] - 1; i >= 0; i--)
2181    {
2182        file = [newNames objectAtIndex: i];
2183        if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] != NSOrderedSame)
2184            [newNames removeObjectAtIndex: i];
2185        else
2186            [newNames replaceObjectAtIndex: i withObject: [path stringByAppendingPathComponent: file]];
2187    }
2188   
2189    BOOL ask = [[fDefaults stringForKey: @"DownloadChoice"] isEqualToString: @"Ask"];
2190   
2191    NSEnumerator * enumerator = [newNames objectEnumerator];
2192    int canAdd, count;
2193    while ((file = [enumerator nextObject]))
2194    {
2195        canAdd = tr_torrentParse(fLib, [file UTF8String], NULL, NULL);
2196        if (canAdd == TR_OK)
2197        {
2198            if (!ask)
2199                count = [fTorrents count];
2200            [self openFiles: [NSArray arrayWithObject: file]];
2201           
2202            //check if torrent was opened
2203            if (!ask && [fTorrents count] > count)
2204                [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Torrent File Auto Added",
2205                    "Growl notification title") description: [file lastPathComponent]
2206                    notificationName: GROWL_AUTO_ADD iconData: nil priority: 0 isSticky: NO clickContext: nil];
2207        }
2208        else if (canAdd == TR_EINVALID)
2209            [fAutoImportedNames removeObject: [file lastPathComponent]];
2210        else;
2211    }
2212   
2213    [newNames release];
2214}
2215
2216- (void) beginCreateFile: (NSNotification *) notification
2217{
2218    if (![fDefaults boolForKey: @"AutoImport"])
2219        return;
2220   
2221    NSString * location = [notification object],
2222            * path = [fDefaults stringForKey: @"AutoImportDirectory"];
2223   
2224    if (location && path && [[[location stringByDeletingLastPathComponent] stringByExpandingTildeInPath]
2225                                    isEqualToString: [path stringByExpandingTildeInPath]])
2226        [fAutoImportedNames addObject: [location lastPathComponent]];
2227}
2228
2229- (int) numberOfRowsInTableView: (NSTableView *) tableview
2230{
2231    return [fDisplayedTorrents count];
2232}
2233
2234- (BOOL) tableView: (NSTableView *) tableView writeRowsWithIndexes: (NSIndexSet *) indexes
2235    toPasteboard: (NSPasteboard *) pasteboard
2236{
2237    //only allow reordering of rows if sorting by order with no filter
2238    if ([[fDefaults stringForKey: @"Sort"] isEqualToString: @"Order"]
2239        && [[fDefaults stringForKey: @"Filter"] isEqualToString: @"None"]
2240        && [[fSearchFilterField stringValue] length] == 0)
2241    {
2242        [pasteboard declareTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE] owner: self];
2243        [pasteboard setData: [NSKeyedArchiver archivedDataWithRootObject: indexes]
2244                                forType: TORRENT_TABLE_VIEW_DATA_TYPE];
2245        return YES;
2246    }
2247    return NO;
2248}
2249
2250- (NSDragOperation) tableView: (NSTableView *) t validateDrop: (id <NSDraggingInfo>) info
2251    proposedRow: (int) row proposedDropOperation: (NSTableViewDropOperation) operation
2252{
2253    NSPasteboard * pasteboard = [info draggingPasteboard];
2254    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2255    {
2256        [fTableView setDropRow: row dropOperation: NSTableViewDropAbove];
2257        return NSDragOperationGeneric;
2258    }
2259    else;
2260   
2261    return NSDragOperationNone;
2262}
2263
2264- (BOOL) tableView: (NSTableView *) t acceptDrop: (id <NSDraggingInfo>) info
2265    row: (int) newRow dropOperation: (NSTableViewDropOperation) operation
2266{
2267    NSPasteboard * pasteboard = [info draggingPasteboard];
2268    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2269    {
2270        //remember selected rows if needed
2271        NSArray * selectedTorrents = nil;
2272        int numSelected = [fTableView numberOfSelectedRows];
2273        if (numSelected > 0 && numSelected < [fDisplayedTorrents count])
2274            selectedTorrents = [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]];
2275   
2276        NSIndexSet * indexes = [NSKeyedUnarchiver unarchiveObjectWithData:
2277                                [pasteboard dataForType: TORRENT_TABLE_VIEW_DATA_TYPE]];
2278       
2279        //move torrent in array
2280        NSArray * movingTorrents = [[fDisplayedTorrents objectsAtIndexes: indexes] retain];
2281        [fDisplayedTorrents removeObjectsInArray: movingTorrents];
2282       
2283        //determine the insertion index now that transfers to move have been removed
2284        int i, decrease = 0;
2285        for (i = [indexes firstIndex]; i < newRow && i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
2286            decrease++;
2287       
2288        //insert objects at new location
2289        for (i = 0; i < [movingTorrents count]; i++)
2290            [fDisplayedTorrents insertObject: [movingTorrents objectAtIndex: i] atIndex: newRow - decrease + i];
2291       
2292        [movingTorrents release];
2293       
2294        //redo order values
2295        int low = [indexes firstIndex], high = [indexes lastIndex];
2296        if (newRow < low)
2297            low = newRow;
2298        else if (newRow > high + 1)
2299            high = newRow - 1;
2300        else;
2301       
2302        for (i = low; i <= high; i++)
2303            [[fDisplayedTorrents objectAtIndex: i] setOrderValue: i];
2304       
2305        [fTableView reloadData];
2306       
2307        //set selected rows if needed
2308        if (selectedTorrents)
2309        {
2310            Torrent * torrent;
2311            NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
2312            NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
2313            while ((torrent = [enumerator nextObject]))
2314                [indexSet addIndex: [fDisplayedTorrents indexOfObject: torrent]];
2315           
2316            [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
2317            [indexSet release];
2318        }
2319    }
2320    else;
2321   
2322    return YES;
2323}
2324
2325- (void) torrentTableViewSelectionDidChange: (NSNotification *) notification
2326{
2327    [fInfoController updateInfoForTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
2328}
2329
2330- (NSDragOperation) draggingEntered: (id <NSDraggingInfo>) info
2331{
2332    NSPasteboard * pasteboard = [info draggingPasteboard];
2333    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2334    {
2335        //check if any torrent files can be added
2336        NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
2337        NSEnumerator * enumerator = [files objectEnumerator];
2338        NSString * file;
2339        BOOL torrent = NO;
2340        int canAdd;
2341        while ((file = [enumerator nextObject]))
2342        {
2343            canAdd = tr_torrentParse(fLib, [file UTF8String], NULL, NULL);
2344            if (canAdd == TR_OK)
2345            {
2346                if (!fOverlayWindow)
2347                    fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2348                [fOverlayWindow setTorrents: files];
2349               
2350                return NSDragOperationCopy;
2351            }
2352            else if (canAdd == TR_EDUPLICATE)
2353                torrent = YES;
2354            else;
2355        }
2356       
2357        //create a torrent file if a single file
2358        if (!torrent && [files count] == 1)
2359        {
2360            if (!fOverlayWindow)
2361                fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2362            [fOverlayWindow setFile: [[files objectAtIndex: 0] lastPathComponent]];
2363           
2364            return NSDragOperationCopy;
2365        }
2366    }
2367    else if ([[pasteboard types] containsObject: NSURLPboardType])
2368    {
2369        if (!fOverlayWindow)
2370            fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2371        [fOverlayWindow setURL: [[NSURL URLFromPasteboard: pasteboard] relativeString]];
2372       
2373        return NSDragOperationCopy;
2374    }
2375    else;
2376   
2377    return NSDragOperationNone;
2378}
2379
2380- (void) draggingExited: (id <NSDraggingInfo>) info
2381{
2382    if (fOverlayWindow)
2383        [fOverlayWindow fadeOut];
2384}
2385
2386- (BOOL) performDragOperation: (id <NSDraggingInfo>) info
2387{
2388    if (fOverlayWindow)
2389        [fOverlayWindow fadeOut];
2390   
2391    NSPasteboard * pasteboard = [info draggingPasteboard];
2392    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2393    {
2394        BOOL torrent = NO, accept = YES;
2395       
2396        //create an array of files that can be opened
2397        NSMutableArray * filesToOpen = [[NSMutableArray alloc] init];
2398        NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
2399        NSEnumerator * enumerator = [files objectEnumerator];
2400        NSString * file;
2401        int canAdd;
2402        while ((file = [enumerator nextObject]))
2403        {
2404            canAdd = tr_torrentParse(fLib, [file UTF8String], NULL, NULL);
2405            if (canAdd == TR_OK)
2406            {
2407                [filesToOpen addObject: file];
2408                torrent = YES;
2409            }
2410            else if (canAdd == TR_EDUPLICATE)
2411                torrent = YES;
2412            else;
2413        }
2414       
2415        if ([filesToOpen count] > 0)
2416            [self application: NSApp openFiles: filesToOpen];
2417        else
2418        {
2419            if (!torrent && [files count] == 1)
2420                [CreatorWindowController createTorrentFile: fLib forFile: [files objectAtIndex: 0]];
2421            else
2422                accept = NO;
2423        }
2424        [filesToOpen release];
2425       
2426        return accept;
2427    }
2428    else if ([[pasteboard types] containsObject: NSURLPboardType])
2429    {
2430        NSURL * url;
2431        if ((url = [NSURL URLFromPasteboard: pasteboard]))
2432        {
2433            [self openURL: url];
2434            return YES;
2435        }
2436    }
2437    else;
2438   
2439    return NO;
2440}
2441
2442- (void) toggleSmallView: (id) sender
2443{
2444    BOOL makeSmall = [fDefaults boolForKey: @"SmallView"];
2445   
2446    [fTableView setRowHeight: makeSmall ? ROW_HEIGHT_SMALL : ROW_HEIGHT_REGULAR];
2447   
2448    //window min height
2449    NSSize contentMinSize = [fWindow contentMinSize],
2450            contentSize = [[fWindow contentView] frame].size;
2451    contentMinSize.height = contentSize.height - [fScrollView frame].size.height
2452                            + [fTableView rowHeight] + [fTableView intercellSpacing].height;
2453    [fWindow setContentMinSize: contentMinSize];
2454   
2455    //resize for larger min height if not set to auto size
2456    if (![fDefaults boolForKey: @"AutoSize"])
2457    {
2458        if (!makeSmall && contentSize.height < contentMinSize.height)
2459        {
2460            NSRect frame = [fWindow frame];
2461            float heightChange = contentMinSize.height - contentSize.height;
2462            frame.size.height += heightChange;
2463            frame.origin.y -= heightChange;
2464           
2465            [fWindow setFrame: frame display: YES];
2466            [fTableView reloadData];
2467        }
2468    }
2469    else
2470        [self setWindowSizeToFit];
2471}
2472
2473- (void) toggleStatusBar: (id) sender
2474{
2475    [self showStatusBar: [fStatusBar isHidden] animate: YES];
2476    [fDefaults setBool: ![fStatusBar isHidden] forKey: @"StatusBar"];
2477}
2478
2479- (NSRect) windowFrameByAddingHeight: (float) height checkLimits: (BOOL) check
2480{
2481    //convert pixels to points
2482    NSRect windowFrame = [fWindow frame];
2483    NSSize windowSize = [fScrollView convertSize: windowFrame.size fromView: nil];
2484    windowSize.height += height;
2485   
2486    if (check)
2487    {
2488        NSSize minSize = [fScrollView convertSize: [fWindow minSize] fromView: nil];
2489       
2490        if (windowSize.height < minSize.height)
2491            windowSize.height = minSize.height;
2492        else
2493        {
2494            NSSize maxSize = [fScrollView convertSize: [[fWindow screen] visibleFrame].size fromView: nil];
2495            if ([fStatusBar isHidden])
2496                maxSize.height -= [fStatusBar frame].size.height;
2497            if ([fFilterBar isHidden])
2498                maxSize.height -= [fFilterBar frame].size.height;
2499            if (windowSize.height > maxSize.height)
2500                windowSize.height = maxSize.height;
2501        }
2502    }
2503
2504    //convert points to pixels
2505    windowSize = [fScrollView convertSize: windowSize toView: nil];
2506
2507    windowFrame.origin.y -= (windowSize.height - windowFrame.size.height);
2508    windowFrame.size.height = windowSize.height;
2509    return windowFrame;
2510}
2511
2512- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
2513{
2514    if (show != [fStatusBar isHidden])
2515        return;
2516
2517    if (show)
2518        [fStatusBar setHidden: NO];
2519
2520    NSRect frame;
2521    float heightChange = [fStatusBar frame].size.height;
2522    if (!show)
2523        heightChange *= -1;
2524   
2525    //allow bar to show even if not enough room
2526    if (show && ![fDefaults boolForKey: @"AutoSize"])
2527    {
2528        frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2529        float change = [[fWindow screen] visibleFrame].size.height - frame.size.height;
2530        if (change < 0.0)
2531        {
2532            frame = [fWindow frame];
2533            frame.size.height += change;
2534            frame.origin.y -= change;
2535            [fWindow setFrame: frame display: NO animate: NO];
2536        }
2537    }
2538
2539    [self updateUI];
2540   
2541    //set views to not autoresize
2542    unsigned int statsMask = [fStatusBar autoresizingMask];
2543    unsigned int filterMask = [fFilterBar autoresizingMask];
2544    unsigned int scrollMask = [fScrollView autoresizingMask];
2545    [fStatusBar setAutoresizingMask: NSViewNotSizable];
2546    [fFilterBar setAutoresizingMask: NSViewNotSizable];
2547    [fScrollView setAutoresizingMask: NSViewNotSizable];
2548   
2549    frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2550    [fWindow setFrame: frame display: YES animate: animate];
2551   
2552    //re-enable autoresize
2553    [fStatusBar setAutoresizingMask: statsMask];
2554    [fFilterBar setAutoresizingMask: filterMask];
2555    [fScrollView setAutoresizingMask: scrollMask];
2556   
2557    //change min size
2558    NSSize minSize = [fWindow contentMinSize];
2559    minSize.height += heightChange;
2560    [fWindow setContentMinSize: minSize];
2561   
2562    if (!show)
2563        [fStatusBar setHidden: YES];
2564}
2565
2566- (void) toggleFilterBar: (id) sender
2567{
2568    //disable filtering when hiding
2569    if (![fFilterBar isHidden])
2570    {
2571        [fSearchFilterField setStringValue: @""];
2572        [self setFilter: fNoFilterButton];
2573    }
2574
2575    [self showFilterBar: [fFilterBar isHidden] animate: YES];
2576    [fDefaults setBool: ![fFilterBar isHidden] forKey: @"FilterBar"];
2577}
2578
2579- (void) showFilterBar: (BOOL) show animate: (BOOL) animate
2580{
2581    if (show != [fFilterBar isHidden])
2582        return;
2583
2584    if (show)
2585        [fFilterBar setHidden: NO];
2586
2587    NSRect frame;
2588    float heightChange = [fFilterBar frame].size.height;
2589    if (!show)
2590        heightChange *= -1;
2591   
2592    //allow bar to show even if not enough room
2593    if (show && ![fDefaults boolForKey: @"AutoSize"])
2594    {
2595        frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2596        float change = [[fWindow screen] visibleFrame].size.height - frame.size.height;
2597        if (change < 0.0)
2598        {
2599            frame = [fWindow frame];
2600            frame.size.height += change;
2601            frame.origin.y -= change;
2602            [fWindow setFrame: frame display: NO animate: NO];
2603        }
2604    }
2605
2606    //set views to not autoresize
2607    unsigned int filterMask = [fFilterBar autoresizingMask];
2608    unsigned int scrollMask = [fScrollView autoresizingMask];
2609    [fFilterBar setAutoresizingMask: NSViewNotSizable];
2610    [fScrollView setAutoresizingMask: NSViewNotSizable];
2611   
2612    frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2613    [fWindow setFrame: frame display: YES animate: animate];
2614   
2615    //re-enable autoresize
2616    [fFilterBar setAutoresizingMask: filterMask];
2617    [fScrollView setAutoresizingMask: scrollMask];
2618   
2619    //change min size
2620    NSSize minSize = [fWindow contentMinSize];
2621    minSize.height += heightChange;
2622    [fWindow setContentMinSize: minSize];
2623   
2624    if (!show)
2625    {
2626        [fFilterBar setHidden: YES];
2627        [fWindow makeFirstResponder: fTableView];
2628    }
2629}
2630
2631/*- (void) toggleAdvancedBar: (id) sender
2632{
2633    [fTableView display];
2634}*/
2635
2636- (void) doNothing: (id) sender {}
2637
2638- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier: (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
2639{
2640    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
2641
2642    if ([ident isEqualToString: TOOLBAR_CREATE])
2643    {
2644        [item setLabel: NSLocalizedString(@"Create", "Create toolbar item -> label")];
2645        [item setPaletteLabel: NSLocalizedString(@"Create Torrent File", "Create toolbar item -> palette label")];
2646        [item setToolTip: NSLocalizedString(@"Create torrent file", "Create toolbar item -> tooltip")];
2647        [item setImage: [NSImage imageNamed: @"Create.png"]];
2648        [item setTarget: self];
2649        [item setAction: @selector(createFile:)];
2650        [item setAutovalidates: NO];
2651    }
2652    else if ([ident isEqualToString: TOOLBAR_OPEN])
2653    {
2654        [item setLabel: NSLocalizedString(@"Open", "Open toolbar item -> label")];
2655        [item setPaletteLabel: NSLocalizedString(@"Open Torrent Files", "Open toolbar item -> palette label")];
2656        [item setToolTip: NSLocalizedString(@"Open torrent files", "Open toolbar item -> tooltip")];
2657        [item setImage: [NSImage imageNamed: @"Open.png"]];
2658        [item setTarget: self];
2659        [item setAction: @selector(openShowSheet:)];
2660        [item setAutovalidates: NO];
2661    }
2662    else if ([ident isEqualToString: TOOLBAR_REMOVE])
2663    {
2664        [item setLabel: NSLocalizedString(@"Remove", "Remove toolbar item -> label")];
2665        [item setPaletteLabel: NSLocalizedString(@"Remove Selected", "Remove toolbar item -> palette label")];
2666        [item setToolTip: NSLocalizedString(@"Remove selected transfers", "Remove toolbar item -> tooltip")];
2667        [item setImage: [NSImage imageNamed: @"Remove.png"]];
2668        [item setTarget: self];
2669        [item setAction: @selector(removeNoDelete:)];
2670    }
2671    else if ([ident isEqualToString: TOOLBAR_INFO])
2672    {
2673        [item setLabel: NSLocalizedString(@"Inspector", "Inspector toolbar item -> label")];
2674        [item setPaletteLabel: NSLocalizedString(@"Toggle Inspector", "Inspector toolbar item -> palette label")];
2675        [item setToolTip: NSLocalizedString(@"Toggle the torrent inspector", "Inspector toolbar item -> tooltip")];
2676        [item setImage: [NSImage imageNamed: @"Info.png"]];
2677        [item setTarget: self];
2678        [item setAction: @selector(showInfo:)];
2679        [item setAutovalidates: NO];
2680    }
2681    else if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
2682    {
2683        [item setLabel: NSLocalizedString(@"Pause All", "Pause All toolbar item -> label")];
2684        [item setPaletteLabel: [item label]];
2685        [item setToolTip: NSLocalizedString(@"Pause all transfers", "Pause All toolbar item -> tooltip")];
2686        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
2687        [item setTarget: self];
2688        [item setAction: @selector(stopAllTorrents:)];
2689    }
2690    else if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
2691    {
2692        [item setLabel: NSLocalizedString(@"Resume All", "Resume All toolbar item -> label")];
2693        [item setPaletteLabel: [item label]];
2694        [item setToolTip: NSLocalizedString(@"Resume all transfers", "Resume All toolbar item -> tooltip")];
2695        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
2696        [item setTarget: self];
2697        [item setAction: @selector(resumeAllTorrents:)];
2698    }
2699    else if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
2700    {
2701        [item setLabel: NSLocalizedString(@"Pause", "Pause toolbar item -> label")];
2702        [item setPaletteLabel: NSLocalizedString(@"Pause Selected", "Pause toolbar item -> palette label")];
2703        [item setToolTip: NSLocalizedString(@"Pause selected transfers", "Pause toolbar item -> tooltip")];
2704        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
2705        [item setTarget: self];
2706        [item setAction: @selector(stopSelectedTorrents:)];
2707    }
2708    else if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
2709    {
2710        [item setLabel: NSLocalizedString(@"Resume", "Resume toolbar item -> label")];
2711        [item setPaletteLabel: NSLocalizedString(@"Resume Selected", "Resume toolbar item -> palette label")];
2712        [item setToolTip: NSLocalizedString(@"Resume selected transfers", "Resume toolbar item -> tooltip")];
2713        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
2714        [item setTarget: self];
2715        [item setAction: @selector(resumeSelectedTorrents:)];
2716    }
2717    else if ([ident isEqualToString: TOOLBAR_FILTER])
2718    {
2719        [item setLabel: NSLocalizedString(@"Filter", "Filter toolbar item -> label")];
2720        [item setPaletteLabel: NSLocalizedString(@"Toggle Filter", "Filter toolbar item -> palette label")];
2721        [item setToolTip: NSLocalizedString(@"Toggle the filter bar", "Filter toolbar item -> tooltip")];
2722        [item setImage: [NSImage imageNamed: @"Filter.png"]];
2723        [item setTarget: self];
2724        [item setAction: @selector(toggleFilterBar:)];
2725        [item setAutovalidates: NO];
2726    }
2727    else
2728    {
2729        [item release];
2730        return nil;
2731    }
2732
2733    return [item autorelease];
2734}
2735
2736- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
2737{
2738    return [NSArray arrayWithObjects:
2739            TOOLBAR_CREATE, TOOLBAR_OPEN, TOOLBAR_REMOVE,
2740            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
2741            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL, TOOLBAR_FILTER, TOOLBAR_INFO,
2742            NSToolbarSeparatorItemIdentifier,
2743            NSToolbarSpaceItemIdentifier,
2744            NSToolbarFlexibleSpaceItemIdentifier,
2745            NSToolbarCustomizeToolbarItemIdentifier, nil];
2746}
2747
2748- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
2749{
2750    return [NSArray arrayWithObjects:
2751            TOOLBAR_CREATE, TOOLBAR_OPEN, TOOLBAR_REMOVE,
2752            NSToolbarSeparatorItemIdentifier,
2753            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
2754            NSToolbarFlexibleSpaceItemIdentifier,
2755            TOOLBAR_FILTER, TOOLBAR_INFO, nil];
2756}
2757
2758#warning validateUserInterfaceItem: ???
2759- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
2760{
2761    NSString * ident = [toolbarItem itemIdentifier];
2762
2763    //enable remove item
2764    if ([ident isEqualToString: TOOLBAR_REMOVE])
2765        return [fTableView numberOfSelectedRows] > 0;
2766
2767    //enable pause all item
2768    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
2769    {
2770        Torrent * torrent;
2771        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2772        while ((torrent = [enumerator nextObject]))
2773            if ([torrent isActive] || [torrent waitingToStart])
2774                return YES;
2775        return NO;
2776    }
2777
2778    //enable resume all item
2779    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
2780    {
2781        Torrent * torrent;
2782        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2783        while ((torrent = [enumerator nextObject]))
2784            if ([torrent isPaused] && ![torrent waitingToStart])
2785                return YES;
2786        return NO;
2787    }
2788
2789    //enable pause item
2790    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
2791    {
2792        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2793        Torrent * torrent;
2794        while ((torrent = [enumerator nextObject]))
2795            if ([torrent isActive] || [torrent waitingToStart])
2796                return YES;
2797        return NO;
2798    }
2799   
2800    //enable resume item
2801    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
2802    {
2803        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2804        Torrent * torrent;
2805        while ((torrent = [enumerator nextObject]))
2806            if ([torrent isPaused] && ![torrent waitingToStart])
2807                return YES;
2808        return NO;
2809    }
2810
2811    return YES;
2812}
2813
2814- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
2815{
2816    SEL action = [menuItem action];
2817
2818    //only enable some items if it is in a context menu or the window is useable
2819    BOOL canUseTable = [fWindow isKeyWindow] || [[menuItem menu] supermenu] != [NSApp mainMenu];
2820
2821    //enable open items
2822    if (action == @selector(openShowSheet:) || action == @selector(openURLShowSheet:))
2823        return [fWindow attachedSheet] == nil;
2824   
2825    //enable sort and advanced bar items
2826    if (action == @selector(setSort:) || /*action == @selector(toggleAdvancedBar:) ||*/ action == @selector(toggleSmallView:))
2827        return [fWindow isVisible];
2828
2829    //enable show info
2830    if (action == @selector(showInfo:))
2831    {
2832        NSString * title = [[fInfoController window] isVisible] ? NSLocalizedString(@"Hide Inspector", "View menu -> Inspector")
2833                            : NSLocalizedString(@"Show Inspector", "View menu -> Inspector");
2834        if (![[menuItem title] isEqualToString: title])
2835            [menuItem setTitle: title];
2836
2837        return YES;
2838    }
2839   
2840    //enable prev/next inspector tab
2841    if (action == @selector(setInfoTab:))
2842        return [[fInfoController window] isVisible];
2843   
2844    //enable toggle status bar
2845    if (action == @selector(toggleStatusBar:))
2846    {
2847        NSString * title = [fStatusBar isHidden] ? NSLocalizedString(@"Show Status Bar", "View menu -> Status Bar")
2848                            : NSLocalizedString(@"Hide Status Bar", "View menu -> Status Bar");
2849        if (![[menuItem title] isEqualToString: title])
2850            [menuItem setTitle: title];
2851
2852        return [fWindow isVisible];
2853    }
2854   
2855    //enable toggle filter bar
2856    if (action == @selector(toggleFilterBar:))
2857    {
2858        NSString * title = [fFilterBar isHidden] ? NSLocalizedString(@"Show Filter Bar", "View menu -> Filter Bar")
2859                            : NSLocalizedString(@"Hide Filter Bar", "View menu -> Filter Bar");
2860        if (![[menuItem title] isEqualToString: title])
2861            [menuItem setTitle: title];
2862
2863        return [fWindow isVisible];
2864    }
2865   
2866    //enable prev/next filter button
2867    if (action == @selector(switchFilter:))
2868        return [fWindow isVisible] && ![fFilterBar isHidden];
2869
2870    //enable reveal in finder
2871    if (action == @selector(revealFile:))
2872        return canUseTable && [fTableView numberOfSelectedRows] > 0;
2873
2874    //enable remove items
2875    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
2876        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteDataAndTorrent:))
2877    {
2878        BOOL warning = NO,
2879            onlyDownloading = [fDefaults boolForKey: @"CheckRemoveDownloading"],
2880            canDelete = action != @selector(removeDeleteTorrent:) && action != @selector(removeDeleteDataAndTorrent:);
2881       
2882        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2883        Torrent * torrent;
2884        while ((torrent = [enumerator nextObject]))
2885        {
2886            if (!warning && [torrent isActive])
2887            {
2888                warning = onlyDownloading ? ![torrent isSeeding] : YES;
2889                if (warning && canDelete)
2890                    break;
2891            }
2892            if (!canDelete && [torrent publicTorrent])
2893            {
2894                canDelete = YES;
2895                if (warning)
2896                    break;
2897            }
2898        }
2899   
2900        //append or remove ellipsis when needed
2901        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
2902        if (warning && [fDefaults boolForKey: @"CheckRemove"])
2903        {
2904            if (![title hasSuffix: ellipsis])
2905                [menuItem setTitle: [title stringByAppendingEllipsis]];
2906        }
2907        else
2908        {
2909            if ([title hasSuffix: ellipsis])
2910                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
2911        }
2912       
2913        return canUseTable && canDelete && [fTableView numberOfSelectedRows] > 0;
2914    }
2915
2916    //enable pause all item
2917    if (action == @selector(stopAllTorrents:))
2918    {
2919        Torrent * torrent;
2920        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2921        while ((torrent = [enumerator nextObject]))
2922            if ([torrent isActive] || [torrent waitingToStart])
2923                return YES;
2924        return NO;
2925    }
2926   
2927    //enable resume all item
2928    if (action == @selector(resumeAllTorrents:))
2929    {
2930        Torrent * torrent;
2931        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2932        while ((torrent = [enumerator nextObject]))
2933            if ([torrent isPaused] && ![torrent waitingToStart])
2934                return YES;
2935        return NO;
2936    }
2937   
2938    //enable resume all waiting item
2939    if (action == @selector(resumeWaitingTorrents:))
2940    {
2941        if (![fDefaults boolForKey: @"Queue"] && ![fDefaults boolForKey: @"QueueSeed"])
2942            return NO;
2943   
2944        Torrent * torrent;
2945        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2946        while ((torrent = [enumerator nextObject]))
2947            if ([torrent isPaused] && [torrent waitingToStart])
2948                return YES;
2949        return NO;
2950    }
2951   
2952    //enable resume selected waiting item
2953    if (action == @selector(resumeSelectedTorrentsNoWait:))
2954    {
2955        if (![fDefaults boolForKey: @"Queue"] && ![fDefaults boolForKey: @"QueueSeed"])
2956            return NO;
2957   
2958        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2959        Torrent * torrent;
2960        while ((torrent = [enumerator nextObject]))
2961            if ([torrent isPaused])
2962                return YES;
2963        return NO;
2964    }
2965
2966    //enable pause item
2967    if (action == @selector(stopSelectedTorrents:))
2968    {
2969        if (!canUseTable)
2970            return NO;
2971   
2972        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2973        Torrent * torrent;
2974        while ((torrent = [enumerator nextObject]))
2975            if ([torrent isActive] || [torrent waitingToStart])
2976                return YES;
2977        return NO;
2978    }
2979   
2980    //enable resume item
2981    if (action == @selector(resumeSelectedTorrents:))
2982    {
2983        if (!canUseTable)
2984            return NO;
2985   
2986        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2987        Torrent * torrent;
2988        while ((torrent = [enumerator nextObject]))
2989            if ([torrent isPaused] && ![torrent waitingToStart])
2990                return YES;
2991        return NO;
2992    }
2993   
2994    //enable manual announce item
2995    if (action == @selector(announceSelectedTorrents:))
2996    {
2997        if (!canUseTable)
2998            return NO;
2999       
3000        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
3001        Torrent * torrent;
3002        while ((torrent = [enumerator nextObject]))
3003            if ([torrent canManualAnnounce])
3004                return YES;
3005        return NO;
3006    }
3007   
3008    //enable reset cache item
3009    if (action == @selector(resetCacheForSelectedTorrents:))
3010        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3011   
3012    //enable move torrent file item
3013    if (action == @selector(moveDataFiles:))
3014        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3015   
3016    //enable copy torrent file item
3017    if (action == @selector(copyTorrentFiles:))
3018        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3019   
3020    //enable reverse sort item
3021    if (action == @selector(setSortReverse:))
3022        return ![[fDefaults stringForKey: @"Sort"] isEqualToString: @"Order"];
3023   
3024    //check proper filter search item
3025    if (action == @selector(setFilterSearchType:))
3026    {
3027        NSString * filterType = [fDefaults stringForKey: @"FilterSearchType"];
3028       
3029        int tag = [menuItem tag];
3030        BOOL state;
3031        if (tag == FILTER_TYPE_TAG_TRACKER)
3032            state = [filterType isEqualToString: FILTER_TYPE_TRACKER];
3033        else
3034            state = [filterType isEqualToString: FILTER_TYPE_NAME];
3035       
3036        [menuItem setState: state ? NSOnState : NSOffState];
3037        return YES;
3038    }
3039   
3040    return YES;
3041}
3042
3043- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument
3044{
3045    NSEnumerator * enumerator;
3046    Torrent * torrent;
3047    BOOL allowSleep;
3048
3049    switch (messageType)
3050    {
3051        case kIOMessageSystemWillSleep:
3052            //close all connections before going to sleep and remember we should resume when we wake up
3053            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
3054
3055            //wait for running transfers to stop (5 second timeout)
3056            NSDate * start = [NSDate date];
3057            BOOL timeUp = NO;
3058           
3059            enumerator = [fTorrents objectEnumerator];
3060            while (!timeUp && (torrent = [enumerator nextObject]))
3061                while (![torrent isPaused] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
3062                {
3063                    usleep(100000);
3064                    [torrent update];
3065                }
3066
3067            IOAllowPowerChange(fRootPort, (long) messageArgument);
3068            break;
3069
3070        case kIOMessageCanSystemSleep:
3071            allowSleep = YES;
3072            if ([fDefaults boolForKey: @"SleepPrevent"])
3073            {
3074                //prevent idle sleep unless no torrents are active
3075                enumerator = [fTorrents objectEnumerator];
3076                while ((torrent = [enumerator nextObject]))
3077                    if ([torrent isActive] && ![torrent isStalled] && ![torrent isError])
3078                    {
3079                        allowSleep = NO;
3080                        break;
3081                    }
3082            }
3083
3084            if (allowSleep)
3085                IOAllowPowerChange(fRootPort, (long) messageArgument);
3086            else
3087                IOCancelPowerChange(fRootPort, (long) messageArgument);
3088            break;
3089
3090        case kIOMessageSystemHasPoweredOn:
3091            //resume sleeping transfers after we wake up
3092            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
3093            [self autoSpeedLimitChange: nil];
3094            break;
3095    }
3096}
3097
3098- (NSMenu *) applicationDockMenu: (NSApplication *) sender
3099{
3100    int seeding = 0, downloading = 0;
3101    NSEnumerator * enumerator = [fTorrents objectEnumerator];
3102    Torrent * torrent;
3103    while ((torrent = [enumerator nextObject]))
3104    {
3105        if ([torrent isSeeding])
3106            seeding++;
3107        else if ([torrent isActive])
3108            downloading++;
3109        else;
3110    }
3111   
3112    NSMenuItem * seedingItem = [fDockMenu itemWithTag: DOCK_SEEDING_TAG],
3113            * downloadingItem = [fDockMenu itemWithTag: DOCK_DOWNLOADING_TAG];
3114   
3115    BOOL hasSeparator = seedingItem || downloadingItem;
3116   
3117    if (seeding > 0)
3118    {
3119        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Seeding",
3120                                                        "Dock item - Seeding"), seeding];
3121        if (!seedingItem)
3122        {
3123            seedingItem = [[[NSMenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""] autorelease];
3124            [seedingItem setTag: DOCK_SEEDING_TAG];
3125            [fDockMenu insertItem: seedingItem atIndex: 0];
3126        }
3127        else
3128            [seedingItem setTitle: title];
3129    }
3130    else
3131    {
3132        if (seedingItem)
3133            [fDockMenu removeItem: seedingItem];
3134    }
3135   
3136    if (downloading > 0)
3137    {
3138        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Downloading",
3139                                                        "Dock item - Downloading"), downloading];
3140        if (!downloadingItem)
3141        {
3142            downloadingItem = [[[NSMenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""] autorelease];
3143            [downloadingItem setTag: DOCK_DOWNLOADING_TAG];
3144            [fDockMenu insertItem: downloadingItem atIndex: seeding > 0 ? 1 : 0];
3145        }
3146        else
3147            [downloadingItem setTitle: title];
3148    }
3149    else
3150    {
3151        if (downloadingItem)
3152            [fDockMenu removeItem: downloadingItem];
3153    }
3154   
3155    if (seeding > 0 || downloading > 0)
3156    {
3157        if (!hasSeparator)
3158            [fDockMenu insertItem: [NSMenuItem separatorItem] atIndex: seeding > 0 && downloading > 0 ? 2 : 1];
3159    }
3160    else
3161    {
3162        if (hasSeparator)
3163            [fDockMenu removeItemAtIndex: 0];
3164    }
3165   
3166    return fDockMenu;
3167}
3168
3169- (void) updateDockBadge: (NSNotification *) notification
3170{
3171    [fBadger updateBadge];
3172}
3173
3174- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
3175{
3176    //if auto size is enabled, the current frame shouldn't need to change
3177    NSRect frame = [fDefaults boolForKey: @"AutoSize"] ? [window frame] : [self sizedWindowFrame];
3178   
3179    frame.size.width = [fDefaults boolForKey: @"SmallView"] ? [fWindow minSize].width : WINDOW_REGULAR_WIDTH;
3180    return frame;
3181}
3182
3183- (void) setWindowSizeToFit
3184{
3185    if ([fDefaults boolForKey: @"AutoSize"])
3186    {
3187        [fScrollView setHasVerticalScroller: NO];
3188        [fWindow setFrame: [self sizedWindowFrame] display: YES animate: YES];
3189        [fScrollView setHasVerticalScroller: YES];
3190    }
3191}
3192
3193- (NSRect) sizedWindowFrame
3194{
3195    float heightChange = [fDisplayedTorrents count] * ([fTableView rowHeight] +
3196            [fTableView intercellSpacing].height) - [fScrollView frame].size.height;
3197    return [self windowFrameByAddingHeight: heightChange checkLimits: YES];
3198}
3199
3200- (void) showMainWindow: (id) sender
3201{
3202    [fWindow makeKeyAndOrderFront: nil];
3203}
3204
3205- (void) windowDidBecomeKey: (NSNotification *) notification
3206{
3207    [fStatusBar setNeedsDisplay: YES];
3208   
3209    [fBadger clearCompleted];
3210    [self updateUI];
3211}
3212
3213- (void) windowDidResignKey: (NSNotification *) notification
3214{
3215    [fStatusBar setNeedsDisplay: YES];
3216}
3217
3218- (NSSize) windowWillResize: (NSWindow *) sender toSize: (NSSize) proposedFrameSize
3219{
3220    //only resize horizontally if autosize is enabled
3221    if ([fDefaults boolForKey: @"AutoSize"])
3222        proposedFrameSize.height = [fWindow frame].size.height;
3223    return proposedFrameSize;
3224}
3225
3226- (void) windowDidResize: (NSNotification *) notification
3227{
3228    //size search filter to not overlap buttons
3229    float pointX = NSMaxX([fPauseFilterButton frame]) + 5.0;
3230    NSRect oldFrame = [fSearchFilterField frame],
3231            frame = NSMakeRect(pointX, oldFrame.origin.y, NSMaxX(oldFrame) - pointX, oldFrame.size.height);
3232   
3233    BOOL show;
3234    if (show = (frame.size.width >= SEARCH_FILTER_MIN_WIDTH))
3235    {
3236        //make sure it is not too long
3237        if (frame.size.width > SEARCH_FILTER_MAX_WIDTH)
3238        {
3239            float different = frame.size.width - SEARCH_FILTER_MAX_WIDTH;
3240            frame.origin.x += different;
3241            frame.size.width -= different;
3242        }
3243        [fSearchFilterField setFrame: frame];
3244    }
3245   
3246    //hide search filter if it overlaps filter buttons
3247    [fSearchFilterField setHidden: !show];
3248}
3249
3250- (void) applicationWillUnhide: (NSNotification *) notification
3251{
3252    [self updateUI];
3253}
3254
3255- (void) linkHomepage: (id) sender
3256{
3257    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
3258}
3259
3260- (void) linkForums: (id) sender
3261{
3262    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
3263}
3264
3265- (void) linkDonate: (id) sender
3266{
3267    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: DONATE_URL]];
3268}
3269
3270- (void) prepareForUpdate: (NSNotification *) notification
3271{
3272    fUpdateInProgress = YES;
3273}
3274
3275- (NSDictionary *) registrationDictionaryForGrowl
3276{
3277    NSArray * notifications = [NSArray arrayWithObjects: GROWL_DOWNLOAD_COMPLETE, GROWL_SEEDING_COMPLETE,
3278                                                            GROWL_AUTO_ADD, GROWL_AUTO_SPEED_LIMIT, nil];
3279    return [NSDictionary dictionaryWithObjectsAndKeys: notifications, GROWL_NOTIFICATIONS_ALL,
3280                                notifications, GROWL_NOTIFICATIONS_DEFAULT, nil];
3281}
3282
3283- (void) growlNotificationWasClicked: (id) clickContext
3284{
3285    if (!clickContext || ![clickContext isKindOfClass: [NSDictionary class]])
3286        return;
3287   
3288    NSString * type = [clickContext objectForKey: @"Type"], * location;
3289    if (([type isEqualToString: GROWL_DOWNLOAD_COMPLETE] || [type isEqualToString: GROWL_SEEDING_COMPLETE])
3290            && (location = [clickContext objectForKey: @"Location"]))
3291        [[NSWorkspace sharedWorkspace] selectFile: location inFileViewerRootedAtPath: nil];
3292}
3293
3294- (void) ipcQuit
3295{
3296    fRemoteQuit = YES;
3297    [NSApp terminate: self];
3298}
3299
3300- (NSArray *) ipcGetTorrentsByID: (NSArray *) idlist
3301{
3302    if (!idlist)
3303        return fTorrents;
3304   
3305    NSMutableArray * torrents = [NSMutableArray array];
3306   
3307    NSEnumerator * torrentEnum = [fTorrents objectEnumerator], * idEnum;
3308    int torId;
3309    Torrent * torrent;
3310    NSNumber * tempId;
3311    while ((torrent = [torrentEnum nextObject]))
3312    {
3313        torId = [torrent torrentID];
3314       
3315        idEnum = [idlist objectEnumerator];
3316        while ((tempId = [idEnum nextObject]))
3317        {
3318            if ([tempId intValue] == torId)
3319            {
3320                [torrents addObject: torrent];
3321                break;
3322            }
3323        }
3324    }
3325
3326    return torrents;
3327}
3328
3329- (NSArray *) ipcGetTorrentsByHash: (NSArray *) hashlist
3330{
3331    if (!hashlist)
3332        return fTorrents;
3333   
3334    NSMutableArray * torrents = [NSMutableArray array];
3335   
3336    NSEnumerator * torrentEnum = [fTorrents objectEnumerator], * hashEnum;
3337    NSString * torHash, * tempHash;
3338    Torrent * torrent;
3339    while ((torrent = [torrentEnum nextObject]))
3340    {
3341        torHash = [torrent hashString];
3342       
3343        hashEnum = [hashlist objectEnumerator];
3344        while ((tempHash = [hashEnum nextObject]))
3345        {
3346            if ([torHash caseInsensitiveCompare: tempHash] == NSOrderedSame)
3347            {
3348                [torrents addObject: torrent];
3349                break;
3350            }
3351        }
3352    }
3353   
3354    return torrents;
3355}
3356
3357- (BOOL) ipcAddTorrents: (NSArray *) torrents
3358{
3359    int oldCount = [fTorrents count];
3360   
3361    [self openFiles: torrents];
3362   
3363    return [fTorrents count] > oldCount;
3364}
3365
3366- (BOOL) ipcAddTorrentFile: (NSString *) path directory: (NSString *) directory
3367{
3368    int oldCount = [fTorrents count];
3369   
3370    [self openFiles: [NSArray arrayWithObject: path] forcePath: directory ignoreDownloadFolder: NO
3371            deleteTorrentFile: TORRENT_FILE_DEFAULT];
3372   
3373    return [fTorrents count] > oldCount;
3374}
3375
3376- (BOOL) ipcAddTorrentFileAutostart: (NSString *) path directory: (NSString *) directory autostart: (BOOL) autostart
3377{
3378    /* 'path' is path to torrent file, 'dir' is the directory it
3379       should download it's files to and may be nil, 'autostart' is a
3380       boolean indicating if the torrent should be automatically
3381       started (or queued to start, I guess), should return NO if
3382       torrent fails to load */
3383    return NO;
3384}
3385
3386- (BOOL) ipcAddTorrentData: (NSData *) data directory: (NSString *) directory
3387{
3388    /* 'data' is the contents of a torrent file, 'directory' is the
3389       directory it should download it's files to and may be nil,
3390       should return NO if torrent fails to load */
3391    return NO;
3392}
3393
3394- (BOOL) ipcAddTorrentDataAutostart: (NSData *) path directory: (NSString *) directory autostart: (BOOL) autostart
3395{
3396    /* 'data' is the contents of a torrent file, 'directory' is the
3397       directory it should download it's files to and may be nil,
3398       'autostart' is a boolean indicating if the torrent should be
3399       automatically started (or queued to start, I guess), should
3400       return NO if torrent fails to load */
3401    return NO;
3402}
3403
3404- (BOOL) ipcStartTorrents: (NSArray *) torrents
3405{
3406    if (!torrents)
3407        [self resumeAllTorrents: self];
3408    else
3409        [self resumeTorrents: torrents];
3410
3411    return YES;
3412}
3413
3414- (BOOL) ipcStopTorrents: (NSArray *) torrents
3415{
3416    if (!torrents)
3417        [self stopAllTorrents: self];
3418    else
3419        [self stopTorrents: torrents];
3420
3421    return YES;
3422}
3423
3424- (BOOL) ipcRemoveTorrents: (NSArray *) torrents
3425{
3426    if (!torrents)
3427        torrents = [NSArray arrayWithArray: fTorrents];
3428
3429    [self confirmRemoveTorrents: torrents deleteData: NO deleteTorrent: NO];
3430
3431    return YES;
3432}
3433
3434@end
Note: See TracBrowser for help on using the repository browser.