source: trunk/macosx/Controller.m @ 848

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

Forgot the updated credits and to remove an unused constant

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