source: trunk/macosx/Controller.m @ 828

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

Option to save log.

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