source: trunk/macosx/Controller.m @ 2205

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

reflect some of the changes in libT (some, which means it still doesn't compile ;))

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