source: trunk/macosx/Controller.m @ 489

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

A little better behavior with queueing when adding seeding torrents.

  • Property svn:keywords set to Date Rev Author Id
File size: 55.6 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 489 2006-06-30 02:57:50Z 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        [torrent update];
342        [self attemptToStartAuto: torrent];
343        [fTorrents addObject: torrent];
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            [torrent update];
391            [self attemptToStartAuto: torrent];
392            [fTorrents addObject: torrent];
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    #warning expand upon
1108    if ([torrent progress] >= 1.0)
1109    {
1110        [torrent startTransfer];
1111        return;
1112    }
1113
1114    if (![torrent waitingToStart])
1115        return;
1116   
1117    NSString * startSetting = [fDefaults stringForKey: @"StartSetting"];
1118    if ([startSetting isEqualToString: @"Wait"])
1119    {
1120        int desiredActive = [fDefaults integerForKey: @"WaitToStartNumber"];
1121       
1122        Torrent * tempTorrent;
1123        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1124        while ((tempTorrent = [enumerator nextObject]))
1125            if ([tempTorrent isActive] && ![tempTorrent isSeeding])
1126            {
1127                desiredActive--;
1128                if (desiredActive <= 0)
1129                    return;
1130            }
1131       
1132        [torrent startTransfer];
1133    }
1134    else if ([startSetting isEqualToString: @"Start"])
1135        [torrent startTransfer];
1136    else;
1137}
1138
1139- (void) reloadInspector: (NSNotification *) notification
1140{
1141    [fInfoController updateInfoForTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]];
1142}
1143
1144- (int) numberOfRowsInTableView: (NSTableView *) t
1145{
1146    return [fTorrents count];
1147}
1148
1149- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell
1150    forTableColumn: (NSTableColumn *) tableColumn row: (int) row
1151{
1152    [cell setTorrent: [fTorrents objectAtIndex: row]];
1153}
1154
1155- (BOOL) tableView: (NSTableView *) t acceptDrop:
1156    (id <NSDraggingInfo>) info row: (int) row dropOperation:
1157    (NSTableViewDropOperation) operation
1158{
1159    [self application: NSApp openFiles: [[[info draggingPasteboard]
1160        propertyListForType: NSFilenamesPboardType]
1161        pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]];
1162    return YES;
1163}
1164
1165- (NSDragOperation) tableView: (NSTableView *) t validateDrop:
1166    (id <NSDraggingInfo>) info proposedRow: (int) row
1167    proposedDropOperation: (NSTableViewDropOperation) operation
1168{
1169    NSPasteboard * pasteboard = [info draggingPasteboard];
1170    if (![[pasteboard types] containsObject: NSFilenamesPboardType]
1171            || [[[pasteboard propertyListForType: NSFilenamesPboardType]
1172        pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]] count] == 0)
1173        return NSDragOperationNone;
1174
1175    [fTableView setDropRow: [fTableView numberOfRows] dropOperation: NSTableViewDropAbove];
1176    return NSDragOperationGeneric;
1177}
1178
1179- (void) tableViewSelectionDidChange: (NSNotification *) notification
1180{
1181    [self reloadInspector: nil];
1182}
1183
1184- (void) toggleStatusBar: (id) sender
1185{
1186    [self showStatusBar: !fStatusBarVisible animate: YES];
1187    [fDefaults setBool: fStatusBarVisible forKey: @"StatusBar"];
1188}
1189
1190- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
1191{
1192    if (show == fStatusBarVisible)
1193        return;
1194
1195    NSRect frame = [fWindow frame];
1196    float heightChange = [fStatusBar frame].size.height;
1197    if (!show)
1198        heightChange *= -1;
1199
1200    frame.size.height += heightChange;
1201    frame.origin.y -= heightChange;
1202       
1203    fStatusBarVisible = !fStatusBarVisible;
1204   
1205    //reloads stats
1206    [self updateUI: nil];
1207   
1208    //set views to not autoresize
1209    unsigned int statsMask = [fStatusBar autoresizingMask];
1210    unsigned int scrollMask = [fScrollView autoresizingMask];
1211    [fStatusBar setAutoresizingMask: 0];
1212    [fScrollView setAutoresizingMask: 0];
1213   
1214    [fWindow setFrame: frame display: YES animate: animate];
1215   
1216    //re-enable autoresize
1217    [fStatusBar setAutoresizingMask: statsMask];
1218    [fScrollView setAutoresizingMask: scrollMask];
1219   
1220    //change min size
1221    NSSize minSize = [fWindow contentMinSize];
1222    minSize.height += heightChange;
1223    [fWindow setContentMinSize: minSize];
1224}
1225
1226- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
1227    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
1228{
1229    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
1230
1231    if( [ident isEqualToString: TOOLBAR_OPEN] )
1232    {
1233        [item setLabel: @"Open"];
1234        [item setPaletteLabel: @"Open Torrent Files"];
1235        [item setToolTip: @"Open torrent files"];
1236        [item setImage: [NSImage imageNamed: @"Open.png"]];
1237        [item setTarget: self];
1238        [item setAction: @selector( openShowSheet: )];
1239    }
1240    else if( [ident isEqualToString: TOOLBAR_REMOVE] )
1241    {
1242        [item setLabel: @"Remove"];
1243        [item setPaletteLabel: @"Remove Selected"];
1244        [item setToolTip: @"Remove selected transfers"];
1245        [item setImage: [NSImage imageNamed: @"Remove.png"]];
1246        [item setTarget: self];
1247        [item setAction: @selector( removeNoDelete: )];
1248    }
1249    else if( [ident isEqualToString: TOOLBAR_INFO] )
1250    {
1251        [item setLabel: @"Inspector"];
1252        [item setPaletteLabel: @"Show/Hide Inspector"];
1253        [item setToolTip: @"Display torrent inspector"];
1254        [item setImage: [NSImage imageNamed: @"Info.png"]];
1255        [item setTarget: self];
1256        [item setAction: @selector( showInfo: )];
1257    }
1258    else if( [ident isEqualToString: TOOLBAR_PAUSE_ALL] )
1259    {
1260        [item setLabel: @"Pause All"];
1261        [item setPaletteLabel: [item label]];
1262        [item setToolTip: @"Pause all transfers"];
1263        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
1264        [item setTarget: self];
1265        [item setAction: @selector( stopAllTorrents: )];
1266    }
1267    else if( [ident isEqualToString: TOOLBAR_RESUME_ALL] )
1268    {
1269        [item setLabel: @"Resume All"];
1270        [item setPaletteLabel: [item label]];
1271        [item setToolTip: @"Resume all transfers"];
1272        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
1273        [item setTarget: self];
1274        [item setAction: @selector( resumeAllTorrents: )];
1275    }
1276    else if( [ident isEqualToString: TOOLBAR_PAUSE_SELECTED] )
1277    {
1278        [item setLabel: @"Pause"];
1279        [item setPaletteLabel: @"Pause Selected"];
1280        [item setToolTip: @"Pause selected transfers"];
1281        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
1282        [item setTarget: self];
1283        [item setAction: @selector( stopTorrent: )];
1284    }
1285    else if( [ident isEqualToString: TOOLBAR_RESUME_SELECTED] )
1286    {
1287        [item setLabel: @"Resume"];
1288        [item setPaletteLabel: @"Resume Selected"];
1289        [item setToolTip: @"Resume selected transfers"];
1290        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
1291        [item setTarget: self];
1292        [item setAction: @selector( resumeTorrent: )];
1293    }
1294    else
1295    {
1296        [item release];
1297        return nil;
1298    }
1299
1300    return item;
1301}
1302
1303- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
1304{
1305    return [NSArray arrayWithObjects:
1306            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1307            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
1308            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
1309            TOOLBAR_INFO,
1310            NSToolbarSeparatorItemIdentifier,
1311            NSToolbarSpaceItemIdentifier,
1312            NSToolbarFlexibleSpaceItemIdentifier,
1313            NSToolbarCustomizeToolbarItemIdentifier, nil];
1314}
1315
1316- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
1317{
1318    return [NSArray arrayWithObjects:
1319            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1320            NSToolbarSeparatorItemIdentifier,
1321            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
1322            NSToolbarFlexibleSpaceItemIdentifier,
1323            TOOLBAR_INFO, nil];
1324}
1325
1326- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
1327{
1328    NSString * ident = [toolbarItem itemIdentifier];
1329
1330    //enable remove item
1331    if ([ident isEqualToString: TOOLBAR_REMOVE])
1332        return [fTableView numberOfSelectedRows] > 0;
1333
1334    //enable pause all item
1335    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
1336    {
1337        Torrent * torrent;
1338        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1339        while ((torrent = [enumerator nextObject]))
1340            if ([torrent isActive])
1341                return YES;
1342        return NO;
1343    }
1344
1345    //enable resume all item
1346    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
1347    {
1348        Torrent * torrent;
1349        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1350        while ((torrent = [enumerator nextObject]))
1351            if ([torrent isPaused])
1352                return YES;
1353        return NO;
1354    }
1355
1356    //enable pause item
1357    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
1358    {
1359        Torrent * torrent;
1360        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1361        unsigned int i;
1362       
1363        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1364            if ([[fTorrents objectAtIndex: i] isActive])
1365                return YES;
1366        return NO;
1367    }
1368   
1369    //enable resume item
1370    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
1371    {
1372        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1373        unsigned int i;
1374       
1375        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1376            if ([[fTorrents objectAtIndex: i] isPaused])
1377                return YES;
1378        return NO;
1379    }
1380
1381    return YES;
1382}
1383
1384- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
1385{
1386    SEL action = [menuItem action];
1387
1388    //only enable some items if it is in a context menu or the window is useable
1389    BOOL canUseMenu = [[[menuItem menu] title] isEqualToString: @"Context"]
1390                        || [fWindow isKeyWindow];
1391
1392    //enable show info
1393    if (action == @selector(showInfo:))
1394    {
1395        NSString * title = [[fInfoController window] isVisible] ? @"Hide Inspector" : @"Show Inspector";
1396        if (![[menuItem title] isEqualToString: title])
1397                [menuItem setTitle: title];
1398
1399        return YES;
1400    }
1401   
1402    if (action == @selector(setInfoTab:))
1403        return [[fInfoController window] isVisible];
1404   
1405    //enable toggle status bar
1406    if (action == @selector(toggleStatusBar:))
1407    {
1408        NSString * title = fStatusBarVisible ? @"Hide Status Bar" : @"Show Status Bar";
1409        if (![[menuItem title] isEqualToString: title])
1410                [menuItem setTitle: title];
1411
1412        return canUseMenu;
1413    }
1414
1415    //enable resume all item
1416    if (action == @selector(resumeAllTorrents:))
1417    {
1418        Torrent * torrent;
1419        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1420        while ((torrent = [enumerator nextObject]))
1421            if ([torrent isPaused])
1422                return YES;
1423        return NO;
1424    }
1425
1426    //enable pause all item
1427    if (action == @selector(stopAllTorrents:))
1428    {
1429        Torrent * torrent;
1430        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1431        while ((torrent = [enumerator nextObject]))
1432            if ([torrent isActive])
1433                return YES;
1434        return NO;
1435    }
1436
1437    if (action == @selector(revealFile:))
1438    {
1439        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1440    }
1441
1442    //enable remove items
1443    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
1444        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteBoth:))
1445    {
1446        BOOL warning = NO,
1447            onlyDownloading = [fDefaults boolForKey: @"CheckRemoveDownloading"],
1448            canDelete = action != @selector(removeDeleteTorrent:) && action != @selector(removeDeleteBoth:);
1449        Torrent * torrent;
1450        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1451        unsigned int i;
1452       
1453        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1454        {
1455            torrent = [fTorrents objectAtIndex: i];
1456            if (!warning && [torrent isActive])
1457            {
1458                warning = onlyDownloading ? ![torrent isSeeding] : YES;
1459                if (warning && canDelete)
1460                    break;
1461            }
1462            if (!canDelete && [torrent publicTorrent])
1463            {
1464                canDelete = YES;
1465                if (warning)
1466                    break;
1467            }
1468        }
1469   
1470        //append or remove ellipsis when needed
1471        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
1472        if (warning && [fDefaults boolForKey: @"CheckRemove"])
1473        {
1474            if (![title hasSuffix: ellipsis])
1475                [menuItem setTitle: [title stringByAppendingEllipsis]];
1476        }
1477        else
1478        {
1479            if ([title hasSuffix: ellipsis])
1480                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
1481        }
1482       
1483        return canUseMenu && canDelete && [fTableView numberOfSelectedRows] > 0;
1484    }
1485
1486    //enable pause item
1487    if( action == @selector(stopTorrent:) )
1488    {
1489        if (!canUseMenu)
1490            return NO;
1491   
1492        Torrent * torrent;
1493        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1494        unsigned int i;
1495       
1496        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1497        {
1498            torrent = [fTorrents objectAtIndex: i];
1499            if ([torrent isActive])
1500                return YES;
1501        }
1502        return NO;
1503    }
1504   
1505    //enable resume item
1506    if( action == @selector(resumeTorrent:) )
1507    {
1508        if (!canUseMenu)
1509            return NO;
1510   
1511        Torrent * torrent;
1512        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1513        unsigned int i;
1514       
1515        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1516        {
1517            torrent = [fTorrents objectAtIndex: i];
1518            if ([torrent isPaused])
1519                return YES;
1520        }
1521        return NO;
1522    }
1523   
1524    //enable resume item
1525    if (action == @selector(setSort:) || (action == @selector(advancedChanged:)))
1526        return canUseMenu;
1527   
1528    //enable copy torrent file item
1529    if( action == @selector(copyTorrentFile:) )
1530    {
1531        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1532    }
1533
1534    return YES;
1535}
1536
1537- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument
1538{
1539    NSEnumerator * enumerator;
1540    Torrent * torrent;
1541    BOOL active;
1542
1543    switch( messageType )
1544    {
1545        case kIOMessageSystemWillSleep:
1546            /* Close all connections before going to sleep and remember
1547               we should resume when we wake up */
1548            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
1549
1550            /* Wait for torrents to stop (5 seconds timeout) */
1551            NSDate * start = [NSDate date];
1552            enumerator = [fTorrents objectEnumerator];
1553            while( ( torrent = [enumerator nextObject] ) )
1554            {
1555                while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
1556                        ![torrent isPaused] )
1557                {
1558                    usleep( 100000 );
1559                    [torrent update];
1560                }
1561            }
1562
1563            IOAllowPowerChange( fRootPort, (long) messageArgument );
1564            break;
1565
1566        case kIOMessageCanSystemSleep:
1567            /* Prevent idle sleep unless all paused */
1568            active = NO;
1569            enumerator = [fTorrents objectEnumerator];
1570            while ((torrent = [enumerator nextObject]))
1571                if ([torrent isActive])
1572                {
1573                    active = YES;
1574                    break;
1575                }
1576
1577            if (active)
1578                IOCancelPowerChange( fRootPort, (long) messageArgument );
1579            else
1580                IOAllowPowerChange( fRootPort, (long) messageArgument );
1581            break;
1582
1583        case kIOMessageSystemHasPoweredOn:
1584            /* Resume download after we wake up */
1585            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
1586            break;
1587    }
1588}
1589
1590- (NSRect) windowWillUseStandardFrame: (NSWindow *) w defaultFrame: (NSRect) defaultFrame
1591{
1592    NSRect windowRect = [fWindow frame];
1593    float newHeight = windowRect.size.height - [fScrollView frame].size.height
1594        + [fTorrents count] * ([fTableView rowHeight] + [fTableView intercellSpacing].height) + 30.0;
1595
1596    float minHeight = [fWindow minSize].height;
1597    if (newHeight < minHeight)
1598        newHeight = minHeight;
1599
1600    windowRect.origin.y -= (newHeight - windowRect.size.height);
1601    windowRect.size.height = newHeight;
1602
1603    return windowRect;
1604}
1605
1606- (void) showMainWindow: (id) sender
1607{
1608    [fWindow makeKeyAndOrderFront: nil];
1609}
1610
1611- (void) windowDidBecomeKey: (NSNotification *) notification
1612{
1613    fCompleted = 0;
1614}
1615
1616- (void) linkHomepage: (id) sender
1617{
1618    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
1619}
1620
1621- (void) linkForums: (id) sender
1622{
1623    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
1624}
1625
1626- (void) notifyGrowl: (NSString * ) file
1627{
1628    if (!fHasGrowl)
1629        return;
1630
1631    NSString * growlScript = [NSString stringWithFormat:
1632        @"tell application \"System Events\"\n"
1633         "  if exists application process \"GrowlHelperApp\" then\n"
1634         "    tell application \"GrowlHelperApp\"\n "
1635         "      notify with name \"Download Complete\""
1636         "        title \"Download Complete\""
1637         "        description \"%@\""
1638         "        application name \"Transmission\"\n"
1639         "    end tell\n"
1640         "  end if\n"
1641         "end tell", file];
1642   
1643    NSAppleScript * appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1644    NSDictionary * error;
1645    if (![appleScript executeAndReturnError: & error])
1646        NSLog(@"Growl notify failed");
1647    [appleScript release];
1648}
1649
1650- (void) growlRegister
1651{
1652    if (!fHasGrowl)
1653        return;
1654
1655    NSString * growlScript = [NSString stringWithFormat:
1656        @"tell application \"System Events\"\n"
1657         "  if exists application process \"GrowlHelperApp\" then\n"
1658         "    tell application \"GrowlHelperApp\"\n"
1659         "      register as application \"Transmission\" "
1660         "        all notifications {\"Download Complete\"}"
1661         "        default notifications {\"Download Complete\"}"
1662         "        icon of application \"Transmission\"\n"
1663         "    end tell\n"
1664         "  end if\n"
1665         "end tell"];
1666
1667    NSAppleScript * appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1668    NSDictionary * error;
1669    if (![appleScript executeAndReturnError: & error])
1670        NSLog(@"Growl registration failed");
1671    [appleScript release];
1672}
1673
1674- (void) checkUpdate: (id) sender
1675{
1676    [fPrefsController checkUpdate];
1677}
1678
1679- (void) prepareForUpdate: (NSNotification *) notification
1680{
1681    fUpdateInProgress = YES;
1682}
1683
1684@end
Note: See TracBrowser for help on using the repository browser.