source: trunk/macosx/Controller.m @ 750

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

remove unneeded retain

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