source: trunk/macosx/Controller.m @ 7426

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

get rid of an unnecessary class

  • Property svn:keywords set to Date Rev Author Id
File size: 162.3 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 7426 2008-12-17 02:13:49Z 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_handle * 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 boolForKey: @"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        [fDisplayedTorrents removeAllObjects];
2117       
2118        NSSortDescriptor * groupDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"groupOrderValue" ascending: YES] autorelease];
2119        allTorrents = [allTorrents sortedArrayUsingDescriptors: [NSArray arrayWithObject: groupDescriptor]];
2120       
2121        NSMutableArray * groupTorrents;
2122        for (NSInteger i = 0, oldGroupValue = -2; i < [allTorrents count]; i++)
2123        {
2124            torrent = [allTorrents objectAtIndex: i];
2125            NSInteger groupValue = [torrent groupValue];
2126            if (groupValue != oldGroupValue)
2127            {
2128                TorrentGroup * group = [[TorrentGroup alloc] initWithGroup: groupValue];
2129                groupTorrents = [group torrents];
2130               
2131                [fDisplayedTorrents addObject: group];
2132                [group release];
2133               
2134                oldGroupValue = groupValue;
2135            }
2136           
2137            [groupTorrents addObject: torrent];
2138        }
2139    }
2140    else
2141        [fDisplayedTorrents setArray: allTorrents];
2142   
2143    //actually sort
2144    [self sortTorrentsIgnoreSelected];
2145   
2146    //reset expanded/collapsed rows
2147    if (groupRows)
2148    {
2149        enumerator = [fDisplayedTorrents objectEnumerator];
2150        TorrentGroup * group;
2151        while ((group = [enumerator nextObject]))
2152        {
2153            if ([fTableView isGroupCollapsed: [group groupIndex]])
2154                [fTableView collapseItem: group];
2155            else
2156                [fTableView expandItem: group];
2157        }
2158    }
2159   
2160    [fTableView selectValues: selectedValues];
2161    #warning still happens anyway?
2162    //removed because it made the inspector reset (noticeable when a transfer with web seeds is selected)
2163    //[self resetInfo]; //if group is already selected, but the torrents in it change
2164   
2165    [self setBottomCountText: groupRows || filterStatus || filterGroup || filterText];
2166   
2167    [self setWindowSizeToFit];
2168}
2169
2170//resets filter and sorts torrents
2171- (void) setFilter: (id) sender
2172{
2173    NSString * oldFilterType = [fDefaults stringForKey: @"Filter"];
2174   
2175    NSButton * prevFilterButton;
2176    if ([oldFilterType isEqualToString: FILTER_PAUSE])
2177        prevFilterButton = fPauseFilterButton;
2178    else if ([oldFilterType isEqualToString: FILTER_ACTIVE])
2179        prevFilterButton = fActiveFilterButton;
2180    else if ([oldFilterType isEqualToString: FILTER_SEED])
2181        prevFilterButton = fSeedFilterButton;
2182    else if ([oldFilterType isEqualToString: FILTER_DOWNLOAD])
2183        prevFilterButton = fDownloadFilterButton;
2184    else
2185        prevFilterButton = fNoFilterButton;
2186   
2187    if (sender != prevFilterButton)
2188    {
2189        [prevFilterButton setState: NSOffState];
2190        [sender setState: NSOnState];
2191
2192        NSString * filterType;
2193        if (sender == fActiveFilterButton)
2194            filterType = FILTER_ACTIVE;
2195        else if (sender == fDownloadFilterButton)
2196            filterType = FILTER_DOWNLOAD;
2197        else if (sender == fPauseFilterButton)
2198            filterType = FILTER_PAUSE;
2199        else if (sender == fSeedFilterButton)
2200            filterType = FILTER_SEED;
2201        else
2202            filterType = FILTER_NONE;
2203
2204        [fDefaults setObject: filterType forKey: @"Filter"];
2205    }
2206    else
2207        [sender setState: NSOnState];
2208
2209    [self applyFilter: nil];
2210}
2211
2212- (void) setFilterSearchType: (id) sender
2213{
2214    NSString * oldFilterType = [fDefaults stringForKey: @"FilterSearchType"];
2215   
2216    NSInteger prevTag, currentTag = [sender tag];
2217    if ([oldFilterType isEqualToString: FILTER_TYPE_TRACKER])
2218        prevTag = FILTER_TYPE_TAG_TRACKER;
2219    else
2220        prevTag = FILTER_TYPE_TAG_NAME;
2221   
2222    if (currentTag != prevTag)
2223    {
2224        NSString * filterType;
2225        if (currentTag == FILTER_TYPE_TAG_TRACKER)
2226            filterType = FILTER_TYPE_TRACKER;
2227        else
2228            filterType = FILTER_TYPE_NAME;
2229       
2230        [fDefaults setObject: filterType forKey: @"FilterSearchType"];
2231       
2232        [[fSearchFilterField cell] setPlaceholderString: [sender title]];
2233    }
2234   
2235    [self applyFilter: nil];
2236}
2237
2238- (void) switchFilter: (id) sender
2239{
2240    NSString * filterType = [fDefaults stringForKey: @"Filter"];
2241   
2242    NSButton * button;
2243    if ([filterType isEqualToString: FILTER_NONE])
2244        button = sender == fNextFilterItem ? fActiveFilterButton : fPauseFilterButton;
2245    else if ([filterType isEqualToString: FILTER_ACTIVE])
2246        button = sender == fNextFilterItem ? fDownloadFilterButton : fNoFilterButton;
2247    else if ([filterType isEqualToString: FILTER_DOWNLOAD])
2248        button = sender == fNextFilterItem ? fSeedFilterButton : fActiveFilterButton;
2249    else if ([filterType isEqualToString: FILTER_SEED])
2250        button = sender == fNextFilterItem ? fPauseFilterButton : fDownloadFilterButton;
2251    else if ([filterType isEqualToString: FILTER_PAUSE])
2252        button = sender == fNextFilterItem ? fNoFilterButton : fSeedFilterButton;
2253    else
2254        button = fNoFilterButton;
2255   
2256    [self setFilter: button];
2257}
2258
2259- (void) setStatusLabel: (id) sender
2260{
2261    NSString * statusLabel;
2262    switch ([sender tag])
2263    {
2264        case STATUS_RATIO_TOTAL_TAG:
2265            statusLabel = STATUS_RATIO_TOTAL;
2266            break;
2267        case STATUS_RATIO_SESSION_TAG:
2268            statusLabel = STATUS_RATIO_SESSION;
2269            break;
2270        case STATUS_TRANSFER_TOTAL_TAG:
2271            statusLabel = STATUS_TRANSFER_TOTAL;
2272            break;
2273        case STATUS_TRANSFER_SESSION_TAG:
2274            statusLabel = STATUS_TRANSFER_SESSION;
2275            break;
2276        default:
2277            return;
2278    }
2279   
2280    [fDefaults setObject: statusLabel forKey: @"StatusLabel"];
2281    [self updateUI];
2282}
2283
2284- (void) menuNeedsUpdate: (NSMenu *) menu
2285{
2286    if (menu == fGroupsSetMenu || menu == fGroupsSetContextMenu)
2287    {
2288        for (NSInteger i = [menu numberOfItems]-1; i >= 0; i--)
2289            [menu removeItemAtIndex: i];
2290       
2291        NSMenu * groupMenu = [[GroupsController groups] groupMenuWithTarget: self action: @selector(setGroup:) isSmall: NO];
2292        const NSInteger groupMenuCount = [groupMenu numberOfItems];
2293        for (NSInteger i = 0; i < groupMenuCount; i++)
2294        {
2295            NSMenuItem * item = [[groupMenu itemAtIndex: 0] retain];
2296            [groupMenu removeItemAtIndex: 0];
2297            [menu addItem: item];
2298            [item release];
2299        }
2300    }
2301    else if (menu == fGroupFilterMenu)
2302    {
2303        for (NSInteger i = [menu numberOfItems]-1; i >= 3; i--)
2304            [menu removeItemAtIndex: i];
2305       
2306        NSMenu * groupMenu = [[GroupsController groups] groupMenuWithTarget: self action: @selector(setGroupFilter:)
2307                                isSmall: YES];
2308        const NSInteger groupMenuCount = [groupMenu numberOfItems];
2309        for (NSInteger i = 0; i < groupMenuCount; i++)
2310        {
2311            NSMenuItem * item = [[groupMenu itemAtIndex: 0] retain];
2312            [groupMenu removeItemAtIndex: 0];
2313            [menu addItem: item];
2314            [item release];
2315        }
2316    }
2317    else if (menu == fUploadMenu || menu == fDownloadMenu)
2318    {
2319        if ([menu numberOfItems] > 3)
2320            return;
2321       
2322        const NSInteger speedLimitActionValue[] = { 5, 10, 20, 30, 40, 50, 75, 100, 150, 200, 250, 500, 750, -1 };
2323       
2324        NSMenuItem * item;
2325        for (NSInteger i = 0; speedLimitActionValue[i] != -1; i++)
2326        {
2327            item = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: NSLocalizedString(@"%d KB/s",
2328                    "Action menu -> upload/download limit"), speedLimitActionValue[i]] action: @selector(setQuickLimitGlobal:)
2329                    keyEquivalent: @""];
2330            [item setTarget: self];
2331            [item setRepresentedObject: [NSNumber numberWithInt: speedLimitActionValue[i]]];
2332            [menu addItem: item];
2333            [item release];
2334        }
2335    }
2336    else if (menu == fRatioStopMenu)
2337    {
2338        if ([menu numberOfItems] > 3)
2339            return;
2340       
2341        const float ratioLimitActionValue[] = { 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, -1 };
2342       
2343        NSMenuItem * item;
2344        for (NSInteger i = 0; ratioLimitActionValue[i] != -1; i++)
2345        {
2346            item = [[NSMenuItem alloc] initWithTitle: [NSString localizedStringWithFormat: @"%.2f", ratioLimitActionValue[i]]
2347                    action: @selector(setQuickRatioGlobal:) keyEquivalent: @""];
2348            [item setTarget: self];
2349            [item setRepresentedObject: [NSNumber numberWithFloat: ratioLimitActionValue[i]]];
2350            [menu addItem: item];
2351            [item release];
2352        }
2353    }
2354    else;
2355}
2356
2357- (void) setGroup: (id) sender
2358{
2359    NSEnumerator * enumerator = [[fTableView selectedTorrents] objectEnumerator];
2360    Torrent * torrent;
2361    while ((torrent = [enumerator nextObject]))
2362    {
2363        [fTableView removeCollapsedGroup: [torrent groupValue]]; //remove old collapsed group
2364       
2365        [torrent setGroupValue: [sender tag]];
2366    }
2367   
2368    [self applyFilter: nil];
2369    [self updateUI];
2370    [self updateTorrentHistory];
2371}
2372
2373- (void) setGroupFilter: (id) sender
2374{
2375    [fDefaults setInteger: [sender tag] forKey: @"FilterGroup"];
2376    [self updateGroupsFilterButton];
2377    [self applyFilter: nil];
2378}
2379
2380- (void) updateGroupsFilterButton
2381{
2382    NSInteger groupIndex = [fDefaults integerForKey: @"FilterGroup"];
2383   
2384    NSImage * icon;
2385    NSString * toolTip;
2386    if (groupIndex == GROUP_FILTER_ALL_TAG)
2387    {
2388        icon = [NSImage imageNamed: @"PinTemplate.png"];
2389        toolTip = NSLocalizedString(@"All Groups", "Groups -> Button");
2390    }
2391    else
2392    {
2393        icon = [[GroupsController groups] imageForIndex: groupIndex];
2394        NSString * groupName = groupIndex != -1 ? [[GroupsController groups] nameForIndex: groupIndex]
2395                                                : NSLocalizedString(@"None", "Groups -> Button");
2396        toolTip = [NSLocalizedString(@"Group", "Groups -> Button") stringByAppendingFormat: @": %@", groupName];
2397    }
2398   
2399    //tiger doesn't have built-in image scaling in buttons
2400    if (![NSApp isOnLeopardOrBetter])
2401    {
2402        icon = [[icon copy] autorelease];
2403        [icon setScalesWhenResized: YES];
2404        [icon setSize: NSMakeSize(12.0, 12.0)];
2405    }
2406   
2407    [[fGroupFilterMenu itemAtIndex: 0] setImage: icon];
2408    [fGroupsButton setToolTip: toolTip];
2409}
2410
2411- (void) updateGroupsFilters: (NSNotification *) notification
2412{
2413    [self updateGroupsFilterButton];
2414    [self applyFilter: nil];
2415}
2416
2417- (void) toggleSpeedLimit: (id) sender
2418{
2419    [fDefaults setBool: ![fDefaults boolForKey: @"SpeedLimit"] forKey: @"SpeedLimit"];
2420    [fPrefsController applySpeedSettings: nil];
2421}
2422
2423- (void) autoSpeedLimitChange: (NSNotification *) notification
2424{
2425    //clear timer here in case it's not being reset
2426    [fSpeedLimitTimer invalidate];
2427    fSpeedLimitTimer = nil;
2428   
2429    if (![fDefaults boolForKey: @"SpeedLimitAuto"])
2430        return;
2431   
2432    NSCalendar * calendar = [NSCalendar currentCalendar];
2433    NSDateComponents * nowComponents = [calendar components: NSHourCalendarUnit | NSMinuteCalendarUnit fromDate: [NSDate date]],
2434                    * onComponents = [calendar components: NSHourCalendarUnit | NSMinuteCalendarUnit
2435                                        fromDate: [fDefaults objectForKey: @"SpeedLimitAutoOnDate"]],
2436                    * offComponents = [calendar components: NSHourCalendarUnit | NSMinuteCalendarUnit
2437                                        fromDate: [fDefaults objectForKey: @"SpeedLimitAutoOffDate"]];
2438   
2439    //check if should be on if within range
2440    NSInteger onTime = [onComponents hour] * 60 + [onComponents minute],
2441        offTime = [offComponents hour] * 60 + [offComponents minute],
2442        nowTime = [nowComponents hour] * 60 + [nowComponents minute];
2443   
2444    BOOL shouldBeOn = NO;
2445    if (onTime < offTime)
2446        shouldBeOn = onTime <= nowTime && nowTime < offTime;
2447    else if (onTime > offTime)
2448        shouldBeOn = onTime <= nowTime || nowTime < offTime;
2449    else;
2450   
2451    if ([fDefaults boolForKey: @"SpeedLimit"] != shouldBeOn)
2452        [self toggleSpeedLimit: nil];
2453   
2454    //no need to set the timer if both times are equal
2455    if (onTime == offTime)
2456        return;
2457   
2458    [self setAutoSpeedLimitTimer: !shouldBeOn];
2459}
2460
2461//only called by fSpeedLimitTimer
2462- (void) autoSpeedLimit: (NSTimer *) timer
2463{
2464    BOOL shouldLimit = [[timer userInfo] boolValue];
2465   
2466    if ([fDefaults boolForKey: @"SpeedLimit"] != shouldLimit)
2467    {
2468        [self toggleSpeedLimit: nil];
2469       
2470        [GrowlApplicationBridge notifyWithTitle: [fDefaults boolForKey: @"SpeedLimit"]
2471                ? NSLocalizedString(@"Speed Limit Auto Enabled", "Growl notification title")
2472                : NSLocalizedString(@"Speed Limit Auto Disabled", "Growl notification title")
2473            description: NSLocalizedString(@"Bandwidth settings changed", "Growl notification description")
2474            notificationName: GROWL_AUTO_SPEED_LIMIT iconData: nil priority: 0 isSticky: NO clickContext: nil];
2475    }
2476   
2477    [self setAutoSpeedLimitTimer: !shouldLimit];
2478}
2479
2480- (void) setAutoSpeedLimitTimer: (BOOL) nextIsLimit
2481{
2482    NSDate * timerDate = [fDefaults objectForKey: nextIsLimit ? @"SpeedLimitAutoOnDate" : @"SpeedLimitAutoOffDate"];
2483   
2484    //create date with combination of the current date and the date to go off
2485    NSCalendar * calendar = [NSCalendar currentCalendar];
2486    NSDateComponents * nowComponents = [calendar components: NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit
2487                                        | NSHourCalendarUnit | NSMinuteCalendarUnit fromDate: [NSDate date]],
2488                    * timerComponents = [calendar components: NSHourCalendarUnit | NSMinuteCalendarUnit fromDate: timerDate];
2489   
2490    //check if should be the next day
2491    NSInteger nowTime = [nowComponents hour] * 60 + [nowComponents minute],
2492        timerTime = [timerComponents hour] * 60 + [timerComponents minute];
2493    if (timerTime < nowTime)
2494        [nowComponents setDay: [nowComponents day] + 1]; //properly goes to next month when appropriate
2495   
2496    [nowComponents setHour: [timerComponents hour]];
2497    [nowComponents setMinute: [timerComponents minute]];
2498    [nowComponents setSecond: 0];
2499   
2500    NSDate * dateToUse = [calendar dateFromComponents: nowComponents];
2501   
2502    fSpeedLimitTimer = [[NSTimer alloc] initWithFireDate: dateToUse interval: 0 target: self selector: @selector(autoSpeedLimit:)
2503                        userInfo: [NSNumber numberWithBool: nextIsLimit] repeats: NO];
2504   
2505    NSRunLoop * loop = [NSApp isOnLeopardOrBetter] ? [NSRunLoop mainRunLoop] : [NSRunLoop currentRunLoop];
2506    [loop addTimer: fSpeedLimitTimer forMode: NSDefaultRunLoopMode];
2507    [loop addTimer: fSpeedLimitTimer forMode: NSModalPanelRunLoopMode];
2508    [loop addTimer: fSpeedLimitTimer forMode: NSEventTrackingRunLoopMode];
2509    [fSpeedLimitTimer release];
2510}
2511
2512- (void) setLimitGlobalEnabled: (id) sender
2513{
2514    BOOL upload = [sender menu] == fUploadMenu;
2515    [fDefaults setBool: sender == (upload ? fUploadLimitItem : fDownloadLimitItem) forKey: upload ? @"CheckUpload" : @"CheckDownload"];
2516   
2517    [fPrefsController applySpeedSettings: nil];
2518}
2519
2520- (void) setQuickLimitGlobal: (id) sender
2521{
2522    BOOL upload = [sender menu] == fUploadMenu;
2523    [fDefaults setInteger: [[sender representedObject] intValue] forKey: upload ? @"UploadLimit" : @"DownloadLimit"];
2524    [fDefaults setBool: YES forKey: upload ? @"CheckUpload" : @"CheckDownload"];
2525   
2526    [fPrefsController updateLimitFields];
2527    [fPrefsController applySpeedSettings: nil];
2528}
2529
2530- (void) setRatioGlobalEnabled: (id) sender
2531{
2532    [fDefaults setBool: sender == fCheckRatioItem forKey: @"RatioCheck"];
2533}
2534
2535- (void) setQuickRatioGlobal: (id) sender
2536{
2537    [fDefaults setBool: YES forKey: @"RatioCheck"];
2538    [fDefaults setFloat: [[sender representedObject] floatValue] forKey: @"RatioLimit"];
2539   
2540    [fPrefsController updateRatioStopField];
2541}
2542
2543- (void) sound: (NSSound *) sound didFinishPlaying: (BOOL) finishedPlaying
2544{
2545    fSoundPlaying = NO;
2546}
2547
2548- (void) watcher: (id<UKFileWatcher>) watcher receivedNotification: (NSString *) notification forPath: (NSString *) path
2549{
2550    if ([notification isEqualToString: UKFileWatcherWriteNotification])
2551    {
2552        if (![fDefaults boolForKey: @"AutoImport"] || ![fDefaults stringForKey: @"AutoImportDirectory"])
2553            return;
2554       
2555        if (fAutoImportTimer)
2556        {
2557            if ([fAutoImportTimer isValid])
2558                [fAutoImportTimer invalidate];
2559            [fAutoImportTimer release];
2560            fAutoImportTimer = nil;
2561        }
2562       
2563        //check again in 10 seconds in case torrent file wasn't complete
2564        fAutoImportTimer = [[NSTimer scheduledTimerWithTimeInterval: 10.0 target: self
2565            selector: @selector(checkAutoImportDirectory) userInfo: nil repeats: NO] retain];
2566       
2567        [self checkAutoImportDirectory];
2568    }
2569}
2570
2571- (void) changeAutoImport
2572{
2573    if (fAutoImportTimer)
2574    {
2575        if ([fAutoImportTimer isValid])
2576            [fAutoImportTimer invalidate];
2577        [fAutoImportTimer release];
2578        fAutoImportTimer = nil;
2579    }
2580   
2581    if (fAutoImportedNames)
2582    {
2583        [fAutoImportedNames release];
2584        fAutoImportedNames = nil;
2585    }
2586   
2587    [self checkAutoImportDirectory];
2588}
2589
2590- (void) checkAutoImportDirectory
2591{
2592    NSString * path;
2593    if (![fDefaults boolForKey: @"AutoImport"] || !(path = [fDefaults stringForKey: @"AutoImportDirectory"]))
2594        return;
2595   
2596    path = [path stringByExpandingTildeInPath];
2597   
2598    NSArray * importedNames;
2599    if (!(importedNames = [[NSFileManager defaultManager] directoryContentsAtPath: path]))
2600        return;
2601   
2602    //only check files that have not been checked yet
2603    NSMutableArray * newNames = [importedNames mutableCopy];
2604   
2605    if (fAutoImportedNames)
2606        [newNames removeObjectsInArray: fAutoImportedNames];
2607    else
2608        fAutoImportedNames = [[NSMutableArray alloc] init];
2609    [fAutoImportedNames setArray: importedNames];
2610   
2611    NSString * file;
2612    for (NSInteger i = [newNames count] - 1; i >= 0; i--)
2613    {
2614        file = [newNames objectAtIndex: i];
2615        if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] != NSOrderedSame)
2616            [newNames removeObjectAtIndex: i];
2617        else
2618            [newNames replaceObjectAtIndex: i withObject: [path stringByAppendingPathComponent: file]];
2619    }
2620   
2621    NSEnumerator * enumerator = [newNames objectEnumerator];
2622    tr_ctor * ctor;
2623    while ((file = [enumerator nextObject]))
2624    {
2625        ctor = tr_ctorNew(fLib);
2626        tr_ctorSetMetainfoFromFile(ctor, [file UTF8String]);
2627       
2628        switch (tr_torrentParse(fLib, ctor, NULL))
2629        {
2630            case TR_OK:
2631                [self openFiles: [NSArray arrayWithObject: file] addType: ADD_AUTO forcePath: nil];
2632               
2633                [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Torrent File Auto Added", "Growl notification title")
2634                    description: [file lastPathComponent] notificationName: GROWL_AUTO_ADD iconData: nil priority: 0 isSticky: NO
2635                    clickContext: nil];
2636                break;
2637           
2638            case TR_EINVALID:
2639                [fAutoImportedNames removeObject: [file lastPathComponent]];
2640        }
2641       
2642        tr_ctorFree(ctor);
2643    }
2644   
2645    [newNames release];
2646}
2647
2648- (void) beginCreateFile: (NSNotification *) notification
2649{
2650    if (![fDefaults boolForKey: @"AutoImport"])
2651        return;
2652   
2653    NSString * location = [notification object],
2654            * path = [fDefaults stringForKey: @"AutoImportDirectory"];
2655   
2656    if (location && path && [[[location stringByDeletingLastPathComponent] stringByExpandingTildeInPath]
2657                                    isEqualToString: [path stringByExpandingTildeInPath]])
2658        [fAutoImportedNames addObject: [location lastPathComponent]];
2659}
2660
2661- (NSInteger) outlineView: (NSOutlineView *) outlineView numberOfChildrenOfItem: (id) item
2662{
2663    if (item)
2664        return [[item torrents] count];
2665    else
2666        return [fDisplayedTorrents count];
2667}
2668
2669- (id) outlineView: (NSOutlineView *) outlineView child: (NSInteger) index ofItem: (id) item
2670{
2671    if (item)
2672        return [[item torrents] objectAtIndex: index];
2673    else
2674        return [fDisplayedTorrents objectAtIndex: index];
2675}
2676
2677- (BOOL) outlineView: (NSOutlineView *) outlineView isItemExpandable: (id) item
2678{
2679    return ![item isKindOfClass: [Torrent class]];
2680}
2681
2682- (id) outlineView: (NSOutlineView *) outlineView objectValueForTableColumn: (NSTableColumn *) tableColumn byItem: (id) item
2683{
2684    if ([item isKindOfClass: [Torrent class]])
2685        return [item hashString];
2686    else
2687    {
2688        NSString * ident = [tableColumn identifier];
2689        if ([ident isEqualToString: @"Group"])
2690        {
2691            NSInteger group = [item groupIndex];
2692            return group != -1 ? [[GroupsController groups] nameForIndex: group]
2693                                : NSLocalizedString(@"No Group", "Group table row");
2694        }
2695        else if ([ident isEqualToString: @"Color"])
2696        {
2697            NSInteger group = [item groupIndex];
2698            return [[GroupsController groups] imageForIndex: group];
2699        }
2700        else if ([ident isEqualToString: @"DL Image"])
2701            return [NSImage imageNamed: @"DownArrowGroupTemplate.png"];
2702        else if ([ident isEqualToString: @"UL Image"])
2703            return [NSImage imageNamed: [fDefaults boolForKey: @"DisplayGroupRowRatio"]
2704                                        ? @"YingYangGroupTemplate.png" : @"UpArrowGroupTemplate.png"];
2705        else
2706        {
2707            TorrentGroup * group = (TorrentGroup *)item;
2708           
2709            if ([fDefaults boolForKey: @"DisplayGroupRowRatio"])
2710                return [NSString stringForRatio: [group ratio]];
2711            else
2712            {
2713                CGFloat rate = [ident isEqualToString: @"UL"] ? [group uploadRate] : [group downloadRate];
2714                return [NSString stringForSpeed: rate];
2715            }
2716        }
2717    }
2718}
2719
2720- (BOOL) outlineView: (NSOutlineView *) outlineView writeItems: (NSArray *) items toPasteboard: (NSPasteboard *) pasteboard
2721{
2722    //only allow reordering of rows if sorting by order
2723    if (([fDefaults boolForKey: @"SortByGroup"] && [NSApp isOnLeopardOrBetter])
2724        || [[fDefaults stringForKey: @"Sort"] isEqualToString: SORT_ORDER])
2725    {
2726        NSMutableIndexSet * indexSet = [NSMutableIndexSet indexSet];
2727        NSEnumerator * enumerator = [items objectEnumerator];
2728        id torrent;
2729        while ((torrent = [enumerator nextObject]))
2730        {
2731            if (![torrent isKindOfClass: [Torrent class]])
2732                return NO;
2733           
2734            [indexSet addIndex: [fTableView rowForItem: torrent]];
2735        }
2736       
2737        [pasteboard declareTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE] owner: self];
2738        [pasteboard setData: [NSKeyedArchiver archivedDataWithRootObject: indexSet] forType: TORRENT_TABLE_VIEW_DATA_TYPE];
2739        return YES;
2740    }
2741    return NO;
2742}
2743
2744- (NSDragOperation) outlineView: (NSOutlineView *) outlineView validateDrop: (id < NSDraggingInfo >) info proposedItem: (id) item
2745    proposedChildIndex: (NSInteger) index
2746{
2747    NSPasteboard * pasteboard = [info draggingPasteboard];
2748    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2749    {
2750        if ([fDefaults boolForKey: @"SortByGroup"])
2751        {
2752            if (!item)
2753                return NSDragOperationNone;
2754           
2755            if ([[fDefaults stringForKey: @"Sort"] isEqualToString: SORT_ORDER])
2756            {
2757                if ([item isKindOfClass: [Torrent class]])
2758                {
2759                    TorrentGroup * group = [fTableView parentForItem: item];
2760                    index = [[group torrents] indexOfObject: item] + 1;
2761                    item = group;
2762                }
2763            }
2764            else
2765            {
2766                if ([item isKindOfClass: [Torrent class]])
2767                    item = [fTableView parentForItem: item];
2768                index = NSOutlineViewDropOnItemIndex;
2769            }
2770        }
2771        else
2772        {
2773            if (item)
2774            {
2775                index = [fTableView rowForItem: item] + 1;
2776                item = nil;
2777            }
2778        }
2779       
2780        [fTableView setDropItem: item dropChildIndex: index];
2781        return NSDragOperationGeneric;
2782    }
2783   
2784    return NSDragOperationNone;
2785}
2786
2787- (BOOL) outlineView: (NSOutlineView *) outlineView acceptDrop: (id < NSDraggingInfo >) info item: (id) item
2788    childIndex: (NSInteger) newRow
2789{
2790    NSPasteboard * pasteboard = [info draggingPasteboard];
2791    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2792    {
2793        //remember selected rows
2794        NSArray * selectedValues = [fTableView selectedValues];
2795   
2796        NSIndexSet * indexes = [NSKeyedUnarchiver unarchiveObjectWithData: [pasteboard dataForType: TORRENT_TABLE_VIEW_DATA_TYPE]];
2797       
2798        //get the torrents to move
2799        NSMutableArray * movingTorrents = [NSMutableArray arrayWithCapacity: [indexes count]];
2800        for (NSUInteger i = [indexes firstIndex]; i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
2801            [movingTorrents addObject: [fTableView itemAtRow: i]];
2802       
2803        //reset groups
2804        if (item)
2805        {
2806            //change groups
2807            NSInteger groupValue = [item groupIndex];
2808            NSEnumerator * enumerator = [movingTorrents objectEnumerator];
2809            Torrent * torrent;
2810            while ((torrent = [enumerator nextObject]))
2811            {
2812                //have to reset objects here to avoid weird crash
2813                [[[fTableView parentForItem: torrent] torrents] removeObject: torrent];
2814                [[item torrents] addObject: torrent];
2815               
2816                [torrent setGroupValue: groupValue];
2817            }
2818            //part 2 of avoiding weird crash
2819            [fTableView reloadItem: nil reloadChildren: YES];
2820        }
2821       
2822        //reorder queue order
2823        if (newRow != NSOutlineViewDropOnItemIndex)
2824        {
2825            //find torrent to place under
2826            NSArray * groupTorrents = item ? [item torrents] : fDisplayedTorrents;
2827            Torrent * topTorrent = nil;
2828            for (NSInteger i = newRow-1; i >= 0; i--)
2829            {
2830                Torrent * tempTorrent = [groupTorrents objectAtIndex: i];
2831                if (![movingTorrents containsObject: tempTorrent])
2832                {
2833                    topTorrent = tempTorrent;
2834                    break;
2835                }
2836            }
2837           
2838            //remove objects to reinsert
2839            [fTorrents removeObjectsInArray: movingTorrents];
2840           
2841            //insert objects at new location
2842            NSUInteger insertIndex = topTorrent ? [fTorrents indexOfObject: topTorrent] + 1 : 0;
2843            NSIndexSet * insertIndexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(insertIndex, [movingTorrents count])];
2844            [fTorrents insertObjects: movingTorrents atIndexes: insertIndexes];
2845           
2846            //redo order values
2847            for (NSInteger i = 0; i < [fTorrents count]; i++)
2848                [[fTorrents objectAtIndex: i] setOrderValue: i];
2849        }
2850       
2851        [self applyFilter: nil];
2852        [fTableView selectValues: selectedValues];
2853    }
2854   
2855    return YES;
2856}
2857
2858- (void) torrentTableViewSelectionDidChange: (NSNotification *) notification
2859{
2860    [self resetInfo];
2861}
2862
2863- (NSDragOperation) draggingEntered: (id <NSDraggingInfo>) info
2864{
2865    NSPasteboard * pasteboard = [info draggingPasteboard];
2866    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2867    {
2868        //check if any torrent files can be added
2869        NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
2870        NSEnumerator * enumerator = [files objectEnumerator];
2871        NSString * file;
2872        BOOL torrent = NO;
2873        tr_ctor * ctor;
2874        while ((file = [enumerator nextObject]))
2875        {
2876            if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame)
2877            {
2878                ctor = tr_ctorNew(fLib);
2879                tr_ctorSetMetainfoFromFile(ctor, [file UTF8String]);
2880                switch (tr_torrentParse(fLib, ctor, NULL))
2881                {
2882                    case TR_OK:
2883                        if (!fOverlayWindow)
2884                            fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2885                        [fOverlayWindow setTorrents: files];
2886                       
2887                        return NSDragOperationCopy;
2888                   
2889                    case TR_EDUPLICATE:
2890                        torrent = YES;
2891                }
2892                tr_ctorFree(ctor);
2893            }
2894        }
2895       
2896        //create a torrent file if a single file
2897        if (!torrent && [files count] == 1)
2898        {
2899            if (!fOverlayWindow)
2900                fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2901            [fOverlayWindow setFile: [[files objectAtIndex: 0] lastPathComponent]];
2902           
2903            return NSDragOperationCopy;
2904        }
2905    }
2906    else if ([[pasteboard types] containsObject: NSURLPboardType])
2907    {
2908        if (!fOverlayWindow)
2909            fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2910        [fOverlayWindow setURL: [[NSURL URLFromPasteboard: pasteboard] relativeString]];
2911       
2912        return NSDragOperationCopy;
2913    }
2914    else;
2915   
2916    return NSDragOperationNone;
2917}
2918
2919- (void) draggingExited: (id <NSDraggingInfo>) info
2920{
2921    if (fOverlayWindow)
2922        [fOverlayWindow fadeOut];
2923}
2924
2925- (BOOL) performDragOperation: (id <NSDraggingInfo>) info
2926{
2927    if (fOverlayWindow)
2928        [fOverlayWindow fadeOut];
2929   
2930    NSPasteboard * pasteboard = [info draggingPasteboard];
2931    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2932    {
2933        BOOL torrent = NO, accept = YES;
2934       
2935        //create an array of files that can be opened
2936        NSMutableArray * filesToOpen = [[NSMutableArray alloc] init];
2937        NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
2938        NSEnumerator * enumerator = [files objectEnumerator];
2939        NSString * file;
2940        tr_ctor * ctor;
2941        while ((file = [enumerator nextObject]))
2942        {
2943            if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame)
2944            {
2945                ctor = tr_ctorNew(fLib);
2946                tr_ctorSetMetainfoFromFile(ctor, [file UTF8String]);
2947                switch (tr_torrentParse(fLib, ctor, NULL))
2948                {
2949                    case TR_OK:
2950                        [filesToOpen addObject: file];
2951                        torrent = YES;
2952                        break;
2953                       
2954                    case TR_EDUPLICATE:
2955                        torrent = YES;
2956                }
2957                tr_ctorFree(ctor);
2958            }
2959        }
2960       
2961        if ([filesToOpen count] > 0)
2962            [self application: NSApp openFiles: filesToOpen];
2963        else
2964        {
2965            if (!torrent && [files count] == 1)
2966                [CreatorWindowController createTorrentFile: fLib forFile: [files objectAtIndex: 0]];
2967            else
2968                accept = NO;
2969        }
2970        [filesToOpen release];
2971       
2972        return accept;
2973    }
2974    else if ([[pasteboard types] containsObject: NSURLPboardType])
2975    {
2976        NSURL * url;
2977        if ((url = [NSURL URLFromPasteboard: pasteboard]))
2978        {
2979            [self openURL: url];
2980            return YES;
2981        }
2982    }
2983    else;
2984   
2985    return NO;
2986}
2987
2988- (void) toggleSmallView: (id) sender
2989{
2990    BOOL makeSmall = ![fDefaults boolForKey: @"SmallView"];
2991    [fDefaults setBool: makeSmall forKey: @"SmallView"];
2992   
2993    [fTableView setRowHeight: makeSmall ? ROW_HEIGHT_SMALL : ROW_HEIGHT_REGULAR];
2994   
2995    [fTableView noteHeightOfRowsWithIndexesChanged: [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [fTableView numberOfRows])]];
2996   
2997    //window min height
2998    NSSize contentMinSize = [fWindow contentMinSize],
2999            contentSize = [[fWindow contentView] frame].size;
3000    contentMinSize.height = contentSize.height - [[fTableView enclosingScrollView] frame].size.height
3001                            + [fTableView rowHeight] + [fTableView intercellSpacing].height;
3002    [fWindow setContentMinSize: contentMinSize];
3003   
3004    //resize for larger min height if not set to auto size
3005    if (![fDefaults boolForKey: @"AutoSize"])
3006    {
3007        if (!makeSmall && contentSize.height < contentMinSize.height)
3008        {
3009            NSRect frame = [fWindow frame];
3010            CGFloat heightChange = contentMinSize.height - contentSize.height;
3011            frame.size.height += heightChange;
3012            frame.origin.y -= heightChange;
3013           
3014            [fWindow setFrame: frame display: YES];
3015        }
3016    }
3017    else
3018        [self setWindowSizeToFit];
3019}
3020
3021- (void) togglePiecesBar: (id) sender
3022{
3023    [fDefaults setBool: ![fDefaults boolForKey: @"PiecesBar"] forKey: @"PiecesBar"];
3024    [fTableView togglePiecesBar];
3025}
3026
3027- (void) toggleAvailabilityBar: (id) sender
3028{
3029    [fDefaults setBool: ![fDefaults boolForKey: @"DisplayProgressBarAvailable"] forKey: @"DisplayProgressBarAvailable"];
3030    [fTableView display];
3031}
3032
3033- (void) toggleStatusString: (id) sender
3034{
3035    if ([fDefaults boolForKey: @"SmallView"])
3036        [fDefaults setBool: ![fDefaults boolForKey: @"DisplaySmallStatusRegular"] forKey: @"DisplaySmallStatusRegular"];
3037    else
3038        [fDefaults setBool: ![fDefaults boolForKey: @"DisplayStatusProgressSelected"] forKey: @"DisplayStatusProgressSelected"];
3039   
3040    [fTableView reloadData];
3041}
3042
3043- (NSRect) windowFrameByAddingHeight: (CGFloat) height checkLimits: (BOOL) check
3044{
3045    NSScrollView * scrollView = [fTableView enclosingScrollView];
3046   
3047    //convert pixels to points
3048    NSRect windowFrame = [fWindow frame];
3049    NSSize windowSize = [scrollView convertSize: windowFrame.size fromView: nil];
3050    windowSize.height += height;
3051   
3052    if (check)
3053    {
3054        NSSize minSize = [scrollView convertSize: [fWindow minSize] fromView: nil];
3055       
3056        if (windowSize.height < minSize.height)
3057            windowSize.height = minSize.height;
3058        else
3059        {
3060            NSSize maxSize = [scrollView convertSize: [[fWindow screen] visibleFrame].size fromView: nil];
3061            if ([fStatusBar isHidden])
3062                maxSize.height -= [fStatusBar frame].size.height;
3063            if ([fFilterBar isHidden])
3064                maxSize.height -= [fFilterBar frame].size.height;
3065            if (windowSize.height > maxSize.height)
3066                windowSize.height = maxSize.height;
3067        }
3068    }
3069
3070    //convert points to pixels
3071    windowSize = [scrollView convertSize: windowSize toView: nil];
3072
3073    windowFrame.origin.y -= (windowSize.height - windowFrame.size.height);
3074    windowFrame.size.height = windowSize.height;
3075    return windowFrame;
3076}
3077
3078- (void) toggleStatusBar: (id) sender
3079{
3080    [self showStatusBar: [fStatusBar isHidden] animate: YES];
3081    [fDefaults setBool: ![fStatusBar isHidden] forKey: @"StatusBar"];
3082}
3083
3084//doesn't save shown state
3085- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
3086{
3087    if (show != [fStatusBar isHidden])
3088        return;
3089
3090    if (show)
3091        [fStatusBar setHidden: NO];
3092
3093    NSRect frame;
3094    CGFloat heightChange = [fStatusBar frame].size.height;
3095    if (!show)
3096        heightChange *= -1;
3097   
3098    //allow bar to show even if not enough room
3099    if (show && ![fDefaults boolForKey: @"AutoSize"])
3100    {
3101        frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
3102        CGFloat change = [[fWindow screen] visibleFrame].size.height - frame.size.height;
3103        if (change < 0.0)
3104        {
3105            frame = [fWindow frame];
3106            frame.size.height += change;
3107            frame.origin.y -= change;
3108            [fWindow setFrame: frame display: NO animate: NO];
3109        }
3110    }
3111
3112    [self updateUI];
3113   
3114    NSScrollView * scrollView = [fTableView enclosingScrollView];
3115   
3116    //set views to not autoresize
3117    NSUInteger statsMask = [fStatusBar autoresizingMask];
3118    NSUInteger filterMask = [fFilterBar autoresizingMask];
3119    NSUInteger scrollMask = [scrollView autoresizingMask];
3120    [fStatusBar setAutoresizingMask: NSViewNotSizable];
3121    [fFilterBar setAutoresizingMask: NSViewNotSizable];
3122    [scrollView setAutoresizingMask: NSViewNotSizable];
3123   
3124    frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
3125    [fWindow setFrame: frame display: YES animate: animate];
3126   
3127    //re-enable autoresize
3128    [fStatusBar setAutoresizingMask: statsMask];
3129    [fFilterBar setAutoresizingMask: filterMask];
3130    [scrollView setAutoresizingMask: scrollMask];
3131   
3132    //change min size
3133    NSSize minSize = [fWindow contentMinSize];
3134    minSize.height += heightChange;
3135    [fWindow setContentMinSize: minSize];
3136   
3137    if (!show)
3138        [fStatusBar setHidden: YES];
3139}
3140
3141- (void) toggleFilterBar: (id) sender
3142{
3143    //disable filtering when hiding
3144    if (![fFilterBar isHidden])
3145    {
3146        [fSearchFilterField setStringValue: @""];
3147        [self setFilter: fNoFilterButton];
3148        [self setGroupFilter: [fGroupFilterMenu itemWithTag: GROUP_FILTER_ALL_TAG]];
3149    }
3150
3151    [self showFilterBar: [fFilterBar isHidden] animate: YES];
3152    [fDefaults setBool: ![fFilterBar isHidden] forKey: @"FilterBar"];
3153}
3154
3155//doesn't save shown state
3156- (void) showFilterBar: (BOOL) show animate: (BOOL) animate
3157{
3158    if (show != [fFilterBar isHidden])
3159        return;
3160
3161    if (show)
3162        [fFilterBar setHidden: NO];
3163
3164    NSRect frame;
3165    CGFloat heightChange = [fFilterBar frame].size.height;
3166    if (!show)
3167        heightChange *= -1;
3168   
3169    //allow bar to show even if not enough room
3170    if (show && ![fDefaults boolForKey: @"AutoSize"])
3171    {
3172        frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
3173        CGFloat change = [[fWindow screen] visibleFrame].size.height - frame.size.height;
3174        if (change < 0.0)
3175        {
3176            frame = [fWindow frame];
3177            frame.size.height += change;
3178            frame.origin.y -= change;
3179            [fWindow setFrame: frame display: NO animate: NO];
3180        }
3181    }
3182   
3183    NSScrollView * scrollView = [fTableView enclosingScrollView];
3184
3185    //set views to not autoresize
3186    NSUInteger filterMask = [fFilterBar autoresizingMask];
3187    NSUInteger scrollMask = [scrollView autoresizingMask];
3188    [fFilterBar setAutoresizingMask: NSViewNotSizable];
3189    [scrollView setAutoresizingMask: NSViewNotSizable];
3190   
3191    frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
3192    [fWindow setFrame: frame display: YES animate: animate];
3193   
3194    //re-enable autoresize
3195    [fFilterBar setAutoresizingMask: filterMask];
3196    [scrollView setAutoresizingMask: scrollMask];
3197   
3198    //change min size
3199    NSSize minSize = [fWindow contentMinSize];
3200    minSize.height += heightChange;
3201    [fWindow setContentMinSize: minSize];
3202   
3203    if (!show)
3204    {
3205        [fFilterBar setHidden: YES];
3206        [fWindow makeFirstResponder: fTableView];
3207    }
3208}
3209
3210- (void) focusFilterField
3211{
3212    [fWindow makeFirstResponder: fSearchFilterField];
3213    if ([fFilterBar isHidden])
3214        [self toggleFilterBar: self];
3215}
3216
3217- (ButtonToolbarItem *) standardToolbarButtonWithIdentifier: (NSString *) ident
3218{
3219    ButtonToolbarItem * item = [[ButtonToolbarItem alloc] initWithItemIdentifier: ident];
3220   
3221    NSButton * button = [[NSButton alloc] initWithFrame: NSZeroRect];
3222    [button setBezelStyle: NSTexturedRoundedBezelStyle];
3223    [button setStringValue: @""];
3224   
3225    [item setView: button];
3226    [button release];
3227   
3228    NSSize buttonSize = NSMakeSize(36.0, 25.0);
3229    [item setMinSize: buttonSize];
3230    [item setMaxSize: buttonSize];
3231   
3232    return [item autorelease];
3233}
3234
3235- (NSToolbarItem *) toolbar: (NSToolbar *) toolbar itemForItemIdentifier: (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
3236{
3237    if ([ident isEqualToString: TOOLBAR_CREATE])
3238    {
3239        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3240       
3241        [item setLabel: NSLocalizedString(@"Create", "Create toolbar item -> label")];
3242        [item setPaletteLabel: NSLocalizedString(@"Create Torrent File", "Create toolbar item -> palette label")];
3243        [item setToolTip: NSLocalizedString(@"Create torrent file", "Create toolbar item -> tooltip")];
3244        [item setImage: [NSImage imageNamed: @"Create.png"]];
3245        [item setTarget: self];
3246        [item setAction: @selector(createFile:)];
3247        [item setAutovalidates: NO];
3248       
3249        return item;
3250    }
3251    else if ([ident isEqualToString: TOOLBAR_OPEN_FILE])
3252    {
3253        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3254       
3255        [item setLabel: NSLocalizedString(@"Open", "Open toolbar item -> label")];
3256        [item setPaletteLabel: NSLocalizedString(@"Open Torrent Files", "Open toolbar item -> palette label")];
3257        [item setToolTip: NSLocalizedString(@"Open torrent files", "Open toolbar item -> tooltip")];
3258        [item setImage: [NSImage imageNamed: @"Open.png"]];
3259        [item setTarget: self];
3260        [item setAction: @selector(openShowSheet:)];
3261        [item setAutovalidates: NO];
3262       
3263        return item;
3264    }
3265    else if ([ident isEqualToString: TOOLBAR_OPEN_WEB])
3266    {
3267        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3268       
3269        [item setLabel: NSLocalizedString(@"Open Address", "Open address toolbar item -> label")];
3270        [item setPaletteLabel: NSLocalizedString(@"Open Torrent Address", "Open address toolbar item -> palette label")];
3271        [item setToolTip: NSLocalizedString(@"Open torrent web address", "Open address toolbar item -> tooltip")];
3272        [item setImage: [NSImage imageNamed: @"OpenWeb.png"]];
3273        [item setTarget: self];
3274        [item setAction: @selector(openURLShowSheet:)];
3275        [item setAutovalidates: NO];
3276       
3277        return item;
3278    }
3279    else if ([ident isEqualToString: TOOLBAR_REMOVE])
3280    {
3281        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3282       
3283        [item setLabel: NSLocalizedString(@"Remove", "Remove toolbar item -> label")];
3284        [item setPaletteLabel: NSLocalizedString(@"Remove Selected", "Remove toolbar item -> palette label")];
3285        [item setToolTip: NSLocalizedString(@"Remove selected transfers", "Remove toolbar item -> tooltip")];
3286        [item setImage: [NSImage imageNamed: @"Remove.png"]];
3287        [item setTarget: self];
3288        [item setAction: @selector(removeNoDelete:)];
3289       
3290        return item;
3291    }
3292    else if ([ident isEqualToString: TOOLBAR_INFO])
3293    {
3294        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3295       
3296        [item setLabel: NSLocalizedString(@"Inspector", "Inspector toolbar item -> label")];
3297        [item setPaletteLabel: NSLocalizedString(@"Toggle Inspector", "Inspector toolbar item -> palette label")];
3298        [item setToolTip: NSLocalizedString(@"Toggle the torrent inspector", "Inspector toolbar item -> tooltip")];
3299        [item setImage: [NSImage imageNamed: @"Info.png"]];
3300        [item setTarget: self];
3301        [item setAction: @selector(showInfo:)];
3302       
3303        return item;
3304    }
3305    else if ([ident isEqualToString: TOOLBAR_PAUSE_RESUME_ALL])
3306    {
3307        GroupToolbarItem * groupItem = [[GroupToolbarItem alloc] initWithItemIdentifier: ident];
3308       
3309        NSSegmentedControl * segmentedControl = [[NSSegmentedControl alloc] initWithFrame: NSZeroRect];
3310        [segmentedControl setCell: [[[ToolbarSegmentedCell alloc] init] autorelease]];
3311        [groupItem setView: segmentedControl];
3312        NSSegmentedCell * segmentedCell = (NSSegmentedCell *)[segmentedControl cell];
3313       
3314        [segmentedControl setSegmentCount: 2];
3315        [segmentedCell setTrackingMode: NSSegmentSwitchTrackingMomentary];
3316       
3317        NSSize groupSize = NSMakeSize(72.0, 25.0);
3318        [groupItem setMinSize: groupSize];
3319        [groupItem setMaxSize: groupSize];
3320       
3321        [groupItem setLabel: NSLocalizedString(@"Apply All", "All toolbar item -> label")];
3322        [groupItem setPaletteLabel: NSLocalizedString(@"Pause / Resume All", "All toolbar item -> palette label")];
3323        [groupItem setTarget: self];
3324        [groupItem setAction: @selector(allToolbarClicked:)];
3325       
3326        [groupItem setIdentifiers: [NSArray arrayWithObjects: TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL, nil]];
3327       
3328        [segmentedCell setTag: TOOLBAR_PAUSE_TAG forSegment: TOOLBAR_PAUSE_TAG];
3329        [segmentedControl setImage: [NSImage imageNamed: @"PauseAll.png"] forSegment: TOOLBAR_PAUSE_TAG];
3330        [segmentedCell setToolTip: NSLocalizedString(@"Pause all transfers",
3331                                    "All toolbar item -> tooltip") forSegment: TOOLBAR_PAUSE_TAG];
3332       
3333        [segmentedCell setTag: TOOLBAR_RESUME_TAG forSegment: TOOLBAR_RESUME_TAG];
3334        [segmentedControl setImage: [NSImage imageNamed: @"ResumeAll.png"] forSegment: TOOLBAR_RESUME_TAG];
3335        [segmentedCell setToolTip: NSLocalizedString(@"Resume all transfers",
3336                                    "All toolbar item -> tooltip") forSegment: TOOLBAR_RESUME_TAG];
3337       
3338        [groupItem createMenu: [NSArray arrayWithObjects: NSLocalizedString(@"Pause All", "All toolbar item -> label"),
3339                                        NSLocalizedString(@"Resume All", "All toolbar item -> label"), nil]];
3340       
3341        [segmentedControl release];
3342        return [groupItem autorelease];
3343    }
3344    else if ([ident isEqualToString: TOOLBAR_PAUSE_RESUME_SELECTED])
3345    {
3346        GroupToolbarItem * groupItem = [[GroupToolbarItem alloc] initWithItemIdentifier: ident];
3347       
3348        NSSegmentedControl * segmentedControl = [[NSSegmentedControl alloc] initWithFrame: NSZeroRect];
3349        [segmentedControl setCell: [[[ToolbarSegmentedCell alloc] init] autorelease]];
3350        [groupItem setView: segmentedControl];
3351        NSSegmentedCell * segmentedCell = (NSSegmentedCell *)[segmentedControl cell];
3352       
3353        [segmentedControl setSegmentCount: 2];
3354        [segmentedCell setTrackingMode: NSSegmentSwitchTrackingMomentary];
3355       
3356        NSSize groupSize = NSMakeSize(72.0, 25.0);
3357        [groupItem setMinSize: groupSize];
3358        [groupItem setMaxSize: groupSize];
3359       
3360        [groupItem setLabel: NSLocalizedString(@"Apply Selected", "Selected toolbar item -> label")];
3361        [groupItem setPaletteLabel: NSLocalizedString(@"Pause / Resume Selected", "Selected toolbar item -> palette label")];
3362        [groupItem setTarget: self];
3363        [groupItem setAction: @selector(selectedToolbarClicked:)];
3364       
3365        [groupItem setIdentifiers: [NSArray arrayWithObjects: TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED, nil]];
3366       
3367        [segmentedCell setTag: TOOLBAR_PAUSE_TAG forSegment: TOOLBAR_PAUSE_TAG];
3368        [segmentedControl setImage: [NSImage imageNamed: @"PauseSelected.png"] forSegment: TOOLBAR_PAUSE_TAG];
3369        [segmentedCell setToolTip: NSLocalizedString(@"Pause selected transfers",
3370                                    "Selected toolbar item -> tooltip") forSegment: TOOLBAR_PAUSE_TAG];
3371       
3372        [segmentedCell setTag: TOOLBAR_RESUME_TAG forSegment: TOOLBAR_RESUME_TAG];
3373        [segmentedControl setImage: [NSImage imageNamed: @"ResumeSelected.png"] forSegment: TOOLBAR_RESUME_TAG];
3374        [segmentedCell setToolTip: NSLocalizedString(@"Resume selected transfers",
3375                                    "Selected toolbar item -> tooltip") forSegment: TOOLBAR_RESUME_TAG];
3376       
3377        [groupItem createMenu: [NSArray arrayWithObjects: NSLocalizedString(@"Pause Selected", "Selected toolbar item -> label"),
3378                                        NSLocalizedString(@"Resume Selected", "Selected toolbar item -> label"), nil]];
3379       
3380        [segmentedControl release];
3381        return [groupItem autorelease];
3382    }
3383    else if ([ident isEqualToString: TOOLBAR_FILTER])
3384    {
3385        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3386       
3387        [item setLabel: NSLocalizedString(@"Filter", "Filter toolbar item -> label")];
3388        [item setPaletteLabel: NSLocalizedString(@"Toggle Filter", "Filter toolbar item -> palette label")];
3389        [item setToolTip: NSLocalizedString(@"Toggle the filter bar", "Filter toolbar item -> tooltip")];
3390        [item setImage: [NSImage imageNamed: @"Filter.png"]];
3391        [item setTarget: self];
3392        [item setAction: @selector(toggleFilterBar:)];
3393       
3394        return item;
3395    }
3396    else if ([ident isEqualToString: TOOLBAR_QUICKLOOK])
3397    {
3398        ButtonToolbarItem * item = [self standardToolbarButtonWithIdentifier: ident];
3399       
3400        [item setLabel: NSLocalizedString(@"Quick Look", "QuickLook toolbar item -> label")];
3401        [item setPaletteLabel: NSLocalizedString(@"Quick Look", "QuickLook toolbar item -> palette label")];
3402        [item setToolTip: NSLocalizedString(@"Quick Look", "QuickLook toolbar item -> tooltip")];
3403        [item setImage: [NSImage imageNamed: NSImageNameQuickLookTemplate]];
3404        [item setTarget: self];
3405        [item setAction: @selector(toggleQuickLook:)];
3406       
3407        return item;
3408    }
3409    else
3410        return nil;
3411}
3412
3413- (void) allToolbarClicked: (id) sender
3414{
3415    NSInteger tagValue = [sender isKindOfClass: [NSSegmentedControl class]]
3416                    ? [(NSSegmentedCell *)[sender cell] tagForSegment: [sender selectedSegment]] : [sender tag];
3417    switch (tagValue)
3418    {
3419        case TOOLBAR_PAUSE_TAG:
3420            [self stopAllTorrents: sender];
3421            break;
3422        case TOOLBAR_RESUME_TAG:
3423            [self resumeAllTorrents: sender];
3424            break;
3425    }
3426}
3427
3428- (void) selectedToolbarClicked: (id) sender
3429{
3430    NSInteger tagValue = [sender isKindOfClass: [NSSegmentedControl class]]
3431                    ? [(NSSegmentedCell *)[sender cell] tagForSegment: [sender selectedSegment]] : [sender tag];
3432    switch (tagValue)
3433    {
3434        case TOOLBAR_PAUSE_TAG:
3435            [self stopSelectedTorrents: sender];
3436            break;
3437        case TOOLBAR_RESUME_TAG:
3438            [self resumeSelectedTorrents: sender];
3439            break;
3440    }
3441}
3442
3443- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar
3444{
3445    NSMutableArray * idents = [NSMutableArray arrayWithObjects:
3446                                TOOLBAR_CREATE, TOOLBAR_OPEN_FILE, TOOLBAR_OPEN_WEB,
3447                                TOOLBAR_REMOVE, TOOLBAR_PAUSE_RESUME_SELECTED, TOOLBAR_PAUSE_RESUME_ALL,
3448                                TOOLBAR_FILTER, TOOLBAR_INFO,
3449                                NSToolbarSeparatorItemIdentifier,
3450                                NSToolbarSpaceItemIdentifier,
3451                                NSToolbarFlexibleSpaceItemIdentifier,
3452                                NSToolbarCustomizeToolbarItemIdentifier, nil];
3453   
3454    //allow quicklook on leopard
3455    if ([NSApp isOnLeopardOrBetter])
3456        [idents insertObject: TOOLBAR_QUICKLOOK atIndex: 6];
3457   
3458    return idents;
3459}
3460
3461- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar
3462{
3463    NSMutableArray * idents =  [NSMutableArray arrayWithObjects:
3464                                TOOLBAR_CREATE, TOOLBAR_OPEN_FILE, TOOLBAR_REMOVE,
3465                                NSToolbarSeparatorItemIdentifier,
3466                                TOOLBAR_PAUSE_RESUME_ALL,
3467                                NSToolbarFlexibleSpaceItemIdentifier,
3468                                TOOLBAR_FILTER, TOOLBAR_INFO, nil];
3469   
3470    //allow quicklook on leopard
3471    if ([NSApp isOnLeopardOrBetter])
3472        [idents insertObject: TOOLBAR_QUICKLOOK atIndex: [idents count]-2];
3473   
3474    return idents;
3475}
3476
3477- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
3478{
3479    NSString * ident = [toolbarItem itemIdentifier];
3480   
3481    //enable remove item
3482    if ([ident isEqualToString: TOOLBAR_REMOVE])
3483        return [fTableView numberOfSelectedRows] > 0;
3484
3485    //enable pause all item
3486    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
3487    {
3488        Torrent * torrent;
3489        NSEnumerator * enumerator = [fTorrents objectEnumerator];
3490        while ((torrent = [enumerator nextObject]))
3491            if ([torrent isActive] || [torrent waitingToStart])
3492                return YES;
3493        return NO;
3494    }
3495
3496    //enable resume all item
3497    if ([ident isEqualToString: TOOLBAR_RESUME_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 pause item
3508    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
3509    {
3510        NSEnumerator * enumerator = [[fTableView selectedTorrents] objectEnumerator];
3511        Torrent * torrent;
3512        while ((torrent = [enumerator nextObject]))
3513            if ([torrent isActive] || [torrent waitingToStart])
3514                return YES;
3515        return NO;
3516    }
3517   
3518    //enable resume item
3519    if ([ident isEqualToString: TOOLBAR_RESUME_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    //set info image
3530    if ([ident isEqualToString: TOOLBAR_INFO])
3531    {
3532        [toolbarItem setImage: [[fInfoController window] isVisible] ? [NSImage imageNamed: @"InfoBlue.png"]
3533                                                                    : [NSImage imageNamed: @"Info.png"]];
3534        return YES;
3535    }
3536   
3537    //set filter image
3538    if ([ident isEqualToString: TOOLBAR_FILTER])
3539    {
3540        [toolbarItem setImage: ![fFilterBar isHidden] ? [NSImage imageNamed: @"FilterBlue.png"] : [NSImage imageNamed: @"Filter.png"]];
3541        return YES;
3542    }
3543   
3544    //enable quicklook item
3545    if ([ident isEqualToString: TOOLBAR_QUICKLOOK])
3546        return [[QuickLookController quickLook] canQuickLook];
3547
3548    return YES;
3549}
3550
3551- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
3552{
3553    SEL action = [menuItem action];
3554   
3555    if (action == @selector(toggleSpeedLimit:))
3556    {
3557        [menuItem setState: [fDefaults boolForKey: @"SpeedLimit"] ? NSOnState : NSOffState];
3558        return YES;
3559    }
3560   
3561    //only enable some items if it is in a context menu or the window is useable
3562    BOOL canUseTable = [fWindow isKeyWindow] || [[menuItem menu] supermenu] != [NSApp mainMenu];
3563
3564    //enable open items
3565    if (action == @selector(openShowSheet:) || action == @selector(openURLShowSheet:))
3566        return [fWindow attachedSheet] == nil;
3567   
3568    //enable sort options
3569    if (action == @selector(setSort:))
3570    {
3571        NSString * sortType;
3572        switch ([menuItem tag])
3573        {
3574            case SORT_ORDER_TAG:
3575                sortType = SORT_ORDER;
3576                break;
3577            case SORT_DATE_TAG:
3578                sortType = SORT_DATE;
3579                break;
3580            case SORT_NAME_TAG:
3581                sortType = SORT_NAME;
3582                break;
3583            case SORT_PROGRESS_TAG:
3584                sortType = SORT_PROGRESS;
3585                break;
3586            case SORT_STATE_TAG:
3587                sortType = SORT_STATE;
3588                break;
3589            case SORT_TRACKER_TAG:
3590                sortType = SORT_TRACKER;
3591                break;
3592            case SORT_ACTIVITY_TAG:
3593                sortType = SORT_ACTIVITY;
3594        }
3595       
3596        [menuItem setState: [sortType isEqualToString: [fDefaults stringForKey: @"Sort"]] ? NSOnState : NSOffState];
3597        return [fWindow isVisible];
3598    }
3599   
3600    //enable sort options
3601    if (action == @selector(setStatusLabel:))
3602    {
3603        NSString * statusLabel;
3604        switch ([menuItem tag])
3605        {
3606            case STATUS_RATIO_TOTAL_TAG:
3607                statusLabel = STATUS_RATIO_TOTAL;
3608                break;
3609            case STATUS_RATIO_SESSION_TAG:
3610                statusLabel = STATUS_RATIO_SESSION;
3611                break;
3612            case STATUS_TRANSFER_TOTAL_TAG:
3613                statusLabel = STATUS_TRANSFER_TOTAL;
3614                break;
3615            case STATUS_TRANSFER_SESSION_TAG:
3616                statusLabel = STATUS_TRANSFER_SESSION;
3617        }
3618       
3619        [menuItem setState: [statusLabel isEqualToString: [fDefaults stringForKey: @"StatusLabel"]] ? NSOnState : NSOffState];
3620        return YES;
3621    }
3622   
3623    if (action == @selector(setGroup:))
3624    {
3625        BOOL checked = NO;
3626       
3627        NSInteger index = [menuItem tag];
3628        NSEnumerator * enumerator = [[fTableView selectedTorrents] objectEnumerator];
3629        Torrent * torrent;
3630        while ((torrent = [enumerator nextObject]))
3631            if (index == [torrent groupValue])
3632            {
3633                checked = YES;
3634                break;
3635            }
3636       
3637        [menuItem setState: checked ? NSOnState : NSOffState];
3638        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3639    }
3640   
3641    if (action == @selector(setGroupFilter:))
3642    {
3643        [menuItem setState: [menuItem tag] == [fDefaults integerForKey: @"FilterGroup"] ? NSOnState : NSOffState];
3644        return YES;
3645    }
3646   
3647    if (action == @selector(toggleSmallView:))
3648    {
3649        [menuItem setState: [fDefaults boolForKey: @"SmallView"] ? NSOnState : NSOffState];
3650        return [fWindow isVisible];
3651    }
3652   
3653    if (action == @selector(togglePiecesBar:))
3654    {
3655        [menuItem setState: [fDefaults boolForKey: @"PiecesBar"] ? NSOnState : NSOffState];
3656        return [fWindow isVisible];
3657    }
3658   
3659    if (action == @selector(toggleStatusString:))
3660    {
3661        if ([fDefaults boolForKey: @"SmallView"])
3662        {
3663            [menuItem setTitle: NSLocalizedString(@"Remaining Time", "Action menu -> status string toggle")];
3664            [menuItem setState: ![fDefaults boolForKey: @"DisplaySmallStatusRegular"] ? NSOnState : NSOffState];
3665        }
3666        else
3667        {
3668            [menuItem setTitle: NSLocalizedString(@"Status of Selected Files", "Action menu -> status string toggle")];
3669            [menuItem setState: [fDefaults boolForKey: @"DisplayStatusProgressSelected"] ? NSOnState : NSOffState];
3670        }
3671       
3672        return [fWindow isVisible];
3673    }
3674   
3675    if (action == @selector(toggleAvailabilityBar:))
3676    {
3677        [menuItem setState: [fDefaults boolForKey: @"DisplayProgressBarAvailable"] ? NSOnState : NSOffState];
3678        return [fWindow isVisible];
3679    }
3680   
3681    if (action == @selector(setLimitGlobalEnabled:))
3682    {
3683        BOOL upload = [menuItem menu] == fUploadMenu;
3684        BOOL limit = menuItem == (upload ? fUploadLimitItem : fDownloadLimitItem);
3685        if (limit)
3686            [menuItem setTitle: [NSString stringWithFormat: NSLocalizedString(@"Limit (%d KB/s)",
3687                                    "Action menu -> upload/download limit"),
3688                                    [fDefaults integerForKey: upload ? @"UploadLimit" : @"DownloadLimit"]]];
3689       
3690        [menuItem setState: [fDefaults boolForKey: upload ? @"CheckUpload" : @"CheckDownload"] ? limit : !limit];
3691        return YES;
3692    }
3693   
3694    if (action == @selector(setRatioGlobalEnabled:))
3695    {
3696        BOOL check = menuItem == fCheckRatioItem;
3697        if (check)
3698            [menuItem setTitle: [NSString localizedStringWithFormat: NSLocalizedString(@"Stop at Ratio (%.2f)",
3699                                    "Action menu -> ratio stop"), [fDefaults floatForKey: @"RatioLimit"]]];
3700       
3701        [menuItem setState: [fDefaults boolForKey: @"RatioCheck"] ? check : !check];
3702        return YES;
3703    }
3704
3705    //enable show info
3706    if (action == @selector(showInfo:))
3707    {
3708        NSString * title = [[fInfoController window] isVisible] ? NSLocalizedString(@"Hide Inspector", "View menu -> Inspector")
3709                            : NSLocalizedString(@"Show Inspector", "View menu -> Inspector");
3710        [menuItem setTitle: title];
3711
3712        return YES;
3713    }
3714   
3715    //enable prev/next inspector tab
3716    if (action == @selector(setInfoTab:))
3717        return [[fInfoController window] isVisible];
3718   
3719    //enable toggle status bar
3720    if (action == @selector(toggleStatusBar:))
3721    {
3722        NSString * title = [fStatusBar isHidden] ? NSLocalizedString(@"Show Status Bar", "View menu -> Status Bar")
3723                            : NSLocalizedString(@"Hide Status Bar", "View menu -> Status Bar");
3724        [menuItem setTitle: title];
3725
3726        return [fWindow isVisible];
3727    }
3728   
3729    //enable toggle filter bar
3730    if (action == @selector(toggleFilterBar:))
3731    {
3732        NSString * title = [fFilterBar isHidden] ? NSLocalizedString(@"Show Filter Bar", "View menu -> Filter Bar")
3733                            : NSLocalizedString(@"Hide Filter Bar", "View menu -> Filter Bar");
3734        [menuItem setTitle: title];
3735
3736        return [fWindow isVisible];
3737    }
3738   
3739    //enable prev/next filter button
3740    if (action == @selector(switchFilter:))
3741        return [fWindow isVisible] && ![fFilterBar isHidden];
3742   
3743    //enable quicklook item
3744    if (action == @selector(toggleQuickLook:))
3745        return [[QuickLookController quickLook] canQuickLook];
3746   
3747    //enable reveal in finder
3748    if (action == @selector(revealFile:))
3749        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3750
3751    //enable remove items
3752    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
3753        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteDataAndTorrent:))
3754    {
3755        BOOL warning = NO,
3756            onlyDownloading = [fDefaults boolForKey: @"CheckRemoveDownloading"],
3757            canDelete = action != @selector(removeDeleteTorrent:) && action != @selector(removeDeleteDataAndTorrent:);
3758       
3759        NSEnumerator * enumerator = [[fTableView selectedTorrents] objectEnumerator];
3760        Torrent * torrent;
3761        while ((torrent = [enumerator nextObject]))
3762        {
3763            if (!warning && [torrent isActive])
3764            {
3765                warning = onlyDownloading ? ![torrent isSeeding] : YES;
3766                if (warning && canDelete)
3767                    break;
3768            }
3769            if (!canDelete && [torrent publicTorrent])
3770            {
3771                canDelete = YES;
3772                if (warning)
3773                    break;
3774            }
3775        }
3776   
3777        //append or remove ellipsis when needed
3778        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
3779        if (warning && [fDefaults boolForKey: @"CheckRemove"])
3780        {
3781            if (![title hasSuffix: ellipsis])
3782                [menuItem setTitle: [title stringByAppendingEllipsis]];
3783        }
3784        else
3785        {
3786            if ([title hasSuffix: ellipsis])
3787                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
3788        }
3789       
3790        return canUseTable && canDelete && [fTableView numberOfSelectedRows] > 0;
3791    }
3792
3793    //enable pause all item
3794    if (action == @selector(stopAllTorrents:))
3795    {
3796        Torrent * torrent;
3797        NSEnumerator * enumerator = [fTorrents objectEnumerator];
3798        while ((torrent = [enumerator nextObject]))
3799            if ([torrent isActive] || [torrent waitingToStart])
3800                return YES;
3801        return NO;
3802    }
3803   
3804    //enable resume all item
3805    if (action == @selector(resumeAllTorrents:))
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 waiting item
3816    if (action == @selector(resumeWaitingTorrents:))
3817    {
3818        if (![fDefaults boolForKey: @"Queue"] && ![fDefaults boolForKey: @"QueueSeed"])
3819            return NO;
3820   
3821        Torrent * torrent;
3822        NSEnumerator * enumerator = [fTorrents objectEnumerator];
3823        while ((torrent = [enumerator nextObject]))
3824            if (![torrent isActive] && [torrent waitingToStart])
3825                return YES;
3826        return NO;
3827    }
3828   
3829    //enable resume selected waiting item
3830    if (action == @selector(resumeSelectedTorrentsNoWait:))
3831    {
3832        if (!canUseTable)
3833            return NO;
3834       
3835        NSEnumerator * enumerator = [[fTableView selectedTorrents] objectEnumerator];
3836        Torrent * torrent;
3837        while ((torrent = [enumerator nextObject]))
3838            if (![torrent isActive])
3839                return YES;
3840        return NO;
3841    }
3842
3843    //enable pause item
3844    if (action == @selector(stopSelectedTorrents:))
3845    {
3846        if (!canUseTable)
3847            return NO;
3848   
3849        NSEnumerator * enumerator = [[fTableView selectedTorrents] objectEnumerator];
3850        Torrent * torrent;
3851        while ((torrent = [enumerator nextObject]))
3852            if ([torrent isActive] || [torrent waitingToStart])
3853                return YES;
3854        return NO;
3855    }
3856   
3857    //enable resume item
3858    if (action == @selector(resumeSelectedTorrents:))
3859    {
3860        if (!canUseTable)
3861            return NO;
3862   
3863        NSEnumerator * enumerator = [[fTableView selectedTorrents] objectEnumerator];
3864        Torrent * torrent;
3865        while ((torrent = [enumerator nextObject]))
3866            if (![torrent isActive] && ![torrent waitingToStart])
3867                return YES;
3868        return NO;
3869    }
3870   
3871    //enable manual announce item
3872    if (action == @selector(announceSelectedTorrents:))
3873    {
3874        if (!canUseTable)
3875            return NO;
3876       
3877        NSEnumerator * enumerator = [[fTableView selectedTorrents] objectEnumerator];
3878        Torrent * torrent;
3879        while ((torrent = [enumerator nextObject]))
3880            if ([torrent canManualAnnounce])
3881                return YES;
3882        return NO;
3883    }
3884   
3885    //enable reset cache item
3886    if (action == @selector(verifySelectedTorrents:))
3887        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3888   
3889    //enable move torrent file item
3890    if (action == @selector(moveDataFilesSelected:))
3891        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3892   
3893    //enable copy torrent file item
3894    if (action == @selector(copyTorrentFiles:))
3895        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3896   
3897    //enable reverse sort item
3898    if (action == @selector(setSortReverse:))
3899    {
3900        [menuItem setState: [fDefaults boolForKey: @"SortReverse"] ? NSOnState : NSOffState];
3901        return ![[fDefaults stringForKey: @"Sort"] isEqualToString: SORT_ORDER];
3902    }
3903   
3904    //enable group sort item
3905    if (action == @selector(setSortByGroup:))
3906    {
3907        [menuItem setState: [fDefaults boolForKey: @"SortByGroup"] ? NSOnState : NSOffState];
3908        return YES;
3909    }
3910   
3911    //check proper filter search item
3912    if (action == @selector(setFilterSearchType:))
3913    {
3914        NSString * filterType = [fDefaults stringForKey: @"FilterSearchType"];
3915       
3916        BOOL state;
3917        if ([menuItem tag] == FILTER_TYPE_TAG_TRACKER)
3918            state = [filterType isEqualToString: FILTER_TYPE_TRACKER];
3919        else
3920            state = [filterType isEqualToString: FILTER_TYPE_NAME];
3921       
3922        [menuItem setState: state ? NSOnState : NSOffState];
3923        return YES;
3924    }
3925   
3926    return YES;
3927}
3928
3929- (void) sleepCallback: (natural_t) messageType argument: (void *) messageArgument
3930{
3931    NSEnumerator * enumerator;
3932    Torrent * torrent;
3933
3934    switch (messageType)
3935    {
3936        case kIOMessageSystemWillSleep:
3937            //if there are any running transfers, wait 15 seconds for them to stop
3938            enumerator = [fTorrents objectEnumerator];
3939            while ((torrent = [enumerator nextObject]))
3940                if ([torrent isActive])
3941                {
3942                    //stop all transfers (since some are active) before going to sleep and remember to resume when we wake up
3943                    [fTorrents makeObjectsPerformSelector: @selector(sleep)];
3944                    sleep(15);
3945                    break;
3946                }
3947
3948            IOAllowPowerChange(fRootPort, (long) messageArgument);
3949            break;
3950
3951        case kIOMessageCanSystemSleep:
3952            if ([fDefaults boolForKey: @"SleepPrevent"])
3953            {
3954                //prevent idle sleep unless no torrents are active
3955                enumerator = [fTorrents objectEnumerator];
3956                while ((torrent = [enumerator nextObject]))
3957                    if ([torrent isActive] && ![torrent isStalled] && ![torrent isError])
3958                    {
3959                        IOCancelPowerChange(fRootPort, (long) messageArgument);
3960                        return;
3961                    }
3962            }
3963           
3964            IOAllowPowerChange(fRootPort, (long) messageArgument);
3965            break;
3966
3967        case kIOMessageSystemHasPoweredOn:
3968            //resume sleeping transfers after we wake up
3969            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
3970            [self autoSpeedLimitChange: nil];
3971            break;
3972    }
3973}
3974
3975- (NSMenu *) applicationDockMenu: (NSApplication *) sender
3976{
3977    NSInteger seeding = 0, downloading = 0;
3978    NSEnumerator * enumerator = [fTorrents objectEnumerator];
3979    Torrent * torrent;
3980    while ((torrent = [enumerator nextObject]))
3981    {
3982        if ([torrent isSeeding])
3983            seeding++;
3984        else if ([torrent isActive])
3985            downloading++;
3986        else;
3987    }
3988   
3989    NSMenuItem * seedingItem = [fDockMenu itemWithTag: DOCK_SEEDING_TAG],
3990            * downloadingItem = [fDockMenu itemWithTag: DOCK_DOWNLOADING_TAG];
3991   
3992    BOOL hasSeparator = seedingItem || downloadingItem;
3993   
3994    if (seeding > 0)
3995    {
3996        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Seeding", "Dock item - Seeding"), seeding];
3997        if (!seedingItem)
3998        {
3999            seedingItem = [[[NSMenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""] autorelease];
4000            [seedingItem setTag: DOCK_SEEDING_TAG];
4001            [fDockMenu insertItem: seedingItem atIndex: 0];
4002        }
4003        else
4004            [seedingItem setTitle: title];
4005    }
4006    else
4007    {
4008        if (seedingItem)
4009            [fDockMenu removeItem: seedingItem];
4010    }
4011   
4012    if (downloading > 0)
4013    {
4014        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Downloading", "Dock item - Downloading"), downloading];
4015        if (!downloadingItem)
4016        {
4017            downloadingItem = [[[NSMenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""] autorelease];
4018            [downloadingItem setTag: DOCK_DOWNLOADING_TAG];
4019            [fDockMenu insertItem: downloadingItem atIndex: seeding > 0 ? 1 : 0];
4020        }
4021        else
4022            [downloadingItem setTitle: title];
4023    }
4024    else
4025    {
4026        if (downloadingItem)
4027            [fDockMenu removeItem: downloadingItem];
4028    }
4029   
4030    if (seeding > 0 || downloading > 0)
4031    {
4032        if (!hasSeparator)
4033            [fDockMenu insertItem: [NSMenuItem separatorItem] atIndex: seeding > 0 && downloading > 0 ? 2 : 1];
4034    }
4035    else
4036    {
4037        if (hasSeparator)
4038            [fDockMenu removeItemAtIndex: 0];
4039    }
4040   
4041    return fDockMenu;
4042}
4043
4044- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
4045{
4046    //if auto size is enabled, the current frame shouldn't need to change
4047    NSRect frame = [fDefaults boolForKey: @"AutoSize"] ? [window frame] : [self sizedWindowFrame];
4048   
4049    frame.size.width = [fDefaults boolForKey: @"SmallView"] ? [fWindow minSize].width : WINDOW_REGULAR_WIDTH;
4050    return frame;
4051}
4052
4053- (void) setWindowSizeToFit
4054{
4055    if ([fDefaults boolForKey: @"AutoSize"])
4056    {
4057        NSScrollView * scrollView = [fTableView enclosingScrollView];
4058       
4059        [scrollView setHasVerticalScroller: NO];
4060        [fWindow setFrame: [self sizedWindowFrame] display: YES animate: YES];
4061        [scrollView setHasVerticalScroller: YES];
4062       
4063        //hack to ensure scrollbars don't disappear after resizing
4064        [scrollView setAutohidesScrollers: NO];
4065        [scrollView setAutohidesScrollers: YES];
4066    }
4067}
4068
4069- (NSRect) sizedWindowFrame
4070{
4071    NSInteger groups = ([fDisplayedTorrents count] > 0 && ![[fDisplayedTorrents objectAtIndex: 0] isKindOfClass: [Torrent class]])
4072                    ? [fDisplayedTorrents count] : 0;
4073   
4074    CGFloat heightChange = (GROUP_SEPARATOR_HEIGHT + [fTableView intercellSpacing].height) * groups
4075                        + ([fTableView rowHeight] + [fTableView intercellSpacing].height) * ([fTableView numberOfRows] - groups)
4076                        - [[fTableView enclosingScrollView] frame].size.height;
4077   
4078    return [self windowFrameByAddingHeight: heightChange checkLimits: YES];
4079}
4080
4081- (void) updateForExpandCollape
4082{
4083    [self setWindowSizeToFit];
4084    [self setBottomCountText: YES];
4085}
4086
4087- (void) showMainWindow: (id) sender
4088{
4089    [fWindow makeKeyAndOrderFront: nil];
4090}
4091
4092- (void) windowDidBecomeMain: (NSNotification *) notification
4093{
4094    [fBadger clearCompleted];
4095    [self updateUI];
4096}
4097
4098- (NSSize) windowWillResize: (NSWindow *) sender toSize: (NSSize) proposedFrameSize
4099{
4100    //only resize horizontally if autosize is enabled
4101    if ([fDefaults boolForKey: @"AutoSize"])
4102        proposedFrameSize.height = [fWindow frame].size.height;
4103    return proposedFrameSize;
4104}
4105
4106- (void) windowDidResize: (NSNotification *) notification
4107{
4108    if (![fStatusBar isHidden])
4109        [self resizeStatusButton];
4110   
4111    if ([fFilterBar isHidden])
4112        return;
4113   
4114    //replace all buttons
4115    [fActiveFilterButton sizeToFit];
4116    [fDownloadFilterButton sizeToFit];
4117    [fSeedFilterButton sizeToFit];
4118    [fPauseFilterButton sizeToFit];
4119   
4120    NSRect activeRect = [fActiveFilterButton frame];
4121   
4122    NSRect downloadRect = [fDownloadFilterButton frame];
4123    downloadRect.origin.x = NSMaxX(activeRect) + 1.0;
4124   
4125    NSRect seedRect = [fSeedFilterButton frame];
4126    seedRect.origin.x = NSMaxX(downloadRect) + 1.0;
4127   
4128    NSRect pauseRect = [fPauseFilterButton frame];
4129    pauseRect.origin.x = NSMaxX(seedRect) + 1.0;
4130   
4131    //size search filter to not overlap buttons
4132    NSRect searchFrame = [fSearchFilterField frame];
4133    searchFrame.origin.x = NSMaxX(pauseRect) + 5.0;
4134    searchFrame.size.width = [fStatusBar frame].size.width - searchFrame.origin.x - 5.0;
4135   
4136    //make sure it is not too long
4137    if (searchFrame.size.width > SEARCH_FILTER_MAX_WIDTH)
4138    {
4139        searchFrame.origin.x += searchFrame.size.width - SEARCH_FILTER_MAX_WIDTH;
4140        searchFrame.size.width = SEARCH_FILTER_MAX_WIDTH;
4141    }
4142    else if (searchFrame.size.width < SEARCH_FILTER_MIN_WIDTH)
4143    {
4144        searchFrame.origin.x += searchFrame.size.width - SEARCH_FILTER_MIN_WIDTH;
4145        searchFrame.size.width = SEARCH_FILTER_MIN_WIDTH;
4146       
4147        //resize the buttons so they don't overlay
4148        NSInteger difference = (NSMaxX(pauseRect) + 5.0) - searchFrame.origin.x - 1; //subtract 1, since 0 will be counted
4149       
4150        //decrease downloading by 8, seeding by 1, paused by 1, active by 1, repeat
4151        NSInteger download = (difference / 11 * 8) + MIN(difference % 11 + 1, 8); //8 for every 11
4152        NSInteger seed = (difference / 11) + (difference % 11 >= 8 ? 1 : 0);
4153        NSInteger paused = (difference / 11) + (difference % 11 >= 9 ? 1 : 0);
4154        NSInteger active = (difference / 11) + (difference % 11 >= 10 ? 1 : 0);
4155       
4156        activeRect.size.width -= active;
4157       
4158        downloadRect.origin.x -= active;
4159        downloadRect.size.width -= download;
4160       
4161        seedRect.origin.x -= active + download;
4162        seedRect.size.width -= seed;
4163       
4164        pauseRect.origin.x -= active + download + seed;
4165        pauseRect.size.width -= paused;
4166    }
4167    else;
4168   
4169    [fActiveFilterButton setFrame: activeRect];
4170    [fDownloadFilterButton setFrame: downloadRect];
4171    [fSeedFilterButton setFrame: seedRect];
4172    [fPauseFilterButton setFrame: pauseRect];
4173   
4174    [fSearchFilterField setFrame: searchFrame];
4175}
4176
4177- (void) applicationWillUnhide: (NSNotification *) notification
4178{
4179    [self updateUI];
4180}
4181
4182- (NSArray *) quickLookURLs
4183{
4184    NSArray * selectedTorrents = [fTableView selectedTorrents];
4185    NSMutableArray * urlArray = [NSMutableArray arrayWithCapacity: [selectedTorrents count]];
4186    NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
4187    Torrent * torrent;
4188   
4189    while ((torrent = [enumerator nextObject]))
4190        if ([self canQuickLookTorrent: torrent])
4191            [urlArray addObject: [NSURL fileURLWithPath: [torrent dataLocation]]];
4192   
4193    return urlArray;
4194}
4195
4196- (BOOL) canQuickLook
4197{
4198    NSArray * selectedTorrents = [fTableView selectedTorrents];
4199    NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
4200    Torrent * torrent;
4201   
4202    while ((torrent = [enumerator nextObject]))
4203        if ([self canQuickLookTorrent: torrent])
4204            return YES;
4205   
4206    return NO;
4207}
4208
4209- (BOOL) canQuickLookTorrent: (Torrent *) torrent
4210{
4211    if (![[NSFileManager defaultManager] fileExistsAtPath: [torrent dataLocation]])
4212        return NO;
4213   
4214    return [torrent isFolder] || [torrent isComplete];
4215}
4216
4217- (NSRect) quickLookFrameWithURL: (NSURL *) url
4218{
4219    if ([fWindow isVisible])
4220    {
4221        NSString * fullPath = [url path];
4222        NSRange visibleRows = [fTableView rowsInRect: [fTableView bounds]];
4223       
4224        for (NSInteger row = 0; row < NSMaxRange(visibleRows); row++)
4225        {
4226            id item = [fTableView itemAtRow: row];
4227            if ([item isKindOfClass: [Torrent class]] && [[(Torrent *)item dataLocation] isEqualToString: fullPath])
4228            {
4229                NSRect frame = [fTableView iconRectForRow: row];
4230                frame.origin = [fTableView convertPoint: frame.origin toView: nil];
4231                frame.origin = [fWindow convertBaseToScreen: frame.origin];
4232                frame.origin.y -= frame.size.height;
4233                return frame;
4234            }
4235        }
4236    }
4237   
4238    return NSZeroRect;
4239}
4240
4241- (void) toggleQuickLook: (id) sender
4242{
4243    [[QuickLookController quickLook] toggleQuickLook];
4244}
4245
4246- (void) linkHomepage: (id) sender
4247{
4248    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
4249}
4250
4251- (void) linkForums: (id) sender
4252{
4253    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
4254}
4255
4256- (void) linkTrac: (id) sender
4257{
4258    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: TRAC_URL]];
4259}
4260
4261- (void) linkDonate: (id) sender
4262{
4263    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: DONATE_URL]];
4264}
4265
4266- (void) updaterWillRelaunchApplication: (SUUpdater *) updater
4267{
4268    fUpdateInProgress = YES;
4269}
4270
4271- (NSDictionary *) registrationDictionaryForGrowl
4272{
4273    NSArray * notifications = [NSArray arrayWithObjects: GROWL_DOWNLOAD_COMPLETE, GROWL_SEEDING_COMPLETE,
4274                                                            GROWL_AUTO_ADD, GROWL_AUTO_SPEED_LIMIT, nil];
4275    return [NSDictionary dictionaryWithObjectsAndKeys: notifications, GROWL_NOTIFICATIONS_ALL,
4276                                notifications, GROWL_NOTIFICATIONS_DEFAULT, nil];
4277}
4278
4279- (void) growlNotificationWasClicked: (id) clickContext
4280{
4281    if (!clickContext || ![clickContext isKindOfClass: [NSDictionary class]])
4282        return;
4283   
4284    NSString * type = [clickContext objectForKey: @"Type"], * location;
4285    if (([type isEqualToString: GROWL_DOWNLOAD_COMPLETE] || [type isEqualToString: GROWL_SEEDING_COMPLETE])
4286            && (location = [clickContext objectForKey: @"Location"]))
4287        [[NSWorkspace sharedWorkspace] selectFile: location inFileViewerRootedAtPath: nil];
4288}
4289
4290- (void) rpcCallback: (tr_rpc_callback_type) type forTorrentStruct: (struct tr_torrent *) torrentStruct
4291{
4292    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
4293   
4294    //get the torrent
4295    Torrent * torrent = nil;
4296    if (torrentStruct != NULL && (type != TR_RPC_TORRENT_ADDED && type != TR_RPC_SESSION_CHANGED))
4297    {
4298        NSEnumerator * enumerator = [fTorrents objectEnumerator];
4299        while ((torrent = [enumerator nextObject]))
4300            if (torrentStruct == [torrent torrentStruct])
4301            {
4302                [torrent retain];
4303                break;
4304            }
4305       
4306        if (!torrent)
4307        {
4308            [pool release];
4309           
4310            NSLog(@"No torrent found matching the given torrent struct from the RPC callback!");
4311            return;
4312        }
4313    }
4314   
4315    switch (type)
4316    {
4317        case TR_RPC_TORRENT_ADDED:
4318            [self performSelectorOnMainThread: @selector(rpcAddTorrentStruct:) withObject:
4319                [[NSValue valueWithPointer: torrentStruct] retain] waitUntilDone: NO];
4320            break;
4321       
4322        case TR_RPC_TORRENT_STARTED:
4323        case TR_RPC_TORRENT_STOPPED:
4324            [self performSelectorOnMainThread: @selector(rpcStartedStoppedTorrent:) withObject: torrent waitUntilDone: NO];
4325            break;
4326       
4327        case TR_RPC_TORRENT_REMOVING:
4328            [self performSelectorOnMainThread: @selector(rpcRemoveTorrent:) withObject: torrent waitUntilDone: NO];
4329            break;
4330       
4331        case TR_RPC_TORRENT_CHANGED:
4332            [self performSelectorOnMainThread: @selector(rpcChangedTorrent:) withObject: torrent waitUntilDone: NO];
4333            break;
4334       
4335        case TR_RPC_SESSION_CHANGED:
4336            [fPrefsController performSelectorOnMainThread: @selector(rpcUpdatePrefs) withObject: nil waitUntilDone: NO];
4337            break;
4338       
4339        default:
4340            NSLog(@"Unknown RPC command received!");
4341            [torrent release];
4342    }
4343   
4344    [pool release];
4345}
4346
4347- (void) rpcAddTorrentStruct: (NSValue *) torrentStructPtr
4348{
4349    tr_torrent * torrentStruct = (tr_torrent *)[torrentStructPtr pointerValue];
4350    [torrentStructPtr release];
4351   
4352    NSString * location = nil;
4353    if (tr_torrentGetDownloadDir(torrentStruct) != NULL)
4354        location = [NSString stringWithUTF8String: tr_torrentGetDownloadDir(torrentStruct)];
4355   
4356    Torrent * torrent = [[Torrent alloc] initWithTorrentStruct: torrentStruct location: location lib: fLib];
4357   
4358    [torrent update];
4359    [fTorrents addObject: torrent];
4360    [torrent release];
4361   
4362    [self updateTorrentsInQueue];
4363}
4364
4365- (void) rpcRemoveTorrent: (Torrent *) torrent
4366{
4367    [self confirmRemoveTorrents: [[NSArray arrayWithObject: torrent] retain] deleteData: NO deleteTorrent: NO];
4368    [torrent release];
4369}
4370
4371- (void) rpcStartedStoppedTorrent: (Torrent *) torrent
4372{
4373    [torrent update];
4374    [torrent release];
4375   
4376    [self updateUI];
4377    [self applyFilter: nil];
4378    [self updateTorrentHistory];
4379}
4380
4381- (void) rpcChangedTorrent: (Torrent *) torrent
4382{
4383    [torrent update];
4384   
4385    if ([[fTableView selectedTorrents] containsObject: torrent])
4386    {
4387        [fInfoController updateInfoStats]; //this will reload the file table
4388        [fInfoController updateOptions];
4389    }
4390   
4391    [torrent release];
4392}
4393
4394@end
Note: See TracBrowser for help on using the repository browser.