source: branches/file_selection/macosx/Controller.m @ 2136

Last change on this file since 2136 was 2136, checked in by livings124, 15 years ago

initialize the overlay window when it is first needed instead of at launch

  • Property svn:keywords set to Date Rev Author Id
File size: 116.1 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 2136 2007-06-17 16:23:37Z livings124 $
3 *
4 * Copyright (c) 2005-2007 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import <IOKit/IOMessage.h>
26
27#import "Controller.h"
28#import "Torrent.h"
29#import "TorrentCell.h"
30#import "TorrentTableView.h"
31#import "CreatorWindowController.h"
32#import "StringAdditions.h"
33#import "UKKQueue.h"
34#import "ActionMenuSpeedToDisplayLimitTransformer.h"
35#import "ActionMenuRatioToDisplayRatioTransformer.h"
36#import "ExpandedPathToPathTransformer.h"
37#import "ExpandedPathToIconTransformer.h"
38#import "SpeedLimitToTurtleIconTransformer.h"
39
40#import <Sparkle/Sparkle.h>
41
42#define TOOLBAR_CREATE          @"Toolbar Create"
43#define TOOLBAR_OPEN            @"Toolbar Open"
44#define TOOLBAR_REMOVE          @"Toolbar Remove"
45#define TOOLBAR_INFO            @"Toolbar Info"
46#define TOOLBAR_PAUSE_ALL       @"Toolbar Pause All"
47#define TOOLBAR_RESUME_ALL      @"Toolbar Resume All"
48#define TOOLBAR_PAUSE_SELECTED  @"Toolbar Pause Selected"
49#define TOOLBAR_RESUME_SELECTED @"Toolbar Resume Selected"
50#define TOOLBAR_FILTER          @"Toolbar Toggle Filter"
51
52#define GROWL_DOWNLOAD_COMPLETE @"Download Complete"
53#define GROWL_SEEDING_COMPLETE  @"Seeding Complete"
54#define GROWL_AUTO_ADD          @"Torrent Auto Added"
55#define GROWL_AUTO_SPEED_LIMIT  @"Speed Limit Auto Changed"
56
57#define TORRENT_TABLE_VIEW_DATA_TYPE    @"TorrentTableViewDataType"
58
59#define ROW_HEIGHT_REGULAR      65.0
60#define ROW_HEIGHT_SMALL        40.0
61#define WINDOW_REGULAR_WIDTH    468.0
62
63#define SEARCH_FILTER_MIN_WIDTH 55.0
64#define SEARCH_FILTER_MAX_WIDTH 115.0
65
66#define UPDATE_UI_SECONDS           1.0
67#define AUTO_SPEED_LIMIT_SECONDS    5.0
68
69#define DOCK_SEEDING_TAG        101
70#define DOCK_DOWNLOADING_TAG    102
71
72#define ANNOUNCE_WAIT_INTERVAL_SECONDS  -60.0
73
74#define WEBSITE_URL @"http://transmission.m0k.org/"
75#define FORUM_URL   @"http://transmission.m0k.org/forum/"
76
77static void sleepCallBack(void * controller, io_service_t y, natural_t messageType, void * messageArgument)
78{
79    Controller * c = controller;
80    [c sleepCallBack: messageType argument: messageArgument];
81}
82
83@implementation Controller
84
85+ (void) initialize
86{
87    //make sure system requirements are met
88    if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_3)
89    {
90        NSAlert * alert = [[NSAlert alloc] init];
91        [alert addButtonWithTitle: NSLocalizedString(@"Quit", "OS update needed -> button")];
92        [alert setMessageText: NSLocalizedString(@"Transmission requires Mac OS X 10.4 or greater.",
93                                                "OS update needed -> title")];
94        [alert setInformativeText: NSLocalizedString(@"Transmission has features that require a more "
95            "up-to-date operating system. The operating system must be updated before this version can run.",
96            "OS update needed -> message")];
97        [alert setAlertStyle: NSWarningAlertStyle];
98       
99        [alert runModal];
100        [alert release];
101       
102        exit(0);
103    }
104   
105    //make sure another Transmission.app isn't running already
106    NSString * bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
107    int processIdentifier = [[NSProcessInfo processInfo] processIdentifier];
108
109    NSDictionary * dic;
110    NSEnumerator * enumerator = [[[NSWorkspace sharedWorkspace] launchedApplications] objectEnumerator];
111    while ((dic = [enumerator nextObject]))
112    {
113        if ([[dic objectForKey: @"NSApplicationBundleIdentifier"] isEqualToString: bundleIdentifier]
114            && [[dic objectForKey: @"NSApplicationProcessIdentifier"] intValue] != processIdentifier)
115        {
116            NSAlert * alert = [[NSAlert alloc] init];
117            [alert addButtonWithTitle: NSLocalizedString(@"Quit", "Transmission already running alert -> button")];
118            [alert setMessageText: NSLocalizedString(@"Transmission is already running.",
119                                                    "Transmission already running alert -> title")];
120            [alert setInformativeText: NSLocalizedString(@"There is already a copy of Transmission running. "
121                "This copy cannot be opened until that instance is quit.", "Transmission already running alert -> message")];
122            [alert setAlertStyle: NSWarningAlertStyle];
123           
124            [alert runModal];
125            [alert release];
126           
127            //activate the already running instance, then kill ourselves right away
128            [[NSWorkspace sharedWorkspace] launchApplication: [dic objectForKey: @"NSApplicationPath"]];
129            exit(0);
130        }
131    }
132   
133    [[NSUserDefaults standardUserDefaults] registerDefaults: [NSDictionary dictionaryWithContentsOfFile:
134        [[NSBundle mainBundle] pathForResource: @"Defaults" ofType: @"plist"]]];
135   
136    //set custom value transformers
137    ActionMenuSpeedToDisplayLimitTransformer * limitTransformer =
138                        [[[ActionMenuSpeedToDisplayLimitTransformer alloc] init] autorelease];
139    [NSValueTransformer setValueTransformer: limitTransformer forName: @"ActionMenuSpeedToDisplayLimitTransformer"];
140   
141    ActionMenuRatioToDisplayRatioTransformer * ratioTransformer =
142                        [[[ActionMenuRatioToDisplayRatioTransformer alloc] init] autorelease];
143    [NSValueTransformer setValueTransformer: ratioTransformer forName: @"ActionMenuRatioToDisplayRatioTransformer"];
144   
145    ExpandedPathToPathTransformer * pathTransformer =
146                        [[[ExpandedPathToPathTransformer alloc] init] autorelease];
147    [NSValueTransformer setValueTransformer: pathTransformer forName: @"ExpandedPathToPathTransformer"];
148   
149    ExpandedPathToIconTransformer * iconTransformer =
150                        [[[ExpandedPathToIconTransformer alloc] init] autorelease];
151    [NSValueTransformer setValueTransformer: iconTransformer forName: @"ExpandedPathToIconTransformer"];
152   
153    SpeedLimitToTurtleIconTransformer * speedLimitIconTransformer =
154                        [[[SpeedLimitToTurtleIconTransformer alloc] init] autorelease];
155    [NSValueTransformer setValueTransformer: speedLimitIconTransformer forName: @"SpeedLimitToTurtleIconTransformer"];
156}
157
158- (id) init
159{
160    if ((self = [super init]))
161    {
162        fLib = tr_init("macosx");
163       
164        fTorrents = [[NSMutableArray alloc] initWithCapacity: 10];
165        fDisplayedTorrents = [[NSMutableArray alloc] initWithCapacity: 10];
166        fPendingTorrentDownloads = [[NSMutableDictionary alloc] init];
167       
168        fDefaults = [NSUserDefaults standardUserDefaults];
169       
170        fMessageController = [[MessageWindowController alloc] initWithWindowNibName: @"MessageWindow"];
171        fInfoController = [[InfoWindowController alloc] initWithWindowNibName: @"InfoWindow"];
172        fPrefsController = [[PrefsController alloc] initWithWindowNibName: @"PrefsWindow" handle: fLib];
173       
174        fBadger = [[Badger alloc] initWithLib: fLib];
175       
176        fIPCController = [[IPCController alloc] init];
177        [fIPCController setDelegate: self];
178        fRemoteQuit = NO;
179       
180        [GrowlApplicationBridge setGrowlDelegate: self];
181       
182        [[UKKQueue sharedFileWatcher] setDelegate: self];
183    }
184    return self;
185}
186
187- (void) dealloc
188{
189    [[NSNotificationCenter defaultCenter] removeObserver: self];
190   
191    [fInfoController release];
192    [fMessageController release];
193    [fPrefsController release];
194   
195    [fToolbar release];
196    [fTorrents release];
197    [fDisplayedTorrents release];
198    [fBadger release];
199    if (fOverlayWindow)
200        [fOverlayWindow release];
201    [fIPCController release];
202   
203    [fAutoImportedNames release];
204    [fPendingTorrentDownloads release];
205   
206    tr_close(fLib);
207    [super dealloc];
208}
209
210- (void) awakeFromNib
211{
212    [fBottomBar setBackgroundImage: [NSImage imageNamed: @"BottomBorder.png"]];
213    [fStatusBar setBackgroundImage: [NSImage imageNamed: @"StatusBarBackground.png"]];
214    [fFilterBar setBackgroundImage: [NSImage imageNamed: @"FilterBarBackground.png"]];
215   
216    [fWindow setAcceptsMouseMovedEvents: YES]; //ensure filter buttons display correctly
217   
218    fToolbar = [[NSToolbar alloc] initWithIdentifier: @"Transmission Toolbar"];
219    [fToolbar setDelegate: self];
220    [fToolbar setAllowsUserCustomization: YES];
221    [fToolbar setAutosavesConfiguration: YES];
222    [fWindow setToolbar: fToolbar];
223    [fWindow setDelegate: self];
224   
225    [fWindow makeFirstResponder: fTableView];
226    [fWindow setExcludedFromWindowsMenu: YES];
227   
228    //set table size
229    if ([fDefaults boolForKey: @"SmallView"])
230        [fTableView setRowHeight: ROW_HEIGHT_SMALL];
231   
232    //window min height
233    NSSize contentMinSize = [fWindow contentMinSize];
234    contentMinSize.height = [[fWindow contentView] frame].size.height - [fScrollView frame].size.height
235                                + [fTableView rowHeight] + [fTableView intercellSpacing].height;
236    [fWindow setContentMinSize: contentMinSize];
237   
238    //set info and filter keyboard shortcuts
239    unichar rightChar = NSRightArrowFunctionKey, leftChar = NSLeftArrowFunctionKey;
240    [fNextInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & rightChar length: 1]];
241    [fPrevInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & leftChar length: 1]];
242   
243    [fNextFilterItem setKeyEquivalent: [NSString stringWithCharacters: & rightChar length: 1]];
244    [fNextFilterItem setKeyEquivalentModifierMask: NSCommandKeyMask + NSAlternateKeyMask];
245    [fPrevFilterItem setKeyEquivalent: [NSString stringWithCharacters: & leftChar length: 1]];
246    [fPrevFilterItem setKeyEquivalentModifierMask: NSCommandKeyMask + NSAlternateKeyMask];
247   
248    //set up filter bar
249    NSView * contentView = [fWindow contentView];
250    NSSize windowSize = [contentView convertSize: [fWindow frame].size fromView: nil];
251    [fFilterBar setHidden: YES];
252   
253    NSRect filterBarFrame = [fFilterBar frame];
254    filterBarFrame.size.width = windowSize.width;
255    [fFilterBar setFrame: filterBarFrame];
256   
257    [contentView addSubview: fFilterBar];
258    [fFilterBar setFrameOrigin: NSMakePoint(0, NSMaxY([contentView frame]))];
259
260    [self showFilterBar: [fDefaults boolForKey: @"FilterBar"] animate: NO];
261   
262    //set up status bar
263    [fStatusBar setHidden: YES];
264   
265    NSRect statusBarFrame = [fStatusBar frame];
266    statusBarFrame.size.width = windowSize.width;
267    [fStatusBar setFrame: statusBarFrame];
268
269    [contentView addSubview: fStatusBar];
270    [fStatusBar setFrameOrigin: NSMakePoint(0, NSMaxY([contentView frame]))];
271    [self showStatusBar: [fDefaults boolForKey: @"StatusBar"] animate: NO];
272
273    [fActionButton setToolTip: NSLocalizedString(@"Shortcuts for changing global settings.",
274                                "Main window -> 1st bottom left button (action) tooltip")];
275    [fSpeedLimitButton setToolTip: NSLocalizedString(@"Speed Limit overrides the total bandwidth limits with its own limits.",
276                                "Main window -> 2nd bottom left button (turtle) tooltip")];
277   
278    [fPrefsController setUpdater: fUpdater];
279   
280    [fTableView setTorrents: fDisplayedTorrents];
281    [[fTableView tableColumnWithIdentifier: @"Torrent"] setDataCell: [[TorrentCell alloc] init]];
282
283    [fTableView registerForDraggedTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE]];
284    [fWindow registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType,
285                                            NSURLPboardType, nil]];
286
287    //register for sleep notifications
288    IONotificationPortRef notify;
289    io_object_t iterator;
290    if (fRootPort = IORegisterForSystemPower(self, & notify, sleepCallBack, & iterator))
291        CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify), kCFRunLoopCommonModes);
292    else
293        NSLog(@"Could not IORegisterForSystemPower");
294
295    //load previous transfers
296    NSArray * history = [[NSArray alloc] initWithContentsOfFile: [[self applicationSupportFolder]
297                                                stringByAppendingPathComponent: @"Transfers.plist"]];
298   
299    //old version saved transfer info in prefs file
300    if (!history)
301        if ((history = [fDefaults arrayForKey: @"History"]))
302            [history retain];
303    [fDefaults removeObjectForKey: @"History"];
304   
305    if (history)
306    {
307        Torrent * torrent;
308        NSDictionary * historyItem;
309        NSEnumerator * enumerator = [history objectEnumerator];
310        while ((historyItem = [enumerator nextObject]))
311            if ((torrent = [[Torrent alloc] initWithHistory: historyItem lib: fLib]))
312            {
313                [fTorrents addObject: torrent];
314                [torrent release];
315            }
316       
317        [history release];
318    }
319   
320    //set sort
321    NSString * sortType = [fDefaults stringForKey: @"Sort"];
322   
323    NSMenuItem * currentSortItem, * currentSortActionItem;
324    if ([sortType isEqualToString: @"Name"])
325    {
326        currentSortItem = fNameSortItem;
327        currentSortActionItem = fNameSortActionItem;
328    }
329    else if ([sortType isEqualToString: @"State"])
330    {
331        currentSortItem = fStateSortItem;
332        currentSortActionItem = fStateSortActionItem;
333    }
334    else if ([sortType isEqualToString: @"Progress"])
335    {
336        currentSortItem = fProgressSortItem;
337        currentSortActionItem = fProgressSortActionItem;
338    }
339    else if ([sortType isEqualToString: @"Order"])
340    {
341        currentSortItem = fOrderSortItem;
342        currentSortActionItem = fOrderSortActionItem;
343    }
344    else
345    {
346        //safety
347        if (![sortType isEqualToString: @"Date"])
348            [fDefaults setObject: @"Date" forKey: @"Sort"];
349       
350        currentSortItem = fDateSortItem;
351        currentSortActionItem = fDateSortActionItem;
352    }
353    [currentSortItem setState: NSOnState];
354    [currentSortActionItem setState: NSOnState];
355   
356    //set filter
357    NSString * filterType = [fDefaults stringForKey: @"Filter"];
358   
359    FilterBarButton * currentFilterButton;
360    if ([filterType isEqualToString: @"Pause"])
361        currentFilterButton = fPauseFilterButton;
362    else if ([filterType isEqualToString: @"Seed"])
363        currentFilterButton = fSeedFilterButton;
364    else if ([filterType isEqualToString: @"Download"])
365        currentFilterButton = fDownloadFilterButton;
366    else
367    {
368        //safety
369        if (![filterType isEqualToString: @"None"])
370            [fDefaults setObject: @"None" forKey: @"Filter"];
371       
372        currentFilterButton = fNoFilterButton;
373    }
374
375    [currentFilterButton setEnabled: YES];
376   
377    //observe notifications
378    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
379   
380    [nc addObserver: self selector: @selector(updateUI)
381                    name: @"UpdateUI" object: nil];
382   
383    [nc addObserver: fInfoController selector: @selector(updateInfoSettings)
384                    name: @"UpdateInfoSettings" object: nil];
385   
386    [nc addObserver: self selector: @selector(torrentFinishedDownloading:)
387                    name: @"TorrentFinishedDownloading" object: nil];
388   
389    [nc addObserver: self selector: @selector(updateControlTint:)
390                    name: NSControlTintDidChangeNotification object: nil];
391   
392    [nc addObserver: self selector: @selector(prepareForUpdate:)
393                    name: SUUpdaterWillRestartNotification object: nil];
394    fUpdateInProgress = NO;
395   
396    [nc addObserver: self selector: @selector(autoSpeedLimitChange:)
397                    name: @"AutoSpeedLimitChange" object: nil];
398   
399    [nc addObserver: self selector: @selector(changeAutoImport)
400                    name: @"AutoImportSettingChange" object: nil];
401   
402    [nc addObserver: self selector: @selector(setWindowSizeToFit)
403                    name: @"AutoSizeSettingChange" object: nil];
404   
405    [nc addObserver: fWindow selector: @selector(makeKeyWindow)
406                    name: @"MakeWindowKey" object: nil];
407   
408    //check if torrent should now start
409    [nc addObserver: self selector: @selector(torrentStoppedForRatio:)
410                    name: @"TorrentStoppedForRatio" object: nil];
411   
412    [nc addObserver: self selector: @selector(updateTorrentsInQueue)
413                    name: @"UpdateQueue" object: nil];
414   
415    //change that just impacts the dock badge
416    [nc addObserver: self selector: @selector(updateDockBadge:)
417                    name: @"DockBadgeChange" object: nil];
418
419    //timer to update the interface every second
420    [self updateUI];
421    fTimer = [NSTimer scheduledTimerWithTimeInterval: UPDATE_UI_SECONDS target: self
422        selector: @selector(updateUI) userInfo: nil repeats: YES];
423    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSModalPanelRunLoopMode];
424    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSEventTrackingRunLoopMode];
425   
426    [self applyFilter: nil];
427   
428    [fWindow makeKeyAndOrderFront: nil];
429
430    if ([fDefaults boolForKey: @"InfoVisible"])
431        [self showInfo: nil];
432   
433    //timer to auto toggle speed limit
434    [self autoSpeedLimitChange: nil];
435    fSpeedLimitTimer = [NSTimer scheduledTimerWithTimeInterval: AUTO_SPEED_LIMIT_SECONDS target: self
436        selector: @selector(autoSpeedLimit) userInfo: nil repeats: YES];
437   
438    //auto importing
439    fAutoImportedNames = [[NSMutableArray alloc] init];
440    [self checkAutoImportDirectory];
441}
442
443- (void) applicationDidFinishLaunching: (NSNotification *) notification
444{
445    [NSApp setServicesProvider: self];
446   
447    //register for dock icon drags
448    [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self
449        andSelector: @selector(handleOpenContentsEvent:replyEvent:)
450        forEventClass: kCoreEventClass andEventID: kAEOpenContents];
451   
452    //debug warning
453    if ([fDefaults boolForKey: @"WarningDebug"] && [fDefaults integerForKey: @"MessageLevel"] == TR_MSG_DBG)
454    {
455        NSAlert * alert = [[NSAlert alloc] init];
456        [alert setMessageText: NSLocalizedString(@"The Message Log is set to \"Debug\"", "Debug log alert -> message")];
457        [alert setInformativeText: NSLocalizedString(@"Continuous use at this level might increase memory usage."
458                                    " This setting can be changed in the Message Log window (accessible from the Window menu).",
459                                    "Debug log alert -> informative message")];
460        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Debug log alert -> button")];
461        [alert addButtonWithTitle: NSLocalizedString(@"Don't Remind Again", "Debug log alert -> button")];
462       
463        if ([alert runModal] == NSAlertSecondButtonReturn)
464            [fDefaults setBool: NO forKey: @"WarningDebug"];
465        [alert release];
466    }
467}
468
469- (BOOL) applicationShouldHandleReopen: (NSApplication *) app hasVisibleWindows: (BOOL) visibleWindows
470{
471    if (![fWindow isVisible] && ![[fPrefsController window] isVisible])
472        [fWindow makeKeyAndOrderFront: nil];
473    return NO;
474}
475
476- (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication *) sender
477{
478    if (!fUpdateInProgress && !fRemoteQuit && [fDefaults boolForKey: @"CheckQuit"])
479    {
480        int active = 0, downloading = 0;
481        Torrent * torrent;
482        NSEnumerator * enumerator = [fTorrents objectEnumerator];
483        while ((torrent = [enumerator nextObject]))
484            if ([torrent isActive])
485            {
486                active++;
487                if (![torrent isSeeding])
488                    downloading++;
489            }
490
491        if ([fDefaults boolForKey: @"CheckQuitDownloading"] ? downloading > 0 : active > 0)
492        {
493            NSString * message = active == 1
494                ? NSLocalizedString(@"There is an active transfer. Do you really want to quit?",
495                    "Confirm Quit panel -> message")
496                : [NSString stringWithFormat: NSLocalizedString(@"There are %d active transfers. Do you really want to quit?",
497                    "Confirm Quit panel -> message"), active];
498
499            NSBeginAlertSheet(NSLocalizedString(@"Confirm Quit", "Confirm Quit panel -> title"),
500                                NSLocalizedString(@"Quit", "Confirm Quit panel -> button"),
501                                NSLocalizedString(@"Cancel", "Confirm Quit panel -> button"), nil, fWindow, self,
502                                @selector(quitSheetDidEnd:returnCode:contextInfo:), nil, nil, message);
503            return NSTerminateLater;
504        }
505    }
506
507    return NSTerminateNow;
508}
509
510- (void) quitSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo
511{
512    [NSApp replyToApplicationShouldTerminate: returnCode == NSAlertDefaultReturn];
513}
514
515- (void) applicationWillTerminate: (NSNotification *) notification
516{
517    //remove all torrent downloads
518    NSEnumerator * enumerator = [[fPendingTorrentDownloads allValues] objectEnumerator];
519    NSDictionary * downloadDict;
520    NSURLDownload * download;
521    while ((downloadDict = [enumerator nextObject]))
522    {
523        download = [downloadDict objectForKey: @"Download"];
524        [download cancel];
525        [download release];
526    }
527    [fPendingTorrentDownloads removeAllObjects];
528   
529    //stop timers
530    [fTimer invalidate];
531    [fSpeedLimitTimer invalidate];
532    if (fAutoImportTimer)
533    {   
534        if ([fAutoImportTimer isValid])
535            [fAutoImportTimer invalidate];
536        [fAutoImportTimer release];
537    }
538   
539    //save history and stop running torrents
540    [self updateTorrentHistory];
541    [fTorrents makeObjectsPerformSelector: @selector(stopTransferForQuit)];
542   
543    //disable NAT traversal
544    tr_natTraversalEnable(fLib, 0);
545   
546    //remember window states and close all windows
547    [fDefaults setBool: [[fInfoController window] isVisible] forKey: @"InfoVisible"];
548    [[NSApp windows] makeObjectsPerformSelector: @selector(close)];
549    [self showStatusBar: NO animate: NO];
550    [self showFilterBar: NO animate: NO];
551   
552    //clear badge
553    [fBadger clearBadge];
554
555    //wait for running transfers to stop (5 second timeout)
556    NSDate * start = [NSDate date];
557    BOOL timeUp = NO;
558   
559    enumerator = [fTorrents objectEnumerator];
560    Torrent * torrent;
561    while (!timeUp && (torrent = [enumerator nextObject]))
562        while (![torrent isPaused] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
563        {
564            usleep(100000);
565            [torrent update];
566        }
567   
568    //wait for NAT to be disabled (same 5 second timeout)
569    while (!([start timeIntervalSinceNow] < -5.0)
570                && tr_handleStatus(fLib)->natTraversalStatus != TR_NAT_TRAVERSAL_DISABLED)
571        usleep(100000);
572}
573
574- (void) handleOpenContentsEvent: (NSAppleEventDescriptor *) event replyEvent: (NSAppleEventDescriptor *) replyEvent
575{
576    NSString * urlString = nil;
577
578    NSAppleEventDescriptor * directObject = [event paramDescriptorForKeyword: keyDirectObject];
579    if ([directObject descriptorType] == typeAEList)
580    {
581        unsigned i;
582        for (i = 1; i <= [directObject numberOfItems]; i++)
583            if ((urlString = [[directObject descriptorAtIndex: i] stringValue]))
584                break;
585    }
586    else
587        urlString = [directObject stringValue];
588   
589    if (urlString)
590        [self openURL: [[[NSURL alloc] initWithString: urlString] autorelease]];
591}
592
593- (void) download: (NSURLDownload *) download decideDestinationWithSuggestedFilename: (NSString *) suggestedName
594{
595    if ([[suggestedName pathExtension] caseInsensitiveCompare: @"torrent"] != NSOrderedSame)
596    {
597        [download cancel];
598       
599        NSRunAlertPanel(NSLocalizedString(@"Torrent download failed",
600            @"Download not a torrent -> title"), [NSString stringWithFormat:
601            NSLocalizedString(@"It appears that the file \"%@\" from %@ is not a torrent file.",
602            @"Download not a torrent -> message"), suggestedName,
603            [[[[download request] URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding]],
604            NSLocalizedString(@"OK", @"Download not a torrent -> button"), nil, nil);
605       
606        [download release];
607    }
608    else
609        [download setDestination: [NSTemporaryDirectory() stringByAppendingPathComponent: [suggestedName lastPathComponent]]
610                    allowOverwrite: NO];
611}
612
613-(void) download: (NSURLDownload *) download didCreateDestination: (NSString *) path
614{
615    [fPendingTorrentDownloads setObject: [NSDictionary dictionaryWithObjectsAndKeys:
616                    path, @"Path", download, @"Download", nil] forKey: [[download request] URL]];
617}
618
619- (void) download: (NSURLDownload *) download didFailWithError: (NSError *) error
620{
621    NSRunAlertPanel(NSLocalizedString(@"Torrent download failed",
622        @"Torrent download error -> title"), [NSString stringWithFormat:
623        NSLocalizedString(@"The torrent could not be downloaded from %@ because an error occurred (%@).",
624        @"Torrent download failed -> message"),
625        [[[[download request] URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding],
626        [error localizedDescription]], NSLocalizedString(@"OK", @"Torrent download failed -> button"), nil, nil);
627   
628    [fPendingTorrentDownloads removeObjectForKey: [[download request] URL]];
629    [download release];
630}
631
632- (void) downloadDidFinish: (NSURLDownload *) download
633{
634    NSString * path = [[fPendingTorrentDownloads objectForKey: [[download request] URL]] objectForKey: @"Path"];
635   
636    [self openFiles: [NSArray arrayWithObject: path] ignoreDownloadFolder:
637        ![[fDefaults stringForKey: @"DownloadChoice"] isEqualToString: @"Constant"] forceDeleteTorrent: YES];
638   
639    [fPendingTorrentDownloads removeObjectForKey: [[download request] URL]];
640    [download release];
641   
642    //delete torrent file if it wasn't already
643    [[NSFileManager defaultManager] removeFileAtPath: path handler: nil];
644}
645
646- (void) application: (NSApplication *) app openFiles: (NSArray *) filenames
647{
648    [self openFiles: filenames ignoreDownloadFolder: NO forceDeleteTorrent: NO];
649}
650
651- (void) openFiles: (NSArray *) filenames ignoreDownloadFolder: (BOOL) ignore forceDeleteTorrent: (BOOL) delete
652{
653    NSString * downloadChoice = [fDefaults stringForKey: @"DownloadChoice"];
654    if (ignore || [downloadChoice isEqualToString: @"Ask"])
655    {
656        [self openFilesAsk: [filenames mutableCopy] forceDeleteTorrent: delete];
657        return;
658    }
659   
660    if ([fDefaults boolForKey: @"UseIncompleteDownloadFolder"]
661        && access([[[fDefaults stringForKey: @"IncompleteDownloadFolder"] stringByExpandingTildeInPath] UTF8String], 0))
662    {
663        NSOpenPanel * panel = [NSOpenPanel openPanel];
664       
665        [panel setPrompt: NSLocalizedString(@"Select", "Default incomplete folder cannot be found alert -> prompt")];
666        [panel setAllowsMultipleSelection: NO];
667        [panel setCanChooseFiles: NO];
668        [panel setCanChooseDirectories: YES];
669        [panel setCanCreateDirectories: YES];
670
671        [panel setMessage: NSLocalizedString(@"The incomplete folder cannot be found."
672                                        " Choose a new location or cancel for none.",
673                                        "Default incomplete folder cannot be found alert -> message")];
674       
675        NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
676                                        filenames, @"Filenames",
677                                        [NSNumber numberWithBool: ignore], @"Ignore",
678                                        [NSNumber numberWithBool: delete], @"Delete", nil];
679       
680        [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: fWindow modalDelegate: self
681                didEndSelector: @selector(incompleteChoiceClosed:returnCode:contextInfo:) contextInfo: dict];
682        return;
683    }
684    if ([downloadChoice isEqualToString: @"Constant"]
685        && access([[[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath] UTF8String], 0))
686    {
687        NSOpenPanel * panel = [NSOpenPanel openPanel];
688       
689        [panel setPrompt: NSLocalizedString(@"Select", "Default folder cannot be found alert -> prompt")];
690        [panel setAllowsMultipleSelection: NO];
691        [panel setCanChooseFiles: NO];
692        [panel setCanChooseDirectories: YES];
693        [panel setCanCreateDirectories: YES];
694
695        [panel setMessage: NSLocalizedString(@"The download folder cannot be found. Choose a new location.",
696                                        "Default folder cannot be found alert -> message")];
697       
698        NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
699                                        filenames, @"Filenames",
700                                        [NSNumber numberWithBool: ignore], @"Ignore",
701                                        [NSNumber numberWithBool: delete], @"Delete", nil];
702       
703        [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: fWindow modalDelegate: self
704                didEndSelector: @selector(downloadChoiceClosed:returnCode:contextInfo:) contextInfo: dict];
705        return;
706    }
707   
708    Torrent * torrent;
709    NSString * torrentPath;
710    NSEnumerator * enumerator = [filenames objectEnumerator];
711    while ((torrentPath = [enumerator nextObject]))
712    {
713        if (!(torrent = [[Torrent alloc] initWithPath: torrentPath forceDeleteTorrent: delete lib: fLib]))
714            continue;
715
716        //add it to the "File > Open Recent" menu
717        [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
718
719        NSString * folder = [downloadChoice isEqualToString: @"Constant"]
720            ? [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath]
721            : [torrentPath stringByDeletingLastPathComponent];
722       
723        [torrent setDownloadFolder: folder];
724        [torrent update];
725       
726        [fTorrents addObject: torrent];
727        [torrent release];
728    }
729
730    [self updateTorrentsInQueue];
731}
732
733- (void) incompleteChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (NSDictionary *) dictionary
734{
735    if (code == NSOKButton)
736        [fDefaults setObject: [[openPanel filenames] objectAtIndex: 0] forKey: @"IncompleteDownloadFolder"];
737    else
738        [fDefaults setBool: NO forKey: @"UseIncompleteDownloadFolder"];
739   
740    [self performSelectorOnMainThread: @selector(openFilesWithDict:) withObject: dictionary waitUntilDone: NO];
741}
742
743- (void) downloadChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (NSDictionary *) dictionary
744{
745    if (code == NSOKButton)
746    {
747        [fDefaults setObject: [[openPanel filenames] objectAtIndex: 0] forKey: @"DownloadFolder"];
748        [self performSelectorOnMainThread: @selector(openFilesWithDict:) withObject: dictionary waitUntilDone: NO];
749    }
750    else
751        [dictionary release];
752}
753
754- (void) openFilesWithDict: (NSDictionary *) dictionary
755{
756    [self openFiles: [dictionary objectForKey: @"Filenames"]
757        ignoreDownloadFolder: [[dictionary objectForKey: @"Ignore"] boolValue]
758        forceDeleteTorrent: [[dictionary objectForKey: @"Delete"] boolValue]];
759   
760    [dictionary release];
761}
762
763//called by the main open method to show sheet for choosing download location
764- (void) openFilesAsk: (NSMutableArray *) files forceDeleteTorrent: (BOOL) delete
765{
766    NSString * torrentPath;
767    tr_torrent_t * tempTor;
768    int error;
769   
770    //determine next file that can be opened
771    do
772    {
773        if ([files count] == 0) //no files left to open
774        {
775            [files release];
776           
777            [self updateTorrentHistory];
778            return;
779        }
780   
781        torrentPath = [[files objectAtIndex: 0] retain];
782        tempTor = tr_torrentInit(fLib, [torrentPath UTF8String], NULL, 0, &error);
783       
784        [files removeObjectAtIndex: 0];
785    } while (!tempTor);
786
787    NSOpenPanel * panel = [NSOpenPanel openPanel];
788
789    [panel setPrompt: NSLocalizedString(@"Select", "Open torrent -> prompt")];
790    [panel setAllowsMultipleSelection: NO];
791    [panel setCanChooseFiles: NO];
792    [panel setCanChooseDirectories: YES];
793    [panel setCanCreateDirectories: YES];
794
795    [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the download folder for \"%@\"",
796                        "Open torrent -> select destination folder"),
797                        [NSString stringWithUTF8String: tr_torrentInfo(tempTor)->name]]];
798   
799    NSDictionary * dictionary = [[NSDictionary alloc] initWithObjectsAndKeys: torrentPath, @"Path",
800                                    files, @"Files", [NSNumber numberWithBool: delete], @"Delete", nil];
801    [torrentPath release];
802
803    tr_torrentClose(tempTor);
804    [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: fWindow modalDelegate: self
805            didEndSelector: @selector(folderChoiceClosed:returnCode:contextInfo:) contextInfo: dictionary];
806}
807
808- (void) folderChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (NSDictionary *) dictionary
809{
810    if (code == NSOKButton)
811    {
812        NSString * torrentPath = [dictionary objectForKey: @"Path"];
813        Torrent * torrent = [[Torrent alloc] initWithPath: torrentPath forceDeleteTorrent:
814                                [[dictionary objectForKey: @"Delete"] boolValue] lib: fLib];
815       
816        //add it to the "File > Open Recent" menu
817        [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
818       
819        [torrent setDownloadFolder: [[openPanel filenames] objectAtIndex: 0]];
820        [torrent update];
821       
822        [fTorrents addObject: torrent];
823        [torrent release];
824       
825        [self updateTorrentsInQueue];
826    }
827   
828    [self performSelectorOnMainThread: @selector(openFilesAskWithDict:) withObject: dictionary waitUntilDone: NO];
829}
830
831- (void) openFilesAskWithDict: (NSDictionary *) dictionary
832{
833    [self openFilesAsk: [dictionary objectForKey: @"Files"]
834            forceDeleteTorrent: [[dictionary objectForKey: @"Delete"] boolValue]];
835    [dictionary release];
836}
837
838//called on by applescript
839- (void) open: (NSArray *) files
840{
841    [self performSelectorOnMainThread: @selector(openFiles:) withObject: files waitUntilDone: NO];
842}
843
844- (void) openFiles: (NSArray *) filenames
845{
846    [self openFiles: filenames ignoreDownloadFolder: NO forceDeleteTorrent: NO];
847}
848
849- (void) openShowSheet: (id) sender
850{
851    NSOpenPanel * panel = [NSOpenPanel openPanel];
852
853    [panel setAllowsMultipleSelection: YES];
854    [panel setCanChooseFiles: YES];
855    [panel setCanChooseDirectories: NO];
856
857    [panel beginSheetForDirectory: nil file: nil types: [NSArray arrayWithObject: @"torrent"]
858        modalForWindow: fWindow modalDelegate: self didEndSelector: @selector(openSheetClosed:returnCode:contextInfo:)
859        contextInfo: [NSNumber numberWithBool: sender == fOpenIgnoreDownloadFolder]];
860}
861
862- (void) openSheetClosed: (NSOpenPanel *) panel returnCode: (int) code contextInfo: (NSNumber *) ignore
863{
864    if (code == NSOKButton)
865    {
866        NSDictionary * dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:
867                                        [panel filenames], @"Files", ignore, @"Ignore", nil];
868        [self performSelectorOnMainThread: @selector(openFromSheet:) withObject: dictionary waitUntilDone: NO];
869    }
870}
871
872- (void) openFromSheet: (NSDictionary *) dictionary
873{
874    [self openFiles: [dictionary objectForKey: @"Files"]
875        ignoreDownloadFolder: [[dictionary objectForKey: @"Ignore"] boolValue] forceDeleteTorrent: NO];
876   
877    [dictionary release];
878}
879
880- (void) openURL: (NSURL *) url
881{
882    [[NSURLDownload alloc] initWithRequest: [NSURLRequest requestWithURL: url] delegate: self];
883}
884
885- (void) openURLShowSheet: (id) sender
886{
887    [NSApp beginSheet: fURLSheetWindow modalForWindow: fWindow modalDelegate: self
888            didEndSelector: @selector(urlSheetDidEnd:returnCode:contextInfo:) contextInfo: nil];
889}
890
891- (void) openURLEndSheet: (id) sender
892{
893    [fURLSheetWindow orderOut: sender];
894    [NSApp endSheet: fURLSheetWindow returnCode: 1];
895}
896
897- (void) openURLCancelEndSheet: (id) sender
898{
899    [fURLSheetWindow orderOut: sender];
900    [NSApp endSheet: fURLSheetWindow returnCode: 0];
901}
902
903- (void) urlSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo
904{
905    [fURLSheetTextField selectText: self];
906    if (returnCode != 1)
907        return;
908   
909    NSString * urlString = [fURLSheetTextField stringValue];
910    if (![urlString isEqualToString: @""])
911    {
912        if ([urlString rangeOfString: @"://"].location == NSNotFound)
913        {
914            if ([urlString rangeOfString: @"."].location == NSNotFound)
915            {
916                int beforeCom;
917                if ((beforeCom = [urlString rangeOfString: @"/"].location) != NSNotFound)
918                    urlString = [NSString stringWithFormat: @"http://www.%@.com/%@",
919                                    [urlString substringToIndex: beforeCom],
920                                    [urlString substringFromIndex: beforeCom + 1]];
921                else
922                    urlString = [NSString stringWithFormat: @"http://www.%@.com", urlString];
923            }
924            else
925                urlString = [@"http://" stringByAppendingString: urlString];
926        }
927       
928        NSURL * url = [NSURL URLWithString: urlString];
929        [self performSelectorOnMainThread: @selector(openURL:) withObject: url waitUntilDone: NO];
930    }
931}
932
933- (void) createFile: (id) sender
934{
935    [CreatorWindowController createTorrentFile: fLib];
936}
937
938- (void) resumeSelectedTorrents: (id) sender
939{
940    [self resumeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
941}
942
943- (void) resumeAllTorrents: (id) sender
944{
945    [self resumeTorrents: fTorrents];
946}
947
948- (void) resumeTorrents: (NSArray *) torrents
949{
950    NSEnumerator * enumerator = [torrents objectEnumerator];
951    Torrent * torrent;
952    while ((torrent = [enumerator nextObject]))
953        [torrent setWaitToStart: YES];
954   
955    [self updateTorrentsInQueue];
956}
957
958- (void) resumeSelectedTorrentsNoWait:  (id) sender
959{
960    [self resumeTorrentsNoWait: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
961}
962
963- (void) resumeWaitingTorrents: (id) sender
964{
965    NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [fTorrents count]];
966   
967    NSEnumerator * enumerator = [fTorrents objectEnumerator];
968    Torrent * torrent;
969    while ((torrent = [enumerator nextObject]))
970        if (![torrent isActive] && [torrent waitingToStart])
971            [torrents addObject: torrent];
972   
973    [self resumeTorrentsNoWait: torrents];
974}
975
976- (void) resumeTorrentsNoWait: (NSArray *) torrents
977{
978    //iterate through instead of all at once to ensure no conflicts
979    NSEnumerator * enumerator = [torrents objectEnumerator];
980    Torrent * torrent;
981    while ((torrent = [enumerator nextObject]))
982        [torrent startTransfer];
983   
984    [self updateUI];
985    [self applyFilter: nil];
986    [self updateTorrentHistory];
987}
988
989- (void) stopSelectedTorrents: (id) sender
990{
991    [self stopTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
992}
993
994- (void) stopAllTorrents: (id) sender
995{
996    [self stopTorrents: fTorrents];
997}
998
999- (void) stopTorrents: (NSArray *) torrents
1000{
1001    //don't want any of these starting then stopping
1002    NSEnumerator * enumerator = [torrents objectEnumerator];
1003    Torrent * torrent;
1004    while ((torrent = [enumerator nextObject]))
1005        [torrent setWaitToStart: NO];
1006
1007    [torrents makeObjectsPerformSelector: @selector(stopTransfer)];
1008   
1009    [self updateUI];
1010    [self applyFilter: nil];
1011    [self updateTorrentHistory];
1012}
1013
1014- (void) removeTorrents: (NSArray *) torrents deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
1015{
1016    [torrents retain];
1017    int active = 0, downloading = 0;
1018
1019    if ([fDefaults boolForKey: @"CheckRemove"])
1020    {
1021        Torrent * torrent;
1022        NSEnumerator * enumerator = [torrents objectEnumerator];
1023        while ((torrent = [enumerator nextObject]))
1024            if ([torrent isActive])
1025            {
1026                active++;
1027                if (![torrent isSeeding])
1028                    downloading++;
1029            }
1030
1031        if ([fDefaults boolForKey: @"CheckRemoveDownloading"] ? downloading > 0 : active > 0)
1032        {
1033            NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
1034                torrents, @"Torrents",
1035                [NSNumber numberWithBool: deleteData], @"DeleteData",
1036                [NSNumber numberWithBool: deleteTorrent], @"DeleteTorrent", nil];
1037
1038            NSString * title, * message;
1039           
1040            int selected = [fTableView numberOfSelectedRows];
1041            if (selected == 1)
1042            {
1043                title = [NSString stringWithFormat: NSLocalizedString(@"Confirm Removal of \"%@\"",
1044                            "Removal confirm panel -> title"),
1045                            [[fDisplayedTorrents objectAtIndex: [fTableView selectedRow]] name]];
1046                message = NSLocalizedString(@"This transfer is active."
1047                            " Once removed, continuing the transfer will require the torrent file."
1048                            " Do you really want to remove it?", "Removal confirm panel -> message");
1049            }
1050            else
1051            {
1052                title = [NSString stringWithFormat: NSLocalizedString(@"Confirm Removal of %d Transfers",
1053                            "Removal confirm panel -> title"), selected];
1054                if (selected == active)
1055                    message = [NSString stringWithFormat: NSLocalizedString(@"There are %d active transfers.",
1056                                "Removal confirm panel -> message part 1"), active];
1057                else
1058                    message = [NSString stringWithFormat: NSLocalizedString(@"There are %d transfers (%d active).",
1059                                "Removal confirm panel -> message part 1"), selected, active];
1060                message = [message stringByAppendingString:
1061                    NSLocalizedString(@" Once removed, continuing the transfers will require the torrent files."
1062                    " Do you really want to remove them?", "Removal confirm panel -> message part 2")];
1063            }
1064
1065            NSBeginAlertSheet(title, NSLocalizedString(@"Remove", "Removal confirm panel -> button"),
1066                NSLocalizedString(@"Cancel", "Removal confirm panel -> button"), nil, fWindow, self,
1067                nil, @selector(removeSheetDidEnd:returnCode:contextInfo:), dict, message);
1068            return;
1069        }
1070    }
1071   
1072    [self confirmRemoveTorrents: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
1073}
1074
1075- (void) removeSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (NSDictionary *) dict
1076{
1077    NSArray * torrents = [dict objectForKey: @"Torrents"];
1078    BOOL deleteData = [[dict objectForKey: @"DeleteData"] boolValue],
1079        deleteTorrent = [[dict objectForKey: @"DeleteTorrent"] boolValue];
1080    [dict release];
1081   
1082    if (returnCode == NSAlertDefaultReturn)
1083        [self confirmRemoveTorrents: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
1084    else
1085        [torrents release];
1086}
1087
1088- (void) confirmRemoveTorrents: (NSArray *) torrents deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
1089{
1090    //don't want any of these starting then stopping
1091    NSEnumerator * enumerator = [torrents objectEnumerator];
1092    Torrent * torrent;
1093    while ((torrent = [enumerator nextObject]))
1094        [torrent setWaitToStart: NO];
1095
1096    NSNumber * lowestOrderValue = nil, * currentOrderValue;
1097
1098    enumerator = [torrents objectEnumerator];
1099    while ((torrent = [enumerator nextObject]))
1100    {
1101        [torrent stopTransfer];
1102
1103        if (deleteData)
1104            [torrent trashData];
1105        if (deleteTorrent)
1106            [torrent trashTorrent];
1107       
1108        //determine lowest order value
1109        currentOrderValue = [torrent orderValue];
1110        if (!lowestOrderValue || [lowestOrderValue compare: currentOrderValue] == NSOrderedDescending)
1111            lowestOrderValue = currentOrderValue;
1112       
1113        [fTorrents removeObject: torrent];
1114        [fDisplayedTorrents removeObject: torrent];
1115    }
1116    [torrents release];
1117
1118    //reset the order values if necessary
1119    if ([lowestOrderValue intValue] < [fTorrents count])
1120    {
1121        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1122                                                @"orderValue" ascending: YES] autorelease];
1123        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1124
1125        NSArray * tempTorrents = [fTorrents sortedArrayUsingDescriptors: descriptors];
1126        [descriptors release];
1127
1128        int i;
1129        for (i = [lowestOrderValue intValue]; i < [tempTorrents count]; i++)
1130            [[tempTorrents objectAtIndex: i] setOrderValue: i];
1131    }
1132   
1133    [fTableView deselectAll: nil];
1134   
1135    [self updateUI];
1136    [self applyFilter: nil];
1137   
1138    [self updateTorrentHistory];
1139}
1140
1141- (void) removeNoDelete: (id) sender
1142{
1143    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1144                deleteData: NO deleteTorrent: NO];
1145}
1146
1147- (void) removeDeleteData: (id) sender
1148{
1149    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1150                deleteData: YES deleteTorrent: NO];
1151}
1152
1153- (void) removeDeleteTorrent: (id) sender
1154{
1155    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1156                deleteData: NO deleteTorrent: YES];
1157}
1158
1159- (void) removeDeleteDataAndTorrent: (id) sender
1160{
1161    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1162                deleteData: YES deleteTorrent: YES];
1163}
1164
1165- (void) moveDataFiles: (id) sender
1166{
1167    [self moveDataFileForTorrents: [[NSMutableArray alloc] initWithArray:
1168            [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]]];
1169}
1170
1171- (void) moveDataFileForTorrents: (NSMutableArray *) torrents
1172{
1173    if ([torrents count] <= 0)
1174    {
1175        [torrents release];
1176        return;
1177    }
1178
1179    Torrent * torrent = [torrents objectAtIndex: 0];
1180
1181    NSOpenPanel * panel = [NSOpenPanel openPanel];
1182   
1183    [panel setPrompt: NSLocalizedString(@"Select", "Move torrent -> prompt")];
1184    [panel setAllowsMultipleSelection: NO];
1185    [panel setCanChooseFiles: NO];
1186    [panel setCanChooseDirectories: YES];
1187    [panel setCanCreateDirectories: YES];
1188
1189    [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the new folder for \"%@\"",
1190                        "Move torrent -> select destination folder"), [torrent name]]];
1191       
1192    [panel beginSheetForDirectory: nil file: [torrent name] modalForWindow: fWindow modalDelegate: self
1193        didEndSelector: @selector(moveDataFileChoiceClosed:returnCode:contextInfo:) contextInfo: torrents];
1194}
1195
1196- (void) moveDataFileChoiceClosed: (NSOpenPanel *) panel returnCode: (int) code contextInfo: (NSMutableArray *) torrents
1197{
1198    //move torrent data file to new location
1199    if (code == NSOKButton)
1200        [[torrents objectAtIndex: 0] moveTorrentDataFileTo: [[panel filenames] objectAtIndex: 0]];
1201   
1202    [torrents removeObjectAtIndex: 0];
1203    [self performSelectorOnMainThread: @selector(moveDataFileForTorrents:) withObject: torrents waitUntilDone: NO];
1204}
1205
1206- (void) copyTorrentFiles: (id) sender
1207{
1208    [self copyTorrentFileForTorrents: [[NSMutableArray alloc] initWithArray:
1209            [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]]];
1210}
1211
1212- (void) copyTorrentFileForTorrents: (NSMutableArray *) torrents
1213{
1214    if ([torrents count] <= 0)
1215    {
1216        [torrents release];
1217        return;
1218    }
1219
1220    Torrent * torrent = [torrents objectAtIndex: 0];
1221
1222    //warn user if torrent file can't be found
1223    if (![[NSFileManager defaultManager] fileExistsAtPath: [torrent torrentLocation]])
1224    {
1225        NSAlert * alert = [[NSAlert alloc] init];
1226        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent file copy alert -> button")];
1227        [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Copy of \"%@\" Cannot Be Created",
1228                                "Torrent file copy alert -> title"), [torrent name]]];
1229        [alert setInformativeText: [NSString stringWithFormat:
1230                NSLocalizedString(@"The torrent file (%@) cannot be found.", "Torrent file copy alert -> message"),
1231                                    [torrent torrentLocation]]];
1232        [alert setAlertStyle: NSWarningAlertStyle];
1233       
1234        [alert runModal];
1235        [alert release];
1236       
1237        [torrents removeObjectAtIndex: 0];
1238        [self copyTorrentFileForTorrents: torrents];
1239    }
1240    else
1241    {
1242        NSSavePanel * panel = [NSSavePanel savePanel];
1243        [panel setRequiredFileType: @"torrent"];
1244        [panel setCanSelectHiddenExtension: YES];
1245       
1246        [panel beginSheetForDirectory: nil file: [torrent name] modalForWindow: fWindow modalDelegate: self
1247            didEndSelector: @selector(saveTorrentCopySheetClosed:returnCode:contextInfo:) contextInfo: torrents];
1248    }
1249}
1250
1251- (void) saveTorrentCopySheetClosed: (NSSavePanel *) panel returnCode: (int) code contextInfo: (NSMutableArray *) torrents
1252{
1253    //copy torrent to new location with name of data file
1254    if (code == NSOKButton)
1255        [[torrents objectAtIndex: 0] copyTorrentFileTo: [panel filename]];
1256   
1257    [torrents removeObjectAtIndex: 0];
1258    [self performSelectorOnMainThread: @selector(copyTorrentFileForTorrents:) withObject: torrents waitUntilDone: NO];
1259}
1260
1261- (void) revealFile: (id) sender
1262{
1263    NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
1264    Torrent * torrent;
1265    while ((torrent = [enumerator nextObject]))
1266        [torrent revealData];
1267}
1268
1269- (void) announceSelectedTorrents: (id) sender
1270{
1271    NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
1272    Torrent * torrent;
1273    NSDate * date;
1274    while ((torrent = [enumerator nextObject]))
1275    {
1276        //time interval returned will be negative
1277        if (!(date = [torrent announceDate]) || [date timeIntervalSinceNow] <= ANNOUNCE_WAIT_INTERVAL_SECONDS)
1278            [torrent announce];
1279    }
1280}
1281
1282- (void) resetCacheForSelectedTorrents: (id) sender
1283{
1284    NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
1285    Torrent * torrent;
1286    while ((torrent = [enumerator nextObject]))
1287    {
1288        [torrent resetCache];
1289    }
1290}
1291
1292- (void) showPreferenceWindow: (id) sender
1293{
1294    NSWindow * window = [fPrefsController window];
1295    if (![window isVisible])
1296        [window center];
1297
1298    [window makeKeyAndOrderFront: nil];
1299}
1300
1301- (void) showInfo: (id) sender
1302{
1303    if ([[fInfoController window] isVisible])
1304        [fInfoController close];
1305    else
1306    {
1307        [fInfoController updateInfoStats];
1308        [[fInfoController window] orderFront: nil];
1309    }
1310}
1311
1312- (void) setInfoTab: (id) sender
1313{
1314    if (sender == fNextInfoTabItem)
1315        [fInfoController setNextTab];
1316    else
1317        [fInfoController setPreviousTab];
1318}
1319
1320- (void) showMessageWindow: (id) sender
1321{
1322    [fMessageController showWindow: nil];
1323}
1324
1325- (void) updateUI
1326{
1327    [fTorrents makeObjectsPerformSelector: @selector(update)];
1328
1329    //resort if necessary or just update the table
1330    NSString * sortType = [fDefaults stringForKey: @"Sort"];
1331    if ([sortType isEqualToString: @"Progress"] || [sortType isEqualToString: @"State"])
1332        [self sortTorrents];
1333    else
1334        [fTableView reloadData];
1335   
1336    //update the global DL/UL rates
1337    float downloadRate, uploadRate;
1338    tr_torrentRates(fLib, & downloadRate, & uploadRate);
1339    if (![fStatusBar isHidden])
1340    {
1341        [fTotalDLField setStringValue: [NSLocalizedString(@"Total DL: ", "Status bar -> total download")
1342                                        stringByAppendingString: [NSString stringForSpeed: downloadRate]]];
1343        [fTotalULField setStringValue: [NSLocalizedString(@"Total UL: ", "Status bar -> total upload")
1344                                        stringByAppendingString: [NSString stringForSpeed: uploadRate]]];
1345    }
1346
1347    //update non-constant parts of info window
1348    if ([[fInfoController window] isVisible])
1349        [fInfoController updateInfoStats];
1350
1351    //badge dock
1352    [fBadger updateBadge];
1353}
1354
1355- (void) updateTorrentsInQueue
1356{
1357    BOOL download = [fDefaults boolForKey: @"Queue"],
1358        seed = [fDefaults boolForKey: @"QueueSeed"];
1359   
1360    //determine the number of downloads needed to start
1361    int desiredDownloadActive = download ? [fDefaults integerForKey: @"QueueDownloadNumber"] : 0,
1362        desiredSeedActive = seed ? [fDefaults integerForKey: @"QueueSeedNumber"] : 0;
1363   
1364    Torrent * torrent;
1365    NSEnumerator * enumerator;
1366    if (download || seed)
1367    {
1368        enumerator = [fTorrents objectEnumerator];
1369        while ((torrent = [enumerator nextObject]))
1370            if ([torrent isActive] && ![torrent isStalled] && ![torrent isError])
1371            {
1372                if ([torrent allDownloaded])
1373                    desiredSeedActive--;
1374                else
1375                    desiredDownloadActive--;
1376               
1377                if (desiredDownloadActive <= 0 && desiredSeedActive <= 0)
1378                    break;
1379            }
1380    }
1381   
1382    //sort torrents by order value
1383    NSArray * sortedTorrents;
1384    if ([fTorrents count] > 1 && (desiredDownloadActive > 0 || desiredSeedActive > 0))
1385    {
1386        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1387                                                @"orderValue" ascending: YES] autorelease];
1388        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1389       
1390        sortedTorrents = [fTorrents sortedArrayUsingDescriptors: descriptors];
1391        [descriptors release];
1392    }
1393    else
1394        sortedTorrents = fTorrents;
1395
1396    enumerator = [sortedTorrents objectEnumerator];
1397    while ((torrent = [enumerator nextObject]))
1398    {
1399        if (![torrent isActive] && [torrent waitingToStart])
1400        {
1401            if (![torrent allDownloaded])
1402            {
1403                if (!download || desiredDownloadActive > 0)
1404                {
1405                    [torrent startTransfer];
1406                    if ([torrent isActive])
1407                        desiredDownloadActive--;
1408                    [torrent update];
1409                }
1410            }
1411            else
1412            {
1413                if (!seed || desiredSeedActive > 0)
1414                {
1415                    [torrent startTransfer];
1416                    if ([torrent isActive])
1417                        desiredSeedActive--;
1418                    [torrent update];
1419                }
1420            }
1421        }
1422    }
1423   
1424    [self updateUI];
1425    [self applyFilter: nil];
1426    [self updateTorrentHistory];
1427}
1428
1429- (void) torrentFinishedDownloading: (NSNotification *) notification
1430{
1431    Torrent * torrent = [notification object];
1432   
1433    [fInfoController updateInfoStats];
1434   
1435    if ([fDefaults boolForKey: @"PlayDownloadSound"])
1436    {
1437        NSSound * sound;
1438        if ((sound = [NSSound soundNamed: [fDefaults stringForKey: @"DownloadSound"]]))
1439            [sound play];
1440    }
1441   
1442    NSDictionary * clickContext = [NSDictionary dictionaryWithObjectsAndKeys: GROWL_DOWNLOAD_COMPLETE, @"Type",
1443                                    [torrent dataLocation] , @"Location", nil];
1444    [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Download Complete", "Growl notification title")
1445                                description: [torrent name] notificationName: GROWL_DOWNLOAD_COMPLETE
1446                                iconData: nil priority: 0 isSticky: NO clickContext: clickContext];
1447   
1448    if (![fWindow isKeyWindow])
1449        [fBadger incrementCompleted];
1450   
1451    if ([fDefaults boolForKey: @"QueueSeed"])
1452    {
1453        [torrent stopTransfer];
1454        [torrent setWaitToStart: YES];
1455    }
1456   
1457    [self updateTorrentsInQueue];
1458}
1459
1460- (void) updateTorrentHistory
1461{
1462    NSMutableArray * history = [NSMutableArray arrayWithCapacity: [fTorrents count]];
1463
1464    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1465    Torrent * torrent;
1466    while ((torrent = [enumerator nextObject]))
1467        [history addObject: [torrent history]];
1468
1469    [history writeToFile: [[self applicationSupportFolder] stringByAppendingPathComponent: @"Transfers.plist"] atomically: YES];
1470}
1471
1472- (void) sortTorrents
1473{
1474    //remember selected rows if needed
1475    NSArray * selectedTorrents = nil;
1476    int numSelected = [fTableView numberOfSelectedRows];
1477    if (numSelected > 0 && numSelected < [fDisplayedTorrents count])
1478        selectedTorrents = [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]];
1479
1480    [self sortTorrentsIgnoreSelected]; //actually sort
1481   
1482    //set selected rows if needed
1483    if (selectedTorrents)
1484    {
1485        Torrent * torrent;
1486        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1487        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1488        while ((torrent = [enumerator nextObject]))
1489            [indexSet addIndex: [fDisplayedTorrents indexOfObject: torrent]];
1490       
1491        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1492        [indexSet release];
1493    }
1494}
1495
1496//doesn't remember selected rows
1497- (void) sortTorrentsIgnoreSelected
1498{
1499    NSString * sortType = [fDefaults stringForKey: @"Sort"];
1500    BOOL asc = ![fDefaults boolForKey: @"SortReverse"];
1501   
1502    NSSortDescriptor * nameDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"name"
1503                            ascending: asc selector: @selector(caseInsensitiveCompare:)] autorelease],
1504                    * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"orderValue"
1505                                            ascending: asc] autorelease];
1506   
1507    NSArray * descriptors;
1508    if ([sortType isEqualToString: @"Name"])
1509        descriptors = [[NSArray alloc] initWithObjects: nameDescriptor, orderDescriptor, nil];
1510    else if ([sortType isEqualToString: @"State"])
1511    {
1512        NSSortDescriptor * stateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1513                                                @"stateSortKey" ascending: !asc] autorelease],
1514                        * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1515                                            @"progressSortKey" ascending: !asc] autorelease],
1516                        * ratioDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1517                                            @"ratioSortKey" ascending: !asc] autorelease];
1518       
1519        descriptors = [[NSArray alloc] initWithObjects: stateDescriptor, progressDescriptor, ratioDescriptor,
1520                                                            nameDescriptor, orderDescriptor, nil];
1521    }
1522    else if ([sortType isEqualToString: @"Progress"])
1523    {
1524        NSSortDescriptor * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1525                                            @"progressSortKey" ascending: asc] autorelease],
1526                        * ratioDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1527                                            @"ratioSortKey" ascending: asc] autorelease];
1528       
1529        descriptors = [[NSArray alloc] initWithObjects: progressDescriptor, ratioDescriptor,
1530                                                            nameDescriptor, orderDescriptor, nil];
1531    }
1532    else if ([sortType isEqualToString: @"Order"])
1533        descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1534    else
1535    {
1536        NSSortDescriptor * dateDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"dateAdded" ascending: asc] autorelease];
1537   
1538        descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, orderDescriptor, nil];
1539    }
1540
1541    [fDisplayedTorrents sortUsingDescriptors: descriptors];
1542    [descriptors release];
1543   
1544    [fTableView reloadData];
1545}
1546
1547- (void) setSort: (id) sender
1548{
1549    NSString * oldSortType = [fDefaults stringForKey: @"Sort"];
1550   
1551    //get checked items
1552    NSMenuItem * prevSortItem, * prevSortActionItem;
1553    if ([oldSortType isEqualToString: @"Name"])
1554    {
1555        prevSortItem = fNameSortItem;
1556        prevSortActionItem = fNameSortActionItem;
1557    }
1558    else if ([oldSortType isEqualToString: @"State"])
1559    {
1560        prevSortItem = fStateSortItem;
1561        prevSortActionItem = fStateSortActionItem;
1562    }
1563    else if ([oldSortType isEqualToString: @"Progress"])
1564    {
1565        prevSortItem = fProgressSortItem;
1566        prevSortActionItem = fProgressSortActionItem;
1567    }
1568    else if ([oldSortType isEqualToString: @"Order"])
1569    {
1570        prevSortItem = fOrderSortItem;
1571        prevSortActionItem = fOrderSortActionItem;
1572    }
1573    else
1574    {
1575        prevSortItem = fDateSortItem;
1576        prevSortActionItem = fDateSortActionItem;
1577    }
1578   
1579    if (sender != prevSortItem && sender != prevSortActionItem)
1580    {
1581        //get new items to check
1582        NSMenuItem * currentSortItem, * currentSortActionItem;
1583        NSString * sortType;
1584        if (sender == fNameSortItem || sender == fNameSortActionItem)
1585        {
1586            currentSortItem = fNameSortItem;
1587            currentSortActionItem = fNameSortActionItem;
1588            sortType = @"Name";
1589        }
1590        else if (sender == fStateSortItem || sender == fStateSortActionItem)
1591        {
1592            currentSortItem = fStateSortItem;
1593            currentSortActionItem = fStateSortActionItem;
1594            sortType = @"State";
1595        }
1596        else if (sender == fProgressSortItem || sender == fProgressSortActionItem)
1597        {
1598            currentSortItem = fProgressSortItem;
1599            currentSortActionItem = fProgressSortActionItem;
1600            sortType = @"Progress";
1601        }
1602        else if (sender == fOrderSortItem || sender == fOrderSortActionItem)
1603        {
1604            currentSortItem = fOrderSortItem;
1605            currentSortActionItem = fOrderSortActionItem;
1606            sortType = @"Order";
1607           
1608            [fDefaults setBool: NO forKey: @"SortReverse"];
1609        }
1610        else
1611        {
1612            currentSortItem = fDateSortItem;
1613            currentSortActionItem = fDateSortActionItem;
1614            sortType = @"Date";
1615        }
1616       
1617        [fDefaults setObject: sortType forKey: @"Sort"];
1618   
1619        [prevSortItem setState: NSOffState];
1620        [prevSortActionItem setState: NSOffState];
1621        [currentSortItem setState: NSOnState];
1622        [currentSortActionItem setState: NSOnState];
1623    }
1624
1625    [self sortTorrents];
1626}
1627
1628- (void) setSortReverse: (id) sender
1629{
1630    [self sortTorrents];
1631}
1632
1633- (void) applyFilter: (id) sender
1634{
1635    //remember selected rows if needed
1636    NSArray * selectedTorrents = [fTableView numberOfSelectedRows] > 0
1637                ? [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] : nil;
1638
1639    NSMutableArray * tempTorrents = [[NSMutableArray alloc] initWithCapacity: [fTorrents count]];
1640   
1641    NSString * filterType = [fDefaults stringForKey: @"Filter"];
1642    BOOL filtering = ![filterType isEqualToString: @"None"];
1643   
1644    int downloading = 0, seeding = 0, paused = 0;
1645    BOOL isDownloading = [filterType isEqualToString: @"Download"],
1646            isSeeding = [filterType isEqualToString: @"Seed"],
1647            isPaused = [filterType isEqualToString: @"Pause"];
1648   
1649    //get count of each type
1650    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1651    Torrent * torrent;
1652    while ((torrent = [enumerator nextObject]))
1653    {
1654        if ([torrent isActive])
1655        {
1656            if ([torrent isSeeding])
1657            {
1658                seeding++;
1659                if (isSeeding)
1660                    [tempTorrents addObject: torrent];
1661            }
1662            else
1663            {
1664                downloading++;
1665                if (isDownloading)
1666                    [tempTorrents addObject: torrent];
1667            }
1668        }
1669        else
1670        {
1671            paused++;
1672            if (isPaused)
1673                [tempTorrents addObject: torrent];
1674        }
1675    }
1676   
1677    if (!filtering)
1678        [tempTorrents setArray: fTorrents];
1679   
1680    //set buttons with counts
1681    [fNoFilterButton setCount: [fTorrents count]];
1682    [fDownloadFilterButton setCount: downloading];
1683    [fSeedFilterButton setCount: seeding];
1684    [fPauseFilterButton setCount: paused];
1685   
1686    NSString * searchString = [fSearchFilterField stringValue];
1687    if ([searchString length] > 0)
1688    {
1689        filtering = YES;
1690       
1691        int i;
1692        for (i = [tempTorrents count] - 1; i >= 0; i--)
1693            if ([[[tempTorrents objectAtIndex: i] name] rangeOfString: searchString
1694                                        options: NSCaseInsensitiveSearch].location == NSNotFound)
1695                [tempTorrents removeObjectAtIndex: i];
1696    }
1697   
1698    [fDisplayedTorrents setArray: tempTorrents];
1699    [tempTorrents release];
1700   
1701    [self sortTorrentsIgnoreSelected];
1702   
1703    //set selected rows if needed
1704    if (selectedTorrents)
1705    {
1706        Torrent * torrent;
1707        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1708        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1709        unsigned index;
1710        while ((torrent = [enumerator nextObject]))
1711            if ((index = [fDisplayedTorrents indexOfObject: torrent]) != NSNotFound)
1712                [indexSet addIndex: index];
1713       
1714        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1715        [indexSet release];
1716    }
1717   
1718    //set status bar torrent count text
1719    NSMutableString * totalTorrentsString = [[NSMutableString alloc] initWithString: @""];
1720    if (filtering)
1721        [totalTorrentsString appendFormat: @"%d/", [fDisplayedTorrents count]];
1722   
1723    int totalCount = [fTorrents count];
1724    if (totalCount != 1)
1725        [totalTorrentsString appendFormat: NSLocalizedString(@"%d Transfers", "Status bar transfer count"), totalCount];
1726    else
1727        [totalTorrentsString appendFormat: NSLocalizedString(@"1 Transfer", "Status bar transfer count")];
1728   
1729    [fTotalTorrentsField setStringValue: totalTorrentsString];
1730    [totalTorrentsString release];
1731
1732    [self setWindowSizeToFit];
1733}
1734
1735//resets filter and sorts torrents
1736- (void) setFilter: (id) sender
1737{
1738    NSString * oldFilterType = [fDefaults stringForKey: @"Filter"];
1739   
1740    FilterBarButton * prevFilterButton;
1741    if ([oldFilterType isEqualToString: @"Pause"])
1742        prevFilterButton = fPauseFilterButton;
1743    else if ([oldFilterType isEqualToString: @"Seed"])
1744        prevFilterButton = fSeedFilterButton;
1745    else if ([oldFilterType isEqualToString: @"Download"])
1746        prevFilterButton = fDownloadFilterButton;
1747    else
1748        prevFilterButton = fNoFilterButton;
1749   
1750    if (sender != prevFilterButton)
1751    {
1752        [prevFilterButton setEnabled: NO];
1753        [sender setEnabled: YES];
1754
1755        NSString * filterType;
1756        if (sender == fDownloadFilterButton)
1757            filterType = @"Download";
1758        else if (sender == fPauseFilterButton)
1759            filterType = @"Pause";
1760        else if (sender == fSeedFilterButton)
1761            filterType = @"Seed";
1762        else
1763            filterType = @"None";
1764
1765        [fDefaults setObject: filterType forKey: @"Filter"];
1766    }
1767
1768    [self applyFilter: nil];
1769}
1770
1771- (void) switchFilter: (id) sender
1772{
1773    NSString * filterType = [fDefaults stringForKey: @"Filter"];
1774   
1775    NSButton * button;
1776    if ([filterType isEqualToString: @"None"])
1777        button = sender == fNextFilterItem ? fDownloadFilterButton : fPauseFilterButton;
1778    else if ([filterType isEqualToString: @"Download"])
1779        button = sender == fNextFilterItem ? fSeedFilterButton : fNoFilterButton;
1780    else if ([filterType isEqualToString: @"Seed"])
1781        button = sender == fNextFilterItem ? fPauseFilterButton : fDownloadFilterButton;
1782    else if ([filterType isEqualToString: @"Pause"])
1783        button = sender == fNextFilterItem ? fNoFilterButton : fSeedFilterButton;
1784    else
1785        button = fNoFilterButton;
1786   
1787    [self setFilter: button];
1788}
1789
1790- (void) updateControlTint: (NSNotification *) notification
1791{
1792    if ([fDefaults boolForKey: @"SpeedLimit"])
1793        [fSpeedLimitButton setImage: [NSColor currentControlTint] == NSBlueControlTint
1794            ? [NSImage imageNamed: @"SpeedLimitButtonBlue.png"] : [NSImage imageNamed: @"SpeedLimitButtonGraphite.png"]];
1795}
1796
1797- (void) applySpeedLimit: (id) sender
1798{
1799    [fPrefsController applySpeedSettings: nil];
1800}
1801
1802- (void) toggleSpeedLimit: (id) sender
1803{
1804    [fDefaults setBool: ![fDefaults boolForKey: @"SpeedLimit"] forKey: @"SpeedLimit"];
1805    [self applySpeedLimit: nil];
1806}
1807
1808- (void) autoSpeedLimitChange: (NSNotification *) notification
1809{
1810    if (![fDefaults boolForKey: @"SpeedLimitAuto"])
1811        return;
1812 
1813    NSCalendarDate * onDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
1814                        [[fDefaults objectForKey: @"SpeedLimitAutoOnDate"] timeIntervalSinceReferenceDate]],
1815        * offDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
1816                        [[fDefaults objectForKey: @"SpeedLimitAutoOffDate"] timeIntervalSinceReferenceDate]],
1817        * nowDate = [NSCalendarDate calendarDate];
1818   
1819    //check if should be on if within range
1820    BOOL shouldBeOn;
1821   
1822    int onTime = [onDate hourOfDay] * 60 + [onDate minuteOfHour],
1823        offTime = [offDate hourOfDay] * 60 + [offDate minuteOfHour],
1824        nowTime = [nowDate hourOfDay] * 60 + [nowDate minuteOfHour];
1825   
1826    if (onTime == offTime)
1827        shouldBeOn = NO;
1828    else if (onTime < offTime)
1829        shouldBeOn = onTime <= nowTime && nowTime < offTime;
1830    else
1831        shouldBeOn = onTime <= nowTime || nowTime < offTime;
1832   
1833    if ([fDefaults boolForKey: @"SpeedLimit"] != shouldBeOn)
1834        [self toggleSpeedLimit: nil];
1835}
1836
1837- (void) autoSpeedLimit
1838{
1839    if (![fDefaults boolForKey: @"SpeedLimitAuto"])
1840        return;
1841   
1842    //only toggle if within first few seconds of minutes
1843    NSCalendarDate * nowDate = [NSCalendarDate calendarDate];
1844    if ([nowDate secondOfMinute] > AUTO_SPEED_LIMIT_SECONDS)
1845        return;
1846   
1847    NSCalendarDate * offDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
1848                        [[fDefaults objectForKey: @"SpeedLimitAutoOffDate"] timeIntervalSinceReferenceDate]];
1849   
1850    BOOL toggle;
1851    if ([fDefaults boolForKey: @"SpeedLimit"])
1852        toggle = [nowDate hourOfDay] == [offDate hourOfDay] && [nowDate minuteOfHour] == [offDate minuteOfHour];
1853    else
1854    {
1855        NSCalendarDate * onDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
1856                        [[fDefaults objectForKey: @"SpeedLimitAutoOnDate"] timeIntervalSinceReferenceDate]];
1857        toggle = ([nowDate hourOfDay] == [onDate hourOfDay] && [nowDate minuteOfHour] == [onDate minuteOfHour])
1858                    && !([onDate hourOfDay] == [offDate hourOfDay] && [onDate minuteOfHour] == [offDate minuteOfHour]);
1859    }
1860   
1861    if (toggle)
1862    {
1863        [self toggleSpeedLimit: nil];
1864       
1865        [GrowlApplicationBridge notifyWithTitle: [fDefaults boolForKey: @"SpeedLimit"]
1866                ? NSLocalizedString(@"Speed Limit Auto Enabled", "Growl notification title")
1867                : NSLocalizedString(@"Speed Limit Auto Disabled", "Growl notification title")
1868            description: NSLocalizedString(@"Bandwidth settings changed", "Growl notification description")
1869            notificationName: GROWL_AUTO_SPEED_LIMIT iconData: nil priority: 0 isSticky: NO clickContext: nil];
1870    }
1871}
1872
1873- (void) setLimitGlobalEnabled: (id) sender
1874{
1875    [fPrefsController applySpeedSettings: nil];
1876}
1877
1878- (void) setQuickLimitGlobal: (id) sender
1879{
1880    [fDefaults setInteger: [[sender title] intValue] forKey: [sender menu] == fUploadMenu ? @"UploadLimit" : @"DownloadLimit"];
1881    [fDefaults setBool: YES forKey: [sender menu] == fUploadMenu ? @"CheckUpload" : @"CheckDownload"];
1882   
1883    [fPrefsController updateLimitFields];
1884    [fPrefsController applySpeedSettings: nil];
1885}
1886
1887- (void) setQuickRatioGlobal: (id) sender
1888{
1889    [fDefaults setBool: YES forKey: @"RatioCheck"];
1890    [fDefaults setFloat: [[sender title] floatValue] forKey: @"RatioLimit"];
1891   
1892    [fPrefsController updateRatioStopField];
1893}
1894
1895- (void) torrentStoppedForRatio: (NSNotification *) notification
1896{
1897    Torrent * torrent = [notification object];
1898   
1899    [self updateTorrentsInQueue];
1900    [fInfoController updateInfoStats];
1901    [fInfoController updateRatioForTorrent: torrent];
1902   
1903    if ([fDefaults boolForKey: @"PlaySeedingSound"])
1904    {
1905        NSSound * sound;
1906        if ((sound = [NSSound soundNamed: [fDefaults stringForKey: @"SeedingSound"]]))
1907            [sound play];
1908    }
1909   
1910    NSDictionary * clickContext = [NSDictionary dictionaryWithObjectsAndKeys: GROWL_SEEDING_COMPLETE, @"Type",
1911                                    [torrent dataLocation], @"Location", nil];
1912    [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Seeding Complete", "Growl notification title")
1913                        description: [torrent name] notificationName: GROWL_SEEDING_COMPLETE
1914                        iconData: nil priority: 0 isSticky: NO clickContext: clickContext];
1915}
1916
1917-(void) watcher: (id<UKFileWatcher>) watcher receivedNotification: (NSString *) notification forPath: (NSString *) path
1918{
1919    if ([notification isEqualToString: UKFileWatcherWriteNotification])
1920    {
1921        if (![fDefaults boolForKey: @"AutoImport"] || ![fDefaults stringForKey: @"AutoImportDirectory"])
1922            return;
1923       
1924        if (fAutoImportTimer)
1925        {
1926            if ([fAutoImportTimer isValid])
1927                [fAutoImportTimer invalidate];
1928            [fAutoImportTimer release];
1929            fAutoImportTimer = nil;
1930        }
1931       
1932        //check again in 10 seconds in case torrent file wasn't complete
1933        fAutoImportTimer = [[NSTimer scheduledTimerWithTimeInterval: 10.0 target: self
1934            selector: @selector(checkAutoImportDirectory) userInfo: nil repeats: NO] retain];
1935       
1936        [self checkAutoImportDirectory];
1937    }
1938}
1939
1940- (void) changeAutoImport
1941{
1942    if (fAutoImportTimer)
1943    {
1944        if ([fAutoImportTimer isValid])
1945            [fAutoImportTimer invalidate];
1946        [fAutoImportTimer release];
1947        fAutoImportTimer = nil;
1948    }
1949   
1950    [fAutoImportedNames removeAllObjects];
1951    [self checkAutoImportDirectory];
1952}
1953
1954- (void) checkAutoImportDirectory
1955{
1956    NSString * path;
1957    if (![fDefaults boolForKey: @"AutoImport"] || !(path = [fDefaults stringForKey: @"AutoImportDirectory"]))
1958        return;
1959   
1960    path = [path stringByExpandingTildeInPath];
1961   
1962    NSArray * importedNames;
1963    if (!(importedNames = [[NSFileManager defaultManager] directoryContentsAtPath: path]))
1964        return;
1965   
1966    //only check files that have not been checked yet
1967    NSMutableArray * newNames = [importedNames mutableCopy];
1968    [newNames removeObjectsInArray: fAutoImportedNames];
1969    [fAutoImportedNames setArray: importedNames];
1970   
1971    NSString * file;
1972    int i;
1973    for (i = [newNames count] - 1; i >= 0; i--)
1974    {
1975        file = [newNames objectAtIndex: i];
1976        if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] != NSOrderedSame)
1977            [newNames removeObjectAtIndex: i];
1978        else
1979            [newNames replaceObjectAtIndex: i withObject: [path stringByAppendingPathComponent: file]];
1980    }
1981   
1982    NSEnumerator * enumerator;
1983    if (![[fDefaults stringForKey: @"DownloadChoice"] isEqualToString: @"Ask"])
1984    {
1985        enumerator = [newNames objectEnumerator];
1986        int count;
1987        while ((file = [enumerator nextObject]))
1988        {
1989            count = [fTorrents count];
1990            [self openFiles: [NSArray arrayWithObject: file]];
1991           
1992            //check if torrent was opened
1993            if ([fTorrents count] > count)
1994                [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Torrent File Auto Added",
1995                    "Growl notification title") description: [file lastPathComponent]
1996                    notificationName: GROWL_AUTO_ADD iconData: nil priority: 0 isSticky: NO clickContext: nil];
1997        }
1998    }
1999    else
2000        [self openFiles: newNames];
2001   
2002    //create temporary torrents to check if an import fails because of an error
2003    enumerator = [newNames objectEnumerator];
2004    int error;
2005    while ((file = [enumerator nextObject]))
2006    {
2007        tr_torrent_t * tempTor = tr_torrentInit(fLib, [file UTF8String], NULL, 0, &error);
2008       
2009        if (tempTor)
2010            tr_torrentClose(tempTor);
2011        else if (error != TR_EUNSUPPORTED && error != TR_EDUPLICATE)
2012            [fAutoImportedNames removeObject: [file lastPathComponent]]; //can try to import later
2013        else;
2014    }
2015   
2016    [newNames release];
2017}
2018
2019- (int) numberOfRowsInTableView: (NSTableView *) tableview
2020{
2021    return [fDisplayedTorrents count];
2022}
2023
2024- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) tableColumn row: (int) row
2025{
2026    return [[fDisplayedTorrents objectAtIndex: row] infoForCurrentView];
2027}
2028
2029- (BOOL) tableView: (NSTableView *) tableView writeRowsWithIndexes: (NSIndexSet *) indexes
2030    toPasteboard: (NSPasteboard *) pasteboard
2031{
2032    //only allow reordering of rows if sorting by order with no filter
2033    if ([[fDefaults stringForKey: @"Sort"] isEqualToString: @"Order"]
2034        && [[fDefaults stringForKey: @"Filter"] isEqualToString: @"None"]
2035        && [[fSearchFilterField stringValue] length] == 0)
2036    {
2037        [pasteboard declareTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE] owner: self];
2038        [pasteboard setData: [NSKeyedArchiver archivedDataWithRootObject: indexes]
2039                                forType: TORRENT_TABLE_VIEW_DATA_TYPE];
2040        return YES;
2041    }
2042    return NO;
2043}
2044
2045- (NSDragOperation) tableView: (NSTableView *) t validateDrop: (id <NSDraggingInfo>) info
2046    proposedRow: (int) row proposedDropOperation: (NSTableViewDropOperation) operation
2047{
2048    NSPasteboard * pasteboard = [info draggingPasteboard];
2049    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2050    {
2051        [fTableView setDropRow: row dropOperation: NSTableViewDropAbove];
2052        return NSDragOperationGeneric;
2053    }
2054    else;
2055   
2056    return NSDragOperationNone;
2057}
2058
2059- (BOOL) tableView: (NSTableView *) t acceptDrop: (id <NSDraggingInfo>) info
2060    row: (int) newRow dropOperation: (NSTableViewDropOperation) operation
2061{
2062    NSPasteboard * pasteboard = [info draggingPasteboard];
2063    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2064    {
2065        //remember selected rows if needed
2066        NSArray * selectedTorrents = nil;
2067        int numSelected = [fTableView numberOfSelectedRows];
2068        if (numSelected > 0 && numSelected < [fDisplayedTorrents count])
2069            selectedTorrents = [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]];
2070   
2071        NSIndexSet * indexes = [NSKeyedUnarchiver unarchiveObjectWithData:
2072                                [pasteboard dataForType: TORRENT_TABLE_VIEW_DATA_TYPE]];
2073       
2074        //move torrent in array
2075        NSArray * movingTorrents = [[fDisplayedTorrents objectsAtIndexes: indexes] retain];
2076        [fDisplayedTorrents removeObjectsInArray: movingTorrents];
2077       
2078        //determine the insertion index now that transfers to move have been removed
2079        int i, decrease = 0;
2080        for (i = [indexes firstIndex]; i < newRow && i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
2081            decrease++;
2082       
2083        //insert objects at new location
2084        for (i = 0; i < [movingTorrents count]; i++)
2085            [fDisplayedTorrents insertObject: [movingTorrents objectAtIndex: i] atIndex: newRow - decrease + i];
2086       
2087        [movingTorrents release];
2088       
2089        //redo order values
2090        int low = [indexes firstIndex], high = [indexes lastIndex];
2091        if (newRow < low)
2092            low = newRow;
2093        else if (newRow > high + 1)
2094            high = newRow - 1;
2095        else;
2096       
2097        for (i = low; i <= high; i++)
2098            [[fDisplayedTorrents objectAtIndex: i] setOrderValue: i];
2099       
2100        [fTableView reloadData];
2101       
2102        //set selected rows if needed
2103        if (selectedTorrents)
2104        {
2105            Torrent * torrent;
2106            NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
2107            NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
2108            while ((torrent = [enumerator nextObject]))
2109                [indexSet addIndex: [fDisplayedTorrents indexOfObject: torrent]];
2110           
2111            [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
2112            [indexSet release];
2113        }
2114    }
2115    else;
2116   
2117    return YES;
2118}
2119
2120- (void) tableViewSelectionDidChange: (NSNotification *) notification
2121{
2122    [fInfoController updateInfoForTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
2123}
2124
2125- (NSDragOperation) draggingEntered: (id <NSDraggingInfo>) info
2126{
2127    NSPasteboard * pasteboard = [info draggingPasteboard];
2128    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2129    {
2130        //check if any files can be added
2131        NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
2132        NSEnumerator * enumerator = [files objectEnumerator];
2133        NSString * file;
2134        tr_torrent_t * tempTor;
2135        while ((file = [enumerator nextObject]))
2136        {
2137            int error;
2138            if ((tempTor = tr_torrentInit(fLib, [file UTF8String], NULL, 0, &error)))
2139            {
2140                tr_torrentClose(tempTor);
2141               
2142                if (!fOverlayWindow)
2143                    fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2144                [fOverlayWindow setTorrents: files];
2145               
2146                return NSDragOperationCopy;
2147            }
2148        }
2149    }
2150    else if ([[pasteboard types] containsObject: NSURLPboardType])
2151    {
2152        if (!fOverlayWindow)
2153            fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2154        [fOverlayWindow setURL: [[NSURL URLFromPasteboard: pasteboard] relativeString]];
2155       
2156        return NSDragOperationCopy;
2157    }
2158    else;
2159   
2160    return NSDragOperationNone;
2161}
2162
2163- (void) draggingExited: (id <NSDraggingInfo>) info
2164{
2165    if (fOverlayWindow)
2166        [fOverlayWindow fadeOut];
2167}
2168
2169- (BOOL) performDragOperation: (id <NSDraggingInfo>) info
2170{
2171    if (fOverlayWindow)
2172        [fOverlayWindow fadeOut];
2173   
2174    NSPasteboard * pasteboard = [info draggingPasteboard];
2175    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2176    {
2177        //create an array of files that can be opened
2178        NSMutableArray * filesToOpen = [[NSMutableArray alloc] init];
2179        NSEnumerator * enumerator = [[pasteboard propertyListForType: NSFilenamesPboardType] objectEnumerator];
2180        NSString * file;
2181        tr_torrent_t * tempTor;
2182        while ((file = [enumerator nextObject]))
2183        {
2184            int error;
2185            if ((tempTor = tr_torrentInit(fLib, [file UTF8String], NULL, 0, &error)))
2186            {
2187                tr_torrentClose(tempTor);
2188                [filesToOpen addObject: file];
2189            }
2190        }
2191       
2192        [self application: NSApp openFiles: filesToOpen];
2193        [filesToOpen release];
2194       
2195        return YES;
2196    }
2197    else if ([[pasteboard types] containsObject: NSURLPboardType])
2198    {
2199        NSURL * url;
2200        if ((url = [NSURL URLFromPasteboard: pasteboard]))
2201        {
2202            [self openURL: url];
2203            return YES;
2204        }
2205    }
2206    else;
2207   
2208    return NO;
2209}
2210
2211- (void) toggleSmallView: (id) sender
2212{
2213    BOOL makeSmall = [fDefaults boolForKey: @"SmallView"];
2214   
2215    [fTableView setRowHeight: makeSmall ? ROW_HEIGHT_SMALL : ROW_HEIGHT_REGULAR];
2216   
2217    //window min height
2218    NSSize contentMinSize = [fWindow contentMinSize],
2219            contentSize = [[fWindow contentView] frame].size;
2220    contentMinSize.height = contentSize.height - [fScrollView frame].size.height
2221                            + [fTableView rowHeight] + [fTableView intercellSpacing].height;
2222    [fWindow setContentMinSize: contentMinSize];
2223   
2224    //resize for larger min height if not set to auto size
2225    if (![fDefaults boolForKey: @"AutoSize"])
2226    {
2227        if (!makeSmall && contentSize.height < contentMinSize.height)
2228        {
2229            NSRect frame = [fWindow frame];
2230            float heightChange = contentMinSize.height - contentSize.height;
2231            frame.size.height += heightChange;
2232            frame.origin.y -= heightChange;
2233           
2234            [fWindow setFrame: frame display: YES];
2235            [fTableView reloadData];
2236        }
2237    }
2238    else
2239        [self setWindowSizeToFit];
2240}
2241
2242- (void) toggleStatusBar: (id) sender
2243{
2244    [self showStatusBar: [fStatusBar isHidden] animate: YES];
2245    [fDefaults setBool: ![fStatusBar isHidden] forKey: @"StatusBar"];
2246}
2247
2248- (NSRect) windowFrameByAddingHeight: (float) height checkLimits: (BOOL) check
2249{
2250    //convert pixels to points
2251    NSRect windowFrame = [fWindow frame];
2252    NSSize windowSize = [fScrollView convertSize: windowFrame.size fromView: nil];
2253    windowSize.height += height;
2254   
2255    if (check)
2256    {
2257        NSSize minSize = [fScrollView convertSize: [fWindow minSize] fromView: nil];
2258       
2259        if (windowSize.height < minSize.height)
2260            windowSize.height = minSize.height;
2261        else
2262        {
2263            NSSize maxSize = [fScrollView convertSize: [[fWindow screen] visibleFrame].size fromView: nil];
2264            if ([fStatusBar isHidden])
2265                maxSize.height -= [fStatusBar frame].size.height;
2266            if ([fFilterBar isHidden])
2267                maxSize.height -= [fFilterBar frame].size.height;
2268            if (windowSize.height > maxSize.height)
2269                windowSize.height = maxSize.height;
2270        }
2271    }
2272
2273    //convert points to pixels
2274    windowSize = [fScrollView convertSize: windowSize toView: nil];
2275
2276    windowFrame.origin.y -= (windowSize.height - windowFrame.size.height);
2277    windowFrame.size.height = windowSize.height;
2278    return windowFrame;
2279}
2280
2281- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
2282{
2283    if (show != [fStatusBar isHidden])
2284        return;
2285
2286    if (show)
2287        [fStatusBar setHidden: NO];
2288
2289    NSRect frame;
2290    float heightChange = [fStatusBar frame].size.height;
2291    if (!show)
2292        heightChange *= -1;
2293   
2294    //allow bar to show even if not enough room
2295    if (show && ![fDefaults boolForKey: @"AutoSize"])
2296    {
2297        frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2298        float change = [[fWindow screen] visibleFrame].size.height - frame.size.height;
2299        if (change < 0.0)
2300        {
2301            frame = [fWindow frame];
2302            frame.size.height += change;
2303            frame.origin.y -= change;
2304            [fWindow setFrame: frame display: NO animate: NO];
2305        }
2306    }
2307
2308    [self updateUI];
2309   
2310    //set views to not autoresize
2311    unsigned int statsMask = [fStatusBar autoresizingMask];
2312    unsigned int filterMask = [fFilterBar autoresizingMask];
2313    unsigned int scrollMask = [fScrollView autoresizingMask];
2314    [fStatusBar setAutoresizingMask: 0];
2315    [fFilterBar setAutoresizingMask: 0];
2316    [fScrollView setAutoresizingMask: 0];
2317   
2318    frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2319    [fWindow setFrame: frame display: YES animate: animate];
2320   
2321    //re-enable autoresize
2322    [fStatusBar setAutoresizingMask: statsMask];
2323    [fFilterBar setAutoresizingMask: filterMask];
2324    [fScrollView setAutoresizingMask: scrollMask];
2325   
2326    //change min size
2327    NSSize minSize = [fWindow contentMinSize];
2328    minSize.height += heightChange;
2329    [fWindow setContentMinSize: minSize];
2330   
2331    if (!show)
2332        [fStatusBar setHidden: YES];
2333}
2334
2335- (void) toggleFilterBar: (id) sender
2336{
2337    //disable filtering when hiding
2338    if (![fFilterBar isHidden])
2339    {
2340        [fSearchFilterField setStringValue: @""];
2341        [self setFilter: fNoFilterButton];
2342    }
2343
2344    [self showFilterBar: [fFilterBar isHidden] animate: YES];
2345    [fDefaults setBool: ![fFilterBar isHidden] forKey: @"FilterBar"];
2346}
2347
2348- (void) showFilterBar: (BOOL) show animate: (BOOL) animate
2349{
2350    if (show != [fFilterBar isHidden])
2351        return;
2352
2353    if (show)
2354        [fFilterBar setHidden: NO];
2355
2356    NSRect frame;
2357    float heightChange = [fFilterBar frame].size.height;
2358    if (!show)
2359        heightChange *= -1;
2360   
2361    //allow bar to show even if not enough room
2362    if (show && ![fDefaults boolForKey: @"AutoSize"])
2363    {
2364        frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2365        float change = [[fWindow screen] visibleFrame].size.height - frame.size.height;
2366        if (change < 0.0)
2367        {
2368            frame = [fWindow frame];
2369            frame.size.height += change;
2370            frame.origin.y -= change;
2371            [fWindow setFrame: frame display: NO animate: NO];
2372        }
2373    }
2374
2375    //set views to not autoresize
2376    unsigned int filterMask = [fFilterBar autoresizingMask];
2377    unsigned int scrollMask = [fScrollView autoresizingMask];
2378    [fFilterBar setAutoresizingMask: 0];
2379    [fScrollView setAutoresizingMask: 0];
2380   
2381    frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2382    [fWindow setFrame: frame display: YES animate: animate];
2383   
2384    //re-enable autoresize
2385    [fFilterBar setAutoresizingMask: filterMask];
2386    [fScrollView setAutoresizingMask: scrollMask];
2387   
2388    //change min size
2389    NSSize minSize = [fWindow contentMinSize];
2390    minSize.height += heightChange;
2391    [fWindow setContentMinSize: minSize];
2392   
2393    if (!show)
2394    {
2395        [fFilterBar setHidden: YES];
2396        [fWindow makeFirstResponder: fTableView];
2397    }
2398}
2399
2400- (void) toggleAdvancedBar: (id) sender
2401{
2402    [fTableView display];
2403}
2404
2405- (void) doNothing: (id) sender {}
2406
2407- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
2408    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
2409{
2410    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
2411
2412    if ([ident isEqualToString: TOOLBAR_CREATE])
2413    {
2414        [item setLabel: NSLocalizedString(@"Create", "Create toolbar item -> label")];
2415        [item setPaletteLabel: NSLocalizedString(@"Create Torrent File", "Create toolbar item -> palette label")];
2416        [item setToolTip: NSLocalizedString(@"Create torrent file", "Create toolbar item -> tooltip")];
2417        [item setImage: [NSImage imageNamed: @"Create.png"]];
2418        [item setTarget: self];
2419        [item setAction: @selector(createFile:)];
2420        [item setAutovalidates: NO];
2421    }
2422    else if ([ident isEqualToString: TOOLBAR_OPEN])
2423    {
2424        [item setLabel: NSLocalizedString(@"Open", "Open toolbar item -> label")];
2425        [item setPaletteLabel: NSLocalizedString(@"Open Torrent Files", "Open toolbar item -> palette label")];
2426        [item setToolTip: NSLocalizedString(@"Open torrent files", "Open toolbar item -> tooltip")];
2427        [item setImage: [NSImage imageNamed: @"Open.png"]];
2428        [item setTarget: self];
2429        [item setAction: @selector(openShowSheet:)];
2430        [item setAutovalidates: NO];
2431    }
2432    else if ([ident isEqualToString: TOOLBAR_REMOVE])
2433    {
2434        [item setLabel: NSLocalizedString(@"Remove", "Remove toolbar item -> label")];
2435        [item setPaletteLabel: NSLocalizedString(@"Remove Selected", "Remove toolbar item -> palette label")];
2436        [item setToolTip: NSLocalizedString(@"Remove selected transfers", "Remove toolbar item -> tooltip")];
2437        [item setImage: [NSImage imageNamed: @"Remove.png"]];
2438        [item setTarget: self];
2439        [item setAction: @selector(removeNoDelete:)];
2440    }
2441    else if ([ident isEqualToString: TOOLBAR_INFO])
2442    {
2443        [item setLabel: NSLocalizedString(@"Inspector", "Inspector toolbar item -> label")];
2444        [item setPaletteLabel: NSLocalizedString(@"Toggle Inspector", "Inspector toolbar item -> palette label")];
2445        [item setToolTip: NSLocalizedString(@"Toggle the torrent inspector", "Inspector toolbar item -> tooltip")];
2446        [item setImage: [NSImage imageNamed: @"Info.png"]];
2447        [item setTarget: self];
2448        [item setAction: @selector(showInfo:)];
2449        [item setAutovalidates: NO];
2450    }
2451    else if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
2452    {
2453        [item setLabel: NSLocalizedString(@"Pause All", "Pause All toolbar item -> label")];
2454        [item setPaletteLabel: [item label]];
2455        [item setToolTip: NSLocalizedString(@"Pause all transfers", "Pause All toolbar item -> tooltip")];
2456        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
2457        [item setTarget: self];
2458        [item setAction: @selector(stopAllTorrents:)];
2459    }
2460    else if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
2461    {
2462        [item setLabel: NSLocalizedString(@"Resume All", "Resume All toolbar item -> label")];
2463        [item setPaletteLabel: [item label]];
2464        [item setToolTip: NSLocalizedString(@"Resume all transfers", "Resume All toolbar item -> tooltip")];
2465        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
2466        [item setTarget: self];
2467        [item setAction: @selector(resumeAllTorrents:)];
2468    }
2469    else if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
2470    {
2471        [item setLabel: NSLocalizedString(@"Pause", "Pause toolbar item -> label")];
2472        [item setPaletteLabel: NSLocalizedString(@"Pause Selected", "Pause toolbar item -> palette label")];
2473        [item setToolTip: NSLocalizedString(@"Pause selected transfers", "Pause toolbar item -> tooltip")];
2474        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
2475        [item setTarget: self];
2476        [item setAction: @selector(stopSelectedTorrents:)];
2477    }
2478    else if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
2479    {
2480        [item setLabel: NSLocalizedString(@"Resume", "Resume toolbar item -> label")];
2481        [item setPaletteLabel: NSLocalizedString(@"Resume Selected", "Resume toolbar item -> palette label")];
2482        [item setToolTip: NSLocalizedString(@"Resume selected transfers", "Resume toolbar item -> tooltip")];
2483        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
2484        [item setTarget: self];
2485        [item setAction: @selector(resumeSelectedTorrents:)];
2486    }
2487    else if ([ident isEqualToString: TOOLBAR_FILTER])
2488    {
2489        [item setLabel: NSLocalizedString(@"Filter", "Filter toolbar item -> label")];
2490        [item setPaletteLabel: NSLocalizedString(@"Toggle Filter", "Filter toolbar item -> palette label")];
2491        [item setToolTip: NSLocalizedString(@"Toggle the filter bar", "Filter toolbar item -> tooltip")];
2492        [item setImage: [NSImage imageNamed: @"Filter.png"]];
2493        [item setTarget: self];
2494        [item setAction: @selector(toggleFilterBar:)];
2495        [item setAutovalidates: NO];
2496    }
2497    else
2498    {
2499        [item release];
2500        return nil;
2501    }
2502
2503    return item;
2504}
2505
2506- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
2507{
2508    return [NSArray arrayWithObjects:
2509            TOOLBAR_CREATE, TOOLBAR_OPEN, TOOLBAR_REMOVE,
2510            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
2511            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL, TOOLBAR_FILTER, TOOLBAR_INFO,
2512            NSToolbarSeparatorItemIdentifier,
2513            NSToolbarSpaceItemIdentifier,
2514            NSToolbarFlexibleSpaceItemIdentifier,
2515            NSToolbarCustomizeToolbarItemIdentifier, nil];
2516}
2517
2518- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
2519{
2520    return [NSArray arrayWithObjects:
2521            TOOLBAR_CREATE, TOOLBAR_OPEN, TOOLBAR_REMOVE,
2522            NSToolbarSeparatorItemIdentifier,
2523            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
2524            NSToolbarFlexibleSpaceItemIdentifier,
2525            TOOLBAR_FILTER, TOOLBAR_INFO, nil];
2526}
2527
2528- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
2529{
2530    NSString * ident = [toolbarItem itemIdentifier];
2531
2532    //enable remove item
2533    if ([ident isEqualToString: TOOLBAR_REMOVE])
2534        return [fTableView numberOfSelectedRows] > 0;
2535
2536    //enable pause all item
2537    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
2538    {
2539        Torrent * torrent;
2540        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2541        while ((torrent = [enumerator nextObject]))
2542            if ([torrent isActive] || [torrent waitingToStart])
2543                return YES;
2544        return NO;
2545    }
2546
2547    //enable resume all item
2548    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
2549    {
2550        Torrent * torrent;
2551        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2552        while ((torrent = [enumerator nextObject]))
2553            if ([torrent isPaused] && ![torrent waitingToStart])
2554                return YES;
2555        return NO;
2556    }
2557
2558    //enable pause item
2559    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
2560    {
2561        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2562        Torrent * torrent;
2563        while ((torrent = [enumerator nextObject]))
2564        {
2565            if ([torrent isActive] || [torrent waitingToStart])
2566                return YES;
2567        }
2568        return NO;
2569    }
2570   
2571    //enable resume item
2572    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
2573    {
2574        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2575        Torrent * torrent;
2576        while ((torrent = [enumerator nextObject]))
2577        {
2578            if ([torrent isPaused] && ![torrent waitingToStart])
2579                return YES;
2580        }
2581        return NO;
2582    }
2583
2584    return YES;
2585}
2586
2587- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
2588{
2589    SEL action = [menuItem action];
2590
2591    //only enable some items if it is in a context menu or the window is useable
2592    BOOL canUseTable = [fWindow isKeyWindow] || [[menuItem menu] supermenu] != [NSApp mainMenu];
2593
2594    //enable open items
2595    if (action == @selector(openShowSheet:) || action == @selector(openURLShowSheet:))
2596        return [fWindow attachedSheet] == nil;
2597   
2598    //enable sort and advanced bar items
2599    if (action == @selector(setSort:) || action == @selector(toggleAdvancedBar:) || action == @selector(toggleSmallView:))
2600        return [fWindow isVisible];
2601
2602    //enable show info
2603    if (action == @selector(showInfo:))
2604    {
2605        NSString * title = [[fInfoController window] isVisible] ? NSLocalizedString(@"Hide Inspector",
2606                            "View menu -> Inspector") : NSLocalizedString(@"Show Inspector", "View menu -> Inspector");
2607        if (![[menuItem title] isEqualToString: title])
2608                [menuItem setTitle: title];
2609
2610        return YES;
2611    }
2612   
2613    //enable prev/next inspector tab
2614    if (action == @selector(setInfoTab:))
2615        return [[fInfoController window] isVisible];
2616   
2617    //enable toggle status bar
2618    if (action == @selector(toggleStatusBar:))
2619    {
2620        NSString * title = [fStatusBar isHidden] ? NSLocalizedString(@"Show Status Bar", "View menu -> Status Bar")
2621                            : NSLocalizedString(@"Hide Status Bar", "View menu -> Status Bar");
2622        if (![[menuItem title] isEqualToString: title])
2623            [menuItem setTitle: title];
2624
2625        return [fWindow isVisible];
2626    }
2627   
2628    //enable toggle filter bar
2629    if (action == @selector(toggleFilterBar:))
2630    {
2631        NSString * title = [fFilterBar isHidden] ? NSLocalizedString(@"Show Filter Bar", "View menu -> Filter Bar")
2632                            : NSLocalizedString(@"Hide Filter Bar", "View menu -> Filter Bar");
2633        if (![[menuItem title] isEqualToString: title])
2634            [menuItem setTitle: title];
2635
2636        return [fWindow isVisible];
2637    }
2638   
2639    //enable prev/next filter button
2640    if (action == @selector(switchFilter:))
2641        return [fWindow isVisible] && ![fFilterBar isHidden];
2642
2643    //enable reveal in finder
2644    if (action == @selector(revealFile:))
2645        return canUseTable && [fTableView numberOfSelectedRows] > 0;
2646
2647    //enable remove items
2648    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
2649        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteDataAndTorrent:))
2650    {
2651        BOOL warning = NO,
2652            onlyDownloading = [fDefaults boolForKey: @"CheckRemoveDownloading"],
2653            canDelete = action != @selector(removeDeleteTorrent:) && action != @selector(removeDeleteDataAndTorrent:);
2654       
2655        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2656        Torrent * torrent;
2657        while ((torrent = [enumerator nextObject]))
2658        {
2659            if (!warning && [torrent isActive])
2660            {
2661                warning = onlyDownloading ? ![torrent isSeeding] : YES;
2662                if (warning && canDelete)
2663                    break;
2664            }
2665            if (!canDelete && [torrent publicTorrent])
2666            {
2667                canDelete = YES;
2668                if (warning)
2669                    break;
2670            }
2671        }
2672   
2673        //append or remove ellipsis when needed
2674        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
2675        if (warning && [fDefaults boolForKey: @"CheckRemove"])
2676        {
2677            if (![title hasSuffix: ellipsis])
2678                [menuItem setTitle: [title stringByAppendingEllipsis]];
2679        }
2680        else
2681        {
2682            if ([title hasSuffix: ellipsis])
2683                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
2684        }
2685       
2686        return canUseTable && canDelete && [fTableView numberOfSelectedRows] > 0;
2687    }
2688
2689    //enable pause all item
2690    if (action == @selector(stopAllTorrents:))
2691    {
2692        Torrent * torrent;
2693        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2694        while ((torrent = [enumerator nextObject]))
2695            if ([torrent isActive] || [torrent waitingToStart])
2696                return YES;
2697        return NO;
2698    }
2699   
2700    //enable resume all item
2701    if (action == @selector(resumeAllTorrents:))
2702    {
2703        Torrent * torrent;
2704        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2705        while ((torrent = [enumerator nextObject]))
2706            if ([torrent isPaused] && ![torrent waitingToStart])
2707                return YES;
2708        return NO;
2709    }
2710   
2711    //enable resume all waiting item
2712    if (action == @selector(resumeWaitingTorrents:))
2713    {
2714        if (![fDefaults boolForKey: @"Queue"] && ![fDefaults boolForKey: @"QueueSeed"])
2715            return NO;
2716   
2717        Torrent * torrent;
2718        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2719        while ((torrent = [enumerator nextObject]))
2720            if ([torrent isPaused] && [torrent waitingToStart])
2721                return YES;
2722        return NO;
2723    }
2724   
2725    //enable resume selected waiting item
2726    if (action == @selector(resumeSelectedTorrentsNoWait:))
2727    {
2728        if (![fDefaults boolForKey: @"Queue"] && ![fDefaults boolForKey: @"QueueSeed"])
2729            return NO;
2730   
2731        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2732        Torrent * torrent;
2733        while ((torrent = [enumerator nextObject]))
2734            if ([torrent isPaused])
2735                return YES;
2736        return NO;
2737    }
2738
2739    //enable pause item
2740    if (action == @selector(stopSelectedTorrents:))
2741    {
2742        if (!canUseTable)
2743            return NO;
2744   
2745        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2746        Torrent * torrent;
2747        while ((torrent = [enumerator nextObject]))
2748            if ([torrent isActive] || [torrent waitingToStart])
2749                return YES;
2750        return NO;
2751    }
2752   
2753    //enable resume item
2754    if (action == @selector(resumeSelectedTorrents:))
2755    {
2756        if (!canUseTable)
2757            return NO;
2758   
2759        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2760        Torrent * torrent;
2761        while ((torrent = [enumerator nextObject]))
2762            if ([torrent isPaused] && ![torrent waitingToStart])
2763                return YES;
2764        return NO;
2765    }
2766   
2767    //enable announce item
2768    if (action == @selector(announceSelectedTorrents:))
2769    {
2770        if (!canUseTable)
2771            return NO;
2772       
2773        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2774        Torrent * torrent;
2775        NSDate * date;
2776        while ((torrent = [enumerator nextObject]))
2777        {
2778            //time interval returned will be negative
2779            if ([torrent isActive] &&
2780                    (!(date = [torrent announceDate]) || [date timeIntervalSinceNow] <= ANNOUNCE_WAIT_INTERVAL_SECONDS))
2781                return YES;
2782        }
2783        return NO;
2784    }
2785   
2786    //enable reset cache item
2787    if (action == @selector(resetCacheForSelectedTorrents:))
2788    {
2789        if (!canUseTable)
2790            return NO;
2791       
2792        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2793        Torrent * torrent;
2794        while ((torrent = [enumerator nextObject]))
2795        {
2796            if (![torrent isActive])
2797                return YES;
2798        }
2799        return NO;
2800    }
2801   
2802    //enable move torrent file item
2803    if (action == @selector(moveDataFiles:))
2804        return canUseTable && [fTableView numberOfSelectedRows] > 0;
2805   
2806    //enable copy torrent file item
2807    if (action == @selector(copyTorrentFiles:))
2808        return canUseTable && [fTableView numberOfSelectedRows] > 0;
2809   
2810    //enable reverse sort item
2811    if (action == @selector(setSortReverse:))
2812        return ![[fDefaults stringForKey: @"Sort"] isEqualToString: @"Order"];
2813
2814    return YES;
2815}
2816
2817- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument
2818{
2819    NSEnumerator * enumerator;
2820    Torrent * torrent;
2821    BOOL active;
2822
2823    switch (messageType)
2824    {
2825        case kIOMessageSystemWillSleep:
2826            //close all connections before going to sleep and remember we should resume when we wake up
2827            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
2828
2829            //wait for running transfers to stop (5 second timeout)
2830            NSDate * start = [NSDate date];
2831            BOOL timeUp = NO;
2832           
2833            enumerator = [fTorrents objectEnumerator];
2834            while (!timeUp && (torrent = [enumerator nextObject]))
2835                while (![torrent isPaused] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
2836                {
2837                    usleep(100000);
2838                    [torrent update];
2839                }
2840
2841            IOAllowPowerChange(fRootPort, (long) messageArgument);
2842            break;
2843
2844        case kIOMessageCanSystemSleep:
2845            //pevent idle sleep unless all paused
2846            active = NO;
2847            enumerator = [fTorrents objectEnumerator];
2848            while ((torrent = [enumerator nextObject]))
2849                if ([torrent isActive])
2850                {
2851                    active = YES;
2852                    break;
2853                }
2854
2855            if (active)
2856                IOCancelPowerChange(fRootPort, (long) messageArgument);
2857            else
2858                IOAllowPowerChange(fRootPort, (long) messageArgument);
2859            break;
2860
2861        case kIOMessageSystemHasPoweredOn:
2862            //resume sleeping transfers after we wake up
2863            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
2864            [self autoSpeedLimitChange: nil];
2865            break;
2866    }
2867}
2868
2869- (NSMenu *) applicationDockMenu: (NSApplication *) sender
2870{
2871    int seeding = 0, downloading = 0;
2872    NSEnumerator * enumerator = [fTorrents objectEnumerator];
2873    Torrent * torrent;
2874    while ((torrent = [enumerator nextObject]))
2875    {
2876        if ([torrent isSeeding])
2877            seeding++;
2878        else if ([torrent isActive])
2879            downloading++;
2880        else;
2881    }
2882   
2883    NSMenuItem * seedingItem = [fDockMenu itemWithTag: DOCK_SEEDING_TAG],
2884            * downloadingItem = [fDockMenu itemWithTag: DOCK_DOWNLOADING_TAG];
2885   
2886    BOOL hasSeparator = seedingItem || downloadingItem;
2887   
2888    if (seeding > 0)
2889    {
2890        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Seeding",
2891                                                        "Dock item - Seeding"), seeding];
2892        if (!seedingItem)
2893        {
2894            seedingItem = [[[NSMenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""] autorelease];
2895            [seedingItem setTag: DOCK_SEEDING_TAG];
2896            [fDockMenu insertItem: seedingItem atIndex: 0];
2897        }
2898        else
2899            [seedingItem setTitle: title];
2900    }
2901    else
2902    {
2903        if (seedingItem)
2904            [fDockMenu removeItem: seedingItem];
2905    }
2906   
2907    if (downloading > 0)
2908    {
2909        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Downloading",
2910                                                        "Dock item - Downloading"), downloading];
2911        if (!downloadingItem)
2912        {
2913            downloadingItem = [[[NSMenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""] autorelease];
2914            [downloadingItem setTag: DOCK_DOWNLOADING_TAG];
2915            [fDockMenu insertItem: downloadingItem atIndex: seeding > 0 ? 1 : 0];
2916        }
2917        else
2918            [downloadingItem setTitle: title];
2919    }
2920    else
2921    {
2922        if (downloadingItem)
2923            [fDockMenu removeItem: downloadingItem];
2924    }
2925   
2926    if (seeding > 0 || downloading > 0)
2927    {
2928        if (!hasSeparator)
2929            [fDockMenu insertItem: [NSMenuItem separatorItem] atIndex: seeding > 0 && downloading > 0 ? 2 : 1];
2930    }
2931    else
2932    {
2933        if (hasSeparator)
2934            [fDockMenu removeItemAtIndex: 0];
2935    }
2936   
2937    return fDockMenu;
2938}
2939
2940- (void) updateDockBadge: (NSNotification *) notification
2941{
2942    [fBadger updateBadge];
2943}
2944
2945- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
2946{
2947    //if auto size is enabled, the current frame shouldn't need to change
2948    NSRect frame = [fDefaults boolForKey: @"AutoSize"] ? [window frame] : [self sizedWindowFrame];
2949   
2950    frame.size.width = [fDefaults boolForKey: @"SmallView"] ? [fWindow minSize].width : WINDOW_REGULAR_WIDTH;
2951    return frame;
2952}
2953
2954- (void) setWindowSizeToFit
2955{
2956    if ([fDefaults boolForKey: @"AutoSize"])
2957    {
2958        [fScrollView setHasVerticalScroller: NO];
2959        [fWindow setFrame: [self sizedWindowFrame] display: YES animate: YES];
2960        [fScrollView setHasVerticalScroller: YES];
2961    }
2962}
2963
2964- (NSRect) sizedWindowFrame
2965{
2966    float heightChange = [fDisplayedTorrents count] * ([fTableView rowHeight] +
2967            [fTableView intercellSpacing].height) - [fScrollView frame].size.height;
2968    return [self windowFrameByAddingHeight: heightChange checkLimits: YES];
2969}
2970
2971- (void) showMainWindow: (id) sender
2972{
2973    [fWindow makeKeyAndOrderFront: nil];
2974}
2975
2976- (void) windowDidBecomeKey: (NSNotification *) notification
2977{
2978    [fBadger clearCompleted];
2979}
2980
2981- (NSSize) windowWillResize: (NSWindow *) sender toSize: (NSSize) proposedFrameSize
2982{
2983    //only resize horizontally if autosize is enabled
2984    if ([fDefaults boolForKey: @"AutoSize"])
2985        proposedFrameSize.height = [fWindow frame].size.height;
2986    return proposedFrameSize;
2987}
2988
2989- (void) windowDidResize: (NSNotification *) notification
2990{
2991    //size search filter to not overlap buttons
2992    float pointX = NSMaxX([fPauseFilterButton frame]) + 5.0;
2993    NSRect oldFrame = [fSearchFilterField frame],
2994            frame = NSMakeRect(pointX, oldFrame.origin.y, NSMaxX(oldFrame) - pointX, oldFrame.size.height);
2995   
2996    BOOL show;
2997    if (show = (frame.size.width >= SEARCH_FILTER_MIN_WIDTH))
2998    {
2999        //make sure it is not too long
3000        if (frame.size.width > SEARCH_FILTER_MAX_WIDTH)
3001        {
3002            float different = frame.size.width - SEARCH_FILTER_MAX_WIDTH;
3003            frame.origin.x += different;
3004            frame.size.width -= different;
3005        }
3006        [fSearchFilterField setFrame: frame];
3007    }
3008   
3009    //hide search filter if it overlaps filter buttons
3010    [fSearchFilterField setHidden: !show];
3011}
3012
3013- (void) linkHomepage: (id) sender
3014{
3015    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
3016}
3017
3018- (void) linkForums: (id) sender
3019{
3020    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
3021}
3022
3023- (void) prepareForUpdate: (NSNotification *) notification
3024{
3025    fUpdateInProgress = YES;
3026}
3027
3028- (NSDictionary *) registrationDictionaryForGrowl
3029{
3030    NSArray * notifications = [NSArray arrayWithObjects: GROWL_DOWNLOAD_COMPLETE, GROWL_SEEDING_COMPLETE,
3031                                                            GROWL_AUTO_ADD, GROWL_AUTO_SPEED_LIMIT, nil];
3032    return [NSDictionary dictionaryWithObjectsAndKeys: notifications, GROWL_NOTIFICATIONS_ALL,
3033                                notifications, GROWL_NOTIFICATIONS_DEFAULT, nil];
3034}
3035
3036- (void) growlNotificationWasClicked: (id) clickContext
3037{
3038    if (!clickContext || ![clickContext isKindOfClass: [NSDictionary class]])
3039        return;
3040   
3041    NSString * type = [clickContext objectForKey: @"Type"], * location;
3042    if (([type isEqualToString: GROWL_DOWNLOAD_COMPLETE] || [type isEqualToString: GROWL_SEEDING_COMPLETE])
3043            && (location = [clickContext objectForKey: @"Location"]))
3044        [[NSWorkspace sharedWorkspace] selectFile: location inFileViewerRootedAtPath: nil];
3045}
3046
3047#warning move to additions
3048- (NSString *) applicationSupportFolder
3049{
3050    return [[NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex: 0]
3051                stringByAppendingPathComponent: [[NSProcessInfo processInfo] processName]];
3052}
3053
3054- (void) ipcQuit
3055{
3056    fRemoteQuit = YES;
3057    [NSApp terminate: self];
3058}
3059
3060- (NSArray *) ipcGetTorrentsByID: (NSArray *) idlist
3061{
3062    if (!idlist)
3063        return fTorrents;
3064   
3065    NSMutableArray * torrents = [NSMutableArray array];
3066   
3067    NSEnumerator * torrentEnum = [fTorrents objectEnumerator], * idEnum;
3068    int torId;
3069    Torrent * torrent;
3070    NSNumber * tempId;
3071    while ((torrent = [torrentEnum nextObject]))
3072    {
3073        torId = [torrent torrentID];
3074       
3075        idEnum = [idlist objectEnumerator];
3076        while ((tempId = [idEnum nextObject]))
3077        {
3078            if ([tempId intValue] == torId)
3079            {
3080                [torrents addObject: torrent];
3081                break;
3082            }
3083        }
3084    }
3085
3086    return torrents;
3087}
3088
3089- (NSArray *) ipcGetTorrentsByHash: (NSArray *) hashlist
3090{
3091    if (!hashlist)
3092        return fTorrents;
3093   
3094    NSMutableArray * torrents = [NSMutableArray array];
3095   
3096    NSEnumerator * torrentEnum = [fTorrents objectEnumerator], * hashEnum;
3097    NSString * torHash, * tempHash;
3098    Torrent * torrent;
3099    while ((torrent = [torrentEnum nextObject]))
3100    {
3101        torHash = [torrent hashString];
3102       
3103        hashEnum = [hashlist objectEnumerator];
3104        while ((tempHash = [hashEnum nextObject]))
3105        {
3106            if ([torHash caseInsensitiveCompare: tempHash] == NSOrderedSame)
3107            {
3108                [torrents addObject: torrent];
3109                break;
3110            }
3111        }
3112    }
3113   
3114    return torrents;
3115}
3116
3117- (BOOL) ipcAddTorrents: (NSArray *) torrents
3118{
3119    /* 'torrents' is NSArray of NSString torrent file paths, should
3120       return NO if torrent fails to load */
3121    return NO;
3122}
3123
3124- (BOOL) ipcAddTorrentFile: (NSString *) path
3125                 directory: (NSString *) dir
3126{
3127    /* 'path' is path to torrent file, 'dir' is the directory it
3128       should download it's files to and may be nil, should return NO
3129       if torrent fails to load */
3130    return NO;
3131}
3132
3133- (BOOL) ipcAddTorrentFileAutostart: (NSString *) path
3134                          directory: (NSString *) dir
3135                          autostart: (BOOL) autostart
3136{
3137    /* 'path' is path to torrent file, 'dir' is the directory it
3138       should download it's files to and may be nil, 'autostart' is a
3139       boolean indicating if the torrent should be automatically
3140       started (or queued to start, I guess), should return NO if
3141       torrent fails to load */
3142    return NO;
3143}
3144
3145- (BOOL) ipcAddTorrentData: (NSData *) data
3146                 directory: (NSString *) dir
3147{
3148    /* 'data' is the contents of a torrent file, 'dir' is the
3149       directory it should download it's files to and may be nil,
3150       should return NO if torrent fails to load */
3151    return NO;
3152}
3153
3154- (BOOL) ipcAddTorrentDataAutostart: (NSData *) path
3155                          directory: (NSString *) dir
3156                          autostart: (BOOL) autostart
3157{
3158    /* 'data' is the contents of a torrent file, 'dir' is the
3159       directory it should download it's files to and may be nil,
3160       'autostart' is a boolean indicating if the torrent should be
3161       automatically started (or queued to start, I guess), should
3162       return NO if torrent fails to load */
3163    return NO;
3164}
3165
3166- (BOOL) ipcStartTorrents: (NSArray *) torrents
3167{
3168    if (!torrents)
3169        [self resumeAllTorrents: self];
3170    else
3171        [self resumeTorrents: torrents];
3172
3173    return YES;
3174}
3175
3176- (BOOL) ipcStopTorrents: (NSArray *) torrents
3177{
3178    if (!torrents)
3179        [self stopAllTorrents: self];
3180    else
3181        [self stopTorrents: torrents];
3182
3183    return YES;
3184}
3185
3186- (BOOL) ipcRemoveTorrents: (NSArray *) torrents
3187{
3188    if (!torrents)
3189        torrents = [NSArray arrayWithArray: fTorrents];
3190
3191    [self confirmRemoveTorrents: torrents deleteData: NO deleteTorrent: NO];
3192
3193    return YES;
3194}
3195
3196@end
Note: See TracBrowser for help on using the repository browser.