source: trunk/macosx/Controller.m @ 5664

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

remove an unnecessary class

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