source: trunk/macosx/Controller.m @ 1089

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

When loading from url, if set to download at the torrent location ask the user for a location instead

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