source: trunk/macosx/Controller.m @ 6276

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

fix bug where inspector would reset when changing transfer state (at the cost of the inspector not updating when a group is selected and a its content changes)

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