source: trunk/macosx/Controller.m @ 1219

Last change on this file since 1219 was 1219, checked in by livings124, 16 years ago

show an error dialog when trying to open another copy of Transmission, and make the formatting match the cocoa-style

  • Property svn:keywords set to Date Rev Author Id
File size: 95.5 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 1219 2006-12-15 01:18:36Z livings124 $
3 *
4 * Copyright (c) 2005-2006 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 "StringAdditions.h"
32#import "UKKQueue.h"
33#import "ActionMenuSpeedToDisplayLimitTransformer.h"
34#import "ActionMenuRatioToDisplayRatioTransformer.h"
35#import "ExpandedPathToPathTransformer.h"
36#import "ExpandedPathToIconTransformer.h"
37#import "SpeedLimitToTurtleIconTransformer.h"
38
39#import <Sparkle/Sparkle.h>
40
41#define TOOLBAR_OPEN            @"Toolbar Open"
42#define TOOLBAR_REMOVE          @"Toolbar Remove"
43#define TOOLBAR_INFO            @"Toolbar Info"
44#define TOOLBAR_PAUSE_ALL       @"Toolbar Pause All"
45#define TOOLBAR_RESUME_ALL      @"Toolbar Resume All"
46#define TOOLBAR_PAUSE_SELECTED  @"Toolbar Pause Selected"
47#define TOOLBAR_RESUME_SELECTED @"Toolbar Resume Selected"
48#define TOOLBAR_FILTER          @"Toolbar Toggle Filter"
49
50#define GROWL_DOWNLOAD_COMPLETE @"Download Complete"
51#define GROWL_SEEDING_COMPLETE  @"Seeding Complete"
52#define GROWL_AUTO_ADD          @"Torrent Auto Added"
53#define GROWL_AUTO_SPEED_LIMIT  @"Speed Limit Auto Changed"
54
55#define TORRENT_TABLE_VIEW_DATA_TYPE    @"TorrentTableViewDataType"
56
57#define ROW_HEIGHT_REGULAR      65.0
58#define ROW_HEIGHT_SMALL        40.0
59#define WINDOW_REGULAR_WIDTH    468.0
60
61#define UPDATE_UI_SECONDS           1.0
62#define AUTO_SPEED_LIMIT_SECONDS    5.0
63
64#define WEBSITE_URL @"http://transmission.m0k.org/"
65#define FORUM_URL   @"http://transmission.m0k.org/forum/"
66
67static void sleepCallBack(void * controller, io_service_t y, natural_t messageType, void * messageArgument)
68{
69    Controller * c = controller;
70    [c sleepCallBack: messageType argument: messageArgument];
71}
72
73@implementation Controller
74
75+ (void) initialize
76{
77    //make sure another Transmission.app isn't running already
78    NSString * bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
79    int processIdentifier = [[NSProcessInfo processInfo] processIdentifier];
80
81    NSDictionary * dic;
82    NSEnumerator * enumerator = [[[NSWorkspace sharedWorkspace] launchedApplications] objectEnumerator];
83    while ((dic = [enumerator nextObject]))
84    {
85        if ([[dic objectForKey: @"NSApplicationBundleIdentifier"] isEqualToString: bundleIdentifier]
86            && [[dic objectForKey: @"NSApplicationProcessIdentifier"] intValue] != processIdentifier)
87        {
88            NSAlert * alert = [[NSAlert alloc] init];
89            [alert addButtonWithTitle: NSLocalizedString(@"Quit", "Transmission already running alert -> button")];
90            [alert setMessageText: NSLocalizedString(@"Transmission is already running",
91                                                    "Transmission already running alert -> title")];
92            [alert setInformativeText: NSLocalizedString(@"There is already a copy of Transmission running. "
93                "Please quit that instance before opening another.", "Transmission already running alert -> message")];
94            [alert setAlertStyle: NSWarningAlertStyle];
95           
96            [alert runModal];
97            [alert release];
98           
99            //activate the already running instance, then kill ourselves right away
100            [[NSWorkspace sharedWorkspace] launchApplication: [dic objectForKey: @"NSApplicationPath"]];
101            exit(0);
102        }
103    }
104
105
106    [[NSUserDefaults standardUserDefaults] registerDefaults: [NSDictionary dictionaryWithContentsOfFile:
107        [[NSBundle mainBundle] pathForResource: @"Defaults" ofType: @"plist"]]];
108   
109    //set custom value transformers
110    ActionMenuSpeedToDisplayLimitTransformer * limitTransformer =
111                        [[[ActionMenuSpeedToDisplayLimitTransformer alloc] init] autorelease];
112    [NSValueTransformer setValueTransformer: limitTransformer forName: @"ActionMenuSpeedToDisplayLimitTransformer"];
113   
114    ActionMenuRatioToDisplayRatioTransformer * ratioTransformer =
115                        [[[ActionMenuRatioToDisplayRatioTransformer alloc] init] autorelease];
116    [NSValueTransformer setValueTransformer: ratioTransformer forName: @"ActionMenuRatioToDisplayRatioTransformer"];
117   
118    ExpandedPathToPathTransformer * pathTransformer =
119                        [[[ExpandedPathToPathTransformer alloc] init] autorelease];
120    [NSValueTransformer setValueTransformer: pathTransformer forName: @"ExpandedPathToPathTransformer"];
121   
122    ExpandedPathToIconTransformer * iconTransformer =
123                        [[[ExpandedPathToIconTransformer alloc] init] autorelease];
124    [NSValueTransformer setValueTransformer: iconTransformer forName: @"ExpandedPathToIconTransformer"];
125   
126    SpeedLimitToTurtleIconTransformer * speedLimitIconTransformer =
127                        [[[SpeedLimitToTurtleIconTransformer alloc] init] autorelease];
128    [NSValueTransformer setValueTransformer: speedLimitIconTransformer forName: @"SpeedLimitToTurtleIconTransformer"];
129}
130
131- (id) init
132{
133    if ((self = [super init]))
134    {
135        fLib = tr_init();
136       
137        fTorrents = [[NSMutableArray alloc] initWithCapacity: 10];
138        fDisplayedTorrents = [[NSMutableArray alloc] initWithCapacity: 10];
139        fPendingTorrentDownloads = [[NSMutableDictionary alloc] init];
140       
141        fDefaults = [NSUserDefaults standardUserDefaults];
142       
143        fMessageController = [[MessageWindowController alloc] initWithWindowNibName: @"MessageWindow"];
144        fInfoController = [[InfoWindowController alloc] initWithWindowNibName: @"InfoWindow"];
145        fPrefsController = [[PrefsController alloc] initWithWindowNibName: @"PrefsWindow" handle: fLib];
146       
147        fBadger = [[Badger alloc] init];
148       
149        [GrowlApplicationBridge setGrowlDelegate: self];
150       
151        [[UKKQueue sharedFileWatcher] setDelegate: self];
152    }
153    return self;
154}
155
156- (void) dealloc
157{
158    [[NSNotificationCenter defaultCenter] removeObserver: self];
159   
160    [fInfoController release];
161    [fMessageController release];
162    [fPrefsController release];
163   
164    [fToolbar release];
165    [fTorrents release];
166    [fDisplayedTorrents release];
167    [fBadger release];
168   
169    [fSortType release];
170    [fFilterType release];
171   
172    [fAutoImportedNames release];
173    [fPendingTorrentDownloads release];
174   
175    tr_close(fLib);
176    [super dealloc];
177}
178
179- (void) awakeFromNib
180{
181    [fStatusBar setBackgroundImage: [NSImage imageNamed: @"StatusBarBackground.png"]];
182    [fFilterBar setBackgroundImage: [NSImage imageNamed: @"FilterBarBackground.png"]];
183   
184    [fWindow setAcceptsMouseMovedEvents: YES]; //ensure filter buttons display correctly
185
186    fToolbar = [[NSToolbar alloc] initWithIdentifier: @"Transmission Toolbar"];
187    [fToolbar setDelegate: self];
188    [fToolbar setAllowsUserCustomization: YES];
189    [fToolbar setAutosavesConfiguration: YES];
190    [fWindow setToolbar: fToolbar];
191    [fWindow setDelegate: self];
192   
193    [fWindow makeFirstResponder: fTableView];
194    [fWindow setExcludedFromWindowsMenu: YES];
195   
196    //set table size
197    if ([fDefaults boolForKey: @"SmallView"])
198        [fTableView setRowHeight: ROW_HEIGHT_SMALL];
199   
200    //window min height
201    NSSize contentMinSize = [fWindow contentMinSize];
202    contentMinSize.height = [[fWindow contentView] frame].size.height - [fScrollView frame].size.height
203                                + [fTableView rowHeight] + [fTableView intercellSpacing].height;
204    [fWindow setContentMinSize: contentMinSize];
205   
206    //set info and filter keyboard shortcuts
207    unichar rightChar = NSRightArrowFunctionKey, leftChar = NSLeftArrowFunctionKey;
208    [fNextInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & rightChar length: 1]];
209    [fPrevInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & leftChar length: 1]];
210   
211    [fNextFilterItem setKeyEquivalent: [NSString stringWithCharacters: & rightChar length: 1]];
212    [fNextFilterItem setKeyEquivalentModifierMask: NSCommandKeyMask + NSAlternateKeyMask];
213    [fPrevFilterItem setKeyEquivalent: [NSString stringWithCharacters: & leftChar length: 1]];
214    [fPrevFilterItem setKeyEquivalentModifierMask: NSCommandKeyMask + NSAlternateKeyMask];
215   
216    //set up filter bar
217    NSView * contentView = [fWindow contentView];
218    [fFilterBar setHidden: YES];
219   
220    NSRect filterBarFrame = [fFilterBar frame];
221    filterBarFrame.size.width = [fWindow frame].size.width;
222    [fFilterBar setFrame: filterBarFrame];
223   
224    [contentView addSubview: fFilterBar];
225    [fFilterBar setFrameOrigin: NSMakePoint(0, NSMaxY([contentView frame]))];
226   
227    [self showFilterBar: [fDefaults boolForKey: @"FilterBar"] animate: NO];
228   
229    //set up status bar
230    [fStatusBar setHidden: YES];
231   
232    NSRect statusBarFrame = [fStatusBar frame];
233    statusBarFrame.size.width = [fWindow frame].size.width;
234    [fStatusBar setFrame: statusBarFrame];
235   
236    [contentView addSubview: fStatusBar];
237    [fStatusBar setFrameOrigin: NSMakePoint(0, NSMaxY([contentView frame]))];
238    [self showStatusBar: [fDefaults boolForKey: @"StatusBar"] animate: NO];
239
240    [fActionButton setToolTip: NSLocalizedString(@"Shortcuts for changing global settings.",
241                                "Main window -> 1st bottom left button (action) tooltip")];
242    [fSpeedLimitButton setToolTip: NSLocalizedString(@"Speed Limit overrides the total bandwidth limits with its own limits.",
243                                "Main window -> 2nd bottom left button (turtle) tooltip")];
244   
245    [fPrefsController setUpdater: fUpdater];
246   
247    [fTableView setTorrents: fDisplayedTorrents];
248    [[fTableView tableColumnWithIdentifier: @"Torrent"] setDataCell: [[TorrentCell alloc] init]];
249
250    [fTableView registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType,
251                                            NSURLPboardType, TORRENT_TABLE_VIEW_DATA_TYPE, nil]];
252
253    //register for sleep notifications
254    IONotificationPortRef notify;
255    io_object_t iterator;
256    if (fRootPort = IORegisterForSystemPower(self, & notify, sleepCallBack, & iterator))
257        CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify), kCFRunLoopCommonModes);
258    else
259        NSLog(@"Could not IORegisterForSystemPower");
260
261    //load torrents from history
262    Torrent * torrent;
263    NSDictionary * historyItem;
264    NSEnumerator * enumerator = [[fDefaults arrayForKey: @"History"] objectEnumerator];
265    while ((historyItem = [enumerator nextObject]))
266        if ((torrent = [[Torrent alloc] initWithHistory: historyItem lib: fLib]))
267        {
268            [fTorrents addObject: torrent];
269            [torrent release];
270        }
271   
272    //set sort
273    fSortType = [[fDefaults stringForKey: @"Sort"] retain];
274   
275    NSMenuItem * currentSortItem, * currentSortActionItem;
276    if ([fSortType isEqualToString: @"Name"])
277    {
278        currentSortItem = fNameSortItem;
279        currentSortActionItem = fNameSortActionItem;
280    }
281    else if ([fSortType isEqualToString: @"State"])
282    {
283        currentSortItem = fStateSortItem;
284        currentSortActionItem = fStateSortActionItem;
285    }
286    else if ([fSortType isEqualToString: @"Progress"])
287    {
288        currentSortItem = fProgressSortItem;
289        currentSortActionItem = fProgressSortActionItem;
290    }
291    else if ([fSortType isEqualToString: @"Date"])
292    {
293        currentSortItem = fDateSortItem;
294        currentSortActionItem = fDateSortActionItem;
295    }
296    else
297    {
298        currentSortItem = fOrderSortItem;
299        currentSortActionItem = fOrderSortActionItem;
300    }
301    [currentSortItem setState: NSOnState];
302    [currentSortActionItem setState: NSOnState];
303   
304    //set filter
305    fFilterType = [[fDefaults stringForKey: @"Filter"] retain];
306
307    BarButton * currentFilterButton;
308    if ([fFilterType isEqualToString: @"Pause"])
309        currentFilterButton = fPauseFilterButton;
310    else if ([fFilterType isEqualToString: @"Seed"])
311        currentFilterButton = fSeedFilterButton;
312    else if ([fFilterType isEqualToString: @"Download"])
313        currentFilterButton = fDownloadFilterButton;
314    else
315        currentFilterButton = fNoFilterButton;
316
317    [currentFilterButton setEnabled: YES];
318   
319    //observe notifications
320    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
321   
322    [nc addObserver: fInfoController selector: @selector(updateInfoSettings)
323                    name: @"UpdateInfoSettings" object: nil];
324   
325    [nc addObserver: self selector: @selector(torrentFinishedDownloading:)
326                    name: @"TorrentFinishedDownloading" object: nil];
327   
328    [nc addObserver: self selector: @selector(updateControlTint:)
329                    name: NSControlTintDidChangeNotification object: nil];
330   
331    [nc addObserver: self selector: @selector(prepareForUpdate:)
332                    name: SUUpdaterWillRestartNotification object: nil];
333    fUpdateInProgress = NO;
334   
335    [nc addObserver: self selector: @selector(autoSpeedLimitChange:)
336                    name: @"AutoSpeedLimitChange" object: nil];
337   
338    [nc addObserver: self selector: @selector(changeAutoImport)
339                    name: @"AutoImportSettingChange" object: nil];
340   
341    [nc addObserver: self selector: @selector(setWindowSizeToFit)
342                    name: @"AutoSizeSettingChange" object: nil];
343   
344    [nc addObserver: self selector: @selector(makeWindowKey)
345                    name: @"MakeWindowKey" object: nil];
346   
347    //check to start another because of stopped torrent
348    [nc addObserver: self selector: @selector(checkWaitingForStopped:)
349                    name: @"StoppedDownloading" object: nil];
350   
351    //check all torrents for starting
352    [nc addObserver: self selector: @selector(globalStartSettingChange:)
353                    name: @"GlobalStartSettingChange" object: nil];
354   
355    //check if torrent should now start
356    [nc addObserver: self selector: @selector(torrentStoppedForRatio:)
357                    name: @"TorrentStoppedForRatio" object: nil];
358   
359    //change that just impacts the dock badge
360    [nc addObserver: self selector: @selector(resetDockBadge:)
361                    name: @"DockBadgeChange" object: nil];
362
363    //timer to update the interface every second
364    fCompleted = 0;
365    [self updateUI: nil];
366    fTimer = [NSTimer scheduledTimerWithTimeInterval: UPDATE_UI_SECONDS target: self
367        selector: @selector(updateUI:) userInfo: nil repeats: YES];
368    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSModalPanelRunLoopMode];
369    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSEventTrackingRunLoopMode];
370   
371    [self applyFilter: nil];
372   
373    [fWindow makeKeyAndOrderFront: nil];
374
375    if ([fDefaults boolForKey: @"InfoVisible"])
376        [self showInfo: nil];
377   
378    //timer to auto toggle speed limit
379    [self autoSpeedLimitChange: nil];
380    fSpeedLimitTimer = [NSTimer scheduledTimerWithTimeInterval: AUTO_SPEED_LIMIT_SECONDS target: self
381        selector: @selector(autoSpeedLimit:) userInfo: nil repeats: YES];
382   
383    //auto importing
384    fAutoImportedNames = [[NSMutableArray alloc] init];
385    [self checkAutoImportDirectory];
386}
387
388- (void) applicationDidFinishLaunching: (NSNotification *) notification
389{
390    [NSApp setServicesProvider: self];
391   
392    //register for dock icon drags
393    [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self
394        andSelector: @selector(handleOpenContentsEvent:replyEvent:)
395        forEventClass: kCoreEventClass andEventID: kAEOpenContents];
396}
397
398- (BOOL) applicationShouldHandleReopen: (NSApplication *) app hasVisibleWindows: (BOOL) visibleWindows
399{
400    if (![fWindow isVisible] && ![[fPrefsController window] isVisible])
401        [fWindow makeKeyAndOrderFront: nil];
402    return NO;
403}
404
405- (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication *) sender
406{
407    if (!fUpdateInProgress && [fDefaults boolForKey: @"CheckQuit"])
408    {
409        int active = 0, downloading = 0;
410        Torrent * torrent;
411        NSEnumerator * enumerator = [fTorrents objectEnumerator];
412        while ((torrent = [enumerator nextObject]))
413            if ([torrent isActive])
414            {
415                active++;
416                if (![torrent isSeeding])
417                    downloading++;
418            }
419
420        if ([fDefaults boolForKey: @"CheckQuitDownloading"] ? downloading > 0 : active > 0)
421        {
422            NSString * message = active == 1
423                ? NSLocalizedString(@"There is an active transfer. Do you really want to quit?",
424                    "Confirm Quit panel -> message")
425                : [NSString stringWithFormat: NSLocalizedString(@"There are %d active transfers. Do you really want to quit?",
426                    "Confirm Quit panel -> message"), active];
427
428            NSBeginAlertSheet(NSLocalizedString(@"Confirm Quit", "Confirm Quit panel -> title"),
429                                NSLocalizedString(@"Quit", "Confirm Quit panel -> button"),
430                                NSLocalizedString(@"Cancel", "Confirm Quit panel -> button"), nil, fWindow, self,
431                                @selector(quitSheetDidEnd:returnCode:contextInfo:), nil, nil, message);
432            return NSTerminateLater;
433        }
434    }
435
436    return NSTerminateNow;
437}
438
439- (void) quitSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo
440{
441    [NSApp replyToApplicationShouldTerminate: returnCode == NSAlertDefaultReturn];
442}
443
444- (void) applicationWillTerminate: (NSNotification *) notification
445{
446    //remove all torrent downloads
447    NSEnumerator * enumerator = [[fPendingTorrentDownloads allValues] objectEnumerator];
448    NSDictionary * downloadDict;
449    NSURLDownload * download;
450    while ((downloadDict = [enumerator nextObject]))
451    {
452        download = [downloadDict objectForKey: @"Download"];
453        [download cancel];
454        [download release];
455    }
456    [fPendingTorrentDownloads removeAllObjects];
457   
458    //stop timers
459    [fSpeedLimitTimer invalidate];
460    [fTimer invalidate];
461   
462    //save history and stop running torrents
463    [self updateTorrentHistory];
464    [fTorrents makeObjectsPerformSelector: @selector(stopTransferForQuit)];
465   
466    //disable NAT traversal
467    tr_natTraversalDisable(fLib);
468   
469    //remember window states and close all windows
470    [fDefaults setBool: [[fInfoController window] isVisible] forKey: @"InfoVisible"];
471    [[NSApp windows] makeObjectsPerformSelector: @selector(close)];
472    [self showStatusBar: NO animate: NO];
473    [self showFilterBar: NO animate: NO];
474   
475    //clear badge
476    [fBadger clearBadge];
477
478    //end quickly if the app is updating
479    if (fUpdateInProgress)
480        return;
481
482    //wait for running transfers to stop (5 second timeout) and for NAT to be disabled
483    NSDate * start = [NSDate date];
484    BOOL timeUp = NO;
485   
486    enumerator = [fTorrents objectEnumerator];
487    Torrent * torrent;
488    while (!timeUp && ((torrent = [enumerator nextObject]) || tr_natTraversalStatus(fLib) != TR_NAT_TRAVERSAL_DISABLED))
489        while (![torrent isPaused] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
490        {
491            usleep(100000);
492            [torrent update];
493        }
494}
495
496- (void) handleOpenContentsEvent: (NSAppleEventDescriptor *) event replyEvent: (NSAppleEventDescriptor *) replyEvent
497{
498    NSString * urlString = nil;
499
500    NSAppleEventDescriptor * directObject = [event paramDescriptorForKeyword: keyDirectObject];
501    if ([directObject descriptorType] == typeAEList)
502    {
503        unsigned i;
504        for (i = 1; i <= [directObject numberOfItems]; i++)
505            if ((urlString = [[directObject descriptorAtIndex: i] stringValue]))
506                break;
507    }
508    else
509        urlString = [directObject stringValue];
510   
511    if (urlString)
512        [self openURL: [[[NSURL alloc] initWithString: urlString] autorelease]];
513}
514
515- (void) download: (NSURLDownload *) download decideDestinationWithSuggestedFilename: (NSString *) suggestedName
516{
517    if ([[suggestedName pathExtension] caseInsensitiveCompare: @"torrent"] != NSOrderedSame)
518    {
519        [download cancel];
520       
521        NSRunAlertPanel(NSLocalizedString(@"Torrent download failed",
522            @"Download not a torrent -> title"), [NSString stringWithFormat:
523            NSLocalizedString(@"It appears that the file \"%@\" from %@ is not a torrent file.",
524            @"Download not a torrent -> message"), suggestedName,
525            [[[[download request] URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding]],
526            NSLocalizedString(@"OK", @"Download not a torrent -> button"), nil, nil);
527       
528        [download release];
529    }
530    else
531        [download setDestination: [NSTemporaryDirectory() stringByAppendingPathComponent: [suggestedName lastPathComponent]]
532                    allowOverwrite: NO];
533}
534
535-(void) download: (NSURLDownload *) download didCreateDestination: (NSString *) path
536{
537    [fPendingTorrentDownloads setObject: [NSDictionary dictionaryWithObjectsAndKeys:
538                    path, @"Path", download, @"Download", nil] forKey: [[download request] URL]];
539}
540
541- (void) download: (NSURLDownload *) download didFailWithError: (NSError *) error
542{
543    NSRunAlertPanel(NSLocalizedString(@"Torrent download failed",
544        @"Torrent download error -> title"), [NSString stringWithFormat:
545        NSLocalizedString(@"The torrent could not be downloaded from %@ because an error occurred (%@).",
546        @"Torrent download failed -> message"),
547        [[[[download request] URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding],
548        [error localizedDescription]], NSLocalizedString(@"OK", @"Torrent download failed -> button"), nil, nil);
549   
550    [fPendingTorrentDownloads removeObjectForKey: [[download request] URL]];
551    [download release];
552}
553
554- (void) downloadDidFinish: (NSURLDownload *) download
555{
556    NSString * path = [[fPendingTorrentDownloads objectForKey: [[download request] URL]] objectForKey: @"Path"];
557   
558    [self openFiles: [NSArray arrayWithObject: path] ignoreDownloadFolder:
559        ![[fDefaults stringForKey: @"DownloadChoice"] isEqualToString: @"Constant"] forceDeleteTorrent: YES];
560   
561    [fPendingTorrentDownloads removeObjectForKey: [[download request] URL]];
562    [download release];
563   
564    //delete torrent file if it wasn't already
565    [[NSFileManager defaultManager] removeFileAtPath: path handler: nil];
566}
567
568- (void) application: (NSApplication *) app openFiles: (NSArray *) filenames
569{
570    [self openFiles: filenames ignoreDownloadFolder: NO forceDeleteTorrent: NO];
571}
572
573- (void) openFiles: (NSArray *) filenames ignoreDownloadFolder: (BOOL) ignore forceDeleteTorrent: (BOOL) delete
574{
575    NSString * downloadChoice = [fDefaults stringForKey: @"DownloadChoice"];
576    if (ignore || [downloadChoice isEqualToString: @"Ask"])
577    {
578        [self openFilesAsk: [filenames mutableCopy] forceDeleteTorrent: delete];
579        return;
580    }
581   
582    Torrent * torrent;
583    NSString * torrentPath;
584    NSEnumerator * enumerator = [filenames objectEnumerator];
585    while ((torrentPath = [enumerator nextObject]))
586    {
587        if (!(torrent = [[Torrent alloc] initWithPath: torrentPath forceDeleteTorrent: delete lib: fLib]))
588            continue;
589
590        //add it to the "File > Open Recent" menu
591        [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
592
593        NSString * folder = [downloadChoice isEqualToString: @"Constant"]
594            ? [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath]
595            : [torrentPath stringByDeletingLastPathComponent];
596       
597        [torrent setDownloadFolder: folder];
598        [torrent update];
599        [self attemptToStartAuto: torrent];
600       
601        [fTorrents addObject: torrent];
602        [torrent release];
603    }
604
605    [self updateUI: nil];
606    [self applyFilter: nil];
607   
608    [self updateTorrentHistory];
609}
610
611//called by the main open method to show sheet for choosing download location
612- (void) openFilesAsk: (NSMutableArray *) files forceDeleteTorrent: (BOOL) delete
613{
614    NSString * torrentPath;
615    Torrent * torrent;
616   
617    //determine next file that can be opened
618    do
619    {
620        if ([files count] == 0) //recursive base case
621        {
622            [files release];
623           
624            [self updateTorrentHistory];
625            return;
626        }
627   
628        torrentPath = [files objectAtIndex: 0];
629        torrent = [[Torrent alloc] initWithPath: torrentPath forceDeleteTorrent: delete lib: fLib];
630       
631        [files removeObjectAtIndex: 0];
632    } while (!torrent);
633
634    //add it to the "File > Open Recent" menu
635    [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
636
637    NSOpenPanel * panel = [NSOpenPanel openPanel];
638
639    [panel setPrompt: @"Select"];
640    [panel setAllowsMultipleSelection: NO];
641    [panel setCanChooseFiles: NO];
642    [panel setCanChooseDirectories: YES];
643
644    [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the download folder for \"%@\"",
645                        "Open torrent -> select destination folder"), [torrent name]]];
646   
647    NSDictionary * dictionary = [[NSDictionary alloc] initWithObjectsAndKeys: torrent, @"Torrent", files, @"Files",
648                                            [NSNumber numberWithBool: delete], @"Delete", nil];
649
650    [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: fWindow modalDelegate: self
651            didEndSelector: @selector(folderChoiceClosed:returnCode:contextInfo:) contextInfo: dictionary];
652    [torrent release];
653}
654
655- (void) folderChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (NSDictionary *) dictionary
656{
657    Torrent * torrent = [dictionary objectForKey: @"Torrent"];
658
659    if (code == NSOKButton)
660    {
661        [torrent setDownloadFolder: [[openPanel filenames] objectAtIndex: 0]];
662        [torrent update];
663        [self attemptToStartAuto: torrent];
664       
665        [fTorrents addObject: torrent];
666       
667        [self updateUI: nil];
668        [self applyFilter: nil];
669    }
670   
671    [self performSelectorOnMainThread: @selector(openFilesAskWithDict:) withObject: dictionary waitUntilDone: NO];
672}
673
674- (void) openFilesAskWithDict: (NSDictionary *) dictionary
675{
676    [self openFilesAsk: [dictionary objectForKey: @"Files"]
677            forceDeleteTorrent: [[dictionary objectForKey: @"Delete"] boolValue]];
678    [dictionary release];
679}
680
681//called on by applescript
682- (void) open: (NSArray *) files
683{
684    [self performSelectorOnMainThread: @selector(openFiles:) withObject: files waitUntilDone: NO];
685}
686
687- (void) openFiles: (NSArray *) filenames
688{
689    [self openFiles: filenames ignoreDownloadFolder: NO forceDeleteTorrent: NO];
690}
691
692- (void) openShowSheet: (id) sender
693{
694    NSOpenPanel * panel = [NSOpenPanel openPanel];
695
696    [panel setAllowsMultipleSelection: YES];
697    [panel setCanChooseFiles: YES];
698    [panel setCanChooseDirectories: NO];
699
700    [panel beginSheetForDirectory: nil file: nil types: [NSArray arrayWithObject: @"torrent"]
701        modalForWindow: fWindow modalDelegate: self didEndSelector: @selector(openSheetClosed:returnCode:contextInfo:)
702        contextInfo: [NSNumber numberWithBool: sender == fOpenIgnoreDownloadFolder]];
703}
704
705- (void) openSheetClosed: (NSOpenPanel *) panel returnCode: (int) code contextInfo: (NSNumber *) ignore
706{
707    if (code == NSOKButton)
708    {
709        NSDictionary * dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:
710                                        [panel filenames], @"Files", ignore, @"Ignore", nil];
711        [self performSelectorOnMainThread: @selector(openFromSheet:) withObject: dictionary waitUntilDone: NO];
712    }
713}
714
715- (void) openFromSheet: (NSDictionary *) dictionary
716{
717    [self openFiles: [dictionary objectForKey: @"Files"]
718        ignoreDownloadFolder: [[dictionary objectForKey: @"Ignore"] boolValue] forceDeleteTorrent: NO];
719   
720    [dictionary release];
721}
722
723- (void) openURL: (NSURL *) url
724{
725    [[NSURLDownload alloc] initWithRequest: [NSURLRequest requestWithURL: url] delegate: self];
726}
727
728- (void) openURLShowSheet: (id) sender
729{
730    [NSApp beginSheet: fURLSheetWindow modalForWindow: fWindow modalDelegate: self
731            didEndSelector: @selector(urlSheetDidEnd:returnCode:contextInfo:) contextInfo: nil];
732}
733
734- (void) openURLEndSheet: (id) sender
735{
736    [fURLSheetWindow orderOut: sender];
737    [NSApp endSheet: fURLSheetWindow returnCode: 1];
738}
739
740- (void) openURLCancelEndSheet: (id) sender
741{
742    [fURLSheetWindow orderOut: sender];
743    [NSApp endSheet: fURLSheetWindow returnCode: 0];
744}
745
746- (void) urlSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo
747{
748    [fURLSheetTextField selectText: self];
749    if (returnCode != 1)
750        return;
751   
752    NSString * urlString = [fURLSheetTextField stringValue];
753    if (![urlString isEqualToString: @""])
754    {
755        if ([urlString rangeOfString: @"://"].location == NSNotFound)
756        {
757            if ([urlString rangeOfString: @"."].location == NSNotFound)
758            {
759                int beforeCom;
760                if ((beforeCom = [urlString rangeOfString: @"/"].location) != NSNotFound)
761                    urlString = [NSString stringWithFormat: @"http://www.%@.com/%@",
762                                    [urlString substringToIndex: beforeCom],
763                                    [urlString substringFromIndex: beforeCom + 1]];
764                else
765                    urlString = [NSString stringWithFormat: @"http://www.%@.com", urlString];
766            }
767            else
768                urlString = [@"http://" stringByAppendingString: urlString];
769        }
770       
771        NSURL * url = [NSURL URLWithString: urlString];
772        [self performSelectorOnMainThread: @selector(openURL:) withObject: url waitUntilDone: NO];
773    }
774}
775
776- (void) resumeSelectedTorrents: (id) sender
777{
778    [self resumeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
779}
780
781- (void) resumeAllTorrents: (id) sender
782{
783    [self resumeTorrents: fTorrents];
784}
785
786- (void) resumeTorrents: (NSArray *) torrents
787{
788    NSEnumerator * enumerator = [torrents objectEnumerator];
789    Torrent * torrent;
790    while ((torrent = [enumerator nextObject]))
791        [torrent setWaitToStart: YES];
792   
793    [self attemptToStartMultipleAuto: torrents];
794   
795    [self updateUI: nil];
796    [self applyFilter: nil];
797    [self updateTorrentHistory];
798}
799
800- (void) resumeSelectedTorrentsNoWait:  (id) sender
801{
802    [self resumeTorrentsNoWait: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
803}
804
805- (void) resumeWaitingTorrents: (id) sender
806{
807    NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [fTorrents count]];
808   
809    NSEnumerator * enumerator = [fTorrents objectEnumerator];
810    Torrent * torrent;
811    while ((torrent = [enumerator nextObject]))
812        if ([torrent waitingToStart])
813            [torrents addObject: torrent];
814   
815    [self resumeTorrentsNoWait: torrents];
816}
817
818- (void) resumeTorrentsNoWait: (NSArray *) torrents
819{
820    //iterate through instead of all at once to ensure no conflicts
821    NSEnumerator * enumerator = [torrents objectEnumerator];
822    Torrent * torrent;
823    while ((torrent = [enumerator nextObject]))
824        [torrent startTransfer];
825   
826    [self updateUI: nil];
827    [self applyFilter: nil];
828    [self updateTorrentHistory];
829}
830
831- (void) stopSelectedTorrents: (id) sender
832{
833    [self stopTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
834}
835
836- (void) stopAllTorrents: (id) sender
837{
838    [self stopTorrents: fTorrents];
839}
840
841- (void) stopTorrents: (NSArray *) torrents
842{
843    //don't want any of these starting then stopping
844    NSEnumerator * enumerator = [torrents objectEnumerator];
845    Torrent * torrent;
846    while ((torrent = [enumerator nextObject]))
847        [torrent setWaitToStart: NO];
848
849    [torrents makeObjectsPerformSelector: @selector(stopTransfer)];
850   
851    [self updateUI: nil];
852    [self applyFilter: nil];
853    [self updateTorrentHistory];
854}
855
856- (void) removeTorrents: (NSArray *) torrents
857        deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
858{
859    [torrents retain];
860    int active = 0, downloading = 0;
861
862    if ([fDefaults boolForKey: @"CheckRemove"])
863    {
864        Torrent * torrent;
865        NSEnumerator * enumerator = [torrents objectEnumerator];
866        while ((torrent = [enumerator nextObject]))
867            if ([torrent isActive])
868            {
869                active++;
870                if (![torrent isSeeding])
871                    downloading++;
872            }
873
874        if ([fDefaults boolForKey: @"CheckRemoveDownloading"] ? downloading > 0 : active > 0)
875        {
876            NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
877                torrents, @"Torrents",
878                [NSNumber numberWithBool: deleteData], @"DeleteData",
879                [NSNumber numberWithBool: deleteTorrent], @"DeleteTorrent", nil];
880
881            NSString * title, * message;
882           
883            int selected = [fTableView numberOfSelectedRows];
884            if (selected == 1)
885            {
886                title = [NSString stringWithFormat: NSLocalizedString(@"Confirm Removal of \"%@\"",
887                            "Removal confirm panel -> title"),
888                            [[fDisplayedTorrents objectAtIndex: [fTableView selectedRow]] name]];
889                message = NSLocalizedString(@"This transfer is active."
890                            " Once removed, continuing the transfer will require the torrent file."
891                            " Do you really want to remove it?", "Removal confirm panel -> message");
892            }
893            else
894            {
895                title = [NSString stringWithFormat: NSLocalizedString(@"Confirm Removal of %d Transfers",
896                            "Removal confirm panel -> title"), selected];
897                if (selected == active)
898                    message = [NSString stringWithFormat: NSLocalizedString(@"There are %d active transfers.",
899                                "Removal confirm panel -> message part 1"), active];
900                else
901                    message = [NSString stringWithFormat: NSLocalizedString(@"There are %d transfers (%d active).",
902                                "Removal confirm panel -> message part 1"), selected, active];
903                message = [message stringByAppendingString:
904                    NSLocalizedString(@" Once removed, continuing the transfers will require the torrent files."
905                    " Do you really want to remove them?", "Removal confirm panel -> message part 2")];
906            }
907
908            NSBeginAlertSheet(title, NSLocalizedString(@"Remove", "Removal confirm panel -> button"),
909                NSLocalizedString(@"Cancel", "Removal confirm panel -> button"), nil, fWindow, self,
910                nil, @selector(removeSheetDidEnd:returnCode:contextInfo:), dict, message);
911            return;
912        }
913    }
914   
915    [self confirmRemoveTorrents: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
916}
917
918- (void) removeSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (NSDictionary *) dict
919{
920    NSArray * torrents = [dict objectForKey: @"Torrents"];
921    BOOL deleteData = [[dict objectForKey: @"DeleteData"] boolValue],
922        deleteTorrent = [[dict objectForKey: @"DeleteTorrent"] boolValue];
923    [dict release];
924   
925    if (returnCode == NSAlertDefaultReturn)
926        [self confirmRemoveTorrents: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
927    else
928        [torrents release];
929}
930
931- (void) confirmRemoveTorrents: (NSArray *) torrents deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
932{
933    //don't want any of these starting then stopping
934    NSEnumerator * enumerator = [torrents objectEnumerator];
935    Torrent * torrent;
936    while ((torrent = [enumerator nextObject]))
937        [torrent setWaitToStart: NO];
938
939    NSNumber * lowestOrderValue = [NSNumber numberWithInt: [torrents count]], * currentOrderValue;
940
941    enumerator = [torrents objectEnumerator];
942    while ((torrent = [enumerator nextObject]))
943    {
944        [torrent stopTransfer];
945
946        if (deleteData)
947            [torrent trashData];
948        if (deleteTorrent)
949            [torrent trashTorrent];
950       
951        //determine lowest order value
952        currentOrderValue = [torrent orderValue];
953        if ([lowestOrderValue compare: currentOrderValue] == NSOrderedDescending)
954            lowestOrderValue = currentOrderValue;
955
956        [torrent removeForever];
957       
958        [fTorrents removeObject: torrent];
959        [fDisplayedTorrents removeObject: torrent];
960    }
961    [torrents release];
962
963    //reset the order values if necessary
964    if ([lowestOrderValue intValue] < [fTorrents count])
965    {
966        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
967                                                @"orderValue" ascending: YES] autorelease];
968        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
969
970        NSArray * tempTorrents = [fTorrents sortedArrayUsingDescriptors: descriptors];
971        [descriptors release];
972
973        int i;
974        for (i = [lowestOrderValue intValue]; i < [tempTorrents count]; i++)
975            [[tempTorrents objectAtIndex: i] setOrderValue: i];
976    }
977   
978    [fTableView deselectAll: nil];
979   
980    [self updateUI: nil];
981    [self applyFilter: nil];
982   
983    [self updateTorrentHistory];
984}
985
986- (void) removeNoDelete: (id) sender
987{
988    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
989                deleteData: NO deleteTorrent: NO];
990}
991
992- (void) removeDeleteData: (id) sender
993{
994    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
995                deleteData: YES deleteTorrent: NO];
996}
997
998- (void) removeDeleteTorrent: (id) sender
999{
1000    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1001                deleteData: NO deleteTorrent: YES];
1002}
1003
1004- (void) removeDeleteDataAndTorrent: (id) sender
1005{
1006    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1007                deleteData: YES deleteTorrent: YES];
1008}
1009
1010- (void) copyTorrentFile: (id) sender
1011{
1012    [self copyTorrentFileForTorrents: [[NSMutableArray alloc] initWithArray:
1013            [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]]];
1014}
1015
1016- (void) copyTorrentFileForTorrents: (NSMutableArray *) torrents
1017{
1018    if ([torrents count] == 0)
1019    {
1020        [torrents release];
1021        return;
1022    }
1023
1024    Torrent * torrent = [torrents objectAtIndex: 0];
1025
1026    //warn user if torrent file can't be found
1027    if (![[NSFileManager defaultManager] fileExistsAtPath: [torrent torrentLocation]])
1028    {
1029        NSAlert * alert = [[NSAlert alloc] init];
1030        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent file copy alert -> button")];
1031        [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Copy of \"%@\" Cannot Be Created",
1032                                "Torrent file copy alert -> title"), [torrent name]]];
1033        [alert setInformativeText: [NSString stringWithFormat:
1034                NSLocalizedString(@"The torrent file (%@) cannot be found.", "Torrent file copy alert -> message"),
1035                                    [torrent torrentLocation]]];
1036        [alert setAlertStyle: NSWarningAlertStyle];
1037       
1038        [alert runModal];
1039        [alert release];
1040       
1041        [torrents removeObjectAtIndex: 0];
1042        [self copyTorrentFileForTorrents: torrents];
1043    }
1044    else
1045    {
1046        NSSavePanel * panel = [NSSavePanel savePanel];
1047        [panel setRequiredFileType: @"torrent"];
1048        [panel setCanSelectHiddenExtension: YES];
1049       
1050        [panel beginSheetForDirectory: nil file: [torrent name] modalForWindow: fWindow modalDelegate: self
1051            didEndSelector: @selector(saveTorrentCopySheetClosed:returnCode:contextInfo:) contextInfo: torrents];
1052    }
1053}
1054
1055- (void) saveTorrentCopySheetClosed: (NSSavePanel *) panel returnCode: (int) code contextInfo: (NSMutableArray *) torrents
1056{
1057    //if save successful, copy torrent to new location with name of data file
1058    if (code == NSOKButton)
1059        [[NSFileManager defaultManager] copyPath: [[torrents objectAtIndex: 0] torrentLocation]
1060            toPath: [panel filename] handler: nil];
1061   
1062    [torrents removeObjectAtIndex: 0];
1063    [self performSelectorOnMainThread: @selector(copyTorrentFileForTorrents:) withObject: torrents waitUntilDone: NO];
1064}
1065
1066- (void) revealFile: (id) sender
1067{
1068    NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1069    unsigned int i;
1070    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1071        [[fDisplayedTorrents objectAtIndex: i] revealData];
1072}
1073
1074- (void) showPreferenceWindow: (id) sender
1075{
1076    NSWindow * window = [fPrefsController window];
1077    if (![window isVisible])
1078        [window center];
1079
1080    [window makeKeyAndOrderFront: nil];
1081}
1082
1083- (void) makeWindowKey
1084{
1085    [fWindow makeKeyWindow];
1086}
1087
1088- (void) showInfo: (id) sender
1089{
1090    if ([[fInfoController window] isVisible])
1091        [fInfoController close];
1092    else
1093    {
1094        [fInfoController updateInfoStats];
1095        [[fInfoController window] orderFront: nil];
1096    }
1097}
1098
1099- (void) setInfoTab: (id) sender
1100{
1101    if (sender == fNextInfoTabItem)
1102        [fInfoController setNextTab];
1103    else
1104        [fInfoController setPreviousTab];
1105}
1106
1107- (void) showMessageWindow: (id) sender
1108{
1109    [fMessageController showWindow: nil];
1110}
1111
1112- (void) updateUI: (NSTimer *) timer
1113{
1114    [fTorrents makeObjectsPerformSelector: @selector(update)];
1115
1116    //resort if necessary or just update the table
1117    if ([fSortType isEqualToString: @"Progress"] || [fSortType isEqualToString: @"State"])
1118        [self sortTorrents];
1119    else
1120        [fTableView reloadData];
1121   
1122    //update the global DL/UL rates
1123    float downloadRate, uploadRate;
1124    tr_torrentRates(fLib, & downloadRate, & uploadRate);
1125    if (![fStatusBar isHidden])
1126    {
1127        [fTotalDLField setStringValue: [NSLocalizedString(@"Total DL: ", "Status bar -> total download")
1128                                        stringByAppendingString: [NSString stringForSpeed: downloadRate]]];
1129        [fTotalULField setStringValue: [NSLocalizedString(@"Total UL: ", "Status bar -> total upload")
1130                                        stringByAppendingString: [NSString stringForSpeed: uploadRate]]];
1131    }
1132
1133    //update non-constant parts of info window
1134    if ([[fInfoController window] isVisible])
1135        [fInfoController updateInfoStats];
1136
1137    //badge dock
1138    [fBadger updateBadgeWithCompleted: fCompleted uploadRate: uploadRate downloadRate: downloadRate];
1139}
1140
1141- (void) torrentFinishedDownloading: (NSNotification *) notification
1142{
1143    Torrent * torrent = [notification object];
1144   
1145    [fInfoController updateInfoSettings];
1146   
1147    [self applyFilter: nil];
1148    [self checkToStartWaiting: torrent];
1149   
1150    if ([fDefaults boolForKey: @"PlayDownloadSound"])
1151    {
1152        NSSound * sound;
1153        if ((sound = [NSSound soundNamed: [fDefaults stringForKey: @"DownloadSound"]]))
1154            [sound play];
1155    }
1156   
1157    [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Download Complete", "Growl notification title")
1158        description: [torrent name]
1159        notificationName: GROWL_DOWNLOAD_COMPLETE iconData: nil priority: 0 isSticky: NO clickContext: nil];
1160   
1161    if (![fWindow isKeyWindow])
1162        fCompleted++;
1163}
1164
1165- (void) updateTorrentHistory
1166{
1167    NSMutableArray * history = [NSMutableArray arrayWithCapacity: [fTorrents count]];
1168
1169    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1170    Torrent * torrent;
1171    while ((torrent = [enumerator nextObject]))
1172        [history addObject: [torrent history]];
1173
1174    [fDefaults setObject: history forKey: @"History"];
1175    [fDefaults synchronize];
1176}
1177
1178- (void) sortTorrents
1179{
1180    //remember selected rows if needed
1181    NSArray * selectedTorrents = nil;
1182    int numSelected = [fTableView numberOfSelectedRows];
1183    if (numSelected > 0 && numSelected < [fDisplayedTorrents count])
1184        selectedTorrents = [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]];
1185
1186    [self sortTorrentsIgnoreSelected]; //actually sort
1187   
1188    //set selected rows if needed
1189    if (selectedTorrents)
1190    {
1191        Torrent * torrent;
1192        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1193        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1194        while ((torrent = [enumerator nextObject]))
1195            [indexSet addIndex: [fDisplayedTorrents indexOfObject: torrent]];
1196       
1197        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1198        [indexSet release];
1199    }
1200}
1201
1202//doesn't remember selected rows
1203- (void) sortTorrentsIgnoreSelected
1204{
1205    NSSortDescriptor * nameDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"name"
1206                            ascending: YES selector: @selector(caseInsensitiveCompare:)] autorelease],
1207                    * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"orderValue"
1208                                            ascending: YES] autorelease];
1209
1210    NSArray * descriptors;
1211    if ([fSortType isEqualToString: @"Name"])
1212        descriptors = [[NSArray alloc] initWithObjects: nameDescriptor, orderDescriptor, nil];
1213    else if ([fSortType isEqualToString: @"State"])
1214    {
1215        NSSortDescriptor * stateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1216                                                @"stateSortKey" ascending: NO] autorelease],
1217                        * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1218                                            @"progressSortKey" ascending: NO] autorelease];
1219       
1220        descriptors = [[NSArray alloc] initWithObjects: stateDescriptor, progressDescriptor,
1221                                                            nameDescriptor, orderDescriptor, nil];
1222    }
1223    else if ([fSortType isEqualToString: @"Progress"])
1224    {
1225        NSSortDescriptor * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1226                                            @"progressSortKey" ascending: YES] autorelease];
1227       
1228        descriptors = [[NSArray alloc] initWithObjects: progressDescriptor, nameDescriptor, orderDescriptor, nil];
1229    }
1230    else if ([fSortType isEqualToString: @"Date"])
1231    {
1232        NSSortDescriptor * dateDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"date" ascending: YES] autorelease];
1233   
1234        descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, orderDescriptor, nil];
1235    }
1236    else
1237        descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1238
1239    [fDisplayedTorrents sortUsingDescriptors: descriptors];
1240    [descriptors release];
1241   
1242    [fTableView reloadData];
1243}
1244
1245- (void) setSort: (id) sender
1246{
1247    //get checked items
1248    NSMenuItem * prevSortItem, * prevSortActionItem;
1249    if ([fSortType isEqualToString: @"Name"])
1250    {
1251        prevSortItem = fNameSortItem;
1252        prevSortActionItem = fNameSortActionItem;
1253    }
1254    else if ([fSortType isEqualToString: @"State"])
1255    {
1256        prevSortItem = fStateSortItem;
1257        prevSortActionItem = fStateSortActionItem;
1258    }
1259    else if ([fSortType isEqualToString: @"Progress"])
1260    {
1261        prevSortItem = fProgressSortItem;
1262        prevSortActionItem = fProgressSortActionItem;
1263    }
1264    else if ([fSortType isEqualToString: @"Date"])
1265    {
1266        prevSortItem = fDateSortItem;
1267        prevSortActionItem = fDateSortActionItem;
1268    }
1269    else
1270    {
1271        prevSortItem = fOrderSortItem;
1272        prevSortActionItem = fOrderSortActionItem;
1273    }
1274   
1275    if (sender != prevSortItem && sender != prevSortActionItem)
1276    {
1277        [fSortType release];
1278       
1279        //get new items to check
1280        NSMenuItem * currentSortItem, * currentSortActionItem;
1281        if (sender == fNameSortItem || sender == fNameSortActionItem)
1282        {
1283            currentSortItem = fNameSortItem;
1284            currentSortActionItem = fNameSortActionItem;
1285            fSortType = [[NSString alloc] initWithString: @"Name"];
1286        }
1287        else if (sender == fStateSortItem || sender == fStateSortActionItem)
1288        {
1289            currentSortItem = fStateSortItem;
1290            currentSortActionItem = fStateSortActionItem;
1291            fSortType = [[NSString alloc] initWithString: @"State"];
1292        }
1293        else if (sender == fProgressSortItem || sender == fProgressSortActionItem)
1294        {
1295            currentSortItem = fProgressSortItem;
1296            currentSortActionItem = fProgressSortActionItem;
1297            fSortType = [[NSString alloc] initWithString: @"Progress"];
1298        }
1299        else if (sender == fDateSortItem || sender == fDateSortActionItem)
1300        {
1301            currentSortItem = fDateSortItem;
1302            currentSortActionItem = fDateSortActionItem;
1303            fSortType = [[NSString alloc] initWithString: @"Date"];
1304        }
1305        else
1306        {
1307            currentSortItem = fOrderSortItem;
1308            currentSortActionItem = fOrderSortActionItem;
1309            fSortType = [[NSString alloc] initWithString: @"Order"];
1310        }
1311   
1312        [prevSortItem setState: NSOffState];
1313        [prevSortActionItem setState: NSOffState];
1314        [currentSortItem setState: NSOnState];
1315        [currentSortActionItem setState: NSOnState];
1316       
1317        [fDefaults setObject: fSortType forKey: @"Sort"];
1318    }
1319
1320    [self sortTorrents];
1321}
1322
1323- (void) applyFilter: (id) sender
1324{
1325    //remember selected rows if needed
1326    NSArray * selectedTorrents = [fTableView numberOfSelectedRows] > 0
1327                ? [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] : nil;
1328
1329    NSMutableArray * tempTorrents = [[NSMutableArray alloc] initWithCapacity: [fTorrents count]];
1330
1331    BOOL filtering = YES;
1332    if ([fFilterType isEqualToString: @"Pause"])
1333    {
1334        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1335        Torrent * torrent;
1336        while ((torrent = [enumerator nextObject]))
1337            if (![torrent isActive])
1338                [tempTorrents addObject: torrent];
1339    }
1340    else if ([fFilterType isEqualToString: @"Seed"])
1341    {
1342        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1343        Torrent * torrent;
1344        while ((torrent = [enumerator nextObject]))
1345            if ([torrent isActive] && [torrent progress] >= 1.0)
1346                [tempTorrents addObject: torrent];
1347    }
1348    else if ([fFilterType isEqualToString: @"Download"])
1349    {
1350        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1351        Torrent * torrent;
1352        while ((torrent = [enumerator nextObject]))
1353            if ([torrent isActive] && [torrent progress] < 1.0)
1354                [tempTorrents addObject: torrent];
1355    }
1356    else
1357    {
1358        filtering = NO;
1359        [tempTorrents setArray: fTorrents];
1360    }
1361   
1362    NSString * searchString = [fSearchFilterField stringValue];
1363    if ([searchString length] > 0)
1364    {
1365        filtering = YES;
1366       
1367        int i;
1368        for (i = [tempTorrents count] - 1; i >= 0; i--)
1369            if ([[[tempTorrents objectAtIndex: i] name] rangeOfString: searchString
1370                                        options: NSCaseInsensitiveSearch].location == NSNotFound)
1371                [tempTorrents removeObjectAtIndex: i];
1372    }
1373   
1374    [fDisplayedTorrents setArray: tempTorrents];
1375    [tempTorrents release];
1376   
1377    [self sortTorrentsIgnoreSelected];
1378   
1379    //set selected rows if needed
1380    if (selectedTorrents)
1381    {
1382        Torrent * torrent;
1383        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1384        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1385        unsigned index;
1386        while ((torrent = [enumerator nextObject]))
1387            if ((index = [fDisplayedTorrents indexOfObject: torrent]) != NSNotFound)
1388                [indexSet addIndex: index];
1389       
1390        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1391        [indexSet release];
1392    }
1393   
1394    //set status bar torrent count text
1395    NSMutableString * totalTorrentsString = [NSMutableString stringWithString: @""];
1396    if (filtering)
1397        [totalTorrentsString appendFormat: @"%d/", [fDisplayedTorrents count]];
1398   
1399    int totalCount = [fTorrents count];
1400    if (totalCount > 1)
1401        [totalTorrentsString appendFormat: NSLocalizedString(@"%d Transfers", "Status bar transfer count"), totalCount];
1402    else
1403        [totalTorrentsString appendFormat: NSLocalizedString(@"%d Transfer", "Status bar transfer count"), totalCount];
1404   
1405    [fTotalTorrentsField setStringValue: totalTorrentsString];
1406
1407    [self setWindowSizeToFit];
1408}
1409
1410//resets filter and sorts torrents
1411- (void) setFilter: (id) sender
1412{
1413    BarButton * prevFilterButton;
1414    if ([fFilterType isEqualToString: @"Pause"])
1415        prevFilterButton = fPauseFilterButton;
1416    else if ([fFilterType isEqualToString: @"Seed"])
1417        prevFilterButton = fSeedFilterButton;
1418    else if ([fFilterType isEqualToString: @"Download"])
1419        prevFilterButton = fDownloadFilterButton;
1420    else
1421        prevFilterButton = fNoFilterButton;
1422   
1423    if (sender != prevFilterButton)
1424    {
1425        [prevFilterButton setEnabled: NO];
1426        [sender setEnabled: YES];
1427
1428        [fFilterType release];
1429        if (sender == fDownloadFilterButton)
1430            fFilterType = [[NSString alloc] initWithString: @"Download"];
1431        else if (sender == fPauseFilterButton)
1432            fFilterType = [[NSString alloc] initWithString: @"Pause"];
1433        else if (sender == fSeedFilterButton)
1434            fFilterType = [[NSString alloc] initWithString: @"Seed"];
1435        else
1436            fFilterType = [[NSString alloc] initWithString: @"None"];
1437
1438        [fDefaults setObject: fFilterType forKey: @"Filter"];
1439    }
1440
1441    [self applyFilter: nil];
1442}
1443
1444- (void) switchFilter: (id) sender
1445{
1446    NSButton * button;
1447    if ([fFilterType isEqualToString: @"None"])
1448        button = sender == fNextFilterItem ? fDownloadFilterButton : fPauseFilterButton;
1449    else if ([fFilterType isEqualToString: @"Download"])
1450        button = sender == fNextFilterItem ? fSeedFilterButton : fNoFilterButton;
1451    else if ([fFilterType isEqualToString: @"Seed"])
1452        button = sender == fNextFilterItem ? fPauseFilterButton : fDownloadFilterButton;
1453    else if ([fFilterType isEqualToString: @"Pause"])
1454        button = sender == fNextFilterItem ? fNoFilterButton : fSeedFilterButton;
1455    else
1456        button = fNoFilterButton;
1457   
1458    [self setFilter: button];
1459}
1460
1461- (void) updateControlTint: (NSNotification *) notification
1462{
1463    if ([fDefaults boolForKey: @"SpeedLimit"])
1464        [fSpeedLimitButton setImage: [NSColor currentControlTint] == NSBlueControlTint
1465            ? [NSImage imageNamed: @"SpeedLimitButtonBlue.png"] : [NSImage imageNamed: @"SpeedLimitButtonGraphite.png"]];
1466}
1467
1468- (void) applySpeedLimit: (id) sender
1469{
1470    [fPrefsController applySpeedSettings: nil];
1471}
1472
1473- (void) toggleSpeedLimit: (id) sender
1474{
1475    [fDefaults setBool: ![fDefaults boolForKey: @"SpeedLimit"] forKey: @"SpeedLimit"];
1476    [self applySpeedLimit: nil];
1477}
1478
1479- (void) autoSpeedLimitChange: (NSNotification *) notification
1480{
1481    if (![fDefaults boolForKey: @"SpeedLimitAuto"])
1482        return;
1483 
1484    NSCalendarDate * onDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
1485                        [[fDefaults objectForKey: @"SpeedLimitAutoOnDate"] timeIntervalSinceReferenceDate]],
1486        * offDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
1487                        [[fDefaults objectForKey: @"SpeedLimitAutoOffDate"] timeIntervalSinceReferenceDate]],
1488        * nowDate = [NSCalendarDate calendarDate];
1489   
1490    //check if should be on if within range
1491    BOOL shouldBeOn;
1492   
1493    int onTime = [onDate hourOfDay] * 60 + [onDate minuteOfHour],
1494        offTime = [offDate hourOfDay] * 60 + [offDate minuteOfHour],
1495        nowTime = [nowDate hourOfDay] * 60 + [nowDate minuteOfHour];
1496   
1497    if (onTime == offTime)
1498        shouldBeOn = NO;
1499    else if (onTime < offTime)
1500        shouldBeOn = onTime <= nowTime && nowTime < offTime;
1501    else
1502        shouldBeOn = onTime <= nowTime || nowTime < offTime;
1503   
1504    if ([fDefaults boolForKey: @"SpeedLimit"] != shouldBeOn)
1505        [self toggleSpeedLimit: nil];
1506}
1507
1508- (void) autoSpeedLimit: (NSTimer *) timer
1509{
1510    if (![fDefaults boolForKey: @"SpeedLimitAuto"])
1511        return;
1512   
1513    //only toggle if within first few seconds of minutes
1514    NSCalendarDate * nowDate = [NSCalendarDate calendarDate];
1515    if ([nowDate secondOfMinute] < AUTO_SPEED_LIMIT_SECONDS)
1516        return;
1517   
1518    NSCalendarDate * offDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
1519                        [[fDefaults objectForKey: @"SpeedLimitAutoOffDate"] timeIntervalSinceReferenceDate]];
1520   
1521    BOOL toggle;
1522    if ([fDefaults boolForKey: @"SpeedLimit"])
1523        toggle = [nowDate hourOfDay] == [offDate hourOfDay] && [nowDate minuteOfHour] == [offDate minuteOfHour];
1524    else
1525    {
1526        NSCalendarDate * onDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
1527                        [[fDefaults objectForKey: @"SpeedLimitAutoOnDate"] timeIntervalSinceReferenceDate]];
1528        toggle = ([nowDate hourOfDay] == [onDate hourOfDay] && [nowDate minuteOfHour] == [onDate minuteOfHour])
1529                    && !([onDate hourOfDay] == [offDate hourOfDay] && [onDate minuteOfHour] == [offDate minuteOfHour]);
1530    }
1531   
1532    if (toggle)
1533    {
1534        [self toggleSpeedLimit: nil];
1535       
1536        [GrowlApplicationBridge notifyWithTitle: [fDefaults boolForKey: @"SpeedLimit"]
1537                ? NSLocalizedString(@"Speed Limit Auto Enabled", "Growl notification title")
1538                : NSLocalizedString(@"Speed Limit Auto Disabled", "Growl notification title")
1539            description: NSLocalizedString(@"Bandwidth settings changed", "Growl notification description")
1540            notificationName: GROWL_AUTO_SPEED_LIMIT iconData: nil priority: 0 isSticky: NO clickContext: nil];
1541    }
1542}
1543
1544- (void) setLimitGlobalEnabled: (id) sender
1545{
1546    [fPrefsController applySpeedSettings: nil];
1547}
1548
1549- (void) setQuickLimitGlobal: (id) sender
1550{
1551    [fDefaults setInteger: [[sender title] intValue] forKey: [sender menu] == fUploadMenu ? @"UploadLimit" : @"DownloadLimit"];
1552    [fDefaults setBool: YES forKey: [sender menu] == fUploadMenu ? @"CheckUpload" : @"CheckDownload"];
1553   
1554    [fPrefsController applySpeedSettings: nil];
1555}
1556
1557- (void) setQuickRatioGlobal: (id) sender
1558{
1559    [fDefaults setBool: YES forKey: @"RatioCheck"];
1560    [fDefaults setFloat: [[sender title] floatValue] forKey: @"RatioLimit"];
1561}
1562
1563- (void) checkWaitingForStopped: (NSNotification *) notification
1564{
1565    [self checkToStartWaiting: [notification object]];
1566   
1567    [self updateUI: nil];
1568    [self applyFilter: nil];
1569    [self updateTorrentHistory];
1570}
1571
1572- (void) checkToStartWaiting: (Torrent *) finishedTorrent
1573{
1574    //don't try to start a transfer if there should be none waiting
1575    if (![fDefaults boolForKey: @"Queue"])
1576        return;
1577
1578    int desiredActive = [fDefaults integerForKey: @"QueueDownloadNumber"];
1579   
1580    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1581    Torrent * torrent, * torrentToStart = nil;
1582    while ((torrent = [enumerator nextObject]))
1583    {
1584        //ignore the torrent just stopped
1585        if (torrent == finishedTorrent)
1586            continue;
1587   
1588        if ([torrent isActive])
1589        {
1590            if (![torrent isSeeding] && ![torrent isError])
1591            {
1592                desiredActive--;
1593                if (desiredActive <= 0)
1594                    return;
1595            }
1596        }
1597        else
1598        {
1599            //use as next if it is waiting to start and either no previous or order value is lower
1600            if ([torrent waitingToStart] && (!torrentToStart
1601                || [[torrentToStart orderValue] compare: [torrent orderValue]] == NSOrderedDescending))
1602                torrentToStart = torrent;
1603        }
1604    }
1605   
1606    //since it hasn't returned, the queue amount has not been met
1607    if (torrentToStart)
1608    {
1609        [torrentToStart startTransfer];
1610       
1611        [self updateUI: nil];
1612        [self applyFilter: nil];
1613        [self updateTorrentHistory];
1614    }
1615}
1616
1617- (void) torrentStartSettingChange: (NSNotification *) notification
1618{
1619    [self attemptToStartMultipleAuto: [notification object]];
1620
1621    [self updateUI: nil];
1622    [self applyFilter: nil];
1623    [self updateTorrentHistory];
1624}
1625
1626- (void) globalStartSettingChange: (NSNotification *) notification
1627{
1628    [self attemptToStartMultipleAuto: fTorrents];
1629   
1630    [self updateUI: nil];
1631    [self applyFilter: nil];
1632    [self updateTorrentHistory];
1633}
1634
1635- (void) torrentStoppedForRatio: (NSNotification *) notification
1636{
1637    [self applyFilter: nil];
1638    [fInfoController updateInfoStats];
1639    [fInfoController updateInfoSettings];
1640   
1641    if ([fDefaults boolForKey: @"PlaySeedingSound"])
1642    {
1643        NSSound * sound;
1644        if ((sound = [NSSound soundNamed: [fDefaults stringForKey: @"SeedingSound"]]))
1645            [sound play];
1646    }
1647   
1648    [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Seeding Complete", "Growl notification title")
1649        description: [[notification object] name]
1650        notificationName: GROWL_SEEDING_COMPLETE iconData: nil priority: 0 isSticky: NO clickContext: nil];
1651}
1652
1653- (void) attemptToStartAuto: (Torrent *) torrent
1654{
1655    [self attemptToStartMultipleAuto: [NSArray arrayWithObject: torrent]];
1656}
1657
1658//will try to start, taking into consideration the start preference
1659- (void) attemptToStartMultipleAuto: (NSArray *) torrents
1660{
1661    if (![fDefaults boolForKey: @"Queue"])
1662    {
1663        NSEnumerator * enumerator = [torrents objectEnumerator];
1664        Torrent * torrent;
1665        while ((torrent = [enumerator nextObject]))
1666            if ([torrent waitingToStart])
1667                [torrent startTransfer];
1668       
1669        return;
1670    }
1671   
1672    //determine the number of downloads needed to start
1673    int desiredActive = [fDefaults integerForKey: @"QueueDownloadNumber"];
1674           
1675    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1676    Torrent * torrent;
1677    while ((torrent = [enumerator nextObject]))
1678        if ([torrent isActive] && ![torrent isSeeding] && ![torrent isError])
1679        {
1680            desiredActive--;
1681            if (desiredActive <= 0)
1682                break;
1683        }
1684   
1685    //sort torrents by order value
1686    NSArray * sortedTorrents;
1687    if ([torrents count] > 1 && desiredActive > 0)
1688    {
1689        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1690                                                    @"orderValue" ascending: YES] autorelease];
1691        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1692       
1693        sortedTorrents = [torrents sortedArrayUsingDescriptors: descriptors];
1694        [descriptors release];
1695    }
1696    else
1697        sortedTorrents = torrents;
1698
1699    enumerator = [sortedTorrents objectEnumerator];
1700    while ((torrent = [enumerator nextObject]))
1701    {
1702        if ([torrent waitingToStart])
1703        {
1704            if ([torrent progress] >= 1.0)
1705                [torrent startTransfer];
1706            else if (desiredActive > 0)
1707            {
1708                [torrent startTransfer];
1709                desiredActive--;
1710            }
1711            else
1712                continue;
1713           
1714            [torrent update];
1715        }
1716    }
1717}
1718
1719-(void) watcher: (id<UKFileWatcher>) watcher receivedNotification: (NSString *) notification forPath: (NSString *) path
1720{
1721    if ([notification isEqualToString: UKFileWatcherWriteNotification])
1722        [self checkAutoImportDirectory];
1723}
1724
1725- (void) changeAutoImport
1726{
1727    [fAutoImportedNames removeAllObjects];
1728    [self checkAutoImportDirectory];
1729}
1730
1731- (void) checkAutoImportDirectory
1732{
1733    if (![fDefaults boolForKey: @"AutoImport"])
1734        return;
1735       
1736    NSString * path = [[fDefaults stringForKey: @"AutoImportDirectory"] stringByExpandingTildeInPath];
1737   
1738    NSArray * importedNames;
1739    if (!(importedNames = [[NSFileManager defaultManager] directoryContentsAtPath: path]))
1740        return;
1741   
1742    //only import those that have not been imported yet
1743    NSMutableArray * newNames = [importedNames mutableCopy];
1744    [newNames removeObjectsInArray: fAutoImportedNames];
1745    [fAutoImportedNames setArray: importedNames];
1746   
1747    NSString * file;
1748    int i;
1749    for (i = [newNames count] - 1; i >= 0; i--)
1750    {
1751        file = [newNames objectAtIndex: i];
1752        if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] != NSOrderedSame)
1753            [newNames removeObjectAtIndex: i];
1754        else
1755            [newNames replaceObjectAtIndex: i withObject: [path stringByAppendingPathComponent: file]];
1756    }
1757   
1758    NSEnumerator * enumerator;
1759    if (![[fDefaults stringForKey: @"DownloadChoice"] isEqualToString: @"Ask"])
1760    {
1761        enumerator = [newNames objectEnumerator];
1762        int count;
1763        while ((file = [enumerator nextObject]))
1764        {
1765            count = [fTorrents count];
1766            [self openFiles: [NSArray arrayWithObject: file]];
1767           
1768            //check if torrent was opened
1769            if ([fTorrents count] > count)
1770                [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Torrent File Auto Added",
1771                    "Growl notification title") description: [file lastPathComponent]
1772                    notificationName: GROWL_AUTO_ADD iconData: nil priority: 0 isSticky: NO clickContext: nil];
1773        }
1774    }
1775    else
1776        [self openFiles: newNames];
1777   
1778    //create temporary torrents to check if an import fails because of an error
1779    enumerator = [newNames objectEnumerator];
1780    int error;
1781    while ((file = [enumerator nextObject]))
1782    {
1783        tr_torrent_t * tempTor = tr_torrentInit(fLib, [file UTF8String], 0, & error);
1784       
1785        if (tempTor)
1786            tr_torrentClose(fLib, tempTor);
1787        else if (error != TR_EUNSUPPORTED && error != TR_EDUPLICATE)
1788            [fAutoImportedNames removeObjectIdenticalTo: [file lastPathComponent]]; //can try to import later
1789        else;
1790    }
1791   
1792    [newNames release];
1793}
1794
1795- (int) numberOfRowsInTableView: (NSTableView *) tableview
1796{
1797    return [fDisplayedTorrents count];
1798}
1799
1800- (id) tableView: (NSTableView *) tableView objectValueForTableColumn: (NSTableColumn *) tableColumn row: (int) row
1801{
1802    return [[fDisplayedTorrents objectAtIndex: row] infoForCurrentView];
1803}
1804
1805- (BOOL) tableView: (NSTableView *) tableView writeRowsWithIndexes: (NSIndexSet *) indexes
1806    toPasteboard: (NSPasteboard *) pasteboard
1807{
1808    //only allow reordering of rows if sorting by order with no filter
1809    if ([fSortType isEqualToString: @"Order"] && [fFilterType isEqualToString: @"None"]
1810            && [[fSearchFilterField stringValue] length] == 0)
1811    {
1812        [pasteboard declareTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE] owner: self];
1813        [pasteboard setData: [NSKeyedArchiver archivedDataWithRootObject: indexes]
1814                                forType: TORRENT_TABLE_VIEW_DATA_TYPE];
1815        return YES;
1816    }
1817    return NO;
1818}
1819
1820- (NSDragOperation) tableView: (NSTableView *) t validateDrop: (id <NSDraggingInfo>) info
1821    proposedRow: (int) row proposedDropOperation: (NSTableViewDropOperation) operation
1822{
1823    NSPasteboard * pasteboard = [info draggingPasteboard];
1824    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
1825    {
1826        //check if any files to add have "torrent" as an extension
1827        NSEnumerator * enumerator = [[pasteboard propertyListForType: NSFilenamesPboardType] objectEnumerator];
1828        NSString * file;
1829        while ((file = [enumerator nextObject]))
1830            if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame)
1831            {
1832                [fTableView setDropRow: -1 dropOperation: NSTableViewDropOn];
1833                return NSDragOperationGeneric;
1834            }
1835    }
1836    else if ([[pasteboard types] containsObject: NSURLPboardType])
1837    {
1838        [fTableView setDropRow: row dropOperation: NSTableViewDropAbove];
1839        return NSDragOperationGeneric;
1840    }
1841    else if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
1842    {
1843        [fTableView setDropRow: row dropOperation: NSTableViewDropAbove];
1844        return NSDragOperationGeneric;
1845    }
1846    else;
1847   
1848    return NSDragOperationNone;
1849}
1850
1851- (BOOL) tableView: (NSTableView *) t acceptDrop: (id <NSDraggingInfo>) info
1852    row: (int) newRow dropOperation: (NSTableViewDropOperation) operation
1853{
1854    NSPasteboard * pasteboard = [info draggingPasteboard];
1855    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
1856    {
1857        //create an array of files with the "torrent" extension
1858        NSMutableArray * filesToOpen = [[NSMutableArray alloc] init];
1859        NSEnumerator * enumerator = [[pasteboard propertyListForType: NSFilenamesPboardType] objectEnumerator];
1860        NSString * file;
1861        while ((file = [enumerator nextObject]))
1862            if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame)
1863                [filesToOpen addObject: file];
1864   
1865        [self application: NSApp openFiles: filesToOpen];
1866        [filesToOpen release];
1867    }
1868    else if ([[pasteboard types] containsObject: NSURLPboardType])
1869    {
1870        NSURL * url;
1871        if ((url = [NSURL URLFromPasteboard: pasteboard]))
1872            [self openURL: url];
1873    }
1874    else if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
1875    {
1876        //remember selected rows if needed
1877        NSArray * selectedTorrents = nil;
1878        int numSelected = [fTableView numberOfSelectedRows];
1879        if (numSelected > 0 && numSelected < [fDisplayedTorrents count])
1880            selectedTorrents = [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]];
1881   
1882        NSIndexSet * indexes = [NSKeyedUnarchiver unarchiveObjectWithData:
1883                                [pasteboard dataForType: TORRENT_TABLE_VIEW_DATA_TYPE]];
1884       
1885        //move torrent in array
1886        NSArray * movingTorrents = [[fDisplayedTorrents objectsAtIndexes: indexes] retain];
1887        [fDisplayedTorrents removeObjectsInArray: movingTorrents];
1888       
1889        //determine the insertion index now that transfers to move have been removed
1890        int i, decrease = 0;
1891        for (i = [indexes firstIndex]; i < newRow && i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
1892            decrease++;
1893       
1894        //insert objects at new location
1895        for (i = 0; i < [movingTorrents count]; i++)
1896            [fDisplayedTorrents insertObject: [movingTorrents objectAtIndex: i] atIndex: newRow - decrease + i];
1897       
1898        [movingTorrents release];
1899       
1900        //redo order values
1901        int low = [indexes firstIndex], high = [indexes lastIndex];
1902        if (newRow < low)
1903            low = newRow;
1904        else if (newRow > high + 1)
1905            high = newRow - 1;
1906        else;
1907       
1908        for (i = low; i <= high; i++)
1909            [[fDisplayedTorrents objectAtIndex: i] setOrderValue: i];
1910       
1911        [fTableView reloadData];
1912       
1913        //set selected rows if needed
1914        if (selectedTorrents)
1915        {
1916            Torrent * torrent;
1917            NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1918            NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1919            while ((torrent = [enumerator nextObject]))
1920                [indexSet addIndex: [fDisplayedTorrents indexOfObject: torrent]];
1921           
1922            [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1923            [indexSet release];
1924        }
1925    }
1926    else;
1927   
1928    return YES;
1929}
1930
1931- (void) tableViewSelectionDidChange: (NSNotification *) notification
1932{
1933    [fInfoController updateInfoForTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
1934}
1935
1936- (void) toggleSmallView: (id) sender
1937{
1938    BOOL makeSmall = [fDefaults boolForKey: @"SmallView"];
1939   
1940    [fTableView setRowHeight: makeSmall ? ROW_HEIGHT_SMALL : ROW_HEIGHT_REGULAR];
1941   
1942    //window min height
1943    NSSize contentMinSize = [fWindow contentMinSize],
1944            contentSize = [[fWindow contentView] frame].size;
1945    contentMinSize.height = contentSize.height - [fScrollView frame].size.height
1946                            + [fTableView rowHeight] + [fTableView intercellSpacing].height;
1947    [fWindow setContentMinSize: contentMinSize];
1948   
1949    //resize for larger min height if not set to auto size
1950    if (![fDefaults boolForKey: @"AutoSize"])
1951    {
1952        if (!makeSmall && contentSize.height < contentMinSize.height)
1953        {
1954            NSRect frame = [fWindow frame];
1955            float heightChange = contentMinSize.height - contentSize.height;
1956            frame.size.height += heightChange;
1957            frame.origin.y -= heightChange;
1958           
1959            [fWindow setFrame: frame display: YES];
1960            [fTableView reloadData];
1961        }
1962    }
1963    else
1964        [self setWindowSizeToFit];
1965}
1966
1967- (void) toggleStatusBar: (id) sender
1968{
1969    [self showStatusBar: [fStatusBar isHidden] animate: YES];
1970    [fDefaults setBool: ![fStatusBar isHidden] forKey: @"StatusBar"];
1971}
1972
1973- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
1974{
1975    if (show != [fStatusBar isHidden])
1976        return;
1977
1978    if (show)
1979        [fStatusBar setHidden: NO];
1980
1981    NSRect frame = [fWindow frame];
1982    float heightChange = [fStatusBar frame].size.height;
1983    if (!show)
1984        heightChange *= -1;
1985   
1986    //allow bar to show even if not enough room
1987    if (show && ![fDefaults boolForKey: @"AutoSize"])
1988    {
1989        float maxHeight = [[fWindow screen] visibleFrame].size.height - heightChange;
1990        if (frame.size.height > maxHeight)
1991        {
1992            float change = maxHeight - frame.size.height;
1993            frame.size.height += change;
1994            frame.origin.y -= change;
1995           
1996            [fWindow setFrame: frame display: NO animate: NO];
1997        }
1998    }
1999
2000    frame.size.height += heightChange;
2001    frame.origin.y -= heightChange;
2002   
2003    [self updateUI: nil];
2004   
2005    //set views to not autoresize
2006    unsigned int statsMask = [fStatusBar autoresizingMask];
2007    unsigned int filterMask = [fFilterBar autoresizingMask];
2008    unsigned int scrollMask = [fScrollView autoresizingMask];
2009    [fStatusBar setAutoresizingMask: 0];
2010    [fFilterBar setAutoresizingMask: 0];
2011    [fScrollView setAutoresizingMask: 0];
2012   
2013    [fWindow setFrame: frame display: YES animate: animate];
2014   
2015    //re-enable autoresize
2016    [fStatusBar setAutoresizingMask: statsMask];
2017    [fFilterBar setAutoresizingMask: filterMask];
2018    [fScrollView setAutoresizingMask: scrollMask];
2019   
2020    //change min size
2021    NSSize minSize = [fWindow contentMinSize];
2022    minSize.height += heightChange;
2023    [fWindow setContentMinSize: minSize];
2024   
2025    if (!show)
2026        [fStatusBar setHidden: YES];
2027}
2028
2029- (void) toggleFilterBar: (id) sender
2030{
2031    //disable filtering when hiding
2032    if (![fFilterBar isHidden])
2033    {
2034        [fSearchFilterField setStringValue: @""];
2035        [self setFilter: fNoFilterButton];
2036    }
2037
2038    [self showFilterBar: [fFilterBar isHidden] animate: YES];
2039    [fDefaults setBool: ![fFilterBar isHidden] forKey: @"FilterBar"];
2040}
2041
2042- (void) showFilterBar: (BOOL) show animate: (BOOL) animate
2043{
2044    if (show != [fFilterBar isHidden])
2045        return;
2046
2047    if (show)
2048        [fFilterBar setHidden: NO];
2049
2050    NSRect frame = [fWindow frame];
2051    float heightChange = [fFilterBar frame].size.height;
2052    if (!show)
2053        heightChange *= -1;
2054   
2055    //allow bar to show even if not enough room
2056    if (show && ![fDefaults boolForKey: @"AutoSize"])
2057    {
2058        float maxHeight = [[fWindow screen] visibleFrame].size.height - heightChange;
2059        if (frame.size.height > maxHeight)
2060        {
2061            float change = maxHeight - frame.size.height;
2062            frame.size.height += change;
2063            frame.origin.y -= change;
2064           
2065            [fWindow setFrame: frame display: NO animate: NO];
2066        }
2067    }
2068
2069    frame.size.height += heightChange;
2070    frame.origin.y -= heightChange;
2071   
2072    //set views to not autoresize
2073    unsigned int filterMask = [fFilterBar autoresizingMask];
2074    unsigned int scrollMask = [fScrollView autoresizingMask];
2075    [fFilterBar setAutoresizingMask: 0];
2076    [fScrollView setAutoresizingMask: 0];
2077   
2078    [fWindow setFrame: frame display: YES animate: animate];
2079   
2080    //re-enable autoresize
2081    [fFilterBar setAutoresizingMask: filterMask];
2082    [fScrollView setAutoresizingMask: scrollMask];
2083   
2084    //change min size
2085    NSSize minSize = [fWindow contentMinSize];
2086    minSize.height += heightChange;
2087    [fWindow setContentMinSize: minSize];
2088   
2089    if (!show)
2090    {
2091        [fFilterBar setHidden: YES];
2092        [fWindow makeFirstResponder: fTableView];
2093    }
2094}
2095
2096- (void) toggleAdvancedBar: (id) sender
2097{
2098    [fTableView display];
2099}
2100
2101- (void) doNothing: (id) sender {}
2102
2103- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
2104    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
2105{
2106    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
2107
2108    if ([ident isEqualToString: TOOLBAR_OPEN])
2109    {
2110        [item setLabel: NSLocalizedString(@"Open", "Open toolbar item -> label")];
2111        [item setPaletteLabel: NSLocalizedString(@"Open Torrent Files", "Open toolbar item -> palette label")];
2112        [item setToolTip: NSLocalizedString(@"Open torrent files", "Open toolbar item -> tooltip")];
2113        [item setImage: [NSImage imageNamed: @"Open.png"]];
2114        [item setTarget: self];
2115        [item setAction: @selector(openShowSheet:)];
2116        [item setAutovalidates: NO];
2117    }
2118    else if ([ident isEqualToString: TOOLBAR_REMOVE])
2119    {
2120        [item setLabel: NSLocalizedString(@"Remove", "Remove toolbar item -> label")];
2121        [item setPaletteLabel: NSLocalizedString(@"Remove Selected", "Remove toolbar item -> palette label")];
2122        [item setToolTip: NSLocalizedString(@"Remove selected transfers", "Remove toolbar item -> tooltip")];
2123        [item setImage: [NSImage imageNamed: @"Remove.png"]];
2124        [item setTarget: self];
2125        [item setAction: @selector(removeNoDelete:)];
2126    }
2127    else if ([ident isEqualToString: TOOLBAR_INFO])
2128    {
2129        [item setLabel: NSLocalizedString(@"Inspector", "Inspector toolbar item -> label")];
2130        [item setPaletteLabel: NSLocalizedString(@"Toggle Inspector", "Inspector toolbar item -> palette label")];
2131        [item setToolTip: NSLocalizedString(@"Toggle the torrent inspector", "Inspector toolbar item -> tooltip")];
2132        [item setImage: [NSImage imageNamed: @"Info.png"]];
2133        [item setTarget: self];
2134        [item setAction: @selector(showInfo:)];
2135        [item setAutovalidates: NO];
2136    }
2137    else if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
2138    {
2139        [item setLabel: NSLocalizedString(@"Pause All", "Pause All toolbar item -> label")];
2140        [item setPaletteLabel: [item label]];
2141        [item setToolTip: NSLocalizedString(@"Pause all transfers", "Pause All toolbar item -> tooltip")];
2142        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
2143        [item setTarget: self];
2144        [item setAction: @selector(stopAllTorrents:)];
2145    }
2146    else if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
2147    {
2148        [item setLabel: NSLocalizedString(@"Resume All", "Resume All toolbar item -> label")];
2149        [item setPaletteLabel: [item label]];
2150        [item setToolTip: NSLocalizedString(@"Resume all transfers", "Resume All toolbar item -> tooltip")];
2151        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
2152        [item setTarget: self];
2153        [item setAction: @selector(resumeAllTorrents:)];
2154    }
2155    else if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
2156    {
2157        [item setLabel: NSLocalizedString(@"Pause", "Pause toolbar item -> label")];
2158        [item setPaletteLabel: NSLocalizedString(@"Pause Selected", "Pause toolbar item -> palette label")];
2159        [item setToolTip: NSLocalizedString(@"Pause selected transfers", "Pause toolbar item -> tooltip")];
2160        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
2161        [item setTarget: self];
2162        [item setAction: @selector(stopSelectedTorrents:)];
2163    }
2164    else if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
2165    {
2166        [item setLabel: NSLocalizedString(@"Resume", "Resume toolbar item -> label")];
2167        [item setPaletteLabel: NSLocalizedString(@"Resume Selected", "Resume toolbar item -> palette label")];
2168        [item setToolTip: NSLocalizedString(@"Resume selected transfers", "Resume toolbar item -> tooltip")];
2169        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
2170        [item setTarget: self];
2171        [item setAction: @selector(resumeSelectedTorrents:)];
2172    }
2173    else if ([ident isEqualToString: TOOLBAR_FILTER])
2174    {
2175        [item setLabel: NSLocalizedString(@"Filter", "Filter toolbar item -> label")];
2176        [item setPaletteLabel: NSLocalizedString(@"Toggle Filter", "Filter toolbar item -> palette label")];
2177        [item setToolTip: NSLocalizedString(@"Toggle the filter bar", "Filter toolbar item -> tooltip")];
2178        [item setImage: [NSImage imageNamed: @"Filter.png"]];
2179        [item setTarget: self];
2180        [item setAction: @selector(toggleFilterBar:)];
2181        [item setAutovalidates: NO];
2182    }
2183    else
2184    {
2185        [item release];
2186        return nil;
2187    }
2188
2189    return item;
2190}
2191
2192- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
2193{
2194    return [NSArray arrayWithObjects:
2195            TOOLBAR_OPEN, TOOLBAR_REMOVE,
2196            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
2197            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL, TOOLBAR_FILTER, TOOLBAR_INFO,
2198            NSToolbarSeparatorItemIdentifier,
2199            NSToolbarSpaceItemIdentifier,
2200            NSToolbarFlexibleSpaceItemIdentifier,
2201            NSToolbarCustomizeToolbarItemIdentifier, nil];
2202}
2203
2204- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
2205{
2206    return [NSArray arrayWithObjects:
2207            TOOLBAR_OPEN, TOOLBAR_REMOVE,
2208            NSToolbarSeparatorItemIdentifier,
2209            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
2210            NSToolbarFlexibleSpaceItemIdentifier,
2211            TOOLBAR_FILTER, TOOLBAR_INFO, nil];
2212}
2213
2214- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
2215{
2216    NSString * ident = [toolbarItem itemIdentifier];
2217
2218    //enable remove item
2219    if ([ident isEqualToString: TOOLBAR_REMOVE])
2220        return [fTableView numberOfSelectedRows] > 0;
2221
2222    //enable pause all item
2223    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
2224    {
2225        Torrent * torrent;
2226        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2227        while ((torrent = [enumerator nextObject]))
2228            if ([torrent isActive] || [torrent waitingToStart])
2229                return YES;
2230        return NO;
2231    }
2232
2233    //enable resume all item
2234    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
2235    {
2236        Torrent * torrent;
2237        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2238        while ((torrent = [enumerator nextObject]))
2239            if ([torrent isPaused] && ![torrent waitingToStart])
2240                return YES;
2241        return NO;
2242    }
2243
2244    //enable pause item
2245    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
2246    {
2247        Torrent * torrent;
2248        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
2249        unsigned int i;
2250       
2251        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
2252        {
2253            torrent = [fDisplayedTorrents objectAtIndex: i];
2254            if ([torrent isActive] || [torrent waitingToStart])
2255                return YES;
2256        }
2257        return NO;
2258    }
2259   
2260    //enable resume item
2261    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
2262    {
2263        Torrent * torrent;
2264        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
2265        unsigned int i;
2266       
2267        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
2268        {
2269            torrent = [fDisplayedTorrents objectAtIndex: i];
2270            if ([torrent isPaused] && ![torrent waitingToStart])
2271                return YES;
2272        }
2273        return NO;
2274    }
2275
2276    return YES;
2277}
2278
2279- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
2280{
2281    SEL action = [menuItem action];
2282
2283    //only enable some items if it is in a context menu or the window is useable
2284    BOOL canUseTable = [fWindow isKeyWindow] || [[[menuItem menu] title] isEqualToString: @"Context"];
2285
2286    //enable open items
2287    if (action == @selector(openShowSheet:) || action == @selector(openURLShowSheet:))
2288        return [fWindow attachedSheet] == nil;
2289   
2290    //enable sort and advanced bar items
2291    if (action == @selector(setSort:) || action == @selector(toggleAdvancedBar:) || action == @selector(toggleSmallView:))
2292        return [fWindow isVisible];
2293
2294    //enable show info
2295    if (action == @selector(showInfo:))
2296    {
2297        NSString * title = [[fInfoController window] isVisible] ? NSLocalizedString(@"Hide Inspector",
2298                            "View menu -> Inspector") : NSLocalizedString(@"Show Inspector", "View menu -> Inspector");
2299        if (![[menuItem title] isEqualToString: title])
2300                [menuItem setTitle: title];
2301
2302        return YES;
2303    }
2304   
2305    //enable prev/next inspector tab
2306    if (action == @selector(setInfoTab:))
2307        return [[fInfoController window] isVisible];
2308   
2309    //enable toggle status bar
2310    if (action == @selector(toggleStatusBar:))
2311    {
2312        NSString * title = [fStatusBar isHidden] ? NSLocalizedString(@"Show Status Bar", "View menu -> Status Bar")
2313                            : NSLocalizedString(@"Hide Status Bar", "View menu -> Status Bar");
2314        if (![[menuItem title] isEqualToString: title])
2315            [menuItem setTitle: title];
2316
2317        return [fWindow isVisible];
2318    }
2319   
2320    //enable toggle filter bar
2321    if (action == @selector(toggleFilterBar:))
2322    {
2323        NSString * title = [fFilterBar isHidden] ? NSLocalizedString(@"Show Filter Bar", "View menu -> Filter Bar")
2324                            : NSLocalizedString(@"Hide Filter Bar", "View menu -> Filter Bar");
2325        if (![[menuItem title] isEqualToString: title])
2326            [menuItem setTitle: title];
2327
2328        return [fWindow isVisible];
2329    }
2330   
2331    //enable prev/next filter button
2332    if (action == @selector(switchFilter:))
2333        return [fWindow isVisible] && ![fFilterBar isHidden];
2334
2335    //enable reveal in finder
2336    if (action == @selector(revealFile:))
2337        return canUseTable && [fTableView numberOfSelectedRows] > 0;
2338
2339    //enable remove items
2340    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
2341        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteDataAndTorrent:))
2342    {
2343        BOOL warning = NO,
2344            onlyDownloading = [fDefaults boolForKey: @"CheckRemoveDownloading"],
2345            canDelete = action != @selector(removeDeleteTorrent:) && action != @selector(removeDeleteDataAndTorrent:);
2346        Torrent * torrent;
2347        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
2348        unsigned int i;
2349       
2350        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
2351        {
2352            torrent = [fDisplayedTorrents objectAtIndex: i];
2353            if (!warning && [torrent isActive])
2354            {
2355                warning = onlyDownloading ? ![torrent isSeeding] : YES;
2356                if (warning && canDelete)
2357                    break;
2358            }
2359            if (!canDelete && [torrent publicTorrent])
2360            {
2361                canDelete = YES;
2362                if (warning)
2363                    break;
2364            }
2365        }
2366   
2367        //append or remove ellipsis when needed
2368        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
2369        if (warning && [fDefaults boolForKey: @"CheckRemove"])
2370        {
2371            if (![title hasSuffix: ellipsis])
2372                [menuItem setTitle: [title stringByAppendingEllipsis]];
2373        }
2374        else
2375        {
2376            if ([title hasSuffix: ellipsis])
2377                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
2378        }
2379       
2380        return canUseTable && canDelete && [fTableView numberOfSelectedRows] > 0;
2381    }
2382
2383    //enable pause all item
2384    if (action == @selector(stopAllTorrents:))
2385    {
2386        Torrent * torrent;
2387        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2388        while ((torrent = [enumerator nextObject]))
2389            if ([torrent isActive] || [torrent waitingToStart])
2390                return YES;
2391        return NO;
2392    }
2393   
2394    //enable resume all item
2395    if (action == @selector(resumeAllTorrents:))
2396    {
2397        Torrent * torrent;
2398        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2399        while ((torrent = [enumerator nextObject]))
2400            if ([torrent isPaused] && ![torrent waitingToStart])
2401                return YES;
2402        return NO;
2403    }
2404   
2405    //enable resume all waiting item
2406    if (action == @selector(resumeWaitingTorrents:))
2407    {
2408        if (![fDefaults boolForKey: @"Queue"])
2409            return NO;
2410   
2411        Torrent * torrent;
2412        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2413        while ((torrent = [enumerator nextObject]))
2414            if ([torrent waitingToStart])
2415                return YES;
2416        return NO;
2417    }
2418   
2419    //enable resume selected waiting item
2420    if (action == @selector(resumeSelectedTorrentsNoWait:))
2421    {
2422        if (![fDefaults boolForKey: @"Queue"])
2423            return NO;
2424   
2425        Torrent * torrent;
2426        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
2427        unsigned int i;
2428       
2429        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
2430        {
2431            torrent = [fDisplayedTorrents objectAtIndex: i];
2432            if ([torrent isPaused] && [torrent progress] < 1.0)
2433                return YES;
2434        }
2435        return NO;
2436    }
2437
2438    //enable pause item
2439    if (action == @selector(stopSelectedTorrents:))
2440    {
2441        if (!canUseTable)
2442            return NO;
2443   
2444        Torrent * torrent;
2445        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
2446        unsigned int i;
2447       
2448        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
2449        {
2450            torrent = [fDisplayedTorrents objectAtIndex: i];
2451            if ([torrent isActive] || [torrent waitingToStart])
2452                return YES;
2453        }
2454        return NO;
2455    }
2456   
2457    //enable resume item
2458    if (action == @selector(resumeSelectedTorrents:))
2459    {
2460        if (!canUseTable)
2461            return NO;
2462   
2463        Torrent * torrent;
2464        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
2465        unsigned int i;
2466       
2467        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
2468        {
2469            torrent = [fDisplayedTorrents objectAtIndex: i];
2470            if ([torrent isPaused] && ![torrent waitingToStart])
2471                return YES;
2472        }
2473        return NO;
2474    }
2475   
2476    //enable copy torrent file item
2477    if (action == @selector(copyTorrentFile:))
2478        return canUseTable && [fTableView numberOfSelectedRows] > 0;
2479
2480    return YES;
2481}
2482
2483- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument
2484{
2485    NSEnumerator * enumerator;
2486    Torrent * torrent;
2487    BOOL active;
2488
2489    switch (messageType)
2490    {
2491        case kIOMessageSystemWillSleep:
2492            //close all connections before going to sleep and remember we should resume when we wake up
2493            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
2494
2495            //wait for running transfers to stop (5 second timeout)
2496            NSDate * start = [NSDate date];
2497            BOOL timeUp = NO;
2498           
2499            NSEnumerator * enumerator = [fTorrents objectEnumerator];
2500            Torrent * torrent;
2501            while (!timeUp && (torrent = [enumerator nextObject]))
2502                while (![torrent isPaused] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
2503                {
2504                    usleep(100000);
2505                    [torrent update];
2506                }
2507
2508            IOAllowPowerChange(fRootPort, (long) messageArgument);
2509            break;
2510
2511        case kIOMessageCanSystemSleep:
2512            //pevent idle sleep unless all paused
2513            active = NO;
2514            enumerator = [fTorrents objectEnumerator];
2515            while ((torrent = [enumerator nextObject]))
2516                if ([torrent isActive])
2517                {
2518                    active = YES;
2519                    break;
2520                }
2521
2522            if (active)
2523                IOCancelPowerChange(fRootPort, (long) messageArgument);
2524            else
2525                IOAllowPowerChange(fRootPort, (long) messageArgument);
2526            break;
2527
2528        case kIOMessageSystemHasPoweredOn:
2529            //resume sleeping transfers after we wake up
2530            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
2531            break;
2532    }
2533}
2534
2535- (void) resetDockBadge: (NSNotification *) notification
2536{
2537    float downloadRate, uploadRate;
2538    tr_torrentRates(fLib, & downloadRate, & uploadRate);
2539   
2540    [fBadger updateBadgeWithCompleted: fCompleted uploadRate: uploadRate downloadRate: downloadRate];
2541}
2542
2543- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
2544{
2545    //if auto size is enabled, the current frame shouldn't need to change
2546    NSRect frame = [fDefaults boolForKey: @"AutoSize"] ? [window frame] : [self sizedWindowFrame];
2547   
2548    frame.size.width = [fDefaults boolForKey: @"SmallView"] ? [fWindow minSize].width : WINDOW_REGULAR_WIDTH;
2549    return frame;
2550}
2551
2552- (void) setWindowSizeToFit
2553{
2554    if ([fDefaults boolForKey: @"AutoSize"])
2555    {
2556        [fScrollView setHasVerticalScroller: NO];
2557        [fWindow setFrame: [self sizedWindowFrame] display: YES animate: YES];
2558        [fScrollView setHasVerticalScroller: YES];
2559    }
2560}
2561
2562- (NSRect) sizedWindowFrame
2563{
2564    NSRect frame = [fWindow frame];
2565    float newHeight = frame.size.height - [fScrollView frame].size.height
2566        + [fDisplayedTorrents count] * ([fTableView rowHeight] + [fTableView intercellSpacing].height);
2567
2568    float minHeight = [fWindow minSize].height;
2569    if (newHeight < minHeight)
2570        newHeight = minHeight;
2571    else
2572    {
2573        float maxHeight = [[fWindow screen] visibleFrame].size.height;
2574        if ([fStatusBar isHidden])
2575            maxHeight -= [fStatusBar frame].size.height;
2576        if ([fFilterBar isHidden])
2577            maxHeight -= [fFilterBar frame].size.height;
2578       
2579        if (newHeight > maxHeight)
2580            newHeight = maxHeight;
2581    }
2582
2583    frame.origin.y -= (newHeight - frame.size.height);
2584    frame.size.height = newHeight;
2585    return frame;
2586}
2587
2588- (void) showMainWindow: (id) sender
2589{
2590    [fWindow makeKeyAndOrderFront: nil];
2591}
2592
2593- (void) windowDidBecomeKey: (NSNotification *) notification
2594{
2595    //reset dock badge for completed
2596    if (fCompleted > 0)
2597    {
2598        fCompleted = 0;
2599        [self resetDockBadge: nil];
2600    }
2601}
2602
2603- (NSSize) windowWillResize: (NSWindow *) sender toSize: (NSSize) proposedFrameSize
2604{
2605    //only resize horizontally if autosize is enabled
2606    if ([fDefaults boolForKey: @"AutoSize"])
2607        proposedFrameSize.height = [fWindow frame].size.height;
2608    return proposedFrameSize;
2609}
2610
2611- (void) windowDidResize: (NSNotification *) notification
2612{
2613    //hide search filter if it overlaps filter buttons
2614    [fSearchFilterField setHidden: NSMaxX([fPauseFilterButton frame]) + 2.0 > [fSearchFilterField frame].origin.x];
2615}
2616
2617- (void) linkHomepage: (id) sender
2618{
2619    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
2620}
2621
2622- (void) linkForums: (id) sender
2623{
2624    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
2625}
2626
2627- (void) prepareForUpdate: (NSNotification *) notification
2628{
2629    fUpdateInProgress = YES;
2630}
2631
2632- (NSDictionary *) registrationDictionaryForGrowl
2633{
2634    NSArray * notifications = [NSArray arrayWithObjects: GROWL_DOWNLOAD_COMPLETE, GROWL_SEEDING_COMPLETE,
2635                                                            GROWL_AUTO_ADD, GROWL_AUTO_SPEED_LIMIT, nil];
2636    return [NSDictionary dictionaryWithObjectsAndKeys: notifications, GROWL_NOTIFICATIONS_ALL,
2637                                notifications, GROWL_NOTIFICATIONS_DEFAULT, nil];
2638}
2639
2640@end
Note: See TracBrowser for help on using the repository browser.