source: trunk/macosx/Controller.m @ 419

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

Now that sorting by stat sorts by progress, it should be resorted more often.

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