source: trunk/macosx/Controller.m @ 5665

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

revert the last commit

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