source: trunk/macosx/Controller.m @ 416

Last change on this file since 416 was 416, checked in by livings124, 15 years ago

Reverse progress sort order for sort by state.

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