source: trunk/macosx/Controller.m @ 849

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

check for files to auto import at launch

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