source: trunk/macosx/Controller.m @ 1005

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

hide scrollers when autoresizing

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