source: branches/0.6/macosx/Controller.m @ 471

Last change on this file since 471 was 471, checked in by titer, 16 years ago

Backport of [442]

  • Property svn:keywords set to Date Rev Author Id
File size: 47.7 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 471 2006-06-25 19:35:19Z titer $
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 (![fWindow isVisible] && ![[fPrefsController window] isVisible])
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(stopTransfer)];
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 startTransfer];
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 startTransfer];
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    unsigned int i;
465    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
466        [[fTorrents objectAtIndex: i] startTransfer];
467   
468    [self updateUI: nil];
469    [self updateTorrentHistory];
470}
471
472- (void) stopTorrent: (id) sender
473{
474    [self stopTorrentWithIndex: [fTableView selectedRowIndexes]];
475}
476
477- (void) stopAllTorrents: (id) sender
478{
479    [self stopTorrentWithIndex: [NSIndexSet indexSetWithIndexesInRange:
480                                    NSMakeRange(0, [fTorrents count])]];
481}
482
483- (void) stopTorrentWithIndex: (NSIndexSet *) indexSet
484{
485    unsigned int i;
486    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
487        [[fTorrents objectAtIndex: i] stopTransfer];
488   
489    [self updateUI: nil];
490    [self updateTorrentHistory];
491}
492
493- (void) removeWithIndex: (NSIndexSet *) indexSet
494        deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
495{
496    NSArray * torrents = [[self torrentsAtIndexes: indexSet] retain];
497    int active = 0;
498
499    Torrent * torrent;
500    NSEnumerator * enumerator = [torrents objectEnumerator];
501    while ((torrent = [enumerator nextObject]))
502        if ([torrent isActive])
503            active++;
504
505    if( active > 0 && [fDefaults boolForKey: @"CheckRemove"] )
506    {
507        NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
508            torrents, @"Torrents",
509            [NSNumber numberWithBool: deleteData], @"DeleteData",
510            [NSNumber numberWithBool: deleteTorrent], @"DeleteTorrent", nil];
511
512        NSString * title, * message;
513       
514        int selected = [fTableView numberOfSelectedRows];
515        if (selected == 1)
516        {
517            title = [NSString stringWithFormat: @"Comfirm Removal of \"%@\"",
518                        [[fTorrents objectAtIndex: [fTableView selectedRow]] name]];
519            message = @"This transfer is active."
520                        " Once removed, continuing the transfer will require the torrent file."
521                        " Do you really want to remove it?";
522        }
523        else
524        {
525            title = [NSString stringWithFormat: @"Comfirm Removal of %d Transfers", selected];
526            if (selected == active)
527                message = [NSString stringWithFormat:
528                    @"There are %d active transfers.", active];
529            else
530                message = [NSString stringWithFormat:
531                    @"There are %d transfers (%d active).", selected, active];
532            message = [message stringByAppendingString:
533                @" Once removed, continuing the transfers will require the torrent files."
534                " Do you really want to remove them?"];
535        }
536
537        NSBeginAlertSheet(title, @"Remove", @"Cancel", nil, fWindow, self,
538            @selector(removeSheetDidEnd:returnCode:contextInfo:), nil, dict, message);
539    }
540    else
541        [self confirmRemove: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
542}
543
544- (void) removeSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode
545                        contextInfo: (NSDictionary *) dict
546{
547    [NSApp stopModal];
548
549    NSArray * torrents = [dict objectForKey: @"Torrents"];
550    BOOL deleteData = [[dict objectForKey: @"DeleteData"] boolValue],
551        deleteTorrent = [[dict objectForKey: @"DeleteTorrent"] boolValue];
552    [dict release];
553   
554    if (returnCode == NSAlertDefaultReturn)
555        [self confirmRemove: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
556    else
557        [torrents release];
558}
559
560- (void) confirmRemove: (NSArray *) torrents
561        deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
562{
563    Torrent * torrent;
564    NSEnumerator * enumerator = [torrents objectEnumerator];
565    while ((torrent = [enumerator nextObject]))
566    {
567        [torrent stopTransfer];
568
569        if (deleteData)
570            [torrent trashData];
571        if (deleteTorrent)
572            [torrent trashTorrent];
573
574        [torrent removeForever];
575        [fTorrents removeObject: torrent];
576    }
577    [torrents release];
578   
579    [self torrentNumberChanged];
580    [fTableView deselectAll: nil];
581    [self updateUI: nil];
582    [self updateTorrentHistory];
583}
584
585- (void) removeNoDelete: (id) sender
586{
587    [self removeWithIndex: [fTableView selectedRowIndexes] deleteData: NO deleteTorrent: NO];
588}
589
590- (void) removeDeleteData: (id) sender
591{
592    [self removeWithIndex: [fTableView selectedRowIndexes] deleteData: YES deleteTorrent: NO];
593}
594
595- (void) removeDeleteTorrent: (id) sender
596{
597    [self removeWithIndex: [fTableView selectedRowIndexes] deleteData: NO deleteTorrent: YES];
598}
599
600- (void) removeDeleteBoth: (id) sender
601{
602    [self removeWithIndex: [fTableView selectedRowIndexes] deleteData: YES deleteTorrent: YES];
603}
604
605- (void) copyTorrentFile: (id) sender
606{
607    [self copyTorrentFileForTorrents: [[NSMutableArray alloc] initWithArray:
608            [self torrentsAtIndexes: [fTableView selectedRowIndexes]]]];
609}
610
611- (void) copyTorrentFileForTorrents: (NSMutableArray *) torrents
612{
613    if ([torrents count] == 0)
614    {
615        [torrents release];
616        return;
617    }
618
619    Torrent * torrent = [torrents objectAtIndex: 0];
620
621    //warn user if torrent file can't be found
622    if (![[NSFileManager defaultManager] fileExistsAtPath: [torrent torrentLocation]])
623    {
624        NSAlert * alert = [[NSAlert alloc] init];
625        [alert addButtonWithTitle: @"OK"];
626        [alert setMessageText: [NSString stringWithFormat:
627                @"Copy of \"%@\" Cannot Be Created", [torrent name]]];
628        [alert setInformativeText: [NSString stringWithFormat:
629                @"The torrent file (%@) cannot be found.", [torrent torrentLocation]]];
630        [alert setAlertStyle: NSWarningAlertStyle];
631       
632        [alert runModal];
633       
634        [torrents removeObjectAtIndex: 0];
635        [self copyTorrentFileForTorrents: torrents];
636    }
637    else
638    {
639        NSSavePanel * panel = [NSSavePanel savePanel];
640        [panel setRequiredFileType: @"torrent"];
641        [panel setCanSelectHiddenExtension: NO];
642        [panel setExtensionHidden: NO];
643       
644        [panel beginSheetForDirectory: nil file: [torrent name]
645            modalForWindow: fWindow modalDelegate: self didEndSelector:
646            @selector( saveTorrentCopySheetClosed:returnCode:contextInfo: )
647            contextInfo: torrents];
648    }
649}
650
651- (void) saveTorrentCopySheetClosed: (NSSavePanel *) panel returnCode: (int) code
652    contextInfo: (NSMutableArray *) torrents
653{
654    //if save successful, copy torrent to new location with name of data file
655    if (code == NSOKButton)
656        [[NSFileManager defaultManager] copyPath: [[torrents objectAtIndex: 0] torrentLocation]
657                toPath: [panel filename] handler: nil];
658   
659    [torrents removeObjectAtIndex: 0];
660    [self performSelectorOnMainThread: @selector(copyTorrentFileForTorrents:)
661                withObject: torrents waitUntilDone: NO];
662}
663
664- (void) revealFile: (id) sender
665{
666    NSIndexSet * indexSet = [fTableView selectedRowIndexes];
667    unsigned int i;
668   
669    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
670        [[fTorrents objectAtIndex: i] revealData];
671}
672
673- (void) showPreferenceWindow: (id) sender
674{
675    NSWindow * window = [fPrefsController window];
676    if (![window isVisible])
677        [window center];
678
679    [window makeKeyAndOrderFront: nil];
680}
681
682- (void) showInfo: (id) sender
683{
684    if ([[fInfoController window] isVisible])
685        [[fInfoController window] performClose: nil];
686    else
687    {
688        [fInfoController updateInfoStats];
689        [[fInfoController window] orderFront: nil];
690    }
691}
692
693- (void) setInfoTab: (id) sender
694{
695    if (sender == fNextInfoTabItem)
696        [fInfoController setNextTab];
697    else
698        [fInfoController setPreviousTab];
699}
700
701- (void) updateUI: (NSTimer *) t
702{
703    NSEnumerator * enumerator = [fTorrents objectEnumerator];
704    Torrent * torrent;
705    while ((torrent = [enumerator nextObject]))
706    {
707        [torrent update];
708
709        if ([torrent justFinished])
710        {
711            //notifications
712            [self notifyGrowl: [torrent name]];
713            if (![fWindow isKeyWindow])
714                fCompleted++;
715        }
716    }
717
718    if ([fSortType isEqualToString: @"Progress"] || [fSortType isEqualToString: @"State"])
719        [self sortTorrents];
720    else
721        [fTableView reloadData];
722   
723    //update the global DL/UL rates
724    float downloadRate, uploadRate;
725    tr_torrentRates(fLib, & downloadRate, & uploadRate);
726    if (fStatusBarVisible)
727    {
728        [fTotalDLField setStringValue: [NSString stringForSpeed: downloadRate]];
729        [fTotalULField setStringValue: [NSString stringForSpeed: uploadRate]];
730    }
731
732    if ([[fInfoController window] isVisible])
733        [fInfoController updateInfoStats];
734
735    //badge dock
736    [fBadger updateBadgeWithCompleted: fCompleted
737        uploadRate: uploadRate downloadRate: downloadRate];
738}
739
740- (void) updateTorrentHistory
741{
742    NSMutableArray * history = [NSMutableArray
743        arrayWithCapacity: [fTorrents count]];
744
745    NSEnumerator * enumerator = [fTorrents objectEnumerator];
746    Torrent * torrent;
747    while( ( torrent = [enumerator nextObject] ) )
748        [history addObject: [torrent history]];
749
750    [fDefaults setObject: history forKey: @"History"];
751    [fDefaults synchronize];
752}
753
754- (void) sortTorrents
755{
756    //remember selected rows if needed
757    NSArray * selectedTorrents = nil;
758    int numSelected = [fTableView numberOfSelectedRows];
759    if (numSelected > 0 && numSelected < [fTorrents count])
760        selectedTorrents = [self torrentsAtIndexes: [fTableView selectedRowIndexes]];
761
762    NSSortDescriptor * nameDescriptor = [[[NSSortDescriptor alloc] initWithKey:
763                                            @"name" ascending: YES] autorelease],
764                    * dateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
765                                            @"date" ascending: YES] autorelease];
766
767    NSArray * descriptors;
768    if ([fSortType isEqualToString: @"Name"])
769        descriptors = [[NSArray alloc] initWithObjects: nameDescriptor, dateDescriptor, nil];
770    else if ([fSortType isEqualToString: @"State"])
771    {
772        NSSortDescriptor * stateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
773                                                @"stateSortKey" ascending: NO] autorelease],
774                        * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
775                                            @"progressSortKey" ascending: NO] autorelease];
776       
777        descriptors = [[NSArray alloc] initWithObjects: stateDescriptor, progressDescriptor,
778                                                            nameDescriptor, dateDescriptor, nil];
779    }
780    else if ([fSortType isEqualToString: @"Progress"])
781    {
782        NSSortDescriptor * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
783                                            @"progressSortKey" ascending: YES] autorelease];
784       
785        descriptors = [[NSArray alloc] initWithObjects: progressDescriptor, nameDescriptor, dateDescriptor, nil];
786    }
787    else
788        descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, nameDescriptor, nil];
789
790    [fTorrents sortUsingDescriptors: descriptors];
791   
792    [descriptors release];
793   
794    [fTableView reloadData];
795   
796    //set selected rows if needed
797    if (selectedTorrents)
798    {
799        Torrent * torrent;
800        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
801        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
802        while ((torrent = [enumerator nextObject]))
803            [indexSet addIndex: [fTorrents indexOfObject: torrent]];
804       
805        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
806        [indexSet release];
807    }
808}
809
810- (void) setSort: (id) sender
811{
812    NSMenuItem * prevSortItem;
813    if ([fSortType isEqualToString: @"Name"])
814        prevSortItem = fNameSortItem;
815    else if ([fSortType isEqualToString: @"State"])
816        prevSortItem = fStateSortItem;
817    else if ([fSortType isEqualToString: @"Progress"])
818        prevSortItem = fProgressSortItem;
819    else
820        prevSortItem = fDateSortItem;
821   
822    if (sender != prevSortItem)
823    {
824        [prevSortItem setState: NSOffState];
825        [sender setState: NSOnState];
826
827        [fSortType release];
828        if (sender == fNameSortItem)
829            fSortType = [[NSString alloc] initWithString: @"Name"];
830        else if (sender == fStateSortItem)
831            fSortType = [[NSString alloc] initWithString: @"State"];
832        else if (sender == fProgressSortItem)
833            fSortType = [[NSString alloc] initWithString: @"Progress"];
834        else
835            fSortType = [[NSString alloc] initWithString: @"Date"];
836           
837        [fDefaults setObject: fSortType forKey: @"Sort"];
838    }
839
840    [self sortTorrents];
841}
842
843- (void) setLimitGlobalEnabled: (id) sender
844{
845    [fPrefsController setLimitEnabled: (sender == fUploadLimitItem || sender == fDownloadLimitItem)
846        type: (sender == fUploadLimitItem || sender == fUploadNoLimitItem) ? @"Upload" : @"Download"];
847}
848
849- (void) setQuickLimitGlobal: (id) sender
850{
851    [fPrefsController setQuickLimit: [[sender title] intValue]
852        type: [sender menu] == fUploadMenu ? @"Upload" : @"Download"];
853}
854
855- (void) limitGlobalChange: (NSNotification *) notification
856{
857    NSDictionary * dict = [notification object];
858   
859    NSMenuItem * limitItem, * noLimitItem;
860    if ([[dict objectForKey: @"Type"] isEqualToString: @"Upload"])
861    {
862        limitItem = fUploadLimitItem;
863        noLimitItem = fUploadNoLimitItem;
864    }
865    else
866    {
867        limitItem = fDownloadLimitItem;
868        noLimitItem = fDownloadNoLimitItem;
869    }
870   
871    BOOL enable = [[dict objectForKey: @"Enable"] boolValue];
872    [limitItem setState: enable ? NSOnState : NSOffState];
873    [noLimitItem setState: !enable ? NSOnState : NSOffState];
874   
875    [limitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
876                            [[dict objectForKey: @"Limit"] intValue]]];
877
878    [dict release];
879}
880
881- (void) setRatioGlobalEnabled: (id) sender
882{
883    [fPrefsController setRatioEnabled: sender == fRatioSetItem];
884}
885
886- (void) setQuickRatioGlobal: (id) sender
887{
888    [fPrefsController setQuickRatio: [[sender title] floatValue]];
889}
890
891- (void) ratioGlobalChange: (NSNotification *) notification
892{
893    NSDictionary * dict = [notification object];
894   
895    BOOL enable = [[dict objectForKey: @"Enable"] boolValue];
896    [fRatioSetItem setState: enable ? NSOnState : NSOffState];
897    [fRatioNotSetItem setState: !enable ? NSOnState : NSOffState];
898   
899    [fRatioSetItem setTitle: [NSString stringWithFormat: @"Stop at Ratio (%.2f)",
900                            [[dict objectForKey: @"Ratio"] floatValue]]];
901
902    [dict release];
903}
904
905- (void) ratioSingleChange: (NSNotification *) notification
906{
907    //update info for changed ratio setting
908    NSArray * torrents = [self torrentsAtIndexes: [fTableView selectedRowIndexes]];
909    if ([torrents containsObject: [notification object]])
910        [fInfoController updateInfoForTorrents: torrents];
911}
912
913- (int) numberOfRowsInTableView: (NSTableView *) t
914{
915    return [fTorrents count];
916}
917
918- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell
919    forTableColumn: (NSTableColumn *) tableColumn row: (int) row
920{
921    [cell setTorrent: [fTorrents objectAtIndex: row]];
922}
923
924- (BOOL) tableView: (NSTableView *) t acceptDrop:
925    (id <NSDraggingInfo>) info row: (int) row dropOperation:
926    (NSTableViewDropOperation) operation
927{
928    [self application: NSApp openFiles: [[[info draggingPasteboard]
929        propertyListForType: NSFilenamesPboardType]
930        pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]];
931    return YES;
932}
933
934- (NSDragOperation) tableView: (NSTableView *) t validateDrop:
935    (id <NSDraggingInfo>) info proposedRow: (int) row
936    proposedDropOperation: (NSTableViewDropOperation) operation
937{
938    NSPasteboard * pasteboard = [info draggingPasteboard];
939    if (![[pasteboard types] containsObject: NSFilenamesPboardType]
940            || [[[pasteboard propertyListForType: NSFilenamesPboardType]
941        pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]] count] == 0)
942        return NSDragOperationNone;
943
944    [fTableView setDropRow: [fTableView numberOfRows] dropOperation: NSTableViewDropAbove];
945    return NSDragOperationGeneric;
946}
947
948- (void) tableViewSelectionDidChange: (NSNotification *) notification
949{
950    [fInfoController updateInfoForTorrents: [self torrentsAtIndexes:
951                                    [fTableView selectedRowIndexes]]];
952}
953
954- (void) toggleStatusBar: (id) sender
955{
956    [self showStatusBar: !fStatusBarVisible animate: YES];
957    [fDefaults setBool: fStatusBarVisible forKey: @"StatusBar"];
958}
959
960- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
961{
962    if (show == fStatusBarVisible)
963        return;
964
965    NSRect frame = [fWindow frame];
966    float heightChange = [fStatusBar frame].size.height;
967    if (!show)
968        heightChange *= -1;
969
970    frame.size.height += heightChange;
971    frame.origin.y -= heightChange;
972       
973    fStatusBarVisible = !fStatusBarVisible;
974   
975    //reloads stats
976    [self updateUI: nil];
977   
978    //set views to not autoresize
979    unsigned int statsMask = [fStatusBar autoresizingMask];
980    unsigned int scrollMask = [fScrollView autoresizingMask];
981    [fStatusBar setAutoresizingMask: 0];
982    [fScrollView setAutoresizingMask: 0];
983   
984    [fWindow setFrame: frame display: YES animate: animate];
985   
986    //re-enable autoresize
987    [fStatusBar setAutoresizingMask: statsMask];
988    [fScrollView setAutoresizingMask: scrollMask];
989   
990    //change min size
991    NSSize minSize = [fWindow contentMinSize];
992    minSize.height += heightChange;
993    [fWindow setContentMinSize: minSize];
994}
995
996- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
997    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
998{
999    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
1000
1001    if( [ident isEqualToString: TOOLBAR_OPEN] )
1002    {
1003        [item setLabel: @"Open"];
1004        [item setPaletteLabel: @"Open Torrent Files"];
1005        [item setToolTip: @"Open torrent files"];
1006        [item setImage: [NSImage imageNamed: @"Open.png"]];
1007        [item setTarget: self];
1008        [item setAction: @selector( openShowSheet: )];
1009    }
1010    else if( [ident isEqualToString: TOOLBAR_REMOVE] )
1011    {
1012        [item setLabel: @"Remove"];
1013        [item setPaletteLabel: @"Remove Selected"];
1014        [item setToolTip: @"Remove selected transfers"];
1015        [item setImage: [NSImage imageNamed: @"Remove.png"]];
1016        [item setTarget: self];
1017        [item setAction: @selector( removeNoDelete: )];
1018    }
1019    else if( [ident isEqualToString: TOOLBAR_INFO] )
1020    {
1021        [item setLabel: @"Inspector"];
1022        [item setPaletteLabel: @"Show/Hide Inspector"];
1023        [item setToolTip: @"Display torrent inspector"];
1024        [item setImage: [NSImage imageNamed: @"Info.png"]];
1025        [item setTarget: self];
1026        [item setAction: @selector( showInfo: )];
1027    }
1028    else if( [ident isEqualToString: TOOLBAR_PAUSE_ALL] )
1029    {
1030        [item setLabel: @"Pause All"];
1031        [item setPaletteLabel: [item label]];
1032        [item setToolTip: @"Pause all transfers"];
1033        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
1034        [item setTarget: self];
1035        [item setAction: @selector( stopAllTorrents: )];
1036    }
1037    else if( [ident isEqualToString: TOOLBAR_RESUME_ALL] )
1038    {
1039        [item setLabel: @"Resume All"];
1040        [item setPaletteLabel: [item label]];
1041        [item setToolTip: @"Resume all transfers"];
1042        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
1043        [item setTarget: self];
1044        [item setAction: @selector( resumeAllTorrents: )];
1045    }
1046    else if( [ident isEqualToString: TOOLBAR_PAUSE_SELECTED] )
1047    {
1048        [item setLabel: @"Pause"];
1049        [item setPaletteLabel: @"Pause Selected"];
1050        [item setToolTip: @"Pause selected transfers"];
1051        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
1052        [item setTarget: self];
1053        [item setAction: @selector( stopTorrent: )];
1054    }
1055    else if( [ident isEqualToString: TOOLBAR_RESUME_SELECTED] )
1056    {
1057        [item setLabel: @"Resume"];
1058        [item setPaletteLabel: @"Resume Selected"];
1059        [item setToolTip: @"Resume selected transfers"];
1060        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
1061        [item setTarget: self];
1062        [item setAction: @selector( resumeTorrent: )];
1063    }
1064    else
1065    {
1066        [item release];
1067        return nil;
1068    }
1069
1070    return item;
1071}
1072
1073- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
1074{
1075    return [NSArray arrayWithObjects:
1076            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1077            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
1078            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
1079            TOOLBAR_INFO,
1080            NSToolbarSeparatorItemIdentifier,
1081            NSToolbarSpaceItemIdentifier,
1082            NSToolbarFlexibleSpaceItemIdentifier,
1083            NSToolbarCustomizeToolbarItemIdentifier, nil];
1084}
1085
1086- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
1087{
1088    return [NSArray arrayWithObjects:
1089            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1090            NSToolbarSeparatorItemIdentifier,
1091            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
1092            NSToolbarFlexibleSpaceItemIdentifier,
1093            TOOLBAR_INFO, nil];
1094}
1095
1096- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
1097{
1098    NSString * ident = [toolbarItem itemIdentifier];
1099
1100    //enable remove item
1101    if ([ident isEqualToString: TOOLBAR_REMOVE])
1102        return [fTableView numberOfSelectedRows] > 0;
1103
1104    //enable pause all item
1105    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
1106    {
1107        Torrent * torrent;
1108        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1109        while ((torrent = [enumerator nextObject]))
1110            if ([torrent isActive])
1111                return YES;
1112        return NO;
1113    }
1114
1115    //enable resume all item
1116    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
1117    {
1118        Torrent * torrent;
1119        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1120        while ((torrent = [enumerator nextObject]))
1121            if ([torrent isPaused])
1122                return YES;
1123        return NO;
1124    }
1125
1126    //enable pause item
1127    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
1128    {
1129        Torrent * torrent;
1130        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1131        unsigned int i;
1132       
1133        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1134            if ([[fTorrents objectAtIndex: i] isActive])
1135                return YES;
1136        return NO;
1137    }
1138   
1139    //enable resume item
1140    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
1141    {
1142        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1143        unsigned int i;
1144       
1145        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1146            if ([[fTorrents objectAtIndex: i] isPaused])
1147                return YES;
1148        return NO;
1149    }
1150
1151    return YES;
1152}
1153
1154- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
1155{
1156    SEL action = [menuItem action];
1157
1158    //only enable some items if it is in a context menu or the window is useable
1159    BOOL canUseMenu = [[[menuItem menu] title] isEqualToString: @"Context"]
1160                        || [fWindow isKeyWindow];
1161
1162    //enable show info
1163    if (action == @selector(showInfo:))
1164    {
1165        NSString * title = [[fInfoController window] isVisible] ? @"Hide Inspector" : @"Show Inspector";
1166        if (![[menuItem title] isEqualToString: title])
1167                [menuItem setTitle: title];
1168
1169        return YES;
1170    }
1171   
1172    if (action == @selector(setInfoTab:))
1173        return [[fInfoController window] isVisible];
1174   
1175    //enable toggle status bar
1176    if (action == @selector(toggleStatusBar:))
1177    {
1178        NSString * title = fStatusBarVisible ? @"Hide Status Bar" : @"Show Status Bar";
1179        if (![[menuItem title] isEqualToString: title])
1180                [menuItem setTitle: title];
1181
1182        return canUseMenu;
1183    }
1184
1185    //enable resume all item
1186    if (action == @selector(resumeAllTorrents:))
1187    {
1188        Torrent * torrent;
1189        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1190        while ((torrent = [enumerator nextObject]))
1191            if ([torrent isPaused])
1192                return YES;
1193        return NO;
1194    }
1195
1196    //enable pause all item
1197    if (action == @selector(stopAllTorrents:))
1198    {
1199        Torrent * torrent;
1200        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1201        while ((torrent = [enumerator nextObject]))
1202            if ([torrent isActive])
1203                return YES;
1204        return NO;
1205    }
1206
1207    if (action == @selector(revealFile:))
1208    {
1209        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1210    }
1211
1212    //enable remove items
1213    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
1214        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteBoth:))
1215    {
1216        BOOL active = NO,
1217            canDelete = action != @selector(removeDeleteTorrent:) && action != @selector(removeDeleteBoth:);
1218        Torrent * torrent;
1219        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1220        unsigned int i;
1221       
1222        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1223        {
1224            torrent = [fTorrents objectAtIndex: i];
1225            if (!active && [torrent isActive])
1226            {
1227                active = YES;
1228                if (canDelete)
1229                    break;
1230            }
1231            if (!canDelete && [torrent publicTorrent])
1232            {
1233                canDelete = YES;
1234                if (active)
1235                    break;
1236            }
1237        }
1238   
1239        //append or remove ellipsis when needed
1240        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
1241        if (active && [fDefaults boolForKey: @"CheckRemove"])
1242        {
1243            if (![title hasSuffix: ellipsis])
1244                [menuItem setTitle: [title stringByAppendingEllipsis]];
1245        }
1246        else
1247        {
1248            if ([title hasSuffix: ellipsis])
1249                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
1250        }
1251       
1252        return canUseMenu && canDelete && [fTableView numberOfSelectedRows] > 0;
1253    }
1254
1255    //enable pause item
1256    if( action == @selector(stopTorrent:) )
1257    {
1258        if (!canUseMenu)
1259            return NO;
1260   
1261        Torrent * torrent;
1262        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1263        unsigned int i;
1264       
1265        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1266        {
1267            torrent = [fTorrents objectAtIndex: i];
1268            if ([torrent isActive])
1269                return YES;
1270        }
1271        return NO;
1272    }
1273   
1274    //enable resume item
1275    if( action == @selector(resumeTorrent:) )
1276    {
1277        if (!canUseMenu)
1278            return NO;
1279   
1280        Torrent * torrent;
1281        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1282        unsigned int i;
1283       
1284        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1285        {
1286            torrent = [fTorrents objectAtIndex: i];
1287            if ([torrent isPaused])
1288                return YES;
1289        }
1290        return NO;
1291    }
1292   
1293    //enable resume item
1294    if (action == @selector(setSort:) || (action == @selector(advancedChanged:)))
1295        return canUseMenu;
1296   
1297    //enable copy torrent file item
1298    if( action == @selector(copyTorrentFile:) )
1299    {
1300        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1301    }
1302
1303    return YES;
1304}
1305
1306- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument
1307{
1308    NSEnumerator * enumerator;
1309    Torrent * torrent;
1310    BOOL active;
1311
1312    switch( messageType )
1313    {
1314        case kIOMessageSystemWillSleep:
1315            /* Close all connections before going to sleep and remember
1316               we should resume when we wake up */
1317            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
1318
1319            /* Wait for torrents to stop (5 seconds timeout) */
1320            NSDate * start = [NSDate date];
1321            enumerator = [fTorrents objectEnumerator];
1322            while( ( torrent = [enumerator nextObject] ) )
1323            {
1324                while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
1325                        ![torrent isPaused] )
1326                {
1327                    usleep( 100000 );
1328                    [torrent update];
1329                }
1330            }
1331
1332            IOAllowPowerChange( fRootPort, (long) messageArgument );
1333            break;
1334
1335        case kIOMessageCanSystemSleep:
1336            /* Prevent idle sleep unless all paused */
1337            active = NO;
1338            enumerator = [fTorrents objectEnumerator];
1339            while ((torrent = [enumerator nextObject]))
1340                if ([torrent isActive])
1341                {
1342                    active = YES;
1343                    break;
1344                }
1345
1346            if (active)
1347                IOCancelPowerChange( fRootPort, (long) messageArgument );
1348            else
1349                IOAllowPowerChange( fRootPort, (long) messageArgument );
1350            break;
1351
1352        case kIOMessageSystemHasPoweredOn:
1353            /* Resume download after we wake up */
1354            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
1355            break;
1356    }
1357}
1358
1359- (NSRect) windowWillUseStandardFrame: (NSWindow *) w defaultFrame: (NSRect) defaultFrame
1360{
1361    NSRect windowRect = [fWindow frame];
1362    float newHeight = windowRect.size.height - [fScrollView frame].size.height
1363        + [fTorrents count] * ([fTableView rowHeight] + [fTableView intercellSpacing].height) + 30.0;
1364
1365    float minHeight = [fWindow minSize].height;
1366    if (newHeight < minHeight)
1367        newHeight = minHeight;
1368
1369    windowRect.origin.y -= (newHeight - windowRect.size.height);
1370    windowRect.size.height = newHeight;
1371
1372    return windowRect;
1373}
1374
1375- (void) showMainWindow: (id) sender
1376{
1377    [fWindow makeKeyAndOrderFront: nil];
1378}
1379
1380- (void) windowDidBecomeKey: (NSNotification *) notification
1381{
1382    fCompleted = 0;
1383}
1384
1385- (void) linkHomepage: (id) sender
1386{
1387    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
1388}
1389
1390- (void) linkForums: (id) sender
1391{
1392    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
1393}
1394
1395- (void) notifyGrowl: (NSString * ) file
1396{
1397    if (!fHasGrowl)
1398        return;
1399
1400    NSString * growlScript = [NSString stringWithFormat:
1401        @"tell application \"System Events\"\n"
1402         "  if exists application process \"GrowlHelperApp\" then\n"
1403         "    tell application \"GrowlHelperApp\"\n "
1404         "      notify with name \"Download Complete\""
1405         "        title \"Download Complete\""
1406         "        description \"%@\""
1407         "        application name \"Transmission\"\n"
1408         "    end tell\n"
1409         "  end if\n"
1410         "end tell", file];
1411   
1412    NSAppleScript * appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1413    NSDictionary * error;
1414    if (![appleScript executeAndReturnError: & error])
1415        NSLog(@"Growl notify failed");
1416    [appleScript release];
1417}
1418
1419- (void) growlRegister
1420{
1421    if (!fHasGrowl)
1422        return;
1423
1424    NSString * growlScript = [NSString stringWithFormat:
1425        @"tell application \"System Events\"\n"
1426         "  if exists application process \"GrowlHelperApp\" then\n"
1427         "    tell application \"GrowlHelperApp\"\n"
1428         "      register as application \"Transmission\" "
1429         "        all notifications {\"Download Complete\"}"
1430         "        default notifications {\"Download Complete\"}"
1431         "        icon of application \"Transmission\"\n"
1432         "    end tell\n"
1433         "  end if\n"
1434         "end tell"];
1435
1436    NSAppleScript * appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1437    NSDictionary * error;
1438    if (![appleScript executeAndReturnError: & error])
1439        NSLog(@"Growl registration failed");
1440    [appleScript release];
1441}
1442
1443- (void) checkUpdate: (id) sender
1444{
1445    [fPrefsController checkUpdate];
1446}
1447
1448- (void) prepareForUpdate: (NSNotification *) notification
1449{
1450    fUpdateInProgress = YES;
1451}
1452
1453@end
Note: See TracBrowser for help on using the repository browser.