source: trunk/macosx/Controller.m @ 384

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

Validate using ident instead of function name...it's more future-proof.

Transfers, not Torrents.

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