source: trunk/macosx/Controller.m @ 1116

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

some more fixes with url dragging and url download error messages

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