source: trunk/macosx/Controller.m @ 5825

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

when sorting by queue order, dragging on group header will place the torrent at the bottom

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