source: trunk/macosx/Controller.m @ 6511

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

always manually remove torrents from libT, even when removed through rpc

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