source: trunk/macosx/Controller.m @ 686

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

Filter buttons now actually are buttons, meaning their behavior matches more to Mail.app's

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