source: trunk/macosx/Controller.m @ 12492

Last change on this file since 12492 was 12492, checked in by livings124, 12 years ago

When hiding the filter bar, make the table view the first responder right away.

  • Property svn:keywords set to Date Rev Author Id
File size: 155.6 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 12492 2011-06-07 23:40:10Z livings124 $
3 *
4 * Copyright (c) 2005-2011 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#import <IOKit/pwr_mgt/IOPMLib.h>
27#import <Carbon/Carbon.h>
28
29#import "Controller.h"
30#import "Torrent.h"
31#import "TorrentGroup.h"
32#import "TorrentTableView.h"
33#import "CreatorWindowController.h"
34#import "StatsWindowController.h"
35#import "InfoWindowController.h"
36#import "PrefsController.h"
37#import "GroupsController.h"
38#import "AboutWindowController.h"
39#import "URLSheetWindowController.h"
40#import "AddWindowController.h"
41#import "AddMagnetWindowController.h"
42#import "MessageWindowController.h"
43#import "ButtonToolbarItem.h"
44#import "GroupToolbarItem.h"
45#import "ToolbarSegmentedCell.h"
46#import "BlocklistDownloader.h"
47#import "StatusBarController.h"
48#import "FilterBarController.h"
49#import "BonjourController.h"
50#import "Badger.h"
51#import "DragOverlayWindow.h"
52#import "NSApplicationAdditions.h"
53#import "NSStringAdditions.h"
54#import "ExpandedPathToPathTransformer.h"
55#import "ExpandedPathToIconTransformer.h"
56
57#import "transmission.h"
58#import "bencode.h"
59#import "utils.h"
60
61#import "UKKQueue.h"
62#import <Sparkle/Sparkle.h>
63
64#define TOOLBAR_CREATE                  @"Toolbar Create"
65#define TOOLBAR_OPEN_FILE               @"Toolbar Open"
66#define TOOLBAR_OPEN_WEB                @"Toolbar Open Web"
67#define TOOLBAR_REMOVE                  @"Toolbar Remove"
68#define TOOLBAR_INFO                    @"Toolbar Info"
69#define TOOLBAR_PAUSE_ALL               @"Toolbar Pause All"
70#define TOOLBAR_RESUME_ALL              @"Toolbar Resume All"
71#define TOOLBAR_PAUSE_RESUME_ALL        @"Toolbar Pause / Resume All"
72#define TOOLBAR_PAUSE_SELECTED          @"Toolbar Pause Selected"
73#define TOOLBAR_RESUME_SELECTED         @"Toolbar Resume Selected"
74#define TOOLBAR_PAUSE_RESUME_SELECTED   @"Toolbar Pause / Resume Selected"
75#define TOOLBAR_FILTER                  @"Toolbar Toggle Filter"
76#define TOOLBAR_QUICKLOOK               @"Toolbar QuickLook"
77
78typedef enum
79{
80    TOOLBAR_PAUSE_TAG = 0,
81    TOOLBAR_RESUME_TAG = 1
82} toolbarGroupTag;
83
84#define SORT_DATE       @"Date"
85#define SORT_NAME       @"Name"
86#define SORT_STATE      @"State"
87#define SORT_PROGRESS   @"Progress"
88#define SORT_TRACKER    @"Tracker"
89#define SORT_ORDER      @"Order"
90#define SORT_ACTIVITY   @"Activity"
91
92typedef enum
93{
94    SORT_ORDER_TAG = 0,
95    SORT_DATE_TAG = 1,
96    SORT_NAME_TAG = 2,
97    SORT_PROGRESS_TAG = 3,
98    SORT_STATE_TAG = 4,
99    SORT_TRACKER_TAG = 5,
100    SORT_ACTIVITY_TAG = 6
101} sortTag;
102
103typedef enum
104{
105    SORT_ASC_TAG = 0,
106    SORT_DESC_TAG = 1
107} sortOrderTag;
108
109#define GROWL_DOWNLOAD_COMPLETE @"Download Complete"
110#define GROWL_SEEDING_COMPLETE  @"Seeding Complete"
111#define GROWL_AUTO_ADD          @"Torrent Auto Added"
112#define GROWL_AUTO_SPEED_LIMIT  @"Speed Limit Auto Changed"
113
114#define TORRENT_TABLE_VIEW_DATA_TYPE    @"TorrentTableViewDataType"
115
116#define ROW_HEIGHT_REGULAR      62.0
117#define ROW_HEIGHT_SMALL        22.0
118#define WINDOW_REGULAR_WIDTH    468.0
119
120#define STATUS_BAR_HEIGHT   21.0
121#define FILTER_BAR_HEIGHT   23.0
122
123#define UPDATE_UI_SECONDS   1.0
124
125#define DOCK_SEEDING_TAG        101
126#define DOCK_DOWNLOADING_TAG    102
127
128#define TRANSFER_PLIST  @"/Library/Application Support/Transmission/Transfers.plist"
129
130#define WEBSITE_URL @"http://www.transmissionbt.com/"
131#define FORUM_URL   @"http://forum.transmissionbt.com/"
132#define TRAC_URL    @"http://trac.transmissionbt.com/"
133#define DONATE_URL  @"http://www.transmissionbt.com/donate.php"
134
135#define DONATE_NAG_TIME (60 * 60 * 24 * 7)
136
137static void altSpeedToggledCallback(tr_session * handle UNUSED, bool active, bool byUser, void * controller)
138{
139    NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys: [[NSNumber alloc] initWithBool: active], @"Active",
140                                [[NSNumber alloc] initWithBool: byUser], @"ByUser", nil];
141    [(Controller *)controller performSelectorOnMainThread: @selector(altSpeedToggledCallbackIsLimited:)
142        withObject: dict waitUntilDone: NO];
143}
144
145static tr_rpc_callback_status rpcCallback(tr_session * handle UNUSED, tr_rpc_callback_type type, struct tr_torrent * torrentStruct,
146                                            void * controller)
147{
148    [(Controller *)controller rpcCallback: type forTorrentStruct: torrentStruct];
149    return TR_RPC_NOREMOVE; //we'll do the remove manually
150}
151
152static void sleepCallback(void * controller, io_service_t y, natural_t messageType, void * messageArgument)
153{
154    [(Controller *)controller sleepCallback: messageType argument: messageArgument];
155}
156
157@implementation Controller
158
159+ (void) initialize
160{
161    //make sure another Transmission.app isn't running already
162    BOOL othersRunning = NO;
163   
164    if ([NSApp isOnSnowLeopardOrBetter])
165    {
166        NSArray * apps = [NSRunningApplicationSL runningApplicationsWithBundleIdentifier: [[NSBundle mainBundle] bundleIdentifier]];
167        othersRunning = [apps count] > 1;
168    }
169    else
170    {
171        NSString * bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
172        const int processIdentifier = [[NSProcessInfo processInfo] processIdentifier];
173
174        for (NSDictionary * dic in [[NSWorkspace sharedWorkspace] launchedApplications])
175        {
176            if ([[dic objectForKey: @"NSApplicationBundleIdentifier"] isEqualToString: bundleIdentifier]
177                    && [[dic objectForKey: @"NSApplicationProcessIdentifier"] intValue] != processIdentifier)
178                othersRunning = YES;
179        }
180    }
181   
182    if (othersRunning)
183    {
184        NSAlert * alert = [[NSAlert alloc] init];
185        [alert addButtonWithTitle: NSLocalizedString(@"Quit", "Transmission already running alert -> button")];
186        [alert setMessageText: NSLocalizedString(@"Transmission is already running.",
187                                                "Transmission already running alert -> title")];
188        [alert setInformativeText: NSLocalizedString(@"There is already a copy of Transmission running. "
189            "This copy cannot be opened until that instance is quit.", "Transmission already running alert -> message")];
190        [alert setAlertStyle: NSCriticalAlertStyle];
191       
192        [alert runModal];
193        [alert release];
194       
195        //kill ourselves right away
196        exit(0);
197    }
198   
199    [[NSUserDefaults standardUserDefaults] registerDefaults: [NSDictionary dictionaryWithContentsOfFile:
200        [[NSBundle mainBundle] pathForResource: @"Defaults" ofType: @"plist"]]];
201   
202    //set custom value transformers
203    ExpandedPathToPathTransformer * pathTransformer = [[[ExpandedPathToPathTransformer alloc] init] autorelease];
204    [NSValueTransformer setValueTransformer: pathTransformer forName: @"ExpandedPathToPathTransformer"];
205   
206    ExpandedPathToIconTransformer * iconTransformer = [[[ExpandedPathToIconTransformer alloc] init] autorelease];
207    [NSValueTransformer setValueTransformer: iconTransformer forName: @"ExpandedPathToIconTransformer"];
208   
209    //cover our asses
210    if ([[NSUserDefaults standardUserDefaults] boolForKey: @"WarningLegal"])
211    {
212        NSAlert * alert = [[NSAlert alloc] init];
213        [alert addButtonWithTitle: NSLocalizedString(@"I Accept", "Legal alert -> button")];
214        [alert addButtonWithTitle: NSLocalizedString(@"Quit", "Legal alert -> button")];
215        [alert setMessageText: NSLocalizedString(@"Welcome to Transmission", "Legal alert -> title")];
216        [alert setInformativeText: NSLocalizedString(@"Transmission is a file-sharing program."
217            " When you run a torrent, its data will be made available to others by means of upload."
218            " You and you alone are fully responsible for exercising proper judgement and abiding by your local laws.",
219            "Legal alert -> message")];
220        [alert setAlertStyle: NSInformationalAlertStyle];
221       
222        if ([alert runModal] == NSAlertSecondButtonReturn)
223            exit(0);
224        [alert release];
225       
226        [[NSUserDefaults standardUserDefaults] setBool: NO forKey: @"WarningLegal"];
227    }
228}
229
230- (id) init
231{
232    if ((self = [super init]))
233    {
234        fDefaults = [NSUserDefaults standardUserDefaults];
235       
236        //checks for old version speeds of -1
237        if ([fDefaults integerForKey: @"UploadLimit"] < 0)
238        {
239            [fDefaults removeObjectForKey: @"UploadLimit"];
240            [fDefaults setBool: NO forKey: @"CheckUpload"];
241        }
242        if ([fDefaults integerForKey: @"DownloadLimit"] < 0)
243        {
244            [fDefaults removeObjectForKey: @"DownloadLimit"];
245            [fDefaults setBool: NO forKey: @"CheckDownload"];
246        }
247       
248        tr_benc settings;
249        tr_bencInitDict(&settings, 41);
250        tr_sessionGetDefaultSettings(&settings);
251       
252        const BOOL usesSpeedLimitSched = [fDefaults boolForKey: @"SpeedLimitAuto"];
253        if (!usesSpeedLimitSched)
254            tr_bencDictAddBool(&settings, TR_PREFS_KEY_ALT_SPEED_ENABLED, [fDefaults boolForKey: @"SpeedLimit"]);
255       
256        tr_bencDictAddInt(&settings, TR_PREFS_KEY_ALT_SPEED_UP_KBps, [fDefaults integerForKey: @"SpeedLimitUploadLimit"]);
257        tr_bencDictAddInt(&settings, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps, [fDefaults integerForKey: @"SpeedLimitDownloadLimit"]);
258       
259        tr_bencDictAddBool(&settings, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, [fDefaults boolForKey: @"SpeedLimitAuto"]);
260        tr_bencDictAddInt(&settings, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, [PrefsController dateToTimeSum:
261                                                                            [fDefaults objectForKey: @"SpeedLimitAutoOnDate"]]);
262        tr_bencDictAddInt(&settings, TR_PREFS_KEY_ALT_SPEED_TIME_END, [PrefsController dateToTimeSum:
263                                                                            [fDefaults objectForKey: @"SpeedLimitAutoOffDate"]]);
264        tr_bencDictAddInt(&settings, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, [fDefaults integerForKey: @"SpeedLimitAutoDay"]);
265       
266        tr_bencDictAddInt(&settings, TR_PREFS_KEY_DSPEED_KBps, [fDefaults integerForKey: @"DownloadLimit"]);
267        tr_bencDictAddBool(&settings, TR_PREFS_KEY_DSPEED_ENABLED, [fDefaults boolForKey: @"CheckDownload"]);
268        tr_bencDictAddInt(&settings, TR_PREFS_KEY_USPEED_KBps, [fDefaults integerForKey: @"UploadLimit"]);
269        tr_bencDictAddBool(&settings, TR_PREFS_KEY_USPEED_ENABLED, [fDefaults boolForKey: @"CheckUpload"]);
270       
271        //hidden prefs
272        if ([fDefaults objectForKey: @"BindAddressIPv4"])
273            tr_bencDictAddStr(&settings, TR_PREFS_KEY_BIND_ADDRESS_IPV4, [[fDefaults stringForKey: @"BindAddressIPv4"] UTF8String]);
274        if ([fDefaults objectForKey: @"BindAddressIPv6"])
275            tr_bencDictAddStr(&settings, TR_PREFS_KEY_BIND_ADDRESS_IPV6, [[fDefaults stringForKey: @"BindAddressIPv6"] UTF8String]);
276       
277        tr_bencDictAddBool(&settings, TR_PREFS_KEY_BLOCKLIST_ENABLED, [fDefaults boolForKey: @"BlocklistNew"]);
278        if ([fDefaults objectForKey: @"BlocklistURL"])
279            tr_bencDictAddStr(&settings, TR_PREFS_KEY_BLOCKLIST_URL, [[fDefaults stringForKey: @"BlocklistURL"] UTF8String]);
280        tr_bencDictAddBool(&settings, TR_PREFS_KEY_DHT_ENABLED, [fDefaults boolForKey: @"DHTGlobal"]);
281        tr_bencDictAddStr(&settings, TR_PREFS_KEY_DOWNLOAD_DIR, [[[fDefaults stringForKey: @"DownloadFolder"]
282                                                                    stringByExpandingTildeInPath] UTF8String]);
283        tr_bencDictAddInt(&settings, TR_PREFS_KEY_IDLE_LIMIT, [fDefaults integerForKey: @"IdleLimitMinutes"]);
284        tr_bencDictAddBool(&settings, TR_PREFS_KEY_IDLE_LIMIT_ENABLED, [fDefaults boolForKey: @"IdleLimitCheck"]);
285        tr_bencDictAddStr(&settings, TR_PREFS_KEY_INCOMPLETE_DIR, [[[fDefaults stringForKey: @"IncompleteDownloadFolder"]
286                                                                    stringByExpandingTildeInPath] UTF8String]);
287        tr_bencDictAddBool(&settings, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED, [fDefaults boolForKey: @"UseIncompleteDownloadFolder"]);
288        tr_bencDictAddBool(&settings, TR_PREFS_KEY_LPD_ENABLED, [fDefaults boolForKey: @"LocalPeerDiscoveryGlobal"]);
289        tr_bencDictAddInt(&settings, TR_PREFS_KEY_MSGLEVEL, TR_MSG_DBG);
290        tr_bencDictAddInt(&settings, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, [fDefaults integerForKey: @"PeersTotal"]);
291        tr_bencDictAddInt(&settings, TR_PREFS_KEY_PEER_LIMIT_TORRENT, [fDefaults integerForKey: @"PeersTorrent"]);
292       
293        const BOOL randomPort = [fDefaults boolForKey: @"RandomPort"];
294        tr_bencDictAddBool(&settings, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, randomPort);
295        if (!randomPort)
296            tr_bencDictAddInt(&settings, TR_PREFS_KEY_PEER_PORT, [fDefaults integerForKey: @"BindPort"]);
297       
298        //hidden pref
299        if ([fDefaults objectForKey: @"PeerSocketTOS"])
300            tr_bencDictAddStr(&settings, TR_PREFS_KEY_PEER_SOCKET_TOS, [[fDefaults stringForKey: @"PeerSocketTOS"] UTF8String]);
301       
302        tr_bencDictAddBool(&settings, TR_PREFS_KEY_PEX_ENABLED, [fDefaults boolForKey: @"PEXGlobal"]);
303        tr_bencDictAddBool(&settings, TR_PREFS_KEY_PORT_FORWARDING, [fDefaults boolForKey: @"NatTraversal"]);
304        tr_bencDictAddReal(&settings, TR_PREFS_KEY_RATIO, [fDefaults floatForKey: @"RatioLimit"]);
305        tr_bencDictAddBool(&settings, TR_PREFS_KEY_RATIO_ENABLED, [fDefaults boolForKey: @"RatioCheck"]);
306        tr_bencDictAddBool(&settings, TR_PREFS_KEY_RENAME_PARTIAL_FILES, [fDefaults boolForKey: @"RenamePartialFiles"]);
307        tr_bencDictAddBool(&settings, TR_PREFS_KEY_RPC_AUTH_REQUIRED,  [fDefaults boolForKey: @"RPCAuthorize"]);
308        tr_bencDictAddBool(&settings, TR_PREFS_KEY_RPC_ENABLED,  [fDefaults boolForKey: @"RPC"]);
309        tr_bencDictAddInt(&settings, TR_PREFS_KEY_RPC_PORT, [fDefaults integerForKey: @"RPCPort"]);
310        tr_bencDictAddStr(&settings, TR_PREFS_KEY_RPC_USERNAME,  [[fDefaults stringForKey: @"RPCUsername"] UTF8String]);
311        tr_bencDictAddBool(&settings, TR_PREFS_KEY_RPC_WHITELIST_ENABLED,  [fDefaults boolForKey: @"RPCUseWhitelist"]);
312        tr_bencDictAddBool(&settings, TR_PREFS_KEY_START, [fDefaults boolForKey: @"AutoStartDownload"]);
313        tr_bencDictAddBool(&settings, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED, [fDefaults boolForKey: @"DoneScriptEnabled"]);
314        tr_bencDictAddStr(&settings, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME, [[fDefaults stringForKey: @"DoneScriptPath"] UTF8String]);
315        tr_bencDictAddBool(&settings, TR_PREFS_KEY_UTP_ENABLED, [fDefaults boolForKey: @"UTPGlobal"]);
316       
317        tr_formatter_size_init([NSApp isOnSnowLeopardOrBetter] ? 1000 : 1024,
318                                    [NSLocalizedString(@"KB", "File size - kilobytes") UTF8String],
319                                    [NSLocalizedString(@"MB", "File size - megabytes") UTF8String],
320                                    [NSLocalizedString(@"GB", "File size - gigabytes") UTF8String],
321                                    [NSLocalizedString(@"TB", "File size - terabytes") UTF8String]);
322
323        tr_formatter_speed_init([NSApp isOnSnowLeopardOrBetter] ? 1000 : 1024,
324                                    [NSLocalizedString(@"KB/s", "Transfer speed (kilobytes per second)") UTF8String],
325                                    [NSLocalizedString(@"MB/s", "Transfer speed (megabytes per second)") UTF8String],
326                                    [NSLocalizedString(@"GB/s", "Transfer speed (gigabytes per second)") UTF8String],
327                                    [NSLocalizedString(@"TB/s", "Transfer speed (terabytes per second)") UTF8String]); //why not?
328
329        tr_formatter_mem_init(1024, [NSLocalizedString(@"KB", "Memory size - kilobytes") UTF8String],
330                                    [NSLocalizedString(@"MB", "Memory size - megabytes") UTF8String],
331                                    [NSLocalizedString(@"GB", "Memory size - gigabytes") UTF8String],
332                                    [NSLocalizedString(@"TB", "Memory size - terabytes") UTF8String]);
333       
334        const char * configDir = tr_getDefaultConfigDir("Transmission");
335        fLib = tr_sessionInit("macosx", configDir, YES, &settings);
336        tr_bencFree(&settings);
337       
338        [NSApp setDelegate: self];
339       
340        //register for magnet URLs (has to be in init)
341        [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self andSelector: @selector(handleOpenContentsEvent:replyEvent:)
342            forEventClass: kInternetEventClass andEventID: kAEGetURL];
343       
344        fTorrents = [[NSMutableArray alloc] init];
345        fDisplayedTorrents = [[NSMutableArray alloc] init];
346       
347        fInfoController = [[InfoWindowController alloc] init];
348       
349        [PrefsController setHandle: fLib];
350        fPrefsController = [[PrefsController alloc] init];
351       
352        fQuitting = NO;
353        fSoundPlaying = NO;
354       
355        tr_sessionSetAltSpeedFunc(fLib, altSpeedToggledCallback, self);
356        if (usesSpeedLimitSched)
357            [fDefaults setBool: tr_sessionUsesAltSpeed(fLib) forKey: @"SpeedLimit"];
358       
359        tr_sessionSetRPCCallback(fLib, rpcCallback, self);
360       
361        [GrowlApplicationBridge setGrowlDelegate: self];
362       
363        [[UKKQueue sharedFileWatcher] setDelegate: self];
364       
365        [[SUUpdater sharedUpdater] setDelegate: self];
366        fQuitRequested = NO;
367       
368        fPauseOnLaunch = (GetCurrentKeyModifiers() & (optionKey | rightOptionKey)) != 0;
369    }
370    return self;
371}
372
373- (void) awakeFromNib
374{
375    NSToolbar * toolbar = [[NSToolbar alloc] initWithIdentifier: @"TRMainToolbar"];
376    [toolbar setDelegate: self];
377    [toolbar setAllowsUserCustomization: YES];
378    [toolbar setAutosavesConfiguration: YES];
379    [toolbar setDisplayMode: NSToolbarDisplayModeIconOnly];
380    [fWindow setToolbar: toolbar];
381    [toolbar release];
382   
383    [fWindow setDelegate: self]; //do manually to avoid placement issue
384   
385    [fWindow makeFirstResponder: fTableView];
386    [fWindow setExcludedFromWindowsMenu: YES];
387   
388    //set table size
389    if ([fDefaults boolForKey: @"SmallView"])
390        [fTableView setRowHeight: ROW_HEIGHT_SMALL];
391   
392    //window min height
393    NSSize contentMinSize = [fWindow contentMinSize];
394    contentMinSize.height = [[fWindow contentView] frame].size.height - [[fTableView enclosingScrollView] frame].size.height
395                                + [fTableView rowHeight] + [fTableView intercellSpacing].height;
396    [fWindow setContentMinSize: contentMinSize];
397    [fWindow setContentBorderThickness: NSMinY([[fTableView enclosingScrollView] frame]) forEdge: NSMinYEdge];
398    [fWindow setMovableByWindowBackground: YES];
399   
400    [[fTotalTorrentsField cell] setBackgroundStyle: NSBackgroundStyleRaised];
401   
402    //set up filter bar
403    [self showFilterBar: [fDefaults boolForKey: @"FilterBar"] animate: NO];
404   
405    //set up status bar
406    [self showStatusBar: [fDefaults boolForKey: @"StatusBar"] animate: NO];
407   
408    [fActionButton setToolTip: NSLocalizedString(@"Shortcuts for changing global settings.",
409                                "Main window -> 1st bottom left button (action) tooltip")];
410    [fSpeedLimitButton setToolTip: NSLocalizedString(@"Speed Limit overrides the total bandwidth limits with its own limits.",
411                                "Main window -> 2nd bottom left button (turtle) tooltip")];
412    [fClearCompletedButton setToolTip: NSLocalizedString(@"Remove all transfers that have completed seeding.",
413                                "Main window -> 3rd bottom left button (remove all) tooltip")];
414   
415    [fTableView registerForDraggedTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE]];
416    [fWindow registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType, NSURLPboardType, nil]];
417
418    //register for sleep notifications
419    IONotificationPortRef notify;
420    io_object_t iterator;
421    if ((fRootPort = IORegisterForSystemPower(self, & notify, sleepCallback, &iterator)))
422        CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify), kCFRunLoopCommonModes);
423    else
424        NSLog(@"Could not IORegisterForSystemPower");
425   
426    //load previous transfers
427    NSArray * history = [NSArray arrayWithContentsOfFile: [NSHomeDirectory() stringByAppendingPathComponent: TRANSFER_PLIST]];
428   
429    if (!history)
430    {
431        //old version saved transfer info in prefs file
432        if ((history = [fDefaults arrayForKey: @"History"]))
433            [fDefaults removeObjectForKey: @"History"];
434    }
435   
436    if (history)
437    {
438        for (NSDictionary * historyItem in history)
439        {
440            Torrent * torrent;
441            if ((torrent = [[Torrent alloc] initWithHistory: historyItem lib: fLib forcePause: fPauseOnLaunch]))
442            {
443                [fTorrents addObject: torrent];
444                [torrent release];
445            }
446        }
447    }
448   
449    fBadger = [[Badger alloc] initWithLib: fLib];
450   
451    //observe notifications
452    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
453   
454    [nc addObserver: self selector: @selector(updateUI)
455                    name: @"UpdateUI" object: nil];
456   
457    [nc addObserver: self selector: @selector(torrentFinishedDownloading:)
458                    name: @"TorrentFinishedDownloading" object: nil];
459   
460    [nc addObserver: self selector: @selector(torrentRestartedDownloading:)
461                    name: @"TorrentRestartedDownloading" object: nil];
462   
463    [nc addObserver: self selector: @selector(torrentFinishedSeeding:)
464                    name: @"TorrentFinishedSeeding" object: nil];
465   
466    //avoids need of setting delegate
467    [nc addObserver: self selector: @selector(torrentTableViewSelectionDidChange:)
468                    name: NSOutlineViewSelectionDidChangeNotification object: fTableView];
469   
470    [nc addObserver: self selector: @selector(changeAutoImport)
471                    name: @"AutoImportSettingChange" object: nil];
472   
473    [nc addObserver: self selector: @selector(setWindowSizeToFit)
474                    name: @"AutoSizeSettingChange" object: nil];
475   
476    [nc addObserver: self selector: @selector(updateForExpandCollape)
477                    name: @"OutlineExpandCollapse" object: nil];
478   
479    [nc addObserver: fWindow selector: @selector(makeKeyWindow)
480                    name: @"MakeWindowKey" object: nil];
481   
482    [nc addObserver: self selector: @selector(updateTorrentsInQueue)
483                    name: @"UpdateQueue" object: nil];
484   
485    [nc addObserver: self selector: @selector(applyFilter)
486                    name: @"ApplyFilter" object: nil];
487   
488    //open newly created torrent file
489    [nc addObserver: self selector: @selector(beginCreateFile:)
490                    name: @"BeginCreateTorrentFile" object: nil];
491   
492    //open newly created torrent file
493    [nc addObserver: self selector: @selector(openCreatedFile:)
494                    name: @"OpenCreatedTorrentFile" object: nil];
495
496    //timer to update the interface every second
497    [self updateUI];
498    fTimer = [NSTimer scheduledTimerWithTimeInterval: UPDATE_UI_SECONDS target: self
499                selector: @selector(updateUI) userInfo: nil repeats: YES];
500    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSModalPanelRunLoopMode];
501    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSEventTrackingRunLoopMode];
502   
503    [self applyFilter];
504   
505    [fWindow makeKeyAndOrderFront: nil];
506   
507    if ([fDefaults boolForKey: @"InfoVisible"])
508        [self showInfo: nil];
509}
510
511- (void) applicationDidFinishLaunching: (NSNotification *) notification
512{
513    [NSApp setServicesProvider: self];
514   
515    //register for dock icon drags (has to be in applicationDidFinishLaunching: to work)
516    [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self andSelector: @selector(handleOpenContentsEvent:replyEvent:)
517        forEventClass: kCoreEventClass andEventID: kAEOpenContents];
518   
519    //auto importing
520    [self checkAutoImportDirectory];
521   
522    //registering the Web UI to Bonjour
523    if ([fDefaults boolForKey: @"RPC"] && [fDefaults boolForKey: @"RPCWebDiscovery"])
524        [[BonjourController defaultController] startWithPort: [fDefaults integerForKey: @"RPCPort"]];
525   
526    //shamelessly ask for donations
527    if ([fDefaults boolForKey: @"WarningDonate"])
528    {
529        tr_session_stats stats;
530        tr_sessionGetCumulativeStats(fLib, &stats);
531        const BOOL firstLaunch = stats.sessionCount <= 1;
532       
533        NSDate * lastDonateDate = [fDefaults objectForKey: @"DonateAskDate"];
534        const BOOL timePassed = !lastDonateDate || (-1 * [lastDonateDate timeIntervalSinceNow]) >= DONATE_NAG_TIME;
535       
536        if (!firstLaunch && timePassed)
537        {
538            [fDefaults setObject: [NSDate date] forKey: @"DonateAskDate"];
539           
540            NSAlert * alert = [[NSAlert alloc] init];
541            [alert setMessageText: NSLocalizedString(@"Support open-source indie software", "Donation beg -> title")];
542           
543            NSString * donateMessage = [NSString stringWithFormat: @"%@\n\n%@",
544                NSLocalizedString(@"Transmission is a full-featured torrent application."
545                    " A lot of time and effort have gone into development, coding, and refinement."
546                    " If you enjoy using it, please consider showing your love with a donation.", "Donation beg -> message"),
547                NSLocalizedString(@"Donate or not, there will be no difference to your torrenting experience.", "Donation beg -> message")];
548           
549            [alert setInformativeText: donateMessage];
550            [alert setAlertStyle: NSInformationalAlertStyle];
551           
552            [alert addButtonWithTitle: [NSLocalizedString(@"Donate", "Donation beg -> button") stringByAppendingEllipsis]];
553            NSButton * noDonateButton = [alert addButtonWithTitle: NSLocalizedString(@"Nope", "Donation beg -> button")];
554            [noDonateButton setKeyEquivalent: @"\e"]; //escape key
555           
556            const BOOL allowNeverAgain = lastDonateDate != nil; //hide the "don't show again" check the first time - give them time to try the app
557            [alert setShowsSuppressionButton: allowNeverAgain];
558            if (allowNeverAgain)
559                [[alert suppressionButton] setTitle: NSLocalizedString(@"Don't bug me about this ever again.", "Donation beg -> button")];
560           
561            const NSInteger donateResult = [alert runModal];
562            if (donateResult == NSAlertFirstButtonReturn)
563                [self linkDonate: self];
564           
565            if (allowNeverAgain)
566                [fDefaults setBool: ([[alert suppressionButton] state] != NSOnState) forKey: @"WarningDonate"];
567           
568            [alert release];
569        }
570    }
571}
572
573- (BOOL) applicationShouldHandleReopen: (NSApplication *) app hasVisibleWindows: (BOOL) visibleWindows
574{
575    NSWindow * mainWindow = [NSApp mainWindow];
576    if (!mainWindow || ![mainWindow isVisible])
577        [fWindow makeKeyAndOrderFront: nil];
578   
579    return NO;
580}
581
582- (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication *) sender
583{
584    if (!fQuitRequested && [fDefaults boolForKey: @"CheckQuit"])
585    {
586        NSInteger active = 0, downloading = 0;
587        for (Torrent * torrent  in fTorrents)
588            if ([torrent isActive] && ![torrent isStalled])
589            {
590                active++;
591                if (![torrent allDownloaded])
592                    downloading++;
593            }
594       
595        if ([fDefaults boolForKey: @"CheckQuitDownloading"] ? downloading > 0 : active > 0)
596        {
597            NSString * message = active == 1
598                ? NSLocalizedString(@"There is an active transfer that will be paused on quit."
599                    " The transfer will automatically resume on the next launch.", "Confirm Quit panel -> message")
600                : [NSString stringWithFormat: NSLocalizedString(@"There are %d active transfers that will be paused on quit."
601                    " The transfers will automatically resume on the next launch.", "Confirm Quit panel -> message"), active];
602
603            NSBeginAlertSheet(NSLocalizedString(@"Are you sure you want to quit?", "Confirm Quit panel -> title"),
604                                NSLocalizedString(@"Quit", "Confirm Quit panel -> button"),
605                                NSLocalizedString(@"Cancel", "Confirm Quit panel -> button"), nil, fWindow, self,
606                                @selector(quitSheetDidEnd:returnCode:contextInfo:), nil, nil, message);
607            return NSTerminateLater;
608        }
609    }
610   
611    return NSTerminateNow;
612}
613
614- (void) quitSheetDidEnd: (NSWindow *) sheet returnCode: (NSInteger) returnCode contextInfo: (void *) contextInfo
615{
616    [NSApp replyToApplicationShouldTerminate: returnCode == NSAlertDefaultReturn];
617}
618
619- (void) applicationWillTerminate: (NSNotification *) notification
620{
621    fQuitting = YES;
622   
623    //stop the Bonjour service
624    [[BonjourController defaultController] stop];
625
626    //stop blocklist download
627    if ([BlocklistDownloader isRunning])
628        [[BlocklistDownloader downloader] cancelDownload];
629   
630    //stop timers and notification checking
631    [[NSNotificationCenter defaultCenter] removeObserver: self];
632   
633    [fTimer invalidate];
634   
635    if (fAutoImportTimer)
636    {   
637        if ([fAutoImportTimer isValid])
638            [fAutoImportTimer invalidate];
639        [fAutoImportTimer release];
640    }
641   
642    [fBadger setQuitting];
643   
644    //remove all torrent downloads
645    if (fPendingTorrentDownloads)
646    {
647        for (NSDictionary * downloadDict in fPendingTorrentDownloads)
648        {
649            NSURLDownload * download = [downloadDict objectForKey: @"Download"];
650            [download cancel];
651            [download release];
652        }
653        [fPendingTorrentDownloads release];
654    }
655   
656    //remember window states and close all windows
657    [fDefaults setBool: [[fInfoController window] isVisible] forKey: @"InfoVisible"];
658   
659    const BOOL quickLookOpen = [NSApp isOnSnowLeopardOrBetter] && [QLPreviewPanelSL sharedPreviewPanelExists]
660                                && [[QLPreviewPanelSL sharedPreviewPanel] isVisible];
661    if (quickLookOpen)
662        [[QLPreviewPanelSL sharedPreviewPanel] updateController];
663   
664    for (NSWindow * window in [NSApp windows])
665        [window close];
666   
667    [self showStatusBar: NO animate: NO];
668    [self showFilterBar: NO animate: NO];
669   
670    //save history
671    [self updateTorrentHistory];
672    [fTableView saveCollapsedGroups];
673   
674    //remaining calls the same as dealloc
675    [fInfoController release];
676    [fMessageController release];
677    [fPrefsController release];
678   
679    [fStatusBar release];
680    [fFilterBar release];
681   
682    [fTorrents release];
683    [fDisplayedTorrents release];
684   
685    [fOverlayWindow release];
686    [fBadger release];
687   
688    [fAutoImportedNames release];
689   
690    [fPreviewPanel release];
691   
692    //complete cleanup
693    tr_sessionClose(fLib);
694}
695
696- (void) handleOpenContentsEvent: (NSAppleEventDescriptor *) event replyEvent: (NSAppleEventDescriptor *) replyEvent
697{
698    NSString * urlString = nil;
699
700    NSAppleEventDescriptor * directObject = [event paramDescriptorForKeyword: keyDirectObject];
701    if ([directObject descriptorType] == typeAEList)
702    {
703        for (NSInteger i = 1; i <= [directObject numberOfItems]; i++)
704            if ((urlString = [[directObject descriptorAtIndex: i] stringValue]))
705                break;
706    }
707    else
708        urlString = [directObject stringValue];
709   
710    if (urlString)
711        [self openURL: urlString];
712}
713
714- (void) download: (NSURLDownload *) download decideDestinationWithSuggestedFilename: (NSString *) suggestedName
715{
716    if ([[suggestedName pathExtension] caseInsensitiveCompare: @"torrent"] != NSOrderedSame)
717    {
718        [download cancel];
719       
720        NSRunAlertPanel(NSLocalizedString(@"Torrent download failed", "Download not a torrent -> title"),
721            [NSString stringWithFormat: NSLocalizedString(@"It appears that the file \"%@\" from %@ is not a torrent file.",
722            "Download not a torrent -> message"), suggestedName,
723            [[[[download request] URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding]],
724            NSLocalizedString(@"OK", "Download not a torrent -> button"), nil, nil);
725       
726        [download release];
727    }
728    else
729        [download setDestination: [NSTemporaryDirectory() stringByAppendingPathComponent: [suggestedName lastPathComponent]]
730                    allowOverwrite: NO];
731}
732
733-(void) download: (NSURLDownload *) download didCreateDestination: (NSString *) path
734{
735    if (!fPendingTorrentDownloads)
736        fPendingTorrentDownloads = [[NSMutableDictionary alloc] init];
737   
738    [fPendingTorrentDownloads setObject: [NSDictionary dictionaryWithObjectsAndKeys:
739                    path, @"Path", download, @"Download", nil] forKey: [[download request] URL]];
740}
741
742- (void) download: (NSURLDownload *) download didFailWithError: (NSError *) error
743{
744    NSRunAlertPanel(NSLocalizedString(@"Torrent download failed", "Torrent download error -> title"),
745        [NSString stringWithFormat: NSLocalizedString(@"The torrent could not be downloaded from %@: %@.",
746        "Torrent download failed -> message"),
747        [[[[download request] URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding],
748        [error localizedDescription]], NSLocalizedString(@"OK", "Torrent download failed -> button"), nil, nil);
749   
750    [fPendingTorrentDownloads removeObjectForKey: [[download request] URL]];
751    if ([fPendingTorrentDownloads count] == 0)
752    {
753        [fPendingTorrentDownloads release];
754        fPendingTorrentDownloads = nil;
755    }
756   
757    [download release];
758}
759
760- (void) downloadDidFinish: (NSURLDownload *) download
761{
762    NSString * path = [[fPendingTorrentDownloads objectForKey: [[download request] URL]] objectForKey: @"Path"];
763   
764    [self openFiles: [NSArray arrayWithObject: path] addType: ADD_URL forcePath: nil];
765   
766    //delete the torrent file after opening
767    [[NSFileManager defaultManager] removeItemAtPath: path error: NULL];
768   
769    [fPendingTorrentDownloads removeObjectForKey: [[download request] URL]];
770    if ([fPendingTorrentDownloads count] == 0)
771    {
772        [fPendingTorrentDownloads release];
773        fPendingTorrentDownloads = nil;
774    }
775   
776    [download release];
777}
778
779- (void) application: (NSApplication *) app openFiles: (NSArray *) filenames
780{
781    [self openFiles: filenames addType: ADD_MANUAL forcePath: nil];
782}
783
784- (void) openFiles: (NSArray *) filenames addType: (addType) type forcePath: (NSString *) path
785{
786    BOOL deleteTorrentFile, canToggleDelete = NO;
787    switch (type)
788    {
789        case ADD_CREATED:
790            deleteTorrentFile = NO;
791            break;
792        case ADD_URL:
793            deleteTorrentFile = YES;
794            break;
795        default:
796            deleteTorrentFile = [fDefaults boolForKey: @"DeleteOriginalTorrent"];
797            canToggleDelete = YES;
798    }
799   
800    for (NSString * torrentPath in filenames)
801    {
802        //ensure torrent doesn't already exist
803        tr_ctor * ctor = tr_ctorNew(fLib);
804        tr_ctorSetMetainfoFromFile(ctor, [torrentPath UTF8String]);
805       
806        tr_info info;
807        const tr_parse_result result = tr_torrentParse(ctor, &info);
808        tr_ctorFree(ctor);
809       
810        if (result != TR_PARSE_OK)
811        {
812            if (result == TR_PARSE_DUPLICATE)
813                [self duplicateOpenAlert: [NSString stringWithUTF8String: info.name]];
814            else if (result == TR_PARSE_ERR)
815            {
816                if (type != ADD_AUTO)
817                    [self invalidOpenAlert: [torrentPath lastPathComponent]];
818            }
819            else
820                NSAssert2(NO, @"Unknown error code (%d) when attempting to open \"%@\"", result, torrentPath);
821           
822            tr_metainfoFree(&info);
823            continue;
824        }
825       
826        //determine download location
827        NSString * location;
828        BOOL lockDestination = NO; //don't override the location with a group location if it has a hardcoded path
829        if (path)
830        {
831            location = [path stringByExpandingTildeInPath];
832            lockDestination = YES;
833        }
834        else if ([fDefaults boolForKey: @"DownloadLocationConstant"])
835            location = [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath];
836        else if (type != ADD_URL)
837            location = [torrentPath stringByDeletingLastPathComponent];
838        else
839            location = nil;
840       
841        //determine to show the options window
842        const BOOL showWindow = type == ADD_SHOW_OPTIONS || ([fDefaults boolForKey: @"DownloadAsk"]
843                                    && (info.isMultifile || ![fDefaults boolForKey: @"DownloadAskMulti"])
844                                    && (type != ADD_AUTO || ![fDefaults boolForKey: @"DownloadAskManual"]));
845        tr_metainfoFree(&info);
846       
847        Torrent * torrent;
848        if (!(torrent = [[Torrent alloc] initWithPath: torrentPath location: location
849                            deleteTorrentFile: showWindow ? NO : deleteTorrentFile lib: fLib]))
850            continue;
851       
852        //change the location if the group calls for it (this has to wait until after the torrent is created)
853        if (!lockDestination && [[GroupsController groups] usesCustomDownloadLocationForIndex: [torrent groupValue]])
854        {
855            location = [[GroupsController groups] customDownloadLocationForIndex: [torrent groupValue]];
856            [torrent changeDownloadFolderBeforeUsing: location];
857        }
858       
859        //verify the data right away if it was newly created
860        if (type == ADD_CREATED)
861            [torrent resetCache];
862       
863        //add it to the "File -> Open Recent" menu
864        [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
865       
866        //show the add window or add directly
867        if (showWindow || !location)
868        {
869            AddWindowController * addController = [[AddWindowController alloc] initWithTorrent: torrent destination: location
870                                                    lockDestination: lockDestination controller: self torrentFile: torrentPath
871                                                    deleteTorrent: deleteTorrentFile canToggleDelete: canToggleDelete];
872            [addController showWindow: self];
873        }
874        else
875        {
876            [torrent setWaitToStart: [fDefaults boolForKey: @"AutoStartDownload"]];
877           
878            [torrent update];
879            [fTorrents addObject: torrent];
880            [torrent release];
881        }
882    }
883
884    [self updateTorrentsInQueue];
885}
886
887- (void) askOpenConfirmed: (AddWindowController *) addController add: (BOOL) add
888{
889    Torrent * torrent = [addController torrent];
890    [addController release];
891   
892    if (add)
893    {
894        [torrent update];
895        [fTorrents addObject: torrent];
896        [torrent release];
897       
898        [self updateTorrentsInQueue];
899    }
900    else
901    {
902        [torrent closeRemoveTorrent: NO];
903        [torrent release];
904    }
905}
906
907- (void) openMagnet: (NSString *) address
908{
909    tr_torrent * duplicateTorrent;
910    if ((duplicateTorrent = tr_torrentFindFromMagnetLink(fLib, [address UTF8String])))
911    {
912        const tr_info * info = tr_torrentInfo(duplicateTorrent);
913        NSString * name = (info != NULL && info->name != NULL) ? [NSString stringWithUTF8String: info->name] : nil;
914        [self duplicateOpenMagnetAlert: address transferName: name];
915        return;
916    }
917   
918    //determine download location
919    NSString * location = nil;
920    if ([fDefaults boolForKey: @"DownloadLocationConstant"])
921        location = [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath];
922   
923    Torrent * torrent;
924    if (!(torrent = [[Torrent alloc] initWithMagnetAddress: address location: location lib: fLib]))
925    {
926        [self invalidOpenMagnetAlert: address];
927        return;
928    }
929   
930    //change the location if the group calls for it (this has to wait until after the torrent is created)
931    if ([[GroupsController groups] usesCustomDownloadLocationForIndex: [torrent groupValue]])
932    {
933        location = [[GroupsController groups] customDownloadLocationForIndex: [torrent groupValue]];
934        [torrent changeDownloadFolderBeforeUsing: location];
935    }
936   
937    if ([fDefaults boolForKey: @"MagnetOpenAsk"] || !location)
938    {
939        AddMagnetWindowController * addController = [[AddMagnetWindowController alloc] initWithTorrent: torrent destination: location
940                                                        controller: self];
941        [addController showWindow: self];
942    }
943    else
944    {
945        [torrent setWaitToStart: [fDefaults boolForKey: @"AutoStartDownload"]];
946       
947        [torrent update];
948        [fTorrents addObject: torrent];
949        [torrent release];
950    }
951
952    [self updateTorrentsInQueue];
953}
954
955- (void) askOpenMagnetConfirmed: (AddMagnetWindowController *) addController add: (BOOL) add
956{
957    Torrent * torrent = [addController torrent];
958    [addController release];
959   
960    if (add)
961    {
962        [torrent update];
963        [fTorrents addObject: torrent];
964        [torrent release];
965       
966        [self updateTorrentsInQueue];
967    }
968    else
969    {
970        [torrent closeRemoveTorrent: NO];
971        [torrent release];
972    }
973}
974
975- (void) openCreatedFile: (NSNotification *) notification
976{
977    NSDictionary * dict = [notification userInfo];
978    [self openFiles: [NSArray arrayWithObject: [dict objectForKey: @"File"]] addType: ADD_CREATED
979                        forcePath: [dict objectForKey: @"Path"]];
980    [dict release];
981}
982
983- (void) openFilesWithDict: (NSDictionary *) dictionary
984{
985    [self openFiles: [dictionary objectForKey: @"Filenames"] addType: [[dictionary objectForKey: @"AddType"] intValue] forcePath: nil];
986   
987    [dictionary release];
988}
989
990//called on by applescript
991- (void) open: (NSArray *) files
992{
993    NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys: files, @"Filenames",
994                                [NSNumber numberWithInt: ADD_MANUAL], @"AddType", nil];
995    [self performSelectorOnMainThread: @selector(openFilesWithDict:) withObject: dict waitUntilDone: NO];
996}
997
998- (void) openShowSheet: (id) sender
999{
1000    NSOpenPanel * panel = [NSOpenPanel openPanel];
1001
1002    [panel setAllowsMultipleSelection: YES];
1003    [panel setCanChooseFiles: YES];
1004    [panel setCanChooseDirectories: NO];
1005
1006    [panel beginSheetForDirectory: nil file: nil types: [NSArray arrayWithObjects: @"org.bittorrent.torrent", @"torrent", nil]
1007        modalForWindow: fWindow modalDelegate: self didEndSelector: @selector(openSheetClosed:returnCode:contextInfo:)
1008        contextInfo: [NSNumber numberWithBool: sender == fOpenIgnoreDownloadFolder]];
1009}
1010
1011- (void) openSheetClosed: (NSOpenPanel *) panel returnCode: (NSInteger) code contextInfo: (NSNumber *) useOptions
1012{
1013    if (code == NSOKButton)
1014    {
1015        NSDictionary * dictionary = [[NSDictionary alloc] initWithObjectsAndKeys: [panel filenames], @"Filenames",
1016            [NSNumber numberWithInt: [useOptions boolValue] ? ADD_SHOW_OPTIONS : ADD_MANUAL], @"AddType", nil];
1017        [self performSelectorOnMainThread: @selector(openFilesWithDict:) withObject: dictionary waitUntilDone: NO];
1018    }
1019}
1020
1021- (void) invalidOpenAlert: (NSString *) filename
1022{
1023    if (![fDefaults boolForKey: @"WarningInvalidOpen"])
1024        return;
1025   
1026    NSAlert * alert = [[NSAlert alloc] init];
1027    [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"\"%@\" is not a valid torrent file.",
1028                            "Open invalid alert -> title"), filename]];
1029    [alert setInformativeText:
1030            NSLocalizedString(@"The torrent file cannot be opened because it contains invalid data.",
1031                            "Open invalid alert -> message")];
1032    [alert setAlertStyle: NSWarningAlertStyle];
1033    [alert addButtonWithTitle: NSLocalizedString(@"OK", "Open invalid alert -> button")];
1034   
1035    [alert runModal];
1036    if ([[alert suppressionButton] state] == NSOnState)
1037        [fDefaults setBool: NO forKey: @"WarningInvalidOpen"];
1038    [alert release];
1039}
1040
1041- (void) invalidOpenMagnetAlert: (NSString *) address
1042{
1043    if (![fDefaults boolForKey: @"WarningInvalidOpen"])
1044        return;
1045   
1046    NSAlert * alert = [[NSAlert alloc] init];
1047    [alert setMessageText: NSLocalizedString(@"Adding magnetized transfer failed.", "Magnet link failed -> title")];
1048    [alert setInformativeText: [NSString stringWithFormat: NSLocalizedString(@"There was an error when adding the magnet link \"%@\"."
1049                                " The transfer will not occur.", "Magnet link failed -> message"), address]];
1050    [alert setAlertStyle: NSWarningAlertStyle];
1051    [alert addButtonWithTitle: NSLocalizedString(@"OK", "Magnet link failed -> button")];
1052   
1053    [alert runModal];
1054    if ([[alert suppressionButton] state] == NSOnState)
1055        [fDefaults setBool: NO forKey: @"WarningInvalidOpen"];
1056    [alert release];
1057}
1058
1059- (void) duplicateOpenAlert: (NSString *) name
1060{
1061    if (![fDefaults boolForKey: @"WarningDuplicate"])
1062        return;
1063   
1064    NSAlert * alert = [[NSAlert alloc] init];
1065    [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"A transfer of \"%@\" already exists.",
1066                            "Open duplicate alert -> title"), name]];
1067    [alert setInformativeText:
1068            NSLocalizedString(@"The transfer cannot be added because it is a duplicate of an already existing transfer.",
1069                            "Open duplicate alert -> message")];
1070    [alert setAlertStyle: NSWarningAlertStyle];
1071    [alert addButtonWithTitle: NSLocalizedString(@"OK", "Open duplicate alert -> button")];
1072    [alert setShowsSuppressionButton: YES];
1073   
1074    [alert runModal];
1075    if ([[alert suppressionButton] state])
1076        [fDefaults setBool: NO forKey: @"WarningDuplicate"];
1077    [alert release];
1078}
1079
1080- (void) duplicateOpenMagnetAlert: (NSString *) address transferName: (NSString *) name
1081{
1082    if (![fDefaults boolForKey: @"WarningDuplicate"])
1083        return;
1084   
1085    NSAlert * alert = [[NSAlert alloc] init];
1086    if (name)
1087        [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"A transfer of \"%@\" already exists.",
1088                                "Open duplicate magnet alert -> title"), name]];
1089    else
1090        [alert setMessageText: NSLocalizedString(@"Magnet link is a duplicate of an existing transfer.",
1091                                "Open duplicate magnet alert -> title")];
1092    [alert setInformativeText: [NSString stringWithFormat:
1093            NSLocalizedString(@"The magnet link  \"%@\" cannot be added because it is a duplicate of an already existing transfer.",
1094                            "Open duplicate magnet alert -> message"), address]];
1095    [alert setAlertStyle: NSWarningAlertStyle];
1096    [alert addButtonWithTitle: NSLocalizedString(@"OK", "Open duplicate magnet alert -> button")];
1097    [alert setShowsSuppressionButton: YES];
1098   
1099    [alert runModal];
1100    if ([[alert suppressionButton] state])
1101        [fDefaults setBool: NO forKey: @"WarningDuplicate"];
1102    [alert release];
1103}
1104
1105- (void) openURL: (NSString *) urlString
1106{
1107    if ([urlString rangeOfString: @"magnet:" options: (NSAnchoredSearch | NSCaseInsensitiveSearch)].location != NSNotFound)
1108        [self openMagnet: urlString];
1109    else
1110    {
1111        if ([urlString rangeOfString: @"://"].location == NSNotFound)
1112        {
1113            if ([urlString rangeOfString: @"."].location == NSNotFound)
1114            {
1115                NSInteger beforeCom;
1116                if ((beforeCom = [urlString rangeOfString: @"/"].location) != NSNotFound)
1117                    urlString = [NSString stringWithFormat: @"http://www.%@.com/%@",
1118                                    [urlString substringToIndex: beforeCom],
1119                                    [urlString substringFromIndex: beforeCom + 1]];
1120                else
1121                    urlString = [NSString stringWithFormat: @"http://www.%@.com/", urlString];
1122            }
1123            else
1124                urlString = [@"http://" stringByAppendingString: urlString];
1125        }
1126       
1127        NSURLRequest * request = [NSURLRequest requestWithURL: [NSURL URLWithString: urlString]
1128                                    cachePolicy: NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval: 60];
1129        [[NSURLDownload alloc] initWithRequest: request delegate: self];
1130    }
1131}
1132
1133- (void) openURLShowSheet: (id) sender
1134{
1135    [[[URLSheetWindowController alloc] initWithController: self] beginSheetForWindow: fWindow];
1136}
1137
1138- (void) urlSheetDidEnd: (URLSheetWindowController *) controller url: (NSString *) urlString returnCode: (NSInteger) returnCode
1139{
1140    if (returnCode == 1)
1141        [self performSelectorOnMainThread: @selector(openURL:) withObject: urlString waitUntilDone: NO];
1142   
1143    [controller release];
1144}
1145
1146- (void) createFile: (id) sender
1147{
1148    [CreatorWindowController createTorrentFile: fLib];
1149}
1150
1151- (void) resumeSelectedTorrents: (id) sender
1152{
1153    [self resumeTorrents: [fTableView selectedTorrents]];
1154}
1155
1156- (void) resumeAllTorrents: (id) sender
1157{
1158    NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [fTorrents count]];
1159   
1160    for (Torrent * torrent in fTorrents)
1161        if (![torrent isFinishedSeeding])
1162            [torrents addObject: torrent];
1163   
1164    [self resumeTorrents: torrents];
1165}
1166
1167- (void) resumeTorrents: (NSArray *) torrents
1168{
1169    for (Torrent * torrent in torrents)
1170        [torrent setWaitToStart: YES];
1171   
1172    [self updateTorrentsInQueue];
1173}
1174
1175- (void) resumeSelectedTorrentsNoWait:  (id) sender
1176{
1177    [self resumeTorrentsNoWait: [fTableView selectedTorrents]];
1178}
1179
1180- (void) resumeWaitingTorrents: (id) sender
1181{
1182    NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [fTorrents count]];
1183   
1184    for (Torrent * torrent in fTorrents)
1185        if (![torrent isActive] && [torrent waitingToStart])
1186            [torrents addObject: torrent];
1187   
1188    [self resumeTorrentsNoWait: torrents];
1189}
1190
1191- (void) resumeTorrentsNoWait: (NSArray *) torrents
1192{
1193    //iterate through instead of all at once to ensure no conflicts
1194    for (Torrent * torrent in torrents)
1195    {
1196        tr_inf( "restarting a torrent in resumeTorrentsNoWait" );
1197        [torrent startTransfer];
1198    }
1199   
1200    [self updateUI];
1201    [self applyFilter];
1202    [[fWindow toolbar] validateVisibleItems];
1203    [self updateTorrentHistory];
1204}
1205
1206- (void) stopSelectedTorrents: (id) sender
1207{
1208    [self stopTorrents: [fTableView selectedTorrents]];
1209}
1210
1211- (void) stopAllTorrents: (id) sender
1212{
1213    [self stopTorrents: fTorrents];
1214}
1215
1216- (void) stopTorrents: (NSArray *) torrents
1217{
1218    //don't want any of these starting then stopping
1219    for (Torrent * torrent in torrents)
1220        [torrent setWaitToStart: NO];
1221
1222    [torrents makeObjectsPerformSelector: @selector(stopTransfer)];
1223   
1224    [self updateUI];
1225    [self applyFilter];
1226    [[fWindow toolbar] validateVisibleItems];
1227    [self updateTorrentHistory];
1228}
1229
1230- (void) removeTorrents: (NSArray *) torrents deleteData: (BOOL) deleteData
1231{
1232    [torrents retain];
1233
1234    if ([fDefaults boolForKey: @"CheckRemove"])
1235    {
1236        NSUInteger active = 0, downloading = 0;
1237        for (Torrent * torrent in torrents)
1238            if ([torrent isActive])
1239            {
1240                ++active;
1241                if (![torrent isSeeding])
1242                    ++downloading;
1243            }
1244
1245        if ([fDefaults boolForKey: @"CheckRemoveDownloading"] ? downloading > 0 : active > 0)
1246        {
1247            NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
1248                                    torrents, @"Torrents",
1249                                    [NSNumber numberWithBool: deleteData], @"DeleteData", nil];
1250           
1251            NSString * title, * message;
1252           
1253            const NSInteger selected = [torrents count];
1254            if (selected == 1)
1255            {
1256                NSString * torrentName = [[torrents objectAtIndex: 0] name];
1257               
1258                if (deleteData)
1259                    title = [NSString stringWithFormat:
1260                                NSLocalizedString(@"Are you sure you want to remove \"%@\" from the transfer list"
1261                                " and trash the data file?", "Removal confirm panel -> title"), torrentName];
1262                else
1263                    title = [NSString stringWithFormat:
1264                                NSLocalizedString(@"Are you sure you want to remove \"%@\" from the transfer list?",
1265                                "Removal confirm panel -> title"), torrentName];
1266               
1267                message = NSLocalizedString(@"This transfer is active."
1268                            " Once removed, continuing the transfer will require the torrent file or magnet link.",
1269                            "Removal confirm panel -> message");
1270            }
1271            else
1272            {
1273                if (deleteData)
1274                    title = [NSString stringWithFormat:
1275                                NSLocalizedString(@"Are you sure you want to remove %@ transfers from the transfer list"
1276                                " and trash the data files?", "Removal confirm panel -> title"), [NSString formattedUInteger: selected]];
1277                else
1278                    title = [NSString stringWithFormat:
1279                                NSLocalizedString(@"Are you sure you want to remove %@ transfers from the transfer list?",
1280                                "Removal confirm panel -> title"), [NSString formattedUInteger: selected]];
1281               
1282                if (selected == active)
1283                    message = [NSString stringWithFormat: NSLocalizedString(@"There are %@ active transfers.",
1284                                "Removal confirm panel -> message part 1"), [NSString formattedUInteger: active]];
1285                else
1286                    message = [NSString stringWithFormat: NSLocalizedString(@"There are %@ transfers (%@ active).",
1287                                "Removal confirm panel -> message part 1"), [NSString formattedUInteger: selected], [NSString formattedUInteger: active]];
1288                message = [message stringByAppendingFormat: @" %@",
1289                            NSLocalizedString(@"Once removed, continuing the transfers will require the torrent files or magnet links.",
1290                            "Removal confirm panel -> message part 2")];
1291            }
1292           
1293            NSBeginAlertSheet(title, NSLocalizedString(@"Remove", "Removal confirm panel -> button"),
1294                NSLocalizedString(@"Cancel", "Removal confirm panel -> button"), nil, fWindow, self,
1295                nil, @selector(removeSheetDidEnd:returnCode:contextInfo:), dict, message);
1296            return;
1297        }
1298    }
1299   
1300    [self confirmRemoveTorrents: torrents deleteData: deleteData];
1301}
1302
1303- (void) removeSheetDidEnd: (NSWindow *) sheet returnCode: (NSInteger) returnCode contextInfo: (NSDictionary *) dict
1304{
1305    NSArray * torrents = [dict objectForKey: @"Torrents"];
1306    if (returnCode == NSAlertDefaultReturn)
1307        [self confirmRemoveTorrents: torrents deleteData: [[dict objectForKey: @"DeleteData"] boolValue]];
1308    else
1309        [torrents release];
1310   
1311    [dict release];
1312}
1313
1314- (void) confirmRemoveTorrents: (NSArray *) torrents deleteData: (BOOL) deleteData
1315{
1316    NSMutableArray * selectedValues = [NSMutableArray arrayWithArray: [fTableView selectedValues]];
1317    [selectedValues removeObjectsInArray: torrents];
1318   
1319    //don't want any of these starting then stopping
1320    for (Torrent * torrent in torrents)
1321        [torrent setWaitToStart: NO];
1322   
1323    [fTorrents removeObjectsInArray: torrents];
1324   
1325    //if not removed from displayed torrents, updateTorrentsInQueue might cause a crash
1326    if ([fDisplayedTorrents count] > 0)
1327    {
1328        if ([[fDisplayedTorrents objectAtIndex: 0] isKindOfClass: [TorrentGroup class]])
1329        {
1330            for (TorrentGroup * group in fDisplayedTorrents)
1331                [[group torrents] removeObjectsInArray: torrents];
1332        }
1333        else
1334            [fDisplayedTorrents removeObjectsInArray: torrents];
1335    }
1336   
1337    for (Torrent * torrent in torrents)
1338    {
1339        //let's expand all groups that have removed items - they either don't exist anymore, are already expanded, or are collapsed (rpc)
1340        [fTableView removeCollapsedGroup: [torrent groupValue]];
1341       
1342        [torrent closeRemoveTorrent: deleteData];
1343    }
1344   
1345    #warning why do we need them retained?
1346    [torrents release];
1347   
1348    [fTableView selectValues: selectedValues];
1349   
1350    [self updateTorrentsInQueue];
1351}
1352
1353- (void) removeNoDelete: (id) sender
1354{
1355    [self removeTorrents: [fTableView selectedTorrents] deleteData: NO];
1356}
1357
1358- (void) removeDeleteData: (id) sender
1359{
1360    [self removeTorrents: [fTableView selectedTorrents] deleteData: YES];
1361}
1362
1363- (void) clearCompleted: (id) sender
1364{
1365    NSMutableArray * torrents = [[NSMutableArray alloc] init];
1366   
1367    for (Torrent * torrent in fTorrents)
1368        if ([torrent isFinishedSeeding])
1369            [torrents addObject: torrent];
1370   
1371    if ([fDefaults boolForKey: @"WarningRemoveCompleted"])
1372    {
1373        NSString * message, * info;
1374        if ([torrents count] == 1)
1375        {
1376            NSString * torrentName = [[torrents objectAtIndex: 0] name];
1377            message = [NSString stringWithFormat: NSLocalizedString(@"Are you sure you want to remove \"%@\" from the transfer list?",
1378                                                                  "Remove completed confirm panel -> title"), torrentName];
1379           
1380            info = NSLocalizedString(@"Once removed, continuing the transfer will require the torrent file or magnet link.",
1381                                     "Remove completed confirm panel -> message");
1382        }
1383        else
1384        {
1385            message = [NSString stringWithFormat: NSLocalizedString(@"Are you sure you want to remove %@ completed transfers from the transfer list?",
1386                                                                  "Remove completed confirm panel -> title"), [NSString formattedUInteger: [torrents count]]];
1387           
1388            info = NSLocalizedString(@"Once removed, continuing the transfers will require the torrent files or magnet links.",
1389                                     "Remove completed confirm panel -> message");
1390        }
1391       
1392        NSAlert * alert = [[[NSAlert alloc] init] autorelease];
1393        [alert setMessageText: message];
1394        [alert setInformativeText: info];
1395        [alert setAlertStyle: NSWarningAlertStyle];
1396        [alert addButtonWithTitle: NSLocalizedString(@"Remove", "Remove completed confirm panel -> button")];
1397        [alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Remove completed confirm panel -> button")];
1398        [alert setShowsSuppressionButton: YES];
1399       
1400        const NSInteger returnCode = [alert runModal];
1401        if ([[alert suppressionButton] state])
1402            [fDefaults setBool: NO forKey: @"WarningRemoveCompleted"];
1403       
1404        if (returnCode != NSAlertFirstButtonReturn)
1405            return;
1406    }
1407   
1408    [self confirmRemoveTorrents: torrents deleteData: NO];
1409}
1410
1411- (void) moveDataFilesSelected: (id) sender
1412{
1413    [self moveDataFiles: [fTableView selectedTorrents]];
1414}
1415
1416- (void) moveDataFiles: (NSArray *) torrents
1417{
1418    NSOpenPanel * panel = [NSOpenPanel openPanel];
1419    [panel setPrompt: NSLocalizedString(@"Select", "Move torrent -> prompt")];
1420    [panel setAllowsMultipleSelection: NO];
1421    [panel setCanChooseFiles: NO];
1422    [panel setCanChooseDirectories: YES];
1423    [panel setCanCreateDirectories: YES];
1424   
1425    torrents = [torrents retain];
1426    NSInteger count = [torrents count];
1427    if (count == 1)
1428        [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the new folder for \"%@\".",
1429                            "Move torrent -> select destination folder"), [[torrents objectAtIndex: 0] name]]];
1430    else
1431        [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the new folder for %d data files.",
1432                            "Move torrent -> select destination folder"), count]];
1433       
1434    [panel beginSheetForDirectory: nil file: nil modalForWindow: fWindow modalDelegate: self
1435        didEndSelector: @selector(moveDataFileChoiceClosed:returnCode:contextInfo:) contextInfo: torrents];
1436}
1437
1438- (void) moveDataFileChoiceClosed: (NSOpenPanel *) panel returnCode: (NSInteger) code contextInfo: (NSArray *) torrents
1439{
1440    if (code == NSOKButton)
1441    {
1442        for (Torrent * torrent in torrents)
1443            [torrent moveTorrentDataFileTo: [[panel filenames] objectAtIndex: 0]];
1444    }
1445   
1446    [torrents release];
1447}
1448
1449- (void) copyTorrentFiles: (id) sender
1450{
1451    [self copyTorrentFileForTorrents: [[NSMutableArray alloc] initWithArray: [fTableView selectedTorrents]]];
1452}
1453
1454- (void) copyTorrentFileForTorrents: (NSMutableArray *) torrents
1455{
1456    if ([torrents count] == 0)
1457    {
1458        [torrents release];
1459        return;
1460    }
1461   
1462    Torrent * torrent = [torrents objectAtIndex: 0];
1463   
1464    if (![torrent isMagnet] && [[NSFileManager defaultManager] fileExistsAtPath: [torrent torrentLocation]])
1465    {
1466        NSSavePanel * panel = [NSSavePanel savePanel];
1467        [panel setAllowedFileTypes: [NSArray arrayWithObjects: @"org.bittorrent.torrent", @"torrent", nil]];
1468        [panel setExtensionHidden: NO];
1469       
1470        [panel beginSheetForDirectory: nil file: [torrent name] modalForWindow: fWindow modalDelegate: self
1471            didEndSelector: @selector(saveTorrentCopySheetClosed:returnCode:contextInfo:) contextInfo: torrents];
1472    }
1473    else
1474    {
1475        if (![torrent isMagnet])
1476        {
1477            NSAlert * alert = [[NSAlert alloc] init];
1478            [alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent file copy alert -> button")];
1479            [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Copy of \"%@\" Cannot Be Created",
1480                                    "Torrent file copy alert -> title"), [torrent name]]];
1481            [alert setInformativeText: [NSString stringWithFormat:
1482                    NSLocalizedString(@"The torrent file (%@) cannot be found.", "Torrent file copy alert -> message"),
1483                                        [torrent torrentLocation]]];
1484            [alert setAlertStyle: NSWarningAlertStyle];
1485           
1486            [alert runModal];
1487            [alert release];
1488        }
1489       
1490        [torrents removeObjectAtIndex: 0];
1491        [self copyTorrentFileForTorrents: torrents];
1492    }
1493}
1494
1495- (void) saveTorrentCopySheetClosed: (NSSavePanel *) panel returnCode: (NSInteger) code contextInfo: (NSMutableArray *) torrents
1496{
1497    //copy torrent to new location with name of data file
1498    if (code == NSOKButton)
1499        [[torrents objectAtIndex: 0] copyTorrentFileTo: [panel filename]];
1500   
1501    [torrents removeObjectAtIndex: 0];
1502    [self performSelectorOnMainThread: @selector(copyTorrentFileForTorrents:) withObject: torrents waitUntilDone: NO];
1503}
1504
1505- (void) copyMagnetLinks: (id) sender
1506{
1507    NSArray * torrents = [fTableView selectedTorrents];
1508   
1509    if ([torrents count] <= 0)
1510        return;
1511   
1512    NSMutableArray * links = [NSMutableArray arrayWithCapacity: [torrents count]];
1513    for (Torrent * torrent in torrents)
1514        [links addObject: [torrent magnetLink]];
1515   
1516    NSString * text = [links componentsJoinedByString: @"\n"];
1517   
1518    NSPasteboard * pb = [NSPasteboard generalPasteboard];
1519    if ([NSApp isOnSnowLeopardOrBetter])
1520    {
1521        [pb clearContents];
1522        [pb writeObjects: [NSArray arrayWithObject: text]];
1523    }
1524    else
1525    {
1526        [pb declareTypes: [NSArray arrayWithObject: NSStringPboardType] owner: nil];
1527        [pb setString: text forType: NSStringPboardType];
1528    }
1529}
1530
1531- (void) revealFile: (id) sender
1532{
1533    NSArray * selected = [fTableView selectedTorrents];
1534    if ([NSApp isOnSnowLeopardOrBetter])
1535    {
1536        NSMutableArray * paths = [NSMutableArray arrayWithCapacity: [selected count]];
1537        for (Torrent * torrent in selected)
1538        {
1539            NSString * location = [torrent dataLocation];
1540            if (location)
1541                [paths addObject: [NSURL fileURLWithPath: location]];
1542        }
1543       
1544        if ([paths count] > 0)
1545            [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: paths];
1546    }
1547    else
1548    {
1549        for (Torrent * torrent in selected)
1550        {
1551            NSString * location = [torrent dataLocation];
1552            if (location)
1553                [[NSWorkspace sharedWorkspace] selectFile: location inFileViewerRootedAtPath: nil];
1554        }
1555    }
1556}
1557
1558- (void) announceSelectedTorrents: (id) sender
1559{
1560    for (Torrent * torrent in [fTableView selectedTorrents])
1561    {
1562        if ([torrent canManualAnnounce])
1563            [torrent manualAnnounce];
1564    }
1565}
1566
1567- (void) verifySelectedTorrents: (id) sender
1568{
1569    [self verifyTorrents: [fTableView selectedTorrents]];
1570}
1571
1572- (void) verifyTorrents: (NSArray *) torrents
1573{
1574    for (Torrent * torrent in torrents)
1575        [torrent resetCache];
1576   
1577    [self applyFilter];
1578}
1579
1580- (void) showPreferenceWindow: (id) sender
1581{
1582    NSWindow * window = [fPrefsController window];
1583    if (![window isVisible])
1584        [window center];
1585
1586    [window makeKeyAndOrderFront: nil];
1587}
1588
1589- (void) showAboutWindow: (id) sender
1590{
1591    [[AboutWindowController aboutController] showWindow: nil];
1592}
1593
1594- (void) showInfo: (id) sender
1595{
1596    if ([[fInfoController window] isVisible])
1597        [fInfoController close];
1598    else
1599    {
1600        [fInfoController updateInfoStats];
1601        [[fInfoController window] orderFront: nil];
1602       
1603        if ([fInfoController canQuickLook]
1604            && [QLPreviewPanelSL sharedPreviewPanelExists] && [[QLPreviewPanelSL sharedPreviewPanel] isVisible])
1605            [[QLPreviewPanelSL sharedPreviewPanel] reloadData];
1606       
1607    }
1608   
1609    [[fWindow toolbar] validateVisibleItems];
1610}
1611
1612- (void) resetInfo
1613{
1614    [fInfoController setInfoForTorrents: [fTableView selectedTorrents]];
1615   
1616    if ([NSApp isOnSnowLeopardOrBetter] && [QLPreviewPanelSL sharedPreviewPanelExists]
1617        && [[QLPreviewPanelSL sharedPreviewPanel] isVisible])
1618        [[QLPreviewPanelSL sharedPreviewPanel] reloadData];
1619}
1620
1621- (void) setInfoTab: (id) sender
1622{
1623    if (sender == fNextInfoTabItem)
1624        [fInfoController setNextTab];
1625    else
1626        [fInfoController setPreviousTab];
1627}
1628
1629- (void) showMessageWindow: (id) sender
1630{
1631    if (!fMessageController)
1632        fMessageController = [[MessageWindowController alloc] init];
1633    [fMessageController showWindow: nil];
1634}
1635
1636- (void) showStatsWindow: (id) sender
1637{
1638    [[StatsWindowController statsWindow: fLib] showWindow: nil];
1639}
1640
1641- (void) updateUI
1642{
1643    [fTorrents makeObjectsPerformSelector: @selector(update)];
1644   
1645    //pull the upload and download speeds - most consistent by using current stats
1646    CGFloat dlRate = 0.0, ulRate = 0.0;
1647    BOOL completed = NO;
1648    for (Torrent * torrent in fTorrents)
1649    {
1650        dlRate += [torrent downloadRate];
1651        ulRate += [torrent uploadRate];
1652       
1653        completed |= [torrent isFinishedSeeding];
1654    }
1655   
1656    if (![NSApp isHidden])
1657    {
1658        if ([fWindow isVisible])
1659        {
1660            [self sortTorrents];
1661           
1662            [fStatusBar updateWithDownload: dlRate upload: ulRate];
1663           
1664            [fClearCompletedButton setHidden: !completed];
1665        }
1666
1667        //update non-constant parts of info window
1668        if ([[fInfoController window] isVisible])
1669            [fInfoController updateInfoStats];
1670    }
1671   
1672    //badge dock
1673    [fBadger updateBadgeWithDownload: dlRate upload: ulRate];
1674}
1675
1676- (void) setBottomCountText: (BOOL) filtering
1677{
1678    NSString * totalTorrentsString;
1679    NSUInteger totalCount = [fTorrents count];
1680    if (totalCount != 1)
1681        totalTorrentsString = [NSString stringWithFormat: NSLocalizedString(@"%@ transfers", "Status bar transfer count"),
1682                                [NSString formattedUInteger: totalCount]];
1683    else
1684        totalTorrentsString = NSLocalizedString(@"1 transfer", "Status bar transfer count");
1685   
1686    if (filtering)
1687    {
1688        NSUInteger count = [fTableView numberOfRows]; //have to factor in collapsed rows
1689        if (count > 0 && ![[fDisplayedTorrents objectAtIndex: 0] isKindOfClass: [Torrent class]])
1690            count -= [fDisplayedTorrents count];
1691       
1692        totalTorrentsString = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@", "Status bar transfer count"),
1693                                [NSString formattedUInteger: count], totalTorrentsString];
1694    }
1695   
1696    [fTotalTorrentsField setStringValue: totalTorrentsString];
1697}
1698
1699- (void) updateTorrentsInQueue
1700{
1701    NSUInteger desiredDownloadActive = [fDefaults boolForKey: @"Queue"] ? [self numToStartFromQueue: YES] : NSUIntegerMax,
1702                desiredSeedActive = [fDefaults boolForKey: @"QueueSeed"] ? [self numToStartFromQueue: NO] : NSUIntegerMax;
1703   
1704    for (Torrent * torrent in fTorrents)
1705    {
1706        if (desiredDownloadActive == 0 && desiredSeedActive == 0)
1707            break;
1708       
1709        if (![torrent isActive] && ![torrent isChecking] && [torrent waitingToStart])
1710        {
1711            if (![torrent allDownloaded])
1712            {
1713                if (desiredDownloadActive > 0)
1714                {
1715                    tr_inf( "restarting download torrent in mac queue" );
1716                    [torrent startTransfer];
1717                    if ([torrent isActive])
1718                        --desiredDownloadActive;
1719                    [torrent update];
1720                }
1721            }
1722            else
1723            {
1724                if (desiredSeedActive > 0)
1725                {
1726                    tr_inf( "restarting seed torrent in mac queue" );
1727                    [torrent startTransfer];
1728                    if ([torrent isActive])
1729                        --desiredSeedActive;
1730                    [torrent update];
1731                }
1732            }
1733        }
1734    }
1735   
1736    [self updateUI];
1737    [self applyFilter];
1738    [[fWindow toolbar] validateVisibleItems];
1739    [self updateTorrentHistory];
1740}
1741
1742- (NSUInteger) numToStartFromQueue: (BOOL) downloadQueue
1743{
1744    if (![fDefaults boolForKey: downloadQueue ? @"Queue" : @"QueueSeed"])
1745        return 0;
1746   
1747    NSUInteger desired = [fDefaults integerForKey: downloadQueue ? @"QueueDownloadNumber" : @"QueueSeedNumber"];
1748       
1749    for (Torrent * torrent in fTorrents)
1750    {
1751        if (desired == 0)
1752            break;
1753       
1754        if ([torrent isChecking])
1755            --desired;
1756        else if ([torrent isActive] && ![torrent isStalled] && ![torrent isError])
1757        {
1758            if ([torrent allDownloaded] != downloadQueue)
1759                --desired;
1760        }
1761        else;
1762    }
1763   
1764    return desired;
1765}
1766
1767- (void) torrentFinishedDownloading: (NSNotification *) notification
1768{
1769    Torrent * torrent = [notification object];
1770   
1771    if ([[[notification userInfo] objectForKey: @"WasRunning"] boolValue])
1772    {
1773        if (!fSoundPlaying && [fDefaults boolForKey: @"PlayDownloadSound"])
1774        {
1775            NSSound * sound;
1776            if ((sound = [NSSound soundNamed: [fDefaults stringForKey: @"DownloadSound"]]))
1777            {
1778                [sound setDelegate: self];
1779                fSoundPlaying = YES;
1780                [sound play];
1781            }
1782        }
1783       
1784        NSMutableDictionary * clickContext = [NSMutableDictionary dictionaryWithObject: GROWL_DOWNLOAD_COMPLETE forKey: @"Type"];
1785       
1786        NSString * location = [torrent dataLocation];
1787        if (location)
1788            [clickContext setObject: location forKey: @"Location"];
1789       
1790        [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Download Complete", "Growl notification title")
1791                                    description: [torrent name] notificationName: GROWL_DOWNLOAD_COMPLETE
1792                                    iconData: nil priority: 0 isSticky: NO clickContext: clickContext];
1793       
1794        if (![fWindow isMainWindow])
1795            [fBadger incrementCompleted];
1796       
1797        //bounce download stack
1798        [[NSDistributedNotificationCenter defaultCenter] postNotificationName: @"com.apple.DownloadFileFinished"
1799            object: [torrent dataLocation]];
1800       
1801        if ([torrent isActive] && [fDefaults boolForKey: @"QueueSeed"] && [self numToStartFromQueue: NO] == 0)
1802        {
1803            [torrent stopTransfer];
1804            [torrent setWaitToStart: YES];
1805        }
1806    }
1807   
1808    [self updateTorrentsInQueue];
1809}
1810
1811- (void) torrentRestartedDownloading: (NSNotification *) notification
1812{
1813    Torrent * torrent = [notification object];
1814    if ([torrent isActive] && [fDefaults boolForKey: @"Queue"] && [self numToStartFromQueue: YES] == 0)
1815    {
1816        [torrent stopTransfer];
1817        [torrent setWaitToStart: YES];
1818    }
1819   
1820    [self updateTorrentsInQueue];
1821}
1822
1823- (void) torrentFinishedSeeding: (NSNotification *) notification
1824{
1825    Torrent * torrent = [notification object];
1826   
1827    [self updateTorrentsInQueue];
1828   
1829    if ([[fTableView selectedTorrents] containsObject: torrent])
1830    {
1831        [fInfoController updateInfoStats];
1832        [fInfoController updateOptions];
1833    }
1834   
1835    if (!fSoundPlaying && [fDefaults boolForKey: @"PlaySeedingSound"])
1836    {
1837        NSSound * sound;
1838        if ((sound = [NSSound soundNamed: [fDefaults stringForKey: @"SeedingSound"]]))
1839        {
1840            [sound setDelegate: self];
1841            fSoundPlaying = YES;
1842            [sound play];
1843        }
1844    }
1845   
1846    NSMutableDictionary * clickContext = [NSMutableDictionary dictionaryWithObject: GROWL_SEEDING_COMPLETE forKey: @"Type"];
1847   
1848    NSString * location = [torrent dataLocation];
1849    if (location)
1850        [clickContext setObject: location forKey: @"Location"];
1851   
1852    [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Seeding Complete", "Growl notification title")
1853                        description: [torrent name] notificationName: GROWL_SEEDING_COMPLETE
1854                        iconData: nil priority: 0 isSticky: NO clickContext: clickContext];
1855}
1856
1857- (void) updateTorrentHistory
1858{
1859    NSMutableArray * history = [NSMutableArray arrayWithCapacity: [fTorrents count]];
1860   
1861    for (Torrent * torrent in fTorrents)
1862        [history addObject: [torrent history]];
1863   
1864    [history writeToFile: [NSHomeDirectory() stringByAppendingPathComponent: TRANSFER_PLIST] atomically: YES];
1865}
1866
1867- (void) setSort: (id) sender
1868{
1869    NSString * sortType;
1870    switch ([sender tag])
1871    {
1872        case SORT_ORDER_TAG:
1873            sortType = SORT_ORDER;
1874            [fDefaults setBool: NO forKey: @"SortReverse"];
1875            break;
1876        case SORT_DATE_TAG:
1877            sortType = SORT_DATE;
1878            break;
1879        case SORT_NAME_TAG:
1880            sortType = SORT_NAME;
1881            break;
1882        case SORT_PROGRESS_TAG:
1883            sortType = SORT_PROGRESS;
1884            break;
1885        case SORT_STATE_TAG:
1886            sortType = SORT_STATE;
1887            break;
1888        case SORT_TRACKER_TAG:
1889            sortType = SORT_TRACKER;
1890            break;
1891        case SORT_ACTIVITY_TAG:
1892            sortType = SORT_ACTIVITY;
1893            break;
1894        default:
1895            NSAssert1(NO, @"Unknown sort tag received: %d", [sender tag]);
1896            return;
1897    }
1898   
1899    [fDefaults setObject: sortType forKey: @"Sort"];
1900    [self applyFilter]; //better than calling sortTorrents because it will even apply to queue order
1901}
1902
1903- (void) setSortByGroup: (id) sender
1904{
1905    BOOL sortByGroup = ![fDefaults boolForKey: @"SortByGroup"];
1906    [fDefaults setBool: sortByGroup forKey: @"SortByGroup"];
1907   
1908    //expand all groups
1909    if (sortByGroup)
1910        [fTableView removeAllCollapsedGroups];
1911   
1912    [self applyFilter];
1913}
1914
1915- (void) setSortReverse: (id) sender
1916{
1917    const BOOL setReverse = [sender tag] == SORT_DESC_TAG;
1918    if (setReverse != [fDefaults boolForKey: @"SortReverse"])
1919    {
1920        [fDefaults setBool: setReverse forKey: @"SortReverse"];
1921        [self sortTorrents];
1922    }
1923}
1924
1925- (void) sortTorrents
1926{
1927    NSArray * selectedValues = [fTableView selectedValues];
1928   
1929    [self sortTorrentsIgnoreSelected]; //actually sort
1930   
1931    [fTableView selectValues: selectedValues];
1932}
1933
1934- (void) sortTorrentsIgnoreSelected
1935{
1936    NSString * sortType = [fDefaults stringForKey: @"Sort"];
1937   
1938    if (![sortType isEqualToString: SORT_ORDER])
1939    {
1940        const BOOL asc = ![fDefaults boolForKey: @"SortReverse"];
1941       
1942        NSArray * descriptors;
1943        NSSortDescriptor * nameDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"name" ascending: asc
1944                                                selector: @selector(compareFinder:)] autorelease];
1945       
1946        if ([sortType isEqualToString: SORT_STATE])
1947        {
1948            NSSortDescriptor * stateDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"stateSortKey" ascending: !asc] autorelease],
1949                            * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"progress" ascending: !asc] autorelease],
1950                            * ratioDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"ratio" ascending: !asc] autorelease];
1951           
1952            descriptors = [[NSArray alloc] initWithObjects: stateDescriptor, progressDescriptor, ratioDescriptor,
1953                                                                nameDescriptor, nil];
1954        }
1955        else if ([sortType isEqualToString: SORT_PROGRESS])
1956        {
1957            NSSortDescriptor * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"progress" ascending: asc] autorelease],
1958                            * ratioProgressDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"progressStopRatio"
1959                                                            ascending: asc] autorelease],
1960                            * ratioDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"ratio" ascending: asc] autorelease];
1961           
1962            descriptors = [[NSArray alloc] initWithObjects: progressDescriptor, ratioProgressDescriptor, ratioDescriptor,
1963                                                                nameDescriptor, nil];
1964        }
1965        else if ([sortType isEqualToString: SORT_TRACKER])
1966        {
1967            NSSortDescriptor * trackerDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"trackerSortKey" ascending: asc
1968                                                    selector: @selector(localizedCaseInsensitiveCompare:)] autorelease];
1969           
1970            descriptors = [[NSArray alloc] initWithObjects: trackerDescriptor, nameDescriptor, nil];
1971        }
1972        else if ([sortType isEqualToString: SORT_ACTIVITY])
1973        {
1974            NSSortDescriptor * rateDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"totalRate" ascending: !asc] autorelease];
1975            NSSortDescriptor * activityDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"dateActivityOrAdd" ascending: !asc]
1976                                                        autorelease];
1977           
1978            descriptors = [[NSArray alloc] initWithObjects: rateDescriptor, activityDescriptor, nameDescriptor, nil];
1979        }
1980        else if ([sortType isEqualToString: SORT_DATE])
1981        {
1982            NSSortDescriptor * dateDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"dateAdded" ascending: asc] autorelease];
1983           
1984            descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, nameDescriptor, nil];
1985        }
1986        else
1987            descriptors = [[NSArray alloc] initWithObjects: nameDescriptor, nil];
1988       
1989        //actually sort
1990        if ([fDefaults boolForKey: @"SortByGroup"])
1991        {
1992            for (TorrentGroup * group in fDisplayedTorrents)
1993                [[group torrents] sortUsingDescriptors: descriptors];
1994        }
1995        else
1996            [fDisplayedTorrents sortUsingDescriptors: descriptors];
1997       
1998        [descriptors release];
1999    }
2000   
2001    [fTableView reloadData];
2002}
2003
2004- (void) applyFilter
2005{
2006    //get all the torrents in the table
2007    NSMutableArray * previousTorrents;
2008    if ([fDisplayedTorrents count] > 0 && [[fDisplayedTorrents objectAtIndex: 0] isKindOfClass: [TorrentGroup class]])
2009    {
2010        previousTorrents = [NSMutableArray array];
2011       
2012        for (TorrentGroup * group in fDisplayedTorrents)
2013            [previousTorrents addObjectsFromArray: [group torrents]];
2014    }
2015    else
2016        previousTorrents = fDisplayedTorrents;
2017   
2018    NSArray * selectedValues = [fTableView selectedValues];
2019   
2020    NSUInteger active = 0, downloading = 0, seeding = 0, paused = 0;
2021    NSString * filterType = [fDefaults stringForKey: @"Filter"];
2022    BOOL filterActive = NO, filterDownload = NO, filterSeed = NO, filterPause = NO, filterStatus = YES;
2023    if ([filterType isEqualToString: FILTER_ACTIVE])
2024        filterActive = YES;
2025    else if ([filterType isEqualToString: FILTER_DOWNLOAD])
2026        filterDownload = YES;
2027    else if ([filterType isEqualToString: FILTER_SEED])
2028        filterSeed = YES;
2029    else if ([filterType isEqualToString: FILTER_PAUSE])
2030        filterPause = YES;
2031    else
2032        filterStatus = NO;
2033   
2034    const NSInteger groupFilterValue = [fDefaults integerForKey: @"FilterGroup"];
2035    const BOOL filterGroup = groupFilterValue != GROUP_FILTER_ALL_TAG;
2036   
2037    NSString * searchString = [fFilterBar searchString];
2038    if (searchString && [searchString isEqualToString: @""])
2039        searchString = nil;
2040    const BOOL filterTracker = searchString && [[fDefaults stringForKey: @"FilterSearchType"] isEqualToString: FILTER_TYPE_TRACKER];
2041   
2042    NSMutableArray * allTorrents = [NSMutableArray arrayWithCapacity: [fTorrents count]];
2043   
2044    //get count of each type
2045    for (Torrent * torrent in fTorrents)
2046    {
2047        //check status
2048        if ([torrent isActive] && ![torrent isCheckingWaiting])
2049        {
2050            const BOOL isActive = ![torrent isStalled];
2051            if (isActive)
2052                ++active;
2053           
2054            if ([torrent isSeeding])
2055            {
2056                ++seeding;
2057                if (filterStatus && !((filterActive && isActive) || filterSeed))
2058                    continue;
2059            }
2060            else
2061            {
2062                ++downloading;
2063                if (filterStatus && !((filterActive && isActive) || filterDownload))
2064                    continue;
2065            }
2066        }
2067        else
2068        {
2069            ++paused;
2070            if (filterStatus && !filterPause)
2071                continue;
2072        }
2073       
2074        //checkGroup
2075        if (filterGroup)
2076            if ([torrent groupValue] != groupFilterValue)
2077                continue;
2078       
2079        //check text field
2080        if (searchString)
2081        {
2082            if (filterTracker)
2083            {
2084                BOOL removeTextField = YES;
2085                for (NSString * tracker in [torrent allTrackersFlat])
2086                {
2087                    if ([tracker rangeOfString: searchString options:
2088                            (NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch)].location != NSNotFound)
2089                    {
2090                        removeTextField = NO;
2091                        break;
2092                    }
2093                }
2094               
2095                if (removeTextField)
2096                    continue;
2097            }
2098            else
2099            {
2100                if ([[torrent name] rangeOfString: searchString options:
2101                        (NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch)].location == NSNotFound)
2102                    continue;
2103            }
2104        }
2105       
2106        [allTorrents addObject: torrent];
2107    }
2108   
2109    //set button tooltips
2110    if (fFilterBar)
2111        [fFilterBar setCountAll: [fTorrents count] active: active downloading: downloading seeding: seeding paused: paused];
2112   
2113    //clear display cache for not-shown torrents
2114    [previousTorrents removeObjectsInArray: allTorrents];
2115    for (Torrent * torrent in previousTorrents)
2116        [torrent setPreviousFinishedPieces: nil];
2117   
2118    //place torrents into groups
2119    const BOOL groupRows = [fDefaults boolForKey: @"SortByGroup"];
2120    if (groupRows)
2121    {
2122        NSMutableArray * oldTorrentGroups = [NSMutableArray array];
2123        if ([fDisplayedTorrents count] > 0 && [[fDisplayedTorrents objectAtIndex: 0] isKindOfClass: [TorrentGroup class]])
2124            [oldTorrentGroups addObjectsFromArray: fDisplayedTorrents];
2125       
2126        [fDisplayedTorrents removeAllObjects];
2127       
2128        NSSortDescriptor * groupDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"groupOrderValue" ascending: YES] autorelease];
2129        [allTorrents sortUsingDescriptors: [NSArray arrayWithObject: groupDescriptor]];
2130       
2131        TorrentGroup * group = nil;
2132        NSInteger lastGroupValue = -2, currentOldGroupIndex = 0;
2133        for (Torrent * torrent in allTorrents)
2134        {
2135            const NSInteger groupValue = [torrent groupValue];
2136            if (groupValue != lastGroupValue)
2137            {
2138                lastGroupValue = groupValue;
2139               
2140                group = nil;
2141               
2142                //try to see if the group already exists
2143                for (; currentOldGroupIndex < [oldTorrentGroups count]; ++currentOldGroupIndex)
2144                {
2145                    TorrentGroup * currentGroup = [oldTorrentGroups objectAtIndex: currentOldGroupIndex];
2146                    const NSInteger currentGroupValue = [currentGroup groupIndex];
2147                    if (currentGroupValue == groupValue)
2148                    {
2149                        group = currentGroup;
2150                        [[currentGroup torrents] removeAllObjects];
2151                       
2152                        ++currentOldGroupIndex;
2153                    }
2154                   
2155                    if (currentGroupValue >= groupValue)
2156                        break;
2157                }
2158               
2159                if (!group)
2160                    group = [[[TorrentGroup alloc] initWithGroup: groupValue] autorelease];
2161                [fDisplayedTorrents addObject: group];
2162            }
2163           
2164            NSAssert(group != nil, @"No group object to add torrents to");
2165            [[group torrents] addObject: torrent];
2166        }
2167    }
2168    else
2169        [fDisplayedTorrents setArray: allTorrents];
2170   
2171    //actually sort
2172    [self sortTorrentsIgnoreSelected];
2173   
2174    //reset expanded/collapsed rows
2175    if (groupRows)
2176    {
2177        for (TorrentGroup * group in fDisplayedTorrents)
2178        {
2179            if ([fTableView isGroupCollapsed: [group groupIndex]])
2180                [fTableView collapseItem: group];
2181            else
2182                [fTableView expandItem: group];
2183        }
2184    }
2185   
2186    [fTableView selectValues: selectedValues];
2187    [self resetInfo]; //if group is already selected, but the torrents in it change
2188   
2189    [self setBottomCountText: groupRows || filterStatus || filterGroup || searchString];
2190   
2191    [self setWindowSizeToFit];
2192}
2193
2194- (void) switchFilter: (id) sender
2195{
2196    [fFilterBar switchFilter: sender == fNextFilterItem];
2197}
2198
2199- (void) menuNeedsUpdate: (NSMenu *) menu
2200{
2201    if (menu == fGroupsSetMenu || menu == fGroupsSetContextMenu)
2202    {
2203        for (NSInteger i = [menu numberOfItems]-1; i >= 0; i--)
2204            [menu removeItemAtIndex: i];
2205       
2206        NSMenu * groupMenu = [[GroupsController groups] groupMenuWithTarget: self action: @selector(setGroup:) isSmall: NO];
2207       
2208        const NSInteger groupMenuCount = [groupMenu numberOfItems];
2209        for (NSInteger i = 0; i < groupMenuCount; i++)
2210        {
2211            NSMenuItem * item = [[groupMenu itemAtIndex: 0] retain];
2212            [groupMenu removeItemAtIndex: 0];
2213            [menu addItem: item];
2214            [item release];
2215        }
2216    }
2217    else if (menu == fUploadMenu || menu == fDownloadMenu)
2218    {
2219        if ([menu numberOfItems] > 3)
2220            return;
2221       
2222        const NSInteger speedLimitActionValue[] = { 5, 10, 20, 30, 40, 50, 75, 100, 150, 200, 250, 500, 750, 1000, 1500, 2000, -1 };
2223       
2224        NSMenuItem * item;
2225        for (NSInteger i = 0; speedLimitActionValue[i] != -1; i++)
2226        {
2227            item = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: NSLocalizedString(@"%d KB/s",
2228                    "Action menu -> upload/download limit"), speedLimitActionValue[i]] action: @selector(setQuickLimitGlobal:)
2229                    keyEquivalent: @""];
2230            [item setTarget: self];
2231            [item setRepresentedObject: [NSNumber numberWithInt: speedLimitActionValue[i]]];
2232            [menu addItem: item];
2233            [item release];
2234        }
2235    }
2236    else if (menu == fRatioStopMenu)
2237    {
2238        if ([menu numberOfItems] > 3)
2239            return;
2240       
2241        const float ratioLimitActionValue[] = { 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, -1 };
2242       
2243        NSMenuItem * item;
2244        for (NSInteger i = 0; ratioLimitActionValue[i] != -1; i++)
2245        {
2246            item = [[NSMenuItem alloc] initWithTitle: [NSString localizedStringWithFormat: @"%.2f", ratioLimitActionValue[i]]
2247                    action: @selector(setQuickRatioGlobal:) keyEquivalent: @""];
2248            [item setTarget: self];
2249            [item setRepresentedObject: [NSNumber numberWithFloat: ratioLimitActionValue[i]]];
2250            [menu addItem: item];
2251            [item release];
2252        }
2253    }
2254    else;
2255}
2256
2257- (void) setGroup: (id) sender
2258{
2259    for (Torrent * torrent in [fTableView selectedTorrents])
2260    {
2261        [fTableView removeCollapsedGroup: [torrent groupValue]]; //remove old collapsed group
2262       
2263        [torrent setGroupValue: [sender tag]];
2264    }
2265   
2266    [self applyFilter];
2267    [self updateUI];
2268    [self updateTorrentHistory];
2269}
2270
2271- (void) toggleSpeedLimit: (id) sender
2272{
2273    [fDefaults setBool: ![fDefaults boolForKey: @"SpeedLimit"] forKey: @"SpeedLimit"];
2274    [self speedLimitChanged: sender];
2275}
2276
2277- (void) speedLimitChanged: (id) sender
2278{
2279    tr_sessionUseAltSpeed(fLib, [fDefaults boolForKey: @"SpeedLimit"]);
2280    [fStatusBar updateSpeedFieldsToolTips];
2281}
2282
2283//dict has been retained
2284- (void) altSpeedToggledCallbackIsLimited: (NSDictionary *) dict
2285{
2286    const BOOL isLimited = [[dict objectForKey: @"Active"] boolValue];
2287
2288    [fDefaults setBool: isLimited forKey: @"SpeedLimit"];
2289    [fStatusBar updateSpeedFieldsToolTips];
2290   
2291    if (![[dict objectForKey: @"ByUser"] boolValue])
2292        [GrowlApplicationBridge notifyWithTitle: isLimited
2293                ? NSLocalizedString(@"Speed Limit Auto Enabled", "Growl notification title")
2294                : NSLocalizedString(@"Speed Limit Auto Disabled", "Growl notification title")
2295            description: NSLocalizedString(@"Bandwidth settings changed", "Growl notification description")
2296            notificationName: GROWL_AUTO_SPEED_LIMIT iconData: nil priority: 0 isSticky: NO clickContext: nil];
2297   
2298    [dict release];
2299}
2300
2301- (void) setLimitGlobalEnabled: (id) sender
2302{
2303    BOOL upload = [sender menu] == fUploadMenu;
2304    [fDefaults setBool: sender == (upload ? fUploadLimitItem : fDownloadLimitItem) forKey: upload ? @"CheckUpload" : @"CheckDownload"];
2305   
2306    [fPrefsController applySpeedSettings: nil];
2307}
2308
2309- (void) setQuickLimitGlobal: (id) sender
2310{
2311    BOOL upload = [sender menu] == fUploadMenu;
2312    [fDefaults setInteger: [[sender representedObject] intValue] forKey: upload ? @"UploadLimit" : @"DownloadLimit"];
2313    [fDefaults setBool: YES forKey: upload ? @"CheckUpload" : @"CheckDownload"];
2314   
2315    [fPrefsController updateLimitFields];
2316    [fPrefsController applySpeedSettings: nil];
2317}
2318
2319- (void) setRatioGlobalEnabled: (id) sender
2320{
2321    [fDefaults setBool: sender == fCheckRatioItem forKey: @"RatioCheck"];
2322   
2323    [fPrefsController applyRatioSetting: nil];
2324}
2325
2326- (void) setQuickRatioGlobal: (id) sender
2327{
2328    [fDefaults setBool: YES forKey: @"RatioCheck"];
2329    [fDefaults setFloat: [[sender representedObject] floatValue] forKey: @"RatioLimit"];
2330   
2331    [fPrefsController updateRatioStopField];
2332}
2333
2334- (void) sound: (NSSound *) sound didFinishPlaying: (BOOL) finishedPlaying
2335{
2336    fSoundPlaying = NO;
2337}
2338
2339- (void) watcher: (id<UKFileWatcher>) watcher receivedNotification: (NSString *) notification forPath: (NSString *) path
2340{
2341    if ([notification isEqualToString: UKFileWatcherWriteNotification])
2342    {
2343        if (![fDefaults boolForKey: @"AutoImport"] || ![fDefaults stringForKey: @"AutoImportDirectory"])
2344            return;
2345       
2346        if (fAutoImportTimer)
2347        {
2348            if ([fAutoImportTimer isValid])
2349                [fAutoImportTimer invalidate];
2350            [fAutoImportTimer release];
2351            fAutoImportTimer = nil;
2352        }
2353       
2354        //check again in 10 seconds in case torrent file wasn't complete
2355        fAutoImportTimer = [[NSTimer scheduledTimerWithTimeInterval: 10.0 target: self
2356            selector: @selector(checkAutoImportDirectory) userInfo: nil repeats: NO] retain];
2357       
2358        [self checkAutoImportDirectory];
2359    }
2360}
2361
2362- (void) changeAutoImport
2363{
2364    if (fAutoImportTimer)
2365    {
2366        if ([fAutoImportTimer isValid])
2367            [fAutoImportTimer invalidate];
2368        [fAutoImportTimer release];
2369        fAutoImportTimer = nil;
2370    }
2371   
2372    if (fAutoImportedNames)
2373    {
2374        [fAutoImportedNames release];
2375        fAutoImportedNames = nil;
2376    }
2377   
2378    [self checkAutoImportDirectory];
2379}
2380
2381- (void) checkAutoImportDirectory
2382{
2383    NSString * path;
2384    if (![fDefaults boolForKey: @"AutoImport"] || !(path = [fDefaults stringForKey: @"AutoImportDirectory"]))
2385        return;
2386   
2387    path = [path stringByExpandingTildeInPath];
2388   
2389    NSArray * importedNames;
2390    if (!(importedNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: path error: NULL]))
2391        return;
2392   
2393    //only check files that have not been checked yet
2394    NSMutableArray * newNames = [importedNames mutableCopy];
2395   
2396    if (fAutoImportedNames)
2397        [newNames removeObjectsInArray: fAutoImportedNames];
2398    else
2399        fAutoImportedNames = [[NSMutableArray alloc] init];
2400    [fAutoImportedNames setArray: importedNames];
2401   
2402    for (NSString * file in newNames)
2403    {
2404        if ([file hasPrefix: @"."])
2405            continue;
2406       
2407        NSString * fullFile = [path stringByAppendingPathComponent: file];
2408       
2409        if (!([[[NSWorkspace sharedWorkspace] typeOfFile: fullFile error: NULL] isEqualToString: @"org.bittorrent.torrent"]
2410                || [[fullFile pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame))
2411            continue;
2412       
2413        tr_ctor * ctor = tr_ctorNew(fLib);
2414        tr_ctorSetMetainfoFromFile(ctor, [fullFile UTF8String]);
2415       
2416        switch (tr_torrentParse(ctor, NULL))
2417        {
2418            case TR_PARSE_OK:
2419                [self openFiles: [NSArray arrayWithObject: fullFile] addType: ADD_AUTO forcePath: nil];
2420               
2421                [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Torrent File Auto Added", "Growl notification title")
2422                    description: file notificationName: GROWL_AUTO_ADD iconData: nil priority: 0 isSticky: NO
2423                    clickContext: nil];
2424                break;
2425           
2426            case TR_PARSE_ERR:
2427                [fAutoImportedNames removeObject: file];
2428                break;
2429           
2430            case TR_PARSE_DUPLICATE: //let's ignore this (but silence a warning)
2431                break;
2432        }
2433       
2434        tr_ctorFree(ctor);
2435    }
2436   
2437    [newNames release];
2438}
2439
2440- (void) beginCreateFile: (NSNotification *) notification
2441{
2442    if (![fDefaults boolForKey: @"AutoImport"])
2443        return;
2444   
2445    NSString * location = [notification object],
2446            * path = [fDefaults stringForKey: @"AutoImportDirectory"];
2447   
2448    if (location && path && [[[location stringByDeletingLastPathComponent] stringByExpandingTildeInPath]
2449                                    isEqualToString: [path stringByExpandingTildeInPath]])
2450        [fAutoImportedNames addObject: [location lastPathComponent]];
2451}
2452
2453- (NSInteger) outlineView: (NSOutlineView *) outlineView numberOfChildrenOfItem: (id) item
2454{
2455    if (item)
2456        return [[item torrents] count];
2457    else
2458        return [fDisplayedTorrents count];
2459}
2460
2461- (id) outlineView: (NSOutlineView *) outlineView child: (NSInteger) index ofItem: (id) item
2462{
2463    if (item)
2464        return [[item torrents] objectAtIndex: index];
2465    else
2466        return [fDisplayedTorrents objectAtIndex: index];
2467}
2468
2469- (BOOL) outlineView: (NSOutlineView *) outlineView isItemExpandable: (id) item
2470{
2471    return ![item isKindOfClass: [Torrent class]];
2472}
2473
2474- (id) outlineView: (NSOutlineView *) outlineView objectValueForTableColumn: (NSTableColumn *) tableColumn byItem: (id) item
2475{
2476    if ([item isKindOfClass: [Torrent class]]) {
2477        if (tableColumn)
2478            return nil;
2479        return [item hashString];
2480    }
2481    else
2482    {
2483        NSString * ident = [tableColumn identifier];
2484        if ([ident isEqualToString: @"Group"])
2485        {
2486            NSInteger group = [item groupIndex];
2487            return group != -1 ? [[GroupsController groups] nameForIndex: group]
2488                                : NSLocalizedString(@"No Group", "Group table row");
2489        }
2490        else if ([ident isEqualToString: @"Color"])
2491        {
2492            NSInteger group = [item groupIndex];
2493            return [[GroupsController groups] imageForIndex: group];
2494        }
2495        else if ([ident isEqualToString: @"DL Image"])
2496            return [NSImage imageNamed: @"DownArrowGroupTemplate.png"];
2497        else if ([ident isEqualToString: @"UL Image"])
2498            return [NSImage imageNamed: [fDefaults boolForKey: @"DisplayGroupRowRatio"]
2499                                        ? @"YingYangGroupTemplate.png" : @"UpArrowGroupTemplate.png"];
2500        else
2501        {
2502            TorrentGroup * group = (TorrentGroup *)item;
2503           
2504            if ([fDefaults boolForKey: @"DisplayGroupRowRatio"])
2505                return [NSString stringForRatio: [group ratio]];
2506            else
2507            {
2508                CGFloat rate = [ident isEqualToString: @"UL"] ? [group uploadRate] : [group downloadRate];
2509                return [NSString stringForSpeed: rate];
2510            }
2511        }
2512    }
2513}
2514
2515- (BOOL) outlineView: (NSOutlineView *) outlineView writeItems: (NSArray *) items toPasteboard: (NSPasteboard *) pasteboard
2516{
2517    //only allow reordering of rows if sorting by order
2518    if ([fDefaults boolForKey: @"SortByGroup"] || [[fDefaults stringForKey: @"Sort"] isEqualToString: SORT_ORDER])
2519    {
2520        NSMutableIndexSet * indexSet = [NSMutableIndexSet indexSet];
2521        for (id torrent in items)
2522        {
2523            if (![torrent isKindOfClass: [Torrent class]])
2524                return NO;
2525           
2526            [indexSet addIndex: [fTableView rowForItem: torrent]];
2527        }
2528       
2529        [pasteboard declareTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE] owner: self];
2530        [pasteboard setData: [NSKeyedArchiver archivedDataWithRootObject: indexSet] forType: TORRENT_TABLE_VIEW_DATA_TYPE];
2531        return YES;
2532    }
2533    return NO;
2534}
2535
2536- (NSDragOperation) outlineView: (NSOutlineView *) outlineView validateDrop: (id < NSDraggingInfo >) info proposedItem: (id) item
2537    proposedChildIndex: (NSInteger) index
2538{
2539    NSPasteboard * pasteboard = [info draggingPasteboard];
2540    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2541    {
2542        if ([fDefaults boolForKey: @"SortByGroup"])
2543        {
2544            if (!item)
2545                return NSDragOperationNone;
2546           
2547            if ([[fDefaults stringForKey: @"Sort"] isEqualToString: SORT_ORDER])
2548            {
2549                if ([item isKindOfClass: [Torrent class]])
2550                {
2551                    TorrentGroup * group = [fTableView parentForItem: item];
2552                    index = [[group torrents] indexOfObject: item] + 1;
2553                    item = group;
2554                }
2555            }
2556            else
2557            {
2558                if ([item isKindOfClass: [Torrent class]])
2559                    item = [fTableView parentForItem: item];
2560                index = NSOutlineViewDropOnItemIndex;
2561            }
2562        }
2563        else
2564        {
2565            if (item)
2566            {
2567                index = [fTableView rowForItem: item] + 1;
2568                item = nil;
2569            }
2570        }
2571       
2572        [fTableView setDropItem: item dropChildIndex: index];
2573        return NSDragOperationGeneric;
2574    }
2575   
2576    return NSDragOperationNone;
2577}
2578
2579- (BOOL) outlineView: (NSOutlineView *) outlineView acceptDrop: (id < NSDraggingInfo >) info item: (id) item
2580    childIndex: (NSInteger) newRow
2581{
2582    NSPasteboard * pasteboard = [info draggingPasteboard];
2583    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2584    {
2585        //remember selected rows
2586        NSArray * selectedValues = [fTableView selectedValues];
2587   
2588        NSIndexSet * indexes = [NSKeyedUnarchiver unarchiveObjectWithData: [pasteboard dataForType: TORRENT_TABLE_VIEW_DATA_TYPE]];
2589       
2590        //get the torrents to move
2591        NSMutableArray * movingTorrents = [NSMutableArray arrayWithCapacity: [indexes count]];
2592        for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
2593            [movingTorrents addObject: [fTableView itemAtRow: i]];
2594       
2595        //reset groups
2596        if (item)
2597        {
2598            //change groups
2599            NSInteger groupValue = [item groupIndex];
2600            for (Torrent * torrent in movingTorrents)
2601            {
2602                //have to reset objects here to avoid weird crash
2603                [[[fTableView parentForItem: torrent] torrents] removeObject: torrent];
2604                [[item torrents] addObject: torrent];
2605               
2606                [torrent setGroupValue: groupValue];
2607            }
2608            //part 2 of avoiding weird crash
2609            [fTableView reloadItem: nil reloadChildren: YES];
2610        }
2611       
2612        //reorder queue order
2613        if (newRow != NSOutlineViewDropOnItemIndex)
2614        {
2615            //find torrent to place under
2616            NSArray * groupTorrents = item ? [item torrents] : fDisplayedTorrents;
2617            Torrent * topTorrent = nil;
2618            for (NSInteger i = newRow-1; i >= 0; i--)
2619            {
2620                Torrent * tempTorrent = [groupTorrents objectAtIndex: i];
2621                if (![movingTorrents containsObject: tempTorrent])
2622                {
2623                    topTorrent = tempTorrent;
2624                    break;
2625                }
2626            }
2627           
2628            //remove objects to reinsert
2629            [fTorrents removeObjectsInArray: movingTorrents];
2630           
2631            //insert objects at new location
2632            NSUInteger insertIndex = topTorrent ? [fTorrents indexOfObject: topTorrent] + 1 : 0;
2633            NSIndexSet * insertIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(insertIndex, [movingTorrents count])];
2634            [fTorrents insertObjects: movingTorrents atIndexes: insertIndexes];
2635        }
2636       
2637        [self applyFilter];
2638        [fTableView selectValues: selectedValues];
2639    }
2640   
2641    return YES;
2642}
2643
2644- (void) torrentTableViewSelectionDidChange: (NSNotification *) notification
2645{
2646    [self resetInfo];
2647    [[fWindow toolbar] validateVisibleItems];
2648}
2649
2650- (NSDragOperation) draggingEntered: (id <NSDraggingInfo>) info
2651{
2652    NSPasteboard * pasteboard = [info draggingPasteboard];
2653    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2654    {
2655        //check if any torrent files can be added
2656        BOOL torrent = NO;
2657        NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
2658        for (NSString * file in files)
2659        {
2660            if ([[[NSWorkspace sharedWorkspace] typeOfFile: file error: NULL] isEqualToString: @"org.bittorrent.torrent"]
2661                    || [[file pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame)
2662            {
2663                torrent = YES;
2664                tr_ctor * ctor = tr_ctorNew(fLib);
2665                tr_ctorSetMetainfoFromFile(ctor, [file UTF8String]);
2666                if (tr_torrentParse(ctor, NULL) == TR_PARSE_OK)
2667                {
2668                    if (!fOverlayWindow)
2669                        fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2670                    [fOverlayWindow setTorrents: files];
2671                   
2672                    return NSDragOperationCopy;
2673                }
2674                tr_ctorFree(ctor);
2675            }
2676        }
2677       
2678        //create a torrent file if a single file
2679        if (!torrent && [files count] == 1)
2680        {
2681            if (!fOverlayWindow)
2682                fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2683            [fOverlayWindow setFile: [[files objectAtIndex: 0] lastPathComponent]];
2684           
2685            return NSDragOperationCopy;
2686        }
2687    }
2688    else if ([[pasteboard types] containsObject: NSURLPboardType])
2689    {
2690        if (!fOverlayWindow)
2691            fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2692        [fOverlayWindow setURL: [[NSURL URLFromPasteboard: pasteboard] relativeString]];
2693       
2694        return NSDragOperationCopy;
2695    }
2696    else;
2697   
2698    return NSDragOperationNone;
2699}
2700
2701- (void) draggingExited: (id <NSDraggingInfo>) info
2702{
2703    if (fOverlayWindow)
2704        [fOverlayWindow fadeOut];
2705}
2706
2707- (BOOL) performDragOperation: (id <NSDraggingInfo>) info
2708{
2709    if (fOverlayWindow)
2710        [fOverlayWindow fadeOut];
2711   
2712    NSPasteboard * pasteboard = [info draggingPasteboard];
2713    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2714    {
2715        BOOL torrent = NO, accept = YES;
2716       
2717        //create an array of files that can be opened
2718        NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
2719        NSMutableArray * filesToOpen = [NSMutableArray arrayWithCapacity: [files count]];
2720        for (NSString * file in files)
2721        {
2722            if ([[[NSWorkspace sharedWorkspace] typeOfFile: file error: NULL] isEqualToString: @"org.bittorrent.torrent"]
2723                    || [[file pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame)
2724            {
2725                torrent = YES;
2726                tr_ctor * ctor = tr_ctorNew(fLib);
2727                tr_ctorSetMetainfoFromFile(ctor, [file UTF8String]);
2728                if (tr_torrentParse(ctor, NULL) == TR_PARSE_OK)
2729                    [filesToOpen addObject: file];
2730                tr_ctorFree(ctor);
2731            }
2732        }
2733       
2734        if ([filesToOpen count] > 0)
2735            [self application: NSApp openFiles: filesToOpen];
2736        else
2737        {
2738            if (!torrent && [files count] == 1)
2739                [CreatorWindowController createTorrentFile: fLib forFile: [files objectAtIndex: 0]];
2740            else
2741                accept = NO;
2742        }
2743       
2744        return accept;
2745    }
2746    else if ([[pasteboard types] containsObject: NSURLPboardType])
2747    {
2748        NSURL * url;
2749        if ((url = [NSURL URLFromPasteboard: pasteboard]))
2750        {
2751            [self openURL: [url absoluteString]];
2752            return YES;
2753        }
2754    }
2755    else;
2756   
2757    return NO;
2758}
2759
2760- (void) toggleSmallView: (id) sender
2761{
2762    BOOL makeSmall = ![fDefaults boolForKey: @"SmallView"];
2763    [fDefaults setBool: makeSmall forKey: @"SmallView"];
2764   
2765    [fTableView setRowHeight: makeSmall ? ROW_HEIGHT_SMALL : ROW_HEIGHT_REGULAR];
2766   
2767    [fTableView noteHeightOfRowsWithIndexesChanged: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTableView numberOfRows])]];
2768   
2769    //window min height
2770    NSSize contentMinSize = [fWindow contentMinSize],
2771            contentSize = [[fWindow contentView] frame].size;
2772    contentMinSize.height = contentSize.height - [[fTableView enclosingScrollView] frame].size.height
2773                            + [fTableView rowHeight] + [fTableView intercellSpacing].height;
2774    [fWindow setContentMinSize: contentMinSize];
2775   
2776    //resize for larger min height if not set to auto size
2777    if (![fDefaults boolForKey: @"AutoSize"])
2778    {
2779        if (!makeSmall && contentSize.height < contentMinSize.height)
2780        {
2781            NSRect frame = [fWindow frame];
2782            CGFloat heightChange = contentMinSize.height - contentSize.height;
2783            frame.size.height += heightChange;
2784            frame.origin.y -= heightChange;
2785           
2786            [fWindow setFrame: frame display: YES];
2787        }
2788    }
2789    else
2790        [self setWindowSizeToFit];
2791}
2792
2793- (void) togglePiecesBar: (id) sender
2794{
2795    [fDefaults setBool: ![fDefaults boolForKey: @"PiecesBar"] forKey: @"PiecesBar"];
2796    [fTableView togglePiecesBar];
2797}
2798
2799- (void) toggleAvailabilityBar: (id) sender
2800{
2801    [fDefaults setBool: ![fDefaults boolForKey: @"DisplayProgressBarAvailable"] forKey: @"DisplayProgressBarAvailable"];
2802    [fTableView display];
2803}
2804
2805- (void) toggleStatusString: (id) sender
2806{
2807    if ([fDefaults boolForKey: @"SmallView"])
2808        [fDefaults setBool: ![fDefaults boolForKey: @"DisplaySmallStatusRegular"] forKey: @"DisplaySmallStatusRegular"];
2809    else
2810        [fDefaults setBool: ![fDefaults boolForKey: @"DisplayStatusProgressSelected"] forKey: @"DisplayStatusProgressSelected"];
2811   
2812    [fTableView reloadData];
2813}
2814
2815- (NSRect) windowFrameByAddingHeight: (CGFloat) height checkLimits: (BOOL) check
2816{
2817    NSScrollView * scrollView = [fTableView enclosingScrollView];
2818   
2819    //convert pixels to points
2820    NSRect windowFrame = [fWindow frame];
2821    NSSize windowSize = [scrollView convertSize: windowFrame.size fromView: nil];
2822    windowSize.height += height;
2823   
2824    if (check)
2825    {
2826        NSSize minSize = [scrollView convertSize: [fWindow minSize] fromView: nil];
2827       
2828        if (windowSize.height < minSize.height)
2829            windowSize.height = minSize.height;
2830        else
2831        {
2832            NSSize maxSize = [scrollView convertSize: [[fWindow screen] visibleFrame].size fromView: nil];
2833            if (!fStatusBar)
2834                maxSize.height -= STATUS_BAR_HEIGHT;
2835            if (!fFilterBar)
2836                maxSize.height -= FILTER_BAR_HEIGHT;
2837            if (windowSize.height > maxSize.height)
2838                windowSize.height = maxSize.height;
2839        }
2840    }
2841
2842    //convert points to pixels
2843    windowSize = [scrollView convertSize: windowSize toView: nil];
2844
2845    windowFrame.origin.y -= (windowSize.height - windowFrame.size.height);
2846    windowFrame.size.height = windowSize.height;
2847    return windowFrame;
2848}
2849
2850- (void) toggleStatusBar: (id) sender
2851{
2852    const BOOL show = fStatusBar == nil;
2853    [self showStatusBar: show animate: YES];
2854    [fDefaults setBool: show forKey: @"StatusBar"];
2855}
2856
2857//doesn't save shown state
2858- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
2859{
2860    const BOOL prevShown = fStatusBar != nil;
2861    if (show == prevShown)
2862        return;
2863   
2864    if (show)
2865    {
2866        fStatusBar = [[StatusBarController alloc] initWithLib: fLib];
2867       
2868        NSView * contentView = [fWindow contentView];
2869        const NSSize windowSize = [contentView convertSize: [fWindow frame].size fromView: nil];
2870       
2871        NSRect statusBarFrame = [[fStatusBar view] frame];
2872        statusBarFrame.size.width = windowSize.width;
2873        [[fStatusBar view] setFrame: statusBarFrame];
2874       
2875        [contentView addSubview: [fStatusBar view]];
2876        [[fStatusBar view] setFrameOrigin: NSMakePoint(0.0, NSMaxY([contentView frame]))];
2877    }
2878   
2879    NSRect frame;
2880    CGFloat heightChange = [[fStatusBar view] frame].size.height;
2881    if (!show)
2882        heightChange *= -1;
2883   
2884    //allow bar to show even if not enough room
2885    if (show && ![fDefaults boolForKey: @"AutoSize"])
2886    {
2887        frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2888        CGFloat change = [[fWindow screen] visibleFrame].size.height - frame.size.height;
2889        if (change < 0.0)
2890        {
2891            frame = [fWindow frame];
2892            frame.size.height += change;
2893            frame.origin.y -= change;
2894            [fWindow setFrame: frame display: NO animate: NO];
2895        }
2896    }
2897   
2898    [self updateUI];
2899   
2900    NSScrollView * scrollView = [fTableView enclosingScrollView];
2901   
2902    //set views to not autoresize
2903    const NSUInteger statsMask = [[fStatusBar view] autoresizingMask];
2904    [[fStatusBar view] setAutoresizingMask: NSViewNotSizable];
2905    NSUInteger filterMask;
2906    if (fFilterBar)
2907    {
2908        filterMask = [[fFilterBar view] autoresizingMask];
2909        [[fFilterBar view] setAutoresizingMask: NSViewNotSizable];
2910    }
2911    const NSUInteger scrollMask = [scrollView autoresizingMask];
2912    [scrollView setAutoresizingMask: NSViewNotSizable];
2913   
2914    frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2915    [fWindow setFrame: frame display: YES animate: animate];
2916   
2917    //re-enable autoresize
2918    [[fStatusBar view] setAutoresizingMask: statsMask];
2919    if (fFilterBar)
2920        [[fFilterBar view] setAutoresizingMask: filterMask];
2921    [scrollView setAutoresizingMask: scrollMask];
2922   
2923    //change min size
2924    NSSize minSize = [fWindow contentMinSize];
2925    minSize.height += heightChange;
2926    [fWindow setContentMinSize: minSize];
2927   
2928    if (!show)
2929    {
2930        [[fStatusBar view] removeFromSuperview];
2931        [fStatusBar release];
2932        fStatusBar = nil;
2933    }
2934}
2935
2936- (void) toggleFilterBar: (id) sender
2937{
2938    const BOOL show = fFilterBar == nil;
2939   
2940    [self showFilterBar: show animate: YES];
2941    [fDefaults setBool: show forKey: @"FilterBar"];
2942    [[fWindow toolbar] validateVisibleItems];
2943   
2944    //disable filtering when hiding
2945    if (!show)
2946    {
2947        [[NSUserDefaults standardUserDefaults] setObject: FILTER_NONE forKey: @"Filter"];
2948        [[NSUserDefaults standardUserDefaults] setInteger: GROUP_FILTER_ALL_TAG forKey: @"FilterGroup"];
2949        [[NSUserDefaults standardUserDefaults] removeObjectForKey: @"FilterSearchString"];
2950    }
2951   
2952    [self applyFilter]; //do even if showing to ensure tooltips are updated
2953}
2954
2955//doesn't save shown state
2956- (void) showFilterBar: (BOOL) show animate: (BOOL) animate
2957{
2958    const BOOL prevShown = fFilterBar != nil;
2959    if (show == prevShown)
2960        return;
2961   
2962    if (show)
2963    {
2964        fFilterBar = [[FilterBarController alloc] init];
2965       
2966        NSView * contentView = [fWindow contentView];
2967        const NSSize windowSize = [contentView convertSize: [fWindow frame].size fromView: nil];
2968       
2969        NSRect filterBarFrame = [[fFilterBar view] frame];
2970        filterBarFrame.size.width = windowSize.width;
2971        [[fFilterBar view] setFrame: filterBarFrame];
2972       
2973        if (fStatusBar)
2974            [contentView addSubview: [fFilterBar view] positioned: NSWindowBelow relativeTo: [fStatusBar view]];
2975        else
2976            [contentView addSubview: [fFilterBar view]];
2977        const CGFloat originY = fStatusBar ? NSMinY([[fStatusBar view] frame]) : NSMaxY([contentView frame]);
2978        [[fFilterBar view] setFrameOrigin: NSMakePoint(0.0, originY)];
2979    }
2980    else
2981        [fWindow makeFirstResponder: fTableView];
2982   
2983    CGFloat heightChange = NSHeight([[fFilterBar view] frame]);
2984    if (!show)
2985        heightChange *= -1;
2986   
2987    //allow bar to show even if not enough room
2988    if (show && ![fDefaults boolForKey: @"AutoSize"])
2989    {
2990        NSRect frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2991        const CGFloat change = NSHeight([[fWindow screen] visibleFrame]) - NSHeight(frame);
2992        if (change < 0.0)
2993        {
2994            frame = [fWindow frame];
2995            frame.size.height += change;
2996            frame.origin.y -= change;
2997            [fWindow setFrame: frame display: NO animate: NO];
2998        }
2999    }
3000   
3001    NSScrollView * scrollView = [fTableView enclosingScrollView];
3002
3003    //set views to not autoresize
3004    const NSUInteger filterMask = [[fFilterBar view] autoresizingMask];
3005    const NSUInteger scrollMask = [scrollView autoresizingMask];
3006    [[fFilterBar view] setAutoresizingMask: NSViewNotSizable];
3007    [scrollView setAutoresizingMask: NSViewNotSizable];
3008   
3009    const NSRect frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
3010    [fWindow setFrame: frame display: YES animate: animate];
3011   
3012    //re-enable autoresize
3013    [[fFilterBar view] setAutoresizingMask: filterMask];
3014    [scrollView setAutoresizingMask: scrollMask];
3015   
3016    //change min size
3017    NSSize minSize = [fWindow contentMinSize];
3018    minSize.height += heightChange;
3019    [fWindow setContentMinSize: minSize];
3020   
3021    if (!show)
3022    {
3023        [[fFilterBar view] removeFromSuperview];
3024        [fFilterBar release];
3025        fFilterBar = nil;
3026    }
3027}
3028
3029- (void) focusFilterField
3030{
3031    if (!fFilterBar)
3032        [self toggleFilterBar: self];
3033    [fFilterBar focusSearchField];
3034}
3035
3036#warning change from id to QLPreviewPanel
3037- (BOOL) acceptsPreviewPanelControl: (id) panel
3038{
3039    return !fQuitting;
3040}
3041
3042- (void) beginPreviewPanelControl: (id) panel
3043{
3044    fPreviewPanel = [panel retain];
3045    [fPreviewPanel setDelegate: self];
3046    [fPreviewPanel setDataSource: self];
3047}
3048
3049- (void) endPreviewPanelControl: (id) panel
3050{
3051    [fPreviewPanel release];
3052    fPreviewPanel = nil;
3053}
3054
3055- (NSArray *) quickLookableTorrents
3056{
3057    NSArray * selectedTorrents = [fTableView selectedTorrents];
3058    NSMutableArray * qlArray = [NSMutableArray arrayWithCapacity: [selectedTorrents count]];
3059   
3060    for (Torrent * torrent in selectedTorrents)
3061        if (([torrent isFolder] || [torrent isComplete]) && [torrent dataLocation])
3062            [qlArray addObject: torrent];
3063   
3064    return qlArray;
3065}
3066
3067- (NSInteger) numberOfPreviewItemsInPreviewPanel: (id) panel
3068{
3069    if ([fInfoController canQuickLook])
3070        return [[fInfoController quickLookURLs] count];
3071    else
3072        return [[self quickLookableTorrents] count];
3073}
3074
3075- (id /*<QLPreviewItem>*/) previewPanel: (id) panel previewItemAtIndex: (NSInteger) index
3076{
3077    if ([fInfoController canQuickLook])
3078        return [[fInfoController quickLookURLs] objectAtIndex: index];
3079    else
3080        return [[self quickLookableTorrents] objectAtIndex: index];
3081}
3082
3083- (BOOL) previewPanel: (id) panel handleEvent: (NSEvent *) event
3084{
3085    /*if ([event type] == NSKeyDown)
3086    {
3087        [super keyDown: event];
3088        return YES;
3089    }*/
3090   
3091    return NO;
3092}
3093
3094- (NSRect) previewPanel: (id) panel sourceFrameOnScreenForPreviewItem: (id /*<QLPreviewItem>*/) item
3095{
3096    if ([fInfoController canQuickLook])
3097        return [fInfoController quickLookSourceFrameForPreviewItem: item];
3098    else
3099    {
3100        if (![fWindow isVisible])
3101            return NSZeroRect;
3102       
3103        const NSInteger row = [fTableView rowForItem: item];
3104        if (row == -1)
3105            return NSZeroRect;
3106       
3107        NSRect frame = [fTableView iconRectForRow: row];
3108       
3109        if (!NSIntersectsRect([fTableView visibleRect], frame))
3110            return NSZeroRect;
3111       
3112        frame.origin = [fTableView convertPoint: frame.origin toView: nil];
3113        frame.origin = [fWindow convertBaseToScreen: frame.origin];
3114        frame.origin.y -= frame.size.height;
3115        return frame;
3116    }
3117}
3118
3119- (ButtonToolbarItem *) standardToolbarButtonWithIdentifier: (NSString *) ident
3120{
3121    ButtonToolbarItem * item = [[ButtonToolbarItem alloc] initWithItemIdentifier: ident];
3122   
3123    NSButton * button = [[NSButton alloc] initWithFrame: NSZeroRect];
3124    [button setBezelStyle: NSTexturedRoundedBezelStyle];
3125    [button setStringValue: @""];
3126   
3127    [item setView: button];
3128    [button release];
3129   
3130    const NSSize buttonSize = NSMakeSize(36.0, 25.0);
3131    [item setMinSize: buttonSize];
3132    [item setMaxSize: buttonSize];
3133   
3134    return [item autorelease];
3135}
3136
3137- (NSToolbarItem *) toolbar: (NSToolbar *) toolbar itemForItemIdentifier: (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
3138{
3139    if ([ident isEqualToString: TOOLBAR_CREATE])
3140    {
3141        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3142       
3143        [item setLabel: NSLocalizedString(@"Create", "Create toolbar item -> label")];
3144        [item setPaletteLabel: NSLocalizedString(@"Create Torrent File", "Create toolbar item -> palette label")];
3145        [item setToolTip: NSLocalizedString(@"Create torrent file", "Create toolbar item -> tooltip")];
3146        [item setImage: [NSImage imageNamed: @"ToolbarCreateTemplate.png"]];
3147        [item setTarget: self];
3148        [item setAction: @selector(createFile:)];
3149        [item setAutovalidates: NO];
3150       
3151        return item;
3152    }
3153    else if ([ident isEqualToString: TOOLBAR_OPEN_FILE])
3154    {
3155        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3156       
3157        [item setLabel: NSLocalizedString(@"Open", "Open toolbar item -> label")];
3158        [item setPaletteLabel: NSLocalizedString(@"Open Torrent Files", "Open toolbar item -> palette label")];
3159        [item setToolTip: NSLocalizedString(@"Open torrent files", "Open toolbar item -> tooltip")];
3160        [item setImage: [NSImage imageNamed: @"ToolbarOpenTemplate.png"]];
3161        [item setTarget: self];
3162        [item setAction: @selector(openShowSheet:)];
3163        [item setAutovalidates: NO];
3164       
3165        return item;
3166    }
3167    else if ([ident isEqualToString: TOOLBAR_OPEN_WEB])
3168    {
3169        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3170       
3171        [item setLabel: NSLocalizedString(@"Open Address", "Open address toolbar item -> label")];
3172        [item setPaletteLabel: NSLocalizedString(@"Open Torrent Address", "Open address toolbar item -> palette label")];
3173        [item setToolTip: NSLocalizedString(@"Open torrent web address", "Open address toolbar item -> tooltip")];
3174        [item setImage: [NSImage imageNamed: @"ToolbarOpenWebTemplate.png"]];
3175        [item setTarget: self];
3176        [item setAction: @selector(openURLShowSheet:)];
3177        [item setAutovalidates: NO];
3178       
3179        return item;
3180    }
3181    else if ([ident isEqualToString: TOOLBAR_REMOVE])
3182    {
3183        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3184       
3185        [item setLabel: NSLocalizedString(@"Remove", "Remove toolbar item -> label")];
3186        [item setPaletteLabel: NSLocalizedString(@"Remove Selected", "Remove toolbar item -> palette label")];
3187        [item setToolTip: NSLocalizedString(@"Remove selected transfers", "Remove toolbar item -> tooltip")];
3188        [item setImage: [NSImage imageNamed: @"ToolbarRemoveTemplate.png"]];
3189        [item setTarget: self];
3190        [item setAction: @selector(removeNoDelete:)];
3191        [item setVisibilityPriority: NSToolbarItemVisibilityPriorityHigh];
3192       
3193        return item;
3194    }
3195    else if ([ident isEqualToString: TOOLBAR_INFO])
3196    {
3197        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3198        [[(NSButton *)[item view] cell] setShowsStateBy: NSContentsCellMask]; //blue when enabled
3199       
3200        [item setLabel: NSLocalizedString(@"Inspector", "Inspector toolbar item -> label")];
3201        [item setPaletteLabel: NSLocalizedString(@"Toggle Inspector", "Inspector toolbar item -> palette label")];
3202        [item setToolTip: NSLocalizedString(@"Toggle the torrent inspector", "Inspector toolbar item -> tooltip")];
3203        [item setImage: [NSImage imageNamed: @"ToolbarInfoTemplate.png"]];
3204        [item setTarget: self];
3205        [item setAction: @selector(showInfo:)];
3206       
3207        return item;
3208    }
3209    else if ([ident isEqualToString: TOOLBAR_PAUSE_RESUME_ALL])
3210    {
3211        GroupToolbarItem * groupItem = [[GroupToolbarItem alloc] initWithItemIdentifier: ident];
3212       
3213        NSSegmentedControl * segmentedControl = [[NSSegmentedControl alloc] initWithFrame: NSZeroRect];
3214        [segmentedControl setCell: [[[ToolbarSegmentedCell alloc] init] autorelease]];
3215        [groupItem setView: segmentedControl];
3216        NSSegmentedCell * segmentedCell = (NSSegmentedCell *)[segmentedControl cell];
3217       
3218        [segmentedControl setSegmentCount: 2];
3219        [segmentedCell setTrackingMode: NSSegmentSwitchTrackingMomentary];
3220       
3221        const NSSize groupSize = NSMakeSize(72.0, 25.0);
3222        [groupItem setMinSize: groupSize];
3223        [groupItem setMaxSize: groupSize];
3224       
3225        [groupItem setLabel: NSLocalizedString(@"Apply All", "All toolbar item -> label")];
3226        [groupItem setPaletteLabel: NSLocalizedString(@"Pause / Resume All", "All toolbar item -> palette label")];
3227        [groupItem setTarget: self];
3228        [groupItem setAction: @selector(allToolbarClicked:)];
3229       
3230        [groupItem setIdentifiers: [NSArray arrayWithObjects: TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL, nil]];
3231       
3232        [segmentedCell setTag: TOOLBAR_PAUSE_TAG forSegment: TOOLBAR_PAUSE_TAG];
3233        [segmentedControl setImage: [NSImage imageNamed: @"ToolbarPauseAllTemplate.png"] forSegment: TOOLBAR_PAUSE_TAG];
3234        [segmentedCell setToolTip: NSLocalizedString(@"Pause all transfers",
3235                                    "All toolbar item -> tooltip") forSegment: TOOLBAR_PAUSE_TAG];
3236       
3237        [segmentedCell setTag: TOOLBAR_RESUME_TAG forSegment: TOOLBAR_RESUME_TAG];
3238        [segmentedControl setImage: [NSImage imageNamed: @"ToolbarResumeAllTemplate.png"] forSegment: TOOLBAR_RESUME_TAG];
3239        [segmentedCell setToolTip: NSLocalizedString(@"Resume all transfers",
3240                                    "All toolbar item -> tooltip") forSegment: TOOLBAR_RESUME_TAG];
3241       
3242        [groupItem createMenu: [NSArray arrayWithObjects: NSLocalizedString(@"Pause All", "All toolbar item -> label"),
3243                                        NSLocalizedString(@"Resume All", "All toolbar item -> label"), nil]];
3244       
3245        [segmentedControl release];
3246       
3247        [groupItem setVisibilityPriority: NSToolbarItemVisibilityPriorityHigh];
3248       
3249        return [groupItem autorelease];
3250    }
3251    else if ([ident isEqualToString: TOOLBAR_PAUSE_RESUME_SELECTED])
3252    {
3253        GroupToolbarItem * groupItem = [[GroupToolbarItem alloc] initWithItemIdentifier: ident];
3254       
3255        NSSegmentedControl * segmentedControl = [[NSSegmentedControl alloc] initWithFrame: NSZeroRect];
3256        [segmentedControl setCell: [[[ToolbarSegmentedCell alloc] init] autorelease]];
3257        [groupItem setView: segmentedControl];
3258        NSSegmentedCell * segmentedCell = (NSSegmentedCell *)[segmentedControl cell];
3259       
3260        [segmentedControl setSegmentCount: 2];
3261        [segmentedCell setTrackingMode: NSSegmentSwitchTrackingMomentary];
3262       
3263        const NSSize groupSize = NSMakeSize(72.0, 25.0);
3264        [groupItem setMinSize: groupSize];
3265        [groupItem setMaxSize: groupSize];
3266       
3267        [groupItem setLabel: NSLocalizedString(@"Apply Selected", "Selected toolbar item -> label")];
3268        [groupItem setPaletteLabel: NSLocalizedString(@"Pause / Resume Selected", "Selected toolbar item -> palette label")];
3269        [groupItem setTarget: self];
3270        [groupItem setAction: @selector(selectedToolbarClicked:)];
3271       
3272        [groupItem setIdentifiers: [NSArray arrayWithObjects: TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED, nil]];
3273       
3274        [segmentedCell setTag: TOOLBAR_PAUSE_TAG forSegment: TOOLBAR_PAUSE_TAG];
3275        [segmentedControl setImage: [NSImage imageNamed: @"ToolbarPauseSelectedTemplate.png"] forSegment: TOOLBAR_PAUSE_TAG];
3276        [segmentedCell setToolTip: NSLocalizedString(@"Pause selected transfers",
3277                                    "Selected toolbar item -> tooltip") forSegment: TOOLBAR_PAUSE_TAG];
3278       
3279        [segmentedCell setTag: TOOLBAR_RESUME_TAG forSegment: TOOLBAR_RESUME_TAG];
3280        [segmentedControl setImage: [NSImage imageNamed: @"ToolbarResumeSelectedTemplate.png"] forSegment: TOOLBAR_RESUME_TAG];
3281        [segmentedCell setToolTip: NSLocalizedString(@"Resume selected transfers",
3282                                    "Selected toolbar item -> tooltip") forSegment: TOOLBAR_RESUME_TAG];
3283       
3284        [groupItem createMenu: [NSArray arrayWithObjects: NSLocalizedString(@"Pause Selected", "Selected toolbar item -> label"),
3285                                        NSLocalizedString(@"Resume Selected", "Selected toolbar item -> label"), nil]];
3286       
3287        [segmentedControl release];
3288       
3289        [groupItem setVisibilityPriority: NSToolbarItemVisibilityPriorityHigh];
3290       
3291        return [groupItem autorelease];
3292    }
3293    else if ([ident isEqualToString: TOOLBAR_FILTER])
3294    {
3295        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3296        [[(NSButton *)[item view] cell] setShowsStateBy: NSContentsCellMask]; //blue when enabled
3297       
3298        [item setLabel: NSLocalizedString(@"Filter", "Filter toolbar item -> label")];
3299        [item setPaletteLabel: NSLocalizedString(@"Toggle Filter", "Filter toolbar item -> palette label")];
3300        [item setToolTip: NSLocalizedString(@"Toggle the filter bar", "Filter toolbar item -> tooltip")];
3301        [item setImage: [NSImage imageNamed: @"ToolbarFilterTemplate.png"]];
3302        [item setTarget: self];
3303        [item setAction: @selector(toggleFilterBar:)];
3304       
3305        return item;
3306    }
3307    else if ([ident isEqualToString: TOOLBAR_QUICKLOOK])
3308    {
3309        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3310        [[(NSButton *)[item view] cell] setShowsStateBy: NSContentsCellMask]; //blue when enabled
3311       
3312        [item setLabel: NSLocalizedString(@"Quick Look", "QuickLook toolbar item -> label")];
3313        [item setPaletteLabel: NSLocalizedString(@"Quick Look", "QuickLook toolbar item -> palette label")];
3314        [item setToolTip: NSLocalizedString(@"Quick Look", "QuickLook toolbar item -> tooltip")];
3315        [item setImage: [NSImage imageNamed: NSImageNameQuickLookTemplate]];
3316        [item setTarget: self];
3317        [item setAction: @selector(toggleQuickLook:)];
3318        [item setVisibilityPriority: NSToolbarItemVisibilityPriorityLow];
3319       
3320        return item;
3321    }
3322    else
3323        return nil;
3324}
3325
3326- (void) allToolbarClicked: (id) sender
3327{
3328    NSInteger tagValue = [sender isKindOfClass: [NSSegmentedControl class]]
3329                    ? [(NSSegmentedCell *)[sender cell] tagForSegment: [sender selectedSegment]] : [sender tag];
3330    switch (tagValue)
3331    {
3332        case TOOLBAR_PAUSE_TAG:
3333            [self stopAllTorrents: sender];
3334            break;
3335        case TOOLBAR_RESUME_TAG:
3336            [self resumeAllTorrents: sender];
3337            break;
3338    }
3339}
3340
3341- (void) selectedToolbarClicked: (id) sender
3342{
3343    NSInteger tagValue = [sender isKindOfClass: [NSSegmentedControl class]]
3344                    ? [(NSSegmentedCell *)[sender cell] tagForSegment: [sender selectedSegment]] : [sender tag];
3345    switch (tagValue)
3346    {
3347        case TOOLBAR_PAUSE_TAG:
3348            [self stopSelectedTorrents: sender];
3349            break;
3350        case TOOLBAR_RESUME_TAG:
3351            [self resumeSelectedTorrents: sender];
3352            break;
3353    }
3354}
3355
3356- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar
3357{
3358    return [NSArray arrayWithObjects:
3359            TOOLBAR_CREATE, TOOLBAR_OPEN_FILE, TOOLBAR_OPEN_WEB, TOOLBAR_REMOVE,
3360            TOOLBAR_PAUSE_RESUME_SELECTED, TOOLBAR_PAUSE_RESUME_ALL,
3361            TOOLBAR_QUICKLOOK, TOOLBAR_FILTER, TOOLBAR_INFO,
3362            NSToolbarSeparatorItemIdentifier,
3363            NSToolbarSpaceItemIdentifier,
3364            NSToolbarFlexibleSpaceItemIdentifier,
3365            NSToolbarCustomizeToolbarItemIdentifier, nil];
3366}
3367
3368- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
3369{
3370    return [NSArray arrayWithObjects:
3371            TOOLBAR_CREATE, TOOLBAR_OPEN_FILE, TOOLBAR_REMOVE, NSToolbarSpaceItemIdentifier,
3372            TOOLBAR_PAUSE_RESUME_ALL, NSToolbarFlexibleSpaceItemIdentifier,
3373            TOOLBAR_QUICKLOOK, TOOLBAR_FILTER, TOOLBAR_INFO, nil];
3374}
3375
3376- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
3377{
3378    NSString * ident = [toolbarItem itemIdentifier];
3379   
3380    //enable remove item
3381    if ([ident isEqualToString: TOOLBAR_REMOVE])
3382        return [fTableView numberOfSelectedRows] > 0;
3383
3384    //enable pause all item
3385    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
3386    {
3387        for (Torrent * torrent in fTorrents)
3388            if ([torrent isActive] || [torrent waitingToStart])
3389                return YES;
3390        return NO;
3391    }
3392
3393    //enable resume all item
3394    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
3395    {
3396        for (Torrent * torrent in fTorrents)
3397            if (![torrent isActive] && ![torrent waitingToStart])
3398                return YES;
3399        return NO;
3400    }
3401
3402    //enable pause item
3403    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
3404    {
3405        for (Torrent * torrent in [fTableView selectedTorrents])
3406            if ([torrent isActive] || [torrent waitingToStart])
3407                return YES;
3408        return NO;
3409    }
3410   
3411    //enable resume item
3412    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
3413    {
3414        for (Torrent * torrent in [fTableView selectedTorrents])
3415            if (![torrent isActive] && ![torrent waitingToStart])
3416                return YES;
3417        return NO;
3418    }
3419   
3420    //set info item
3421    if ([ident isEqualToString: TOOLBAR_INFO])
3422    {
3423        [(NSButton *)[toolbarItem view] setState: [[fInfoController window] isVisible]];
3424        return YES;
3425    }
3426   
3427    //set filter item
3428    if ([ident isEqualToString: TOOLBAR_FILTER])
3429    {
3430        [(NSButton *)[toolbarItem view] setState: fFilterBar != nil];
3431        return YES;
3432    }
3433   
3434    //set quick look item
3435    if ([ident isEqualToString: TOOLBAR_QUICKLOOK])
3436    {
3437        [(NSButton *)[toolbarItem view] setState: [NSApp isOnSnowLeopardOrBetter] && [QLPreviewPanelSL sharedPreviewPanelExists]
3438                                                    && [[QLPreviewPanelSL sharedPreviewPanel] isVisible]];
3439        return [NSApp isOnSnowLeopardOrBetter];
3440    }
3441
3442    return YES;
3443}
3444
3445- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
3446{
3447    SEL action = [menuItem action];
3448   
3449    if (action == @selector(toggleSpeedLimit:))
3450    {
3451        [menuItem setState: [fDefaults boolForKey: @"SpeedLimit"] ? NSOnState : NSOffState];
3452        return YES;
3453    }
3454   
3455    //only enable some items if it is in a context menu or the window is useable
3456    BOOL canUseTable = [fWindow isKeyWindow] || [[menuItem menu] supermenu] != [NSApp mainMenu];
3457
3458    //enable open items
3459    if (action == @selector(openShowSheet:) || action == @selector(openURLShowSheet:))
3460        return [fWindow attachedSheet] == nil;
3461   
3462    //enable sort options
3463    if (action == @selector(setSort:))
3464    {
3465        NSString * sortType;
3466        switch ([menuItem tag])
3467        {
3468            case SORT_ORDER_TAG:
3469                sortType = SORT_ORDER;
3470                break;
3471            case SORT_DATE_TAG:
3472                sortType = SORT_DATE;
3473                break;
3474            case SORT_NAME_TAG:
3475                sortType = SORT_NAME;
3476                break;
3477            case SORT_PROGRESS_TAG:
3478                sortType = SORT_PROGRESS;
3479                break;
3480            case SORT_STATE_TAG:
3481                sortType = SORT_STATE;
3482                break;
3483            case SORT_TRACKER_TAG:
3484                sortType = SORT_TRACKER;
3485                break;
3486            case SORT_ACTIVITY_TAG:
3487                sortType = SORT_ACTIVITY;
3488                break;
3489            default:
3490                NSAssert1(NO, @"Unknown sort tag received: %d", [menuItem tag]);
3491        }
3492       
3493        [menuItem setState: [sortType isEqualToString: [fDefaults stringForKey: @"Sort"]] ? NSOnState : NSOffState];
3494        return [fWindow isVisible];
3495    }
3496   
3497    if (action == @selector(setGroup:))
3498    {
3499        BOOL checked = NO;
3500       
3501        NSInteger index = [menuItem tag];
3502        for (Torrent * torrent in [fTableView selectedTorrents])
3503            if (index == [torrent groupValue])
3504            {
3505                checked = YES;
3506                break;
3507            }
3508       
3509        [menuItem setState: checked ? NSOnState : NSOffState];
3510        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3511    }
3512   
3513    if (action == @selector(toggleSmallView:))
3514    {
3515        [menuItem setState: [fDefaults boolForKey: @"SmallView"] ? NSOnState : NSOffState];
3516        return [fWindow isVisible];
3517    }
3518   
3519    if (action == @selector(togglePiecesBar:))
3520    {
3521        [menuItem setState: [fDefaults boolForKey: @"PiecesBar"] ? NSOnState : NSOffState];
3522        return [fWindow isVisible];
3523    }
3524   
3525    if (action == @selector(toggleStatusString:))
3526    {
3527        if ([fDefaults boolForKey: @"SmallView"])
3528        {
3529            [menuItem setTitle: NSLocalizedString(@"Remaining Time", "Action menu -> status string toggle")];
3530            [menuItem setState: ![fDefaults boolForKey: @"DisplaySmallStatusRegular"] ? NSOnState : NSOffState];
3531        }
3532        else
3533        {
3534            [menuItem setTitle: NSLocalizedString(@"Status of Selected Files", "Action menu -> status string toggle")];
3535            [menuItem setState: [fDefaults boolForKey: @"DisplayStatusProgressSelected"] ? NSOnState : NSOffState];
3536        }
3537       
3538        return [fWindow isVisible];
3539    }
3540   
3541    if (action == @selector(toggleAvailabilityBar:))
3542    {
3543        [menuItem setState: [fDefaults boolForKey: @"DisplayProgressBarAvailable"] ? NSOnState : NSOffState];
3544        return [fWindow isVisible];
3545    }
3546   
3547    if (action == @selector(setLimitGlobalEnabled:))
3548    {
3549        BOOL upload = [menuItem menu] == fUploadMenu;
3550        BOOL limit = menuItem == (upload ? fUploadLimitItem : fDownloadLimitItem);
3551        if (limit)
3552            [menuItem setTitle: [NSString stringWithFormat: NSLocalizedString(@"Limit (%d KB/s)",
3553                                    "Action menu -> upload/download limit"),
3554                                    [fDefaults integerForKey: upload ? @"UploadLimit" : @"DownloadLimit"]]];
3555       
3556        [menuItem setState: [fDefaults boolForKey: upload ? @"CheckUpload" : @"CheckDownload"] ? limit : !limit];
3557        return YES;
3558    }
3559   
3560    if (action == @selector(setRatioGlobalEnabled:))
3561    {
3562        BOOL check = menuItem == fCheckRatioItem;
3563        if (check)
3564            [menuItem setTitle: [NSString localizedStringWithFormat: NSLocalizedString(@"Stop at Ratio (%.2f)",
3565                                    "Action menu -> ratio stop"), [fDefaults floatForKey: @"RatioLimit"]]];
3566       
3567        [menuItem setState: [fDefaults boolForKey: @"RatioCheck"] ? check : !check];
3568        return YES;
3569    }
3570
3571    //enable show info
3572    if (action == @selector(showInfo:))
3573    {
3574        NSString * title = [[fInfoController window] isVisible] ? NSLocalizedString(@"Hide Inspector", "View menu -> Inspector")
3575                            : NSLocalizedString(@"Show Inspector", "View menu -> Inspector");
3576        [menuItem setTitle: title];
3577
3578        return YES;
3579    }
3580   
3581    //enable prev/next inspector tab
3582    if (action == @selector(setInfoTab:))
3583        return [[fInfoController window] isVisible];
3584   
3585    //enable toggle status bar
3586    if (action == @selector(toggleStatusBar:))
3587    {
3588        NSString * title = !fStatusBar ? NSLocalizedString(@"Show Status Bar", "View menu -> Status Bar")
3589                            : NSLocalizedString(@"Hide Status Bar", "View menu -> Status Bar");
3590        [menuItem setTitle: title];
3591
3592        return [fWindow isVisible];
3593    }
3594   
3595    //enable toggle filter bar
3596    if (action == @selector(toggleFilterBar:))
3597    {
3598        NSString * title = !fFilterBar ? NSLocalizedString(@"Show Filter Bar", "View menu -> Filter Bar")
3599                            : NSLocalizedString(@"Hide Filter Bar", "View menu -> Filter Bar");
3600        [menuItem setTitle: title];
3601
3602        return [fWindow isVisible];
3603    }
3604   
3605    //enable prev/next filter button
3606    if (action == @selector(switchFilter:))
3607        return [fWindow isVisible] && fFilterBar;
3608   
3609    //enable reveal in finder
3610    if (action == @selector(revealFile:))
3611        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3612
3613    //enable remove items
3614    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:))
3615    {
3616        BOOL warning = NO;
3617       
3618        for (Torrent * torrent in [fTableView selectedTorrents])
3619        {
3620            if ([torrent isActive])
3621            {
3622                if ([fDefaults boolForKey: @"CheckRemoveDownloading"] ? ![torrent isSeeding] : YES)
3623                {
3624                    warning = YES;
3625                    break;
3626                }
3627            }
3628        }
3629   
3630        //append or remove ellipsis when needed
3631        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
3632        if (warning && [fDefaults boolForKey: @"CheckRemove"])
3633        {
3634            if (![title hasSuffix: ellipsis])
3635                [menuItem setTitle: [title stringByAppendingEllipsis]];
3636        }
3637        else
3638        {
3639            if ([title hasSuffix: ellipsis])
3640                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
3641        }
3642       
3643        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3644    }
3645   
3646    //remove all completed transfers item
3647    if (action == @selector(clearCompleted:))
3648    {
3649        //append or remove ellipsis when needed
3650        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
3651        if ([fDefaults boolForKey: @"WarningRemoveCompleted"])
3652        {
3653            if (![title hasSuffix: ellipsis])
3654                [menuItem setTitle: [title stringByAppendingEllipsis]];
3655        }
3656        else
3657        {
3658            if ([title hasSuffix: ellipsis])
3659                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
3660        }
3661       
3662        for (Torrent * torrent in fTorrents)
3663            if ([torrent isFinishedSeeding])
3664                return YES;
3665        return NO;
3666    }
3667
3668    //enable pause all item
3669    if (action == @selector(stopAllTorrents:))
3670    {
3671        for (Torrent * torrent in fTorrents)
3672            if ([torrent isActive] || [torrent waitingToStart])
3673                return YES;
3674        return NO;
3675    }
3676   
3677    //enable resume all item
3678    if (action == @selector(resumeAllTorrents:))
3679    {
3680        for (Torrent * torrent in fTorrents)
3681            if (![torrent isActive] && ![torrent waitingToStart] && ![torrent isFinishedSeeding])
3682                return YES;
3683        return NO;
3684    }
3685   
3686    //enable resume all waiting item
3687    if (action == @selector(resumeWaitingTorrents:))
3688    {
3689        if (![fDefaults boolForKey: @"Queue"] && ![fDefaults boolForKey: @"QueueSeed"])
3690            return NO;
3691   
3692        for (Torrent * torrent in fTorrents)
3693            if (![torrent isActive] && [torrent waitingToStart])
3694                return YES;
3695        return NO;
3696    }
3697   
3698    //enable resume selected waiting item
3699    if (action == @selector(resumeSelectedTorrentsNoWait:))
3700    {
3701        if (!canUseTable)
3702            return NO;
3703       
3704        for (Torrent * torrent in [fTableView selectedTorrents])
3705            if (![torrent isActive])
3706                return YES;
3707        return NO;
3708    }
3709
3710    //enable pause item
3711    if (action == @selector(stopSelectedTorrents:))
3712    {
3713        if (!canUseTable)
3714            return NO;
3715   
3716        for (Torrent * torrent in [fTableView selectedTorrents])
3717            if ([torrent isActive] || [torrent waitingToStart])
3718                return YES;
3719        return NO;
3720    }
3721   
3722    //enable resume item
3723    if (action == @selector(resumeSelectedTorrents:))
3724    {
3725        if (!canUseTable)
3726            return NO;
3727   
3728        for (Torrent * torrent in [fTableView selectedTorrents])
3729            if (![torrent isActive] && ![torrent waitingToStart])
3730                return YES;
3731        return NO;
3732    }
3733   
3734    //enable manual announce item
3735    if (action == @selector(announceSelectedTorrents:))
3736    {
3737        if (!canUseTable)
3738            return NO;
3739       
3740        for (Torrent * torrent in [fTableView selectedTorrents])
3741            if ([torrent canManualAnnounce])
3742                return YES;
3743        return NO;
3744    }
3745   
3746    //enable reset cache item
3747    if (action == @selector(verifySelectedTorrents:))
3748    {
3749        if (!canUseTable)
3750            return NO;
3751           
3752        for (Torrent * torrent in [fTableView selectedTorrents])
3753            if (![torrent isMagnet])
3754                return YES;
3755        return NO;
3756    }
3757   
3758    //enable move torrent file item
3759    if (action == @selector(moveDataFilesSelected:))
3760        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3761   
3762    //enable copy torrent file item
3763    if (action == @selector(copyTorrentFiles:))
3764    {
3765        if (!canUseTable)
3766            return NO;
3767       
3768        for (Torrent * torrent in [fTableView selectedTorrents])
3769            if (![torrent isMagnet])
3770                return YES;
3771        return NO;
3772    }
3773   
3774    //enable copy torrent file item
3775    if (action == @selector(copyMagnetLinks:))
3776        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3777   
3778    //enable reverse sort item
3779    if (action == @selector(setSortReverse:))
3780    {
3781        const BOOL isReverse = [menuItem tag] == SORT_DESC_TAG;
3782        [menuItem setState: (isReverse == [fDefaults boolForKey: @"SortReverse"]) ? NSOnState : NSOffState];
3783        return ![[fDefaults stringForKey: @"Sort"] isEqualToString: SORT_ORDER];
3784    }
3785   
3786    //enable group sort item
3787    if (action == @selector(setSortByGroup:))
3788    {
3789        [menuItem setState: [fDefaults boolForKey: @"SortByGroup"] ? NSOnState : NSOffState];
3790        return YES;
3791    }
3792   
3793    if (action == @selector(toggleQuickLook:))
3794    {
3795        const BOOL visible = [NSApp isOnSnowLeopardOrBetter] && [QLPreviewPanelSL sharedPreviewPanelExists]
3796                                && [[QLPreviewPanelSL sharedPreviewPanel] isVisible];
3797        //text consistent with Finder
3798        NSString * title = !visible ? NSLocalizedString(@"Quick Look", "View menu -> Quick Look")
3799                                    : NSLocalizedString(@"Close Quick Look", "View menu -> Quick Look");
3800        [menuItem setTitle: title];
3801       
3802        return [NSApp isOnSnowLeopardOrBetter];
3803    }
3804   
3805    return YES;
3806}
3807
3808- (void) sleepCallback: (natural_t) messageType argument: (void *) messageArgument
3809{
3810    switch (messageType)
3811    {
3812        case kIOMessageSystemWillSleep:
3813            //if there are any running transfers, wait 15 seconds for them to stop
3814            for (Torrent * torrent in fTorrents)
3815                if ([torrent isActive])
3816                {
3817                    //stop all transfers (since some are active) before going to sleep and remember to resume when we wake up
3818                    for (Torrent * torrent in fTorrents)
3819                        [torrent sleep];
3820                    sleep(15);
3821                    break;
3822                }
3823
3824            IOAllowPowerChange(fRootPort, (long) messageArgument);
3825            break;
3826
3827        case kIOMessageCanSystemSleep:
3828            if ([fDefaults boolForKey: @"SleepPrevent"])
3829            {
3830                //prevent idle sleep unless no torrents are active
3831                for (Torrent * torrent in fTorrents)
3832                    if ([torrent isActive] && ![torrent isStalled] && ![torrent isError])
3833                    {
3834                        IOCancelPowerChange(fRootPort, (long) messageArgument);
3835                        return;
3836                    }
3837            }
3838           
3839            IOAllowPowerChange(fRootPort, (long) messageArgument);
3840            break;
3841
3842        case kIOMessageSystemHasPoweredOn:
3843            //resume sleeping transfers after we wake up
3844            for (Torrent * torrent in fTorrents)
3845                [torrent wakeUp];
3846            break;
3847    }
3848}
3849
3850- (NSMenu *) applicationDockMenu: (NSApplication *) sender
3851{
3852    NSInteger seeding = 0, downloading = 0;
3853    for (Torrent * torrent in fTorrents)
3854    {
3855        if ([torrent isSeeding])
3856            seeding++;
3857        else if ([torrent isActive])
3858            downloading++;
3859        else;
3860    }
3861   
3862    NSMenuItem * seedingItem = [fDockMenu itemWithTag: DOCK_SEEDING_TAG],
3863            * downloadingItem = [fDockMenu itemWithTag: DOCK_DOWNLOADING_TAG];
3864   
3865    BOOL hasSeparator = seedingItem || downloadingItem;
3866   
3867    if (seeding > 0)
3868    {
3869        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Seeding", "Dock item - Seeding"), seeding];
3870        if (!seedingItem)
3871        {
3872            seedingItem = [[[NSMenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""] autorelease];
3873            [seedingItem setTag: DOCK_SEEDING_TAG];
3874            [fDockMenu insertItem: seedingItem atIndex: 0];
3875        }
3876        else
3877            [seedingItem setTitle: title];
3878    }
3879    else
3880    {
3881        if (seedingItem)
3882            [fDockMenu removeItem: seedingItem];
3883    }
3884   
3885    if (downloading > 0)
3886    {
3887        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Downloading", "Dock item - Downloading"), downloading];
3888        if (!downloadingItem)
3889        {
3890            downloadingItem = [[[NSMenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""] autorelease];
3891            [downloadingItem setTag: DOCK_DOWNLOADING_TAG];
3892            [fDockMenu insertItem: downloadingItem atIndex: seeding > 0 ? 1 : 0];
3893        }
3894        else
3895            [downloadingItem setTitle: title];
3896    }
3897    else
3898    {
3899        if (downloadingItem)
3900            [fDockMenu removeItem: downloadingItem];
3901    }
3902   
3903    if (seeding > 0 || downloading > 0)
3904    {
3905        if (!hasSeparator)
3906            [fDockMenu insertItem: [NSMenuItem separatorItem] atIndex: (seeding > 0 && downloading > 0) ? 2 : 1];
3907    }
3908    else
3909    {
3910        if (hasSeparator)
3911            [fDockMenu removeItemAtIndex: 0];
3912    }
3913   
3914    return fDockMenu;
3915}
3916
3917- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
3918{
3919    //if auto size is enabled, the current frame shouldn't need to change
3920    NSRect frame = [fDefaults boolForKey: @"AutoSize"] ? [window frame] : [self sizedWindowFrame];
3921   
3922    frame.size.width = [fDefaults boolForKey: @"SmallView"] ? [fWindow minSize].width : WINDOW_REGULAR_WIDTH;
3923    return frame;
3924}
3925
3926- (void) setWindowSizeToFit
3927{
3928    if ([fDefaults boolForKey: @"AutoSize"])
3929    {
3930        NSScrollView * scrollView = [fTableView enclosingScrollView];
3931       
3932        [scrollView setHasVerticalScroller: NO];
3933        [fWindow setFrame: [self sizedWindowFrame] display: YES animate: YES];
3934        [scrollView setHasVerticalScroller: YES];
3935       
3936        //hack to ensure scrollbars don't disappear after resizing
3937        if (![NSApp isOnSnowLeopardOrBetter])
3938        {
3939            [scrollView setAutohidesScrollers: NO];
3940            [scrollView setAutohidesScrollers: YES];
3941        }
3942    }
3943}
3944
3945- (NSRect) sizedWindowFrame
3946{
3947    NSInteger groups = ([fDisplayedTorrents count] > 0 && ![[fDisplayedTorrents objectAtIndex: 0] isKindOfClass: [Torrent class]])
3948                    ? [fDisplayedTorrents count] : 0;
3949   
3950    CGFloat heightChange = (GROUP_SEPARATOR_HEIGHT + [fTableView intercellSpacing].height) * groups
3951                        + ([fTableView rowHeight] + [fTableView intercellSpacing].height) * ([fTableView numberOfRows] - groups)
3952                        - [[fTableView enclosingScrollView] frame].size.height;
3953   
3954    return [self windowFrameByAddingHeight: heightChange checkLimits: YES];
3955}
3956
3957- (void) updateForExpandCollape
3958{
3959    [self setWindowSizeToFit];
3960    [self setBottomCountText: YES];
3961}
3962
3963- (void) showMainWindow: (id) sender
3964{
3965    [fWindow makeKeyAndOrderFront: nil];
3966}
3967
3968- (void) windowDidBecomeMain: (NSNotification *) notification
3969{
3970    [fBadger clearCompleted];
3971    [self updateUI];
3972}
3973
3974- (NSSize) windowWillResize: (NSWindow *) sender toSize: (NSSize) proposedFrameSize
3975{
3976    //only resize horizontally if autosize is enabled
3977    if ([fDefaults boolForKey: @"AutoSize"])
3978        proposedFrameSize.height = [fWindow frame].size.height;
3979    return proposedFrameSize;
3980}
3981
3982- (void) applicationWillUnhide: (NSNotification *) notification
3983{
3984    [self updateUI];
3985}
3986
3987- (void) toggleQuickLook: (id) sender
3988{
3989    if (![NSApp isOnSnowLeopardOrBetter])
3990        return;
3991   
3992    if ([[QLPreviewPanelSL sharedPreviewPanel] isVisible])
3993        [[QLPreviewPanelSL sharedPreviewPanel] orderOut: nil];
3994    else
3995        [[QLPreviewPanelSL sharedPreviewPanel] makeKeyAndOrderFront: nil];
3996}
3997
3998- (void) linkHomepage: (id) sender
3999{
4000    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
4001}
4002
4003- (void) linkForums: (id) sender
4004{
4005    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
4006}
4007
4008- (void) linkTrac: (id) sender
4009{
4010    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: TRAC_URL]];
4011}
4012
4013- (void) linkDonate: (id) sender
4014{
4015    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: DONATE_URL]];
4016}
4017
4018- (void) updaterWillRelaunchApplication: (SUUpdater *) updater
4019{
4020    fQuitRequested = YES;
4021}
4022
4023- (NSDictionary *) registrationDictionaryForGrowl
4024{
4025    NSArray * notifications = [NSArray arrayWithObjects: GROWL_DOWNLOAD_COMPLETE, GROWL_SEEDING_COMPLETE,
4026                                                            GROWL_AUTO_ADD, GROWL_AUTO_SPEED_LIMIT, nil];
4027    return [NSDictionary dictionaryWithObjectsAndKeys: notifications, GROWL_NOTIFICATIONS_ALL,
4028                                notifications, GROWL_NOTIFICATIONS_DEFAULT, nil];
4029}
4030
4031- (void) growlNotificationWasClicked: (id) clickContext
4032{
4033    if (!clickContext || ![clickContext isKindOfClass: [NSDictionary class]])
4034        return;
4035   
4036    NSString * type = [clickContext objectForKey: @"Type"], * location;
4037    if (([type isEqualToString: GROWL_DOWNLOAD_COMPLETE] || [type isEqualToString: GROWL_SEEDING_COMPLETE])
4038            && (location = [clickContext objectForKey: @"Location"]))
4039    {
4040        if ([NSApp isOnSnowLeopardOrBetter])
4041        {
4042            NSURL * file = [NSURL fileURLWithPath: location];
4043            [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: [NSArray arrayWithObject: file]];
4044        }
4045        else
4046            [[NSWorkspace sharedWorkspace] selectFile: location inFileViewerRootedAtPath: nil];
4047    }
4048}
4049
4050- (void) rpcCallback: (tr_rpc_callback_type) type forTorrentStruct: (struct tr_torrent *) torrentStruct
4051{
4052    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
4053   
4054    //get the torrent
4055    Torrent * torrent = nil;
4056    if (torrentStruct != NULL && (type != TR_RPC_TORRENT_ADDED && type != TR_RPC_SESSION_CHANGED && type != TR_RPC_SESSION_CLOSE))
4057    {
4058        for (torrent in fTorrents)
4059            if (torrentStruct == [torrent torrentStruct])
4060            {
4061                [torrent retain];
4062                break;
4063            }
4064       
4065        if (!torrent)
4066        {
4067            [pool drain];
4068           
4069            NSLog(@"No torrent found matching the given torrent struct from the RPC callback!");
4070            return;
4071        }
4072    }
4073   
4074    switch (type)
4075    {
4076        case TR_RPC_TORRENT_ADDED:
4077            [self performSelectorOnMainThread: @selector(rpcAddTorrentStruct:) withObject:
4078                [[NSValue valueWithPointer: torrentStruct] retain] waitUntilDone: NO];
4079            break;
4080       
4081        case TR_RPC_TORRENT_STARTED:
4082        case TR_RPC_TORRENT_STOPPED:
4083            [self performSelectorOnMainThread: @selector(rpcStartedStoppedTorrent:) withObject: torrent waitUntilDone: NO];
4084            break;
4085       
4086        case TR_RPC_TORRENT_REMOVING:
4087            [self performSelectorOnMainThread: @selector(rpcRemoveTorrent:) withObject: torrent waitUntilDone: NO];
4088            break;
4089       
4090        case TR_RPC_TORRENT_TRASHING:
4091            [self performSelectorOnMainThread: @selector(rpcRemoveTorrentDeleteData:) withObject: torrent waitUntilDone: NO];
4092            break;
4093       
4094        case TR_RPC_TORRENT_CHANGED:
4095            [self performSelectorOnMainThread: @selector(rpcChangedTorrent:) withObject: torrent waitUntilDone: NO];
4096            break;
4097       
4098        case TR_RPC_TORRENT_MOVED:
4099            [self performSelectorOnMainThread: @selector(rpcMovedTorrent:) withObject: torrent waitUntilDone: NO];
4100            break;
4101       
4102        case TR_RPC_SESSION_CHANGED:
4103            [fPrefsController performSelectorOnMainThread: @selector(rpcUpdatePrefs) withObject: nil waitUntilDone: NO];
4104            break;
4105       
4106        case TR_RPC_SESSION_CLOSE:
4107            fQuitRequested = YES;
4108            [NSApp performSelectorOnMainThread: @selector(terminate:) withObject: self waitUntilDone: NO];
4109            break;
4110       
4111        default:
4112            NSAssert1(NO, @"Unknown RPC command received: %d", type);
4113            [torrent release];
4114    }
4115   
4116    [pool drain];
4117}
4118
4119- (void) rpcAddTorrentStruct: (NSValue *) torrentStructPtr
4120{
4121    tr_torrent * torrentStruct = (tr_torrent *)[torrentStructPtr pointerValue];
4122    [torrentStructPtr release];
4123   
4124    NSString * location = nil;
4125    if (tr_torrentGetDownloadDir(torrentStruct) != NULL)
4126        location = [NSString stringWithUTF8String: tr_torrentGetDownloadDir(torrentStruct)];
4127   
4128    Torrent * torrent = [[Torrent alloc] initWithTorrentStruct: torrentStruct location: location lib: fLib];
4129   
4130    [torrent update];
4131    [fTorrents addObject: torrent];
4132    [torrent release];
4133   
4134    [self updateTorrentsInQueue];
4135}
4136
4137- (void) rpcRemoveTorrent: (Torrent *) torrent
4138{
4139    [self confirmRemoveTorrents: [[NSArray arrayWithObject: torrent] retain] deleteData: NO];
4140    [torrent release];
4141}
4142
4143- (void) rpcRemoveTorrentDeleteData: (Torrent *) torrent
4144{
4145    [self confirmRemoveTorrents: [[NSArray arrayWithObject: torrent] retain] deleteData: YES];
4146    [torrent release];
4147}
4148
4149- (void) rpcStartedStoppedTorrent: (Torrent *) torrent
4150{
4151    [torrent update];
4152    [torrent release];
4153   
4154    [self updateUI];
4155    [self applyFilter];
4156    [self updateTorrentHistory];
4157}
4158
4159- (void) rpcChangedTorrent: (Torrent *) torrent
4160{
4161    [torrent update];
4162   
4163    if ([[fTableView selectedTorrents] containsObject: torrent])
4164    {
4165        [fInfoController updateInfoStats]; //this will reload the file table
4166        [fInfoController updateOptions];
4167    }
4168   
4169    [torrent release];
4170}
4171
4172- (void) rpcMovedTorrent: (Torrent *) torrent
4173{
4174    [torrent update];
4175    [torrent updateTimeMachineExclude];
4176   
4177    if ([[fTableView selectedTorrents] containsObject: torrent])
4178        [fInfoController updateInfoStats];
4179   
4180    [torrent release];
4181}
4182
4183@end
Note: See TracBrowser for help on using the repository browser.