source: trunk/macosx/Controller.m @ 5873

Last change on this file since 5873 was 5873, checked in by livings124, 14 years ago

add QuickLook? to the main window (keeping it in the info window as well)

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