source: trunk/macosx/Controller.m @ 484

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

Added an option to only show the remove/quit warning if transfers are downloading (not seeding). Also change the word "torrent" to "transfer" in the prefs window.

  • Property svn:keywords set to Date Rev Author Id
File size: 55.3 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 484 2006-06-26 05:52:51Z 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    [fActionButton setToolTip: @"Shortcuts for changing global settings."];
129
130    [fTableView setTorrents: fTorrents];
131    [[fTableView tableColumnWithIdentifier: @"Torrent"] setDataCell:
132        [[TorrentCell alloc] init]];
133
134    [fTableView registerForDraggedTypes:
135        [NSArray arrayWithObject: NSFilenamesPboardType]];
136
137    //register for sleep notifications
138    IONotificationPortRef notify;
139    io_object_t iterator;
140    if (fRootPort = IORegisterForSystemPower(self, & notify, sleepCallBack, & iterator))
141    {
142        CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify),
143                            kCFRunLoopCommonModes);
144    }
145    else
146        NSLog(@"Could not IORegisterForSystemPower");
147
148    //load torrents from history
149    Torrent * torrent;
150    NSDictionary * historyItem;
151    NSEnumerator * enumerator = [[fDefaults arrayForKey: @"History"] objectEnumerator];
152    while ((historyItem = [enumerator nextObject]))
153        if ((torrent = [[Torrent alloc] initWithHistory: historyItem lib: fLib]))
154        {
155            [fTorrents addObject: torrent];
156            [torrent release];
157        }
158   
159    [self torrentNumberChanged];
160   
161    //set sort
162    fSortType = [[fDefaults stringForKey: @"Sort"] retain];
163   
164    NSMenuItem * currentSortItem;
165    if ([fSortType isEqualToString: @"Name"])
166        currentSortItem = fNameSortItem;
167    else if ([fSortType isEqualToString: @"State"])
168        currentSortItem = fStateSortItem;
169    else if ([fSortType isEqualToString: @"Progress"])
170        currentSortItem = fProgressSortItem;
171    else if ([fSortType isEqualToString: @"Date"])
172        currentSortItem = fDateSortItem;
173    else
174        currentSortItem = fOrderSortItem;
175    [currentSortItem setState: NSOnState];
176   
177    //set upload limit action button
178    [fUploadLimitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
179                    [fDefaults integerForKey: @"UploadLimit"]]];
180    if ([fDefaults boolForKey: @"CheckUpload"])
181        [fUploadLimitItem setState: NSOnState];
182    else
183        [fUploadNoLimitItem setState: NSOnState];
184
185        //set download limit action menu
186    [fDownloadLimitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
187                    [fDefaults integerForKey: @"DownloadLimit"]]];
188    if ([fDefaults boolForKey: @"CheckDownload"])
189        [fDownloadLimitItem setState: NSOnState];
190    else
191        [fDownloadNoLimitItem setState: NSOnState];
192   
193    //set ratio action menu
194    [fRatioSetItem setTitle: [NSString stringWithFormat: @"Stop at Ratio (%.2f)",
195                                [fDefaults floatForKey: @"RatioLimit"]]];
196    if ([fDefaults boolForKey: @"RatioCheck"])
197        [fRatioSetItem setState: NSOnState];
198    else
199        [fRatioNotSetItem setState: NSOnState];
200   
201    //observe notifications
202    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
203   
204    [nc addObserver: self selector: @selector(prepareForUpdate:)
205                    name: SUUpdaterWillRestartNotification object: nil];
206    fUpdateInProgress = NO;
207   
208    [nc addObserver: self selector: @selector(limitGlobalChange:)
209                    name: @"LimitGlobalChange" object: nil];
210   
211    [nc addObserver: self selector: @selector(ratioGlobalChange:)
212                    name: @"RatioGlobalChange" object: nil];
213   
214    //check to start another because of stopped torrent
215    [nc addObserver: self selector: @selector(checkWaitingForStopped:)
216                    name: @"StoppedDownloading" object: nil];
217   
218    //check all torrents for starting
219    [nc addObserver: self selector: @selector(globalStartSettingChange:)
220                    name: @"GlobalStartSettingChange" object: nil];
221
222    //check if torrent should now start
223    [nc addObserver: self selector: @selector(torrentStartSettingChange:)
224                    name: @"TorrentStartSettingChange" object: nil];
225   
226    //change that just impacts the inspector
227    [nc addObserver: self selector: @selector(reloadInspector:)
228                    name: @"TorrentSettingChange" object: nil];
229
230    //timer to update the interface
231    fCompleted = 0;
232    [self updateUI: nil];
233    fTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self
234        selector: @selector( updateUI: ) userInfo: nil repeats: YES];
235    [[NSRunLoop currentRunLoop] addTimer: fTimer
236        forMode: NSModalPanelRunLoopMode];
237    [[NSRunLoop currentRunLoop] addTimer: fTimer
238        forMode: NSEventTrackingRunLoopMode];
239   
240    [self sortTorrents];
241   
242    //show windows
243    [fWindow makeKeyAndOrderFront: nil];
244
245    [self reloadInspector: nil];
246    if ([fDefaults boolForKey: @"InfoVisible"])
247        [self showInfo: nil];
248}
249
250- (BOOL) applicationShouldHandleReopen: (NSApplication *) app hasVisibleWindows: (BOOL) visibleWindows
251{
252    if (![fWindow isVisible] && ![[fPrefsController window] isVisible])
253        [fWindow makeKeyAndOrderFront: nil];
254    return NO;
255}
256
257- (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication *) sender
258{
259    if (!fUpdateInProgress && [fDefaults boolForKey: @"CheckQuit"])
260    {
261        int active = 0, downloading = 0;
262        Torrent * torrent;
263        NSEnumerator * enumerator = [fTorrents objectEnumerator];
264        while ((torrent = [enumerator nextObject]))
265            if ([torrent isActive])
266            {
267                active++;
268                if (![torrent isSeeding])
269                    downloading++;
270            }
271
272        BOOL shouldAsk = [fDefaults boolForKey: @"CheckRemoveDownloading"] ? downloading > 0 : active > 0;
273        if (shouldAsk)
274        {
275            NSString * message = active == 1
276                ? @"There is an active transfer. Do you really want to quit?"
277                : [NSString stringWithFormat:
278                    @"There are %d active transfers. Do you really want to quit?", active];
279
280            NSBeginAlertSheet(@"Confirm Quit", @"Quit", @"Cancel", nil, fWindow, self,
281                                @selector(quitSheetDidEnd:returnCode:contextInfo:),
282                                nil, nil, message);
283            return NSTerminateLater;
284        }
285    }
286
287    return NSTerminateNow;
288}
289
290- (void) quitSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode
291    contextInfo: (void *) contextInfo
292{
293    [NSApp stopModal];
294    [NSApp replyToApplicationShouldTerminate: returnCode == NSAlertDefaultReturn];
295}
296
297- (void) applicationWillTerminate: (NSNotification *) notification
298{
299    // Stop updating the interface
300    [fTimer invalidate];
301   
302    //save history
303    [self updateTorrentHistory];
304   
305    //remember window states
306    [fDefaults setBool: [[fInfoController window] isVisible] forKey: @"InfoVisible"];
307    [fWindow close];
308    [self showStatusBar: NO animate: NO];
309   
310    //clear badge
311    [fBadger clearBadge];
312
313    //end quickly if updated version will open
314    if (fUpdateInProgress)
315        return;
316
317    //stop running torrents and wait for them to stop (5 seconds timeout)
318    [fTorrents makeObjectsPerformSelector: @selector(stopTransfer)];
319   
320    NSDate * start = [NSDate date];
321    Torrent * torrent;
322    while ([fTorrents count] > 0)
323    {
324        torrent = [fTorrents objectAtIndex: 0];
325        while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
326                ![torrent isPaused] )
327        {
328            usleep( 100000 );
329            [torrent update];
330        }
331        [fTorrents removeObject: torrent];
332    }
333}
334
335- (void) folderChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code
336    contextInfo: (Torrent *) torrent
337{
338    if (code == NSOKButton)
339    {
340        [torrent setDownloadFolder: [[openPanel filenames] objectAtIndex: 0]];
341        [self attemptToStartAuto: torrent];
342        [fTorrents addObject: torrent];
343        [torrent update];
344       
345        [self torrentNumberChanged];
346    }
347   
348    [NSApp stopModal];
349}
350
351- (void) application: (NSApplication *) sender openFiles: (NSArray *) filenames
352{
353    NSString * downloadChoice = [fDefaults stringForKey: @"DownloadChoice"], * torrentPath;
354    Torrent * torrent;
355    NSEnumerator * enumerator = [filenames objectEnumerator];
356    while ((torrentPath = [enumerator nextObject]))
357    {
358        if (!(torrent = [[Torrent alloc] initWithPath: torrentPath lib: fLib]))
359            continue;
360
361        //add it to the "File > Open Recent" menu
362        [[NSDocumentController sharedDocumentController]
363            noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
364
365        if ([downloadChoice isEqualToString: @"Ask"])
366        {
367            NSOpenPanel * panel = [NSOpenPanel openPanel];
368
369            [panel setPrompt: @"Select Download Folder"];
370            [panel setAllowsMultipleSelection: NO];
371            [panel setCanChooseFiles: NO];
372            [panel setCanChooseDirectories: YES];
373
374            [panel setMessage: [@"Select the download folder for "
375                    stringByAppendingString: [torrentPath lastPathComponent]]];
376
377            [panel beginSheetForDirectory: nil file: nil types: nil
378                modalForWindow: fWindow modalDelegate: self didEndSelector:
379                @selector( folderChoiceClosed:returnCode:contextInfo: )
380                contextInfo: torrent];
381            [NSApp runModalForWindow: panel];
382        }
383        else
384        {
385            NSString * folder = [downloadChoice isEqualToString: @"Constant"]
386                ? [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath]
387                : [torrentPath stringByDeletingLastPathComponent];
388
389            [torrent setDownloadFolder: folder];
390            [self attemptToStartAuto: torrent];
391            [fTorrents addObject: torrent];
392            [torrent update];
393        }
394       
395        [torrent release];
396    }
397
398    [self torrentNumberChanged];
399
400    [self updateUI: nil];
401    [self sortTorrents];
402    [self updateTorrentHistory];
403}
404
405- (NSArray *) torrentsAtIndexes: (NSIndexSet *) indexSet
406{
407    if ([fTorrents respondsToSelector: @selector(objectsAtIndexes:)])
408        return [fTorrents objectsAtIndexes: indexSet];
409    else
410    {
411        NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [indexSet count]];
412        unsigned int i;
413        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
414            [torrents addObject: [fTorrents objectAtIndex: i]];
415
416        return torrents;
417    }
418}
419
420- (void) torrentNumberChanged
421{
422    int count = [fTorrents count];
423    [fTotalTorrentsField setStringValue: [NSString stringWithFormat:
424        @"%d Transfer%s", count, count == 1 ? "" : "s"]];
425}
426
427- (void) advancedChanged: (id) sender
428{
429    [fAdvancedBarItem setState: ![fAdvancedBarItem state]];
430    [fDefaults setBool: [fAdvancedBarItem state] forKey: @"UseAdvancedBar"];
431
432    [fTableView display];
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) setLimitGlobalEnabled: (id) sender
919{
920    [fPrefsController setLimitEnabled: (sender == fUploadLimitItem || sender == fDownloadLimitItem)
921        type: (sender == fUploadLimitItem || sender == fUploadNoLimitItem) ? @"Upload" : @"Download"];
922}
923
924- (void) setQuickLimitGlobal: (id) sender
925{
926    [fPrefsController setQuickLimit: [[sender title] intValue]
927        type: [sender menu] == fUploadMenu ? @"Upload" : @"Download"];
928}
929
930- (void) limitGlobalChange: (NSNotification *) notification
931{
932    NSDictionary * dict = [notification object];
933   
934    NSMenuItem * limitItem, * noLimitItem;
935    if ([[dict objectForKey: @"Type"] isEqualToString: @"Upload"])
936    {
937        limitItem = fUploadLimitItem;
938        noLimitItem = fUploadNoLimitItem;
939    }
940    else
941    {
942        limitItem = fDownloadLimitItem;
943        noLimitItem = fDownloadNoLimitItem;
944    }
945   
946    BOOL enable = [[dict objectForKey: @"Enable"] boolValue];
947    [limitItem setState: enable ? NSOnState : NSOffState];
948    [noLimitItem setState: !enable ? NSOnState : NSOffState];
949   
950    [limitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
951                            [[dict objectForKey: @"Limit"] intValue]]];
952
953    [dict release];
954}
955
956- (void) setRatioGlobalEnabled: (id) sender
957{
958    [fPrefsController setRatioEnabled: sender == fRatioSetItem];
959}
960
961- (void) setQuickRatioGlobal: (id) sender
962{
963    [fPrefsController setQuickRatio: [[sender title] floatValue]];
964}
965
966- (void) ratioGlobalChange: (NSNotification *) notification
967{
968    NSDictionary * dict = [notification object];
969   
970    BOOL enable = [[dict objectForKey: @"Enable"] boolValue];
971    [fRatioSetItem setState: enable ? NSOnState : NSOffState];
972    [fRatioNotSetItem setState: !enable ? NSOnState : NSOffState];
973   
974    [fRatioSetItem setTitle: [NSString stringWithFormat: @"Stop at Ratio (%.2f)",
975                            [[dict objectForKey: @"Ratio"] floatValue]]];
976
977    [dict release];
978}
979
980- (void) checkWaitingForStopped: (NSNotification *) notification
981{
982    [self checkWaitingForFinished: [notification object]];
983}
984
985- (void) checkWaitingForFinished: (Torrent *) finishedTorrent
986{
987    //don't try to start a transfer if there should be none waiting
988    if (![[fDefaults stringForKey: @"StartSetting"] isEqualToString: @"Wait"])
989        return;
990
991    int desiredActive = [fDefaults integerForKey: @"WaitToStartNumber"], active = 0;
992   
993    NSEnumerator * enumerator = [fTorrents objectEnumerator];
994    Torrent * torrent, * torrentToStart = nil;
995    while ((torrent = [enumerator nextObject]))
996    {
997        //ignore the torrent just stopped; for some reason it is not marked instantly as not active
998        if (torrent == finishedTorrent)
999            continue;
1000   
1001        if ([torrent isActive])
1002        {
1003            if (![torrent isSeeding])
1004            {
1005                active++;
1006                if (active >= desiredActive)
1007                    return;
1008            }
1009        }
1010        else
1011        {
1012            //use as next if it is waiting to start and either no previous or order value is lower
1013            if ([torrent waitingToStart] && (!torrentToStart
1014                || [[torrentToStart orderValue] compare: [torrent orderValue]] == NSOrderedDescending))
1015                torrentToStart = torrent;
1016        }
1017    }
1018   
1019    //since it hasn't returned, the queue amount has not been met
1020    if (torrentToStart)
1021    {
1022        [torrentToStart startTransfer];
1023        [self updateUI: nil];
1024    }
1025}
1026
1027- (void) globalStartSettingChange: (NSNotification *) notification
1028{
1029    NSString * startSetting = [fDefaults stringForKey: @"StartSetting"];
1030   
1031    if ([startSetting isEqualToString: @"Start"])
1032    {
1033        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1034        Torrent * torrent;
1035        while ((torrent = [enumerator nextObject]))
1036            if ([torrent waitingToStart])
1037                [torrent startTransfer];
1038    }
1039    else if ([startSetting isEqualToString: @"Wait"])
1040    {
1041        NSMutableArray * waitingTorrents = [[NSMutableArray alloc] initWithCapacity: [fTorrents count]];
1042   
1043        int amountToStart = [fDefaults integerForKey: @"WaitToStartNumber"];
1044       
1045        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1046        Torrent * torrent;
1047        while ((torrent = [enumerator nextObject]))
1048        {
1049            if ([torrent isActive])
1050            {
1051                if (![torrent isSeeding])
1052                {
1053                    amountToStart--;
1054                    if (amountToStart <= 0)
1055                        break;
1056                }
1057            }
1058            else if ([torrent waitingToStart])
1059                [waitingTorrents addObject: torrent];
1060            else;
1061        }
1062       
1063        int waitingCount = [waitingTorrents count];
1064        if (amountToStart > 0 && waitingCount > 0)
1065        {
1066            if (amountToStart > waitingCount)
1067                amountToStart = waitingCount;
1068           
1069            //sort torrents by date to start earliest added
1070            if (amountToStart < waitingCount)
1071            {
1072                NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1073                                                            @"orderValue" ascending: YES] autorelease];
1074                NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1075               
1076                [waitingTorrents sortUsingDescriptors: descriptors];
1077                [descriptors release];
1078            }
1079           
1080            int i;
1081            for (i = 0; i < amountToStart; i++)
1082                [[waitingTorrents objectAtIndex: i] startTransfer];
1083        }
1084       
1085        [waitingTorrents release];
1086    }
1087    else;
1088   
1089    [self updateUI: nil];
1090   
1091    //update info for changed start setting
1092    [self reloadInspector: nil];
1093}
1094
1095- (void) torrentStartSettingChange: (NSNotification *) notification
1096{
1097    [self attemptToStartAuto: [notification object]];
1098
1099    [self updateUI: nil];
1100    [self reloadInspector: nil];
1101    [self updateTorrentHistory];
1102}
1103
1104//will try to start, taking into consideration the start preference
1105- (void) attemptToStartAuto: (Torrent *) torrent
1106{
1107    if (![torrent waitingToStart])
1108            return;
1109   
1110    NSString * startSetting = [fDefaults stringForKey: @"StartSetting"];
1111    if ([startSetting isEqualToString: @"Wait"])
1112    {
1113        int desiredActive = [fDefaults integerForKey: @"WaitToStartNumber"];
1114       
1115        Torrent * tempTorrent;
1116        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1117        while ((tempTorrent = [enumerator nextObject]))
1118            if ([tempTorrent isActive] && ![tempTorrent isSeeding])
1119            {
1120                desiredActive--;
1121                if (desiredActive <= 0)
1122                    return;
1123            }
1124       
1125        [torrent startTransfer];
1126    }
1127    else if ([startSetting isEqualToString: @"Start"])
1128        [torrent startTransfer];
1129    else;
1130}
1131
1132- (void) reloadInspector: (NSNotification *) notification
1133{
1134    [fInfoController updateInfoForTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]];
1135}
1136
1137- (int) numberOfRowsInTableView: (NSTableView *) t
1138{
1139    return [fTorrents count];
1140}
1141
1142- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell
1143    forTableColumn: (NSTableColumn *) tableColumn row: (int) row
1144{
1145    [cell setTorrent: [fTorrents objectAtIndex: row]];
1146}
1147
1148- (BOOL) tableView: (NSTableView *) t acceptDrop:
1149    (id <NSDraggingInfo>) info row: (int) row dropOperation:
1150    (NSTableViewDropOperation) operation
1151{
1152    [self application: NSApp openFiles: [[[info draggingPasteboard]
1153        propertyListForType: NSFilenamesPboardType]
1154        pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]];
1155    return YES;
1156}
1157
1158- (NSDragOperation) tableView: (NSTableView *) t validateDrop:
1159    (id <NSDraggingInfo>) info proposedRow: (int) row
1160    proposedDropOperation: (NSTableViewDropOperation) operation
1161{
1162    NSPasteboard * pasteboard = [info draggingPasteboard];
1163    if (![[pasteboard types] containsObject: NSFilenamesPboardType]
1164            || [[[pasteboard propertyListForType: NSFilenamesPboardType]
1165        pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]] count] == 0)
1166        return NSDragOperationNone;
1167
1168    [fTableView setDropRow: [fTableView numberOfRows] dropOperation: NSTableViewDropAbove];
1169    return NSDragOperationGeneric;
1170}
1171
1172- (void) tableViewSelectionDidChange: (NSNotification *) notification
1173{
1174    [self reloadInspector: nil];
1175}
1176
1177- (void) toggleStatusBar: (id) sender
1178{
1179    [self showStatusBar: !fStatusBarVisible animate: YES];
1180    [fDefaults setBool: fStatusBarVisible forKey: @"StatusBar"];
1181}
1182
1183- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
1184{
1185    if (show == fStatusBarVisible)
1186        return;
1187
1188    NSRect frame = [fWindow frame];
1189    float heightChange = [fStatusBar frame].size.height;
1190    if (!show)
1191        heightChange *= -1;
1192
1193    frame.size.height += heightChange;
1194    frame.origin.y -= heightChange;
1195       
1196    fStatusBarVisible = !fStatusBarVisible;
1197   
1198    //reloads stats
1199    [self updateUI: nil];
1200   
1201    //set views to not autoresize
1202    unsigned int statsMask = [fStatusBar autoresizingMask];
1203    unsigned int scrollMask = [fScrollView autoresizingMask];
1204    [fStatusBar setAutoresizingMask: 0];
1205    [fScrollView setAutoresizingMask: 0];
1206   
1207    [fWindow setFrame: frame display: YES animate: animate];
1208   
1209    //re-enable autoresize
1210    [fStatusBar setAutoresizingMask: statsMask];
1211    [fScrollView setAutoresizingMask: scrollMask];
1212   
1213    //change min size
1214    NSSize minSize = [fWindow contentMinSize];
1215    minSize.height += heightChange;
1216    [fWindow setContentMinSize: minSize];
1217}
1218
1219- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
1220    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
1221{
1222    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
1223
1224    if( [ident isEqualToString: TOOLBAR_OPEN] )
1225    {
1226        [item setLabel: @"Open"];
1227        [item setPaletteLabel: @"Open Torrent Files"];
1228        [item setToolTip: @"Open torrent files"];
1229        [item setImage: [NSImage imageNamed: @"Open.png"]];
1230        [item setTarget: self];
1231        [item setAction: @selector( openShowSheet: )];
1232    }
1233    else if( [ident isEqualToString: TOOLBAR_REMOVE] )
1234    {
1235        [item setLabel: @"Remove"];
1236        [item setPaletteLabel: @"Remove Selected"];
1237        [item setToolTip: @"Remove selected transfers"];
1238        [item setImage: [NSImage imageNamed: @"Remove.png"]];
1239        [item setTarget: self];
1240        [item setAction: @selector( removeNoDelete: )];
1241    }
1242    else if( [ident isEqualToString: TOOLBAR_INFO] )
1243    {
1244        [item setLabel: @"Inspector"];
1245        [item setPaletteLabel: @"Show/Hide Inspector"];
1246        [item setToolTip: @"Display torrent inspector"];
1247        [item setImage: [NSImage imageNamed: @"Info.png"]];
1248        [item setTarget: self];
1249        [item setAction: @selector( showInfo: )];
1250    }
1251    else if( [ident isEqualToString: TOOLBAR_PAUSE_ALL] )
1252    {
1253        [item setLabel: @"Pause All"];
1254        [item setPaletteLabel: [item label]];
1255        [item setToolTip: @"Pause all transfers"];
1256        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
1257        [item setTarget: self];
1258        [item setAction: @selector( stopAllTorrents: )];
1259    }
1260    else if( [ident isEqualToString: TOOLBAR_RESUME_ALL] )
1261    {
1262        [item setLabel: @"Resume All"];
1263        [item setPaletteLabel: [item label]];
1264        [item setToolTip: @"Resume all transfers"];
1265        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
1266        [item setTarget: self];
1267        [item setAction: @selector( resumeAllTorrents: )];
1268    }
1269    else if( [ident isEqualToString: TOOLBAR_PAUSE_SELECTED] )
1270    {
1271        [item setLabel: @"Pause"];
1272        [item setPaletteLabel: @"Pause Selected"];
1273        [item setToolTip: @"Pause selected transfers"];
1274        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
1275        [item setTarget: self];
1276        [item setAction: @selector( stopTorrent: )];
1277    }
1278    else if( [ident isEqualToString: TOOLBAR_RESUME_SELECTED] )
1279    {
1280        [item setLabel: @"Resume"];
1281        [item setPaletteLabel: @"Resume Selected"];
1282        [item setToolTip: @"Resume selected transfers"];
1283        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
1284        [item setTarget: self];
1285        [item setAction: @selector( resumeTorrent: )];
1286    }
1287    else
1288    {
1289        [item release];
1290        return nil;
1291    }
1292
1293    return item;
1294}
1295
1296- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
1297{
1298    return [NSArray arrayWithObjects:
1299            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1300            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
1301            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
1302            TOOLBAR_INFO,
1303            NSToolbarSeparatorItemIdentifier,
1304            NSToolbarSpaceItemIdentifier,
1305            NSToolbarFlexibleSpaceItemIdentifier,
1306            NSToolbarCustomizeToolbarItemIdentifier, nil];
1307}
1308
1309- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
1310{
1311    return [NSArray arrayWithObjects:
1312            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1313            NSToolbarSeparatorItemIdentifier,
1314            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
1315            NSToolbarFlexibleSpaceItemIdentifier,
1316            TOOLBAR_INFO, nil];
1317}
1318
1319- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
1320{
1321    NSString * ident = [toolbarItem itemIdentifier];
1322
1323    //enable remove item
1324    if ([ident isEqualToString: TOOLBAR_REMOVE])
1325        return [fTableView numberOfSelectedRows] > 0;
1326
1327    //enable pause all item
1328    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
1329    {
1330        Torrent * torrent;
1331        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1332        while ((torrent = [enumerator nextObject]))
1333            if ([torrent isActive])
1334                return YES;
1335        return NO;
1336    }
1337
1338    //enable resume all item
1339    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
1340    {
1341        Torrent * torrent;
1342        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1343        while ((torrent = [enumerator nextObject]))
1344            if ([torrent isPaused])
1345                return YES;
1346        return NO;
1347    }
1348
1349    //enable pause item
1350    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
1351    {
1352        Torrent * torrent;
1353        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1354        unsigned int i;
1355       
1356        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1357            if ([[fTorrents objectAtIndex: i] isActive])
1358                return YES;
1359        return NO;
1360    }
1361   
1362    //enable resume item
1363    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
1364    {
1365        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1366        unsigned int i;
1367       
1368        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1369            if ([[fTorrents objectAtIndex: i] isPaused])
1370                return YES;
1371        return NO;
1372    }
1373
1374    return YES;
1375}
1376
1377- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
1378{
1379    SEL action = [menuItem action];
1380
1381    //only enable some items if it is in a context menu or the window is useable
1382    BOOL canUseMenu = [[[menuItem menu] title] isEqualToString: @"Context"]
1383                        || [fWindow isKeyWindow];
1384
1385    //enable show info
1386    if (action == @selector(showInfo:))
1387    {
1388        NSString * title = [[fInfoController window] isVisible] ? @"Hide Inspector" : @"Show Inspector";
1389        if (![[menuItem title] isEqualToString: title])
1390                [menuItem setTitle: title];
1391
1392        return YES;
1393    }
1394   
1395    if (action == @selector(setInfoTab:))
1396        return [[fInfoController window] isVisible];
1397   
1398    //enable toggle status bar
1399    if (action == @selector(toggleStatusBar:))
1400    {
1401        NSString * title = fStatusBarVisible ? @"Hide Status Bar" : @"Show Status Bar";
1402        if (![[menuItem title] isEqualToString: title])
1403                [menuItem setTitle: title];
1404
1405        return canUseMenu;
1406    }
1407
1408    //enable resume all item
1409    if (action == @selector(resumeAllTorrents:))
1410    {
1411        Torrent * torrent;
1412        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1413        while ((torrent = [enumerator nextObject]))
1414            if ([torrent isPaused])
1415                return YES;
1416        return NO;
1417    }
1418
1419    //enable pause all item
1420    if (action == @selector(stopAllTorrents:))
1421    {
1422        Torrent * torrent;
1423        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1424        while ((torrent = [enumerator nextObject]))
1425            if ([torrent isActive])
1426                return YES;
1427        return NO;
1428    }
1429
1430    if (action == @selector(revealFile:))
1431    {
1432        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1433    }
1434
1435    //enable remove items
1436    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
1437        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteBoth:))
1438    {
1439        BOOL active = NO,
1440            canDelete = action != @selector(removeDeleteTorrent:) && action != @selector(removeDeleteBoth:);
1441        Torrent * torrent;
1442        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1443        unsigned int i;
1444       
1445        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1446        {
1447            torrent = [fTorrents objectAtIndex: i];
1448            if (!active && [torrent isActive])
1449            {
1450                active = YES;
1451                if (canDelete)
1452                    break;
1453            }
1454            if (!canDelete && [torrent publicTorrent])
1455            {
1456                canDelete = YES;
1457                if (active)
1458                    break;
1459            }
1460        }
1461   
1462        //append or remove ellipsis when needed
1463        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
1464        if (active && [fDefaults boolForKey: @"CheckRemove"])
1465        {
1466            if (![title hasSuffix: ellipsis])
1467                [menuItem setTitle: [title stringByAppendingEllipsis]];
1468        }
1469        else
1470        {
1471            if ([title hasSuffix: ellipsis])
1472                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
1473        }
1474       
1475        return canUseMenu && canDelete && [fTableView numberOfSelectedRows] > 0;
1476    }
1477
1478    //enable pause item
1479    if( action == @selector(stopTorrent:) )
1480    {
1481        if (!canUseMenu)
1482            return NO;
1483   
1484        Torrent * torrent;
1485        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1486        unsigned int i;
1487       
1488        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1489        {
1490            torrent = [fTorrents objectAtIndex: i];
1491            if ([torrent isActive])
1492                return YES;
1493        }
1494        return NO;
1495    }
1496   
1497    //enable resume item
1498    if( action == @selector(resumeTorrent:) )
1499    {
1500        if (!canUseMenu)
1501            return NO;
1502   
1503        Torrent * torrent;
1504        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1505        unsigned int i;
1506       
1507        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1508        {
1509            torrent = [fTorrents objectAtIndex: i];
1510            if ([torrent isPaused])
1511                return YES;
1512        }
1513        return NO;
1514    }
1515   
1516    //enable resume item
1517    if (action == @selector(setSort:) || (action == @selector(advancedChanged:)))
1518        return canUseMenu;
1519   
1520    //enable copy torrent file item
1521    if( action == @selector(copyTorrentFile:) )
1522    {
1523        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1524    }
1525
1526    return YES;
1527}
1528
1529- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument
1530{
1531    NSEnumerator * enumerator;
1532    Torrent * torrent;
1533    BOOL active;
1534
1535    switch( messageType )
1536    {
1537        case kIOMessageSystemWillSleep:
1538            /* Close all connections before going to sleep and remember
1539               we should resume when we wake up */
1540            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
1541
1542            /* Wait for torrents to stop (5 seconds timeout) */
1543            NSDate * start = [NSDate date];
1544            enumerator = [fTorrents objectEnumerator];
1545            while( ( torrent = [enumerator nextObject] ) )
1546            {
1547                while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
1548                        ![torrent isPaused] )
1549                {
1550                    usleep( 100000 );
1551                    [torrent update];
1552                }
1553            }
1554
1555            IOAllowPowerChange( fRootPort, (long) messageArgument );
1556            break;
1557
1558        case kIOMessageCanSystemSleep:
1559            /* Prevent idle sleep unless all paused */
1560            active = NO;
1561            enumerator = [fTorrents objectEnumerator];
1562            while ((torrent = [enumerator nextObject]))
1563                if ([torrent isActive])
1564                {
1565                    active = YES;
1566                    break;
1567                }
1568
1569            if (active)
1570                IOCancelPowerChange( fRootPort, (long) messageArgument );
1571            else
1572                IOAllowPowerChange( fRootPort, (long) messageArgument );
1573            break;
1574
1575        case kIOMessageSystemHasPoweredOn:
1576            /* Resume download after we wake up */
1577            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
1578            break;
1579    }
1580}
1581
1582- (NSRect) windowWillUseStandardFrame: (NSWindow *) w defaultFrame: (NSRect) defaultFrame
1583{
1584    NSRect windowRect = [fWindow frame];
1585    float newHeight = windowRect.size.height - [fScrollView frame].size.height
1586        + [fTorrents count] * ([fTableView rowHeight] + [fTableView intercellSpacing].height) + 30.0;
1587
1588    float minHeight = [fWindow minSize].height;
1589    if (newHeight < minHeight)
1590        newHeight = minHeight;
1591
1592    windowRect.origin.y -= (newHeight - windowRect.size.height);
1593    windowRect.size.height = newHeight;
1594
1595    return windowRect;
1596}
1597
1598- (void) showMainWindow: (id) sender
1599{
1600    [fWindow makeKeyAndOrderFront: nil];
1601}
1602
1603- (void) windowDidBecomeKey: (NSNotification *) notification
1604{
1605    fCompleted = 0;
1606}
1607
1608- (void) linkHomepage: (id) sender
1609{
1610    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
1611}
1612
1613- (void) linkForums: (id) sender
1614{
1615    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
1616}
1617
1618- (void) notifyGrowl: (NSString * ) file
1619{
1620    if (!fHasGrowl)
1621        return;
1622
1623    NSString * growlScript = [NSString stringWithFormat:
1624        @"tell application \"System Events\"\n"
1625         "  if exists application process \"GrowlHelperApp\" then\n"
1626         "    tell application \"GrowlHelperApp\"\n "
1627         "      notify with name \"Download Complete\""
1628         "        title \"Download Complete\""
1629         "        description \"%@\""
1630         "        application name \"Transmission\"\n"
1631         "    end tell\n"
1632         "  end if\n"
1633         "end tell", file];
1634   
1635    NSAppleScript * appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1636    NSDictionary * error;
1637    if (![appleScript executeAndReturnError: & error])
1638        NSLog(@"Growl notify failed");
1639    [appleScript release];
1640}
1641
1642- (void) growlRegister
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         "      register as application \"Transmission\" "
1652         "        all notifications {\"Download Complete\"}"
1653         "        default notifications {\"Download Complete\"}"
1654         "        icon of application \"Transmission\"\n"
1655         "    end tell\n"
1656         "  end if\n"
1657         "end tell"];
1658
1659    NSAppleScript * appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1660    NSDictionary * error;
1661    if (![appleScript executeAndReturnError: & error])
1662        NSLog(@"Growl registration failed");
1663    [appleScript release];
1664}
1665
1666- (void) checkUpdate: (id) sender
1667{
1668    [fPrefsController checkUpdate];
1669}
1670
1671- (void) prepareForUpdate: (NSNotification *) notification
1672{
1673    fUpdateInProgress = YES;
1674}
1675
1676@end
Note: See TracBrowser for help on using the repository browser.