source: trunk/macosx/Controller.m @ 12647

Last change on this file since 12647 was 12647, checked in by livings124, 11 years ago

invert the logic for the "show remaining time" checkbox in the global action popover

  • Property svn:keywords set to Date Rev Author Id
File size: 157.8 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 12647 2011-08-08 12:34:11Z 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        [self fullUpdateUI];
923    }
924    else
925    {
926        [torrent closeRemoveTorrent: NO];
927        [torrent release];
928    }
929}
930
931- (void) openMagnet: (NSString *) address
932{
933    tr_torrent * duplicateTorrent;
934    if ((duplicateTorrent = tr_torrentFindFromMagnetLink(fLib, [address UTF8String])))
935    {
936        const tr_info * info = tr_torrentInfo(duplicateTorrent);
937        NSString * name = (info != NULL && info->name != NULL) ? [NSString stringWithUTF8String: info->name] : nil;
938        [self duplicateOpenMagnetAlert: address transferName: name];
939        return;
940    }
941   
942    //determine download location
943    NSString * location = nil;
944    if ([fDefaults boolForKey: @"DownloadLocationConstant"])
945        location = [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath];
946   
947    Torrent * torrent;
948    if (!(torrent = [[Torrent alloc] initWithMagnetAddress: address location: location lib: fLib]))
949    {
950        [self invalidOpenMagnetAlert: address];
951        return;
952    }
953   
954    //change the location if the group calls for it (this has to wait until after the torrent is created)
955    if ([[GroupsController groups] usesCustomDownloadLocationForIndex: [torrent groupValue]])
956    {
957        location = [[GroupsController groups] customDownloadLocationForIndex: [torrent groupValue]];
958        [torrent changeDownloadFolderBeforeUsing: location];
959    }
960   
961    if ([fDefaults boolForKey: @"MagnetOpenAsk"] || !location)
962    {
963        AddMagnetWindowController * addController = [[AddMagnetWindowController alloc] initWithTorrent: torrent destination: location
964                                                        controller: self];
965        [addController showWindow: self];
966    }
967    else
968    {
969        if ([fDefaults boolForKey: @"AutoStartDownload"])
970            [torrent startTransfer];
971       
972        [torrent update];
973        [fTorrents addObject: torrent];
974        [torrent release];
975    }
976
977    [self fullUpdateUI];
978}
979
980- (void) askOpenMagnetConfirmed: (AddMagnetWindowController *) addController add: (BOOL) add
981{
982    Torrent * torrent = [addController torrent];
983    [addController release];
984   
985    if (add)
986    {
987        [torrent update];
988        [fTorrents addObject: torrent];
989        [torrent release];
990       
991        [self fullUpdateUI];
992    }
993    else
994    {
995        [torrent closeRemoveTorrent: NO];
996        [torrent release];
997    }
998}
999
1000- (void) openCreatedFile: (NSNotification *) notification
1001{
1002    NSDictionary * dict = [notification userInfo];
1003    [self openFiles: [NSArray arrayWithObject: [dict objectForKey: @"File"]] addType: ADD_CREATED
1004                        forcePath: [dict objectForKey: @"Path"]];
1005    [dict release];
1006}
1007
1008- (void) openFilesWithDict: (NSDictionary *) dictionary
1009{
1010    [self openFiles: [dictionary objectForKey: @"Filenames"] addType: [[dictionary objectForKey: @"AddType"] intValue] forcePath: nil];
1011   
1012    [dictionary release];
1013}
1014
1015//called on by applescript
1016- (void) open: (NSArray *) files
1017{
1018    NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys: files, @"Filenames",
1019                                [NSNumber numberWithInt: ADD_MANUAL], @"AddType", nil];
1020    [self performSelectorOnMainThread: @selector(openFilesWithDict:) withObject: dict waitUntilDone: NO];
1021}
1022
1023- (void) openShowSheet: (id) sender
1024{
1025    NSOpenPanel * panel = [NSOpenPanel openPanel];
1026
1027    [panel setAllowsMultipleSelection: YES];
1028    [panel setCanChooseFiles: YES];
1029    [panel setCanChooseDirectories: NO];
1030
1031    [panel beginSheetForDirectory: nil file: nil types: [NSArray arrayWithObjects: @"org.bittorrent.torrent", @"torrent", nil]
1032        modalForWindow: fWindow modalDelegate: self didEndSelector: @selector(openSheetClosed:returnCode:contextInfo:)
1033        contextInfo: [NSNumber numberWithBool: sender == fOpenIgnoreDownloadFolder]];
1034}
1035
1036- (void) openSheetClosed: (NSOpenPanel *) panel returnCode: (NSInteger) code contextInfo: (NSNumber *) useOptions
1037{
1038    if (code == NSOKButton)
1039    {
1040        NSDictionary * dictionary = [[NSDictionary alloc] initWithObjectsAndKeys: [panel filenames], @"Filenames",
1041            [NSNumber numberWithInt: [useOptions boolValue] ? ADD_SHOW_OPTIONS : ADD_MANUAL], @"AddType", nil];
1042        [self performSelectorOnMainThread: @selector(openFilesWithDict:) withObject: dictionary waitUntilDone: NO];
1043    }
1044}
1045
1046- (void) invalidOpenAlert: (NSString *) filename
1047{
1048    if (![fDefaults boolForKey: @"WarningInvalidOpen"])
1049        return;
1050   
1051    NSAlert * alert = [[NSAlert alloc] init];
1052    [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"\"%@\" is not a valid torrent file.",
1053                            "Open invalid alert -> title"), filename]];
1054    [alert setInformativeText:
1055            NSLocalizedString(@"The torrent file cannot be opened because it contains invalid data.",
1056                            "Open invalid alert -> message")];
1057    [alert setAlertStyle: NSWarningAlertStyle];
1058    [alert addButtonWithTitle: NSLocalizedString(@"OK", "Open invalid alert -> button")];
1059   
1060    [alert runModal];
1061    if ([[alert suppressionButton] state] == NSOnState)
1062        [fDefaults setBool: NO forKey: @"WarningInvalidOpen"];
1063    [alert release];
1064}
1065
1066- (void) invalidOpenMagnetAlert: (NSString *) address
1067{
1068    if (![fDefaults boolForKey: @"WarningInvalidOpen"])
1069        return;
1070   
1071    NSAlert * alert = [[NSAlert alloc] init];
1072    [alert setMessageText: NSLocalizedString(@"Adding magnetized transfer failed.", "Magnet link failed -> title")];
1073    [alert setInformativeText: [NSString stringWithFormat: NSLocalizedString(@"There was an error when adding the magnet link \"%@\"."
1074                                " The transfer will not occur.", "Magnet link failed -> message"), address]];
1075    [alert setAlertStyle: NSWarningAlertStyle];
1076    [alert addButtonWithTitle: NSLocalizedString(@"OK", "Magnet link failed -> button")];
1077   
1078    [alert runModal];
1079    if ([[alert suppressionButton] state] == NSOnState)
1080        [fDefaults setBool: NO forKey: @"WarningInvalidOpen"];
1081    [alert release];
1082}
1083
1084- (void) duplicateOpenAlert: (NSString *) name
1085{
1086    if (![fDefaults boolForKey: @"WarningDuplicate"])
1087        return;
1088   
1089    NSAlert * alert = [[NSAlert alloc] init];
1090    [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"A transfer of \"%@\" already exists.",
1091                            "Open duplicate alert -> title"), name]];
1092    [alert setInformativeText:
1093            NSLocalizedString(@"The transfer cannot be added because it is a duplicate of an already existing transfer.",
1094                            "Open duplicate alert -> message")];
1095    [alert setAlertStyle: NSWarningAlertStyle];
1096    [alert addButtonWithTitle: NSLocalizedString(@"OK", "Open duplicate alert -> button")];
1097    [alert setShowsSuppressionButton: YES];
1098   
1099    [alert runModal];
1100    if ([[alert suppressionButton] state])
1101        [fDefaults setBool: NO forKey: @"WarningDuplicate"];
1102    [alert release];
1103}
1104
1105- (void) duplicateOpenMagnetAlert: (NSString *) address transferName: (NSString *) name
1106{
1107    if (![fDefaults boolForKey: @"WarningDuplicate"])
1108        return;
1109   
1110    NSAlert * alert = [[NSAlert alloc] init];
1111    if (name)
1112        [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"A transfer of \"%@\" already exists.",
1113                                "Open duplicate magnet alert -> title"), name]];
1114    else
1115        [alert setMessageText: NSLocalizedString(@"Magnet link is a duplicate of an existing transfer.",
1116                                "Open duplicate magnet alert -> title")];
1117    [alert setInformativeText: [NSString stringWithFormat:
1118            NSLocalizedString(@"The magnet link  \"%@\" cannot be added because it is a duplicate of an already existing transfer.",
1119                            "Open duplicate magnet alert -> message"), address]];
1120    [alert setAlertStyle: NSWarningAlertStyle];
1121    [alert addButtonWithTitle: NSLocalizedString(@"OK", "Open duplicate magnet alert -> button")];
1122    [alert setShowsSuppressionButton: YES];
1123   
1124    [alert runModal];
1125    if ([[alert suppressionButton] state])
1126        [fDefaults setBool: NO forKey: @"WarningDuplicate"];
1127    [alert release];
1128}
1129
1130- (void) openURL: (NSString *) urlString
1131{
1132    if ([urlString rangeOfString: @"magnet:" options: (NSAnchoredSearch | NSCaseInsensitiveSearch)].location != NSNotFound)
1133        [self openMagnet: urlString];
1134    else
1135    {
1136        if ([urlString rangeOfString: @"://"].location == NSNotFound)
1137        {
1138            if ([urlString rangeOfString: @"."].location == NSNotFound)
1139            {
1140                NSInteger beforeCom;
1141                if ((beforeCom = [urlString rangeOfString: @"/"].location) != NSNotFound)
1142                    urlString = [NSString stringWithFormat: @"http://www.%@.com/%@",
1143                                    [urlString substringToIndex: beforeCom],
1144                                    [urlString substringFromIndex: beforeCom + 1]];
1145                else
1146                    urlString = [NSString stringWithFormat: @"http://www.%@.com/", urlString];
1147            }
1148            else
1149                urlString = [@"http://" stringByAppendingString: urlString];
1150        }
1151       
1152        NSURLRequest * request = [NSURLRequest requestWithURL: [NSURL URLWithString: urlString]
1153                                    cachePolicy: NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval: 60];
1154        [[NSURLDownload alloc] initWithRequest: request delegate: self];
1155    }
1156}
1157
1158- (void) openURLShowSheet: (id) sender
1159{
1160    [[[URLSheetWindowController alloc] initWithController: self] beginSheetForWindow: fWindow];
1161}
1162
1163- (void) urlSheetDidEnd: (URLSheetWindowController *) controller url: (NSString *) urlString returnCode: (NSInteger) returnCode
1164{
1165    if (returnCode == 1)
1166        [self performSelectorOnMainThread: @selector(openURL:) withObject: urlString waitUntilDone: NO];
1167   
1168    [controller release];
1169}
1170
1171- (void) createFile: (id) sender
1172{
1173    [CreatorWindowController createTorrentFile: fLib];
1174}
1175
1176- (void) resumeSelectedTorrents: (id) sender
1177{
1178    [self resumeTorrents: [fTableView selectedTorrents]];
1179}
1180
1181- (void) resumeAllTorrents: (id) sender
1182{
1183    NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [fTorrents count]];
1184   
1185    for (Torrent * torrent in fTorrents)
1186        if (![torrent isFinishedSeeding])
1187            [torrents addObject: torrent];
1188   
1189    [self resumeTorrents: torrents];
1190}
1191
1192- (void) resumeTorrents: (NSArray *) torrents
1193{
1194    for (Torrent * torrent in torrents)
1195        [torrent startTransfer];
1196   
1197    [self fullUpdateUI];
1198}
1199
1200- (void) resumeSelectedTorrentsNoWait:  (id) sender
1201{
1202    [self resumeTorrentsNoWait: [fTableView selectedTorrents]];
1203}
1204
1205- (void) resumeWaitingTorrents: (id) sender
1206{
1207    NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [fTorrents count]];
1208   
1209    for (Torrent * torrent in fTorrents)
1210        if ([torrent waitingToStart])
1211            [torrents addObject: torrent];
1212   
1213    [self resumeTorrentsNoWait: torrents];
1214}
1215
1216- (void) resumeTorrentsNoWait: (NSArray *) torrents
1217{
1218    //iterate through instead of all at once to ensure no conflicts
1219    for (Torrent * torrent in torrents)
1220        [torrent startTransferNoQueue];
1221   
1222    [self fullUpdateUI];
1223}
1224
1225- (void) stopSelectedTorrents: (id) sender
1226{
1227    [self stopTorrents: [fTableView selectedTorrents]];
1228}
1229
1230- (void) stopAllTorrents: (id) sender
1231{
1232    [self stopTorrents: fTorrents];
1233}
1234
1235- (void) stopTorrents: (NSArray *) torrents
1236{
1237    //don't want any of these starting then stopping
1238    for (Torrent * torrent in torrents)
1239         if ([torrent waitingToStart])
1240             [torrent stopTransfer];
1241   
1242    for (Torrent * torrent in torrents)
1243        [torrent stopTransfer];
1244   
1245    [self fullUpdateUI];
1246}
1247
1248- (void) removeTorrents: (NSArray *) torrents deleteData: (BOOL) deleteData
1249{
1250    [torrents retain];
1251
1252    if ([fDefaults boolForKey: @"CheckRemove"])
1253    {
1254        NSUInteger active = 0, downloading = 0;
1255        for (Torrent * torrent in torrents)
1256            if ([torrent isActive])
1257            {
1258                ++active;
1259                if (![torrent isSeeding])
1260                    ++downloading;
1261            }
1262
1263        if ([fDefaults boolForKey: @"CheckRemoveDownloading"] ? downloading > 0 : active > 0)
1264        {
1265            NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
1266                                    torrents, @"Torrents",
1267                                    [NSNumber numberWithBool: deleteData], @"DeleteData", nil];
1268           
1269            NSString * title, * message;
1270           
1271            const NSInteger selected = [torrents count];
1272            if (selected == 1)
1273            {
1274                NSString * torrentName = [[torrents objectAtIndex: 0] name];
1275               
1276                if (deleteData)
1277                    title = [NSString stringWithFormat:
1278                                NSLocalizedString(@"Are you sure you want to remove \"%@\" from the transfer list"
1279                                " and trash the data file?", "Removal confirm panel -> title"), torrentName];
1280                else
1281                    title = [NSString stringWithFormat:
1282                                NSLocalizedString(@"Are you sure you want to remove \"%@\" from the transfer list?",
1283                                "Removal confirm panel -> title"), torrentName];
1284               
1285                message = NSLocalizedString(@"This transfer is active."
1286                            " Once removed, continuing the transfer will require the torrent file or magnet link.",
1287                            "Removal confirm panel -> message");
1288            }
1289            else
1290            {
1291                if (deleteData)
1292                    title = [NSString stringWithFormat:
1293                                NSLocalizedString(@"Are you sure you want to remove %@ transfers from the transfer list"
1294                                " and trash the data files?", "Removal confirm panel -> title"), [NSString formattedUInteger: selected]];
1295                else
1296                    title = [NSString stringWithFormat:
1297                                NSLocalizedString(@"Are you sure you want to remove %@ transfers from the transfer list?",
1298                                "Removal confirm panel -> title"), [NSString formattedUInteger: selected]];
1299               
1300                if (selected == active)
1301                    message = [NSString stringWithFormat: NSLocalizedString(@"There are %@ active transfers.",
1302                                "Removal confirm panel -> message part 1"), [NSString formattedUInteger: active]];
1303                else
1304                    message = [NSString stringWithFormat: NSLocalizedString(@"There are %@ transfers (%@ active).",
1305                                "Removal confirm panel -> message part 1"), [NSString formattedUInteger: selected], [NSString formattedUInteger: active]];
1306                message = [message stringByAppendingFormat: @" %@",
1307                            NSLocalizedString(@"Once removed, continuing the transfers will require the torrent files or magnet links.",
1308                            "Removal confirm panel -> message part 2")];
1309            }
1310           
1311            NSBeginAlertSheet(title, NSLocalizedString(@"Remove", "Removal confirm panel -> button"),
1312                NSLocalizedString(@"Cancel", "Removal confirm panel -> button"), nil, fWindow, self,
1313                nil, @selector(removeSheetDidEnd:returnCode:contextInfo:), dict, message);
1314            return;
1315        }
1316    }
1317   
1318    [self confirmRemoveTorrents: torrents deleteData: deleteData];
1319}
1320
1321- (void) removeSheetDidEnd: (NSWindow *) sheet returnCode: (NSInteger) returnCode contextInfo: (NSDictionary *) dict
1322{
1323    NSArray * torrents = [dict objectForKey: @"Torrents"];
1324    if (returnCode == NSAlertDefaultReturn)
1325        [self confirmRemoveTorrents: torrents deleteData: [[dict objectForKey: @"DeleteData"] boolValue]];
1326    else
1327        [torrents release];
1328   
1329    [dict release];
1330}
1331
1332- (void) confirmRemoveTorrents: (NSArray *) torrents deleteData: (BOOL) deleteData
1333{
1334    NSMutableArray * selectedValues = [NSMutableArray arrayWithArray: [fTableView selectedValues]];
1335    [selectedValues removeObjectsInArray: torrents];
1336   
1337    //don't want any of these starting then stopping
1338    for (Torrent * torrent in torrents)
1339        if ([torrent waitingToStart])
1340            [torrent stopTransfer];
1341   
1342    [fTorrents removeObjectsInArray: torrents];
1343   
1344    //if not removed from displayed torrents, fullUpdateUI might cause a crash
1345    if ([fDisplayedTorrents count] > 0)
1346    {
1347        if ([[fDisplayedTorrents objectAtIndex: 0] isKindOfClass: [TorrentGroup class]])
1348        {
1349            for (TorrentGroup * group in fDisplayedTorrents)
1350                [[group torrents] removeObjectsInArray: torrents];
1351        }
1352        else
1353            [fDisplayedTorrents removeObjectsInArray: torrents];
1354    }
1355   
1356    for (Torrent * torrent in torrents)
1357    {
1358        //let's expand all groups that have removed items - they either don't exist anymore, are already expanded, or are collapsed (rpc)
1359        [fTableView removeCollapsedGroup: [torrent groupValue]];
1360       
1361        //we can't assume the window is active - RPC removal, for example
1362        [fBadger removeTorrent: torrent];
1363       
1364        [torrent closeRemoveTorrent: deleteData];
1365    }
1366   
1367    #warning why do we need them retained?
1368    [torrents release];
1369   
1370    [fTableView selectValues: selectedValues];
1371   
1372    [self fullUpdateUI];
1373}
1374
1375- (void) removeNoDelete: (id) sender
1376{
1377    [self removeTorrents: [fTableView selectedTorrents] deleteData: NO];
1378}
1379
1380- (void) removeDeleteData: (id) sender
1381{
1382    [self removeTorrents: [fTableView selectedTorrents] deleteData: YES];
1383}
1384
1385- (void) clearCompleted: (id) sender
1386{
1387    NSMutableArray * torrents = [[NSMutableArray alloc] init];
1388   
1389    for (Torrent * torrent in fTorrents)
1390        if ([torrent isFinishedSeeding])
1391            [torrents addObject: torrent];
1392   
1393    if ([fDefaults boolForKey: @"WarningRemoveCompleted"])
1394    {
1395        NSString * message, * info;
1396        if ([torrents count] == 1)
1397        {
1398            NSString * torrentName = [[torrents objectAtIndex: 0] name];
1399            message = [NSString stringWithFormat: NSLocalizedString(@"Are you sure you want to remove \"%@\" from the transfer list?",
1400                                                                  "Remove completed confirm panel -> title"), torrentName];
1401           
1402            info = NSLocalizedString(@"Once removed, continuing the transfer will require the torrent file or magnet link.",
1403                                     "Remove completed confirm panel -> message");
1404        }
1405        else
1406        {
1407            message = [NSString stringWithFormat: NSLocalizedString(@"Are you sure you want to remove %@ completed transfers from the transfer list?",
1408                                                                  "Remove completed confirm panel -> title"), [NSString formattedUInteger: [torrents count]]];
1409           
1410            info = NSLocalizedString(@"Once removed, continuing the transfers will require the torrent files or magnet links.",
1411                                     "Remove completed confirm panel -> message");
1412        }
1413       
1414        NSAlert * alert = [[[NSAlert alloc] init] autorelease];
1415        [alert setMessageText: message];
1416        [alert setInformativeText: info];
1417        [alert setAlertStyle: NSWarningAlertStyle];
1418        [alert addButtonWithTitle: NSLocalizedString(@"Remove", "Remove completed confirm panel -> button")];
1419        [alert addButtonWithTitle: NSLocalizedString(@"Cancel", "Remove completed confirm panel -> button")];
1420        [alert setShowsSuppressionButton: YES];
1421       
1422        const NSInteger returnCode = [alert runModal];
1423        if ([[alert suppressionButton] state])
1424            [fDefaults setBool: NO forKey: @"WarningRemoveCompleted"];
1425       
1426        if (returnCode != NSAlertFirstButtonReturn)
1427        {
1428            [torrents release];
1429            return;
1430        }
1431    }
1432   
1433    [self confirmRemoveTorrents: torrents deleteData: NO];
1434}
1435
1436- (void) moveDataFilesSelected: (id) sender
1437{
1438    [self moveDataFiles: [fTableView selectedTorrents]];
1439}
1440
1441- (void) moveDataFiles: (NSArray *) torrents
1442{
1443    NSOpenPanel * panel = [NSOpenPanel openPanel];
1444    [panel setPrompt: NSLocalizedString(@"Select", "Move torrent -> prompt")];
1445    [panel setAllowsMultipleSelection: NO];
1446    [panel setCanChooseFiles: NO];
1447    [panel setCanChooseDirectories: YES];
1448    [panel setCanCreateDirectories: YES];
1449   
1450    torrents = [torrents retain];
1451    NSInteger count = [torrents count];
1452    if (count == 1)
1453        [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the new folder for \"%@\".",
1454                            "Move torrent -> select destination folder"), [[torrents objectAtIndex: 0] name]]];
1455    else
1456        [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the new folder for %d data files.",
1457                            "Move torrent -> select destination folder"), count]];
1458       
1459    [panel beginSheetForDirectory: nil file: nil modalForWindow: fWindow modalDelegate: self
1460        didEndSelector: @selector(moveDataFileChoiceClosed:returnCode:contextInfo:) contextInfo: torrents];
1461}
1462
1463- (void) moveDataFileChoiceClosed: (NSOpenPanel *) panel returnCode: (NSInteger) code contextInfo: (NSArray *) torrents
1464{
1465    if (code == NSOKButton)
1466    {
1467        for (Torrent * torrent in torrents)
1468            [torrent moveTorrentDataFileTo: [[panel filenames] objectAtIndex: 0]];
1469    }
1470   
1471    [torrents release];
1472}
1473
1474- (void) copyTorrentFiles: (id) sender
1475{
1476    [self copyTorrentFileForTorrents: [[NSMutableArray alloc] initWithArray: [fTableView selectedTorrents]]];
1477}
1478
1479- (void) copyTorrentFileForTorrents: (NSMutableArray *) torrents
1480{
1481    if ([torrents count] == 0)
1482    {
1483        [torrents release];
1484        return;
1485    }
1486   
1487    Torrent * torrent = [torrents objectAtIndex: 0];
1488   
1489    if (![torrent isMagnet] && [[NSFileManager defaultManager] fileExistsAtPath: [torrent torrentLocation]])
1490    {
1491        NSSavePanel * panel = [NSSavePanel savePanel];
1492        [panel setAllowedFileTypes: [NSArray arrayWithObjects: @"org.bittorrent.torrent", @"torrent", nil]];
1493        [panel setExtensionHidden: NO];
1494       
1495        [panel beginSheetForDirectory: nil file: [torrent name] modalForWindow: fWindow modalDelegate: self
1496            didEndSelector: @selector(saveTorrentCopySheetClosed:returnCode:contextInfo:) contextInfo: torrents];
1497    }
1498    else
1499    {
1500        if (![torrent isMagnet])
1501        {
1502            NSAlert * alert = [[NSAlert alloc] init];
1503            [alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent file copy alert -> button")];
1504            [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Copy of \"%@\" Cannot Be Created",
1505                                    "Torrent file copy alert -> title"), [torrent name]]];
1506            [alert setInformativeText: [NSString stringWithFormat:
1507                    NSLocalizedString(@"The torrent file (%@) cannot be found.", "Torrent file copy alert -> message"),
1508                                        [torrent torrentLocation]]];
1509            [alert setAlertStyle: NSWarningAlertStyle];
1510           
1511            [alert runModal];
1512            [alert release];
1513        }
1514       
1515        [torrents removeObjectAtIndex: 0];
1516        [self copyTorrentFileForTorrents: torrents];
1517    }
1518}
1519
1520- (void) saveTorrentCopySheetClosed: (NSSavePanel *) panel returnCode: (NSInteger) code contextInfo: (NSMutableArray *) torrents
1521{
1522    //copy torrent to new location with name of data file
1523    if (code == NSOKButton)
1524        [[torrents objectAtIndex: 0] copyTorrentFileTo: [panel filename]];
1525   
1526    [torrents removeObjectAtIndex: 0];
1527    [self performSelectorOnMainThread: @selector(copyTorrentFileForTorrents:) withObject: torrents waitUntilDone: NO];
1528}
1529
1530- (void) copyMagnetLinks: (id) sender
1531{
1532    NSArray * torrents = [fTableView selectedTorrents];
1533   
1534    if ([torrents count] <= 0)
1535        return;
1536   
1537    NSMutableArray * links = [NSMutableArray arrayWithCapacity: [torrents count]];
1538    for (Torrent * torrent in torrents)
1539        [links addObject: [torrent magnetLink]];
1540   
1541    NSString * text = [links componentsJoinedByString: @"\n"];
1542   
1543    NSPasteboard * pb = [NSPasteboard generalPasteboard];
1544    if ([NSApp isOnSnowLeopardOrBetter])
1545    {
1546        [pb clearContents];
1547        [pb writeObjects: [NSArray arrayWithObject: text]];
1548    }
1549    else
1550    {
1551        [pb declareTypes: [NSArray arrayWithObject: NSStringPboardType] owner: nil];
1552        [pb setString: text forType: NSStringPboardType];
1553    }
1554}
1555
1556- (void) revealFile: (id) sender
1557{
1558    NSArray * selected = [fTableView selectedTorrents];
1559    if ([NSApp isOnSnowLeopardOrBetter])
1560    {
1561        NSMutableArray * paths = [NSMutableArray arrayWithCapacity: [selected count]];
1562        for (Torrent * torrent in selected)
1563        {
1564            NSString * location = [torrent dataLocation];
1565            if (location)
1566                [paths addObject: [NSURL fileURLWithPath: location]];
1567        }
1568       
1569        if ([paths count] > 0)
1570            [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: paths];
1571    }
1572    else
1573    {
1574        for (Torrent * torrent in selected)
1575        {
1576            NSString * location = [torrent dataLocation];
1577            if (location)
1578                [[NSWorkspace sharedWorkspace] selectFile: location inFileViewerRootedAtPath: nil];
1579        }
1580    }
1581}
1582
1583- (void) announceSelectedTorrents: (id) sender
1584{
1585    for (Torrent * torrent in [fTableView selectedTorrents])
1586    {
1587        if ([torrent canManualAnnounce])
1588            [torrent manualAnnounce];
1589    }
1590}
1591
1592- (void) verifySelectedTorrents: (id) sender
1593{
1594    [self verifyTorrents: [fTableView selectedTorrents]];
1595}
1596
1597- (void) verifyTorrents: (NSArray *) torrents
1598{
1599    for (Torrent * torrent in torrents)
1600        [torrent resetCache];
1601   
1602    [self applyFilter];
1603}
1604
1605- (void) showPreferenceWindow: (id) sender
1606{
1607    NSWindow * window = [fPrefsController window];
1608    if (![window isVisible])
1609        [window center];
1610
1611    [window makeKeyAndOrderFront: nil];
1612}
1613
1614- (void) showAboutWindow: (id) sender
1615{
1616    [[AboutWindowController aboutController] showWindow: nil];
1617}
1618
1619- (void) showInfo: (id) sender
1620{
1621    if ([[fInfoController window] isVisible])
1622        [fInfoController close];
1623    else
1624    {
1625        [fInfoController updateInfoStats];
1626        [[fInfoController window] orderFront: nil];
1627       
1628        if ([fInfoController canQuickLook]
1629            && [QLPreviewPanelSL sharedPreviewPanelExists] && [[QLPreviewPanelSL sharedPreviewPanel] isVisible])
1630            [[QLPreviewPanelSL sharedPreviewPanel] reloadData];
1631       
1632    }
1633   
1634    [[fWindow toolbar] validateVisibleItems];
1635}
1636
1637- (void) resetInfo
1638{
1639    [fInfoController setInfoForTorrents: [fTableView selectedTorrents]];
1640   
1641    if ([NSApp isOnSnowLeopardOrBetter] && [QLPreviewPanelSL sharedPreviewPanelExists]
1642        && [[QLPreviewPanelSL sharedPreviewPanel] isVisible])
1643        [[QLPreviewPanelSL sharedPreviewPanel] reloadData];
1644}
1645
1646- (void) setInfoTab: (id) sender
1647{
1648    if (sender == fNextInfoTabItem)
1649        [fInfoController setNextTab];
1650    else
1651        [fInfoController setPreviousTab];
1652}
1653
1654- (void) showMessageWindow: (id) sender
1655{
1656    if (!fMessageController)
1657        fMessageController = [[MessageWindowController alloc] init];
1658    [fMessageController showWindow: nil];
1659}
1660
1661- (void) showStatsWindow: (id) sender
1662{
1663    [[StatsWindowController statsWindow: fLib] showWindow: nil];
1664}
1665
1666- (void) updateUI
1667{
1668    [fTorrents makeObjectsPerformSelector: @selector(update)];
1669   
1670    //pull the upload and download speeds - most consistent by using current stats
1671    CGFloat dlRate = 0.0, ulRate = 0.0;
1672    BOOL completed = NO;
1673    for (Torrent * torrent in fTorrents)
1674    {
1675        dlRate += [torrent downloadRate];
1676        ulRate += [torrent uploadRate];
1677       
1678        completed |= [torrent isFinishedSeeding];
1679    }
1680   
1681    if (![NSApp isHidden])
1682    {
1683        if ([fWindow isVisible])
1684        {
1685            [self sortTorrents];
1686           
1687            [fStatusBar updateWithDownload: dlRate upload: ulRate];
1688           
1689            [fClearCompletedButton setHidden: !completed];
1690        }
1691
1692        //update non-constant parts of info window
1693        if ([[fInfoController window] isVisible])
1694            [fInfoController updateInfoStats];
1695    }
1696   
1697    //badge dock
1698    [fBadger updateBadgeWithDownload: dlRate upload: ulRate];
1699}
1700
1701#warning can this be removed or refined?
1702- (void) fullUpdateUI
1703{
1704    [self updateUI];
1705    [self applyFilter];
1706    [[fWindow toolbar] validateVisibleItems];
1707    [self updateTorrentHistory];
1708}
1709
1710- (void) setBottomCountText: (BOOL) filtering
1711{
1712    NSString * totalTorrentsString;
1713    NSUInteger totalCount = [fTorrents count];
1714    if (totalCount != 1)
1715        totalTorrentsString = [NSString stringWithFormat: NSLocalizedString(@"%@ transfers", "Status bar transfer count"),
1716                                [NSString formattedUInteger: totalCount]];
1717    else
1718        totalTorrentsString = NSLocalizedString(@"1 transfer", "Status bar transfer count");
1719   
1720    if (filtering)
1721    {
1722        NSUInteger count = [fTableView numberOfRows]; //have to factor in collapsed rows
1723        if (count > 0 && ![[fDisplayedTorrents objectAtIndex: 0] isKindOfClass: [Torrent class]])
1724            count -= [fDisplayedTorrents count];
1725       
1726        totalTorrentsString = [NSString stringWithFormat: NSLocalizedString(@"%@ of %@", "Status bar transfer count"),
1727                                [NSString formattedUInteger: count], totalTorrentsString];
1728    }
1729   
1730    [fTotalTorrentsField setStringValue: totalTorrentsString];
1731}
1732
1733- (void) torrentFinishedDownloading: (NSNotification *) notification
1734{
1735    Torrent * torrent = [notification object];
1736   
1737    if ([[[notification userInfo] objectForKey: @"WasRunning"] boolValue])
1738    {
1739        if (!fSoundPlaying && [fDefaults boolForKey: @"PlayDownloadSound"])
1740        {
1741            NSSound * sound;
1742            if ((sound = [NSSound soundNamed: [fDefaults stringForKey: @"DownloadSound"]]))
1743            {
1744                [sound setDelegate: self];
1745                fSoundPlaying = YES;
1746                [sound play];
1747            }
1748        }
1749       
1750        NSMutableDictionary * clickContext = [NSMutableDictionary dictionaryWithObject: GROWL_DOWNLOAD_COMPLETE forKey: @"Type"];
1751       
1752        NSString * location = [torrent dataLocation];
1753        if (location)
1754            [clickContext setObject: location forKey: @"Location"];
1755       
1756        [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Download Complete", "Growl notification title")
1757                                    description: [torrent name] notificationName: GROWL_DOWNLOAD_COMPLETE
1758                                    iconData: nil priority: 0 isSticky: NO clickContext: clickContext];
1759       
1760        if (![fWindow isMainWindow])
1761            [fBadger addCompletedTorrent: torrent];
1762       
1763        //bounce download stack
1764        [[NSDistributedNotificationCenter defaultCenter] postNotificationName: @"com.apple.DownloadFileFinished"
1765            object: [torrent dataLocation]];
1766    }
1767   
1768    [self fullUpdateUI];
1769}
1770
1771#warning remove?
1772- (void) torrentRestartedDownloading: (NSNotification *) notification
1773{
1774    [self fullUpdateUI];
1775}
1776
1777- (void) torrentFinishedSeeding: (NSNotification *) notification
1778{
1779    Torrent * torrent = [notification object];
1780   
1781    [self fullUpdateUI];
1782   
1783    if ([[fTableView selectedTorrents] containsObject: torrent])
1784    {
1785        [fInfoController updateInfoStats];
1786        [fInfoController updateOptions];
1787    }
1788   
1789    if (!fSoundPlaying && [fDefaults boolForKey: @"PlaySeedingSound"])
1790    {
1791        NSSound * sound;
1792        if ((sound = [NSSound soundNamed: [fDefaults stringForKey: @"SeedingSound"]]))
1793        {
1794            [sound setDelegate: self];
1795            fSoundPlaying = YES;
1796            [sound play];
1797        }
1798    }
1799   
1800    NSMutableDictionary * clickContext = [NSMutableDictionary dictionaryWithObject: GROWL_SEEDING_COMPLETE forKey: @"Type"];
1801   
1802    NSString * location = [torrent dataLocation];
1803    if (location)
1804        [clickContext setObject: location forKey: @"Location"];
1805   
1806    [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Seeding Complete", "Growl notification title")
1807                        description: [torrent name] notificationName: GROWL_SEEDING_COMPLETE
1808                        iconData: nil priority: 0 isSticky: NO clickContext: clickContext];
1809}
1810
1811- (void) updateTorrentHistory
1812{
1813    NSMutableArray * history = [NSMutableArray arrayWithCapacity: [fTorrents count]];
1814   
1815    for (Torrent * torrent in fTorrents)
1816        [history addObject: [torrent history]];
1817   
1818    [history writeToFile: [NSHomeDirectory() stringByAppendingPathComponent: TRANSFER_PLIST] atomically: YES];
1819}
1820
1821- (void) setSort: (id) sender
1822{
1823    NSString * sortType;
1824    switch ([sender tag])
1825    {
1826        case SORT_ORDER_TAG:
1827            sortType = SORT_ORDER;
1828            [fDefaults setBool: NO forKey: @"SortReverse"];
1829            break;
1830        case SORT_DATE_TAG:
1831            sortType = SORT_DATE;
1832            break;
1833        case SORT_NAME_TAG:
1834            sortType = SORT_NAME;
1835            break;
1836        case SORT_PROGRESS_TAG:
1837            sortType = SORT_PROGRESS;
1838            break;
1839        case SORT_STATE_TAG:
1840            sortType = SORT_STATE;
1841            break;
1842        case SORT_TRACKER_TAG:
1843            sortType = SORT_TRACKER;
1844            break;
1845        case SORT_ACTIVITY_TAG:
1846            sortType = SORT_ACTIVITY;
1847            break;
1848        case SORT_SIZE_TAG:
1849            sortType = SORT_SIZE;
1850            break;
1851        default:
1852            NSAssert1(NO, @"Unknown sort tag received: %d", [sender tag]);
1853            return;
1854    }
1855   
1856    [fDefaults setObject: sortType forKey: @"Sort"];
1857    [self applyFilter]; //better than calling sortTorrents because it will even apply to queue order
1858}
1859
1860- (void) setSortByGroup: (id) sender
1861{
1862    BOOL sortByGroup = ![fDefaults boolForKey: @"SortByGroup"];
1863    [fDefaults setBool: sortByGroup forKey: @"SortByGroup"];
1864   
1865    //expand all groups
1866    if (sortByGroup)
1867        [fTableView removeAllCollapsedGroups];
1868   
1869    [self applyFilter];
1870}
1871
1872- (void) setSortReverse: (id) sender
1873{
1874    const BOOL setReverse = [sender tag] == SORT_DESC_TAG;
1875    if (setReverse != [fDefaults boolForKey: @"SortReverse"])
1876    {
1877        [fDefaults setBool: setReverse forKey: @"SortReverse"];
1878        [self sortTorrents];
1879    }
1880}
1881
1882- (void) sortTorrents
1883{
1884    NSArray * selectedValues = [fTableView selectedValues];
1885   
1886    [self sortTorrentsIgnoreSelected]; //actually sort
1887   
1888    [fTableView selectValues: selectedValues];
1889}
1890
1891- (void) sortTorrentsIgnoreSelected
1892{
1893    NSString * sortType = [fDefaults stringForKey: @"Sort"];
1894   
1895    if (![sortType isEqualToString: SORT_ORDER])
1896    {
1897        const BOOL asc = ![fDefaults boolForKey: @"SortReverse"];
1898       
1899        NSArray * descriptors;
1900        NSSortDescriptor * nameDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"name" ascending: asc
1901                                                selector: @selector(compareFinder:)] autorelease];
1902       
1903        if ([sortType isEqualToString: SORT_STATE])
1904        {
1905            NSSortDescriptor * stateDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"stateSortKey" ascending: !asc] autorelease],
1906                            * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"progress" ascending: !asc] autorelease],
1907                            * ratioDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"ratio" ascending: !asc] autorelease];
1908           
1909            descriptors = [[NSArray alloc] initWithObjects: stateDescriptor, progressDescriptor, ratioDescriptor,
1910                                                                nameDescriptor, nil];
1911        }
1912        else if ([sortType isEqualToString: SORT_PROGRESS])
1913        {
1914            NSSortDescriptor * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"progress" ascending: asc] autorelease],
1915                            * ratioProgressDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"progressStopRatio"
1916                                                            ascending: asc] autorelease],
1917                            * ratioDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"ratio" ascending: asc] autorelease];
1918           
1919            descriptors = [[NSArray alloc] initWithObjects: progressDescriptor, ratioProgressDescriptor, ratioDescriptor,
1920                                                                nameDescriptor, nil];
1921        }
1922        else if ([sortType isEqualToString: SORT_TRACKER])
1923        {
1924            NSSortDescriptor * trackerDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"trackerSortKey" ascending: asc
1925                                                    selector: @selector(localizedCaseInsensitiveCompare:)] autorelease];
1926           
1927            descriptors = [[NSArray alloc] initWithObjects: trackerDescriptor, nameDescriptor, nil];
1928        }
1929        else if ([sortType isEqualToString: SORT_ACTIVITY])
1930        {
1931            NSSortDescriptor * rateDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"totalRate" ascending: !asc] autorelease];
1932            NSSortDescriptor * activityDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"dateActivityOrAdd" ascending: !asc]
1933                                                        autorelease];
1934           
1935            descriptors = [[NSArray alloc] initWithObjects: rateDescriptor, activityDescriptor, nameDescriptor, nil];
1936        }
1937        else if ([sortType isEqualToString: SORT_DATE])
1938        {
1939            NSSortDescriptor * dateDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"dateAdded" ascending: asc] autorelease];
1940           
1941            descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, nameDescriptor, nil];
1942        }
1943        else if ([sortType isEqualToString: SORT_SIZE])
1944        {
1945            NSSortDescriptor * sizeDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"size" ascending: asc] autorelease];
1946           
1947            descriptors = [[NSArray alloc] initWithObjects: sizeDescriptor, nameDescriptor, nil];
1948        }
1949        else
1950            descriptors = [[NSArray alloc] initWithObjects: nameDescriptor, nil];
1951       
1952        //actually sort
1953        if ([fDefaults boolForKey: @"SortByGroup"])
1954        {
1955            for (TorrentGroup * group in fDisplayedTorrents)
1956                [[group torrents] sortUsingDescriptors: descriptors];
1957        }
1958        else
1959            [fDisplayedTorrents sortUsingDescriptors: descriptors];
1960       
1961        [descriptors release];
1962    }
1963   
1964    [fTableView reloadData];
1965}
1966
1967- (void) applyFilter
1968{
1969    //get all the torrents in the table
1970    NSMutableArray * previousTorrents;
1971    if ([fDisplayedTorrents count] > 0 && [[fDisplayedTorrents objectAtIndex: 0] isKindOfClass: [TorrentGroup class]])
1972    {
1973        previousTorrents = [NSMutableArray array];
1974       
1975        for (TorrentGroup * group in fDisplayedTorrents)
1976            [previousTorrents addObjectsFromArray: [group torrents]];
1977    }
1978    else
1979        previousTorrents = fDisplayedTorrents;
1980   
1981    NSArray * selectedValues = [fTableView selectedValues];
1982   
1983    NSUInteger active = 0, downloading = 0, seeding = 0, paused = 0;
1984    NSString * filterType = [fDefaults stringForKey: @"Filter"];
1985    BOOL filterActive = NO, filterDownload = NO, filterSeed = NO, filterPause = NO, filterStatus = YES;
1986    if ([filterType isEqualToString: FILTER_ACTIVE])
1987        filterActive = YES;
1988    else if ([filterType isEqualToString: FILTER_DOWNLOAD])
1989        filterDownload = YES;
1990    else if ([filterType isEqualToString: FILTER_SEED])
1991        filterSeed = YES;
1992    else if ([filterType isEqualToString: FILTER_PAUSE])
1993        filterPause = YES;
1994    else
1995        filterStatus = NO;
1996   
1997    const NSInteger groupFilterValue = [fDefaults integerForKey: @"FilterGroup"];
1998    const BOOL filterGroup = groupFilterValue != GROUP_FILTER_ALL_TAG;
1999   
2000    NSString * searchString = [fFilterBar searchString];
2001    if (searchString && [searchString isEqualToString: @""])
2002        searchString = nil;
2003    const BOOL filterTracker = searchString && [[fDefaults stringForKey: @"FilterSearchType"] isEqualToString: FILTER_TYPE_TRACKER];
2004   
2005    NSMutableArray * allTorrents = [NSMutableArray arrayWithCapacity: [fTorrents count]];
2006   
2007    //get count of each type
2008    for (Torrent * torrent in fTorrents)
2009    {
2010        //check status
2011        if ([torrent isActive] && ![torrent isCheckingWaiting])
2012        {
2013            const BOOL isActive = ![torrent isStalled];
2014            if (isActive)
2015                ++active;
2016           
2017            if ([torrent isSeeding])
2018            {
2019                ++seeding;
2020                if (filterStatus && !((filterActive && isActive) || filterSeed))
2021                    continue;
2022            }
2023            else
2024            {
2025                ++downloading;
2026                if (filterStatus && !((filterActive && isActive) || filterDownload))
2027                    continue;
2028            }
2029        }
2030        else
2031        {
2032            ++paused;
2033            if (filterStatus && !filterPause)
2034                continue;
2035        }
2036       
2037        //checkGroup
2038        if (filterGroup)
2039            if ([torrent groupValue] != groupFilterValue)
2040                continue;
2041       
2042        //check text field
2043        if (searchString)
2044        {
2045            if (filterTracker)
2046            {
2047                BOOL removeTextField = YES;
2048                for (NSString * tracker in [torrent allTrackersFlat])
2049                {
2050                    if ([tracker rangeOfString: searchString options:
2051                            (NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch)].location != NSNotFound)
2052                    {
2053                        removeTextField = NO;
2054                        break;
2055                    }
2056                }
2057               
2058                if (removeTextField)
2059                    continue;
2060            }
2061            else
2062            {
2063                if ([[torrent name] rangeOfString: searchString options:
2064                        (NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch)].location == NSNotFound)
2065                    continue;
2066            }
2067        }
2068       
2069        [allTorrents addObject: torrent];
2070    }
2071   
2072    //set button tooltips
2073    if (fFilterBar)
2074        [fFilterBar setCountAll: [fTorrents count] active: active downloading: downloading seeding: seeding paused: paused];
2075   
2076    //clear display cache for not-shown torrents
2077    [previousTorrents removeObjectsInArray: allTorrents];
2078    for (Torrent * torrent in previousTorrents)
2079        [torrent setPreviousFinishedPieces: nil];
2080   
2081    //place torrents into groups
2082    const BOOL groupRows = [fDefaults boolForKey: @"SortByGroup"];
2083    if (groupRows)
2084    {
2085        NSMutableArray * oldTorrentGroups = [NSMutableArray array];
2086        if ([fDisplayedTorrents count] > 0 && [[fDisplayedTorrents objectAtIndex: 0] isKindOfClass: [TorrentGroup class]])
2087            [oldTorrentGroups addObjectsFromArray: fDisplayedTorrents];
2088       
2089        [fDisplayedTorrents removeAllObjects];
2090       
2091        NSSortDescriptor * groupDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"groupOrderValue" ascending: YES] autorelease];
2092        [allTorrents sortUsingDescriptors: [NSArray arrayWithObject: groupDescriptor]];
2093       
2094        TorrentGroup * group = nil;
2095        NSInteger lastGroupValue = -2, currentOldGroupIndex = 0;
2096        for (Torrent * torrent in allTorrents)
2097        {
2098            const NSInteger groupValue = [torrent groupValue];
2099            if (groupValue != lastGroupValue)
2100            {
2101                lastGroupValue = groupValue;
2102               
2103                group = nil;
2104               
2105                //try to see if the group already exists
2106                for (; currentOldGroupIndex < [oldTorrentGroups count]; ++currentOldGroupIndex)
2107                {
2108                    TorrentGroup * currentGroup = [oldTorrentGroups objectAtIndex: currentOldGroupIndex];
2109                    const NSInteger currentGroupValue = [currentGroup groupIndex];
2110                    if (currentGroupValue == groupValue)
2111                    {
2112                        group = currentGroup;
2113                        [[currentGroup torrents] removeAllObjects];
2114                       
2115                        ++currentOldGroupIndex;
2116                    }
2117                   
2118                    if (currentGroupValue >= groupValue)
2119                        break;
2120                }
2121               
2122                if (!group)
2123                    group = [[[TorrentGroup alloc] initWithGroup: groupValue] autorelease];
2124                [fDisplayedTorrents addObject: group];
2125            }
2126           
2127            NSAssert(group != nil, @"No group object to add torrents to");
2128            [[group torrents] addObject: torrent];
2129        }
2130    }
2131    else
2132        [fDisplayedTorrents setArray: allTorrents];
2133   
2134    //actually sort
2135    [self sortTorrentsIgnoreSelected];
2136   
2137    //reset expanded/collapsed rows
2138    if (groupRows)
2139    {
2140        for (TorrentGroup * group in fDisplayedTorrents)
2141        {
2142            if ([fTableView isGroupCollapsed: [group groupIndex]])
2143                [fTableView collapseItem: group];
2144            else
2145                [fTableView expandItem: group];
2146        }
2147    }
2148   
2149    [fTableView selectValues: selectedValues];
2150    [self resetInfo]; //if group is already selected, but the torrents in it change
2151   
2152    [self setBottomCountText: groupRows || filterStatus || filterGroup || searchString];
2153   
2154    [self setWindowSizeToFit];
2155}
2156
2157- (void) switchFilter: (id) sender
2158{
2159    [fFilterBar switchFilter: sender == fNextFilterItem];
2160}
2161
2162- (IBAction) showGlobalPopover: (id) sender
2163{
2164    if ([NSApp isOnLionOrBetter])
2165    {
2166        if (fGlobalPopoverShown)
2167            return;
2168       
2169        NSPopover * popover = [[NSPopoverLion alloc] init];
2170        [popover setBehavior: NSPopoverBehaviorTransient];
2171        GlobalOptionsPopoverViewController * viewController = [[GlobalOptionsPopoverViewController alloc] initWithHandle: [PrefsController handle]];
2172        [popover setContentViewController: viewController];
2173        [popover setDelegate: self];
2174       
2175        [popover showRelativeToRect: [sender frame] ofView: sender preferredEdge: NSMaxYEdge];
2176       
2177        [viewController release];
2178        [popover release];
2179    }
2180    else
2181    {
2182        //place menu below button
2183        NSRect rect = [sender frame];
2184        NSPoint location = rect.origin;
2185        location.y += NSHeight(rect) + 5.0;
2186       
2187        if ([NSApp isOnSnowLeopardOrBetter])
2188            [fActionMenu popUpMenuPositioningItem: nil atLocation: location inView: sender];
2189        else
2190        {
2191            NSEvent * newEvent = [NSEvent mouseEventWithType: NSLeftMouseDown location: location
2192                                               modifierFlags: NSLeftMouseDownMask timestamp: GetCurrentEventTime() windowNumber: [fWindow windowNumber]
2193                                                     context: nil eventNumber: 1 clickCount: 1 pressure: 1];
2194             
2195            [NSMenu popUpContextMenu: fActionMenu withEvent: newEvent forView: sender];
2196        }
2197    }
2198}
2199
2200//don't show multiple popovers when clicking the gear button repeatedly
2201- (void) popoverWillShow: (NSNotification *) notification
2202{
2203    fGlobalPopoverShown = YES;
2204}
2205
2206- (void) popoverWillClose: (NSNotification *) notification
2207{
2208    fGlobalPopoverShown = NO;
2209}
2210
2211- (void) menuNeedsUpdate: (NSMenu *) menu
2212{
2213    if (menu == fGroupsSetMenu || menu == fGroupsSetContextMenu)
2214    {
2215        for (NSInteger i = [menu numberOfItems]-1; i >= 0; i--)
2216            [menu removeItemAtIndex: i];
2217       
2218        NSMenu * groupMenu = [[GroupsController groups] groupMenuWithTarget: self action: @selector(setGroup:) isSmall: NO];
2219       
2220        const NSInteger groupMenuCount = [groupMenu numberOfItems];
2221        for (NSInteger i = 0; i < groupMenuCount; i++)
2222        {
2223            NSMenuItem * item = [[groupMenu itemAtIndex: 0] retain];
2224            [groupMenu removeItemAtIndex: 0];
2225            [menu addItem: item];
2226            [item release];
2227        }
2228    }
2229    else if (menu == fUploadMenu || menu == fDownloadMenu)
2230    {
2231        if ([menu numberOfItems] > 3)
2232            return;
2233       
2234        const NSInteger speedLimitActionValue[] = { 5, 10, 20, 30, 40, 50, 75, 100, 150, 200, 250, 500, 750, 1000, 1500, 2000, -1 };
2235       
2236        NSMenuItem * item;
2237        for (NSInteger i = 0; speedLimitActionValue[i] != -1; i++)
2238        {
2239            item = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: NSLocalizedString(@"%d KB/s",
2240                    "Action menu -> upload/download limit"), speedLimitActionValue[i]] action: @selector(setQuickLimitGlobal:)
2241                    keyEquivalent: @""];
2242            [item setTarget: self];
2243            [item setRepresentedObject: [NSNumber numberWithInt: speedLimitActionValue[i]]];
2244            [menu addItem: item];
2245            [item release];
2246        }
2247    }
2248    else if (menu == fRatioStopMenu)
2249    {
2250        if ([menu numberOfItems] > 3)
2251            return;
2252       
2253        const float ratioLimitActionValue[] = { 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, -1 };
2254       
2255        NSMenuItem * item;
2256        for (NSInteger i = 0; ratioLimitActionValue[i] != -1; i++)
2257        {
2258            item = [[NSMenuItem alloc] initWithTitle: [NSString localizedStringWithFormat: @"%.2f", ratioLimitActionValue[i]]
2259                    action: @selector(setQuickRatioGlobal:) keyEquivalent: @""];
2260            [item setTarget: self];
2261            [item setRepresentedObject: [NSNumber numberWithFloat: ratioLimitActionValue[i]]];
2262            [menu addItem: item];
2263            [item release];
2264        }
2265    }
2266    else;
2267}
2268
2269- (void) setGroup: (id) sender
2270{
2271    for (Torrent * torrent in [fTableView selectedTorrents])
2272    {
2273        [fTableView removeCollapsedGroup: [torrent groupValue]]; //remove old collapsed group
2274       
2275        [torrent setGroupValue: [sender tag]];
2276    }
2277   
2278    [self applyFilter];
2279    [self updateUI];
2280    [self updateTorrentHistory];
2281}
2282
2283- (void) toggleSpeedLimit: (id) sender
2284{
2285    [fDefaults setBool: ![fDefaults boolForKey: @"SpeedLimit"] forKey: @"SpeedLimit"];
2286    [self speedLimitChanged: sender];
2287}
2288
2289- (void) speedLimitChanged: (id) sender
2290{
2291    tr_sessionUseAltSpeed(fLib, [fDefaults boolForKey: @"SpeedLimit"]);
2292    [fStatusBar updateSpeedFieldsToolTips];
2293}
2294
2295//dict has been retained
2296- (void) altSpeedToggledCallbackIsLimited: (NSDictionary *) dict
2297{
2298    const BOOL isLimited = [[dict objectForKey: @"Active"] boolValue];
2299
2300    [fDefaults setBool: isLimited forKey: @"SpeedLimit"];
2301    [fStatusBar updateSpeedFieldsToolTips];
2302   
2303    if (![[dict objectForKey: @"ByUser"] boolValue])
2304        [GrowlApplicationBridge notifyWithTitle: isLimited
2305                ? NSLocalizedString(@"Speed Limit Auto Enabled", "Growl notification title")
2306                : NSLocalizedString(@"Speed Limit Auto Disabled", "Growl notification title")
2307            description: NSLocalizedString(@"Bandwidth settings changed", "Growl notification description")
2308            notificationName: GROWL_AUTO_SPEED_LIMIT iconData: nil priority: 0 isSticky: NO clickContext: nil];
2309   
2310    [dict release];
2311}
2312
2313- (void) setLimitGlobalEnabled: (id) sender
2314{
2315    BOOL upload = [sender menu] == fUploadMenu;
2316    [fDefaults setBool: sender == (upload ? fUploadLimitItem : fDownloadLimitItem) forKey: upload ? @"CheckUpload" : @"CheckDownload"];
2317   
2318    [fPrefsController applySpeedSettings: nil];
2319}
2320
2321- (void) setQuickLimitGlobal: (id) sender
2322{
2323    BOOL upload = [sender menu] == fUploadMenu;
2324    [fDefaults setInteger: [[sender representedObject] intValue] forKey: upload ? @"UploadLimit" : @"DownloadLimit"];
2325    [fDefaults setBool: YES forKey: upload ? @"CheckUpload" : @"CheckDownload"];
2326   
2327    [fPrefsController updateLimitFields];
2328    [fPrefsController applySpeedSettings: nil];
2329}
2330
2331- (void) setRatioGlobalEnabled: (id) sender
2332{
2333    [fDefaults setBool: sender == fCheckRatioItem forKey: @"RatioCheck"];
2334   
2335    [fPrefsController applyRatioSetting: nil];
2336}
2337
2338- (void) setQuickRatioGlobal: (id) sender
2339{
2340    [fDefaults setBool: YES forKey: @"RatioCheck"];
2341    [fDefaults setFloat: [[sender representedObject] floatValue] forKey: @"RatioLimit"];
2342   
2343    [fPrefsController updateRatioStopFieldOld];
2344}
2345
2346- (void) sound: (NSSound *) sound didFinishPlaying: (BOOL) finishedPlaying
2347{
2348    fSoundPlaying = NO;
2349}
2350
2351- (void) watcher: (id<UKFileWatcher>) watcher receivedNotification: (NSString *) notification forPath: (NSString *) path
2352{
2353    if ([notification isEqualToString: UKFileWatcherWriteNotification])
2354    {
2355        if (![fDefaults boolForKey: @"AutoImport"] || ![fDefaults stringForKey: @"AutoImportDirectory"])
2356            return;
2357       
2358        if (fAutoImportTimer)
2359        {
2360            if ([fAutoImportTimer isValid])
2361                [fAutoImportTimer invalidate];
2362            [fAutoImportTimer release];
2363            fAutoImportTimer = nil;
2364        }
2365       
2366        //check again in 10 seconds in case torrent file wasn't complete
2367        fAutoImportTimer = [[NSTimer scheduledTimerWithTimeInterval: 10.0 target: self
2368            selector: @selector(checkAutoImportDirectory) userInfo: nil repeats: NO] retain];
2369       
2370        [self checkAutoImportDirectory];
2371    }
2372}
2373
2374- (void) changeAutoImport
2375{
2376    if (fAutoImportTimer)
2377    {
2378        if ([fAutoImportTimer isValid])
2379            [fAutoImportTimer invalidate];
2380        [fAutoImportTimer release];
2381        fAutoImportTimer = nil;
2382    }
2383   
2384    if (fAutoImportedNames)
2385    {
2386        [fAutoImportedNames release];
2387        fAutoImportedNames = nil;
2388    }
2389   
2390    [self checkAutoImportDirectory];
2391}
2392
2393- (void) checkAutoImportDirectory
2394{
2395    NSString * path;
2396    if (![fDefaults boolForKey: @"AutoImport"] || !(path = [fDefaults stringForKey: @"AutoImportDirectory"]))
2397        return;
2398   
2399    path = [path stringByExpandingTildeInPath];
2400   
2401    NSArray * importedNames;
2402    if (!(importedNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: path error: NULL]))
2403        return;
2404   
2405    //only check files that have not been checked yet
2406    NSMutableArray * newNames = [importedNames mutableCopy];
2407   
2408    if (fAutoImportedNames)
2409        [newNames removeObjectsInArray: fAutoImportedNames];
2410    else
2411        fAutoImportedNames = [[NSMutableArray alloc] init];
2412    [fAutoImportedNames setArray: importedNames];
2413   
2414    for (NSString * file in newNames)
2415    {
2416        if ([file hasPrefix: @"."])
2417            continue;
2418       
2419        NSString * fullFile = [path stringByAppendingPathComponent: file];
2420       
2421        if (!([[[NSWorkspace sharedWorkspace] typeOfFile: fullFile error: NULL] isEqualToString: @"org.bittorrent.torrent"]
2422                || [[fullFile pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame))
2423            continue;
2424       
2425        tr_ctor * ctor = tr_ctorNew(fLib);
2426        tr_ctorSetMetainfoFromFile(ctor, [fullFile UTF8String]);
2427       
2428        switch (tr_torrentParse(ctor, NULL))
2429        {
2430            case TR_PARSE_OK:
2431                [self openFiles: [NSArray arrayWithObject: fullFile] addType: ADD_AUTO forcePath: nil];
2432               
2433                [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Torrent File Auto Added", "Growl notification title")
2434                    description: file notificationName: GROWL_AUTO_ADD iconData: nil priority: 0 isSticky: NO
2435                    clickContext: nil];
2436                break;
2437           
2438            case TR_PARSE_ERR:
2439                [fAutoImportedNames removeObject: file];
2440                break;
2441           
2442            case TR_PARSE_DUPLICATE: //let's ignore this (but silence a warning)
2443                break;
2444        }
2445       
2446        tr_ctorFree(ctor);
2447    }
2448   
2449    [newNames release];
2450}
2451
2452- (void) beginCreateFile: (NSNotification *) notification
2453{
2454    if (![fDefaults boolForKey: @"AutoImport"])
2455        return;
2456   
2457    NSString * location = [notification object],
2458            * path = [fDefaults stringForKey: @"AutoImportDirectory"];
2459   
2460    if (location && path && [[[location stringByDeletingLastPathComponent] stringByExpandingTildeInPath]
2461                                    isEqualToString: [path stringByExpandingTildeInPath]])
2462        [fAutoImportedNames addObject: [location lastPathComponent]];
2463}
2464
2465- (NSInteger) outlineView: (NSOutlineView *) outlineView numberOfChildrenOfItem: (id) item
2466{
2467    if (item)
2468        return [[item torrents] count];
2469    else
2470        return [fDisplayedTorrents count];
2471}
2472
2473- (id) outlineView: (NSOutlineView *) outlineView child: (NSInteger) index ofItem: (id) item
2474{
2475    if (item)
2476        return [[item torrents] objectAtIndex: index];
2477    else
2478        return [fDisplayedTorrents objectAtIndex: index];
2479}
2480
2481- (BOOL) outlineView: (NSOutlineView *) outlineView isItemExpandable: (id) item
2482{
2483    return ![item isKindOfClass: [Torrent class]];
2484}
2485
2486- (id) outlineView: (NSOutlineView *) outlineView objectValueForTableColumn: (NSTableColumn *) tableColumn byItem: (id) item
2487{
2488    if ([item isKindOfClass: [Torrent class]]) {
2489        if (tableColumn)
2490            return nil;
2491        return [item hashString];
2492    }
2493    else
2494    {
2495        NSString * ident = [tableColumn identifier];
2496        if ([ident isEqualToString: @"Group"])
2497        {
2498            NSInteger group = [item groupIndex];
2499            return group != -1 ? [[GroupsController groups] nameForIndex: group]
2500                                : NSLocalizedString(@"No Group", "Group table row");
2501        }
2502        else if ([ident isEqualToString: @"Color"])
2503        {
2504            NSInteger group = [item groupIndex];
2505            return [[GroupsController groups] imageForIndex: group];
2506        }
2507        else if ([ident isEqualToString: @"DL Image"])
2508            return [NSImage imageNamed: @"DownArrowGroupTemplate.png"];
2509        else if ([ident isEqualToString: @"UL Image"])
2510            return [NSImage imageNamed: [fDefaults boolForKey: @"DisplayGroupRowRatio"]
2511                                        ? @"YingYangGroupTemplate.png" : @"UpArrowGroupTemplate.png"];
2512        else
2513        {
2514            TorrentGroup * group = (TorrentGroup *)item;
2515           
2516            if ([fDefaults boolForKey: @"DisplayGroupRowRatio"])
2517                return [NSString stringForRatio: [group ratio]];
2518            else
2519            {
2520                CGFloat rate = [ident isEqualToString: @"UL"] ? [group uploadRate] : [group downloadRate];
2521                return [NSString stringForSpeed: rate];
2522            }
2523        }
2524    }
2525}
2526
2527- (BOOL) outlineView: (NSOutlineView *) outlineView writeItems: (NSArray *) items toPasteboard: (NSPasteboard *) pasteboard
2528{
2529    //only allow reordering of rows if sorting by order
2530    if ([fDefaults boolForKey: @"SortByGroup"] || [[fDefaults stringForKey: @"Sort"] isEqualToString: SORT_ORDER])
2531    {
2532        NSMutableIndexSet * indexSet = [NSMutableIndexSet indexSet];
2533        for (id torrent in items)
2534        {
2535            if (![torrent isKindOfClass: [Torrent class]])
2536                return NO;
2537           
2538            [indexSet addIndex: [fTableView rowForItem: torrent]];
2539        }
2540       
2541        [pasteboard declareTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE] owner: self];
2542        [pasteboard setData: [NSKeyedArchiver archivedDataWithRootObject: indexSet] forType: TORRENT_TABLE_VIEW_DATA_TYPE];
2543        return YES;
2544    }
2545    return NO;
2546}
2547
2548- (NSDragOperation) outlineView: (NSOutlineView *) outlineView validateDrop: (id < NSDraggingInfo >) info proposedItem: (id) item
2549    proposedChildIndex: (NSInteger) index
2550{
2551    NSPasteboard * pasteboard = [info draggingPasteboard];
2552    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2553    {
2554        if ([fDefaults boolForKey: @"SortByGroup"])
2555        {
2556            if (!item)
2557                return NSDragOperationNone;
2558           
2559            if ([[fDefaults stringForKey: @"Sort"] isEqualToString: SORT_ORDER])
2560            {
2561                if ([item isKindOfClass: [Torrent class]])
2562                {
2563                    TorrentGroup * group = [fTableView parentForItem: item];
2564                    index = [[group torrents] indexOfObject: item] + 1;
2565                    item = group;
2566                }
2567            }
2568            else
2569            {
2570                if ([item isKindOfClass: [Torrent class]])
2571                    item = [fTableView parentForItem: item];
2572                index = NSOutlineViewDropOnItemIndex;
2573            }
2574        }
2575        else
2576        {
2577            if (item)
2578            {
2579                index = [fTableView rowForItem: item] + 1;
2580                item = nil;
2581            }
2582        }
2583       
2584        [fTableView setDropItem: item dropChildIndex: index];
2585        return NSDragOperationGeneric;
2586    }
2587   
2588    return NSDragOperationNone;
2589}
2590
2591- (BOOL) outlineView: (NSOutlineView *) outlineView acceptDrop: (id < NSDraggingInfo >) info item: (id) item
2592    childIndex: (NSInteger) newRow
2593{
2594    NSPasteboard * pasteboard = [info draggingPasteboard];
2595    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2596    {
2597        //remember selected rows
2598        NSArray * selectedValues = [fTableView selectedValues];
2599   
2600        NSIndexSet * indexes = [NSKeyedUnarchiver unarchiveObjectWithData: [pasteboard dataForType: TORRENT_TABLE_VIEW_DATA_TYPE]];
2601       
2602        //get the torrents to move
2603        NSMutableArray * movingTorrents = [NSMutableArray arrayWithCapacity: [indexes count]];
2604        for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
2605            [movingTorrents addObject: [fTableView itemAtRow: i]];
2606       
2607        //reset groups
2608        if (item)
2609        {
2610            //change groups
2611            NSInteger groupValue = [item groupIndex];
2612            for (Torrent * torrent in movingTorrents)
2613            {
2614                //have to reset objects here to avoid weird crash
2615                [[[fTableView parentForItem: torrent] torrents] removeObject: torrent];
2616                [[item torrents] addObject: torrent];
2617               
2618                [torrent setGroupValue: groupValue];
2619            }
2620            //part 2 of avoiding weird crash
2621            [fTableView reloadItem: nil reloadChildren: YES];
2622        }
2623       
2624        //reorder queue order
2625        if (newRow != NSOutlineViewDropOnItemIndex)
2626        {
2627            //find torrent to place under
2628            NSArray * groupTorrents = item ? [item torrents] : fDisplayedTorrents;
2629            Torrent * topTorrent = nil;
2630            for (NSInteger i = newRow-1; i >= 0; i--)
2631            {
2632                Torrent * tempTorrent = [groupTorrents objectAtIndex: i];
2633                if (![movingTorrents containsObject: tempTorrent])
2634                {
2635                    topTorrent = tempTorrent;
2636                    break;
2637                }
2638            }
2639           
2640            //remove objects to reinsert
2641            [fTorrents removeObjectsInArray: movingTorrents];
2642           
2643            //insert objects at new location
2644            NSUInteger insertIndex = topTorrent ? [fTorrents indexOfObject: topTorrent] + 1 : 0;
2645            NSIndexSet * insertIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(insertIndex, [movingTorrents count])];
2646            for (Torrent * torrent in movingTorrents)
2647                [torrent setQueueIndex: insertIndex++];
2648            [fTorrents insertObjects: movingTorrents atIndexes: insertIndexes];
2649        }
2650       
2651        [self applyFilter];
2652        [fTableView selectValues: selectedValues];
2653    }
2654   
2655    return YES;
2656}
2657
2658- (void) torrentTableViewSelectionDidChange: (NSNotification *) notification
2659{
2660    [self resetInfo];
2661    [[fWindow toolbar] validateVisibleItems];
2662}
2663
2664- (NSDragOperation) draggingEntered: (id <NSDraggingInfo>) info
2665{
2666    NSPasteboard * pasteboard = [info draggingPasteboard];
2667    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2668    {
2669        //check if any torrent files can be added
2670        BOOL torrent = NO;
2671        NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
2672        for (NSString * file in files)
2673        {
2674            if ([[[NSWorkspace sharedWorkspace] typeOfFile: file error: NULL] isEqualToString: @"org.bittorrent.torrent"]
2675                    || [[file pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame)
2676            {
2677                torrent = YES;
2678                tr_ctor * ctor = tr_ctorNew(fLib);
2679                tr_ctorSetMetainfoFromFile(ctor, [file UTF8String]);
2680                if (tr_torrentParse(ctor, NULL) == TR_PARSE_OK)
2681                {
2682                    if (!fOverlayWindow)
2683                        fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2684                    [fOverlayWindow setTorrents: files];
2685                   
2686                    return NSDragOperationCopy;
2687                }
2688                tr_ctorFree(ctor);
2689            }
2690        }
2691       
2692        //create a torrent file if a single file
2693        if (!torrent && [files count] == 1)
2694        {
2695            if (!fOverlayWindow)
2696                fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2697            [fOverlayWindow setFile: [[files objectAtIndex: 0] lastPathComponent]];
2698           
2699            return NSDragOperationCopy;
2700        }
2701    }
2702    else if ([[pasteboard types] containsObject: NSURLPboardType])
2703    {
2704        if (!fOverlayWindow)
2705            fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2706        [fOverlayWindow setURL: [[NSURL URLFromPasteboard: pasteboard] relativeString]];
2707       
2708        return NSDragOperationCopy;
2709    }
2710    else;
2711   
2712    return NSDragOperationNone;
2713}
2714
2715- (void) draggingExited: (id <NSDraggingInfo>) info
2716{
2717    if (fOverlayWindow)
2718        [fOverlayWindow fadeOut];
2719}
2720
2721- (BOOL) performDragOperation: (id <NSDraggingInfo>) info
2722{
2723    if (fOverlayWindow)
2724        [fOverlayWindow fadeOut];
2725   
2726    NSPasteboard * pasteboard = [info draggingPasteboard];
2727    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2728    {
2729        BOOL torrent = NO, accept = YES;
2730       
2731        //create an array of files that can be opened
2732        NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
2733        NSMutableArray * filesToOpen = [NSMutableArray arrayWithCapacity: [files count]];
2734        for (NSString * file in files)
2735        {
2736            if ([[[NSWorkspace sharedWorkspace] typeOfFile: file error: NULL] isEqualToString: @"org.bittorrent.torrent"]
2737                    || [[file pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame)
2738            {
2739                torrent = YES;
2740                tr_ctor * ctor = tr_ctorNew(fLib);
2741                tr_ctorSetMetainfoFromFile(ctor, [file UTF8String]);
2742                if (tr_torrentParse(ctor, NULL) == TR_PARSE_OK)
2743                    [filesToOpen addObject: file];
2744                tr_ctorFree(ctor);
2745            }
2746        }
2747       
2748        if ([filesToOpen count] > 0)
2749            [self application: NSApp openFiles: filesToOpen];
2750        else
2751        {
2752            if (!torrent && [files count] == 1)
2753                [CreatorWindowController createTorrentFile: fLib forFile: [files objectAtIndex: 0]];
2754            else
2755                accept = NO;
2756        }
2757       
2758        return accept;
2759    }
2760    else if ([[pasteboard types] containsObject: NSURLPboardType])
2761    {
2762        NSURL * url;
2763        if ((url = [NSURL URLFromPasteboard: pasteboard]))
2764        {
2765            [self openURL: [url absoluteString]];
2766            return YES;
2767        }
2768    }
2769    else;
2770   
2771    return NO;
2772}
2773
2774- (void) toggleSmallView: (id) sender
2775{
2776    BOOL makeSmall = ![fDefaults boolForKey: @"SmallView"];
2777    [fDefaults setBool: makeSmall forKey: @"SmallView"];
2778   
2779    [fTableView setUsesAlternatingRowBackgroundColors: !makeSmall];
2780   
2781    [fTableView setRowHeight: makeSmall ? ROW_HEIGHT_SMALL : ROW_HEIGHT_REGULAR];
2782   
2783    [fTableView noteHeightOfRowsWithIndexesChanged: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTableView numberOfRows])]];
2784   
2785    //resize for larger min height if not set to auto size
2786    if (![fDefaults boolForKey: @"AutoSize"])
2787    {
2788        const NSSize contentSize = [[fWindow contentView] frame].size;
2789       
2790        NSSize contentMinSize = [fWindow contentMinSize];
2791        contentMinSize.height = [self minWindowContentSizeAllowed];
2792        [fWindow setContentMinSize: contentMinSize];
2793       
2794        //make sure the window already isn't too small
2795        if (!makeSmall && contentSize.height < contentMinSize.height)
2796        {
2797            NSRect frame = [fWindow frame];
2798            CGFloat heightChange = contentMinSize.height - contentSize.height;
2799            frame.size.height += heightChange;
2800            frame.origin.y -= heightChange;
2801           
2802            [fWindow setFrame: frame display: YES];
2803        }
2804    }
2805    else
2806        [self setWindowSizeToFit];
2807}
2808
2809- (void) togglePiecesBar: (id) sender
2810{
2811    [fDefaults setBool: ![fDefaults boolForKey: @"PiecesBar"] forKey: @"PiecesBar"];
2812    [fTableView togglePiecesBar];
2813}
2814
2815- (void) toggleAvailabilityBar: (id) sender
2816{
2817    [fDefaults setBool: ![fDefaults boolForKey: @"DisplayProgressBarAvailable"] forKey: @"DisplayProgressBarAvailable"];
2818    [fTableView display];
2819}
2820
2821- (void) toggleStatusString: (id) sender
2822{
2823    if ([fDefaults boolForKey: @"SmallView"])
2824        [fDefaults setBool: ![fDefaults boolForKey: @"DisplaySmallStatusRegular"] forKey: @"DisplaySmallStatusRegular"];
2825    else
2826        [fDefaults setBool: ![fDefaults boolForKey: @"DisplayStatusProgressSelected"] forKey: @"DisplayStatusProgressSelected"];
2827   
2828    [fTableView reloadData];
2829}
2830
2831- (NSRect) windowFrameByAddingHeight: (CGFloat) height checkLimits: (BOOL) check
2832{
2833    NSScrollView * scrollView = [fTableView enclosingScrollView];
2834   
2835    //convert pixels to points
2836    NSRect windowFrame = [fWindow frame];
2837    NSSize windowSize = [scrollView convertSize: windowFrame.size fromView: nil];
2838    windowSize.height += height;
2839   
2840    if (check)
2841    {
2842        //we can't call minSize, since it might be set to the current size (auto size)
2843        const CGFloat minHeight = [self minWindowContentSizeAllowed]
2844                                    + (NSHeight([fWindow frame]) - NSHeight([[fWindow contentView] frame])); //contentView to window
2845       
2846        if (windowSize.height < minHeight)
2847            windowSize.height =minHeight;
2848        else
2849        {
2850            NSSize maxSize = [scrollView convertSize: [[fWindow screen] visibleFrame].size fromView: nil];
2851            if (!fStatusBar)
2852                maxSize.height -= STATUS_BAR_HEIGHT;
2853            if (!fFilterBar)
2854                maxSize.height -= FILTER_BAR_HEIGHT;
2855            if (windowSize.height > maxSize.height)
2856                windowSize.height = maxSize.height;
2857        }
2858    }
2859
2860    //convert points to pixels
2861    windowSize = [scrollView convertSize: windowSize toView: nil];
2862
2863    windowFrame.origin.y -= (windowSize.height - windowFrame.size.height);
2864    windowFrame.size.height = windowSize.height;
2865    return windowFrame;
2866}
2867
2868- (void) toggleStatusBar: (id) sender
2869{
2870    const BOOL show = fStatusBar == nil;
2871    [self showStatusBar: show animate: YES];
2872    [fDefaults setBool: show forKey: @"StatusBar"];
2873}
2874
2875//doesn't save shown state
2876- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
2877{
2878    const BOOL prevShown = fStatusBar != nil;
2879    if (show == prevShown)
2880        return;
2881   
2882    if (show)
2883    {
2884        fStatusBar = [[StatusBarController alloc] initWithLib: fLib];
2885       
2886        NSView * contentView = [fWindow contentView];
2887        const NSSize windowSize = [contentView convertSize: [fWindow frame].size fromView: nil];
2888       
2889        NSRect statusBarFrame = [[fStatusBar view] frame];
2890        statusBarFrame.size.width = windowSize.width;
2891        [[fStatusBar view] setFrame: statusBarFrame];
2892       
2893        [contentView addSubview: [fStatusBar view]];
2894        [[fStatusBar view] setFrameOrigin: NSMakePoint(0.0, NSMaxY([contentView frame]))];
2895    }
2896   
2897    NSRect frame;
2898    CGFloat heightChange = [[fStatusBar view] frame].size.height;
2899    if (!show)
2900        heightChange *= -1;
2901   
2902    //allow bar to show even if not enough room
2903    if (show && ![fDefaults boolForKey: @"AutoSize"])
2904    {
2905        frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2906        CGFloat change = [[fWindow screen] visibleFrame].size.height - frame.size.height;
2907        if (change < 0.0)
2908        {
2909            frame = [fWindow frame];
2910            frame.size.height += change;
2911            frame.origin.y -= change;
2912            [fWindow setFrame: frame display: NO animate: NO];
2913        }
2914    }
2915   
2916    [self updateUI];
2917   
2918    NSScrollView * scrollView = [fTableView enclosingScrollView];
2919   
2920    //set views to not autoresize
2921    const NSUInteger statsMask = [[fStatusBar view] autoresizingMask];
2922    [[fStatusBar view] setAutoresizingMask: NSViewNotSizable];
2923    NSUInteger filterMask;
2924    if (fFilterBar)
2925    {
2926        filterMask = [[fFilterBar view] autoresizingMask];
2927        [[fFilterBar view] setAutoresizingMask: NSViewNotSizable];
2928    }
2929    const NSUInteger scrollMask = [scrollView autoresizingMask];
2930    [scrollView setAutoresizingMask: NSViewNotSizable];
2931   
2932    frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2933    [fWindow setFrame: frame display: YES animate: animate];
2934   
2935    //re-enable autoresize
2936    [[fStatusBar view] setAutoresizingMask: statsMask];
2937    if (fFilterBar)
2938        [[fFilterBar view] setAutoresizingMask: filterMask];
2939    [scrollView setAutoresizingMask: scrollMask];
2940   
2941    if (!show)
2942    {
2943        [[fStatusBar view] removeFromSuperviewWithoutNeedingDisplay];
2944        [fStatusBar release];
2945        fStatusBar = nil;
2946    }
2947   
2948    if ([fDefaults boolForKey: @"AutoSize"])
2949        [self setWindowMinMaxToCurrent];
2950    else
2951    {
2952        //change min size
2953        NSSize minSize = [fWindow contentMinSize];
2954        minSize.height += heightChange;
2955        [fWindow setContentMinSize: minSize];
2956    }
2957}
2958
2959- (void) toggleFilterBar: (id) sender
2960{
2961    const BOOL show = fFilterBar == nil;
2962   
2963    [self showFilterBar: show animate: YES];
2964    [fDefaults setBool: show forKey: @"FilterBar"];
2965    [[fWindow toolbar] validateVisibleItems];
2966   
2967    //disable filtering when hiding
2968    if (!show)
2969    {
2970        [[NSUserDefaults standardUserDefaults] setObject: FILTER_NONE forKey: @"Filter"];
2971        [[NSUserDefaults standardUserDefaults] setInteger: GROUP_FILTER_ALL_TAG forKey: @"FilterGroup"];
2972        [[NSUserDefaults standardUserDefaults] removeObjectForKey: @"FilterSearchString"];
2973    }
2974   
2975    [self applyFilter]; //do even if showing to ensure tooltips are updated
2976}
2977
2978//doesn't save shown state
2979- (void) showFilterBar: (BOOL) show animate: (BOOL) animate
2980{
2981    const BOOL prevShown = fFilterBar != nil;
2982    if (show == prevShown)
2983        return;
2984   
2985    if (show)
2986    {
2987        fFilterBar = [[FilterBarController alloc] init];
2988       
2989        NSView * contentView = [fWindow contentView];
2990        const NSSize windowSize = [contentView convertSize: [fWindow frame].size fromView: nil];
2991       
2992        NSRect filterBarFrame = [[fFilterBar view] frame];
2993        filterBarFrame.size.width = windowSize.width;
2994        [[fFilterBar view] setFrame: filterBarFrame];
2995       
2996        if (fStatusBar)
2997            [contentView addSubview: [fFilterBar view] positioned: NSWindowBelow relativeTo: [fStatusBar view]];
2998        else
2999            [contentView addSubview: [fFilterBar view]];
3000        const CGFloat originY = fStatusBar ? NSMinY([[fStatusBar view] frame]) : NSMaxY([contentView frame]);
3001        [[fFilterBar view] setFrameOrigin: NSMakePoint(0.0, originY)];
3002    }
3003    else
3004        [fWindow makeFirstResponder: fTableView];
3005   
3006    CGFloat heightChange = NSHeight([[fFilterBar view] frame]);
3007    if (!show)
3008        heightChange *= -1;
3009   
3010    //allow bar to show even if not enough room
3011    if (show && ![fDefaults boolForKey: @"AutoSize"])
3012    {
3013        NSRect frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
3014        const CGFloat change = NSHeight([[fWindow screen] visibleFrame]) - NSHeight(frame);
3015        if (change < 0.0)
3016        {
3017            frame = [fWindow frame];
3018            frame.size.height += change;
3019            frame.origin.y -= change;
3020            [fWindow setFrame: frame display: NO animate: NO];
3021        }
3022    }
3023   
3024    NSScrollView * scrollView = [fTableView enclosingScrollView];
3025
3026    //set views to not autoresize
3027    const NSUInteger filterMask = [[fFilterBar view] autoresizingMask];
3028    const NSUInteger scrollMask = [scrollView autoresizingMask];
3029    [[fFilterBar view] setAutoresizingMask: NSViewNotSizable];
3030    [scrollView setAutoresizingMask: NSViewNotSizable];
3031   
3032    const NSRect frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
3033    [fWindow setFrame: frame display: YES animate: animate];
3034   
3035    //re-enable autoresize
3036    [[fFilterBar view] setAutoresizingMask: filterMask];
3037    [scrollView setAutoresizingMask: scrollMask];
3038   
3039    if (!show)
3040    {
3041        [[fFilterBar view] removeFromSuperviewWithoutNeedingDisplay];
3042        [fFilterBar release];
3043        fFilterBar = nil;
3044    }
3045   
3046    if ([fDefaults boolForKey: @"AutoSize"])
3047        [self setWindowMinMaxToCurrent];
3048    else
3049    {
3050        //change min size
3051        NSSize minSize = [fWindow contentMinSize];
3052        minSize.height += heightChange;
3053        [fWindow setContentMinSize: minSize];
3054    }
3055}
3056
3057- (void) focusFilterField
3058{
3059    if (!fFilterBar)
3060        [self toggleFilterBar: self];
3061    [fFilterBar focusSearchField];
3062}
3063
3064#warning change from id to QLPreviewPanel
3065- (BOOL) acceptsPreviewPanelControl: (id) panel
3066{
3067    return !fQuitting;
3068}
3069
3070- (void) beginPreviewPanelControl: (id) panel
3071{
3072    fPreviewPanel = [panel retain];
3073    [fPreviewPanel setDelegate: self];
3074    [fPreviewPanel setDataSource: self];
3075}
3076
3077- (void) endPreviewPanelControl: (id) panel
3078{
3079    [fPreviewPanel release];
3080    fPreviewPanel = nil;
3081}
3082
3083- (NSArray *) quickLookableTorrents
3084{
3085    NSArray * selectedTorrents = [fTableView selectedTorrents];
3086    NSMutableArray * qlArray = [NSMutableArray arrayWithCapacity: [selectedTorrents count]];
3087   
3088    for (Torrent * torrent in selectedTorrents)
3089        if (([torrent isFolder] || [torrent isComplete]) && [torrent dataLocation])
3090            [qlArray addObject: torrent];
3091   
3092    return qlArray;
3093}
3094
3095- (NSInteger) numberOfPreviewItemsInPreviewPanel: (id) panel
3096{
3097    if ([fInfoController canQuickLook])
3098        return [[fInfoController quickLookURLs] count];
3099    else
3100        return [[self quickLookableTorrents] count];
3101}
3102
3103- (id /*<QLPreviewItem>*/) previewPanel: (id) panel previewItemAtIndex: (NSInteger) index
3104{
3105    if ([fInfoController canQuickLook])
3106        return [[fInfoController quickLookURLs] objectAtIndex: index];
3107    else
3108        return [[self quickLookableTorrents] objectAtIndex: index];
3109}
3110
3111- (BOOL) previewPanel: (id) panel handleEvent: (NSEvent *) event
3112{
3113    /*if ([event type] == NSKeyDown)
3114    {
3115        [super keyDown: event];
3116        return YES;
3117    }*/
3118   
3119    return NO;
3120}
3121
3122- (NSRect) previewPanel: (id) panel sourceFrameOnScreenForPreviewItem: (id /*<QLPreviewItem>*/) item
3123{
3124    if ([fInfoController canQuickLook])
3125        return [fInfoController quickLookSourceFrameForPreviewItem: item];
3126    else
3127    {
3128        if (![fWindow isVisible])
3129            return NSZeroRect;
3130       
3131        const NSInteger row = [fTableView rowForItem: item];
3132        if (row == -1)
3133            return NSZeroRect;
3134       
3135        NSRect frame = [fTableView iconRectForRow: row];
3136       
3137        if (!NSIntersectsRect([fTableView visibleRect], frame))
3138            return NSZeroRect;
3139       
3140        frame.origin = [fTableView convertPoint: frame.origin toView: nil];
3141        frame.origin = [fWindow convertBaseToScreen: frame.origin];
3142        frame.origin.y -= frame.size.height;
3143        return frame;
3144    }
3145}
3146
3147- (ButtonToolbarItem *) standardToolbarButtonWithIdentifier: (NSString *) ident
3148{
3149    ButtonToolbarItem * item = [[ButtonToolbarItem alloc] initWithItemIdentifier: ident];
3150   
3151    NSButton * button = [[NSButton alloc] init];
3152    [button setBezelStyle: NSTexturedRoundedBezelStyle];
3153    [button setStringValue: @""];
3154   
3155    [item setView: button];
3156    [button release];
3157   
3158    const NSSize buttonSize = NSMakeSize(36.0, 25.0);
3159    [item setMinSize: buttonSize];
3160    [item setMaxSize: buttonSize];
3161   
3162    return [item autorelease];
3163}
3164
3165- (NSToolbarItem *) toolbar: (NSToolbar *) toolbar itemForItemIdentifier: (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
3166{
3167    if ([ident isEqualToString: TOOLBAR_CREATE])
3168    {
3169        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3170       
3171        [item setLabel: NSLocalizedString(@"Create", "Create toolbar item -> label")];
3172        [item setPaletteLabel: NSLocalizedString(@"Create Torrent File", "Create toolbar item -> palette label")];
3173        [item setToolTip: NSLocalizedString(@"Create torrent file", "Create toolbar item -> tooltip")];
3174        [item setImage: [NSImage imageNamed: @"ToolbarCreateTemplate.png"]];
3175        [item setTarget: self];
3176        [item setAction: @selector(createFile:)];
3177        [item setAutovalidates: NO];
3178       
3179        return item;
3180    }
3181    else if ([ident isEqualToString: TOOLBAR_OPEN_FILE])
3182    {
3183        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3184       
3185        [item setLabel: NSLocalizedString(@"Open", "Open toolbar item -> label")];
3186        [item setPaletteLabel: NSLocalizedString(@"Open Torrent Files", "Open toolbar item -> palette label")];
3187        [item setToolTip: NSLocalizedString(@"Open torrent files", "Open toolbar item -> tooltip")];
3188        [item setImage: [NSImage imageNamed: @"ToolbarOpenTemplate.png"]];
3189        [item setTarget: self];
3190        [item setAction: @selector(openShowSheet:)];
3191        [item setAutovalidates: NO];
3192       
3193        return item;
3194    }
3195    else if ([ident isEqualToString: TOOLBAR_OPEN_WEB])
3196    {
3197        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3198       
3199        [item setLabel: NSLocalizedString(@"Open Address", "Open address toolbar item -> label")];
3200        [item setPaletteLabel: NSLocalizedString(@"Open Torrent Address", "Open address toolbar item -> palette label")];
3201        [item setToolTip: NSLocalizedString(@"Open torrent web address", "Open address toolbar item -> tooltip")];
3202        [item setImage: [NSImage imageNamed: @"ToolbarOpenWebTemplate.png"]];
3203        [item setTarget: self];
3204        [item setAction: @selector(openURLShowSheet:)];
3205        [item setAutovalidates: NO];
3206       
3207        return item;
3208    }
3209    else if ([ident isEqualToString: TOOLBAR_REMOVE])
3210    {
3211        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3212       
3213        [item setLabel: NSLocalizedString(@"Remove", "Remove toolbar item -> label")];
3214        [item setPaletteLabel: NSLocalizedString(@"Remove Selected", "Remove toolbar item -> palette label")];
3215        [item setToolTip: NSLocalizedString(@"Remove selected transfers", "Remove toolbar item -> tooltip")];
3216        [item setImage: [NSImage imageNamed: @"ToolbarRemoveTemplate.png"]];
3217        [item setTarget: self];
3218        [item setAction: @selector(removeNoDelete:)];
3219        [item setVisibilityPriority: NSToolbarItemVisibilityPriorityHigh];
3220       
3221        return item;
3222    }
3223    else if ([ident isEqualToString: TOOLBAR_INFO])
3224    {
3225        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3226        [[(NSButton *)[item view] cell] setShowsStateBy: NSContentsCellMask]; //blue when enabled
3227       
3228        [item setLabel: NSLocalizedString(@"Inspector", "Inspector toolbar item -> label")];
3229        [item setPaletteLabel: NSLocalizedString(@"Toggle Inspector", "Inspector toolbar item -> palette label")];
3230        [item setToolTip: NSLocalizedString(@"Toggle the torrent inspector", "Inspector toolbar item -> tooltip")];
3231        [item setImage: [NSImage imageNamed: @"ToolbarInfoTemplate.png"]];
3232        [item setTarget: self];
3233        [item setAction: @selector(showInfo:)];
3234       
3235        return item;
3236    }
3237    else if ([ident isEqualToString: TOOLBAR_PAUSE_RESUME_ALL])
3238    {
3239        GroupToolbarItem * groupItem = [[GroupToolbarItem alloc] initWithItemIdentifier: ident];
3240       
3241        NSSegmentedControl * segmentedControl = [[NSSegmentedControl alloc] initWithFrame: NSZeroRect];
3242        [segmentedControl setCell: [[[ToolbarSegmentedCell alloc] init] autorelease]];
3243        [groupItem setView: segmentedControl];
3244        NSSegmentedCell * segmentedCell = (NSSegmentedCell *)[segmentedControl cell];
3245       
3246        [segmentedControl setSegmentCount: 2];
3247        [segmentedCell setTrackingMode: NSSegmentSwitchTrackingMomentary];
3248       
3249        const NSSize groupSize = NSMakeSize(72.0, 25.0);
3250        [groupItem setMinSize: groupSize];
3251        [groupItem setMaxSize: groupSize];
3252       
3253        [groupItem setLabel: NSLocalizedString(@"Apply All", "All toolbar item -> label")];
3254        [groupItem setPaletteLabel: NSLocalizedString(@"Pause / Resume All", "All toolbar item -> palette label")];
3255        [groupItem setTarget: self];
3256        [groupItem setAction: @selector(allToolbarClicked:)];
3257       
3258        [groupItem setIdentifiers: [NSArray arrayWithObjects: TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL, nil]];
3259       
3260        [segmentedCell setTag: TOOLBAR_PAUSE_TAG forSegment: TOOLBAR_PAUSE_TAG];
3261        [segmentedControl setImage: [NSImage imageNamed: @"ToolbarPauseAllTemplate.png"] forSegment: TOOLBAR_PAUSE_TAG];
3262        [segmentedCell setToolTip: NSLocalizedString(@"Pause all transfers",
3263                                    "All toolbar item -> tooltip") forSegment: TOOLBAR_PAUSE_TAG];
3264       
3265        [segmentedCell setTag: TOOLBAR_RESUME_TAG forSegment: TOOLBAR_RESUME_TAG];
3266        [segmentedControl setImage: [NSImage imageNamed: @"ToolbarResumeAllTemplate.png"] forSegment: TOOLBAR_RESUME_TAG];
3267        [segmentedCell setToolTip: NSLocalizedString(@"Resume all transfers",
3268                                    "All toolbar item -> tooltip") forSegment: TOOLBAR_RESUME_TAG];
3269       
3270        [groupItem createMenu: [NSArray arrayWithObjects: NSLocalizedString(@"Pause All", "All toolbar item -> label"),
3271                                        NSLocalizedString(@"Resume All", "All toolbar item -> label"), nil]];
3272       
3273        [segmentedControl release];
3274       
3275        [groupItem setVisibilityPriority: NSToolbarItemVisibilityPriorityHigh];
3276       
3277        return [groupItem autorelease];
3278    }
3279    else if ([ident isEqualToString: TOOLBAR_PAUSE_RESUME_SELECTED])
3280    {
3281        GroupToolbarItem * groupItem = [[GroupToolbarItem alloc] initWithItemIdentifier: ident];
3282       
3283        NSSegmentedControl * segmentedControl = [[NSSegmentedControl alloc] initWithFrame: NSZeroRect];
3284        [segmentedControl setCell: [[[ToolbarSegmentedCell alloc] init] autorelease]];
3285        [groupItem setView: segmentedControl];
3286        NSSegmentedCell * segmentedCell = (NSSegmentedCell *)[segmentedControl cell];
3287       
3288        [segmentedControl setSegmentCount: 2];
3289        [segmentedCell setTrackingMode: NSSegmentSwitchTrackingMomentary];
3290       
3291        const NSSize groupSize = NSMakeSize(72.0, 25.0);
3292        [groupItem setMinSize: groupSize];
3293        [groupItem setMaxSize: groupSize];
3294       
3295        [groupItem setLabel: NSLocalizedString(@"Apply Selected", "Selected toolbar item -> label")];
3296        [groupItem setPaletteLabel: NSLocalizedString(@"Pause / Resume Selected", "Selected toolbar item -> palette label")];
3297        [groupItem setTarget: self];
3298        [groupItem setAction: @selector(selectedToolbarClicked:)];
3299       
3300        [groupItem setIdentifiers: [NSArray arrayWithObjects: TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED, nil]];
3301       
3302        [segmentedCell setTag: TOOLBAR_PAUSE_TAG forSegment: TOOLBAR_PAUSE_TAG];
3303        [segmentedControl setImage: [NSImage imageNamed: @"ToolbarPauseSelectedTemplate.png"] forSegment: TOOLBAR_PAUSE_TAG];
3304        [segmentedCell setToolTip: NSLocalizedString(@"Pause selected transfers",
3305                                    "Selected toolbar item -> tooltip") forSegment: TOOLBAR_PAUSE_TAG];
3306       
3307        [segmentedCell setTag: TOOLBAR_RESUME_TAG forSegment: TOOLBAR_RESUME_TAG];
3308        [segmentedControl setImage: [NSImage imageNamed: @"ToolbarResumeSelectedTemplate.png"] forSegment: TOOLBAR_RESUME_TAG];
3309        [segmentedCell setToolTip: NSLocalizedString(@"Resume selected transfers",
3310                                    "Selected toolbar item -> tooltip") forSegment: TOOLBAR_RESUME_TAG];
3311       
3312        [groupItem createMenu: [NSArray arrayWithObjects: NSLocalizedString(@"Pause Selected", "Selected toolbar item -> label"),
3313                                        NSLocalizedString(@"Resume Selected", "Selected toolbar item -> label"), nil]];
3314       
3315        [segmentedControl release];
3316       
3317        [groupItem setVisibilityPriority: NSToolbarItemVisibilityPriorityHigh];
3318       
3319        return [groupItem autorelease];
3320    }
3321    else if ([ident isEqualToString: TOOLBAR_FILTER])
3322    {
3323        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3324        [[(NSButton *)[item view] cell] setShowsStateBy: NSContentsCellMask]; //blue when enabled
3325       
3326        [item setLabel: NSLocalizedString(@"Filter", "Filter toolbar item -> label")];
3327        [item setPaletteLabel: NSLocalizedString(@"Toggle Filter", "Filter toolbar item -> palette label")];
3328        [item setToolTip: NSLocalizedString(@"Toggle the filter bar", "Filter toolbar item -> tooltip")];
3329        [item setImage: [NSImage imageNamed: @"ToolbarFilterTemplate.png"]];
3330        [item setTarget: self];
3331        [item setAction: @selector(toggleFilterBar:)];
3332       
3333        return item;
3334    }
3335    else if ([ident isEqualToString: TOOLBAR_QUICKLOOK])
3336    {
3337        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3338        [[(NSButton *)[item view] cell] setShowsStateBy: NSContentsCellMask]; //blue when enabled
3339       
3340        [item setLabel: NSLocalizedString(@"Quick Look", "QuickLook toolbar item -> label")];
3341        [item setPaletteLabel: NSLocalizedString(@"Quick Look", "QuickLook toolbar item -> palette label")];
3342        [item setToolTip: NSLocalizedString(@"Quick Look", "QuickLook toolbar item -> tooltip")];
3343        [item setImage: [NSImage imageNamed: NSImageNameQuickLookTemplate]];
3344        [item setTarget: self];
3345        [item setAction: @selector(toggleQuickLook:)];
3346        [item setVisibilityPriority: NSToolbarItemVisibilityPriorityLow];
3347       
3348        return item;
3349    }
3350    else
3351        return nil;
3352}
3353
3354- (void) allToolbarClicked: (id) sender
3355{
3356    NSInteger tagValue = [sender isKindOfClass: [NSSegmentedControl class]]
3357                    ? [(NSSegmentedCell *)[sender cell] tagForSegment: [sender selectedSegment]] : [sender tag];
3358    switch (tagValue)
3359    {
3360        case TOOLBAR_PAUSE_TAG:
3361            [self stopAllTorrents: sender];
3362            break;
3363        case TOOLBAR_RESUME_TAG:
3364            [self resumeAllTorrents: sender];
3365            break;
3366    }
3367}
3368
3369- (void) selectedToolbarClicked: (id) sender
3370{
3371    NSInteger tagValue = [sender isKindOfClass: [NSSegmentedControl class]]
3372                    ? [(NSSegmentedCell *)[sender cell] tagForSegment: [sender selectedSegment]] : [sender tag];
3373    switch (tagValue)
3374    {
3375        case TOOLBAR_PAUSE_TAG:
3376            [self stopSelectedTorrents: sender];
3377            break;
3378        case TOOLBAR_RESUME_TAG:
3379            [self resumeSelectedTorrents: sender];
3380            break;
3381    }
3382}
3383
3384- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar
3385{
3386    return [NSArray arrayWithObjects:
3387            TOOLBAR_CREATE, TOOLBAR_OPEN_FILE, TOOLBAR_OPEN_WEB, TOOLBAR_REMOVE,
3388            TOOLBAR_PAUSE_RESUME_SELECTED, TOOLBAR_PAUSE_RESUME_ALL,
3389            TOOLBAR_QUICKLOOK, TOOLBAR_FILTER, TOOLBAR_INFO,
3390            NSToolbarSeparatorItemIdentifier,
3391            NSToolbarSpaceItemIdentifier,
3392            NSToolbarFlexibleSpaceItemIdentifier,
3393            NSToolbarCustomizeToolbarItemIdentifier, nil];
3394}
3395
3396- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
3397{
3398    return [NSArray arrayWithObjects:
3399            TOOLBAR_CREATE, TOOLBAR_OPEN_FILE, TOOLBAR_REMOVE, NSToolbarSpaceItemIdentifier,
3400            TOOLBAR_PAUSE_RESUME_ALL, NSToolbarFlexibleSpaceItemIdentifier,
3401            TOOLBAR_QUICKLOOK, TOOLBAR_FILTER, TOOLBAR_INFO, nil];
3402}
3403
3404- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
3405{
3406    NSString * ident = [toolbarItem itemIdentifier];
3407   
3408    //enable remove item
3409    if ([ident isEqualToString: TOOLBAR_REMOVE])
3410        return [fTableView numberOfSelectedRows] > 0;
3411
3412    //enable pause all item
3413    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
3414    {
3415        for (Torrent * torrent in fTorrents)
3416            if ([torrent isActive] || [torrent waitingToStart])
3417                return YES;
3418        return NO;
3419    }
3420
3421    //enable resume all item
3422    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
3423    {
3424        for (Torrent * torrent in fTorrents)
3425            if (![torrent isActive] && ![torrent waitingToStart])
3426                return YES;
3427        return NO;
3428    }
3429
3430    //enable pause item
3431    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
3432    {
3433        for (Torrent * torrent in [fTableView selectedTorrents])
3434            if ([torrent isActive] || [torrent waitingToStart])
3435                return YES;
3436        return NO;
3437    }
3438   
3439    //enable resume item
3440    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
3441    {
3442        for (Torrent * torrent in [fTableView selectedTorrents])
3443            if (![torrent isActive] && ![torrent waitingToStart])
3444                return YES;
3445        return NO;
3446    }
3447   
3448    //set info item
3449    if ([ident isEqualToString: TOOLBAR_INFO])
3450    {
3451        [(NSButton *)[toolbarItem view] setState: [[fInfoController window] isVisible]];
3452        return YES;
3453    }
3454   
3455    //set filter item
3456    if ([ident isEqualToString: TOOLBAR_FILTER])
3457    {
3458        [(NSButton *)[toolbarItem view] setState: fFilterBar != nil];
3459        return YES;
3460    }
3461   
3462    //set quick look item
3463    if ([ident isEqualToString: TOOLBAR_QUICKLOOK])
3464    {
3465        [(NSButton *)[toolbarItem view] setState: [NSApp isOnSnowLeopardOrBetter] && [QLPreviewPanelSL sharedPreviewPanelExists]
3466                                                    && [[QLPreviewPanelSL sharedPreviewPanel] isVisible]];
3467        return [NSApp isOnSnowLeopardOrBetter];
3468    }
3469
3470    return YES;
3471}
3472
3473- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
3474{
3475    SEL action = [menuItem action];
3476   
3477    if (action == @selector(toggleSpeedLimit:))
3478    {
3479        [menuItem setState: [fDefaults boolForKey: @"SpeedLimit"] ? NSOnState : NSOffState];
3480        return YES;
3481    }
3482   
3483    //only enable some items if it is in a context menu or the window is useable
3484    BOOL canUseTable = [fWindow isKeyWindow] || [[menuItem menu] supermenu] != [NSApp mainMenu];
3485
3486    //enable open items
3487    if (action == @selector(openShowSheet:) || action == @selector(openURLShowSheet:))
3488        return [fWindow attachedSheet] == nil;
3489   
3490    //enable sort options
3491    if (action == @selector(setSort:))
3492    {
3493        NSString * sortType;
3494        switch ([menuItem tag])
3495        {
3496            case SORT_ORDER_TAG:
3497                sortType = SORT_ORDER;
3498                break;
3499            case SORT_DATE_TAG:
3500                sortType = SORT_DATE;
3501                break;
3502            case SORT_NAME_TAG:
3503                sortType = SORT_NAME;
3504                break;
3505            case SORT_PROGRESS_TAG:
3506                sortType = SORT_PROGRESS;
3507                break;
3508            case SORT_STATE_TAG:
3509                sortType = SORT_STATE;
3510                break;
3511            case SORT_TRACKER_TAG:
3512                sortType = SORT_TRACKER;
3513                break;
3514            case SORT_ACTIVITY_TAG:
3515                sortType = SORT_ACTIVITY;
3516                break;
3517            case SORT_SIZE_TAG:
3518                sortType = SORT_SIZE;
3519                break;
3520            default:
3521                NSAssert1(NO, @"Unknown sort tag received: %d", [menuItem tag]);
3522        }
3523       
3524        [menuItem setState: [sortType isEqualToString: [fDefaults stringForKey: @"Sort"]] ? NSOnState : NSOffState];
3525        return [fWindow isVisible];
3526    }
3527   
3528    if (action == @selector(setGroup:))
3529    {
3530        BOOL checked = NO;
3531       
3532        NSInteger index = [menuItem tag];
3533        for (Torrent * torrent in [fTableView selectedTorrents])
3534            if (index == [torrent groupValue])
3535            {
3536                checked = YES;
3537                break;
3538            }
3539       
3540        [menuItem setState: checked ? NSOnState : NSOffState];
3541        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3542    }
3543   
3544    if (action == @selector(toggleSmallView:))
3545    {
3546        [menuItem setState: [fDefaults boolForKey: @"SmallView"] ? NSOnState : NSOffState];
3547        return [fWindow isVisible];
3548    }
3549   
3550    if (action == @selector(togglePiecesBar:))
3551    {
3552        [menuItem setState: [fDefaults boolForKey: @"PiecesBar"] ? NSOnState : NSOffState];
3553        return [fWindow isVisible];
3554    }
3555   
3556    #warning remove when menu is removed
3557    if (action == @selector(toggleStatusString:))
3558    {
3559        if ([fDefaults boolForKey: @"SmallView"])
3560        {
3561            [menuItem setTitle: NSLocalizedString(@"Remaining Time", "Action menu -> status string toggle")];
3562            [menuItem setState: ![fDefaults boolForKey: @"DisplaySmallStatusRegular"] ? NSOnState : NSOffState];
3563        }
3564        else
3565        {
3566            [menuItem setTitle: NSLocalizedString(@"Status of Selected Files", "Action menu -> status string toggle")];
3567            [menuItem setState: [fDefaults boolForKey: @"DisplayStatusProgressSelected"] ? NSOnState : NSOffState];
3568        }
3569       
3570        return [fWindow isVisible];
3571    }
3572   
3573    if (action == @selector(toggleAvailabilityBar:))
3574    {
3575        [menuItem setState: [fDefaults boolForKey: @"DisplayProgressBarAvailable"] ? NSOnState : NSOffState];
3576        return [fWindow isVisible];
3577    }
3578   
3579    if (action == @selector(setLimitGlobalEnabled:))
3580    {
3581        BOOL upload = [menuItem menu] == fUploadMenu;
3582        BOOL limit = menuItem == (upload ? fUploadLimitItem : fDownloadLimitItem);
3583        if (limit)
3584            [menuItem setTitle: [NSString stringWithFormat: NSLocalizedString(@"Limit (%d KB/s)",
3585                                    "Action menu -> upload/download limit"),
3586                                    [fDefaults integerForKey: upload ? @"UploadLimit" : @"DownloadLimit"]]];
3587       
3588        [menuItem setState: [fDefaults boolForKey: upload ? @"CheckUpload" : @"CheckDownload"] ? limit : !limit];
3589        return YES;
3590    }
3591   
3592    if (action == @selector(setRatioGlobalEnabled:))
3593    {
3594        BOOL check = menuItem == fCheckRatioItem;
3595        if (check)
3596            [menuItem setTitle: [NSString localizedStringWithFormat: NSLocalizedString(@"Stop at Ratio (%.2f)",
3597                                    "Action menu -> ratio stop"), [fDefaults floatForKey: @"RatioLimit"]]];
3598       
3599        [menuItem setState: [fDefaults boolForKey: @"RatioCheck"] ? check : !check];
3600        return YES;
3601    }
3602
3603    //enable show info
3604    if (action == @selector(showInfo:))
3605    {
3606        NSString * title = [[fInfoController window] isVisible] ? NSLocalizedString(@"Hide Inspector", "View menu -> Inspector")
3607                            : NSLocalizedString(@"Show Inspector", "View menu -> Inspector");
3608        [menuItem setTitle: title];
3609
3610        return YES;
3611    }
3612   
3613    //enable prev/next inspector tab
3614    if (action == @selector(setInfoTab:))
3615        return [[fInfoController window] isVisible];
3616   
3617    //enable toggle status bar
3618    if (action == @selector(toggleStatusBar:))
3619    {
3620        NSString * title = !fStatusBar ? NSLocalizedString(@"Show Status Bar", "View menu -> Status Bar")
3621                            : NSLocalizedString(@"Hide Status Bar", "View menu -> Status Bar");
3622        [menuItem setTitle: title];
3623
3624        return [fWindow isVisible];
3625    }
3626   
3627    //enable toggle filter bar
3628    if (action == @selector(toggleFilterBar:))
3629    {
3630        NSString * title = !fFilterBar ? NSLocalizedString(@"Show Filter Bar", "View menu -> Filter Bar")
3631                            : NSLocalizedString(@"Hide Filter Bar", "View menu -> Filter Bar");
3632        [menuItem setTitle: title];
3633
3634        return [fWindow isVisible];
3635    }
3636   
3637    //enable prev/next filter button
3638    if (action == @selector(switchFilter:))
3639        return [fWindow isVisible] && fFilterBar;
3640   
3641    //enable reveal in finder
3642    if (action == @selector(revealFile:))
3643        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3644
3645    //enable remove items
3646    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:))
3647    {
3648        BOOL warning = NO;
3649       
3650        for (Torrent * torrent in [fTableView selectedTorrents])
3651        {
3652            if ([torrent isActive])
3653            {
3654                if ([fDefaults boolForKey: @"CheckRemoveDownloading"] ? ![torrent isSeeding] : YES)
3655                {
3656                    warning = YES;
3657                    break;
3658                }
3659            }
3660        }
3661   
3662        //append or remove ellipsis when needed
3663        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
3664        if (warning && [fDefaults boolForKey: @"CheckRemove"])
3665        {
3666            if (![title hasSuffix: ellipsis])
3667                [menuItem setTitle: [title stringByAppendingEllipsis]];
3668        }
3669        else
3670        {
3671            if ([title hasSuffix: ellipsis])
3672                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
3673        }
3674       
3675        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3676    }
3677   
3678    //remove all completed transfers item
3679    if (action == @selector(clearCompleted:))
3680    {
3681        //append or remove ellipsis when needed
3682        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
3683        if ([fDefaults boolForKey: @"WarningRemoveCompleted"])
3684        {
3685            if (![title hasSuffix: ellipsis])
3686                [menuItem setTitle: [title stringByAppendingEllipsis]];
3687        }
3688        else
3689        {
3690            if ([title hasSuffix: ellipsis])
3691                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
3692        }
3693       
3694        for (Torrent * torrent in fTorrents)
3695            if ([torrent isFinishedSeeding])
3696                return YES;
3697        return NO;
3698    }
3699
3700    //enable pause all item
3701    if (action == @selector(stopAllTorrents:))
3702    {
3703        for (Torrent * torrent in fTorrents)
3704            if ([torrent isActive] || [torrent waitingToStart])
3705                return YES;
3706        return NO;
3707    }
3708   
3709    //enable resume all item
3710    if (action == @selector(resumeAllTorrents:))
3711    {
3712        for (Torrent * torrent in fTorrents)
3713            if (![torrent isActive] && ![torrent waitingToStart] && ![torrent isFinishedSeeding])
3714                return YES;
3715        return NO;
3716    }
3717   
3718    //enable resume all waiting item
3719    if (action == @selector(resumeWaitingTorrents:))
3720    {
3721        if (![fDefaults boolForKey: @"Queue"] && ![fDefaults boolForKey: @"QueueSeed"])
3722            return NO;
3723   
3724        for (Torrent * torrent in fTorrents)
3725            if (![torrent isActive] && [torrent waitingToStart])
3726                return YES;
3727        return NO;
3728    }
3729   
3730    //enable resume selected waiting item
3731    if (action == @selector(resumeSelectedTorrentsNoWait:))
3732    {
3733        if (!canUseTable)
3734            return NO;
3735       
3736        for (Torrent * torrent in [fTableView selectedTorrents])
3737            if (![torrent isActive])
3738                return YES;
3739        return NO;
3740    }
3741
3742    //enable pause item
3743    if (action == @selector(stopSelectedTorrents:))
3744    {
3745        if (!canUseTable)
3746            return NO;
3747   
3748        for (Torrent * torrent in [fTableView selectedTorrents])
3749            if ([torrent isActive] || [torrent waitingToStart])
3750                return YES;
3751        return NO;
3752    }
3753   
3754    //enable resume item
3755    if (action == @selector(resumeSelectedTorrents:))
3756    {
3757        if (!canUseTable)
3758            return NO;
3759   
3760        for (Torrent * torrent in [fTableView selectedTorrents])
3761            if (![torrent isActive] && ![torrent waitingToStart])
3762                return YES;
3763        return NO;
3764    }
3765   
3766    //enable manual announce item
3767    if (action == @selector(announceSelectedTorrents:))
3768    {
3769        if (!canUseTable)
3770            return NO;
3771       
3772        for (Torrent * torrent in [fTableView selectedTorrents])
3773            if ([torrent canManualAnnounce])
3774                return YES;
3775        return NO;
3776    }
3777   
3778    //enable reset cache item
3779    if (action == @selector(verifySelectedTorrents:))
3780    {
3781        if (!canUseTable)
3782            return NO;
3783           
3784        for (Torrent * torrent in [fTableView selectedTorrents])
3785            if (![torrent isMagnet])
3786                return YES;
3787        return NO;
3788    }
3789   
3790    //enable move torrent file item
3791    if (action == @selector(moveDataFilesSelected:))
3792        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3793   
3794    //enable copy torrent file item
3795    if (action == @selector(copyTorrentFiles:))
3796    {
3797        if (!canUseTable)
3798            return NO;
3799       
3800        for (Torrent * torrent in [fTableView selectedTorrents])
3801            if (![torrent isMagnet])
3802                return YES;
3803        return NO;
3804    }
3805   
3806    //enable copy torrent file item
3807    if (action == @selector(copyMagnetLinks:))
3808        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3809   
3810    //enable reverse sort item
3811    if (action == @selector(setSortReverse:))
3812    {
3813        const BOOL isReverse = [menuItem tag] == SORT_DESC_TAG;
3814        [menuItem setState: (isReverse == [fDefaults boolForKey: @"SortReverse"]) ? NSOnState : NSOffState];
3815        return ![[fDefaults stringForKey: @"Sort"] isEqualToString: SORT_ORDER];
3816    }
3817   
3818    //enable group sort item
3819    if (action == @selector(setSortByGroup:))
3820    {
3821        [menuItem setState: [fDefaults boolForKey: @"SortByGroup"] ? NSOnState : NSOffState];
3822        return YES;
3823    }
3824   
3825    if (action == @selector(toggleQuickLook:))
3826    {
3827        const BOOL visible = [NSApp isOnSnowLeopardOrBetter] && [QLPreviewPanelSL sharedPreviewPanelExists]
3828                                && [[QLPreviewPanelSL sharedPreviewPanel] isVisible];
3829        //text consistent with Finder
3830        NSString * title = !visible ? NSLocalizedString(@"Quick Look", "View menu -> Quick Look")
3831                                    : NSLocalizedString(@"Close Quick Look", "View menu -> Quick Look");
3832        [menuItem setTitle: title];
3833       
3834        return [NSApp isOnSnowLeopardOrBetter];
3835    }
3836   
3837    return YES;
3838}
3839
3840- (void) sleepCallback: (natural_t) messageType argument: (void *) messageArgument
3841{
3842    switch (messageType)
3843    {
3844        case kIOMessageSystemWillSleep:
3845            //if there are any running transfers, wait 15 seconds for them to stop
3846            for (Torrent * torrent in fTorrents)
3847                if ([torrent isActive])
3848                {
3849                    //stop all transfers (since some are active) before going to sleep and remember to resume when we wake up
3850                    for (Torrent * torrent in fTorrents)
3851                        [torrent sleep];
3852                    sleep(15);
3853                    break;
3854                }
3855
3856            IOAllowPowerChange(fRootPort, (long) messageArgument);
3857            break;
3858
3859        case kIOMessageCanSystemSleep:
3860            if ([fDefaults boolForKey: @"SleepPrevent"])
3861            {
3862                //prevent idle sleep unless no torrents are active
3863                for (Torrent * torrent in fTorrents)
3864                    if ([torrent isActive] && ![torrent isStalled] && ![torrent isError])
3865                    {
3866                        IOCancelPowerChange(fRootPort, (long) messageArgument);
3867                        return;
3868                    }
3869            }
3870           
3871            IOAllowPowerChange(fRootPort, (long) messageArgument);
3872            break;
3873
3874        case kIOMessageSystemHasPoweredOn:
3875            //resume sleeping transfers after we wake up
3876            for (Torrent * torrent in fTorrents)
3877                [torrent wakeUp];
3878            break;
3879    }
3880}
3881
3882- (NSMenu *) applicationDockMenu: (NSApplication *) sender
3883{
3884    if (fQuitting)
3885        return nil;
3886   
3887    NSInteger seeding = 0, downloading = 0;
3888    for (Torrent * torrent in fTorrents)
3889    {
3890        if ([torrent isSeeding])
3891            seeding++;
3892        else if ([torrent isActive])
3893            downloading++;
3894        else;
3895    }
3896   
3897    NSMenu * menu = [[NSMenu alloc] init];
3898   
3899    if (seeding > 0)
3900    {
3901        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Seeding", "Dock item - Seeding"), seeding];
3902        [menu addItemWithTitle: title action: nil keyEquivalent: @""];
3903    }
3904   
3905    if (downloading > 0)
3906    {
3907        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Downloading", "Dock item - Downloading"), downloading];
3908        [menu addItemWithTitle: title action: nil keyEquivalent: @""];
3909    }
3910   
3911    if (seeding > 0 || downloading > 0)
3912        [menu addItem: [NSMenuItem separatorItem]];
3913   
3914    [menu addItemWithTitle: NSLocalizedString(@"Pause All", "Dock item") action: @selector(stopAllTorrents:) keyEquivalent: @""];
3915    [menu addItemWithTitle: NSLocalizedString(@"Resume All", "Dock item") action: @selector(resumeAllTorrents:) keyEquivalent: @""];
3916    [menu addItem: [NSMenuItem separatorItem]];
3917    [menu addItemWithTitle: NSLocalizedString(@"Speed Limit", "Dock item") action: @selector(toggleSpeedLimit:) keyEquivalent: @""];
3918   
3919    return [menu autorelease];
3920}
3921
3922- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
3923{
3924    //if auto size is enabled, the current frame shouldn't need to change
3925    NSRect frame = [fDefaults boolForKey: @"AutoSize"] ? [window frame] : [self sizedWindowFrame];
3926   
3927    frame.size.width = [fDefaults boolForKey: @"SmallView"] ? [fWindow minSize].width : WINDOW_REGULAR_WIDTH;
3928    return frame;
3929}
3930
3931- (void) setWindowSizeToFit
3932{
3933    if ([fDefaults boolForKey: @"AutoSize"])
3934    {
3935        NSScrollView * scrollView = [fTableView enclosingScrollView];
3936       
3937        [scrollView setHasVerticalScroller: NO];
3938        [fWindow setFrame: [self sizedWindowFrame] display: YES animate: YES];
3939        [scrollView setHasVerticalScroller: YES];
3940       
3941        //hack to ensure scrollbars don't disappear after resizing
3942        if (![NSApp isOnSnowLeopardOrBetter])
3943        {
3944            [scrollView setAutohidesScrollers: NO];
3945            [scrollView setAutohidesScrollers: YES];
3946        }
3947       
3948        [self setWindowMinMaxToCurrent];
3949    }
3950}
3951
3952- (NSRect) sizedWindowFrame
3953{
3954    NSInteger groups = ([fDisplayedTorrents count] > 0 && ![[fDisplayedTorrents objectAtIndex: 0] isKindOfClass: [Torrent class]])
3955                    ? [fDisplayedTorrents count] : 0;
3956   
3957    CGFloat heightChange = (GROUP_SEPARATOR_HEIGHT + [fTableView intercellSpacing].height) * groups
3958                        + ([fTableView rowHeight] + [fTableView intercellSpacing].height) * ([fTableView numberOfRows] - groups)
3959                        - NSHeight([[fTableView enclosingScrollView] frame]);
3960   
3961    return [self windowFrameByAddingHeight: heightChange checkLimits: YES];
3962}
3963
3964- (void) updateForAutoSize
3965{
3966    if ([fDefaults boolForKey: @"AutoSize"])
3967        [self setWindowSizeToFit];
3968    else
3969    {
3970        NSSize contentMinSize = [fWindow contentMinSize];
3971        contentMinSize.height = [self minWindowContentSizeAllowed];
3972       
3973        [fWindow setContentMinSize: contentMinSize];
3974       
3975        NSSize contentMaxSize = [fWindow contentMaxSize];
3976        contentMaxSize.height = FLT_MAX;
3977        [fWindow setContentMaxSize: contentMaxSize];
3978    }
3979}
3980
3981- (void) setWindowMinMaxToCurrent
3982{
3983    const CGFloat height = NSHeight([[fWindow contentView] frame]);
3984   
3985    NSSize minSize = [fWindow contentMinSize],
3986            maxSize = [fWindow contentMaxSize];
3987    minSize.height = height;
3988    maxSize.height = height;
3989   
3990    [fWindow setContentMinSize: minSize];
3991    [fWindow setContentMaxSize: maxSize];
3992}
3993
3994- (CGFloat) minWindowContentSizeAllowed
3995{
3996    CGFloat contentMinHeight = NSHeight([[fWindow contentView] frame]) - NSHeight([[fTableView enclosingScrollView] frame])
3997                                + [fTableView rowHeight] + [fTableView intercellSpacing].height;
3998    return contentMinHeight;
3999}
4000
4001- (void) updateForExpandCollape
4002{
4003    [self setWindowSizeToFit];
4004    [self setBottomCountText: YES];
4005}
4006
4007- (void) showMainWindow: (id) sender
4008{
4009    [fWindow makeKeyAndOrderFront: nil];
4010}
4011
4012- (void) windowDidBecomeMain: (NSNotification *) notification
4013{
4014    [fBadger clearCompleted];
4015    [self updateUI];
4016}
4017
4018- (void) applicationWillUnhide: (NSNotification *) notification
4019{
4020    [self updateUI];
4021}
4022
4023- (void) toggleQuickLook: (id) sender
4024{
4025    if (![NSApp isOnSnowLeopardOrBetter])
4026        return;
4027   
4028    if ([[QLPreviewPanelSL sharedPreviewPanel] isVisible])
4029        [[QLPreviewPanelSL sharedPreviewPanel] orderOut: nil];
4030    else
4031        [[QLPreviewPanelSL sharedPreviewPanel] makeKeyAndOrderFront: nil];
4032}
4033
4034- (void) linkHomepage: (id) sender
4035{
4036    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
4037}
4038
4039- (void) linkForums: (id) sender
4040{
4041    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
4042}
4043
4044- (void) linkTrac: (id) sender
4045{
4046    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: TRAC_URL]];
4047}
4048
4049- (void) linkDonate: (id) sender
4050{
4051    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: DONATE_URL]];
4052}
4053
4054- (void) updaterWillRelaunchApplication: (SUUpdater *) updater
4055{
4056    fQuitRequested = YES;
4057}
4058
4059- (NSDictionary *) registrationDictionaryForGrowl
4060{
4061    NSArray * notifications = [NSArray arrayWithObjects: GROWL_DOWNLOAD_COMPLETE, GROWL_SEEDING_COMPLETE,
4062                                                            GROWL_AUTO_ADD, GROWL_AUTO_SPEED_LIMIT, nil];
4063    return [NSDictionary dictionaryWithObjectsAndKeys: notifications, GROWL_NOTIFICATIONS_ALL,
4064                                notifications, GROWL_NOTIFICATIONS_DEFAULT, nil];
4065}
4066
4067- (void) growlNotificationWasClicked: (id) clickContext
4068{
4069    if (!clickContext || ![clickContext isKindOfClass: [NSDictionary class]])
4070        return;
4071   
4072    NSString * type = [clickContext objectForKey: @"Type"], * location;
4073    if (([type isEqualToString: GROWL_DOWNLOAD_COMPLETE] || [type isEqualToString: GROWL_SEEDING_COMPLETE])
4074            && (location = [clickContext objectForKey: @"Location"]))
4075    {
4076        if ([NSApp isOnSnowLeopardOrBetter])
4077        {
4078            NSURL * file = [NSURL fileURLWithPath: location];
4079            [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: [NSArray arrayWithObject: file]];
4080        }
4081        else
4082            [[NSWorkspace sharedWorkspace] selectFile: location inFileViewerRootedAtPath: nil];
4083    }
4084}
4085
4086#warning handle queue state changes
4087- (void) rpcCallback: (tr_rpc_callback_type) type forTorrentStruct: (struct tr_torrent *) torrentStruct
4088{
4089    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
4090   
4091    //get the torrent
4092    Torrent * torrent = nil;
4093    if (torrentStruct != NULL && (type != TR_RPC_TORRENT_ADDED && type != TR_RPC_SESSION_CHANGED && type != TR_RPC_SESSION_CLOSE))
4094    {
4095        for (torrent in fTorrents)
4096            if (torrentStruct == [torrent torrentStruct])
4097            {
4098                [torrent retain];
4099                break;
4100            }
4101       
4102        if (!torrent)
4103        {
4104            [pool drain];
4105           
4106            NSLog(@"No torrent found matching the given torrent struct from the RPC callback!");
4107            return;
4108        }
4109    }
4110   
4111    switch (type)
4112    {
4113        case TR_RPC_TORRENT_ADDED:
4114            [self performSelectorOnMainThread: @selector(rpcAddTorrentStruct:) withObject:
4115                [[NSValue valueWithPointer: torrentStruct] retain] waitUntilDone: NO];
4116            break;
4117       
4118        case TR_RPC_TORRENT_STARTED:
4119        case TR_RPC_TORRENT_STOPPED:
4120            [self performSelectorOnMainThread: @selector(rpcStartedStoppedTorrent:) withObject: torrent waitUntilDone: NO];
4121            break;
4122       
4123        case TR_RPC_TORRENT_REMOVING:
4124            [self performSelectorOnMainThread: @selector(rpcRemoveTorrent:) withObject: torrent waitUntilDone: NO];
4125            break;
4126       
4127        case TR_RPC_TORRENT_TRASHING:
4128            [self performSelectorOnMainThread: @selector(rpcRemoveTorrentDeleteData:) withObject: torrent waitUntilDone: NO];
4129            break;
4130       
4131        case TR_RPC_TORRENT_CHANGED:
4132            [self performSelectorOnMainThread: @selector(rpcChangedTorrent:) withObject: torrent waitUntilDone: NO];
4133            break;
4134       
4135        case TR_RPC_TORRENT_MOVED:
4136            [self performSelectorOnMainThread: @selector(rpcMovedTorrent:) withObject: torrent waitUntilDone: NO];
4137            break;
4138       
4139        case TR_RPC_SESSION_CHANGED:
4140            [fPrefsController performSelectorOnMainThread: @selector(rpcUpdatePrefs) withObject: nil waitUntilDone: NO];
4141            break;
4142       
4143        case TR_RPC_SESSION_CLOSE:
4144            fQuitRequested = YES;
4145            [NSApp performSelectorOnMainThread: @selector(terminate:) withObject: self waitUntilDone: NO];
4146            break;
4147       
4148        default:
4149            NSAssert1(NO, @"Unknown RPC command received: %d", type);
4150            [torrent release];
4151    }
4152   
4153    [pool drain];
4154}
4155
4156- (void) rpcAddTorrentStruct: (NSValue *) torrentStructPtr
4157{
4158    tr_torrent * torrentStruct = (tr_torrent *)[torrentStructPtr pointerValue];
4159    [torrentStructPtr release];
4160   
4161    NSString * location = nil;
4162    if (tr_torrentGetDownloadDir(torrentStruct) != NULL)
4163        location = [NSString stringWithUTF8String: tr_torrentGetDownloadDir(torrentStruct)];
4164   
4165    Torrent * torrent = [[Torrent alloc] initWithTorrentStruct: torrentStruct location: location lib: fLib];
4166   
4167    [torrent update];
4168    [fTorrents addObject: torrent];
4169    [torrent release];
4170   
4171    [self fullUpdateUI];
4172}
4173
4174- (void) rpcRemoveTorrent: (Torrent *) torrent
4175{
4176    [self confirmRemoveTorrents: [[NSArray arrayWithObject: torrent] retain] deleteData: NO];
4177    [torrent release];
4178}
4179
4180- (void) rpcRemoveTorrentDeleteData: (Torrent *) torrent
4181{
4182    [self confirmRemoveTorrents: [[NSArray arrayWithObject: torrent] retain] deleteData: YES];
4183    [torrent release];
4184}
4185
4186- (void) rpcStartedStoppedTorrent: (Torrent *) torrent
4187{
4188    [torrent update];
4189    [torrent release];
4190   
4191    [self updateUI];
4192    [self applyFilter];
4193    [self updateTorrentHistory];
4194}
4195
4196- (void) rpcChangedTorrent: (Torrent *) torrent
4197{
4198    [torrent update];
4199   
4200    if ([[fTableView selectedTorrents] containsObject: torrent])
4201    {
4202        [fInfoController updateInfoStats]; //this will reload the file table
4203        [fInfoController updateOptions];
4204    }
4205   
4206    [torrent release];
4207}
4208
4209- (void) rpcMovedTorrent: (Torrent *) torrent
4210{
4211    [torrent update];
4212    [torrent updateTimeMachineExclude];
4213   
4214    if ([[fTableView selectedTorrents] containsObject: torrent])
4215        [fInfoController updateInfoStats];
4216   
4217    [torrent release];
4218}
4219
4220@end
Note: See TracBrowser for help on using the repository browser.