source: trunk/macosx/Controller.m @ 6883

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

by default, show the Quick Look toolbar button on Leopard; update NEWS

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