source: trunk/macosx/Controller.m @ 1088

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

quick fix for last commit

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