source: trunk/macosx/Controller.m @ 729

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

Only show ratio, not upload speed, when seeding in minimal mode and set to display alternate status string.

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