source: trunk/macosx/Controller.m @ 664

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

Zoom now works horizontally. This also gets around the problems with zoom when auto resizing.

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