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

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

get the basics of file creation working, some better error checking, and combine the name and location fields

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