source: trunk/macosx/Controller.m @ 3489

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

set some menu titles without regard to what it is already set to (this should in theory be faster and looks cleaner)

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