source: trunk/macosx/Controller.m @ 1117

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

url entry window is now a bit like iTunes's

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