source: trunk/macosx/Controller.m @ 2149

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

Merge file selection and torrent creation into the main branch.

The new code for these features is under a new license.

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