source: trunk/macosx/Controller.m @ 5851

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

more ipc code removal

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