source: trunk/macosx/Controller.m @ 5879

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

only "suck" in and out when quicklook-ing when the window is visible

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