source: trunk/macosx/Controller.m @ 496

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

Dock menu item for Speed Limit, among smaller changed.

  • Property svn:keywords set to Date Rev Author Id
File size: 56.3 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 496 2006-07-01 18:02:36Z livings124 $
3 *
4 * Copyright (c) 2005-2006 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import <IOKit/IOMessage.h>
26
27#import "Controller.h"
28#import "Torrent.h"
29#import "TorrentCell.h"
30#import "TorrentTableView.h"
31#import "StringAdditions.h"
32
33#import <Sparkle/Sparkle.h>
34
35#define TOOLBAR_OPEN            @"Toolbar Open"
36#define TOOLBAR_REMOVE          @"Toolbar Remove"
37#define TOOLBAR_INFO            @"Toolbar Info"
38#define TOOLBAR_PAUSE_ALL       @"Toolbar Pause All"
39#define TOOLBAR_RESUME_ALL      @"Toolbar Resume All"
40#define TOOLBAR_PAUSE_SELECTED  @"Toolbar Pause Selected"
41#define TOOLBAR_RESUME_SELECTED @"Toolbar Resume Selected"
42
43#define WEBSITE_URL @"http://transmission.m0k.org/"
44#define FORUM_URL   @"http://transmission.m0k.org/forum/"
45
46#define GROWL_PATH  @"/Library/PreferencePanes/Growl.prefPane/Contents/Resources/GrowlHelperApp.app"
47
48static void sleepCallBack(void * controller, io_service_t y,
49        natural_t messageType, void * messageArgument)
50{
51    Controller * c = controller;
52    [c sleepCallBack: messageType argument: messageArgument];
53}
54
55
56@implementation Controller
57
58- (id) init
59{
60    if ((self = [super init]))
61    {
62        fLib = tr_init();
63        fTorrents = [[NSMutableArray alloc] initWithCapacity: 10];
64        fDefaults = [NSUserDefaults standardUserDefaults];
65        fInfoController = [[InfoWindowController alloc] initWithWindowNibName: @"InfoWindow"];
66        fPrefsController = [[PrefsController alloc] initWithWindowNibName: @"PrefsWindow"];
67        fBadger = [[Badger alloc] init];
68       
69        //check and register Growl if it is installed for this user or all users
70        NSFileManager * manager = [NSFileManager defaultManager];
71        fHasGrowl = [manager fileExistsAtPath: GROWL_PATH]
72            || [manager fileExistsAtPath: [NSHomeDirectory() stringByAppendingPathComponent: GROWL_PATH]];
73        [self growlRegister];
74    }
75    return self;
76}
77
78- (void) dealloc
79{
80    [[NSNotificationCenter defaultCenter] removeObserver: self];
81
82    [fTorrents release];
83    [fToolbar release];
84    [fInfoController release];
85    [fBadger release];
86    [fSortType release];
87   
88    tr_close( fLib );
89    [super dealloc];
90}
91
92- (void) awakeFromNib
93{
94    [fPrefsController setPrefs: fLib];
95   
96    [fAdvancedBarItem setState: [fDefaults boolForKey: @"UseAdvancedBar"]];
97
98    fToolbar = [[NSToolbar alloc] initWithIdentifier: @"Transmission Toolbar"];
99    [fToolbar setDelegate: self];
100    [fToolbar setAllowsUserCustomization: YES];
101    [fToolbar setAutosavesConfiguration: YES];
102    [fWindow setToolbar: fToolbar];
103    [fWindow setDelegate: self];
104   
105    //window min height
106    NSSize contentMinSize = [fWindow contentMinSize];
107    contentMinSize.height = [[fWindow contentView] frame].size.height - [fScrollView frame].size.height
108                                + [fTableView rowHeight] + [fTableView intercellSpacing].height;
109    [fWindow setContentMinSize: contentMinSize];
110   
111    //set info keyboard shortcuts
112    unichar ch = NSRightArrowFunctionKey;
113    [fNextInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & ch length: 1]];
114    ch = NSLeftArrowFunctionKey;
115    [fPrevInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & ch length: 1]];
116   
117    //set up status bar
118    NSRect statusBarFrame = [fStatusBar frame];
119    statusBarFrame.size.width = [fWindow frame].size.width;
120    [fStatusBar setFrame: statusBarFrame];
121   
122    NSView * contentView = [fWindow contentView];
123    [contentView addSubview: fStatusBar];
124    [fStatusBar setFrameOrigin: NSMakePoint(0, [fScrollView frame].origin.y
125                                                + [fScrollView frame].size.height)];
126    [self showStatusBar: [fDefaults boolForKey: @"StatusBar"] animate: NO];
127   
128    //set speed limit
129    BOOL speedLimit = [fDefaults boolForKey: @"SpeedLimit"];
130    if (speedLimit)
131    {
132        [fSpeedLimitItem setState: speedLimit];
133        [fSpeedLimitDockItem setState: speedLimit];
134        [fSpeedLimitButton setState: NSOnState];
135    }
136   
137    [fActionButton setToolTip: @"Shortcuts for changing global settings."];
138    [fSpeedLimitButton setToolTip: @"Speed Limit overrides the total bandwidth limits with its own limits."];
139
140    [fTableView setTorrents: fTorrents];
141    [[fTableView tableColumnWithIdentifier: @"Torrent"] setDataCell: [[TorrentCell alloc] init]];
142
143    [fTableView registerForDraggedTypes: [NSArray arrayWithObject: NSFilenamesPboardType]];
144
145    //register for sleep notifications
146    IONotificationPortRef notify;
147    io_object_t iterator;
148    if (fRootPort = IORegisterForSystemPower(self, & notify, sleepCallBack, & iterator))
149    {
150        CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify),
151                            kCFRunLoopCommonModes);
152    }
153    else
154        NSLog(@"Could not IORegisterForSystemPower");
155
156    //load torrents from history
157    Torrent * torrent;
158    NSDictionary * historyItem;
159    NSEnumerator * enumerator = [[fDefaults arrayForKey: @"History"] objectEnumerator];
160    while ((historyItem = [enumerator nextObject]))
161        if ((torrent = [[Torrent alloc] initWithHistory: historyItem lib: fLib]))
162        {
163            [fTorrents addObject: torrent];
164            [torrent release];
165        }
166   
167    [self torrentNumberChanged];
168   
169    //set sort
170    fSortType = [[fDefaults stringForKey: @"Sort"] retain];
171   
172    NSMenuItem * currentSortItem;
173    if ([fSortType isEqualToString: @"Name"])
174        currentSortItem = fNameSortItem;
175    else if ([fSortType isEqualToString: @"State"])
176        currentSortItem = fStateSortItem;
177    else if ([fSortType isEqualToString: @"Progress"])
178        currentSortItem = fProgressSortItem;
179    else if ([fSortType isEqualToString: @"Date"])
180        currentSortItem = fDateSortItem;
181    else
182        currentSortItem = fOrderSortItem;
183    [currentSortItem setState: NSOnState];
184   
185    //set upload limit action button
186    [fUploadLimitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
187                    [fDefaults integerForKey: @"UploadLimit"]]];
188    if ([fDefaults boolForKey: @"CheckUpload"])
189        [fUploadLimitItem setState: NSOnState];
190    else
191        [fUploadNoLimitItem setState: NSOnState];
192
193        //set download limit action menu
194    [fDownloadLimitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
195                    [fDefaults integerForKey: @"DownloadLimit"]]];
196    if ([fDefaults boolForKey: @"CheckDownload"])
197        [fDownloadLimitItem setState: NSOnState];
198    else
199        [fDownloadNoLimitItem setState: NSOnState];
200   
201    //set ratio action menu
202    [fRatioSetItem setTitle: [NSString stringWithFormat: @"Stop at Ratio (%.2f)",
203                                [fDefaults floatForKey: @"RatioLimit"]]];
204    if ([fDefaults boolForKey: @"RatioCheck"])
205        [fRatioSetItem setState: NSOnState];
206    else
207        [fRatioNotSetItem setState: NSOnState];
208   
209    //observe notifications
210    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
211   
212    [nc addObserver: self selector: @selector(prepareForUpdate:)
213                    name: SUUpdaterWillRestartNotification object: nil];
214    fUpdateInProgress = NO;
215   
216    [nc addObserver: self selector: @selector(limitGlobalChange:)
217                    name: @"LimitGlobalChange" object: nil];
218   
219    [nc addObserver: self selector: @selector(ratioGlobalChange:)
220                    name: @"RatioGlobalChange" object: nil];
221   
222    //check to start another because of stopped torrent
223    [nc addObserver: self selector: @selector(checkWaitingForStopped:)
224                    name: @"StoppedDownloading" object: nil];
225   
226    //check all torrents for starting
227    [nc addObserver: self selector: @selector(globalStartSettingChange:)
228                    name: @"GlobalStartSettingChange" object: nil];
229
230    //check if torrent should now start
231    [nc addObserver: self selector: @selector(torrentStartSettingChange:)
232                    name: @"TorrentStartSettingChange" object: nil];
233   
234    //change that just impacts the inspector
235    [nc addObserver: self selector: @selector(reloadInspector:)
236                    name: @"TorrentSettingChange" object: nil];
237
238    //timer to update the interface
239    fCompleted = 0;
240    [self updateUI: nil];
241    fTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self
242        selector: @selector( updateUI: ) userInfo: nil repeats: YES];
243    [[NSRunLoop currentRunLoop] addTimer: fTimer
244        forMode: NSModalPanelRunLoopMode];
245    [[NSRunLoop currentRunLoop] addTimer: fTimer
246        forMode: NSEventTrackingRunLoopMode];
247   
248    [self sortTorrents];
249   
250    //show windows
251    [fWindow makeKeyAndOrderFront: nil];
252
253    [self reloadInspector: nil];
254    if ([fDefaults boolForKey: @"InfoVisible"])
255        [self showInfo: nil];
256}
257
258- (BOOL) applicationShouldHandleReopen: (NSApplication *) app hasVisibleWindows: (BOOL) visibleWindows
259{
260    if (![fWindow isVisible] && ![[fPrefsController window] isVisible])
261        [fWindow makeKeyAndOrderFront: nil];
262    return NO;
263}
264
265- (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication *) sender
266{
267    if (!fUpdateInProgress && [fDefaults boolForKey: @"CheckQuit"])
268    {
269        int active = 0, downloading = 0;
270        Torrent * torrent;
271        NSEnumerator * enumerator = [fTorrents objectEnumerator];
272        while ((torrent = [enumerator nextObject]))
273            if ([torrent isActive])
274            {
275                active++;
276                if (![torrent isSeeding])
277                    downloading++;
278            }
279
280        BOOL shouldAsk = [fDefaults boolForKey: @"CheckQuitDownloading"] ? downloading > 0 : active > 0;
281        if (shouldAsk)
282        {
283            NSString * message = active == 1
284                ? @"There is an active transfer. Do you really want to quit?"
285                : [NSString stringWithFormat:
286                    @"There are %d active transfers. Do you really want to quit?", active];
287
288            NSBeginAlertSheet(@"Confirm Quit", @"Quit", @"Cancel", nil, fWindow, self,
289                                @selector(quitSheetDidEnd:returnCode:contextInfo:),
290                                nil, nil, message);
291            return NSTerminateLater;
292        }
293    }
294
295    return NSTerminateNow;
296}
297
298- (void) quitSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode
299    contextInfo: (void *) contextInfo
300{
301    [NSApp stopModal];
302    [NSApp replyToApplicationShouldTerminate: returnCode == NSAlertDefaultReturn];
303}
304
305- (void) applicationWillTerminate: (NSNotification *) notification
306{
307    // Stop updating the interface
308    [fTimer invalidate];
309   
310    //save history
311    [self updateTorrentHistory];
312   
313    //remember window states
314    [fDefaults setBool: [[fInfoController window] isVisible] forKey: @"InfoVisible"];
315    [fWindow close];
316    [self showStatusBar: NO animate: NO];
317   
318    //clear badge
319    [fBadger clearBadge];
320
321    //end quickly if updated version will open
322    if (fUpdateInProgress)
323        return;
324
325    //stop running torrents and wait for them to stop (5 seconds timeout)
326    [fTorrents makeObjectsPerformSelector: @selector(stopTransfer)];
327   
328    NSDate * start = [NSDate date];
329    Torrent * torrent;
330    while ([fTorrents count] > 0)
331    {
332        torrent = [fTorrents objectAtIndex: 0];
333        while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
334                ![torrent isPaused] )
335        {
336            usleep( 100000 );
337            [torrent update];
338        }
339        [fTorrents removeObject: torrent];
340    }
341}
342
343- (void) folderChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code
344    contextInfo: (Torrent *) torrent
345{
346    if (code == NSOKButton)
347    {
348        [torrent setDownloadFolder: [[openPanel filenames] objectAtIndex: 0]];
349        [torrent update];
350        [self attemptToStartAuto: torrent];
351        [fTorrents addObject: torrent];
352       
353        [self torrentNumberChanged];
354    }
355   
356    [NSApp stopModal];
357}
358
359- (void) application: (NSApplication *) sender openFiles: (NSArray *) filenames
360{
361    NSString * downloadChoice = [fDefaults stringForKey: @"DownloadChoice"], * torrentPath;
362    Torrent * torrent;
363    NSEnumerator * enumerator = [filenames objectEnumerator];
364    while ((torrentPath = [enumerator nextObject]))
365    {
366        if (!(torrent = [[Torrent alloc] initWithPath: torrentPath lib: fLib]))
367            continue;
368
369        //add it to the "File > Open Recent" menu
370        [[NSDocumentController sharedDocumentController]
371            noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
372
373        if ([downloadChoice isEqualToString: @"Ask"])
374        {
375            NSOpenPanel * panel = [NSOpenPanel openPanel];
376
377            [panel setPrompt: @"Select Download Folder"];
378            [panel setAllowsMultipleSelection: NO];
379            [panel setCanChooseFiles: NO];
380            [panel setCanChooseDirectories: YES];
381
382            [panel setMessage: [@"Select the download folder for "
383                    stringByAppendingString: [torrentPath lastPathComponent]]];
384
385            [panel beginSheetForDirectory: nil file: nil types: nil
386                modalForWindow: fWindow modalDelegate: self didEndSelector:
387                @selector( folderChoiceClosed:returnCode:contextInfo: )
388                contextInfo: torrent];
389            [NSApp runModalForWindow: panel];
390        }
391        else
392        {
393            NSString * folder = [downloadChoice isEqualToString: @"Constant"]
394                ? [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath]
395                : [torrentPath stringByDeletingLastPathComponent];
396
397            [torrent setDownloadFolder: folder];
398            [torrent update];
399            [self attemptToStartAuto: torrent];
400            [fTorrents addObject: torrent];
401        }
402       
403        [torrent release];
404    }
405
406    [self torrentNumberChanged];
407
408    [self updateUI: nil];
409    [self sortTorrents];
410    [self updateTorrentHistory];
411}
412
413- (NSArray *) torrentsAtIndexes: (NSIndexSet *) indexSet
414{
415    if ([fTorrents respondsToSelector: @selector(objectsAtIndexes:)])
416        return [fTorrents objectsAtIndexes: indexSet];
417    else
418    {
419        NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [indexSet count]];
420        unsigned int i;
421        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
422            [torrents addObject: [fTorrents objectAtIndex: i]];
423
424        return torrents;
425    }
426}
427
428- (void) torrentNumberChanged
429{
430    int count = [fTorrents count];
431    [fTotalTorrentsField setStringValue: [NSString stringWithFormat:
432        @"%d Transfer%s", count, count == 1 ? "" : "s"]];
433}
434
435//called on by applescript
436- (void) open: (NSArray *) files
437{
438    [self performSelectorOnMainThread: @selector(openFromSheet:) withObject: files waitUntilDone: NO];
439}
440
441- (void) openShowSheet: (id) sender
442{
443    NSOpenPanel * panel = [NSOpenPanel openPanel];
444    NSArray * fileTypes = [NSArray arrayWithObject: @"torrent"];
445
446    [panel setAllowsMultipleSelection: YES];
447    [panel setCanChooseFiles: YES];
448    [panel setCanChooseDirectories: NO];
449
450    [panel beginSheetForDirectory: nil file: nil types: fileTypes
451        modalForWindow: fWindow modalDelegate: self didEndSelector:
452        @selector(openSheetClosed:returnCode:contextInfo:) contextInfo: nil];
453}
454
455- (void) openFromSheet: (NSArray *) filenames
456{
457    [self application: NSApp openFiles: filenames];
458}
459
460- (void) openSheetClosed: (NSOpenPanel *) panel returnCode: (int) code
461    contextInfo: (void *) info
462{
463    if (code == NSOKButton)
464        [self performSelectorOnMainThread: @selector(openFromSheet:)
465                    withObject: [panel filenames] waitUntilDone: NO];
466}
467
468- (void) resumeTorrent: (id) sender
469{
470    [self resumeTorrentWithIndex: [fTableView selectedRowIndexes]];
471}
472
473- (void) resumeAllTorrents: (id) sender
474{
475    [self resumeTorrentWithIndex: [NSIndexSet indexSetWithIndexesInRange:
476                                    NSMakeRange(0, [fTorrents count])]];
477}
478
479- (void) resumeTorrentWithIndex: (NSIndexSet *) indexSet
480{
481    unsigned int i;
482    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
483        [[fTorrents objectAtIndex: i] startTransfer];
484   
485    [self updateUI: nil];
486    [self reloadInspector: nil];
487    [self updateTorrentHistory];
488}
489
490- (void) stopTorrent: (id) sender
491{
492    [self stopTorrentWithIndex: [fTableView selectedRowIndexes]];
493}
494
495- (void) stopAllTorrents: (id) sender
496{
497    [self stopTorrentWithIndex: [NSIndexSet indexSetWithIndexesInRange:
498                                    NSMakeRange(0, [fTorrents count])]];
499}
500
501- (void) stopTorrentWithIndex: (NSIndexSet *) indexSet
502{
503    //don't want any of these starting then stopping
504    unsigned int i;
505    Torrent * torrent;
506    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
507    {
508        torrent = [fTorrents objectAtIndex: i];
509        [torrent setWaitToStart: NO];
510    }
511
512    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
513        [[fTorrents objectAtIndex: i] stopTransfer];
514   
515    [self updateUI: nil];
516    [self reloadInspector: nil];
517    [self updateTorrentHistory];
518}
519
520- (void) removeWithIndex: (NSIndexSet *) indexSet
521        deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
522{
523    NSArray * torrents = [[self torrentsAtIndexes: indexSet] retain];
524    int active = 0, downloading = 0;
525
526    if ([fDefaults boolForKey: @"CheckRemove"])
527    {
528        Torrent * torrent;
529        NSEnumerator * enumerator = [torrents objectEnumerator];
530        while ((torrent = [enumerator nextObject]))
531            if ([torrent isActive])
532            {
533                active++;
534                if (![torrent isSeeding])
535                    downloading++;
536            }
537    }
538
539    BOOL shouldAsk = [fDefaults boolForKey: @"CheckRemoveDownloading"] ? downloading > 0 : active > 0;
540    if (shouldAsk)
541    {
542        NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
543            torrents, @"Torrents",
544            [NSNumber numberWithBool: deleteData], @"DeleteData",
545            [NSNumber numberWithBool: deleteTorrent], @"DeleteTorrent", nil];
546
547        NSString * title, * message;
548       
549        int selected = [fTableView numberOfSelectedRows];
550        if (selected == 1)
551        {
552            title = [NSString stringWithFormat: @"Comfirm Removal of \"%@\"",
553                        [[fTorrents objectAtIndex: [fTableView selectedRow]] name]];
554            message = @"This transfer is active."
555                        " Once removed, continuing the transfer will require the torrent file."
556                        " Do you really want to remove it?";
557        }
558        else
559        {
560            title = [NSString stringWithFormat: @"Comfirm Removal of %d Transfers", selected];
561            if (selected == active)
562                message = [NSString stringWithFormat:
563                    @"There are %d active transfers.", active];
564            else
565                message = [NSString stringWithFormat:
566                    @"There are %d transfers (%d active).", selected, active];
567            message = [message stringByAppendingString:
568                @" Once removed, continuing the transfers will require the torrent files."
569                " Do you really want to remove them?"];
570        }
571
572        NSBeginAlertSheet(title, @"Remove", @"Cancel", nil, fWindow, self,
573            @selector(removeSheetDidEnd:returnCode:contextInfo:), nil, dict, message);
574    }
575    else
576        [self confirmRemove: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
577}
578
579- (void) removeSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode
580                        contextInfo: (NSDictionary *) dict
581{
582    [NSApp stopModal];
583
584    NSArray * torrents = [dict objectForKey: @"Torrents"];
585    BOOL deleteData = [[dict objectForKey: @"DeleteData"] boolValue],
586        deleteTorrent = [[dict objectForKey: @"DeleteTorrent"] boolValue];
587    [dict release];
588   
589    if (returnCode == NSAlertDefaultReturn)
590        [self confirmRemove: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
591    else
592        [torrents release];
593}
594
595- (void) confirmRemove: (NSArray *) torrents
596        deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
597{
598    //don't want any of these starting then stopping
599    NSEnumerator * enumerator = [torrents objectEnumerator];
600    Torrent * torrent;
601    while ((torrent = [enumerator nextObject]))
602        [torrent setWaitToStart: NO];
603
604    NSNumber * lowestOrderValue = [NSNumber numberWithInt: [torrents count]], * currentOrederValue;
605
606    enumerator = [torrents objectEnumerator];
607    while ((torrent = [enumerator nextObject]))
608    {
609        [torrent stopTransfer];
610
611        if (deleteData)
612            [torrent trashData];
613        if (deleteTorrent)
614            [torrent trashTorrent];
615       
616        //determine lowest order value
617        currentOrederValue = [torrent orderValue];
618        if ([lowestOrderValue compare: currentOrederValue] == NSOrderedDescending)
619            lowestOrderValue = currentOrederValue;
620
621        [torrent removeForever];
622        [fTorrents removeObject: torrent];
623    }
624    [torrents release];
625
626    //reset the order values if necessary
627    if ([lowestOrderValue intValue] < [fTorrents count])
628    {
629        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
630                                                @"orderValue" ascending: YES] autorelease];
631        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
632
633        NSArray * tempTorrents = [fTorrents sortedArrayUsingDescriptors: descriptors];
634        [descriptors release];
635
636        int i;
637        for (i = [lowestOrderValue intValue]; i < [tempTorrents count]; i++)
638            [[tempTorrents objectAtIndex: i] setOrderValue: i];
639    }
640   
641    [self torrentNumberChanged];
642    [fTableView deselectAll: nil];
643    [self updateUI: nil];
644    [self updateTorrentHistory];
645}
646
647- (void) removeNoDelete: (id) sender
648{
649    [self removeWithIndex: [fTableView selectedRowIndexes] deleteData: NO deleteTorrent: NO];
650}
651
652- (void) removeDeleteData: (id) sender
653{
654    [self removeWithIndex: [fTableView selectedRowIndexes] deleteData: YES deleteTorrent: NO];
655}
656
657- (void) removeDeleteTorrent: (id) sender
658{
659    [self removeWithIndex: [fTableView selectedRowIndexes] deleteData: NO deleteTorrent: YES];
660}
661
662- (void) removeDeleteBoth: (id) sender
663{
664    [self removeWithIndex: [fTableView selectedRowIndexes] deleteData: YES deleteTorrent: YES];
665}
666
667- (void) copyTorrentFile: (id) sender
668{
669    [self copyTorrentFileForTorrents: [[NSMutableArray alloc] initWithArray:
670            [self torrentsAtIndexes: [fTableView selectedRowIndexes]]]];
671}
672
673- (void) copyTorrentFileForTorrents: (NSMutableArray *) torrents
674{
675    if ([torrents count] == 0)
676    {
677        [torrents release];
678        return;
679    }
680
681    Torrent * torrent = [torrents objectAtIndex: 0];
682
683    //warn user if torrent file can't be found
684    if (![[NSFileManager defaultManager] fileExistsAtPath: [torrent torrentLocation]])
685    {
686        NSAlert * alert = [[NSAlert alloc] init];
687        [alert addButtonWithTitle: @"OK"];
688        [alert setMessageText: [NSString stringWithFormat:
689                @"Copy of \"%@\" Cannot Be Created", [torrent name]]];
690        [alert setInformativeText: [NSString stringWithFormat:
691                @"The torrent file (%@) cannot be found.", [torrent torrentLocation]]];
692        [alert setAlertStyle: NSWarningAlertStyle];
693       
694        [alert runModal];
695       
696        [torrents removeObjectAtIndex: 0];
697        [self copyTorrentFileForTorrents: torrents];
698    }
699    else
700    {
701        NSSavePanel * panel = [NSSavePanel savePanel];
702        [panel setRequiredFileType: @"torrent"];
703        [panel setCanSelectHiddenExtension: NO];
704        [panel setExtensionHidden: NO];
705       
706        [panel beginSheetForDirectory: nil file: [torrent name]
707            modalForWindow: fWindow modalDelegate: self didEndSelector:
708            @selector( saveTorrentCopySheetClosed:returnCode:contextInfo: )
709            contextInfo: torrents];
710    }
711}
712
713- (void) saveTorrentCopySheetClosed: (NSSavePanel *) panel returnCode: (int) code
714    contextInfo: (NSMutableArray *) torrents
715{
716    //if save successful, copy torrent to new location with name of data file
717    if (code == NSOKButton)
718        [[NSFileManager defaultManager] copyPath: [[torrents objectAtIndex: 0] torrentLocation]
719                toPath: [panel filename] handler: nil];
720   
721    [torrents removeObjectAtIndex: 0];
722    [self performSelectorOnMainThread: @selector(copyTorrentFileForTorrents:)
723                withObject: torrents waitUntilDone: NO];
724}
725
726- (void) revealFile: (id) sender
727{
728    NSIndexSet * indexSet = [fTableView selectedRowIndexes];
729    unsigned int i;
730   
731    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
732        [[fTorrents objectAtIndex: i] revealData];
733}
734
735- (void) showPreferenceWindow: (id) sender
736{
737    NSWindow * window = [fPrefsController window];
738    if (![window isVisible])
739        [window center];
740
741    [window makeKeyAndOrderFront: nil];
742}
743
744- (void) showInfo: (id) sender
745{
746    if ([[fInfoController window] isVisible])
747        [[fInfoController window] performClose: nil];
748    else
749    {
750        [fInfoController updateInfoStats];
751        [[fInfoController window] orderFront: nil];
752    }
753}
754
755- (void) setInfoTab: (id) sender
756{
757    if (sender == fNextInfoTabItem)
758        [fInfoController setNextTab];
759    else
760        [fInfoController setPreviousTab];
761}
762
763- (void) updateUI: (NSTimer *) t
764{
765    NSEnumerator * enumerator = [fTorrents objectEnumerator];
766    Torrent * torrent;
767    while ((torrent = [enumerator nextObject]))
768    {
769        [torrent update];
770
771        if ([torrent justFinished])
772        {
773            [self checkWaitingForFinished: torrent];
774       
775            //notifications
776            [self notifyGrowl: [torrent name]];
777            if (![fWindow isKeyWindow])
778                fCompleted++;
779        }
780    }
781
782    if ([fSortType isEqualToString: @"Progress"] || [fSortType isEqualToString: @"State"])
783        [self sortTorrents];
784    else
785        [fTableView reloadData];
786   
787    //update the global DL/UL rates
788    float downloadRate, uploadRate;
789    tr_torrentRates(fLib, & downloadRate, & uploadRate);
790    if (fStatusBarVisible)
791    {
792        [fTotalDLField setStringValue: [NSString stringForSpeed: downloadRate]];
793        [fTotalULField setStringValue: [NSString stringForSpeed: uploadRate]];
794    }
795
796    if ([[fInfoController window] isVisible])
797        [fInfoController updateInfoStats];
798
799    //badge dock
800    [fBadger updateBadgeWithCompleted: fCompleted
801        uploadRate: uploadRate downloadRate: downloadRate];
802}
803
804- (void) updateTorrentHistory
805{
806    NSMutableArray * history = [NSMutableArray
807        arrayWithCapacity: [fTorrents count]];
808
809    NSEnumerator * enumerator = [fTorrents objectEnumerator];
810    Torrent * torrent;
811    while( ( torrent = [enumerator nextObject] ) )
812        [history addObject: [torrent history]];
813
814    [fDefaults setObject: history forKey: @"History"];
815    [fDefaults synchronize];
816}
817
818- (void) sortTorrents
819{
820    //remember selected rows if needed
821    NSArray * selectedTorrents = nil;
822    int numSelected = [fTableView numberOfSelectedRows];
823    if (numSelected > 0 && numSelected < [fTorrents count])
824        selectedTorrents = [self torrentsAtIndexes: [fTableView selectedRowIndexes]];
825
826    NSSortDescriptor * nameDescriptor = [[[NSSortDescriptor alloc] initWithKey:
827                                            @"name" ascending: YES] autorelease],
828                    * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
829                                            @"orderValue" ascending: YES] autorelease];
830
831    NSArray * descriptors;
832    if ([fSortType isEqualToString: @"Name"])
833        descriptors = [[NSArray alloc] initWithObjects: nameDescriptor, orderDescriptor, nil];
834    else if ([fSortType isEqualToString: @"State"])
835    {
836        NSSortDescriptor * stateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
837                                                @"stateSortKey" ascending: NO] autorelease],
838                        * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
839                                            @"progressSortKey" ascending: NO] autorelease];
840       
841        descriptors = [[NSArray alloc] initWithObjects: stateDescriptor, progressDescriptor,
842                                                            nameDescriptor, orderDescriptor, nil];
843    }
844    else if ([fSortType isEqualToString: @"Progress"])
845    {
846        NSSortDescriptor * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
847                                            @"progressSortKey" ascending: YES] autorelease];
848       
849        descriptors = [[NSArray alloc] initWithObjects: progressDescriptor, nameDescriptor, orderDescriptor, nil];
850    }
851    else if ([fSortType isEqualToString: @"Date"])
852    {
853        NSSortDescriptor * dateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
854                                            @"date" ascending: YES] autorelease];
855   
856        descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, orderDescriptor, nil];
857    }
858    else
859        descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
860
861    [fTorrents sortUsingDescriptors: descriptors];
862   
863    [descriptors release];
864   
865    [fTableView reloadData];
866   
867    //set selected rows if needed
868    if (selectedTorrents)
869    {
870        Torrent * torrent;
871        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
872        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
873        while ((torrent = [enumerator nextObject]))
874            [indexSet addIndex: [fTorrents indexOfObject: torrent]];
875       
876        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
877        [indexSet release];
878    }
879}
880
881- (void) setSort: (id) sender
882{
883    NSMenuItem * prevSortItem;
884    if ([fSortType isEqualToString: @"Name"])
885        prevSortItem = fNameSortItem;
886    else if ([fSortType isEqualToString: @"State"])
887        prevSortItem = fStateSortItem;
888    else if ([fSortType isEqualToString: @"Progress"])
889        prevSortItem = fProgressSortItem;
890    else if ([fSortType isEqualToString: @"Date"])
891        prevSortItem = fDateSortItem;
892    else
893        prevSortItem = fOrderSortItem;
894   
895    if (sender != prevSortItem)
896    {
897        [prevSortItem setState: NSOffState];
898        [sender setState: NSOnState];
899
900        [fSortType release];
901        if (sender == fNameSortItem)
902            fSortType = [[NSString alloc] initWithString: @"Name"];
903        else if (sender == fStateSortItem)
904            fSortType = [[NSString alloc] initWithString: @"State"];
905        else if (sender == fProgressSortItem)
906            fSortType = [[NSString alloc] initWithString: @"Progress"];
907        else if (sender == fDateSortItem)
908            fSortType = [[NSString alloc] initWithString: @"Date"];
909        else
910            fSortType = [[NSString alloc] initWithString: @"Order"];
911           
912        [fDefaults setObject: fSortType forKey: @"Sort"];
913    }
914
915    [self sortTorrents];
916}
917
918- (void) toggleSpeedLimit: (id) sender
919{
920    int state = [fSpeedLimitItem state] ? NSOffState : NSOnState;
921
922    [fSpeedLimitItem setState: state];
923    [fSpeedLimitDockItem setState: state];
924    [fSpeedLimitButton setState: state];
925   
926    [fPrefsController enableSpeedLimit: state];
927}
928
929- (void) setLimitGlobalEnabled: (id) sender
930{
931    [fPrefsController setLimitEnabled: (sender == fUploadLimitItem || sender == fDownloadLimitItem)
932        type: (sender == fUploadLimitItem || sender == fUploadNoLimitItem) ? @"Upload" : @"Download"];
933}
934
935- (void) setQuickLimitGlobal: (id) sender
936{
937    [fPrefsController setQuickLimit: [[sender title] intValue]
938        type: [sender menu] == fUploadMenu ? @"Upload" : @"Download"];
939}
940
941- (void) limitGlobalChange: (NSNotification *) notification
942{
943    NSDictionary * dict = [notification object];
944   
945    NSMenuItem * limitItem, * noLimitItem;
946    if ([[dict objectForKey: @"Type"] isEqualToString: @"Upload"])
947    {
948        limitItem = fUploadLimitItem;
949        noLimitItem = fUploadNoLimitItem;
950    }
951    else
952    {
953        limitItem = fDownloadLimitItem;
954        noLimitItem = fDownloadNoLimitItem;
955    }
956   
957    BOOL enable = [[dict objectForKey: @"Enable"] boolValue];
958    [limitItem setState: enable ? NSOnState : NSOffState];
959    [noLimitItem setState: !enable ? NSOnState : NSOffState];
960   
961    [limitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
962                            [[dict objectForKey: @"Limit"] intValue]]];
963
964    [dict release];
965}
966
967- (void) setRatioGlobalEnabled: (id) sender
968{
969    [fPrefsController setRatioEnabled: sender == fRatioSetItem];
970}
971
972- (void) setQuickRatioGlobal: (id) sender
973{
974    [fPrefsController setQuickRatio: [[sender title] floatValue]];
975}
976
977- (void) ratioGlobalChange: (NSNotification *) notification
978{
979    NSDictionary * dict = [notification object];
980   
981    BOOL enable = [[dict objectForKey: @"Enable"] boolValue];
982    [fRatioSetItem setState: enable ? NSOnState : NSOffState];
983    [fRatioNotSetItem setState: !enable ? NSOnState : NSOffState];
984   
985    [fRatioSetItem setTitle: [NSString stringWithFormat: @"Stop at Ratio (%.2f)",
986                            [[dict objectForKey: @"Ratio"] floatValue]]];
987
988    [dict release];
989}
990
991- (void) checkWaitingForStopped: (NSNotification *) notification
992{
993    [self checkWaitingForFinished: [notification object]];
994}
995
996- (void) checkWaitingForFinished: (Torrent *) finishedTorrent
997{
998    //don't try to start a transfer if there should be none waiting
999    if (![[fDefaults stringForKey: @"StartSetting"] isEqualToString: @"Wait"])
1000        return;
1001
1002    int desiredActive = [fDefaults integerForKey: @"WaitToStartNumber"], active = 0;
1003   
1004    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1005    Torrent * torrent, * torrentToStart = nil;
1006    while ((torrent = [enumerator nextObject]))
1007    {
1008        //ignore the torrent just stopped; for some reason it is not marked instantly as not active
1009        if (torrent == finishedTorrent)
1010            continue;
1011   
1012        if ([torrent isActive])
1013        {
1014            if (![torrent isSeeding])
1015            {
1016                active++;
1017                if (active >= desiredActive)
1018                    return;
1019            }
1020        }
1021        else
1022        {
1023            //use as next if it is waiting to start and either no previous or order value is lower
1024            if ([torrent waitingToStart] && (!torrentToStart
1025                || [[torrentToStart orderValue] compare: [torrent orderValue]] == NSOrderedDescending))
1026                torrentToStart = torrent;
1027        }
1028    }
1029   
1030    //since it hasn't returned, the queue amount has not been met
1031    if (torrentToStart)
1032    {
1033        [torrentToStart startTransfer];
1034        [self updateUI: nil];
1035    }
1036}
1037
1038- (void) globalStartSettingChange: (NSNotification *) notification
1039{
1040    NSString * startSetting = [fDefaults stringForKey: @"StartSetting"];
1041   
1042    if ([startSetting isEqualToString: @"Start"])
1043    {
1044        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1045        Torrent * torrent;
1046        while ((torrent = [enumerator nextObject]))
1047            if ([torrent waitingToStart])
1048                [torrent startTransfer];
1049    }
1050    else if ([startSetting isEqualToString: @"Wait"])
1051    {
1052        NSMutableArray * waitingTorrents = [[NSMutableArray alloc] initWithCapacity: [fTorrents count]];
1053   
1054        int amountToStart = [fDefaults integerForKey: @"WaitToStartNumber"];
1055       
1056        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1057        Torrent * torrent;
1058        while ((torrent = [enumerator nextObject]))
1059        {
1060            if ([torrent isActive])
1061            {
1062                if (![torrent isSeeding])
1063                {
1064                    amountToStart--;
1065                    if (amountToStart <= 0)
1066                        break;
1067                }
1068            }
1069            else if ([torrent waitingToStart])
1070                [waitingTorrents addObject: torrent];
1071            else;
1072        }
1073       
1074        int waitingCount = [waitingTorrents count];
1075        if (amountToStart > 0 && waitingCount > 0)
1076        {
1077            if (amountToStart > waitingCount)
1078                amountToStart = waitingCount;
1079           
1080            //sort torrents by date to start earliest added
1081            if (amountToStart < waitingCount)
1082            {
1083                NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1084                                                            @"orderValue" ascending: YES] autorelease];
1085                NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1086               
1087                [waitingTorrents sortUsingDescriptors: descriptors];
1088                [descriptors release];
1089            }
1090           
1091            int i;
1092            for (i = 0; i < amountToStart; i++)
1093                [[waitingTorrents objectAtIndex: i] startTransfer];
1094        }
1095       
1096        [waitingTorrents release];
1097    }
1098    else;
1099   
1100    [self updateUI: nil];
1101   
1102    //update info for changed start setting
1103    [self reloadInspector: nil];
1104}
1105
1106- (void) torrentStartSettingChange: (NSNotification *) notification
1107{
1108    [self attemptToStartAuto: [notification object]];
1109
1110    [self updateUI: nil];
1111    [self reloadInspector: nil];
1112    [self updateTorrentHistory];
1113}
1114
1115//will try to start, taking into consideration the start preference
1116- (void) attemptToStartAuto: (Torrent *) torrent
1117{
1118    if ([torrent progress] >= 1.0)
1119        [torrent startTransfer];
1120    else
1121    {
1122        if (![torrent waitingToStart])
1123            return;
1124       
1125        NSString * startSetting = [fDefaults stringForKey: @"StartSetting"];
1126        if ([startSetting isEqualToString: @"Wait"])
1127        {
1128            int desiredActive = [fDefaults integerForKey: @"WaitToStartNumber"];
1129           
1130            Torrent * tempTorrent;
1131            NSEnumerator * enumerator = [fTorrents objectEnumerator];
1132            while ((tempTorrent = [enumerator nextObject]))
1133                if ([tempTorrent isActive] && ![tempTorrent isSeeding])
1134                {
1135                    desiredActive--;
1136                    if (desiredActive <= 0)
1137                        return;
1138                }
1139           
1140            [torrent startTransfer];
1141        }
1142        else if ([startSetting isEqualToString: @"Start"])
1143            [torrent startTransfer];
1144        else;
1145    }
1146   
1147    [torrent update];
1148}
1149
1150- (void) reloadInspector: (NSNotification *) notification
1151{
1152    [fInfoController updateInfoForTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]];
1153}
1154
1155- (int) numberOfRowsInTableView: (NSTableView *) t
1156{
1157    return [fTorrents count];
1158}
1159
1160- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell
1161    forTableColumn: (NSTableColumn *) tableColumn row: (int) row
1162{
1163    [cell setTorrent: [fTorrents objectAtIndex: row]];
1164}
1165
1166- (BOOL) tableView: (NSTableView *) t acceptDrop:
1167    (id <NSDraggingInfo>) info row: (int) row dropOperation:
1168    (NSTableViewDropOperation) operation
1169{
1170    [self application: NSApp openFiles: [[[info draggingPasteboard]
1171        propertyListForType: NSFilenamesPboardType]
1172        pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]];
1173    return YES;
1174}
1175
1176- (NSDragOperation) tableView: (NSTableView *) t validateDrop:
1177    (id <NSDraggingInfo>) info proposedRow: (int) row
1178    proposedDropOperation: (NSTableViewDropOperation) operation
1179{
1180    NSPasteboard * pasteboard = [info draggingPasteboard];
1181    if (![[pasteboard types] containsObject: NSFilenamesPboardType]
1182            || [[[pasteboard propertyListForType: NSFilenamesPboardType]
1183        pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]] count] == 0)
1184        return NSDragOperationNone;
1185
1186    [fTableView setDropRow: [fTableView numberOfRows] dropOperation: NSTableViewDropAbove];
1187    return NSDragOperationGeneric;
1188}
1189
1190- (void) tableViewSelectionDidChange: (NSNotification *) notification
1191{
1192    [self reloadInspector: nil];
1193}
1194
1195- (void) toggleStatusBar: (id) sender
1196{
1197    [self showStatusBar: !fStatusBarVisible animate: YES];
1198    [fDefaults setBool: fStatusBarVisible forKey: @"StatusBar"];
1199}
1200
1201- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
1202{
1203    if (show == fStatusBarVisible)
1204        return;
1205
1206    NSRect frame = [fWindow frame];
1207    float heightChange = [fStatusBar frame].size.height;
1208    if (!show)
1209        heightChange *= -1;
1210
1211    frame.size.height += heightChange;
1212    frame.origin.y -= heightChange;
1213       
1214    fStatusBarVisible = !fStatusBarVisible;
1215   
1216    [self updateUI: nil];
1217   
1218    //set views to not autoresize
1219    unsigned int statsMask = [fStatusBar autoresizingMask];
1220    unsigned int scrollMask = [fScrollView autoresizingMask];
1221    [fStatusBar setAutoresizingMask: 0];
1222    [fScrollView setAutoresizingMask: 0];
1223   
1224    [fWindow setFrame: frame display: YES animate: animate];
1225   
1226    //re-enable autoresize
1227    [fStatusBar setAutoresizingMask: statsMask];
1228    [fScrollView setAutoresizingMask: scrollMask];
1229   
1230    //change min size
1231    NSSize minSize = [fWindow contentMinSize];
1232    minSize.height += heightChange;
1233    [fWindow setContentMinSize: minSize];
1234}
1235
1236- (void) toggleAdvancedBar: (id) sender
1237{
1238    [fAdvancedBarItem setState: ![fAdvancedBarItem state]];
1239    [fDefaults setBool: [fAdvancedBarItem state] forKey: @"UseAdvancedBar"];
1240
1241    [fTableView display];
1242}
1243
1244- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
1245    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
1246{
1247    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
1248
1249    if( [ident isEqualToString: TOOLBAR_OPEN] )
1250    {
1251        [item setLabel: @"Open"];
1252        [item setPaletteLabel: @"Open Torrent Files"];
1253        [item setToolTip: @"Open torrent files"];
1254        [item setImage: [NSImage imageNamed: @"Open.png"]];
1255        [item setTarget: self];
1256        [item setAction: @selector( openShowSheet: )];
1257    }
1258    else if( [ident isEqualToString: TOOLBAR_REMOVE] )
1259    {
1260        [item setLabel: @"Remove"];
1261        [item setPaletteLabel: @"Remove Selected"];
1262        [item setToolTip: @"Remove selected transfers"];
1263        [item setImage: [NSImage imageNamed: @"Remove.png"]];
1264        [item setTarget: self];
1265        [item setAction: @selector( removeNoDelete: )];
1266    }
1267    else if( [ident isEqualToString: TOOLBAR_INFO] )
1268    {
1269        [item setLabel: @"Inspector"];
1270        [item setPaletteLabel: @"Show/Hide Inspector"];
1271        [item setToolTip: @"Display torrent inspector"];
1272        [item setImage: [NSImage imageNamed: @"Info.png"]];
1273        [item setTarget: self];
1274        [item setAction: @selector( showInfo: )];
1275    }
1276    else if( [ident isEqualToString: TOOLBAR_PAUSE_ALL] )
1277    {
1278        [item setLabel: @"Pause All"];
1279        [item setPaletteLabel: [item label]];
1280        [item setToolTip: @"Pause all transfers"];
1281        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
1282        [item setTarget: self];
1283        [item setAction: @selector( stopAllTorrents: )];
1284    }
1285    else if( [ident isEqualToString: TOOLBAR_RESUME_ALL] )
1286    {
1287        [item setLabel: @"Resume All"];
1288        [item setPaletteLabel: [item label]];
1289        [item setToolTip: @"Resume all transfers"];
1290        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
1291        [item setTarget: self];
1292        [item setAction: @selector( resumeAllTorrents: )];
1293    }
1294    else if( [ident isEqualToString: TOOLBAR_PAUSE_SELECTED] )
1295    {
1296        [item setLabel: @"Pause"];
1297        [item setPaletteLabel: @"Pause Selected"];
1298        [item setToolTip: @"Pause selected transfers"];
1299        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
1300        [item setTarget: self];
1301        [item setAction: @selector( stopTorrent: )];
1302    }
1303    else if( [ident isEqualToString: TOOLBAR_RESUME_SELECTED] )
1304    {
1305        [item setLabel: @"Resume"];
1306        [item setPaletteLabel: @"Resume Selected"];
1307        [item setToolTip: @"Resume selected transfers"];
1308        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
1309        [item setTarget: self];
1310        [item setAction: @selector( resumeTorrent: )];
1311    }
1312    else
1313    {
1314        [item release];
1315        return nil;
1316    }
1317
1318    return item;
1319}
1320
1321- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
1322{
1323    return [NSArray arrayWithObjects:
1324            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1325            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
1326            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL, TOOLBAR_INFO,
1327            NSToolbarSeparatorItemIdentifier,
1328            NSToolbarSpaceItemIdentifier,
1329            NSToolbarFlexibleSpaceItemIdentifier,
1330            NSToolbarCustomizeToolbarItemIdentifier, nil];
1331}
1332
1333- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
1334{
1335    return [NSArray arrayWithObjects:
1336            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1337            NSToolbarSeparatorItemIdentifier,
1338            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
1339            NSToolbarFlexibleSpaceItemIdentifier,
1340            TOOLBAR_INFO, nil];
1341}
1342
1343- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
1344{
1345    NSString * ident = [toolbarItem itemIdentifier];
1346
1347    //enable remove item
1348    if ([ident isEqualToString: TOOLBAR_REMOVE])
1349        return [fTableView numberOfSelectedRows] > 0;
1350
1351    //enable pause all item
1352    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
1353    {
1354        Torrent * torrent;
1355        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1356        while ((torrent = [enumerator nextObject]))
1357            if ([torrent isActive])
1358                return YES;
1359        return NO;
1360    }
1361
1362    //enable resume all item
1363    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
1364    {
1365        Torrent * torrent;
1366        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1367        while ((torrent = [enumerator nextObject]))
1368            if ([torrent isPaused])
1369                return YES;
1370        return NO;
1371    }
1372
1373    //enable pause item
1374    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
1375    {
1376        Torrent * torrent;
1377        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1378        unsigned int i;
1379       
1380        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1381            if ([[fTorrents objectAtIndex: i] isActive])
1382                return YES;
1383        return NO;
1384    }
1385   
1386    //enable resume item
1387    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
1388    {
1389        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1390        unsigned int i;
1391       
1392        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1393            if ([[fTorrents objectAtIndex: i] isPaused])
1394                return YES;
1395        return NO;
1396    }
1397
1398    return YES;
1399}
1400
1401- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
1402{
1403    SEL action = [menuItem action];
1404
1405    //only enable some items if it is in a context menu or the window is useable
1406    BOOL canUseMenu = [fWindow isKeyWindow] || [[[menuItem menu] title] isEqualToString: @"Context"];
1407
1408    //enable show info
1409    if (action == @selector(showInfo:))
1410    {
1411        NSString * title = [[fInfoController window] isVisible] ? @"Hide Inspector" : @"Show Inspector";
1412        if (![[menuItem title] isEqualToString: title])
1413                [menuItem setTitle: title];
1414
1415        return YES;
1416    }
1417   
1418    //enable prev/next inspector tab
1419    if (action == @selector(setInfoTab:))
1420        return [[fInfoController window] isVisible];
1421   
1422    //enable toggle status bar
1423    if (action == @selector(toggleStatusBar:))
1424    {
1425        NSString * title = fStatusBarVisible ? @"Hide Status Bar" : @"Show Status Bar";
1426        if (![[menuItem title] isEqualToString: title])
1427                [menuItem setTitle: title];
1428
1429        return canUseMenu;
1430    }
1431
1432    //enable resume all item
1433    if (action == @selector(resumeAllTorrents:))
1434    {
1435        Torrent * torrent;
1436        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1437        while ((torrent = [enumerator nextObject]))
1438            if ([torrent isPaused])
1439                return YES;
1440        return NO;
1441    }
1442
1443    //enable pause all item
1444    if (action == @selector(stopAllTorrents:))
1445    {
1446        Torrent * torrent;
1447        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1448        while ((torrent = [enumerator nextObject]))
1449            if ([torrent isActive])
1450                return YES;
1451        return NO;
1452    }
1453
1454    //enable reveal in finder
1455    if (action == @selector(revealFile:))
1456        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1457
1458    //enable remove items
1459    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
1460        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteBoth:))
1461    {
1462        BOOL warning = NO,
1463            onlyDownloading = [fDefaults boolForKey: @"CheckRemoveDownloading"],
1464            canDelete = action != @selector(removeDeleteTorrent:) && action != @selector(removeDeleteBoth:);
1465        Torrent * torrent;
1466        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1467        unsigned int i;
1468       
1469        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1470        {
1471            torrent = [fTorrents objectAtIndex: i];
1472            if (!warning && [torrent isActive])
1473            {
1474                warning = onlyDownloading ? ![torrent isSeeding] : YES;
1475                if (warning && canDelete)
1476                    break;
1477            }
1478            if (!canDelete && [torrent publicTorrent])
1479            {
1480                canDelete = YES;
1481                if (warning)
1482                    break;
1483            }
1484        }
1485   
1486        //append or remove ellipsis when needed
1487        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
1488        if (warning && [fDefaults boolForKey: @"CheckRemove"])
1489        {
1490            if (![title hasSuffix: ellipsis])
1491                [menuItem setTitle: [title stringByAppendingEllipsis]];
1492        }
1493        else
1494        {
1495            if ([title hasSuffix: ellipsis])
1496                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
1497        }
1498       
1499        return canUseMenu && canDelete && [fTableView numberOfSelectedRows] > 0;
1500    }
1501
1502    //enable pause item
1503    if( action == @selector(stopTorrent:) )
1504    {
1505        if (!canUseMenu)
1506            return NO;
1507   
1508        Torrent * torrent;
1509        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1510        unsigned int i;
1511       
1512        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1513        {
1514            torrent = [fTorrents objectAtIndex: i];
1515            if ([torrent isActive])
1516                return YES;
1517        }
1518        return NO;
1519    }
1520   
1521    //enable resume item
1522    if( action == @selector(resumeTorrent:) )
1523    {
1524        if (!canUseMenu)
1525            return NO;
1526   
1527        Torrent * torrent;
1528        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1529        unsigned int i;
1530       
1531        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1532        {
1533            torrent = [fTorrents objectAtIndex: i];
1534            if ([torrent isPaused])
1535                return YES;
1536        }
1537        return NO;
1538    }
1539   
1540    //enable sort and advanced bar items
1541    if (action == @selector(setSort:) || (action == @selector(toggleAdvancedBar:)))
1542        return canUseMenu;
1543   
1544    //enable copy torrent file item
1545    if( action == @selector(copyTorrentFile:) )
1546    {
1547        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1548    }
1549
1550    return YES;
1551}
1552
1553- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument
1554{
1555    NSEnumerator * enumerator;
1556    Torrent * torrent;
1557    BOOL active;
1558
1559    switch( messageType )
1560    {
1561        case kIOMessageSystemWillSleep:
1562            /* Close all connections before going to sleep and remember
1563               we should resume when we wake up */
1564            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
1565
1566            /* Wait for torrents to stop (5 seconds timeout) */
1567            NSDate * start = [NSDate date];
1568            enumerator = [fTorrents objectEnumerator];
1569            while( ( torrent = [enumerator nextObject] ) )
1570            {
1571                while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
1572                        ![torrent isPaused] )
1573                {
1574                    usleep( 100000 );
1575                    [torrent update];
1576                }
1577            }
1578
1579            IOAllowPowerChange( fRootPort, (long) messageArgument );
1580            break;
1581
1582        case kIOMessageCanSystemSleep:
1583            /* Prevent idle sleep unless all paused */
1584            active = NO;
1585            enumerator = [fTorrents objectEnumerator];
1586            while ((torrent = [enumerator nextObject]))
1587                if ([torrent isActive])
1588                {
1589                    active = YES;
1590                    break;
1591                }
1592
1593            if (active)
1594                IOCancelPowerChange( fRootPort, (long) messageArgument );
1595            else
1596                IOAllowPowerChange( fRootPort, (long) messageArgument );
1597            break;
1598
1599        case kIOMessageSystemHasPoweredOn:
1600            /* Resume download after we wake up */
1601            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
1602            break;
1603    }
1604}
1605
1606- (NSRect) windowWillUseStandardFrame: (NSWindow *) w defaultFrame: (NSRect) defaultFrame
1607{
1608    NSRect windowRect = [fWindow frame];
1609    float newHeight = windowRect.size.height - [fScrollView frame].size.height
1610        + [fTorrents count] * ([fTableView rowHeight] + [fTableView intercellSpacing].height) + 30.0;
1611
1612    float minHeight = [fWindow minSize].height;
1613    if (newHeight < minHeight)
1614        newHeight = minHeight;
1615
1616    windowRect.origin.y -= (newHeight - windowRect.size.height);
1617    windowRect.size.height = newHeight;
1618
1619    return windowRect;
1620}
1621
1622- (void) showMainWindow: (id) sender
1623{
1624    [fWindow makeKeyAndOrderFront: nil];
1625}
1626
1627- (void) windowDidBecomeKey: (NSNotification *) notification
1628{
1629    fCompleted = 0;
1630}
1631
1632- (void) linkHomepage: (id) sender
1633{
1634    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
1635}
1636
1637- (void) linkForums: (id) sender
1638{
1639    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
1640}
1641
1642- (void) notifyGrowl: (NSString * ) file
1643{
1644    if (!fHasGrowl)
1645        return;
1646
1647    NSString * growlScript = [NSString stringWithFormat:
1648        @"tell application \"System Events\"\n"
1649         "  if exists application process \"GrowlHelperApp\" then\n"
1650         "    tell application \"GrowlHelperApp\"\n "
1651         "      notify with name \"Download Complete\""
1652         "        title \"Download Complete\""
1653         "        description \"%@\""
1654         "        application name \"Transmission\"\n"
1655         "    end tell\n"
1656         "  end if\n"
1657         "end tell", file];
1658   
1659    NSAppleScript * appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1660    NSDictionary * error;
1661    if (![appleScript executeAndReturnError: & error])
1662        NSLog(@"Growl notify failed");
1663    [appleScript release];
1664}
1665
1666- (void) growlRegister
1667{
1668    if (!fHasGrowl)
1669        return;
1670
1671    NSString * growlScript = [NSString stringWithFormat:
1672        @"tell application \"System Events\"\n"
1673         "  if exists application process \"GrowlHelperApp\" then\n"
1674         "    tell application \"GrowlHelperApp\"\n"
1675         "      register as application \"Transmission\" "
1676         "        all notifications {\"Download Complete\"}"
1677         "        default notifications {\"Download Complete\"}"
1678         "        icon of application \"Transmission\"\n"
1679         "    end tell\n"
1680         "  end if\n"
1681         "end tell"];
1682
1683    NSAppleScript * appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1684    NSDictionary * error;
1685    if (![appleScript executeAndReturnError: & error])
1686        NSLog(@"Growl registration failed");
1687    [appleScript release];
1688}
1689
1690- (void) checkUpdate: (id) sender
1691{
1692    [fPrefsController checkUpdate];
1693}
1694
1695- (void) prepareForUpdate: (NSNotification *) notification
1696{
1697    fUpdateInProgress = YES;
1698}
1699
1700@end
Note: See TracBrowser for help on using the repository browser.