source: trunk/macosx/Controller.m @ 361

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

By popular demand: the return of "Remove Torrent File." It only applies to the files not moved to the private folder.

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