source: trunk/macosx/Controller.m @ 703

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

Auto resizing now respects the max possible size. This should help avoid strange resizing behavior.

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