source: trunk/macosx/Controller.m @ 749

Last change on this file since 749 was 749, checked in by livings124, 15 years ago

save history

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