source: trunk/macosx/Controller.m @ 655

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

New prefs setting to keep the window sized perfectly for the current number of transfers.

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