source: branches/nat-traversal/macosx/Controller.m @ 884

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

Remove unnecessary code.

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