source: trunk/macosx/Controller.m @ 700

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

Capitalization change.

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