source: trunk/macosx/Controller.m @ 1102

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

show sheet where torrent url can be entered, with some basic string to url conversion

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