source: trunk/macosx/Controller.m @ 624

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

Turn multiple classes into a generic class for image backgrounds.

  • Property svn:keywords set to Date Rev Author Id
File size: 70.8 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 624 2006-07-16 21:02:29Z 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
33#import <Sparkle/Sparkle.h>
34#import <Growl/GrowlApplicationBridge.h>
35
36#define TOOLBAR_OPEN            @"Toolbar Open"
37#define TOOLBAR_REMOVE          @"Toolbar Remove"
38#define TOOLBAR_INFO            @"Toolbar Info"
39#define TOOLBAR_PAUSE_ALL       @"Toolbar Pause All"
40#define TOOLBAR_RESUME_ALL      @"Toolbar Resume All"
41#define TOOLBAR_PAUSE_SELECTED  @"Toolbar Pause Selected"
42#define TOOLBAR_RESUME_SELECTED @"Toolbar Resume Selected"
43
44#define TORRENT_TABLE_VIEW_DATA_TYPE    @"TorrentTableViewDataType"
45
46#define ROW_HEIGHT_REGULAR 65.0
47#define ROW_HEIGHT_SMALL 40.0
48
49#define WEBSITE_URL @"http://transmission.m0k.org/"
50#define FORUM_URL   @"http://transmission.m0k.org/forum/"
51
52static void sleepCallBack(void * controller, io_service_t y,
53        natural_t messageType, void * messageArgument)
54{
55    Controller * c = controller;
56    [c sleepCallBack: messageType argument: messageArgument];
57}
58
59
60@implementation Controller
61
62- (id) init
63{
64    if ((self = [super init]))
65    {
66        fLib = tr_init();
67       
68        fTorrents = [[NSMutableArray alloc] initWithCapacity: 10];
69        fFilteredTorrents = [[NSMutableArray alloc] initWithCapacity: 10];
70       
71        fDefaults = [NSUserDefaults standardUserDefaults];
72        fInfoController = [[InfoWindowController alloc] initWithWindowNibName: @"InfoWindow"];
73        fPrefsController = [[PrefsController alloc] initWithWindowNibName: @"PrefsWindow"];
74        fBadger = [[Badger alloc] init];
75       
76        [GrowlApplicationBridge setGrowlDelegate: self];
77    }
78    return self;
79}
80
81- (void) dealloc
82{
83    [[NSNotificationCenter defaultCenter] removeObserver: self];
84
85    [fTorrents release];
86    [fFilteredTorrents release];
87   
88    [fToolbar release];
89    [fInfoController release];
90    [fPrefsController release];
91    [fBadger release];
92   
93    [fSortType release];
94    [fFilterType release];
95    [fAutoImportedNames release];
96   
97    tr_close(fLib);
98    [super dealloc];
99}
100
101- (void) awakeFromNib
102{
103    [fPrefsController setPrefs: fLib];
104   
105    [fStatusBar setBackgroundImage: [NSImage imageNamed: @"StatusBorder.png"]];
106    [fFilterBar setBackgroundImage: [NSImage imageNamed: @"FilterBarBackground.png"]];
107   
108    [fWindow setAcceptsMouseMovedEvents: YES]; //ensure filter buttons display correctly
109   
110    [fAdvancedBarItem setState: [fDefaults boolForKey: @"UseAdvancedBar"]];
111
112    fToolbar = [[NSToolbar alloc] initWithIdentifier: @"Transmission Toolbar"];
113    [fToolbar setDelegate: self];
114    [fToolbar setAllowsUserCustomization: YES];
115    [fToolbar setAutosavesConfiguration: YES];
116    [fWindow setToolbar: fToolbar];
117    [fWindow setDelegate: self];
118   
119    [fWindow makeFirstResponder: fTableView];
120   
121    //set table size
122    if ([fDefaults boolForKey: @"SmallView"])
123    {
124        [fTableView setRowHeight: ROW_HEIGHT_SMALL];
125        [fSmallViewItem setState: NSOnState];
126    }
127   
128    //window min height
129    NSSize contentMinSize = [fWindow contentMinSize];
130    contentMinSize.height = [[fWindow contentView] frame].size.height - [fScrollView frame].size.height
131                                + [fTableView rowHeight] + [fTableView intercellSpacing].height;
132    [fWindow setContentMinSize: contentMinSize];
133   
134    //set info keyboard shortcuts
135    unichar ch = NSRightArrowFunctionKey;
136    [fNextInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & ch length: 1]];
137    ch = NSLeftArrowFunctionKey;
138    [fPrevInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & ch length: 1]];
139   
140    //set up filter bar
141    NSView * contentView = [fWindow contentView];
142   
143    fFilterBarVisible = NO;
144    NSRect filterBarFrame = [fFilterBar frame];
145    filterBarFrame.size.width = [fWindow frame].size.width;
146    [fFilterBar setFrame: filterBarFrame];
147   
148    [contentView addSubview: fFilterBar];
149    [fFilterBar setFrameOrigin: NSMakePoint(0, [contentView frame].origin.y + [contentView frame].size.height)];
150    [self showFilterBar: [fDefaults boolForKey: @"FilterBar"] animate: NO];
151   
152    //set up status bar
153    fStatusBarVisible = NO;
154    NSRect statusBarFrame = [fStatusBar frame];
155    statusBarFrame.size.width = [fWindow frame].size.width;
156    [fStatusBar setFrame: statusBarFrame];
157   
158    [contentView addSubview: fStatusBar];
159    [fStatusBar setFrameOrigin: NSMakePoint(0, [contentView frame].origin.y + [contentView frame].size.height)];
160    [self showStatusBar: [fDefaults boolForKey: @"StatusBar"] animate: NO];
161   
162    //set speed limit
163    [self updateControlTint: nil];
164   
165    if ([fDefaults boolForKey: @"SpeedLimit"])
166    {
167        [fSpeedLimitItem setState: NSOnState];
168        [fSpeedLimitDockItem setState: NSOnState];
169        [fSpeedLimitButton setState: NSOnState];
170    }
171   
172    [[fSpeedLimitButton cell] setHighlightsBy: NSNoCellMask];
173
174    [fActionButton setToolTip: @"Shortcuts for changing global settings."];
175    [fSpeedLimitButton setToolTip: @"Speed Limit overrides the total bandwidth limits with its own limits."];
176
177    [fTableView setTorrents: fFilteredTorrents];
178    [[fTableView tableColumnWithIdentifier: @"Torrent"] setDataCell: [[TorrentCell alloc] init]];
179
180    [fTableView registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType,
181                                                        TORRENT_TABLE_VIEW_DATA_TYPE, nil]];
182
183    //register for sleep notifications
184    IONotificationPortRef notify;
185    io_object_t iterator;
186    if (fRootPort = IORegisterForSystemPower(self, & notify, sleepCallBack, & iterator))
187        CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify),
188                            kCFRunLoopCommonModes);
189    else
190        NSLog(@"Could not IORegisterForSystemPower");
191
192    //load torrents from history
193    Torrent * torrent;
194    NSDictionary * historyItem;
195    NSEnumerator * enumerator = [[fDefaults arrayForKey: @"History"] objectEnumerator];
196    while ((historyItem = [enumerator nextObject]))
197        if ((torrent = [[Torrent alloc] initWithHistory: historyItem lib: fLib]))
198        {
199            [fTorrents addObject: torrent];
200            [torrent release];
201        }
202   
203    [self torrentNumberChanged];
204   
205    //set sort
206    fSortType = [[fDefaults stringForKey: @"Sort"] retain];
207   
208    NSMenuItem * currentSortItem;
209    if ([fSortType isEqualToString: @"Name"])
210        currentSortItem = fNameSortItem;
211    else if ([fSortType isEqualToString: @"State"])
212        currentSortItem = fStateSortItem;
213    else if ([fSortType isEqualToString: @"Progress"])
214        currentSortItem = fProgressSortItem;
215    else if ([fSortType isEqualToString: @"Date"])
216        currentSortItem = fDateSortItem;
217    else
218        currentSortItem = fOrderSortItem;
219    [currentSortItem setState: NSOnState];
220   
221    //set filter
222    fFilterType = [[fDefaults stringForKey: @"Filter"] retain];
223
224    [fNoFilterButton setText: @"All"];
225    [fPauseFilterButton setText: @"Paused"];
226    [fSeedFilterButton setText: @"Seeding"];
227    [fDownloadFilterButton setText: @"Downloading"];
228
229    NSMenuItem * currentFilterItem;
230    BarButton * currentFilterButton;
231    if ([fFilterType isEqualToString: @"Pause"])
232        currentFilterButton = fPauseFilterButton;
233    else if ([fFilterType isEqualToString: @"Seed"])
234        currentFilterButton = fSeedFilterButton;
235    else if ([fFilterType isEqualToString: @"Download"])
236        currentFilterButton = fDownloadFilterButton;
237    else
238        currentFilterButton = fNoFilterButton;
239
240    [currentFilterButton setEnabled: YES];
241   
242    //set upload limit action button
243    [fUploadLimitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
244                    [fDefaults integerForKey: @"UploadLimit"]]];
245    if ([fDefaults boolForKey: @"CheckUpload"])
246        [fUploadLimitItem setState: NSOnState];
247    else
248        [fUploadNoLimitItem setState: NSOnState];
249
250        //set download limit action menu
251    [fDownloadLimitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
252                    [fDefaults integerForKey: @"DownloadLimit"]]];
253    if ([fDefaults boolForKey: @"CheckDownload"])
254        [fDownloadLimitItem setState: NSOnState];
255    else
256        [fDownloadNoLimitItem setState: NSOnState];
257   
258    //set ratio action menu
259    [fRatioSetItem setTitle: [NSString stringWithFormat: @"Stop at Ratio (%.2f)",
260                                [fDefaults floatForKey: @"RatioLimit"]]];
261    if ([fDefaults boolForKey: @"RatioCheck"])
262        [fRatioSetItem setState: NSOnState];
263    else
264        [fRatioNotSetItem setState: NSOnState];
265   
266    //observe notifications
267    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
268   
269    [nc addObserver: self selector: @selector(updateControlTint:)
270                    name: NSControlTintDidChangeNotification object: nil];
271   
272    [nc addObserver: self selector: @selector(prepareForUpdate:)
273                    name: SUUpdaterWillRestartNotification object: nil];
274    fUpdateInProgress = NO;
275   
276    [nc addObserver: self selector: @selector(limitGlobalChange:)
277                    name: @"LimitGlobalChange" object: nil];
278   
279    [nc addObserver: self selector: @selector(ratioGlobalChange:)
280                    name: @"RatioGlobalChange" object: nil];
281   
282    //check to start another because of stopped torrent
283    [nc addObserver: self selector: @selector(checkWaitingForStopped:)
284                    name: @"StoppedDownloading" object: nil];
285   
286    //check all torrents for starting
287    [nc addObserver: self selector: @selector(globalStartSettingChange:)
288                    name: @"GlobalStartSettingChange" object: nil];
289
290    //check if torrent should now start
291    [nc addObserver: self selector: @selector(torrentStartSettingChange:)
292                    name: @"TorrentStartSettingChange" object: nil];
293   
294    //check if torrent should now start
295    [nc addObserver: self selector: @selector(torrentStoppedForRatio:)
296                    name: @"TorrentStoppedForRatio" object: nil];
297   
298    //change that just impacts the inspector
299    [nc addObserver: self selector: @selector(reloadInspectorSettings:)
300                    name: @"TorrentSettingChange" object: nil];
301   
302    //reset auto import
303    [nc addObserver: self selector: @selector(autoImportChange:)
304                    name: @"AutoImportSettingChange" object: nil];
305
306    //timer to update the interface every second
307    fCompleted = 0;
308    [self updateUI: nil];
309    fTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self
310        selector: @selector(updateUI:) userInfo: nil repeats: YES];
311    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSModalPanelRunLoopMode];
312    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSEventTrackingRunLoopMode];
313   
314    //timer for auto import, will check every 15 seconds
315    fAutoImportedNames = [[NSMutableArray alloc] init];
316   
317    [self checkAutoImportDirectory: nil];
318    fAutoImportTimer = [NSTimer scheduledTimerWithTimeInterval: 15.0 target: self
319        selector: @selector(checkAutoImportDirectory:) userInfo: nil repeats: YES];
320    [[NSRunLoop currentRunLoop] addTimer: fAutoImportTimer forMode: NSDefaultRunLoopMode];
321   
322    [self applyFilter: nil];
323   
324    [fWindow makeKeyAndOrderFront: nil];
325
326    //load info for no torrents
327    [fInfoController updateInfoForTorrents: [NSArray array]];
328    if ([fDefaults boolForKey: @"InfoVisible"])
329        [self showInfo: nil];
330}
331
332- (BOOL) applicationShouldHandleReopen: (NSApplication *) app hasVisibleWindows: (BOOL) visibleWindows
333{
334    if (![fWindow isVisible] && ![[fPrefsController window] isVisible])
335        [fWindow makeKeyAndOrderFront: nil];
336    return NO;
337}
338
339- (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication *) sender
340{
341    if (!fUpdateInProgress && [fDefaults boolForKey: @"CheckQuit"])
342    {
343        int active = 0, downloading = 0;
344        Torrent * torrent;
345        NSEnumerator * enumerator = [fTorrents objectEnumerator];
346        while ((torrent = [enumerator nextObject]))
347            if ([torrent isActive])
348            {
349                active++;
350                if (![torrent isSeeding])
351                    downloading++;
352            }
353
354        if ([fDefaults boolForKey: @"CheckQuitDownloading"] ? downloading > 0 : active > 0)
355        {
356            NSString * message = active == 1
357                ? @"There is an active transfer. Do you really want to quit?"
358                : [NSString stringWithFormat:
359                    @"There are %d active transfers. Do you really want to quit?", active];
360
361            NSBeginAlertSheet(@"Confirm Quit", @"Quit", @"Cancel", nil, fWindow, self,
362                                @selector(quitSheetDidEnd:returnCode:contextInfo:),
363                                nil, nil, message);
364            return NSTerminateLater;
365        }
366    }
367
368    return NSTerminateNow;
369}
370
371- (void) quitSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode
372    contextInfo: (void *) contextInfo
373{
374    [NSApp stopModal];
375    [NSApp replyToApplicationShouldTerminate: returnCode == NSAlertDefaultReturn];
376}
377
378- (void) applicationWillTerminate: (NSNotification *) notification
379{
380    //stop timers
381    [fAutoImportTimer invalidate];
382    [fTimer invalidate];
383   
384    //save history and stop running torrents
385    [self updateTorrentHistory];
386    [fTorrents makeObjectsPerformSelector: @selector(stopTransferForQuit)];
387   
388    //remember window states and close all windows
389    [fDefaults setBool: [[fInfoController window] isVisible] forKey: @"InfoVisible"];
390    [[NSApp windows] makeObjectsPerformSelector: @selector(close)];
391    [self showStatusBar: NO animate: NO];
392    [self showFilterBar: NO animate: NO];
393   
394    //clear badge
395    [fBadger clearBadge];
396
397    //end quickly if the app is updating
398    if (fUpdateInProgress)
399        return;
400
401    //wait for running transfers to stop (5 second timeout)
402    NSDate * start = [NSDate date];
403    BOOL timeUp = NO;
404   
405    NSEnumerator * enumerator = [fTorrents objectEnumerator];
406    Torrent * torrent;
407    while (!timeUp && (torrent = [enumerator nextObject]))
408        while (![torrent isPaused] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
409        {
410            usleep(100000);
411            [torrent update];
412        }
413}
414
415- (void) folderChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code
416    contextInfo: (Torrent *) torrent
417{
418    if (code == NSOKButton)
419    {
420        [torrent setDownloadFolder: [[openPanel filenames] objectAtIndex: 0]];
421        [torrent update];
422        [self attemptToStartAuto: torrent];
423       
424        [fTorrents addObject: torrent];
425       
426        [self torrentNumberChanged];
427    }
428   
429    [NSApp stopModal];
430}
431
432- (void) application: (NSApplication *) sender openFiles: (NSArray *) filenames
433{
434    NSString * downloadChoice = [fDefaults stringForKey: @"DownloadChoice"], * torrentPath;
435    Torrent * torrent;
436    NSEnumerator * enumerator = [filenames objectEnumerator];
437    while ((torrentPath = [enumerator nextObject]))
438    {
439        if (!(torrent = [[Torrent alloc] initWithPath: torrentPath lib: fLib]))
440            continue;
441
442        //add it to the "File > Open Recent" menu
443        [[NSDocumentController sharedDocumentController]
444            noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
445
446        if ([downloadChoice isEqualToString: @"Ask"])
447        {
448            NSOpenPanel * panel = [NSOpenPanel openPanel];
449
450            [panel setPrompt: @"Select Download Folder"];
451            [panel setAllowsMultipleSelection: NO];
452            [panel setCanChooseFiles: NO];
453            [panel setCanChooseDirectories: YES];
454
455            [panel setMessage: [NSString stringWithFormat: @"Select the download folder for \"%@\"", [torrent name]]];
456
457            [panel beginSheetForDirectory: nil file: nil types: nil
458                modalForWindow: fWindow modalDelegate: self didEndSelector:
459                @selector( folderChoiceClosed:returnCode:contextInfo: )
460                contextInfo: torrent];
461            [NSApp runModalForWindow: panel];
462        }
463        else
464        {
465            NSString * folder = [downloadChoice isEqualToString: @"Constant"]
466                ? [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath]
467                : [torrentPath stringByDeletingLastPathComponent];
468
469            [torrent setDownloadFolder: folder];
470            [torrent update];
471            [self attemptToStartAuto: torrent];
472           
473            [fTorrents addObject: torrent];
474        }
475       
476        [torrent release];
477    }
478
479    [self torrentNumberChanged];
480
481    [self updateUI: nil];
482    [self applyFilter: nil];
483    [self updateTorrentHistory];
484}
485
486- (NSArray *) torrentsAtIndexes: (NSIndexSet *) indexSet
487{
488    if ([fFilteredTorrents respondsToSelector: @selector(objectsAtIndexes:)])
489        return [fFilteredTorrents objectsAtIndexes: indexSet];
490    else
491    {
492        NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [indexSet count]];
493        unsigned int i;
494        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
495            [torrents addObject: [fFilteredTorrents objectAtIndex: i]];
496
497        return torrents;
498    }
499}
500
501- (void) torrentNumberChanged
502{
503    int count = [fTorrents count];
504    [fTotalTorrentsField setStringValue: [NSString stringWithFormat:
505        @"%d Transfer%s", count, count == 1 ? "" : "s"]];
506}
507
508//called on by applescript
509- (void) open: (NSArray *) files
510{
511    [self performSelectorOnMainThread: @selector(openFromSheet:) withObject: files waitUntilDone: NO];
512}
513
514- (void) openShowSheet: (id) sender
515{
516    NSOpenPanel * panel = [NSOpenPanel openPanel];
517
518    [panel setAllowsMultipleSelection: YES];
519    [panel setCanChooseFiles: YES];
520    [panel setCanChooseDirectories: NO];
521
522    [panel beginSheetForDirectory: nil file: nil types: [NSArray arrayWithObject: @"torrent"]
523        modalForWindow: fWindow modalDelegate: self didEndSelector:
524        @selector(openSheetClosed:returnCode:contextInfo:) contextInfo: nil];
525}
526
527- (void) openSheetClosed: (NSOpenPanel *) panel returnCode: (int) code
528    contextInfo: (void *) info
529{
530    if (code == NSOKButton)
531        [self performSelectorOnMainThread: @selector(openFromSheet:)
532                    withObject: [panel filenames] waitUntilDone: NO];
533}
534
535- (void) openFromSheet: (NSArray *) filenames
536{
537    [self application: NSApp openFiles: filenames];
538}
539
540- (void) resumeSelectedTorrents: (id) sender
541{
542    [self resumeTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]];
543}
544
545- (void) resumeAllTorrents: (id) sender
546{
547    [self resumeTorrents: fTorrents];
548}
549
550- (void) resumeTorrents: (NSArray *) torrents;
551{
552    [torrents makeObjectsPerformSelector: @selector(startTransfer)];
553   
554    [self updateUI: nil];
555    [self applyFilter: nil];
556    [fInfoController updateInfoStatsAndSettings];
557    [self updateTorrentHistory];
558}
559
560- (void) stopSelectedTorrents: (id) sender
561{
562    [self stopTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]];
563}
564
565- (void) stopAllTorrents: (id) sender
566{
567    [self stopTorrents: fTorrents];
568}
569
570- (void) stopTorrents: (NSArray *) torrents;
571{
572    //don't want any of these starting then stopping
573    NSEnumerator * enumerator = [torrents objectEnumerator];
574    Torrent * torrent;
575    while ((torrent = [enumerator nextObject]))
576        [torrent setWaitToStart: NO];
577
578    [torrents makeObjectsPerformSelector: @selector(stopTransfer)];
579   
580    [self updateUI: nil];
581    [self applyFilter: nil];
582    [fInfoController updateInfoStatsAndSettings];
583    [self updateTorrentHistory];
584}
585
586- (void) removeTorrents: (NSArray *) torrents
587        deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
588{
589    [torrents retain];
590    int active = 0, downloading = 0;
591
592    if ([fDefaults boolForKey: @"CheckRemove"])
593    {
594        Torrent * torrent;
595        NSEnumerator * enumerator = [torrents objectEnumerator];
596        while ((torrent = [enumerator nextObject]))
597            if ([torrent isActive])
598            {
599                active++;
600                if (![torrent isSeeding])
601                    downloading++;
602            }
603
604        if ([fDefaults boolForKey: @"CheckRemoveDownloading"] ? downloading > 0 : active > 0)
605        {
606            NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
607                torrents, @"Torrents",
608                [NSNumber numberWithBool: deleteData], @"DeleteData",
609                [NSNumber numberWithBool: deleteTorrent], @"DeleteTorrent", nil];
610
611            NSString * title, * message;
612           
613            int selected = [fTableView numberOfSelectedRows];
614            if (selected == 1)
615            {
616                title = [NSString stringWithFormat: @"Comfirm Removal of \"%@\"",
617                            [[fFilteredTorrents objectAtIndex: [fTableView selectedRow]] name]];
618                message = @"This transfer is active."
619                            " Once removed, continuing the transfer will require the torrent file."
620                            " Do you really want to remove it?";
621            }
622            else
623            {
624                title = [NSString stringWithFormat: @"Comfirm Removal of %d Transfers", selected];
625                if (selected == active)
626                    message = [NSString stringWithFormat:
627                        @"There are %d active transfers.", active];
628                else
629                    message = [NSString stringWithFormat:
630                        @"There are %d transfers (%d active).", selected, active];
631                message = [message stringByAppendingString:
632                    @" Once removed, continuing the transfers will require the torrent files."
633                    " Do you really want to remove them?"];
634            }
635
636            NSBeginAlertSheet(title, @"Remove", @"Cancel", nil, fWindow, self,
637                @selector(removeSheetDidEnd:returnCode:contextInfo:), nil, dict, message);
638            return;
639        }
640    }
641   
642    [self confirmRemoveTorrents: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
643}
644
645- (void) removeSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (NSDictionary *) dict
646{
647    [NSApp stopModal];
648
649    NSArray * torrents = [dict objectForKey: @"Torrents"];
650    BOOL deleteData = [[dict objectForKey: @"DeleteData"] boolValue],
651        deleteTorrent = [[dict objectForKey: @"DeleteTorrent"] boolValue];
652    [dict release];
653   
654    if (returnCode == NSAlertDefaultReturn)
655        [self confirmRemoveTorrents: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
656    else
657        [torrents release];
658}
659
660- (void) confirmRemoveTorrents: (NSArray *) torrents deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
661{
662    //don't want any of these starting then stopping
663    NSEnumerator * enumerator = [torrents objectEnumerator];
664    Torrent * torrent;
665    while ((torrent = [enumerator nextObject]))
666        [torrent setWaitToStart: NO];
667
668    NSNumber * lowestOrderValue = [NSNumber numberWithInt: [torrents count]], * currentOrderValue;
669
670    enumerator = [torrents objectEnumerator];
671    while ((torrent = [enumerator nextObject]))
672    {
673        [torrent stopTransfer];
674
675        if (deleteData)
676            [torrent trashData];
677        if (deleteTorrent)
678            [torrent trashTorrent];
679       
680        //determine lowest order value
681        currentOrderValue = [torrent orderValue];
682        if ([lowestOrderValue compare: currentOrderValue] == NSOrderedDescending)
683            lowestOrderValue = currentOrderValue;
684
685        [torrent removeForever];
686       
687        [fFilteredTorrents removeObject: torrent];
688        [fTorrents removeObject: torrent];
689    }
690    [torrents release];
691
692    //reset the order values if necessary
693    if ([lowestOrderValue intValue] < [fTorrents count])
694    {
695        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
696                                                @"orderValue" ascending: YES] autorelease];
697        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
698
699        NSArray * tempTorrents = [fTorrents sortedArrayUsingDescriptors: descriptors];
700        [descriptors release];
701
702        int i;
703        for (i = [lowestOrderValue intValue]; i < [tempTorrents count]; i++)
704            [[tempTorrents objectAtIndex: i] setOrderValue: i];
705    }
706   
707    [self torrentNumberChanged];
708    [fTableView deselectAll: nil];
709    [self updateUI: nil];
710    [self updateTorrentHistory];
711}
712
713- (void) removeNoDelete: (id) sender
714{
715    [self removeTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]
716                deleteData: NO deleteTorrent: NO];
717}
718
719- (void) removeDeleteData: (id) sender
720{
721    [self removeTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]
722                deleteData: YES deleteTorrent: NO];
723}
724
725- (void) removeDeleteTorrent: (id) sender
726{
727    [self removeTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]
728                deleteData: NO deleteTorrent: YES];
729}
730
731- (void) removeDeleteDataAndTorrent: (id) sender
732{
733    [self removeTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]
734                deleteData: YES deleteTorrent: YES];
735}
736
737- (void) copyTorrentFile: (id) sender
738{
739    [self copyTorrentFileForTorrents: [[NSMutableArray alloc] initWithArray:
740            [self torrentsAtIndexes: [fTableView selectedRowIndexes]]]];
741}
742
743- (void) copyTorrentFileForTorrents: (NSMutableArray *) torrents
744{
745    if ([torrents count] == 0)
746    {
747        [torrents release];
748        return;
749    }
750
751    Torrent * torrent = [torrents objectAtIndex: 0];
752
753    //warn user if torrent file can't be found
754    if (![[NSFileManager defaultManager] fileExistsAtPath: [torrent torrentLocation]])
755    {
756        NSAlert * alert = [[NSAlert alloc] init];
757        [alert addButtonWithTitle: @"OK"];
758        [alert setMessageText: [NSString stringWithFormat:
759                @"Copy of \"%@\" Cannot Be Created", [torrent name]]];
760        [alert setInformativeText: [NSString stringWithFormat:
761                @"The torrent file (%@) cannot be found.", [torrent torrentLocation]]];
762        [alert setAlertStyle: NSWarningAlertStyle];
763       
764        [alert runModal];
765       
766        [torrents removeObjectAtIndex: 0];
767        [self copyTorrentFileForTorrents: torrents];
768    }
769    else
770    {
771        NSSavePanel * panel = [NSSavePanel savePanel];
772        [panel setRequiredFileType: @"torrent"];
773        [panel setCanSelectHiddenExtension: YES];
774       
775        [panel beginSheetForDirectory: nil file: [torrent name]
776            modalForWindow: fWindow modalDelegate: self didEndSelector:
777            @selector(saveTorrentCopySheetClosed:returnCode:contextInfo:) contextInfo: torrents];
778    }
779}
780
781- (void) saveTorrentCopySheetClosed: (NSSavePanel *) panel returnCode: (int) code
782    contextInfo: (NSMutableArray *) torrents
783{
784    //if save successful, copy torrent to new location with name of data file
785    if (code == NSOKButton)
786        [[NSFileManager defaultManager] copyPath: [[torrents objectAtIndex: 0] torrentLocation]
787                toPath: [panel filename] handler: nil];
788   
789    [torrents removeObjectAtIndex: 0];
790    [self performSelectorOnMainThread: @selector(copyTorrentFileForTorrents:)
791                withObject: torrents waitUntilDone: NO];
792}
793
794- (void) revealFile: (id) sender
795{
796    NSIndexSet * indexSet = [fTableView selectedRowIndexes];
797    unsigned int i;
798   
799    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
800        [[fFilteredTorrents objectAtIndex: i] revealData];
801}
802
803- (void) showPreferenceWindow: (id) sender
804{
805    NSWindow * window = [fPrefsController window];
806    if (![window isVisible])
807        [window center];
808
809    [window makeKeyAndOrderFront: nil];
810}
811
812- (void) showInfo: (id) sender
813{
814    if ([[fInfoController window] isVisible])
815        [fInfoController close];
816    else
817    {
818        [fInfoController updateInfoStats];
819        [[fInfoController window] orderFront: nil];
820    }
821}
822
823- (void) setInfoTab: (id) sender
824{
825    if (sender == fNextInfoTabItem)
826        [fInfoController setNextTab];
827    else
828        [fInfoController setPreviousTab];
829}
830
831- (void) updateControlTint: (NSNotification *) notification
832{
833    [fSpeedLimitButton setAlternateImage: [fDefaults integerForKey: @"AppleAquaColorVariant"] == NSBlueControlTint
834            ? [NSImage imageNamed: @"SpeedLimitButtonPressedBlue.png"]
835            : [NSImage imageNamed: @"SpeedLimitButtonPressedGraphite.png"]];
836}
837
838- (void) updateUI: (NSTimer *) t
839{
840    NSEnumerator * enumerator = [fTorrents objectEnumerator];
841    Torrent * torrent;
842    while ((torrent = [enumerator nextObject]))
843    {
844        [torrent update];
845
846        if ([torrent justFinished])
847        {
848            [self applyFilter: nil];
849            [self checkToStartWaiting: torrent];
850       
851            [GrowlApplicationBridge notifyWithTitle: @"Download Complete"
852                description: [torrent name] notificationName: @"Download Complete" iconData: nil
853                priority: 0 isSticky: NO clickContext: nil];
854
855            if (![fWindow isKeyWindow])
856                fCompleted++;
857        }
858    }
859
860    if ([fSortType isEqualToString: @"Progress"] || [fSortType isEqualToString: @"State"])
861        [self sortTorrents];
862    else
863        [fTableView reloadData];
864   
865    //update the global DL/UL rates
866    float downloadRate, uploadRate;
867    tr_torrentRates(fLib, & downloadRate, & uploadRate);
868    if (fStatusBarVisible)
869    {
870        [fTotalDLField setStringValue: [NSString stringForSpeed: downloadRate]];
871        [fTotalULField setStringValue: [NSString stringForSpeed: uploadRate]];
872    }
873
874    if ([[fInfoController window] isVisible])
875        [fInfoController updateInfoStats];
876
877    //badge dock
878    [fBadger updateBadgeWithCompleted: fCompleted
879        uploadRate: uploadRate downloadRate: downloadRate];
880}
881
882- (void) updateTorrentHistory
883{
884    NSMutableArray * history = [NSMutableArray arrayWithCapacity: [fTorrents count]];
885
886    NSEnumerator * enumerator = [fTorrents objectEnumerator];
887    Torrent * torrent;
888    while ((torrent = [enumerator nextObject]))
889        [history addObject: [torrent history]];
890
891    [fDefaults setObject: history forKey: @"History"];
892    [fDefaults synchronize];
893}
894
895- (void) sortTorrents
896{
897    //remember selected rows if needed
898    NSArray * selectedTorrents = nil;
899    int numSelected = [fTableView numberOfSelectedRows];
900    if (numSelected > 0 && numSelected < [fFilteredTorrents count])
901        selectedTorrents = [self torrentsAtIndexes: [fTableView selectedRowIndexes]];
902
903    [self sortTorrentsIgnoreSelected]; //actually sort
904   
905    //set selected rows if needed
906    if (selectedTorrents)
907    {
908        Torrent * torrent;
909        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
910        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
911        while ((torrent = [enumerator nextObject]))
912            [indexSet addIndex: [fFilteredTorrents indexOfObject: torrent]];
913       
914        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
915        [indexSet release];
916    }
917}
918
919//doesn't remember selected rows
920- (void) sortTorrentsIgnoreSelected
921{
922    NSSortDescriptor * nameDescriptor = [[[NSSortDescriptor alloc] initWithKey:
923                                            @"name" ascending: YES] autorelease],
924                    * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
925                                            @"orderValue" ascending: YES] autorelease];
926
927    NSArray * descriptors;
928    if ([fSortType isEqualToString: @"Name"])
929        descriptors = [[NSArray alloc] initWithObjects: nameDescriptor, orderDescriptor, nil];
930    else if ([fSortType isEqualToString: @"State"])
931    {
932        NSSortDescriptor * stateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
933                                                @"stateSortKey" ascending: NO] autorelease],
934                        * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
935                                            @"progressSortKey" ascending: NO] autorelease];
936       
937        descriptors = [[NSArray alloc] initWithObjects: stateDescriptor, progressDescriptor,
938                                                            nameDescriptor, orderDescriptor, nil];
939    }
940    else if ([fSortType isEqualToString: @"Progress"])
941    {
942        NSSortDescriptor * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
943                                            @"progressSortKey" ascending: YES] autorelease];
944       
945        descriptors = [[NSArray alloc] initWithObjects: progressDescriptor, nameDescriptor, orderDescriptor, nil];
946    }
947    else if ([fSortType isEqualToString: @"Date"])
948    {
949        NSSortDescriptor * dateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
950                                            @"date" ascending: YES] autorelease];
951   
952        descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, orderDescriptor, nil];
953    }
954    else
955        descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
956
957    [fFilteredTorrents sortUsingDescriptors: descriptors];
958    [descriptors release];
959   
960    [fTableView reloadData];
961}
962
963- (void) setSort: (id) sender
964{
965    NSMenuItem * prevSortItem;
966    if ([fSortType isEqualToString: @"Name"])
967        prevSortItem = fNameSortItem;
968    else if ([fSortType isEqualToString: @"State"])
969        prevSortItem = fStateSortItem;
970    else if ([fSortType isEqualToString: @"Progress"])
971        prevSortItem = fProgressSortItem;
972    else if ([fSortType isEqualToString: @"Date"])
973        prevSortItem = fDateSortItem;
974    else
975        prevSortItem = fOrderSortItem;
976   
977    if (sender != prevSortItem)
978    {
979        [prevSortItem setState: NSOffState];
980        [sender setState: NSOnState];
981
982        [fSortType release];
983        if (sender == fNameSortItem)
984            fSortType = [[NSString alloc] initWithString: @"Name"];
985        else if (sender == fStateSortItem)
986            fSortType = [[NSString alloc] initWithString: @"State"];
987        else if (sender == fProgressSortItem)
988            fSortType = [[NSString alloc] initWithString: @"Progress"];
989        else if (sender == fDateSortItem)
990            fSortType = [[NSString alloc] initWithString: @"Date"];
991        else
992            fSortType = [[NSString alloc] initWithString: @"Order"];
993           
994        [fDefaults setObject: fSortType forKey: @"Sort"];
995    }
996
997    [self sortTorrents];
998}
999
1000- (void) applyFilter: (id) sender
1001{
1002    //remember selected rows if needed
1003    NSArray * selectedTorrents = [fTableView numberOfSelectedRows] > 0
1004                ? [self torrentsAtIndexes: [fTableView selectedRowIndexes]] : nil;
1005
1006    NSMutableArray * tempTorrents = [[NSMutableArray alloc] initWithCapacity: [fTorrents count]];
1007
1008    if ([fFilterType isEqualToString: @"Pause"])
1009    {
1010        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1011        Torrent * torrent;
1012        while ((torrent = [enumerator nextObject]))
1013            if (![torrent isActive])
1014                [tempTorrents addObject: torrent];
1015    }
1016    else if ([fFilterType isEqualToString: @"Seed"])
1017    {
1018        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1019        Torrent * torrent;
1020        while ((torrent = [enumerator nextObject]))
1021            if ([torrent isActive] && [torrent progress] >= 1.0)
1022                [tempTorrents addObject: torrent];
1023    }
1024    else if ([fFilterType isEqualToString: @"Download"])
1025    {
1026        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1027        Torrent * torrent;
1028        while ((torrent = [enumerator nextObject]))
1029            if ([torrent isActive] && [torrent progress] < 1.0)
1030                [tempTorrents addObject: torrent];
1031    }
1032    else
1033        [tempTorrents setArray: fTorrents];
1034   
1035    NSString * searchString = [fSearchFilterField stringValue];
1036    if (![searchString isEqualToString: @""])
1037    {
1038        int i;
1039        for (i = [tempTorrents count] - 1; i >= 0; i--)
1040            if ([[[tempTorrents objectAtIndex: i] name] rangeOfString: searchString
1041                                        options: NSCaseInsensitiveSearch].location == NSNotFound)
1042                [tempTorrents removeObjectAtIndex: i];
1043    }
1044   
1045    [fFilteredTorrents setArray: tempTorrents];
1046    [tempTorrents release];
1047   
1048    [self sortTorrentsIgnoreSelected];
1049   
1050    //set selected rows if needed
1051    if (selectedTorrents)
1052    {
1053        Torrent * torrent;
1054        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1055        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1056        unsigned index;
1057        while ((torrent = [enumerator nextObject]))
1058            if ((index = [fFilteredTorrents indexOfObject: torrent]) != NSNotFound)
1059                [indexSet addIndex: index];
1060       
1061        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1062        [indexSet release];
1063    }
1064}
1065
1066//resets filter and sorts torrents
1067- (void) setFilter: (id) sender
1068{
1069    BarButton * prevFilterButton;
1070    if ([fFilterType isEqualToString: @"Pause"])
1071        prevFilterButton = fPauseFilterButton;
1072    else if ([fFilterType isEqualToString: @"Seed"])
1073        prevFilterButton = fSeedFilterButton;
1074    else if ([fFilterType isEqualToString: @"Download"])
1075        prevFilterButton = fDownloadFilterButton;
1076    else
1077        prevFilterButton = fNoFilterButton;
1078   
1079    if (sender != prevFilterButton)
1080    {
1081        [prevFilterButton setEnabled: NO];
1082        [sender setEnabled: YES];
1083
1084        [fFilterType release];
1085        if (sender == fDownloadFilterButton)
1086            fFilterType = [[NSString alloc] initWithString: @"Download"];
1087        else if (sender == fPauseFilterButton)
1088            fFilterType = [[NSString alloc] initWithString: @"Pause"];
1089        else if (sender == fSeedFilterButton)
1090            fFilterType = [[NSString alloc] initWithString: @"Seed"];
1091        else
1092            fFilterType = [[NSString alloc] initWithString: @"None"];
1093
1094        [fDefaults setObject: fFilterType forKey: @"Filter"];
1095    }
1096
1097    [self applyFilter: nil];
1098}
1099
1100- (void) toggleSpeedLimit: (id) sender
1101{
1102    int state = [fSpeedLimitItem state] ? NSOffState : NSOnState;
1103
1104    [fSpeedLimitItem setState: state];
1105    [fSpeedLimitDockItem setState: state];
1106    [fSpeedLimitButton setState: state];
1107   
1108    [fPrefsController enableSpeedLimit: state];
1109}
1110
1111- (void) setLimitGlobalEnabled: (id) sender
1112{
1113    [fPrefsController setLimitEnabled: (sender == fUploadLimitItem || sender == fDownloadLimitItem)
1114        type: (sender == fUploadLimitItem || sender == fUploadNoLimitItem) ? @"Upload" : @"Download"];
1115}
1116
1117- (void) setQuickLimitGlobal: (id) sender
1118{
1119    [fPrefsController setQuickLimit: [[sender title] intValue]
1120        type: [sender menu] == fUploadMenu ? @"Upload" : @"Download"];
1121}
1122
1123- (void) limitGlobalChange: (NSNotification *) notification
1124{
1125    NSDictionary * dict = [notification object];
1126   
1127    NSMenuItem * limitItem, * noLimitItem;
1128    if ([[dict objectForKey: @"Type"] isEqualToString: @"Upload"])
1129    {
1130        limitItem = fUploadLimitItem;
1131        noLimitItem = fUploadNoLimitItem;
1132    }
1133    else
1134    {
1135        limitItem = fDownloadLimitItem;
1136        noLimitItem = fDownloadNoLimitItem;
1137    }
1138   
1139    BOOL enable = [[dict objectForKey: @"Enable"] boolValue];
1140    [limitItem setState: enable ? NSOnState : NSOffState];
1141    [noLimitItem setState: !enable ? NSOnState : NSOffState];
1142   
1143    [limitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
1144                            [[dict objectForKey: @"Limit"] intValue]]];
1145
1146    [dict release];
1147}
1148
1149- (void) setRatioGlobalEnabled: (id) sender
1150{
1151    [fPrefsController setRatioEnabled: sender == fRatioSetItem];
1152}
1153
1154- (void) setQuickRatioGlobal: (id) sender
1155{
1156    [fPrefsController setQuickRatio: [[sender title] floatValue]];
1157}
1158
1159- (void) ratioGlobalChange: (NSNotification *) notification
1160{
1161    NSDictionary * dict = [notification object];
1162   
1163    BOOL enable = [[dict objectForKey: @"Enable"] boolValue];
1164    [fRatioSetItem setState: enable ? NSOnState : NSOffState];
1165    [fRatioNotSetItem setState: !enable ? NSOnState : NSOffState];
1166   
1167    [fRatioSetItem setTitle: [NSString stringWithFormat: @"Stop at Ratio (%.2f)",
1168                            [[dict objectForKey: @"Ratio"] floatValue]]];
1169
1170    [dict release];
1171}
1172
1173- (void) checkWaitingForStopped: (NSNotification *) notification
1174{
1175    [self checkToStartWaiting: [notification object]];
1176}
1177
1178- (void) checkToStartWaiting: (Torrent *) finishedTorrent
1179{
1180    //don't try to start a transfer if there should be none waiting
1181    if (![[fDefaults stringForKey: @"StartSetting"] isEqualToString: @"Wait"])
1182        return;
1183
1184    int desiredActive = [fDefaults integerForKey: @"WaitToStartNumber"];
1185   
1186    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1187    Torrent * torrent, * torrentToStart = nil;
1188    while ((torrent = [enumerator nextObject]))
1189    {
1190        //ignore the torrent just stopped
1191        if (torrent == finishedTorrent)
1192            continue;
1193   
1194        if ([torrent isActive])
1195        {
1196            if (![torrent isSeeding])
1197            {
1198                desiredActive--;
1199                if (desiredActive <= 0)
1200                    return;
1201            }
1202        }
1203        else
1204        {
1205            //use as next if it is waiting to start and either no previous or order value is lower
1206            if ([torrent waitingToStart] && (!torrentToStart
1207                || [[torrentToStart orderValue] compare: [torrent orderValue]] == NSOrderedDescending))
1208                torrentToStart = torrent;
1209        }
1210    }
1211   
1212    //since it hasn't returned, the queue amount has not been met
1213    if (torrentToStart)
1214    {
1215        [torrentToStart startTransfer];
1216       
1217        [self applyFilter: nil];
1218        [self updateUI: nil];
1219        [fInfoController updateInfoStatsAndSettings];
1220        [self updateTorrentHistory];
1221    }
1222}
1223
1224- (void) torrentStartSettingChange: (NSNotification *) notification
1225{
1226    [self attemptToStartMultipleAuto: [notification object]];
1227
1228    [self applyFilter: nil];
1229    [self updateUI: nil];
1230    [fInfoController updateInfoStatsAndSettings];
1231    [self updateTorrentHistory];
1232}
1233
1234- (void) globalStartSettingChange: (NSNotification *) notification
1235{
1236    [self attemptToStartMultipleAuto: fTorrents];
1237   
1238    [self applyFilter: nil];
1239    [self updateUI: nil];
1240    [fInfoController updateInfoStatsAndSettings];
1241    [self updateTorrentHistory];
1242}
1243
1244- (void) torrentStoppedForRatio: (NSNotification *) notification
1245{
1246    [self applyFilter: nil];
1247    [fInfoController updateInfoStatsAndSettings];
1248   
1249    [GrowlApplicationBridge notifyWithTitle: @"Seeding Complete"
1250                description: [[notification object] name] notificationName: @"Seeding Complete"
1251                iconData: nil priority: 0 isSticky: NO clickContext: nil];
1252}
1253
1254- (void) attemptToStartAuto: (Torrent *) torrent
1255{
1256    [self attemptToStartMultipleAuto: [NSArray arrayWithObject: torrent]];
1257}
1258
1259//will try to start, taking into consideration the start preference
1260- (void) attemptToStartMultipleAuto: (NSArray *) torrents
1261{
1262    NSString * startSetting = [fDefaults stringForKey: @"StartSetting"];
1263    if ([startSetting isEqualToString: @"Start"])
1264    {
1265        NSEnumerator * enumerator = [torrents objectEnumerator];
1266        Torrent * torrent;
1267        while ((torrent = [enumerator nextObject]))
1268            if ([torrent waitingToStart])
1269                [torrent startTransfer];
1270       
1271        return;
1272    }
1273    else if (![startSetting isEqualToString: @"Wait"])
1274        return;
1275    else;
1276   
1277    //determine the number of downloads needed to start
1278    int desiredActive = [fDefaults integerForKey: @"WaitToStartNumber"];
1279           
1280    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1281    Torrent * torrent;
1282    while ((torrent = [enumerator nextObject]))
1283        if ([torrent isActive] && ![torrent isSeeding])
1284        {
1285            desiredActive--;
1286            if (desiredActive <= 0)
1287                break;
1288        }
1289   
1290    //sort torrents by order value
1291    NSArray * sortedTorrents;
1292    if ([torrents count] > 1 && desiredActive > 0)
1293    {
1294        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1295                                                    @"orderValue" ascending: YES] autorelease];
1296        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1297       
1298        sortedTorrents = [torrents sortedArrayUsingDescriptors: descriptors];
1299        [descriptors release];
1300    }
1301    else
1302        sortedTorrents = torrents;
1303
1304    enumerator = [sortedTorrents objectEnumerator];
1305    while ((torrent = [enumerator nextObject]))
1306    {
1307        if ([torrent progress] >= 1.0)
1308            [torrent startTransfer];
1309        else
1310        {
1311            if ([torrent waitingToStart] && desiredActive > 0)
1312            {
1313                [torrent startTransfer];
1314                desiredActive--;
1315            }
1316        }
1317       
1318        [torrent update];
1319    }
1320}
1321
1322- (void) reloadInspectorSettings: (NSNotification *) notification
1323{
1324    [fInfoController updateInfoStatsAndSettings];
1325}
1326
1327- (void) checkAutoImportDirectory: (NSTimer *) timer
1328{
1329    if (![fDefaults boolForKey: @"AutoImport"])
1330        return;
1331       
1332    NSString * path = [[fDefaults stringForKey: @"AutoImportDirectory"] stringByExpandingTildeInPath];
1333   
1334    //if folder cannot be found or the contents hasn't changed simply give up
1335    NSArray * allFileNames;
1336    if (!(allFileNames = [[NSFileManager defaultManager] directoryContentsAtPath: path])
1337            || [allFileNames isEqualToArray: fAutoImportedNames])
1338        return;
1339
1340    //try to add files that haven't already been added
1341    NSMutableArray * newFileNames = [NSMutableArray arrayWithArray: allFileNames];
1342    [newFileNames removeObjectsInArray: fAutoImportedNames];
1343   
1344    //save the current list of files
1345    [fAutoImportedNames setArray: allFileNames];
1346   
1347    NSEnumerator * enumerator = [newFileNames objectEnumerator];
1348    NSString * file;
1349    unsigned oldCount;
1350    while ((file = [enumerator nextObject]))
1351        if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame)
1352        {
1353            oldCount = [fTorrents count];
1354            [self openFromSheet: [NSArray arrayWithObject: [path stringByAppendingPathComponent: file]]];
1355           
1356            //import only actually happened if the torrent array is larger
1357            if (oldCount < [fTorrents count])
1358                [GrowlApplicationBridge notifyWithTitle: @"Torrent File Auto Added"
1359                    description: file notificationName: @"Torrent Auto Added" iconData: nil
1360                    priority: 0 isSticky: NO clickContext: nil];
1361        }
1362}
1363
1364- (void) autoImportChange: (NSNotification *) notification
1365{
1366    [fAutoImportedNames removeAllObjects];
1367    [self checkAutoImportDirectory: nil];
1368}
1369
1370- (int) numberOfRowsInTableView: (NSTableView *) t
1371{
1372    return [fFilteredTorrents count];
1373}
1374
1375- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell
1376    forTableColumn: (NSTableColumn *) tableColumn row: (int) row
1377{
1378    [cell setTorrent: [fFilteredTorrents objectAtIndex: row]];
1379}
1380
1381- (BOOL) tableView: (NSTableView *) tableView writeRowsWithIndexes: (NSIndexSet *) indexes
1382    toPasteboard: (NSPasteboard *) pasteboard
1383{
1384    //only allow reordering of rows if sorting by order with no filter
1385    if ([fSortType isEqualToString: @"Order"] && [fFilterType isEqualToString: @"None"])
1386    {
1387        [pasteboard declareTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE] owner: self];
1388        [pasteboard setData: [NSKeyedArchiver archivedDataWithRootObject: indexes]
1389                                forType: TORRENT_TABLE_VIEW_DATA_TYPE];
1390        return YES;
1391    }
1392    return NO;
1393}
1394
1395- (NSDragOperation) tableView: (NSTableView *) t validateDrop: (id <NSDraggingInfo>) info
1396    proposedRow: (int) row proposedDropOperation: (NSTableViewDropOperation) operation
1397{
1398    NSPasteboard * pasteboard = [info draggingPasteboard];
1399    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
1400    {
1401        //check if any files to add have "torrent" as an extension
1402        NSEnumerator * enumerator = [[pasteboard propertyListForType: NSFilenamesPboardType] objectEnumerator];
1403        NSString * file;
1404        while ((file = [enumerator nextObject]))
1405            if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame)
1406            {
1407                [fTableView setDropRow: -1 dropOperation: NSTableViewDropOn];
1408                return NSDragOperationGeneric;
1409            }
1410    }
1411    else if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
1412    {
1413        [fTableView setDropRow: row dropOperation: NSTableViewDropAbove];
1414        return NSDragOperationGeneric;
1415    }
1416    else;
1417   
1418    return NSDragOperationNone;
1419}
1420
1421- (BOOL) tableView: (NSTableView *) t acceptDrop: (id <NSDraggingInfo>) info
1422    row: (int) newRow dropOperation: (NSTableViewDropOperation) operation
1423{
1424    NSPasteboard * pasteboard = [info draggingPasteboard];
1425    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
1426    {
1427        //create an array of files with the "torrent" extension
1428        NSMutableArray * filesToOpen = [[NSMutableArray alloc] init];
1429        NSEnumerator * enumerator = [[pasteboard propertyListForType: NSFilenamesPboardType] objectEnumerator];
1430        NSString * file;
1431        while ((file = [enumerator nextObject]))
1432            if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame)
1433                [filesToOpen addObject: file];
1434   
1435        [self application: NSApp openFiles: filesToOpen];
1436        [filesToOpen release];
1437    }
1438    else
1439    {
1440        //remember selected rows if needed
1441        NSArray * selectedTorrents = nil;
1442        int numSelected = [fTableView numberOfSelectedRows];
1443        if (numSelected > 0 && numSelected < [fFilteredTorrents count])
1444            selectedTorrents = [self torrentsAtIndexes: [fTableView selectedRowIndexes]];
1445   
1446        NSIndexSet * indexes = [NSKeyedUnarchiver unarchiveObjectWithData:
1447                                [pasteboard dataForType: TORRENT_TABLE_VIEW_DATA_TYPE]];
1448       
1449        //move torrent in array
1450        NSArray * movingTorrents = [[self torrentsAtIndexes: indexes] retain];
1451        [fFilteredTorrents removeObjectsInArray: movingTorrents];
1452       
1453        //determine the insertion index now that transfers to move have been removed
1454        int i, decrease = 0;
1455        for (i = [indexes firstIndex]; i < newRow && i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
1456            decrease++;
1457       
1458        //insert objects at new location
1459        for (i = 0; i < [movingTorrents count]; i++)
1460            [fFilteredTorrents insertObject: [movingTorrents objectAtIndex: i] atIndex: newRow - decrease + i];
1461       
1462        [movingTorrents release];
1463       
1464        //redo order values
1465        int low = [indexes firstIndex], high = [indexes lastIndex];
1466        if (newRow < low)
1467            low = newRow;
1468        else if (newRow > high + 1)
1469            high = newRow - 1;
1470        else;
1471       
1472        for (i = low; i <= high; i++)
1473            [[fFilteredTorrents objectAtIndex: i] setOrderValue: i];
1474       
1475        [fTableView reloadData];
1476       
1477        //set selected rows if needed
1478        if (selectedTorrents)
1479        {
1480            Torrent * torrent;
1481            NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1482            NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1483            while ((torrent = [enumerator nextObject]))
1484                [indexSet addIndex: [fFilteredTorrents indexOfObject: torrent]];
1485           
1486            [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1487            [indexSet release];
1488        }
1489    }
1490   
1491    return YES;
1492}
1493
1494- (void) tableViewSelectionDidChange: (NSNotification *) notification
1495{
1496    [fInfoController updateInfoForTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]];
1497}
1498
1499- (void) toggleSmallView: (id) sender
1500{
1501    BOOL makeSmall = ![fDefaults boolForKey: @"SmallView"];
1502   
1503    [fTableView setRowHeight: makeSmall ? ROW_HEIGHT_SMALL : ROW_HEIGHT_REGULAR];
1504    [fSmallViewItem setState: makeSmall];
1505   
1506    [fDefaults setBool: makeSmall forKey: @"SmallView"];
1507   
1508    //window min height
1509    NSSize contentMinSize = [fWindow contentMinSize],
1510            contentSize = [[fWindow contentView] frame].size;
1511    contentMinSize.height = contentSize.height - [fScrollView frame].size.height
1512                            + [fTableView rowHeight] + [fTableView intercellSpacing].height;
1513    [fWindow setContentMinSize: contentMinSize];
1514   
1515    //resize for larger min height
1516    if (!makeSmall && contentSize.height < contentMinSize.height)
1517    {
1518        NSRect frame = [fWindow frame];
1519        float heightChange = contentMinSize.height - contentSize.height;
1520        frame.size.height += heightChange;
1521        frame.origin.y -= heightChange;
1522       
1523        [fWindow setFrame: frame display: YES];
1524        [fTableView reloadData];
1525    }
1526}
1527
1528- (void) toggleStatusBar: (id) sender
1529{
1530    [self showStatusBar: !fStatusBarVisible animate: YES];
1531    [fDefaults setBool: fStatusBarVisible forKey: @"StatusBar"];
1532}
1533
1534- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
1535{
1536    if (show == fStatusBarVisible)
1537        return;
1538
1539    NSRect frame = [fWindow frame];
1540    float heightChange = [fStatusBar frame].size.height;
1541    if (!show)
1542        heightChange *= -1;
1543
1544    frame.size.height += heightChange;
1545    frame.origin.y -= heightChange;
1546       
1547    fStatusBarVisible = !fStatusBarVisible;
1548   
1549    [self updateUI: nil];
1550   
1551    //set views to not autoresize
1552    unsigned int statsMask = [fStatusBar autoresizingMask];
1553    unsigned int filterMask = [fFilterBar autoresizingMask];
1554    unsigned int scrollMask = [fScrollView autoresizingMask];
1555    [fStatusBar setAutoresizingMask: 0];
1556    [fFilterBar setAutoresizingMask: 0];
1557    [fScrollView setAutoresizingMask: 0];
1558   
1559    [fWindow setFrame: frame display: YES animate: animate];
1560   
1561    //re-enable autoresize
1562    [fStatusBar setAutoresizingMask: statsMask];
1563    [fFilterBar setAutoresizingMask: filterMask];
1564    [fScrollView setAutoresizingMask: scrollMask];
1565   
1566    //change min size
1567    NSSize minSize = [fWindow contentMinSize];
1568    minSize.height += heightChange;
1569    [fWindow setContentMinSize: minSize];
1570   
1571    //reset tracking rects for filter buttons
1572    [fNoFilterButton resetBounds: nil];
1573    [fSeedFilterButton resetBounds: nil];
1574    [fDownloadFilterButton resetBounds: nil];
1575    [fPauseFilterButton resetBounds: nil];
1576}
1577
1578- (void) toggleFilterBar: (id) sender
1579{
1580    //first make search filter lose focus
1581    if (fFilterBarVisible)
1582        [fWindow makeFirstResponder: fTableView];
1583
1584    [self showFilterBar: !fFilterBarVisible animate: YES];
1585    [fDefaults setBool: fFilterBarVisible forKey: @"FilterBar"];
1586   
1587    //disable filtering when hiding
1588    if (!fFilterBarVisible)
1589    {
1590        [fSearchFilterField setStringValue: @""];
1591        [self setFilter: fNoFilterButton];
1592    }
1593}
1594
1595- (void) showFilterBar: (BOOL) show animate: (BOOL) animate
1596{
1597    if (show == fFilterBarVisible)
1598        return;
1599
1600    NSRect frame = [fWindow frame];
1601    float heightChange = [fFilterBar frame].size.height;
1602    if (!show)
1603        heightChange *= -1;
1604
1605    frame.size.height += heightChange;
1606    frame.origin.y -= heightChange;
1607       
1608    fFilterBarVisible = !fFilterBarVisible;
1609   
1610    //set views to not autoresize
1611    unsigned int filterMask = [fFilterBar autoresizingMask];
1612    unsigned int scrollMask = [fScrollView autoresizingMask];
1613    [fFilterBar setAutoresizingMask: 0];
1614    [fScrollView setAutoresizingMask: 0];
1615   
1616    [fWindow setFrame: frame display: YES animate: animate];
1617   
1618    //re-enable autoresize
1619    [fFilterBar setAutoresizingMask: filterMask];
1620    [fScrollView setAutoresizingMask: scrollMask];
1621   
1622    //change min size
1623    NSSize minSize = [fWindow contentMinSize];
1624    minSize.height += heightChange;
1625    [fWindow setContentMinSize: minSize];
1626   
1627    //reset tracking rects for filter buttons
1628    [fNoFilterButton resetBounds: nil];
1629    [fSeedFilterButton resetBounds: nil];
1630    [fDownloadFilterButton resetBounds: nil];
1631    [fPauseFilterButton resetBounds: nil];
1632}
1633
1634- (void) toggleAdvancedBar: (id) sender
1635{
1636    int state = ![fAdvancedBarItem state];
1637    [fAdvancedBarItem setState: state];
1638    [fDefaults setBool: state forKey: @"UseAdvancedBar"];
1639
1640    [fTableView display];
1641}
1642
1643- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
1644    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
1645{
1646    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
1647
1648    if ([ident isEqualToString: TOOLBAR_OPEN])
1649    {
1650        [item setLabel: @"Open"];
1651        [item setPaletteLabel: @"Open Torrent Files"];
1652        [item setToolTip: @"Open torrent files"];
1653        [item setImage: [NSImage imageNamed: @"Open.png"]];
1654        [item setTarget: self];
1655        [item setAction: @selector(openShowSheet:)];
1656    }
1657    else if ([ident isEqualToString: TOOLBAR_REMOVE])
1658    {
1659        [item setLabel: @"Remove"];
1660        [item setPaletteLabel: @"Remove Selected"];
1661        [item setToolTip: @"Remove selected transfers"];
1662        [item setImage: [NSImage imageNamed: @"Remove.png"]];
1663        [item setTarget: self];
1664        [item setAction: @selector(removeNoDelete:)];
1665    }
1666    else if ([ident isEqualToString: TOOLBAR_INFO])
1667    {
1668        [item setLabel: @"Inspector"];
1669        [item setPaletteLabel: @"Show/Hide Inspector"];
1670        [item setToolTip: @"Display torrent inspector"];
1671        [item setImage: [NSImage imageNamed: @"Info.png"]];
1672        [item setTarget: self];
1673        [item setAction: @selector(showInfo:)];
1674    }
1675    else if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
1676    {
1677        [item setLabel: @"Pause All"];
1678        [item setPaletteLabel: [item label]];
1679        [item setToolTip: @"Pause all transfers"];
1680        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
1681        [item setTarget: self];
1682        [item setAction: @selector(stopAllTorrents:)];
1683    }
1684    else if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
1685    {
1686        [item setLabel: @"Resume All"];
1687        [item setPaletteLabel: [item label]];
1688        [item setToolTip: @"Resume all transfers"];
1689        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
1690        [item setTarget: self];
1691        [item setAction: @selector(resumeAllTorrents:)];
1692    }
1693    else if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
1694    {
1695        [item setLabel: @"Pause"];
1696        [item setPaletteLabel: @"Pause Selected"];
1697        [item setToolTip: @"Pause selected transfers"];
1698        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
1699        [item setTarget: self];
1700        [item setAction: @selector(stopSelectedTorrents:)];
1701    }
1702    else if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
1703    {
1704        [item setLabel: @"Resume"];
1705        [item setPaletteLabel: @"Resume Selected"];
1706        [item setToolTip: @"Resume selected transfers"];
1707        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
1708        [item setTarget: self];
1709        [item setAction: @selector(resumeSelectedTorrents:)];
1710    }
1711    else
1712    {
1713        [item release];
1714        return nil;
1715    }
1716
1717    return item;
1718}
1719
1720- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
1721{
1722    return [NSArray arrayWithObjects:
1723            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1724            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
1725            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL, TOOLBAR_INFO,
1726            NSToolbarSeparatorItemIdentifier,
1727            NSToolbarSpaceItemIdentifier,
1728            NSToolbarFlexibleSpaceItemIdentifier,
1729            NSToolbarCustomizeToolbarItemIdentifier, nil];
1730}
1731
1732- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
1733{
1734    return [NSArray arrayWithObjects:
1735            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1736            NSToolbarSeparatorItemIdentifier,
1737            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
1738            NSToolbarFlexibleSpaceItemIdentifier,
1739            TOOLBAR_INFO, nil];
1740}
1741
1742- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
1743{
1744    NSString * ident = [toolbarItem itemIdentifier];
1745
1746    //enable remove item
1747    if ([ident isEqualToString: TOOLBAR_REMOVE])
1748        return [fTableView numberOfSelectedRows] > 0;
1749
1750    //enable pause all item
1751    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
1752    {
1753        Torrent * torrent;
1754        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1755        while ((torrent = [enumerator nextObject]))
1756            if ([torrent isActive])
1757                return YES;
1758        return NO;
1759    }
1760
1761    //enable resume all item
1762    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
1763    {
1764        Torrent * torrent;
1765        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1766        while ((torrent = [enumerator nextObject]))
1767            if ([torrent isPaused])
1768                return YES;
1769        return NO;
1770    }
1771
1772    //enable pause item
1773    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
1774    {
1775        Torrent * torrent;
1776        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1777        unsigned int i;
1778       
1779        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1780            if ([[fFilteredTorrents objectAtIndex: i] isActive])
1781                return YES;
1782        return NO;
1783    }
1784   
1785    //enable resume item
1786    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
1787    {
1788        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1789        unsigned int i;
1790       
1791        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1792            if ([[fFilteredTorrents objectAtIndex: i] isPaused])
1793                return YES;
1794        return NO;
1795    }
1796
1797    return YES;
1798}
1799
1800- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
1801{
1802    SEL action = [menuItem action];
1803
1804    //only enable some items if it is in a context menu or the window is useable
1805    BOOL canUseMenu = [fWindow isKeyWindow] || [[[menuItem menu] title] isEqualToString: @"Context"];
1806
1807    //enable show info
1808    if (action == @selector(showInfo:))
1809    {
1810        NSString * title = [[fInfoController window] isVisible] ? @"Hide Inspector" : @"Show Inspector";
1811        if (![[menuItem title] isEqualToString: title])
1812                [menuItem setTitle: title];
1813
1814        return YES;
1815    }
1816   
1817    //enable prev/next inspector tab
1818    if (action == @selector(setInfoTab:))
1819        return [[fInfoController window] isVisible];
1820   
1821    //enable toggle status bar
1822    if (action == @selector(toggleStatusBar:))
1823    {
1824        NSString * title = fStatusBarVisible ? @"Hide Status Bar" : @"Show Status Bar";
1825        if (![[menuItem title] isEqualToString: title])
1826                [menuItem setTitle: title];
1827
1828        return canUseMenu;
1829    }
1830   
1831    //enable toggle filter bar
1832    if (action == @selector(toggleFilterBar:))
1833    {
1834        NSString * title = fFilterBarVisible ? @"Hide Filter Bar" : @"Show Filter Bar";
1835        if (![[menuItem title] isEqualToString: title])
1836                [menuItem setTitle: title];
1837
1838        return canUseMenu;
1839    }
1840
1841    //enable resume all item
1842    if (action == @selector(resumeAllTorrents:))
1843    {
1844        Torrent * torrent;
1845        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1846        while ((torrent = [enumerator nextObject]))
1847            if ([torrent isPaused])
1848                return YES;
1849        return NO;
1850    }
1851
1852    //enable pause all item
1853    if (action == @selector(stopAllTorrents:))
1854    {
1855        Torrent * torrent;
1856        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1857        while ((torrent = [enumerator nextObject]))
1858            if ([torrent isActive])
1859                return YES;
1860        return NO;
1861    }
1862
1863    //enable reveal in finder
1864    if (action == @selector(revealFile:))
1865        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1866
1867    //enable remove items
1868    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
1869        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteDataAndTorrent:))
1870    {
1871        BOOL warning = NO,
1872            onlyDownloading = [fDefaults boolForKey: @"CheckRemoveDownloading"],
1873            canDelete = action != @selector(removeDeleteTorrent:) && action != @selector(removeDeleteDataAndTorrent:);
1874        Torrent * torrent;
1875        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1876        unsigned int i;
1877       
1878        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1879        {
1880            torrent = [fFilteredTorrents objectAtIndex: i];
1881            if (!warning && [torrent isActive])
1882            {
1883                warning = onlyDownloading ? ![torrent isSeeding] : YES;
1884                if (warning && canDelete)
1885                    break;
1886            }
1887            if (!canDelete && [torrent publicTorrent])
1888            {
1889                canDelete = YES;
1890                if (warning)
1891                    break;
1892            }
1893        }
1894   
1895        //append or remove ellipsis when needed
1896        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
1897        if (warning && [fDefaults boolForKey: @"CheckRemove"])
1898        {
1899            if (![title hasSuffix: ellipsis])
1900                [menuItem setTitle: [title stringByAppendingEllipsis]];
1901        }
1902        else
1903        {
1904            if ([title hasSuffix: ellipsis])
1905                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
1906        }
1907       
1908        return canUseMenu && canDelete && [fTableView numberOfSelectedRows] > 0;
1909    }
1910
1911    //enable pause item
1912    if (action == @selector(stopSelectedTorrents:))
1913    {
1914        if (!canUseMenu)
1915            return NO;
1916   
1917        Torrent * torrent;
1918        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1919        unsigned int i;
1920       
1921        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1922        {
1923            torrent = [fFilteredTorrents objectAtIndex: i];
1924            if ([torrent isActive])
1925                return YES;
1926        }
1927        return NO;
1928    }
1929   
1930    //enable resume item
1931    if (action == @selector(resumeSelectedTorrents:))
1932    {
1933        if (!canUseMenu)
1934            return NO;
1935   
1936        Torrent * torrent;
1937        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1938        unsigned int i;
1939       
1940        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1941        {
1942            torrent = [fFilteredTorrents objectAtIndex: i];
1943            if ([torrent isPaused])
1944                return YES;
1945        }
1946        return NO;
1947    }
1948   
1949    //enable sort and advanced bar items
1950    if (action == @selector(setSort:) || action == @selector(toggleAdvancedBar:)
1951            || action == @selector(toggleSmallView:))
1952        return canUseMenu;
1953   
1954    //enable copy torrent file item
1955    if (action == @selector(copyTorrentFile:))
1956    {
1957        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1958    }
1959
1960    return YES;
1961}
1962
1963- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument
1964{
1965    NSEnumerator * enumerator;
1966    Torrent * torrent;
1967    BOOL active;
1968
1969    switch (messageType)
1970    {
1971        case kIOMessageSystemWillSleep:
1972            //close all connections before going to sleep and remember we should resume when we wake up
1973            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
1974
1975            //wait for running transfers to stop (5 second timeout)
1976            NSDate * start = [NSDate date];
1977            BOOL timeUp = NO;
1978           
1979            NSEnumerator * enumerator = [fTorrents objectEnumerator];
1980            Torrent * torrent;
1981            while (!timeUp && (torrent = [enumerator nextObject]))
1982                while (![torrent isPaused] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
1983                {
1984                    usleep(100000);
1985                    [torrent update];
1986                }
1987
1988            IOAllowPowerChange(fRootPort, (long) messageArgument);
1989            break;
1990
1991        case kIOMessageCanSystemSleep:
1992            //pevent idle sleep unless all paused
1993            active = NO;
1994            enumerator = [fTorrents objectEnumerator];
1995            while ((torrent = [enumerator nextObject]))
1996                if ([torrent isActive])
1997                {
1998                    active = YES;
1999                    break;
2000                }
2001
2002            if (active)
2003                IOCancelPowerChange(fRootPort, (long) messageArgument);
2004            else
2005                IOAllowPowerChange(fRootPort, (long) messageArgument);
2006            break;
2007
2008        case kIOMessageSystemHasPoweredOn:
2009            //resume sleeping transfers after we wake up
2010            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
2011            break;
2012    }
2013}
2014
2015- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
2016{
2017    NSRect windowRect = [fWindow frame];
2018    float newHeight = windowRect.size.height - [fScrollView frame].size.height
2019        + [fFilteredTorrents count] * ([fTableView rowHeight] + [fTableView intercellSpacing].height);
2020
2021    float minHeight = [fWindow minSize].height;
2022    if (newHeight < minHeight)
2023        newHeight = minHeight;
2024
2025    windowRect.origin.y -= (newHeight - windowRect.size.height);
2026    windowRect.size.height = newHeight;
2027
2028    return windowRect;
2029}
2030
2031- (void) showMainWindow: (id) sender
2032{
2033    [fWindow makeKeyAndOrderFront: nil];
2034}
2035
2036- (void) windowDidBecomeKey: (NSNotification *) notification
2037{
2038    fCompleted = 0;
2039}
2040
2041- (void) windowDidResize: (NSNotification *) notification
2042{
2043    //hide search filter if it overlaps filter buttons
2044    NSRect buttonFrame = [fPauseFilterButton frame];
2045    BOOL overlap = buttonFrame.origin.x + buttonFrame.size.width > [fSearchFilterField frame].origin.x;
2046   
2047    if (![fSearchFilterField isHidden])
2048    {
2049        if (overlap)
2050            [fSearchFilterField setHidden: YES];
2051    }
2052    else
2053    {
2054        if (!overlap)
2055            [fSearchFilterField setHidden: NO];
2056    }
2057}
2058
2059- (void) linkHomepage: (id) sender
2060{
2061    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
2062}
2063
2064- (void) linkForums: (id) sender
2065{
2066    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
2067}
2068
2069- (void) checkUpdate: (id) sender
2070{
2071    [fPrefsController checkUpdate];
2072}
2073
2074- (void) prepareForUpdate: (NSNotification *) notification
2075{
2076    fUpdateInProgress = YES;
2077}
2078
2079- (NSDictionary *) registrationDictionaryForGrowl
2080{
2081    NSArray * notifications = [NSArray arrayWithObjects: @"Download Complete",
2082                                @"Seeding Complete", @"Torrent Auto Added", nil];
2083    return [NSDictionary dictionaryWithObjectsAndKeys: notifications, GROWL_NOTIFICATIONS_ALL,
2084                                notifications, GROWL_NOTIFICATIONS_DEFAULT, nil];
2085}
2086
2087@end
Note: See TracBrowser for help on using the repository browser.