source: trunk/macosx/Controller.m @ 12710

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

update each torrent's stats in the same loop as where we pull the transfer rates (now that the update method doesn't have any other effects)

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