source: trunk/macosx/Controller.m @ 1081

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

Patch by Brian Webster to automatically download and open torrents by dragging url's onto the window/dock icon.

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