source: trunk/macosx/Controller.m @ 1110

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

one less log :)

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