source: trunk/macosx/Controller.m @ 671

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

Fixed bug with auto resize when changing from minimal view to regular view.

  • Property svn:keywords set to Date Rev Author Id
File size: 75.3 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 671 2006-07-23 15:59:06Z livings124 $
3 *
4 * Copyright (c) 2005-2006 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import <IOKit/IOMessage.h>
26
27#import "Controller.h"
28#import "Torrent.h"
29#import "TorrentCell.h"
30#import "TorrentTableView.h"
31#import "StringAdditions.h"
32
33#import <Sparkle/Sparkle.h>
34
35#define TOOLBAR_OPEN            @"Toolbar Open"
36#define TOOLBAR_REMOVE          @"Toolbar Remove"
37#define TOOLBAR_INFO            @"Toolbar Info"
38#define TOOLBAR_PAUSE_ALL       @"Toolbar Pause All"
39#define TOOLBAR_RESUME_ALL      @"Toolbar Resume All"
40#define TOOLBAR_PAUSE_SELECTED  @"Toolbar Pause Selected"
41#define TOOLBAR_RESUME_SELECTED @"Toolbar Resume Selected"
42#define TOOLBAR_FILTER   @"Toolbar Toggle Filter"
43
44#define TORRENT_TABLE_VIEW_DATA_TYPE    @"TorrentTableViewDataType"
45
46#define ROW_HEIGHT_REGULAR 65.0
47#define ROW_HEIGHT_SMALL 40.0
48
49#define WEBSITE_URL @"http://transmission.m0k.org/"
50#define FORUM_URL   @"http://transmission.m0k.org/forum/"
51
52static void sleepCallBack(void * controller, io_service_t y, natural_t messageType, void * messageArgument)
53{
54    Controller * c = controller;
55    [c sleepCallBack: messageType argument: messageArgument];
56}
57
58
59@implementation Controller
60
61- (id) init
62{
63    if ((self = [super init]))
64    {
65        fLib = tr_init();
66       
67        fTorrents = [[NSMutableArray alloc] initWithCapacity: 10];
68        fFilteredTorrents = [[NSMutableArray alloc] initWithCapacity: 10];
69       
70        fDefaults = [NSUserDefaults standardUserDefaults];
71        fInfoController = [[InfoWindowController alloc] initWithWindowNibName: @"InfoWindow"];
72        fPrefsController = [[PrefsController alloc] initWithWindowNibName: @"PrefsWindow"];
73        fBadger = [[Badger alloc] init];
74       
75        [GrowlApplicationBridge setGrowlDelegate: self];
76    }
77    return self;
78}
79
80- (void) dealloc
81{
82    [[NSNotificationCenter defaultCenter] removeObserver: self];
83
84    [fTorrents release];
85    [fFilteredTorrents release];
86   
87    [fToolbar release];
88    [fInfoController release];
89    [fPrefsController release];
90    [fBadger release];
91   
92    [fSortType release];
93    [fFilterType release];
94    [fAutoImportedNames release];
95   
96    tr_close(fLib);
97    [super dealloc];
98}
99
100- (void) awakeFromNib
101{
102    [fPrefsController setPrefs: fLib];
103   
104    [fStatusBar setBackgroundImage: [NSImage imageNamed: @"StatusBarBackground.png"]];
105    [fFilterBar setBackgroundImage: [NSImage imageNamed: @"FilterBarBackground.png"]];
106   
107    [fWindow setAcceptsMouseMovedEvents: YES]; //ensure filter buttons display correctly
108   
109    [fAdvancedBarItem setState: [fDefaults boolForKey: @"UseAdvancedBar"]];
110
111    fToolbar = [[NSToolbar alloc] initWithIdentifier: @"Transmission Toolbar"];
112    [fToolbar setDelegate: self];
113    [fToolbar setAllowsUserCustomization: YES];
114    [fToolbar setAutosavesConfiguration: YES];
115    [fWindow setToolbar: fToolbar];
116    [fWindow setDelegate: self];
117   
118    [fWindow makeFirstResponder: fTableView];
119   
120    //set table size
121    if ([fDefaults boolForKey: @"SmallView"])
122    {
123        [fTableView setRowHeight: ROW_HEIGHT_SMALL];
124        [fSmallViewItem setState: NSOnState];
125    }
126   
127    //window min height
128    NSSize contentMinSize = [fWindow contentMinSize];
129    contentMinSize.height = [[fWindow contentView] frame].size.height - [fScrollView frame].size.height
130                                + [fTableView rowHeight] + [fTableView intercellSpacing].height;
131    [fWindow setContentMinSize: contentMinSize];
132   
133    //set info keyboard shortcuts
134    unichar ch = NSRightArrowFunctionKey;
135    [fNextInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & ch length: 1]];
136    ch = NSLeftArrowFunctionKey;
137    [fPrevInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & ch length: 1]];
138   
139    //set up filter bar
140    NSView * contentView = [fWindow contentView];
141    [fFilterBar setHidden: YES];
142   
143    fFilterBarVisible = NO;
144    NSRect filterBarFrame = [fFilterBar frame];
145    filterBarFrame.size.width = [fWindow frame].size.width;
146    [fFilterBar setFrame: filterBarFrame];
147   
148    [contentView addSubview: fFilterBar];
149    [fFilterBar setFrameOrigin: NSMakePoint(0, [contentView frame].origin.y + [contentView frame].size.height)];
150   
151    [self showFilterBar: [fDefaults boolForKey: @"FilterBar"] animate: NO];
152   
153    //set up status bar
154    fStatusBarVisible = NO;
155    [fStatusBar setHidden: YES];
156   
157    NSRect statusBarFrame = [fStatusBar frame];
158    statusBarFrame.size.width = [fWindow frame].size.width;
159    [fStatusBar setFrame: statusBarFrame];
160   
161    [contentView addSubview: fStatusBar];
162    [fStatusBar setFrameOrigin: NSMakePoint(0, [contentView frame].origin.y + [contentView frame].size.height)];
163    [self showStatusBar: [fDefaults boolForKey: @"StatusBar"] animate: NO];
164   
165    //set speed limit
166    fSpeedLimitNormalImage = [fSpeedLimitButton image];
167    fSpeedLimitBlueImage = [NSImage imageNamed: @"SpeedLimitButtonBlue.png"];
168    fSpeedLimitGraphiteImage = [NSImage imageNamed: @"SpeedLimitButtonGraphite.png"];
169   
170    [self updateControlTint: nil];
171   
172    if ((fSpeedLimitEnabled = [fDefaults boolForKey: @"SpeedLimit"]))
173    {
174        [fSpeedLimitItem setState: NSOnState];
175        [fSpeedLimitDockItem setState: NSOnState];
176       
177        [fSpeedLimitButton setImage: [NSColor currentControlTint] == NSBlueControlTint
178                                        ? fSpeedLimitBlueImage : fSpeedLimitGraphiteImage];
179    }
180
181    [fActionButton setToolTip: @"Shortcuts for changing global settings."];
182    [fSpeedLimitButton setToolTip: @"Speed Limit overrides the total bandwidth limits with its own limits."];
183
184    [fTableView setTorrents: fFilteredTorrents];
185    [[fTableView tableColumnWithIdentifier: @"Torrent"] setDataCell: [[TorrentCell alloc] init]];
186
187    [fTableView registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType,
188                                                        TORRENT_TABLE_VIEW_DATA_TYPE, nil]];
189
190    //register for sleep notifications
191    IONotificationPortRef notify;
192    io_object_t iterator;
193    if (fRootPort = IORegisterForSystemPower(self, & notify, sleepCallBack, & iterator))
194        CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify),
195                            kCFRunLoopCommonModes);
196    else
197        NSLog(@"Could not IORegisterForSystemPower");
198
199    //load torrents from history
200    Torrent * torrent;
201    NSDictionary * historyItem;
202    NSEnumerator * enumerator = [[fDefaults arrayForKey: @"History"] objectEnumerator];
203    while ((historyItem = [enumerator nextObject]))
204        if ((torrent = [[Torrent alloc] initWithHistory: historyItem lib: fLib]))
205        {
206            [fTorrents addObject: torrent];
207            [torrent release];
208        }
209   
210    [self torrentNumberChanged];
211   
212    //set sort
213    fSortType = [[fDefaults stringForKey: @"Sort"] retain];
214   
215    NSMenuItem * currentSortItem, * currentSortActionItem;
216    if ([fSortType isEqualToString: @"Name"])
217    {
218        currentSortItem = fNameSortItem;
219        currentSortActionItem = fNameSortActionItem;
220    }
221    else if ([fSortType isEqualToString: @"State"])
222    {
223        currentSortItem = fStateSortItem;
224        currentSortActionItem = fStateSortActionItem;
225    }
226    else if ([fSortType isEqualToString: @"Progress"])
227    {
228        currentSortItem = fProgressSortItem;
229        currentSortActionItem = fProgressSortActionItem;
230    }
231    else if ([fSortType isEqualToString: @"Date"])
232    {
233        currentSortItem = fDateSortItem;
234        currentSortActionItem = fDateSortActionItem;
235    }
236    else
237    {
238        currentSortItem = fOrderSortItem;
239        currentSortActionItem = fOrderSortActionItem;
240    }
241    [currentSortItem setState: NSOnState];
242    [currentSortActionItem setState: NSOnState];
243   
244    //set filter
245    fFilterType = [[fDefaults stringForKey: @"Filter"] retain];
246
247    //button width should be 9 pixels surrounding text length
248    [fNoFilterButton setText: @"All"]; //16.64
249    [fDownloadFilterButton setText: @"Downloading"]; //81.69
250    [fSeedFilterButton setText: @"Seeding"]; //48.57
251    [fPauseFilterButton setText: @"Paused"]; //44.06
252
253    BarButton * currentFilterButton;
254    if ([fFilterType isEqualToString: @"Pause"])
255        currentFilterButton = fPauseFilterButton;
256    else if ([fFilterType isEqualToString: @"Seed"])
257        currentFilterButton = fSeedFilterButton;
258    else if ([fFilterType isEqualToString: @"Download"])
259        currentFilterButton = fDownloadFilterButton;
260    else
261        currentFilterButton = fNoFilterButton;
262
263    [currentFilterButton setEnabled: YES];
264   
265    //set upload limit action button
266    [fUploadLimitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
267                    [fDefaults integerForKey: @"UploadLimit"]]];
268    if ([fDefaults boolForKey: @"CheckUpload"])
269        [fUploadLimitItem setState: NSOnState];
270    else
271        [fUploadNoLimitItem setState: NSOnState];
272
273        //set download limit action menu
274    [fDownloadLimitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
275                    [fDefaults integerForKey: @"DownloadLimit"]]];
276    if ([fDefaults boolForKey: @"CheckDownload"])
277        [fDownloadLimitItem setState: NSOnState];
278    else
279        [fDownloadNoLimitItem setState: NSOnState];
280   
281    //set ratio action menu
282    [fRatioSetItem setTitle: [NSString stringWithFormat: @"Stop at Ratio (%.2f)",
283                                [fDefaults floatForKey: @"RatioLimit"]]];
284    if ([fDefaults boolForKey: @"RatioCheck"])
285        [fRatioSetItem setState: NSOnState];
286    else
287        [fRatioNotSetItem setState: NSOnState];
288   
289    //observe notifications
290    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
291   
292    [nc addObserver: self selector: @selector(updateControlTint:)
293                    name: NSControlTintDidChangeNotification object: nil];
294   
295    [nc addObserver: self selector: @selector(prepareForUpdate:)
296                    name: SUUpdaterWillRestartNotification object: nil];
297    fUpdateInProgress = NO;
298   
299    [nc addObserver: self selector: @selector(limitGlobalChange:)
300                    name: @"LimitGlobalChange" object: nil];
301   
302    [nc addObserver: self selector: @selector(ratioGlobalChange:)
303                    name: @"RatioGlobalChange" object: nil];
304   
305    [nc addObserver: self selector: @selector(autoImportChange:)
306                    name: @"AutoImportSettingChange" object: nil];
307   
308    [nc addObserver: self selector: @selector(setWindowSizeToFit)
309                    name: @"AutoSizeSettingChange" object: nil];
310   
311    //check to start another because of stopped torrent
312    [nc addObserver: self selector: @selector(checkWaitingForStopped:)
313                    name: @"StoppedDownloading" object: nil];
314   
315    //check all torrents for starting
316    [nc addObserver: self selector: @selector(globalStartSettingChange:)
317                    name: @"GlobalStartSettingChange" object: nil];
318
319    //check if torrent should now start
320    [nc addObserver: self selector: @selector(torrentStartSettingChange:)
321                    name: @"TorrentStartSettingChange" object: nil];
322   
323    //check if torrent should now start
324    [nc addObserver: self selector: @selector(torrentStoppedForRatio:)
325                    name: @"TorrentStoppedForRatio" object: nil];
326   
327    //change that just impacts the inspector
328    [nc addObserver: self selector: @selector(reloadInspectorSettings:)
329                    name: @"TorrentSettingChange" object: nil];
330
331    //timer to update the interface every second
332    fCompleted = 0;
333    [self updateUI: nil];
334    fTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self
335        selector: @selector(updateUI:) userInfo: nil repeats: YES];
336    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSModalPanelRunLoopMode];
337    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSEventTrackingRunLoopMode];
338   
339    //timer for auto import, will check every 15 seconds
340    fAutoImportedNames = [[NSMutableArray alloc] init];
341   
342    [self applyFilter: nil];
343   
344    [fWindow makeKeyAndOrderFront: nil];
345
346    //load info for no torrents
347    [fInfoController updateInfoForTorrents: [NSArray array]];
348    if ([fDefaults boolForKey: @"InfoVisible"])
349        [self showInfo: nil];
350   
351    //must do after everything else is set up
352    [self checkAutoImportDirectory: nil];
353    fAutoImportTimer = [NSTimer scheduledTimerWithTimeInterval: 15.0 target: self
354        selector: @selector(checkAutoImportDirectory:) userInfo: nil repeats: YES];
355    [[NSRunLoop currentRunLoop] addTimer: fAutoImportTimer forMode: NSDefaultRunLoopMode];
356}
357
358- (BOOL) applicationShouldHandleReopen: (NSApplication *) app hasVisibleWindows: (BOOL) visibleWindows
359{
360    if (![fWindow isVisible] && ![[fPrefsController window] isVisible])
361        [fWindow makeKeyAndOrderFront: nil];
362    return NO;
363}
364
365- (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication *) sender
366{
367    if (!fUpdateInProgress && [fDefaults boolForKey: @"CheckQuit"])
368    {
369        int active = 0, downloading = 0;
370        Torrent * torrent;
371        NSEnumerator * enumerator = [fTorrents objectEnumerator];
372        while ((torrent = [enumerator nextObject]))
373            if ([torrent isActive])
374            {
375                active++;
376                if (![torrent isSeeding])
377                    downloading++;
378            }
379
380        if ([fDefaults boolForKey: @"CheckQuitDownloading"] ? downloading > 0 : active > 0)
381        {
382            NSString * message = active == 1
383                ? @"There is an active transfer. Do you really want to quit?"
384                : [NSString stringWithFormat:
385                    @"There are %d active transfers. Do you really want to quit?", active];
386
387            NSBeginAlertSheet(@"Confirm Quit", @"Quit", @"Cancel", nil, fWindow, self,
388                                @selector(quitSheetDidEnd:returnCode:contextInfo:), nil, nil, message);
389            return NSTerminateLater;
390        }
391    }
392
393    return NSTerminateNow;
394}
395
396- (void) quitSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo
397{
398    [NSApp replyToApplicationShouldTerminate: returnCode == NSAlertDefaultReturn];
399}
400
401- (void) applicationWillTerminate: (NSNotification *) notification
402{
403    //stop timers
404    [fAutoImportTimer invalidate];
405    [fTimer invalidate];
406   
407    //save history and stop running torrents
408    [self updateTorrentHistory];
409    [fTorrents makeObjectsPerformSelector: @selector(stopTransferForQuit)];
410   
411    //remember window states and close all windows
412    [fDefaults setBool: [[fInfoController window] isVisible] forKey: @"InfoVisible"];
413    [[NSApp windows] makeObjectsPerformSelector: @selector(close)];
414    [self showStatusBar: NO animate: NO];
415    [self showFilterBar: NO animate: NO];
416   
417    //clear badge
418    [fBadger clearBadge];
419
420    //end quickly if the app is updating
421    if (fUpdateInProgress)
422        return;
423
424    //wait for running transfers to stop (5 second timeout)
425    NSDate * start = [NSDate date];
426    BOOL timeUp = NO;
427   
428    NSEnumerator * enumerator = [fTorrents objectEnumerator];
429    Torrent * torrent;
430    while (!timeUp && (torrent = [enumerator nextObject]))
431        while (![torrent isPaused] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
432        {
433            usleep(100000);
434            [torrent update];
435        }
436}
437
438- (void) folderChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code
439    contextInfo: (Torrent *) torrent
440{
441    if (code == NSOKButton)
442    {
443        [torrent setDownloadFolder: [[openPanel filenames] objectAtIndex: 0]];
444        [torrent update];
445        [self attemptToStartAuto: torrent];
446       
447        [fTorrents addObject: torrent];
448    }
449   
450    [NSApp stopModal];
451}
452
453- (void) application: (NSApplication *) sender openFiles: (NSArray *) filenames
454{
455    NSString * downloadChoice = [fDefaults stringForKey: @"DownloadChoice"], * torrentPath;
456    Torrent * torrent;
457    NSEnumerator * enumerator = [filenames objectEnumerator];
458    while ((torrentPath = [enumerator nextObject]))
459    {
460        if (!(torrent = [[Torrent alloc] initWithPath: torrentPath lib: fLib]))
461            continue;
462
463        //add it to the "File > Open Recent" menu
464        [[NSDocumentController sharedDocumentController]
465            noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
466
467        if ([downloadChoice isEqualToString: @"Ask"])
468        {
469            NSOpenPanel * panel = [NSOpenPanel openPanel];
470
471            [panel setPrompt: @"Select Download Folder"];
472            [panel setAllowsMultipleSelection: NO];
473            [panel setCanChooseFiles: NO];
474            [panel setCanChooseDirectories: YES];
475
476            [panel setMessage: [NSString stringWithFormat: @"Select the download folder for \"%@\"", [torrent name]]];
477
478            [panel beginSheetForDirectory: nil file: nil types: nil
479                modalForWindow: fWindow modalDelegate: self didEndSelector:
480                @selector( folderChoiceClosed:returnCode:contextInfo: )
481                contextInfo: torrent];
482            [NSApp runModalForWindow: panel];
483        }
484        else
485        {
486            NSString * folder = [downloadChoice isEqualToString: @"Constant"]
487                ? [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath]
488                : [torrentPath stringByDeletingLastPathComponent];
489
490            [torrent setDownloadFolder: folder];
491            [torrent update];
492            [self attemptToStartAuto: torrent];
493           
494            [fTorrents addObject: torrent];
495        }
496       
497        [torrent release];
498    }
499
500    [self torrentNumberChanged];
501
502    [self updateUI: nil];
503    [self applyFilter: nil];
504   
505    [self updateTorrentHistory];
506}
507
508- (NSArray *) torrentsAtIndexes: (NSIndexSet *) indexSet
509{
510    if ([fFilteredTorrents respondsToSelector: @selector(objectsAtIndexes:)])
511        return [fFilteredTorrents objectsAtIndexes: indexSet];
512    else
513    {
514        NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [indexSet count]];
515        unsigned int i;
516        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
517            [torrents addObject: [fFilteredTorrents objectAtIndex: i]];
518
519        return torrents;
520    }
521}
522
523- (void) torrentNumberChanged
524{
525    int count = [fTorrents count];
526    [fTotalTorrentsField setStringValue: [NSString stringWithFormat:
527        @"%d Transfer%s", count, count == 1 ? "" : "s"]];
528}
529
530//called on by applescript
531- (void) open: (NSArray *) files
532{
533    [self performSelectorOnMainThread: @selector(openFromSheet:) withObject: files waitUntilDone: NO];
534}
535
536- (void) openShowSheet: (id) sender
537{
538    NSOpenPanel * panel = [NSOpenPanel openPanel];
539
540    [panel setAllowsMultipleSelection: YES];
541    [panel setCanChooseFiles: YES];
542    [panel setCanChooseDirectories: NO];
543
544    [panel beginSheetForDirectory: nil file: nil types: [NSArray arrayWithObject: @"torrent"]
545        modalForWindow: fWindow modalDelegate: self didEndSelector:
546        @selector(openSheetClosed:returnCode:contextInfo:) contextInfo: nil];
547}
548
549- (void) openSheetClosed: (NSOpenPanel *) panel returnCode: (int) code contextInfo: (void *) info
550{
551    if (code == NSOKButton)
552        [self performSelectorOnMainThread: @selector(openFromSheet:)
553                    withObject: [panel filenames] waitUntilDone: NO];
554}
555
556- (void) openFromSheet: (NSArray *) filenames
557{
558    [self application: NSApp openFiles: filenames];
559}
560
561- (void) resumeSelectedTorrents: (id) sender
562{
563    [self resumeTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]];
564}
565
566- (void) resumeAllTorrents: (id) sender
567{
568    [self resumeTorrents: fTorrents];
569}
570
571- (void) resumeTorrents: (NSArray *) torrents
572{
573    [torrents makeObjectsPerformSelector: @selector(startTransfer)];
574   
575    [self updateUI: nil];
576    [self applyFilter: nil];
577    [fInfoController updateInfoStatsAndSettings];
578    [self updateTorrentHistory];
579}
580
581- (void) stopSelectedTorrents: (id) sender
582{
583    [self stopTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]];
584}
585
586- (void) stopAllTorrents: (id) sender
587{
588    [self stopTorrents: fTorrents];
589}
590
591- (void) stopTorrents: (NSArray *) torrents
592{
593    //don't want any of these starting then stopping
594    NSEnumerator * enumerator = [torrents objectEnumerator];
595    Torrent * torrent;
596    while ((torrent = [enumerator nextObject]))
597        [torrent setWaitToStart: NO];
598
599    [torrents makeObjectsPerformSelector: @selector(stopTransfer)];
600   
601    [self updateUI: nil];
602    [self applyFilter: nil];
603    [fInfoController updateInfoStatsAndSettings];
604    [self updateTorrentHistory];
605}
606
607- (void) removeTorrents: (NSArray *) torrents
608        deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
609{
610    [torrents retain];
611    int active = 0, downloading = 0;
612
613    if ([fDefaults boolForKey: @"CheckRemove"])
614    {
615        Torrent * torrent;
616        NSEnumerator * enumerator = [torrents objectEnumerator];
617        while ((torrent = [enumerator nextObject]))
618            if ([torrent isActive])
619            {
620                active++;
621                if (![torrent isSeeding])
622                    downloading++;
623            }
624
625        if ([fDefaults boolForKey: @"CheckRemoveDownloading"] ? downloading > 0 : active > 0)
626        {
627            NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
628                torrents, @"Torrents",
629                [NSNumber numberWithBool: deleteData], @"DeleteData",
630                [NSNumber numberWithBool: deleteTorrent], @"DeleteTorrent", nil];
631
632            NSString * title, * message;
633           
634            int selected = [fTableView numberOfSelectedRows];
635            if (selected == 1)
636            {
637                title = [NSString stringWithFormat: @"Comfirm Removal of \"%@\"",
638                            [[fFilteredTorrents objectAtIndex: [fTableView selectedRow]] name]];
639                message = @"This transfer is active."
640                            " Once removed, continuing the transfer will require the torrent file."
641                            " Do you really want to remove it?";
642            }
643            else
644            {
645                title = [NSString stringWithFormat: @"Comfirm Removal of %d Transfers", selected];
646                if (selected == active)
647                    message = [NSString stringWithFormat:
648                        @"There are %d active transfers.", active];
649                else
650                    message = [NSString stringWithFormat:
651                        @"There are %d transfers (%d active).", selected, active];
652                message = [message stringByAppendingString:
653                    @" Once removed, continuing the transfers will require the torrent files."
654                    " Do you really want to remove them?"];
655            }
656
657            NSBeginAlertSheet(title, @"Remove", @"Cancel", nil, fWindow, self,
658                nil, @selector(removeSheetDidEnd:returnCode:contextInfo:), dict, message);
659            return;
660        }
661    }
662   
663    [self confirmRemoveTorrents: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
664}
665
666- (void) removeSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (NSDictionary *) dict
667{
668    [NSApp stopModal];
669
670    NSArray * torrents = [dict objectForKey: @"Torrents"];
671    BOOL deleteData = [[dict objectForKey: @"DeleteData"] boolValue],
672        deleteTorrent = [[dict objectForKey: @"DeleteTorrent"] boolValue];
673    [dict release];
674   
675    if (returnCode == NSAlertDefaultReturn)
676        [self confirmRemoveTorrents: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
677    else
678        [torrents release];
679}
680
681- (void) confirmRemoveTorrents: (NSArray *) torrents deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
682{
683    //don't want any of these starting then stopping
684    NSEnumerator * enumerator = [torrents objectEnumerator];
685    Torrent * torrent;
686    while ((torrent = [enumerator nextObject]))
687        [torrent setWaitToStart: NO];
688
689    NSNumber * lowestOrderValue = [NSNumber numberWithInt: [torrents count]], * currentOrderValue;
690
691    enumerator = [torrents objectEnumerator];
692    while ((torrent = [enumerator nextObject]))
693    {
694        [torrent stopTransfer];
695
696        if (deleteData)
697            [torrent trashData];
698        if (deleteTorrent)
699            [torrent trashTorrent];
700       
701        //determine lowest order value
702        currentOrderValue = [torrent orderValue];
703        if ([lowestOrderValue compare: currentOrderValue] == NSOrderedDescending)
704            lowestOrderValue = currentOrderValue;
705
706        [torrent removeForever];
707       
708        [fFilteredTorrents removeObject: torrent];
709        [fTorrents removeObject: torrent];
710    }
711    [torrents release];
712
713    //reset the order values if necessary
714    if ([lowestOrderValue intValue] < [fTorrents count])
715    {
716        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
717                                                @"orderValue" ascending: YES] autorelease];
718        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
719
720        NSArray * tempTorrents = [fTorrents sortedArrayUsingDescriptors: descriptors];
721        [descriptors release];
722
723        int i;
724        for (i = [lowestOrderValue intValue]; i < [tempTorrents count]; i++)
725            [[tempTorrents objectAtIndex: i] setOrderValue: i];
726    }
727   
728    [self torrentNumberChanged];
729   
730    [self updateUI: nil];
731    [self setWindowSizeToFit];
732   
733    [fTableView deselectAll: nil];
734   
735    [self updateTorrentHistory];
736}
737
738- (void) removeNoDelete: (id) sender
739{
740    [self removeTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]
741                deleteData: NO deleteTorrent: NO];
742}
743
744- (void) removeDeleteData: (id) sender
745{
746    [self removeTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]
747                deleteData: YES deleteTorrent: NO];
748}
749
750- (void) removeDeleteTorrent: (id) sender
751{
752    [self removeTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]
753                deleteData: NO deleteTorrent: YES];
754}
755
756- (void) removeDeleteDataAndTorrent: (id) sender
757{
758    [self removeTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]
759                deleteData: YES deleteTorrent: YES];
760}
761
762- (void) copyTorrentFile: (id) sender
763{
764    [self copyTorrentFileForTorrents: [[NSMutableArray alloc] initWithArray:
765            [self torrentsAtIndexes: [fTableView selectedRowIndexes]]]];
766}
767
768- (void) copyTorrentFileForTorrents: (NSMutableArray *) torrents
769{
770    if ([torrents count] == 0)
771    {
772        [torrents release];
773        return;
774    }
775
776    Torrent * torrent = [torrents objectAtIndex: 0];
777
778    //warn user if torrent file can't be found
779    if (![[NSFileManager defaultManager] fileExistsAtPath: [torrent torrentLocation]])
780    {
781        NSAlert * alert = [[NSAlert alloc] init];
782        [alert addButtonWithTitle: @"OK"];
783        [alert setMessageText: [NSString stringWithFormat:
784                @"Copy of \"%@\" Cannot Be Created", [torrent name]]];
785        [alert setInformativeText: [NSString stringWithFormat:
786                @"The torrent file (%@) cannot be found.", [torrent torrentLocation]]];
787        [alert setAlertStyle: NSWarningAlertStyle];
788       
789        [alert runModal];
790       
791        [torrents removeObjectAtIndex: 0];
792        [self copyTorrentFileForTorrents: torrents];
793    }
794    else
795    {
796        NSSavePanel * panel = [NSSavePanel savePanel];
797        [panel setRequiredFileType: @"torrent"];
798        [panel setCanSelectHiddenExtension: YES];
799       
800        [panel beginSheetForDirectory: nil file: [torrent name]
801            modalForWindow: fWindow modalDelegate: self didEndSelector:
802            @selector(saveTorrentCopySheetClosed:returnCode:contextInfo:) contextInfo: torrents];
803    }
804}
805
806- (void) saveTorrentCopySheetClosed: (NSSavePanel *) panel returnCode: (int) code
807    contextInfo: (NSMutableArray *) torrents
808{
809    //if save successful, copy torrent to new location with name of data file
810    if (code == NSOKButton)
811        [[NSFileManager defaultManager] copyPath: [[torrents objectAtIndex: 0] torrentLocation]
812                toPath: [panel filename] handler: nil];
813   
814    [torrents removeObjectAtIndex: 0];
815    [self performSelectorOnMainThread: @selector(copyTorrentFileForTorrents:)
816                withObject: torrents waitUntilDone: NO];
817}
818
819- (void) revealFile: (id) sender
820{
821    NSIndexSet * indexSet = [fTableView selectedRowIndexes];
822    unsigned int i;
823   
824    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
825        [[fFilteredTorrents objectAtIndex: i] revealData];
826}
827
828- (void) showPreferenceWindow: (id) sender
829{
830    NSWindow * window = [fPrefsController window];
831    if (![window isVisible])
832        [window center];
833
834    [window makeKeyAndOrderFront: nil];
835}
836
837- (void) showInfo: (id) sender
838{
839    if ([[fInfoController window] isVisible])
840        [fInfoController close];
841    else
842    {
843        [fInfoController updateInfoStats];
844        [[fInfoController window] orderFront: nil];
845    }
846}
847
848- (void) setInfoTab: (id) sender
849{
850    if (sender == fNextInfoTabItem)
851        [fInfoController setNextTab];
852    else
853        [fInfoController setPreviousTab];
854}
855
856- (void) updateControlTint: (NSNotification *) notification
857{
858    if (fSpeedLimitEnabled)
859        [fSpeedLimitButton setImage: [NSColor currentControlTint] == NSBlueControlTint
860                                        ? fSpeedLimitBlueImage : fSpeedLimitGraphiteImage];
861}
862
863- (void) updateUI: (NSTimer *) t
864{
865    NSEnumerator * enumerator = [fTorrents objectEnumerator];
866    Torrent * torrent;
867    while ((torrent = [enumerator nextObject]))
868    {
869        [torrent update];
870
871        if ([torrent justFinished])
872        {
873            [self applyFilter: nil];
874            [self checkToStartWaiting: torrent];
875       
876            [GrowlApplicationBridge notifyWithTitle: @"Download Complete"
877                description: [torrent name] notificationName: @"Download Complete" iconData: nil
878                priority: 0 isSticky: NO clickContext: nil];
879
880            if (![fWindow isKeyWindow])
881                fCompleted++;
882        }
883    }
884
885    if ([fSortType isEqualToString: @"Progress"] || [fSortType isEqualToString: @"State"])
886        [self sortTorrents];
887    else
888        [fTableView reloadData];
889   
890    //update the global DL/UL rates
891    float downloadRate, uploadRate;
892    tr_torrentRates(fLib, & downloadRate, & uploadRate);
893    if (fStatusBarVisible)
894    {
895        [fTotalDLField setStringValue: [@"Total DL: " stringByAppendingString: [NSString stringForSpeed: downloadRate]]];
896        [fTotalULField setStringValue: [@"Total UL: " stringByAppendingString: [NSString stringForSpeed: uploadRate]]];
897    }
898
899    if ([[fInfoController window] isVisible])
900        [fInfoController updateInfoStats];
901
902    //badge dock
903    [fBadger updateBadgeWithCompleted: fCompleted uploadRate: uploadRate downloadRate: downloadRate];
904}
905
906- (void) updateTorrentHistory
907{
908    NSMutableArray * history = [NSMutableArray arrayWithCapacity: [fTorrents count]];
909
910    NSEnumerator * enumerator = [fTorrents objectEnumerator];
911    Torrent * torrent;
912    while ((torrent = [enumerator nextObject]))
913        [history addObject: [torrent history]];
914
915    [fDefaults setObject: history forKey: @"History"];
916    [fDefaults synchronize];
917}
918
919- (void) sortTorrents
920{
921    //remember selected rows if needed
922    NSArray * selectedTorrents = nil;
923    int numSelected = [fTableView numberOfSelectedRows];
924    if (numSelected > 0 && numSelected < [fFilteredTorrents count])
925        selectedTorrents = [self torrentsAtIndexes: [fTableView selectedRowIndexes]];
926
927    [self sortTorrentsIgnoreSelected]; //actually sort
928   
929    //set selected rows if needed
930    if (selectedTorrents)
931    {
932        Torrent * torrent;
933        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
934        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
935        while ((torrent = [enumerator nextObject]))
936            [indexSet addIndex: [fFilteredTorrents indexOfObject: torrent]];
937       
938        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
939        [indexSet release];
940    }
941}
942
943//doesn't remember selected rows
944- (void) sortTorrentsIgnoreSelected
945{
946    NSSortDescriptor * nameDescriptor = [[[NSSortDescriptor alloc] initWithKey:
947                                            @"name" ascending: YES] autorelease],
948                    * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
949                                            @"orderValue" ascending: YES] autorelease];
950
951    NSArray * descriptors;
952    if ([fSortType isEqualToString: @"Name"])
953        descriptors = [[NSArray alloc] initWithObjects: nameDescriptor, orderDescriptor, nil];
954    else if ([fSortType isEqualToString: @"State"])
955    {
956        NSSortDescriptor * stateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
957                                                @"stateSortKey" ascending: NO] autorelease],
958                        * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
959                                            @"progressSortKey" ascending: NO] autorelease];
960       
961        descriptors = [[NSArray alloc] initWithObjects: stateDescriptor, progressDescriptor,
962                                                            nameDescriptor, orderDescriptor, nil];
963    }
964    else if ([fSortType isEqualToString: @"Progress"])
965    {
966        NSSortDescriptor * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
967                                            @"progressSortKey" ascending: YES] autorelease];
968       
969        descriptors = [[NSArray alloc] initWithObjects: progressDescriptor, nameDescriptor, orderDescriptor, nil];
970    }
971    else if ([fSortType isEqualToString: @"Date"])
972    {
973        NSSortDescriptor * dateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
974                                            @"date" ascending: YES] autorelease];
975   
976        descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, orderDescriptor, nil];
977    }
978    else
979        descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
980
981    [fFilteredTorrents sortUsingDescriptors: descriptors];
982    [descriptors release];
983   
984    [fTableView reloadData];
985}
986
987- (void) setSort: (id) sender
988{
989    //get checked items
990    NSMenuItem * prevSortItem, * prevSortActionItem;
991    if ([fSortType isEqualToString: @"Name"])
992    {
993        prevSortItem = fNameSortItem;
994        prevSortActionItem = fNameSortActionItem;
995    }
996    else if ([fSortType isEqualToString: @"State"])
997    {
998        prevSortItem = fStateSortItem;
999        prevSortActionItem = fStateSortActionItem;
1000    }
1001    else if ([fSortType isEqualToString: @"Progress"])
1002    {
1003        prevSortItem = fProgressSortItem;
1004        prevSortActionItem = fProgressSortActionItem;
1005    }
1006    else if ([fSortType isEqualToString: @"Date"])
1007    {
1008        prevSortItem = fDateSortItem;
1009        prevSortActionItem = fDateSortActionItem;
1010    }
1011    else
1012    {
1013        prevSortItem = fOrderSortItem;
1014        prevSortActionItem = fOrderSortActionItem;
1015    }
1016   
1017    if (sender != prevSortItem && sender != prevSortActionItem)
1018    {
1019        [fSortType release];
1020       
1021        //get new items to check
1022        NSMenuItem * currentSortItem, * currentSortActionItem;
1023        if (sender == fNameSortItem || sender == fNameSortActionItem)
1024        {
1025            currentSortItem = fNameSortItem;
1026            currentSortActionItem = fNameSortActionItem;
1027            fSortType = [[NSString alloc] initWithString: @"Name"];
1028        }
1029        else if (sender == fStateSortItem || sender == fStateSortActionItem)
1030        {
1031            currentSortItem = fStateSortItem;
1032            currentSortActionItem = fStateSortActionItem;
1033            fSortType = [[NSString alloc] initWithString: @"State"];
1034        }
1035        else if (sender == fProgressSortItem || sender == fProgressSortActionItem)
1036        {
1037            currentSortItem = fProgressSortItem;
1038            currentSortActionItem = fProgressSortActionItem;
1039            fSortType = [[NSString alloc] initWithString: @"Progress"];
1040        }
1041        else if (sender == fDateSortItem || sender == fDateSortActionItem)
1042        {
1043            currentSortItem = fDateSortItem;
1044            currentSortActionItem = fDateSortActionItem;
1045            fSortType = [[NSString alloc] initWithString: @"Date"];
1046        }
1047        else
1048        {
1049            currentSortItem = fOrderSortItem;
1050            currentSortActionItem = fOrderSortActionItem;
1051            fSortType = [[NSString alloc] initWithString: @"Order"];
1052        }
1053   
1054        [prevSortItem setState: NSOffState];
1055        [prevSortActionItem setState: NSOffState];
1056        [currentSortItem setState: NSOnState];
1057        [currentSortActionItem setState: NSOnState];
1058       
1059        [fDefaults setObject: fSortType forKey: @"Sort"];
1060    }
1061
1062    [self sortTorrents];
1063}
1064
1065- (void) applyFilter: (id) sender
1066{
1067    //remember selected rows if needed
1068    NSArray * selectedTorrents = [fTableView numberOfSelectedRows] > 0
1069                ? [self torrentsAtIndexes: [fTableView selectedRowIndexes]] : nil;
1070
1071    NSMutableArray * tempTorrents = [[NSMutableArray alloc] initWithCapacity: [fTorrents count]];
1072
1073    if ([fFilterType isEqualToString: @"Pause"])
1074    {
1075        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1076        Torrent * torrent;
1077        while ((torrent = [enumerator nextObject]))
1078            if (![torrent isActive])
1079                [tempTorrents addObject: torrent];
1080    }
1081    else if ([fFilterType isEqualToString: @"Seed"])
1082    {
1083        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1084        Torrent * torrent;
1085        while ((torrent = [enumerator nextObject]))
1086            if ([torrent isActive] && [torrent progress] >= 1.0)
1087                [tempTorrents addObject: torrent];
1088    }
1089    else if ([fFilterType isEqualToString: @"Download"])
1090    {
1091        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1092        Torrent * torrent;
1093        while ((torrent = [enumerator nextObject]))
1094            if ([torrent isActive] && [torrent progress] < 1.0)
1095                [tempTorrents addObject: torrent];
1096    }
1097    else
1098        [tempTorrents setArray: fTorrents];
1099   
1100    NSString * searchString = [fSearchFilterField stringValue];
1101    if (![searchString isEqualToString: @""])
1102    {
1103        int i;
1104        for (i = [tempTorrents count] - 1; i >= 0; i--)
1105            if ([[[tempTorrents objectAtIndex: i] name] rangeOfString: searchString
1106                                        options: NSCaseInsensitiveSearch].location == NSNotFound)
1107                [tempTorrents removeObjectAtIndex: i];
1108    }
1109   
1110    [fFilteredTorrents setArray: tempTorrents];
1111    [tempTorrents release];
1112   
1113    [self sortTorrentsIgnoreSelected];
1114   
1115    //set selected rows if needed
1116    if (selectedTorrents)
1117    {
1118        Torrent * torrent;
1119        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1120        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1121        unsigned index;
1122        while ((torrent = [enumerator nextObject]))
1123            if ((index = [fFilteredTorrents indexOfObject: torrent]) != NSNotFound)
1124                [indexSet addIndex: index];
1125       
1126        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1127        [indexSet release];
1128    }
1129   
1130    [self setWindowSizeToFit];
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 if not set to auto size
1594    if (!makeSmall && ![fDefaults boolForKey: @"AutoSize"] && 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    //disable filtering when hiding
1665    if (fFilterBarVisible)
1666    {
1667        [fSearchFilterField setStringValue: @""];
1668        [self setFilter: fNoFilterButton];
1669    }
1670
1671    [self showFilterBar: !fFilterBarVisible animate: YES];
1672    [fDefaults setBool: fFilterBarVisible forKey: @"FilterBar"];
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 : 468.0;
2119    return frame;
2120}
2121
2122- (void) setWindowSizeToFit
2123{
2124    if ([fDefaults boolForKey: @"AutoSize"])
2125        [fWindow setFrame: [self windowFrameForAmount: [fFilteredTorrents count]] display: YES animate: YES];
2126}
2127
2128- (NSRect) windowFrameForAmount: (int) amount
2129{
2130    NSRect frame = [fWindow frame];
2131    float newHeight = frame.size.height - [fScrollView frame].size.height
2132        + amount * ([fTableView rowHeight] + [fTableView intercellSpacing].height);
2133
2134    float minHeight = [fWindow minSize].height;
2135    if (newHeight < minHeight)
2136        newHeight = minHeight;
2137
2138    frame.origin.y -= (newHeight - frame.size.height);
2139    frame.size.height = newHeight;
2140   
2141    return frame;
2142}
2143
2144- (void) showMainWindow: (id) sender
2145{
2146    [fWindow makeKeyAndOrderFront: nil];
2147}
2148
2149- (void) windowDidBecomeKey: (NSNotification *) notification
2150{
2151    //reset dock badge for completed
2152    if (fCompleted > 0)
2153    {
2154        fCompleted = 0;
2155        [self updateUI: nil];
2156    }
2157   
2158    //set filter images as active
2159    [fNoFilterButton setForActive];
2160    [fSeedFilterButton setForActive];
2161    [fDownloadFilterButton setForActive];
2162    [fPauseFilterButton setForActive];
2163}
2164
2165- (void) windowDidResignKey: (NSNotification *) notification
2166{
2167    //set filter images as inactive
2168    [fNoFilterButton setForInactive];
2169    [fSeedFilterButton setForInactive];
2170    [fDownloadFilterButton setForInactive];
2171    [fPauseFilterButton setForInactive];
2172}
2173
2174- (NSSize) windowWillResize: (NSWindow *) sender toSize: (NSSize) proposedFrameSize
2175{
2176    if ([fDefaults boolForKey: @"AutoSize"])
2177        proposedFrameSize.height = [fWindow frame].size.height;
2178    return proposedFrameSize;
2179}
2180
2181- (void) windowDidResize: (NSNotification *) notification
2182{
2183    //hide search filter if it overlaps filter buttons
2184    NSRect buttonFrame = [fPauseFilterButton frame];
2185    if (buttonFrame.origin.x + buttonFrame.size.width + 2.0 > [fSearchFilterField frame].origin.x)
2186    {
2187        if (![fSearchFilterField isHidden])
2188            [fSearchFilterField setHidden: YES];
2189    }
2190    else
2191    {
2192        if ([fSearchFilterField isHidden])
2193            [fSearchFilterField setHidden: NO];
2194    }
2195}
2196
2197- (void) linkHomepage: (id) sender
2198{
2199    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
2200}
2201
2202- (void) linkForums: (id) sender
2203{
2204    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
2205}
2206
2207- (void) checkUpdate: (id) sender
2208{
2209    [fPrefsController checkUpdate];
2210}
2211
2212- (void) prepareForUpdate: (NSNotification *) notification
2213{
2214    fUpdateInProgress = YES;
2215}
2216
2217- (NSDictionary *) registrationDictionaryForGrowl
2218{
2219    NSArray * notifications = [NSArray arrayWithObjects: @"Download Complete",
2220                                @"Seeding Complete", @"Torrent Auto Added", nil];
2221    return [NSDictionary dictionaryWithObjectsAndKeys: notifications, GROWL_NOTIFICATIONS_ALL,
2222                                notifications, GROWL_NOTIFICATIONS_DEFAULT, nil];
2223}
2224
2225@end
Note: See TracBrowser for help on using the repository browser.