source: trunk/macosx/Controller.m @ 698

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

Minimal view status toggle now works only when on actual status text, and even if the row isn't already selected.

  • Property svn:keywords set to Date Rev Author Id
File size: 75.3 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 698 2006-07-26 02:42:07Z livings124 $
3 *
4 * Copyright (c) 2005-2006 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import <IOKit/IOMessage.h>
26
27#import "Controller.h"
28#import "Torrent.h"
29#import "TorrentCell.h"
30#import "TorrentTableView.h"
31#import "StringAdditions.h"
32
33#import <Sparkle/Sparkle.h>
34
35#define TOOLBAR_OPEN            @"Toolbar Open"
36#define TOOLBAR_REMOVE          @"Toolbar Remove"
37#define TOOLBAR_INFO            @"Toolbar Info"
38#define TOOLBAR_PAUSE_ALL       @"Toolbar Pause All"
39#define TOOLBAR_RESUME_ALL      @"Toolbar Resume All"
40#define TOOLBAR_PAUSE_SELECTED  @"Toolbar Pause Selected"
41#define TOOLBAR_RESUME_SELECTED @"Toolbar Resume Selected"
42#define TOOLBAR_FILTER          @"Toolbar Toggle Filter"
43
44#define GROWL_DOWNLOAD_COMPLETE @"Download Complete"
45#define GROWL_SEEDING_COMPLETE  @"Seeding Complete"
46#define GROWL_AUTO_ADD          @"Torrent Auto Added"
47
48#define TORRENT_TABLE_VIEW_DATA_TYPE    @"TorrentTableViewDataType"
49
50#define ROW_HEIGHT_REGULAR  65.0
51#define ROW_HEIGHT_SMALL    40.0
52#define WINDOW_REGULAR_WIDTH       468.0
53
54#define WEBSITE_URL @"http://transmission.m0k.org/"
55#define FORUM_URL   @"http://transmission.m0k.org/forum/"
56
57static void sleepCallBack(void * controller, io_service_t y, natural_t messageType, void * messageArgument)
58{
59    Controller * c = controller;
60    [c sleepCallBack: messageType argument: messageArgument];
61}
62
63
64@implementation Controller
65
66- (id) init
67{
68    if ((self = [super init]))
69    {
70        fLib = tr_init();
71       
72        fTorrents = [[NSMutableArray alloc] initWithCapacity: 10];
73        fFilteredTorrents = [[NSMutableArray alloc] initWithCapacity: 10];
74       
75        fDefaults = [NSUserDefaults standardUserDefaults];
76        fInfoController = [[InfoWindowController alloc] initWithWindowNibName: @"InfoWindow"];
77        fPrefsController = [[PrefsController alloc] initWithWindowNibName: @"PrefsWindow"];
78        fBadger = [[Badger alloc] init];
79       
80        [GrowlApplicationBridge setGrowlDelegate: self];
81    }
82    return self;
83}
84
85- (void) dealloc
86{
87    [[NSNotificationCenter defaultCenter] removeObserver: self];
88
89    [fTorrents release];
90    [fFilteredTorrents release];
91   
92    [fToolbar release];
93    [fInfoController release];
94    [fPrefsController release];
95    [fBadger release];
96   
97    [fSortType release];
98    [fFilterType release];
99    [fAutoImportedNames release];
100   
101    tr_close(fLib);
102    [super dealloc];
103}
104
105- (void) awakeFromNib
106{
107    [fPrefsController setPrefs: fLib];
108   
109    [fStatusBar setBackgroundImage: [NSImage imageNamed: @"StatusBarBackground.png"]];
110    [fFilterBar setBackgroundImage: [NSImage imageNamed: @"FilterBarBackground.png"]];
111   
112    [fWindow setAcceptsMouseMovedEvents: YES]; //ensure filter buttons display correctly
113   
114    [fAdvancedBarItem setState: [fDefaults boolForKey: @"UseAdvancedBar"]];
115
116    fToolbar = [[NSToolbar alloc] initWithIdentifier: @"Transmission Toolbar"];
117    [fToolbar setDelegate: self];
118    [fToolbar setAllowsUserCustomization: YES];
119    [fToolbar setAutosavesConfiguration: YES];
120    [fWindow setToolbar: fToolbar];
121    [fWindow setDelegate: self];
122   
123    [fWindow makeFirstResponder: fTableView];
124   
125    //set table size
126    if ([fDefaults boolForKey: @"SmallView"])
127    {
128        [fTableView setRowHeight: ROW_HEIGHT_SMALL];
129        [fSmallViewItem setState: NSOnState];
130    }
131   
132    //window min height
133    NSSize contentMinSize = [fWindow contentMinSize];
134    contentMinSize.height = [[fWindow contentView] frame].size.height - [fScrollView frame].size.height
135                                + [fTableView rowHeight] + [fTableView intercellSpacing].height;
136    [fWindow setContentMinSize: contentMinSize];
137   
138    //set info keyboard shortcuts
139    unichar ch = NSRightArrowFunctionKey;
140    [fNextInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & ch length: 1]];
141    ch = NSLeftArrowFunctionKey;
142    [fPrevInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & ch length: 1]];
143   
144    //set up filter bar
145    NSView * contentView = [fWindow contentView];
146    [fFilterBar setHidden: YES];
147   
148    fFilterBarVisible = NO;
149    NSRect filterBarFrame = [fFilterBar frame];
150    filterBarFrame.size.width = [fWindow frame].size.width;
151    [fFilterBar setFrame: filterBarFrame];
152   
153    [contentView addSubview: fFilterBar];
154    [fFilterBar setFrameOrigin: NSMakePoint(0, NSMaxY([contentView frame]))];
155   
156    [self showFilterBar: [fDefaults boolForKey: @"FilterBar"] animate: NO];
157   
158    //set up status bar
159    fStatusBarVisible = NO;
160    [fStatusBar setHidden: YES];
161   
162    NSRect statusBarFrame = [fStatusBar frame];
163    statusBarFrame.size.width = [fWindow frame].size.width;
164    [fStatusBar setFrame: statusBarFrame];
165   
166    [contentView addSubview: fStatusBar];
167    [fStatusBar setFrameOrigin: NSMakePoint(0, NSMaxY([contentView frame]))];
168    [self showStatusBar: [fDefaults boolForKey: @"StatusBar"] animate: NO];
169   
170    //set speed limit
171    fSpeedLimitNormalImage = [fSpeedLimitButton image];
172    fSpeedLimitBlueImage = [NSImage imageNamed: @"SpeedLimitButtonBlue.png"];
173    fSpeedLimitGraphiteImage = [NSImage imageNamed: @"SpeedLimitButtonGraphite.png"];
174   
175    [self updateControlTint: nil];
176   
177    if ((fSpeedLimitEnabled = [fDefaults boolForKey: @"SpeedLimit"]))
178    {
179        [fSpeedLimitItem setState: NSOnState];
180        [fSpeedLimitDockItem setState: NSOnState];
181       
182        [fSpeedLimitButton setImage: [NSColor currentControlTint] == NSBlueControlTint
183                                        ? fSpeedLimitBlueImage : fSpeedLimitGraphiteImage];
184    }
185
186    [fActionButton setToolTip: @"Shortcuts for changing global settings."];
187    [fSpeedLimitButton setToolTip: @"Speed Limit overrides the total bandwidth limits with its own limits."];
188
189    [fTableView setTorrents: fFilteredTorrents];
190    [[fTableView tableColumnWithIdentifier: @"Torrent"] setDataCell: [[TorrentCell alloc] init]];
191
192    [fTableView registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType,
193                                                        TORRENT_TABLE_VIEW_DATA_TYPE, nil]];
194
195    //register for sleep notifications
196    IONotificationPortRef notify;
197    io_object_t iterator;
198    if (fRootPort = IORegisterForSystemPower(self, & notify, sleepCallBack, & iterator))
199        CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify),
200                            kCFRunLoopCommonModes);
201    else
202        NSLog(@"Could not IORegisterForSystemPower");
203
204    //load torrents from history
205    Torrent * torrent;
206    NSDictionary * historyItem;
207    NSEnumerator * enumerator = [[fDefaults arrayForKey: @"History"] objectEnumerator];
208    while ((historyItem = [enumerator nextObject]))
209        if ((torrent = [[Torrent alloc] initWithHistory: historyItem lib: fLib]))
210        {
211            [fTorrents addObject: torrent];
212            [torrent release];
213        }
214   
215    //set sort
216    fSortType = [[fDefaults stringForKey: @"Sort"] retain];
217   
218    NSMenuItem * currentSortItem, * currentSortActionItem;
219    if ([fSortType isEqualToString: @"Name"])
220    {
221        currentSortItem = fNameSortItem;
222        currentSortActionItem = fNameSortActionItem;
223    }
224    else if ([fSortType isEqualToString: @"State"])
225    {
226        currentSortItem = fStateSortItem;
227        currentSortActionItem = fStateSortActionItem;
228    }
229    else if ([fSortType isEqualToString: @"Progress"])
230    {
231        currentSortItem = fProgressSortItem;
232        currentSortActionItem = fProgressSortActionItem;
233    }
234    else if ([fSortType isEqualToString: @"Date"])
235    {
236        currentSortItem = fDateSortItem;
237        currentSortActionItem = fDateSortActionItem;
238    }
239    else
240    {
241        currentSortItem = fOrderSortItem;
242        currentSortActionItem = fOrderSortActionItem;
243    }
244    [currentSortItem setState: NSOnState];
245    [currentSortActionItem setState: NSOnState];
246   
247    //set filter
248    fFilterType = [[fDefaults stringForKey: @"Filter"] retain];
249
250    BarButton * currentFilterButton;
251    if ([fFilterType isEqualToString: @"Pause"])
252        currentFilterButton = fPauseFilterButton;
253    else if ([fFilterType isEqualToString: @"Seed"])
254        currentFilterButton = fSeedFilterButton;
255    else if ([fFilterType isEqualToString: @"Download"])
256        currentFilterButton = fDownloadFilterButton;
257    else
258        currentFilterButton = fNoFilterButton;
259
260    [currentFilterButton setEnabled: YES];
261   
262    //set upload limit action button
263    [fUploadLimitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
264                    [fDefaults integerForKey: @"UploadLimit"]]];
265    if ([fDefaults boolForKey: @"CheckUpload"])
266        [fUploadLimitItem setState: NSOnState];
267    else
268        [fUploadNoLimitItem setState: NSOnState];
269
270        //set download limit action menu
271    [fDownloadLimitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
272                    [fDefaults integerForKey: @"DownloadLimit"]]];
273    if ([fDefaults boolForKey: @"CheckDownload"])
274        [fDownloadLimitItem setState: NSOnState];
275    else
276        [fDownloadNoLimitItem setState: NSOnState];
277   
278    //set ratio action menu
279    [fRatioSetItem setTitle: [NSString stringWithFormat: @"Stop at Ratio (%.2f)",
280                                [fDefaults floatForKey: @"RatioLimit"]]];
281    if ([fDefaults boolForKey: @"RatioCheck"])
282        [fRatioSetItem setState: NSOnState];
283    else
284        [fRatioNotSetItem setState: NSOnState];
285   
286    //observe notifications
287    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
288   
289    [nc addObserver: self selector: @selector(updateControlTint:)
290                    name: NSControlTintDidChangeNotification object: nil];
291   
292    [nc addObserver: self selector: @selector(prepareForUpdate:)
293                    name: SUUpdaterWillRestartNotification object: nil];
294    fUpdateInProgress = NO;
295   
296    [nc addObserver: self selector: @selector(limitGlobalChange:)
297                    name: @"LimitGlobalChange" object: nil];
298   
299    [nc addObserver: self selector: @selector(ratioGlobalChange:)
300                    name: @"RatioGlobalChange" object: nil];
301   
302    [nc addObserver: self selector: @selector(autoImportChange:)
303                    name: @"AutoImportSettingChange" object: nil];
304   
305    [nc addObserver: self selector: @selector(setWindowSizeToFit)
306                    name: @"AutoSizeSettingChange" object: nil];
307   
308    //check to start another because of stopped torrent
309    [nc addObserver: self selector: @selector(checkWaitingForStopped:)
310                    name: @"StoppedDownloading" object: nil];
311   
312    //check all torrents for starting
313    [nc addObserver: self selector: @selector(globalStartSettingChange:)
314                    name: @"GlobalStartSettingChange" object: nil];
315
316    //check if torrent should now start
317    [nc addObserver: self selector: @selector(torrentStartSettingChange:)
318                    name: @"TorrentStartSettingChange" object: nil];
319   
320    //check if torrent should now start
321    [nc addObserver: self selector: @selector(torrentStoppedForRatio:)
322                    name: @"TorrentStoppedForRatio" object: nil];
323   
324    //change that just impacts the inspector
325    [nc addObserver: self selector: @selector(reloadInspectorSettings:)
326                    name: @"TorrentSettingChange" object: nil];
327
328    //timer to update the interface every second
329    fCompleted = 0;
330    [self updateUI: nil];
331    fTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self
332        selector: @selector(updateUI:) userInfo: nil repeats: YES];
333    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSModalPanelRunLoopMode];
334    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSEventTrackingRunLoopMode];
335   
336    [self applyFilter: nil];
337   
338    [fWindow makeKeyAndOrderFront: nil];
339
340    if ([fDefaults boolForKey: @"InfoVisible"])
341        [self showInfo: nil];
342   
343    //timer to check for auto import every 15 seconds, must do after everything else is set up
344    fAutoImportedNames = [[NSMutableArray alloc] init];
345   
346    [self checkAutoImportDirectory: nil];
347    fAutoImportTimer = [NSTimer scheduledTimerWithTimeInterval: 15.0 target: self
348        selector: @selector(checkAutoImportDirectory:) userInfo: nil repeats: YES];
349    [[NSRunLoop currentRunLoop] addTimer: fAutoImportTimer forMode: NSDefaultRunLoopMode];
350}
351
352- (BOOL) applicationShouldHandleReopen: (NSApplication *) app hasVisibleWindows: (BOOL) visibleWindows
353{
354    if (![fWindow isVisible] && ![[fPrefsController window] isVisible])
355        [fWindow makeKeyAndOrderFront: nil];
356    return NO;
357}
358
359- (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication *) sender
360{
361    if (!fUpdateInProgress && [fDefaults boolForKey: @"CheckQuit"])
362    {
363        int active = 0, downloading = 0;
364        Torrent * torrent;
365        NSEnumerator * enumerator = [fTorrents objectEnumerator];
366        while ((torrent = [enumerator nextObject]))
367            if ([torrent isActive])
368            {
369                active++;
370                if (![torrent isSeeding])
371                    downloading++;
372            }
373
374        if ([fDefaults boolForKey: @"CheckQuitDownloading"] ? downloading > 0 : active > 0)
375        {
376            NSString * message = active == 1
377                ? @"There is an active transfer. Do you really want to quit?"
378                : [NSString stringWithFormat:
379                    @"There are %d active transfers. Do you really want to quit?", active];
380
381            NSBeginAlertSheet(@"Confirm Quit", @"Quit", @"Cancel", nil, fWindow, self,
382                                @selector(quitSheetDidEnd:returnCode:contextInfo:), nil, nil, message);
383            return NSTerminateLater;
384        }
385    }
386
387    return NSTerminateNow;
388}
389
390- (void) quitSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo
391{
392    [NSApp replyToApplicationShouldTerminate: returnCode == NSAlertDefaultReturn];
393}
394
395- (void) applicationWillTerminate: (NSNotification *) notification
396{
397    //stop timers
398    [fAutoImportTimer invalidate];
399    [fTimer invalidate];
400   
401    //save history and stop running torrents
402    [self updateTorrentHistory];
403    [fTorrents makeObjectsPerformSelector: @selector(stopTransferForQuit)];
404   
405    //remember window states and close all windows
406    [fDefaults setBool: [[fInfoController window] isVisible] forKey: @"InfoVisible"];
407    [[NSApp windows] makeObjectsPerformSelector: @selector(close)];
408    [self showStatusBar: NO animate: NO];
409    [self showFilterBar: NO animate: NO];
410   
411    //clear badge
412    [fBadger clearBadge];
413
414    //end quickly if the app is updating
415    if (fUpdateInProgress)
416        return;
417
418    //wait for running transfers to stop (5 second timeout)
419    NSDate * start = [NSDate date];
420    BOOL timeUp = NO;
421   
422    NSEnumerator * enumerator = [fTorrents objectEnumerator];
423    Torrent * torrent;
424    while (!timeUp && (torrent = [enumerator nextObject]))
425        while (![torrent isPaused] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
426        {
427            usleep(100000);
428            [torrent update];
429        }
430}
431
432- (void) folderChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code
433    contextInfo: (Torrent *) torrent
434{
435    if (code == NSOKButton)
436    {
437        [torrent setDownloadFolder: [[openPanel filenames] objectAtIndex: 0]];
438        [torrent update];
439        [self attemptToStartAuto: torrent];
440       
441        [fTorrents addObject: torrent];
442    }
443   
444    [NSApp stopModal];
445}
446
447- (void) application: (NSApplication *) sender openFiles: (NSArray *) filenames
448{
449    NSString * downloadChoice = [fDefaults stringForKey: @"DownloadChoice"], * torrentPath;
450    Torrent * torrent;
451    NSEnumerator * enumerator = [filenames objectEnumerator];
452    while ((torrentPath = [enumerator nextObject]))
453    {
454        if (!(torrent = [[Torrent alloc] initWithPath: torrentPath lib: fLib]))
455            continue;
456
457        //add it to the "File > Open Recent" menu
458        [[NSDocumentController sharedDocumentController]
459            noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
460
461        if ([downloadChoice isEqualToString: @"Ask"])
462        {
463            NSOpenPanel * panel = [NSOpenPanel openPanel];
464
465            [panel setPrompt: @"Select Download Folder"];
466            [panel setAllowsMultipleSelection: NO];
467            [panel setCanChooseFiles: NO];
468            [panel setCanChooseDirectories: YES];
469
470            [panel setMessage: [NSString stringWithFormat: @"Select the download folder for \"%@\"", [torrent name]]];
471
472            [panel beginSheetForDirectory: nil file: nil types: nil
473                modalForWindow: fWindow modalDelegate: self didEndSelector:
474                @selector( folderChoiceClosed:returnCode:contextInfo: )
475                contextInfo: torrent];
476            [NSApp runModalForWindow: panel];
477        }
478        else
479        {
480            NSString * folder = [downloadChoice isEqualToString: @"Constant"]
481                ? [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath]
482                : [torrentPath stringByDeletingLastPathComponent];
483
484            [torrent setDownloadFolder: folder];
485            [torrent update];
486            [self attemptToStartAuto: torrent];
487           
488            [fTorrents addObject: torrent];
489        }
490       
491        [torrent release];
492    }
493
494    [self updateUI: nil];
495    [self applyFilter: nil];
496   
497    [self updateTorrentHistory];
498}
499
500- (NSArray *) torrentsAtIndexes: (NSIndexSet *) indexSet
501{
502    if ([fFilteredTorrents respondsToSelector: @selector(objectsAtIndexes:)])
503        return [fFilteredTorrents objectsAtIndexes: indexSet];
504    else
505    {
506        NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [indexSet count]];
507        unsigned int i;
508        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
509            [torrents addObject: [fFilteredTorrents objectAtIndex: i]];
510
511        return torrents;
512    }
513}
514
515//called on by applescript
516- (void) open: (NSArray *) files
517{
518    [self performSelectorOnMainThread: @selector(openFromSheet:) withObject: files waitUntilDone: NO];
519}
520
521- (void) openShowSheet: (id) sender
522{
523    NSOpenPanel * panel = [NSOpenPanel openPanel];
524
525    [panel setAllowsMultipleSelection: YES];
526    [panel setCanChooseFiles: YES];
527    [panel setCanChooseDirectories: NO];
528
529    [panel beginSheetForDirectory: nil file: nil types: [NSArray arrayWithObject: @"torrent"]
530        modalForWindow: fWindow modalDelegate: self didEndSelector:
531        @selector(openSheetClosed:returnCode:contextInfo:) contextInfo: nil];
532}
533
534- (void) openSheetClosed: (NSOpenPanel *) panel returnCode: (int) code contextInfo: (void *) info
535{
536    if (code == NSOKButton)
537        [self performSelectorOnMainThread: @selector(openFromSheet:)
538                    withObject: [panel filenames] waitUntilDone: NO];
539}
540
541- (void) openFromSheet: (NSArray *) filenames
542{
543    [self application: NSApp openFiles: filenames];
544}
545
546- (void) resumeSelectedTorrents: (id) sender
547{
548    [self resumeTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]];
549}
550
551- (void) resumeAllTorrents: (id) sender
552{
553    [self resumeTorrents: fTorrents];
554}
555
556- (void) resumeTorrents: (NSArray *) torrents
557{
558    [torrents makeObjectsPerformSelector: @selector(startTransfer)];
559   
560    [self updateUI: nil];
561    [self applyFilter: nil];
562    [fInfoController updateInfoStatsAndSettings];
563    [self updateTorrentHistory];
564}
565
566- (void) stopSelectedTorrents: (id) sender
567{
568    [self stopTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]];
569}
570
571- (void) stopAllTorrents: (id) sender
572{
573    [self stopTorrents: fTorrents];
574}
575
576- (void) stopTorrents: (NSArray *) torrents
577{
578    //don't want any of these starting then stopping
579    NSEnumerator * enumerator = [torrents objectEnumerator];
580    Torrent * torrent;
581    while ((torrent = [enumerator nextObject]))
582        [torrent setWaitToStart: NO];
583
584    [torrents makeObjectsPerformSelector: @selector(stopTransfer)];
585   
586    [self updateUI: nil];
587    [self applyFilter: nil];
588    [fInfoController updateInfoStatsAndSettings];
589    [self updateTorrentHistory];
590}
591
592- (void) removeTorrents: (NSArray *) torrents
593        deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
594{
595    [torrents retain];
596    int active = 0, downloading = 0;
597
598    if ([fDefaults boolForKey: @"CheckRemove"])
599    {
600        Torrent * torrent;
601        NSEnumerator * enumerator = [torrents objectEnumerator];
602        while ((torrent = [enumerator nextObject]))
603            if ([torrent isActive])
604            {
605                active++;
606                if (![torrent isSeeding])
607                    downloading++;
608            }
609
610        if ([fDefaults boolForKey: @"CheckRemoveDownloading"] ? downloading > 0 : active > 0)
611        {
612            NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
613                torrents, @"Torrents",
614                [NSNumber numberWithBool: deleteData], @"DeleteData",
615                [NSNumber numberWithBool: deleteTorrent], @"DeleteTorrent", nil];
616
617            NSString * title, * message;
618           
619            int selected = [fTableView numberOfSelectedRows];
620            if (selected == 1)
621            {
622                title = [NSString stringWithFormat: @"Comfirm Removal of \"%@\"",
623                            [[fFilteredTorrents objectAtIndex: [fTableView selectedRow]] name]];
624                message = @"This transfer is active."
625                            " Once removed, continuing the transfer will require the torrent file."
626                            " Do you really want to remove it?";
627            }
628            else
629            {
630                title = [NSString stringWithFormat: @"Comfirm Removal of %d Transfers", selected];
631                if (selected == active)
632                    message = [NSString stringWithFormat:
633                        @"There are %d active transfers.", active];
634                else
635                    message = [NSString stringWithFormat:
636                        @"There are %d transfers (%d active).", selected, active];
637                message = [message stringByAppendingString:
638                    @" Once removed, continuing the transfers will require the torrent files."
639                    " Do you really want to remove them?"];
640            }
641
642            NSBeginAlertSheet(title, @"Remove", @"Cancel", nil, fWindow, self,
643                nil, @selector(removeSheetDidEnd:returnCode:contextInfo:), dict, message);
644            return;
645        }
646    }
647   
648    [self confirmRemoveTorrents: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
649}
650
651- (void) removeSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (NSDictionary *) dict
652{
653    [NSApp stopModal];
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        [fFilteredTorrents removeObject: torrent];
694        [fTorrents 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.