source: trunk/macosx/Controller.m @ 653

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

Warning about growl delegate is gone.

  • Property svn:keywords set to Date Rev Author Id
File size: 75.5 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 653 2006-07-22 15:10:16Z 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    #warning remove eventually
106    NSImage * image = [NSImage imageNamed: @"StatusBorder.png"];
107    [image setName: nil];
108    [fStatusBar setBackgroundImage: [NSImage imageNamed: @"StatusBorder.png"]];
109    [fFilterBar setBackgroundImage: [NSImage imageNamed: @"FilterBarBackground.png"]];
110   
111    [fWindow setAcceptsMouseMovedEvents: YES]; //ensure filter buttons display correctly
112   
113    [fAdvancedBarItem setState: [fDefaults boolForKey: @"UseAdvancedBar"]];
114
115    fToolbar = [[NSToolbar alloc] initWithIdentifier: @"Transmission Toolbar"];
116    [fToolbar setDelegate: self];
117    [fToolbar setAllowsUserCustomization: YES];
118    [fToolbar setAutosavesConfiguration: YES];
119    [fWindow setToolbar: fToolbar];
120    [fWindow setDelegate: self];
121   
122    [fWindow makeFirstResponder: fTableView];
123   
124    //set table size
125    if ([fDefaults boolForKey: @"SmallView"])
126    {
127        [fTableView setRowHeight: ROW_HEIGHT_SMALL];
128        [fSmallViewItem setState: NSOnState];
129    }
130   
131    //window min height
132    NSSize contentMinSize = [fWindow contentMinSize];
133    contentMinSize.height = [[fWindow contentView] frame].size.height - [fScrollView frame].size.height
134                                + [fTableView rowHeight] + [fTableView intercellSpacing].height;
135    [fWindow setContentMinSize: contentMinSize];
136   
137    //set info keyboard shortcuts
138    unichar ch = NSRightArrowFunctionKey;
139    [fNextInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & ch length: 1]];
140    ch = NSLeftArrowFunctionKey;
141    [fPrevInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & ch length: 1]];
142   
143    //set up filter bar
144    NSView * contentView = [fWindow contentView];
145    [fFilterBar setHidden: YES];
146   
147    fFilterBarVisible = NO;
148    NSRect filterBarFrame = [fFilterBar frame];
149    filterBarFrame.size.width = [fWindow frame].size.width;
150    [fFilterBar setFrame: filterBarFrame];
151   
152    [contentView addSubview: fFilterBar];
153    [fFilterBar setFrameOrigin: NSMakePoint(0, [contentView frame].origin.y + [contentView frame].size.height)];
154   
155    [self showFilterBar: [fDefaults boolForKey: @"FilterBar"] animate: NO];
156   
157    //set up status bar
158    fStatusBarVisible = NO;
159    [fStatusBar setHidden: YES];
160   
161    NSRect statusBarFrame = [fStatusBar frame];
162    statusBarFrame.size.width = [fWindow frame].size.width;
163    [fStatusBar setFrame: statusBarFrame];
164   
165    [contentView addSubview: fStatusBar];
166    [fStatusBar setFrameOrigin: NSMakePoint(0, [contentView frame].origin.y + [contentView frame].size.height)];
167    [self showStatusBar: [fDefaults boolForKey: @"StatusBar"] animate: NO];
168   
169    //set speed limit
170    fSpeedLimitNormalImage = [fSpeedLimitButton image];
171    fSpeedLimitBlueImage = [NSImage imageNamed: @"SpeedLimitButtonBlue.png"];
172    fSpeedLimitGraphiteImage = [NSImage imageNamed: @"SpeedLimitButtonGraphite.png"];
173   
174    [self updateControlTint: nil];
175   
176    if ((fSpeedLimitEnabled = [fDefaults boolForKey: @"SpeedLimit"]))
177    {
178        [fSpeedLimitItem setState: NSOnState];
179        [fSpeedLimitDockItem setState: NSOnState];
180       
181        [fSpeedLimitButton setImage: [NSColor currentControlTint] == NSBlueControlTint
182                                        ? fSpeedLimitBlueImage : fSpeedLimitGraphiteImage];
183    }
184
185    [fActionButton setToolTip: @"Shortcuts for changing global settings."];
186    [fSpeedLimitButton setToolTip: @"Speed Limit overrides the total bandwidth limits with its own limits."];
187
188    [fTableView setTorrents: fFilteredTorrents];
189    [[fTableView tableColumnWithIdentifier: @"Torrent"] setDataCell: [[TorrentCell alloc] init]];
190
191    [fTableView registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType,
192                                                        TORRENT_TABLE_VIEW_DATA_TYPE, nil]];
193
194    //register for sleep notifications
195    IONotificationPortRef notify;
196    io_object_t iterator;
197    if (fRootPort = IORegisterForSystemPower(self, & notify, sleepCallBack, & iterator))
198        CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify),
199                            kCFRunLoopCommonModes);
200    else
201        NSLog(@"Could not IORegisterForSystemPower");
202
203    //load torrents from history
204    Torrent * torrent;
205    NSDictionary * historyItem;
206    NSEnumerator * enumerator = [[fDefaults arrayForKey: @"History"] objectEnumerator];
207    while ((historyItem = [enumerator nextObject]))
208        if ((torrent = [[Torrent alloc] initWithHistory: historyItem lib: fLib]))
209        {
210            [fTorrents addObject: torrent];
211            [torrent release];
212        }
213   
214    [self torrentNumberChanged];
215   
216    //set sort
217    fSortType = [[fDefaults stringForKey: @"Sort"] retain];
218   
219    NSMenuItem * currentSortItem, * currentSortActionItem;
220    if ([fSortType isEqualToString: @"Name"])
221    {
222        currentSortItem = fNameSortItem;
223        currentSortActionItem = fNameSortActionItem;
224    }
225    else if ([fSortType isEqualToString: @"State"])
226    {
227        currentSortItem = fStateSortItem;
228        currentSortActionItem = fStateSortActionItem;
229    }
230    else if ([fSortType isEqualToString: @"Progress"])
231    {
232        currentSortItem = fProgressSortItem;
233        currentSortActionItem = fProgressSortActionItem;
234    }
235    else if ([fSortType isEqualToString: @"Date"])
236    {
237        currentSortItem = fDateSortItem;
238        currentSortActionItem = fDateSortActionItem;
239    }
240    else
241    {
242        currentSortItem = fOrderSortItem;
243        currentSortActionItem = fOrderSortActionItem;
244    }
245    [currentSortItem setState: NSOnState];
246    [currentSortActionItem setState: NSOnState];
247   
248    //set filter
249    fFilterType = [[fDefaults stringForKey: @"Filter"] retain];
250
251    //button width should be 9 pixels surrounding text length
252    [fNoFilterButton setText: @"All"]; //16.64
253    [fDownloadFilterButton setText: @"Downloading"]; //81.69
254    [fSeedFilterButton setText: @"Seeding"]; //48.57
255    [fPauseFilterButton setText: @"Paused"]; //44.06
256
257    BarButton * currentFilterButton;
258    if ([fFilterType isEqualToString: @"Pause"])
259        currentFilterButton = fPauseFilterButton;
260    else if ([fFilterType isEqualToString: @"Seed"])
261        currentFilterButton = fSeedFilterButton;
262    else if ([fFilterType isEqualToString: @"Download"])
263        currentFilterButton = fDownloadFilterButton;
264    else
265        currentFilterButton = fNoFilterButton;
266
267    [currentFilterButton setEnabled: YES];
268   
269    //set upload limit action button
270    [fUploadLimitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
271                    [fDefaults integerForKey: @"UploadLimit"]]];
272    if ([fDefaults boolForKey: @"CheckUpload"])
273        [fUploadLimitItem setState: NSOnState];
274    else
275        [fUploadNoLimitItem setState: NSOnState];
276
277        //set download limit action menu
278    [fDownloadLimitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
279                    [fDefaults integerForKey: @"DownloadLimit"]]];
280    if ([fDefaults boolForKey: @"CheckDownload"])
281        [fDownloadLimitItem setState: NSOnState];
282    else
283        [fDownloadNoLimitItem setState: NSOnState];
284   
285    //set ratio action menu
286    [fRatioSetItem setTitle: [NSString stringWithFormat: @"Stop at Ratio (%.2f)",
287                                [fDefaults floatForKey: @"RatioLimit"]]];
288    if ([fDefaults boolForKey: @"RatioCheck"])
289        [fRatioSetItem setState: NSOnState];
290    else
291        [fRatioNotSetItem setState: NSOnState];
292   
293    //observe notifications
294    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
295   
296    [nc addObserver: self selector: @selector(updateControlTint:)
297                    name: NSControlTintDidChangeNotification object: nil];
298   
299    [nc addObserver: self selector: @selector(prepareForUpdate:)
300                    name: SUUpdaterWillRestartNotification object: nil];
301    fUpdateInProgress = NO;
302   
303    [nc addObserver: self selector: @selector(limitGlobalChange:)
304                    name: @"LimitGlobalChange" object: nil];
305   
306    [nc addObserver: self selector: @selector(ratioGlobalChange:)
307                    name: @"RatioGlobalChange" object: nil];
308   
309    //check to start another because of stopped torrent
310    [nc addObserver: self selector: @selector(checkWaitingForStopped:)
311                    name: @"StoppedDownloading" object: nil];
312   
313    //check all torrents for starting
314    [nc addObserver: self selector: @selector(globalStartSettingChange:)
315                    name: @"GlobalStartSettingChange" object: nil];
316
317    //check if torrent should now start
318    [nc addObserver: self selector: @selector(torrentStartSettingChange:)
319                    name: @"TorrentStartSettingChange" object: nil];
320   
321    //check if torrent should now start
322    [nc addObserver: self selector: @selector(torrentStoppedForRatio:)
323                    name: @"TorrentStoppedForRatio" object: nil];
324   
325    //change that just impacts the inspector
326    [nc addObserver: self selector: @selector(reloadInspectorSettings:)
327                    name: @"TorrentSettingChange" object: nil];
328   
329    //reset auto import
330    [nc addObserver: self selector: @selector(autoImportChange:)
331                    name: @"AutoImportSettingChange" object: nil];
332
333    //timer to update the interface every second
334    fCompleted = 0;
335    [self updateUI: nil];
336    fTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self
337        selector: @selector(updateUI:) userInfo: nil repeats: YES];
338    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSModalPanelRunLoopMode];
339    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSEventTrackingRunLoopMode];
340   
341    //timer for auto import, will check every 15 seconds
342    fAutoImportedNames = [[NSMutableArray alloc] init];
343   
344    [self checkAutoImportDirectory: nil];
345    fAutoImportTimer = [NSTimer scheduledTimerWithTimeInterval: 15.0 target: self
346        selector: @selector(checkAutoImportDirectory:) userInfo: nil repeats: YES];
347    [[NSRunLoop currentRunLoop] addTimer: fAutoImportTimer forMode: NSDefaultRunLoopMode];
348   
349    [self applyFilter: nil];
350   
351    [fWindow makeKeyAndOrderFront: nil];
352
353    //load info for no torrents
354    [fInfoController updateInfoForTorrents: [NSArray array]];
355    if ([fDefaults boolForKey: @"InfoVisible"])
356        [self showInfo: nil];
357}
358
359- (BOOL) applicationShouldHandleReopen: (NSApplication *) app hasVisibleWindows: (BOOL) visibleWindows
360{
361    if (![fWindow isVisible] && ![[fPrefsController window] isVisible])
362        [fWindow makeKeyAndOrderFront: nil];
363    return NO;
364}
365
366- (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication *) sender
367{
368    if (!fUpdateInProgress && [fDefaults boolForKey: @"CheckQuit"])
369    {
370        int active = 0, downloading = 0;
371        Torrent * torrent;
372        NSEnumerator * enumerator = [fTorrents objectEnumerator];
373        while ((torrent = [enumerator nextObject]))
374            if ([torrent isActive])
375            {
376                active++;
377                if (![torrent isSeeding])
378                    downloading++;
379            }
380
381        if ([fDefaults boolForKey: @"CheckQuitDownloading"] ? downloading > 0 : active > 0)
382        {
383            NSString * message = active == 1
384                ? @"There is an active transfer. Do you really want to quit?"
385                : [NSString stringWithFormat:
386                    @"There are %d active transfers. Do you really want to quit?", active];
387
388            NSBeginAlertSheet(@"Confirm Quit", @"Quit", @"Cancel", nil, fWindow, self,
389                                @selector(quitSheetDidEnd:returnCode:contextInfo:),
390                                nil, nil, message);
391            return NSTerminateLater;
392        }
393    }
394
395    return NSTerminateNow;
396}
397
398- (void) quitSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode
399    contextInfo: (void *) contextInfo
400{
401    [NSApp stopModal];
402    [NSApp replyToApplicationShouldTerminate: returnCode == NSAlertDefaultReturn];
403}
404
405- (void) applicationWillTerminate: (NSNotification *) notification
406{
407    //stop timers
408    [fAutoImportTimer invalidate];
409    [fTimer invalidate];
410   
411    //save history and stop running torrents
412    [self updateTorrentHistory];
413    [fTorrents makeObjectsPerformSelector: @selector(stopTransferForQuit)];
414   
415    //remember window states and close all windows
416    [fDefaults setBool: [[fInfoController window] isVisible] forKey: @"InfoVisible"];
417    [[NSApp windows] makeObjectsPerformSelector: @selector(close)];
418    [self showStatusBar: NO animate: NO];
419    [self showFilterBar: NO animate: NO];
420   
421    //clear badge
422    [fBadger clearBadge];
423
424    //end quickly if the app is updating
425    if (fUpdateInProgress)
426        return;
427
428    //wait for running transfers to stop (5 second timeout)
429    NSDate * start = [NSDate date];
430    BOOL timeUp = NO;
431   
432    NSEnumerator * enumerator = [fTorrents objectEnumerator];
433    Torrent * torrent;
434    while (!timeUp && (torrent = [enumerator nextObject]))
435        while (![torrent isPaused] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
436        {
437            usleep(100000);
438            [torrent update];
439        }
440}
441
442- (void) folderChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code
443    contextInfo: (Torrent *) torrent
444{
445    if (code == NSOKButton)
446    {
447        [torrent setDownloadFolder: [[openPanel filenames] objectAtIndex: 0]];
448        [torrent update];
449        [self attemptToStartAuto: torrent];
450       
451        [fTorrents addObject: torrent];
452       
453        [self torrentNumberChanged];
454    }
455   
456    [NSApp stopModal];
457}
458
459- (void) application: (NSApplication *) sender openFiles: (NSArray *) filenames
460{
461    NSString * downloadChoice = [fDefaults stringForKey: @"DownloadChoice"], * torrentPath;
462    Torrent * torrent;
463    NSEnumerator * enumerator = [filenames objectEnumerator];
464    while ((torrentPath = [enumerator nextObject]))
465    {
466        if (!(torrent = [[Torrent alloc] initWithPath: torrentPath lib: fLib]))
467            continue;
468
469        //add it to the "File > Open Recent" menu
470        [[NSDocumentController sharedDocumentController]
471            noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
472
473        if ([downloadChoice isEqualToString: @"Ask"])
474        {
475            NSOpenPanel * panel = [NSOpenPanel openPanel];
476
477            [panel setPrompt: @"Select Download Folder"];
478            [panel setAllowsMultipleSelection: NO];
479            [panel setCanChooseFiles: NO];
480            [panel setCanChooseDirectories: YES];
481
482            [panel setMessage: [NSString stringWithFormat: @"Select the download folder for \"%@\"", [torrent name]]];
483
484            [panel beginSheetForDirectory: nil file: nil types: nil
485                modalForWindow: fWindow modalDelegate: self didEndSelector:
486                @selector( folderChoiceClosed:returnCode:contextInfo: )
487                contextInfo: torrent];
488            [NSApp runModalForWindow: panel];
489        }
490        else
491        {
492            NSString * folder = [downloadChoice isEqualToString: @"Constant"]
493                ? [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath]
494                : [torrentPath stringByDeletingLastPathComponent];
495
496            [torrent setDownloadFolder: folder];
497            [torrent update];
498            [self attemptToStartAuto: torrent];
499           
500            [fTorrents addObject: torrent];
501        }
502       
503        [torrent release];
504    }
505
506    [self torrentNumberChanged];
507
508    [self updateUI: nil];
509    [self applyFilter: nil];
510    [self updateTorrentHistory];
511}
512
513- (NSArray *) torrentsAtIndexes: (NSIndexSet *) indexSet
514{
515    if ([fFilteredTorrents respondsToSelector: @selector(objectsAtIndexes:)])
516        return [fFilteredTorrents objectsAtIndexes: indexSet];
517    else
518    {
519        NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [indexSet count]];
520        unsigned int i;
521        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
522            [torrents addObject: [fFilteredTorrents objectAtIndex: i]];
523
524        return torrents;
525    }
526}
527
528- (void) torrentNumberChanged
529{
530    int count = [fTorrents count];
531    [fTotalTorrentsField setStringValue: [NSString stringWithFormat:
532        @"%d Transfer%s", count, count == 1 ? "" : "s"]];
533}
534
535//called on by applescript
536- (void) open: (NSArray *) files
537{
538    [self performSelectorOnMainThread: @selector(openFromSheet:) withObject: files waitUntilDone: NO];
539}
540
541- (void) openShowSheet: (id) sender
542{
543    NSOpenPanel * panel = [NSOpenPanel openPanel];
544
545    [panel setAllowsMultipleSelection: YES];
546    [panel setCanChooseFiles: YES];
547    [panel setCanChooseDirectories: NO];
548
549    [panel beginSheetForDirectory: nil file: nil types: [NSArray arrayWithObject: @"torrent"]
550        modalForWindow: fWindow modalDelegate: self didEndSelector:
551        @selector(openSheetClosed:returnCode:contextInfo:) contextInfo: nil];
552}
553
554- (void) openSheetClosed: (NSOpenPanel *) panel returnCode: (int) code contextInfo: (void *) info
555{
556    if (code == NSOKButton)
557        [self performSelectorOnMainThread: @selector(openFromSheet:)
558                    withObject: [panel filenames] waitUntilDone: NO];
559}
560
561- (void) openFromSheet: (NSArray *) filenames
562{
563    [self application: NSApp openFiles: filenames];
564}
565
566- (void) resumeSelectedTorrents: (id) sender
567{
568    [self resumeTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]];
569}
570
571- (void) resumeAllTorrents: (id) sender
572{
573    [self resumeTorrents: fTorrents];
574}
575
576- (void) resumeTorrents: (NSArray *) torrents
577{
578    [torrents makeObjectsPerformSelector: @selector(startTransfer)];
579   
580    [self updateUI: nil];
581    [self applyFilter: nil];
582    [fInfoController updateInfoStatsAndSettings];
583    [self updateTorrentHistory];
584}
585
586- (void) stopSelectedTorrents: (id) sender
587{
588    [self stopTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]];
589}
590
591- (void) stopAllTorrents: (id) sender
592{
593    [self stopTorrents: fTorrents];
594}
595
596- (void) stopTorrents: (NSArray *) torrents
597{
598    //don't want any of these starting then stopping
599    NSEnumerator * enumerator = [torrents objectEnumerator];
600    Torrent * torrent;
601    while ((torrent = [enumerator nextObject]))
602        [torrent setWaitToStart: NO];
603
604    [torrents makeObjectsPerformSelector: @selector(stopTransfer)];
605   
606    [self updateUI: nil];
607    [self applyFilter: nil];
608    [fInfoController updateInfoStatsAndSettings];
609    [self updateTorrentHistory];
610}
611
612- (void) removeTorrents: (NSArray *) torrents
613        deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
614{
615    [torrents retain];
616    int active = 0, downloading = 0;
617
618    if ([fDefaults boolForKey: @"CheckRemove"])
619    {
620        Torrent * torrent;
621        NSEnumerator * enumerator = [torrents objectEnumerator];
622        while ((torrent = [enumerator nextObject]))
623            if ([torrent isActive])
624            {
625                active++;
626                if (![torrent isSeeding])
627                    downloading++;
628            }
629
630        if ([fDefaults boolForKey: @"CheckRemoveDownloading"] ? downloading > 0 : active > 0)
631        {
632            NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
633                torrents, @"Torrents",
634                [NSNumber numberWithBool: deleteData], @"DeleteData",
635                [NSNumber numberWithBool: deleteTorrent], @"DeleteTorrent", nil];
636
637            NSString * title, * message;
638           
639            int selected = [fTableView numberOfSelectedRows];
640            if (selected == 1)
641            {
642                title = [NSString stringWithFormat: @"Comfirm Removal of \"%@\"",
643                            [[fFilteredTorrents objectAtIndex: [fTableView selectedRow]] name]];
644                message = @"This transfer is active."
645                            " Once removed, continuing the transfer will require the torrent file."
646                            " Do you really want to remove it?";
647            }
648            else
649            {
650                title = [NSString stringWithFormat: @"Comfirm Removal of %d Transfers", selected];
651                if (selected == active)
652                    message = [NSString stringWithFormat:
653                        @"There are %d active transfers.", active];
654                else
655                    message = [NSString stringWithFormat:
656                        @"There are %d transfers (%d active).", selected, active];
657                message = [message stringByAppendingString:
658                    @" Once removed, continuing the transfers will require the torrent files."
659                    " Do you really want to remove them?"];
660            }
661
662            NSBeginAlertSheet(title, @"Remove", @"Cancel", nil, fWindow, self,
663                @selector(removeSheetDidEnd:returnCode:contextInfo:), nil, dict, message);
664            return;
665        }
666    }
667   
668    [self confirmRemoveTorrents: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
669}
670
671- (void) removeSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (NSDictionary *) dict
672{
673    [NSApp stopModal];
674
675    NSArray * torrents = [dict objectForKey: @"Torrents"];
676    BOOL deleteData = [[dict objectForKey: @"DeleteData"] boolValue],
677        deleteTorrent = [[dict objectForKey: @"DeleteTorrent"] boolValue];
678    [dict release];
679   
680    if (returnCode == NSAlertDefaultReturn)
681        [self confirmRemoveTorrents: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
682    else
683        [torrents release];
684}
685
686- (void) confirmRemoveTorrents: (NSArray *) torrents deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
687{
688    //don't want any of these starting then stopping
689    NSEnumerator * enumerator = [torrents objectEnumerator];
690    Torrent * torrent;
691    while ((torrent = [enumerator nextObject]))
692        [torrent setWaitToStart: NO];
693
694    NSNumber * lowestOrderValue = [NSNumber numberWithInt: [torrents count]], * currentOrderValue;
695
696    enumerator = [torrents objectEnumerator];
697    while ((torrent = [enumerator nextObject]))
698    {
699        [torrent stopTransfer];
700
701        if (deleteData)
702            [torrent trashData];
703        if (deleteTorrent)
704            [torrent trashTorrent];
705       
706        //determine lowest order value
707        currentOrderValue = [torrent orderValue];
708        if ([lowestOrderValue compare: currentOrderValue] == NSOrderedDescending)
709            lowestOrderValue = currentOrderValue;
710
711        [torrent removeForever];
712       
713        [fFilteredTorrents removeObject: torrent];
714        [fTorrents removeObject: torrent];
715    }
716    [torrents release];
717
718    //reset the order values if necessary
719    if ([lowestOrderValue intValue] < [fTorrents count])
720    {
721        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
722                                                @"orderValue" ascending: YES] autorelease];
723        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
724
725        NSArray * tempTorrents = [fTorrents sortedArrayUsingDescriptors: descriptors];
726        [descriptors release];
727
728        int i;
729        for (i = [lowestOrderValue intValue]; i < [tempTorrents count]; i++)
730            [[tempTorrents objectAtIndex: i] setOrderValue: i];
731    }
732   
733    [self torrentNumberChanged];
734    [fTableView deselectAll: nil];
735    [self updateUI: nil];
736    [self updateTorrentHistory];
737}
738
739- (void) removeNoDelete: (id) sender
740{
741    [self removeTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]
742                deleteData: NO deleteTorrent: NO];
743}
744
745- (void) removeDeleteData: (id) sender
746{
747    [self removeTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]
748                deleteData: YES deleteTorrent: NO];
749}
750
751- (void) removeDeleteTorrent: (id) sender
752{
753    [self removeTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]
754                deleteData: NO deleteTorrent: YES];
755}
756
757- (void) removeDeleteDataAndTorrent: (id) sender
758{
759    [self removeTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]
760                deleteData: YES deleteTorrent: YES];
761}
762
763- (void) copyTorrentFile: (id) sender
764{
765    [self copyTorrentFileForTorrents: [[NSMutableArray alloc] initWithArray:
766            [self torrentsAtIndexes: [fTableView selectedRowIndexes]]]];
767}
768
769- (void) copyTorrentFileForTorrents: (NSMutableArray *) torrents
770{
771    if ([torrents count] == 0)
772    {
773        [torrents release];
774        return;
775    }
776
777    Torrent * torrent = [torrents objectAtIndex: 0];
778
779    //warn user if torrent file can't be found
780    if (![[NSFileManager defaultManager] fileExistsAtPath: [torrent torrentLocation]])
781    {
782        NSAlert * alert = [[NSAlert alloc] init];
783        [alert addButtonWithTitle: @"OK"];
784        [alert setMessageText: [NSString stringWithFormat:
785                @"Copy of \"%@\" Cannot Be Created", [torrent name]]];
786        [alert setInformativeText: [NSString stringWithFormat:
787                @"The torrent file (%@) cannot be found.", [torrent torrentLocation]]];
788        [alert setAlertStyle: NSWarningAlertStyle];
789       
790        [alert runModal];
791       
792        [torrents removeObjectAtIndex: 0];
793        [self copyTorrentFileForTorrents: torrents];
794    }
795    else
796    {
797        NSSavePanel * panel = [NSSavePanel savePanel];
798        [panel setRequiredFileType: @"torrent"];
799        [panel setCanSelectHiddenExtension: YES];
800       
801        [panel beginSheetForDirectory: nil file: [torrent name]
802            modalForWindow: fWindow modalDelegate: self didEndSelector:
803            @selector(saveTorrentCopySheetClosed:returnCode:contextInfo:) contextInfo: torrents];
804    }
805}
806
807- (void) saveTorrentCopySheetClosed: (NSSavePanel *) panel returnCode: (int) code
808    contextInfo: (NSMutableArray *) torrents
809{
810    //if save successful, copy torrent to new location with name of data file
811    if (code == NSOKButton)
812        [[NSFileManager defaultManager] copyPath: [[torrents objectAtIndex: 0] torrentLocation]
813                toPath: [panel filename] handler: nil];
814   
815    [torrents removeObjectAtIndex: 0];
816    [self performSelectorOnMainThread: @selector(copyTorrentFileForTorrents:)
817                withObject: torrents waitUntilDone: NO];
818}
819
820- (void) revealFile: (id) sender
821{
822    NSIndexSet * indexSet = [fTableView selectedRowIndexes];
823    unsigned int i;
824   
825    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
826        [[fFilteredTorrents objectAtIndex: i] revealData];
827}
828
829- (void) showPreferenceWindow: (id) sender
830{
831    NSWindow * window = [fPrefsController window];
832    if (![window isVisible])
833        [window center];
834
835    [window makeKeyAndOrderFront: nil];
836}
837
838- (void) showInfo: (id) sender
839{
840    if ([[fInfoController window] isVisible])
841        [fInfoController close];
842    else
843    {
844        [fInfoController updateInfoStats];
845        [[fInfoController window] orderFront: nil];
846    }
847}
848
849- (void) setInfoTab: (id) sender
850{
851    if (sender == fNextInfoTabItem)
852        [fInfoController setNextTab];
853    else
854        [fInfoController setPreviousTab];
855}
856
857- (void) updateControlTint: (NSNotification *) notification
858{
859    if (fSpeedLimitEnabled)
860        [fSpeedLimitButton setImage: [NSColor currentControlTint] == NSBlueControlTint
861                                        ? fSpeedLimitBlueImage : fSpeedLimitGraphiteImage];
862}
863
864- (void) updateUI: (NSTimer *) t
865{
866    NSEnumerator * enumerator = [fTorrents objectEnumerator];
867    Torrent * torrent;
868    while ((torrent = [enumerator nextObject]))
869    {
870        [torrent update];
871
872        if ([torrent justFinished])
873        {
874            [self applyFilter: nil];
875            [self checkToStartWaiting: torrent];
876       
877            [GrowlApplicationBridge notifyWithTitle: @"Download Complete"
878                description: [torrent name] notificationName: @"Download Complete" iconData: nil
879                priority: 0 isSticky: NO clickContext: nil];
880
881            if (![fWindow isKeyWindow])
882                fCompleted++;
883        }
884    }
885
886    if ([fSortType isEqualToString: @"Progress"] || [fSortType isEqualToString: @"State"])
887        [self sortTorrents];
888    else
889        [fTableView reloadData];
890   
891    //update the global DL/UL rates
892    float downloadRate, uploadRate;
893    tr_torrentRates(fLib, & downloadRate, & uploadRate);
894    if (fStatusBarVisible)
895    {
896        [fTotalDLField setStringValue: [@"Total DL: " stringByAppendingString: [NSString stringForSpeed: downloadRate]]];
897        [fTotalULField setStringValue: [@"Total UL: " stringByAppendingString: [NSString stringForSpeed: uploadRate]]];
898    }
899
900    if ([[fInfoController window] isVisible])
901        [fInfoController updateInfoStats];
902
903    //badge dock
904    [fBadger updateBadgeWithCompleted: fCompleted
905        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    //resize for larger min height
1592    if (!makeSmall && contentSize.height < contentMinSize.height)
1593    {
1594        NSRect frame = [fWindow frame];
1595        float heightChange = contentMinSize.height - contentSize.height;
1596        frame.size.height += heightChange;
1597        frame.origin.y -= heightChange;
1598       
1599        [fWindow setFrame: frame display: YES];
1600        [fTableView reloadData];
1601    }
1602}
1603
1604- (void) toggleStatusBar: (id) sender
1605{
1606    [self showStatusBar: !fStatusBarVisible animate: YES];
1607    [fDefaults setBool: fStatusBarVisible forKey: @"StatusBar"];
1608}
1609
1610- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
1611{
1612    if (show == fStatusBarVisible)
1613        return;
1614
1615    if (show)
1616        [fStatusBar setHidden: NO];
1617
1618    NSRect frame = [fWindow frame];
1619    float heightChange = [fStatusBar frame].size.height;
1620    if (!show)
1621        heightChange *= -1;
1622
1623    frame.size.height += heightChange;
1624    frame.origin.y -= heightChange;
1625       
1626    fStatusBarVisible = show;
1627   
1628    [self updateUI: nil];
1629   
1630    //set views to not autoresize
1631    unsigned int statsMask = [fStatusBar autoresizingMask];
1632    unsigned int filterMask = [fFilterBar autoresizingMask];
1633    unsigned int scrollMask = [fScrollView autoresizingMask];
1634    [fStatusBar setAutoresizingMask: 0];
1635    [fFilterBar setAutoresizingMask: 0];
1636    [fScrollView setAutoresizingMask: 0];
1637   
1638    [fWindow setFrame: frame display: YES animate: animate];
1639   
1640    //re-enable autoresize
1641    [fStatusBar setAutoresizingMask: statsMask];
1642    [fFilterBar setAutoresizingMask: filterMask];
1643    [fScrollView setAutoresizingMask: scrollMask];
1644   
1645    //change min size
1646    NSSize minSize = [fWindow contentMinSize];
1647    minSize.height += heightChange;
1648    [fWindow setContentMinSize: minSize];
1649   
1650    if (!show)
1651        [fStatusBar setHidden: YES];
1652   
1653    //reset tracking rects for filter buttons
1654    [fNoFilterButton resetBounds: nil];
1655    [fSeedFilterButton resetBounds: nil];
1656    [fDownloadFilterButton resetBounds: nil];
1657    [fPauseFilterButton resetBounds: nil];
1658}
1659
1660- (void) toggleFilterBar: (id) sender
1661{
1662    [self showFilterBar: !fFilterBarVisible animate: YES];
1663    [fDefaults setBool: fFilterBarVisible forKey: @"FilterBar"];
1664   
1665    //disable filtering when hiding
1666    if (!fFilterBarVisible)
1667    {
1668        [fSearchFilterField setStringValue: @""];
1669        [self setFilter: fNoFilterButton];
1670    }
1671}
1672
1673- (void) showFilterBar: (BOOL) show animate: (BOOL) animate
1674{
1675    if (show == fFilterBarVisible)
1676        return;
1677
1678    if (show)
1679        [fFilterBar setHidden: NO];
1680
1681    NSRect frame = [fWindow frame];
1682    float heightChange = [fFilterBar frame].size.height;
1683    if (!show)
1684        heightChange *= -1;
1685
1686    frame.size.height += heightChange;
1687    frame.origin.y -= heightChange;
1688       
1689    fFilterBarVisible = show;
1690   
1691    //set views to not autoresize
1692    unsigned int filterMask = [fFilterBar autoresizingMask];
1693    unsigned int scrollMask = [fScrollView autoresizingMask];
1694    [fFilterBar setAutoresizingMask: 0];
1695    [fScrollView setAutoresizingMask: 0];
1696   
1697    [fWindow setFrame: frame display: YES animate: animate];
1698   
1699    //re-enable autoresize
1700    [fFilterBar setAutoresizingMask: filterMask];
1701    [fScrollView setAutoresizingMask: scrollMask];
1702   
1703    //change min size
1704    NSSize minSize = [fWindow contentMinSize];
1705    minSize.height += heightChange;
1706    [fWindow setContentMinSize: minSize];
1707   
1708    if (!show)
1709    {
1710        [fFilterBar setHidden: YES];
1711        [fWindow makeFirstResponder: fTableView];
1712    }
1713   
1714    //reset tracking rects for filter buttons
1715    [fNoFilterButton resetBounds: nil];
1716    [fSeedFilterButton resetBounds: nil];
1717    [fDownloadFilterButton resetBounds: nil];
1718    [fPauseFilterButton resetBounds: nil];
1719}
1720
1721- (void) toggleAdvancedBar: (id) sender
1722{
1723    int state = ![fAdvancedBarItem state];
1724    [fAdvancedBarItem setState: state];
1725    [fDefaults setBool: state forKey: @"UseAdvancedBar"];
1726   
1727    [fTableView display];
1728}
1729
1730- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
1731    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
1732{
1733    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
1734
1735    if ([ident isEqualToString: TOOLBAR_OPEN])
1736    {
1737        [item setLabel: @"Open"];
1738        [item setPaletteLabel: @"Open Torrent Files"];
1739        [item setToolTip: @"Open torrent files"];
1740        [item setImage: [NSImage imageNamed: @"Open.png"]];
1741        [item setTarget: self];
1742        [item setAction: @selector(openShowSheet:)];
1743    }
1744    else if ([ident isEqualToString: TOOLBAR_REMOVE])
1745    {
1746        [item setLabel: @"Remove"];
1747        [item setPaletteLabel: @"Remove Selected"];
1748        [item setToolTip: @"Remove selected transfers"];
1749        [item setImage: [NSImage imageNamed: @"Remove.png"]];
1750        [item setTarget: self];
1751        [item setAction: @selector(removeNoDelete:)];
1752    }
1753    else if ([ident isEqualToString: TOOLBAR_INFO])
1754    {
1755        [item setLabel: @"Inspector"];
1756        [item setPaletteLabel: @"Toggle Inspector"];
1757        [item setToolTip: @"Toggle the torrent inspector"];
1758        [item setImage: [NSImage imageNamed: @"Info.png"]];
1759        [item setTarget: self];
1760        [item setAction: @selector(showInfo:)];
1761    }
1762    else if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
1763    {
1764        [item setLabel: @"Pause All"];
1765        [item setPaletteLabel: [item label]];
1766        [item setToolTip: @"Pause all transfers"];
1767        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
1768        [item setTarget: self];
1769        [item setAction: @selector(stopAllTorrents:)];
1770    }
1771    else if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
1772    {
1773        [item setLabel: @"Resume All"];
1774        [item setPaletteLabel: [item label]];
1775        [item setToolTip: @"Resume all transfers"];
1776        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
1777        [item setTarget: self];
1778        [item setAction: @selector(resumeAllTorrents:)];
1779    }
1780    else if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
1781    {
1782        [item setLabel: @"Pause"];
1783        [item setPaletteLabel: @"Pause Selected"];
1784        [item setToolTip: @"Pause selected transfers"];
1785        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
1786        [item setTarget: self];
1787        [item setAction: @selector(stopSelectedTorrents:)];
1788    }
1789    else if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
1790    {
1791        [item setLabel: @"Resume"];
1792        [item setPaletteLabel: @"Resume Selected"];
1793        [item setToolTip: @"Resume selected transfers"];
1794        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
1795        [item setTarget: self];
1796        [item setAction: @selector(resumeSelectedTorrents:)];
1797    }
1798    else if ([ident isEqualToString: TOOLBAR_FILTER])
1799    {
1800        [item setLabel: @"Filter"];
1801        [item setPaletteLabel: @"Toggle Filter"];
1802        [item setToolTip: @"Toggle the filter bar"];
1803        [item setImage: [NSImage imageNamed: @"Filter.png"]];
1804        [item setTarget: self];
1805        [item setAction: @selector(toggleFilterBar:)];
1806    }
1807    else
1808    {
1809        [item release];
1810        return nil;
1811    }
1812
1813    return item;
1814}
1815
1816- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
1817{
1818    return [NSArray arrayWithObjects:
1819            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1820            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
1821            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL, TOOLBAR_FILTER, TOOLBAR_INFO,
1822            NSToolbarSeparatorItemIdentifier,
1823            NSToolbarSpaceItemIdentifier,
1824            NSToolbarFlexibleSpaceItemIdentifier,
1825            NSToolbarCustomizeToolbarItemIdentifier, nil];
1826}
1827
1828- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
1829{
1830    return [NSArray arrayWithObjects:
1831            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1832            NSToolbarSeparatorItemIdentifier,
1833            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
1834            NSToolbarFlexibleSpaceItemIdentifier,
1835            TOOLBAR_FILTER, TOOLBAR_INFO, nil];
1836}
1837
1838- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
1839{
1840    NSString * ident = [toolbarItem itemIdentifier];
1841
1842    //enable remove item
1843    if ([ident isEqualToString: TOOLBAR_REMOVE])
1844        return [fTableView numberOfSelectedRows] > 0;
1845
1846    //enable pause all item
1847    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
1848    {
1849        Torrent * torrent;
1850        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1851        while ((torrent = [enumerator nextObject]))
1852            if ([torrent isActive])
1853                return YES;
1854        return NO;
1855    }
1856
1857    //enable resume all item
1858    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
1859    {
1860        Torrent * torrent;
1861        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1862        while ((torrent = [enumerator nextObject]))
1863            if ([torrent isPaused])
1864                return YES;
1865        return NO;
1866    }
1867
1868    //enable pause item
1869    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
1870    {
1871        Torrent * torrent;
1872        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1873        unsigned int i;
1874       
1875        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1876            if ([[fFilteredTorrents objectAtIndex: i] isActive])
1877                return YES;
1878        return NO;
1879    }
1880   
1881    //enable resume item
1882    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
1883    {
1884        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1885        unsigned int i;
1886       
1887        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1888            if ([[fFilteredTorrents objectAtIndex: i] isPaused])
1889                return YES;
1890        return NO;
1891    }
1892
1893    return YES;
1894}
1895
1896- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
1897{
1898    SEL action = [menuItem action];
1899
1900    //only enable some items if it is in a context menu or the window is useable
1901    BOOL canUseMenu = [fWindow isKeyWindow] || [[[menuItem menu] title] isEqualToString: @"Context"];
1902
1903    //enable show info
1904    if (action == @selector(showInfo:))
1905    {
1906        NSString * title = [[fInfoController window] isVisible] ? @"Hide Inspector" : @"Show Inspector";
1907        if (![[menuItem title] isEqualToString: title])
1908                [menuItem setTitle: title];
1909
1910        return YES;
1911    }
1912   
1913    //enable prev/next inspector tab
1914    if (action == @selector(setInfoTab:))
1915        return [[fInfoController window] isVisible];
1916   
1917    //enable toggle status bar
1918    if (action == @selector(toggleStatusBar:))
1919    {
1920        NSString * title = fStatusBarVisible ? @"Hide Status Bar" : @"Show Status Bar";
1921        if (![[menuItem title] isEqualToString: title])
1922                [menuItem setTitle: title];
1923
1924        return canUseMenu;
1925    }
1926   
1927    //enable toggle filter bar
1928    if (action == @selector(toggleFilterBar:))
1929    {
1930        NSString * title = fFilterBarVisible ? @"Hide Filter Bar" : @"Show Filter Bar";
1931        if (![[menuItem title] isEqualToString: title])
1932                [menuItem setTitle: title];
1933
1934        return canUseMenu;
1935    }
1936
1937    //enable resume all item
1938    if (action == @selector(resumeAllTorrents:))
1939    {
1940        Torrent * torrent;
1941        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1942        while ((torrent = [enumerator nextObject]))
1943            if ([torrent isPaused])
1944                return YES;
1945        return NO;
1946    }
1947
1948    //enable pause all item
1949    if (action == @selector(stopAllTorrents:))
1950    {
1951        Torrent * torrent;
1952        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1953        while ((torrent = [enumerator nextObject]))
1954            if ([torrent isActive])
1955                return YES;
1956        return NO;
1957    }
1958
1959    //enable reveal in finder
1960    if (action == @selector(revealFile:))
1961        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1962
1963    //enable remove items
1964    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
1965        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteDataAndTorrent:))
1966    {
1967        BOOL warning = NO,
1968            onlyDownloading = [fDefaults boolForKey: @"CheckRemoveDownloading"],
1969            canDelete = action != @selector(removeDeleteTorrent:) && action != @selector(removeDeleteDataAndTorrent:);
1970        Torrent * torrent;
1971        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1972        unsigned int i;
1973       
1974        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1975        {
1976            torrent = [fFilteredTorrents objectAtIndex: i];
1977            if (!warning && [torrent isActive])
1978            {
1979                warning = onlyDownloading ? ![torrent isSeeding] : YES;
1980                if (warning && canDelete)
1981                    break;
1982            }
1983            if (!canDelete && [torrent publicTorrent])
1984            {
1985                canDelete = YES;
1986                if (warning)
1987                    break;
1988            }
1989        }
1990   
1991        //append or remove ellipsis when needed
1992        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
1993        if (warning && [fDefaults boolForKey: @"CheckRemove"])
1994        {
1995            if (![title hasSuffix: ellipsis])
1996                [menuItem setTitle: [title stringByAppendingEllipsis]];
1997        }
1998        else
1999        {
2000            if ([title hasSuffix: ellipsis])
2001                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
2002        }
2003       
2004        return canUseMenu && canDelete && [fTableView numberOfSelectedRows] > 0;
2005    }
2006
2007    //enable pause item
2008    if (action == @selector(stopSelectedTorrents:))
2009    {
2010        if (!canUseMenu)
2011            return NO;
2012   
2013        Torrent * torrent;
2014        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
2015        unsigned int i;
2016       
2017        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
2018        {
2019            torrent = [fFilteredTorrents objectAtIndex: i];
2020            if ([torrent isActive])
2021                return YES;
2022        }
2023        return NO;
2024    }
2025   
2026    //enable resume item
2027    if (action == @selector(resumeSelectedTorrents:))
2028    {
2029        if (!canUseMenu)
2030            return NO;
2031   
2032        Torrent * torrent;
2033        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
2034        unsigned int i;
2035       
2036        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
2037        {
2038            torrent = [fFilteredTorrents objectAtIndex: i];
2039            if ([torrent isPaused])
2040                return YES;
2041        }
2042        return NO;
2043    }
2044   
2045    //enable sort and advanced bar items
2046    if (action == @selector(setSort:) || action == @selector(toggleAdvancedBar:)
2047            || action == @selector(toggleSmallView:))
2048        return canUseMenu;
2049   
2050    //enable copy torrent file item
2051    if (action == @selector(copyTorrentFile:))
2052    {
2053        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
2054    }
2055
2056    return YES;
2057}
2058
2059- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument
2060{
2061    NSEnumerator * enumerator;
2062    Torrent * torrent;
2063    BOOL active;
2064
2065    switch (messageType)
2066    {
2067        case kIOMessageSystemWillSleep:
2068            //close all connections before going to sleep and remember we should resume when we wake up
2069            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
2070
2071            //wait for running transfers to stop (5 second timeout)
2072            NSDate * start = [NSDate date];
2073            BOOL timeUp = NO;
2074           
2075            NSEnumerator * enumerator = [fTorrents objectEnumerator];
2076            Torrent * torrent;
2077            while (!timeUp && (torrent = [enumerator nextObject]))
2078                while (![torrent isPaused] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
2079                {
2080                    usleep(100000);
2081                    [torrent update];
2082                }
2083
2084            IOAllowPowerChange(fRootPort, (long) messageArgument);
2085            break;
2086
2087        case kIOMessageCanSystemSleep:
2088            //pevent idle sleep unless all paused
2089            active = NO;
2090            enumerator = [fTorrents objectEnumerator];
2091            while ((torrent = [enumerator nextObject]))
2092                if ([torrent isActive])
2093                {
2094                    active = YES;
2095                    break;
2096                }
2097
2098            if (active)
2099                IOCancelPowerChange(fRootPort, (long) messageArgument);
2100            else
2101                IOAllowPowerChange(fRootPort, (long) messageArgument);
2102            break;
2103
2104        case kIOMessageSystemHasPoweredOn:
2105            //resume sleeping transfers after we wake up
2106            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
2107            break;
2108    }
2109}
2110
2111- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
2112{
2113    NSRect windowRect = [fWindow frame];
2114    float newHeight = windowRect.size.height - [fScrollView frame].size.height
2115        + [fFilteredTorrents count] * ([fTableView rowHeight] + [fTableView intercellSpacing].height);
2116
2117    float minHeight = [fWindow minSize].height;
2118    if (newHeight < minHeight)
2119        newHeight = minHeight;
2120
2121    windowRect.origin.y -= (newHeight - windowRect.size.height);
2122    windowRect.size.height = newHeight;
2123
2124    return windowRect;
2125}
2126
2127- (void) showMainWindow: (id) sender
2128{
2129    [fWindow makeKeyAndOrderFront: nil];
2130}
2131
2132- (void) windowDidBecomeKey: (NSNotification *) notification
2133{
2134    //reset dock badge for completed
2135    if (fCompleted > 0)
2136    {
2137        fCompleted = 0;
2138        [self updateUI: nil];
2139    }
2140   
2141    //set status fields inactive color
2142    /*NSColor * enabledColor = [NSColor controlTextColor];
2143    [fTotalTorrentsField setTextColor: enabledColor];
2144    [fTotalDLField setTextColor: enabledColor];
2145    [fTotalULField setTextColor: enabledColor];*/
2146   
2147    //set filter images as active
2148    [fNoFilterButton setForActive];
2149    [fSeedFilterButton setForActive];
2150    [fDownloadFilterButton setForActive];
2151    [fPauseFilterButton setForActive];
2152   
2153    /*if (fSpeedLimitEnabled)
2154        [fSpeedLimitButton setImage: [NSColor currentControlTint] == NSBlueControlTint
2155                                        ? fSpeedLimitBlueImage : fSpeedLimitGraphiteImage];*/
2156}
2157
2158- (void) windowDidResignKey: (NSNotification *) notification
2159{
2160    #warning should it do this?
2161    //set status fields inactive color
2162    /*NSColor * disabledColor = [NSColor disabledControlTextColor];
2163    [fTotalTorrentsField setTextColor: disabledColor];
2164    [fTotalDLField setTextColor: disabledColor];
2165    [fTotalULField setTextColor: disabledColor];*/
2166
2167    //set filter images as inactive
2168    [fNoFilterButton setForInactive];
2169    [fSeedFilterButton setForInactive];
2170    [fDownloadFilterButton setForInactive];
2171    [fPauseFilterButton setForInactive];
2172   
2173    #warning need real inactive image, or to not do anything at all (?)
2174    /*if (fSpeedLimitEnabled)
2175        [fSpeedLimitButton setImage: fSpeedLimitNormalImage];*/
2176}
2177
2178- (void) windowDidResize: (NSNotification *) notification
2179{
2180    //hide search filter if it overlaps filter buttons
2181    NSRect buttonFrame = [fPauseFilterButton frame];
2182    BOOL overlap = buttonFrame.origin.x + buttonFrame.size.width + 2.0 > [fSearchFilterField frame].origin.x;
2183   
2184    if (![fSearchFilterField isHidden])
2185    {
2186        if (overlap)
2187            [fSearchFilterField setHidden: YES];
2188    }
2189    else
2190    {
2191        if (!overlap)
2192            [fSearchFilterField setHidden: NO];
2193    }
2194}
2195
2196- (void) linkHomepage: (id) sender
2197{
2198    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
2199}
2200
2201- (void) linkForums: (id) sender
2202{
2203    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
2204}
2205
2206- (void) checkUpdate: (id) sender
2207{
2208    [fPrefsController checkUpdate];
2209}
2210
2211- (void) prepareForUpdate: (NSNotification *) notification
2212{
2213    fUpdateInProgress = YES;
2214}
2215
2216- (NSDictionary *) registrationDictionaryForGrowl
2217{
2218    NSArray * notifications = [NSArray arrayWithObjects: @"Download Complete",
2219                                @"Seeding Complete", @"Torrent Auto Added", nil];
2220    return [NSDictionary dictionaryWithObjectsAndKeys: notifications, GROWL_NOTIFICATIONS_ALL,
2221                                notifications, GROWL_NOTIFICATIONS_DEFAULT, nil];
2222}
2223
2224@end
Note: See TracBrowser for help on using the repository browser.