source: trunk/macosx/Controller.m @ 421

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

A lot of cleanup; remove cantfindaname once and for all.

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