source: trunk/macosx/Controller.m @ 7187

Last change on this file since 7187 was 7187, checked in by livings124, 12 years ago

remove an unused "Show Groups" menu item

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