source: trunk/macosx/Controller.m @ 748

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

I hate memory management in objective-c! This should have better behavior when opening multiple files with "always ask" on.

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