source: trunk/macosx/Controller.m @ 7506

Last change on this file since 7506 was 7506, checked in by livings124, 12 years ago

change the "Use Groups" shortcut to Cmd-G

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