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