source: trunk/macosx/Controller.m @ 8227

Last change on this file since 8227 was 8156, checked in by livings124, 13 years ago

save the value of the speed limit day without conversion

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