source: trunk/macosx/Controller.m @ 7290

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

#1395 - patch from Waldorf: Bonjour support for Web Interface

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