source: trunk/macosx/Controller.m @ 13138

Last change on this file since 13138 was 13138, checked in by livings124, 10 years ago

Animate rearranging the queue separately from the main filter animation. This allows us to ensure that the animation shows the dragged torrents being moved instead of the torrents around them.

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