source: trunk/macosx/Controller.m @ 669

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

Turn off filter before hiding the bar, not the other way around. This should make it more obvious that the filter is turned off.

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