source: trunk/macosx/Controller.m @ 7071

Last change on this file since 7071 was 7071, checked in by livings124, 13 years ago

formatting and release notes update

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