source: trunk/macosx/Controller.m @ 415

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

sort by state will sort by progress afterwards

  • Property svn:keywords set to Date Rev Author Id
File size: 48.3 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 415 2006-06-20 14:46:15Z 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                    * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
795                                            @"progressSortKey" ascending: YES] autorelease];
796
797    NSArray * descriptors;
798    if ([fSortType isEqualToString: @"Name"])
799        descriptors = [[NSArray alloc] initWithObjects: nameDescriptor, dateDescriptor, nil];
800    else if ([fSortType isEqualToString: @"State"])
801    {
802        NSSortDescriptor * stateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
803                                                @"stateSortKey" ascending: NO] autorelease];
804        descriptors = [[NSArray alloc] initWithObjects: stateDescriptor, progressDescriptor,
805                                                            nameDescriptor, dateDescriptor, nil];
806    }
807    else if ([fSortType isEqualToString: @"Progress"])
808        descriptors = [[NSArray alloc] initWithObjects: progressDescriptor, nameDescriptor, dateDescriptor, nil];
809    else
810        descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, nameDescriptor, nil];
811
812    [fTorrents sortUsingDescriptors: descriptors];
813   
814    [descriptors release];
815   
816    [fTableView reloadData];
817   
818    //set selected rows if needed
819    if (selectedTorrents)
820    {
821        Torrent * torrent;
822        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
823        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
824        while ((torrent = [enumerator nextObject]))
825            [indexSet addIndex: [fTorrents indexOfObject: torrent]];
826       
827        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
828        [indexSet release];
829    }
830}
831
832- (void) setSort: (id) sender
833{
834    NSMenuItem * prevSortItem;
835    if ([fSortType isEqualToString: @"Name"])
836        prevSortItem = fNameSortItem;
837    else if ([fSortType isEqualToString: @"State"])
838        prevSortItem = fStateSortItem;
839    else if ([fSortType isEqualToString: @"Progress"])
840        prevSortItem = fProgressSortItem;
841    else
842        prevSortItem = fDateSortItem;
843   
844    if (sender != prevSortItem)
845    {
846        [prevSortItem setState: NSOffState];
847        [sender setState: NSOnState];
848
849        [fSortType release];
850        if (sender == fNameSortItem)
851            fSortType = [[NSString alloc] initWithString: @"Name"];
852        else if (sender == fStateSortItem)
853            fSortType = [[NSString alloc] initWithString: @"State"];
854        else if (sender == fProgressSortItem)
855            fSortType = [[NSString alloc] initWithString: @"Progress"];
856        else
857            fSortType = [[NSString alloc] initWithString: @"Date"];
858           
859        [fDefaults setObject: fSortType forKey: @"Sort"];
860    }
861
862    [self sortTorrents];
863}
864
865- (void) setLimitGlobalEnabled: (id) sender
866{
867    [fPrefsController setLimitEnabled: (sender == fUploadLimitItem || sender == fDownloadLimitItem)
868        type: (sender == fUploadLimitItem || sender == fUploadNoLimitItem) ? @"Upload" : @"Download"];
869}
870
871- (void) setQuickLimitGlobal: (id) sender
872{
873    [fPrefsController setQuickLimit: [[sender title] intValue]
874        type: [sender menu] == fUploadMenu ? @"Upload" : @"Download"];
875}
876
877- (void) limitGlobalChange: (NSNotification *) notification
878{
879    NSDictionary * dict = [notification object];
880   
881    NSMenuItem * limitItem, * noLimitItem;
882    if ([[dict objectForKey: @"Type"] isEqualToString: @"Upload"])
883    {
884        limitItem = fUploadLimitItem;
885        noLimitItem = fUploadNoLimitItem;
886    }
887    else
888    {
889        limitItem = fDownloadLimitItem;
890        noLimitItem = fDownloadNoLimitItem;
891    }
892   
893    BOOL enable = [[dict objectForKey: @"Enable"] boolValue];
894    [limitItem setState: enable ? NSOnState : NSOffState];
895    [noLimitItem setState: !enable ? NSOnState : NSOffState];
896   
897    [limitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
898                            [[dict objectForKey: @"Limit"] intValue]]];
899
900    [dict release];
901}
902
903- (void) setRatioGlobalEnabled: (id) sender
904{
905    [fPrefsController setRatioEnabled: sender == fRatioSetItem];
906}
907
908- (void) setQuickRatioGlobal: (id) sender
909{
910    [fPrefsController setQuickRatio: [[sender title] floatValue]];
911}
912
913- (void) ratioGlobalChange: (NSNotification *) notification
914{
915    NSDictionary * dict = [notification object];
916   
917    BOOL enable = [[dict objectForKey: @"Enable"] boolValue];
918    [fRatioSetItem setState: enable ? NSOnState : NSOffState];
919    [fRatioNotSetItem setState: !enable ? NSOnState : NSOffState];
920   
921    [fRatioSetItem setTitle: [NSString stringWithFormat: @"Stop at Ratio (%.2f)",
922                            [[dict objectForKey: @"Ratio"] floatValue]]];
923
924    [dict release];
925}
926
927- (void) ratioSingleChange: (NSNotification *) notification
928{
929    if ([fSortType isEqualToString: @"State"])
930        [self sortTorrents];
931   
932    //update info for changed ratio setting
933    NSArray * torrents = [self torrentsAtIndexes: [fTableView selectedRowIndexes]];
934    if ([torrents containsObject: [notification object]])
935        [fInfoController updateInfoForTorrents: torrents];
936}
937
938- (int) numberOfRowsInTableView: (NSTableView *) t
939{
940    return [fTorrents count];
941}
942
943- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell
944    forTableColumn: (NSTableColumn *) tableColumn row: (int) row
945{
946    [cell setTorrent: [fTorrents objectAtIndex: row]];
947}
948
949- (BOOL) tableView: (NSTableView *) t acceptDrop:
950    (id <NSDraggingInfo>) info row: (int) row dropOperation:
951    (NSTableViewDropOperation) operation
952{
953    [self application: NSApp openFiles: [[[info draggingPasteboard]
954        propertyListForType: NSFilenamesPboardType]
955        pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]];
956    return YES;
957}
958
959- (NSDragOperation) tableView: (NSTableView *) t validateDrop:
960    (id <NSDraggingInfo>) info proposedRow: (int) row
961    proposedDropOperation: (NSTableViewDropOperation) operation
962{
963    NSPasteboard * pasteboard = [info draggingPasteboard];
964    if (![[pasteboard types] containsObject: NSFilenamesPboardType]
965            || [[[pasteboard propertyListForType: NSFilenamesPboardType]
966        pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]] count] == 0)
967        return NSDragOperationNone;
968
969    [fTableView setDropRow: [fTableView numberOfRows] dropOperation: NSTableViewDropAbove];
970    return NSDragOperationGeneric;
971}
972
973- (void) tableViewSelectionDidChange: (NSNotification *) notification
974{
975    [fInfoController updateInfoForTorrents: [self torrentsAtIndexes:
976                                    [fTableView selectedRowIndexes]]];
977}
978
979- (void) toggleStatusBar: (id) sender
980{
981    [self showStatusBar: !fStatusBarVisible animate: YES];
982    [fDefaults setBool: fStatusBarVisible forKey: @"StatusBar"];
983}
984
985- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
986{
987    if (show == fStatusBarVisible)
988        return;
989
990    NSRect frame = [fWindow frame];
991    float heightChange = [fStatusBar frame].size.height;
992    if (!show)
993        heightChange *= -1;
994
995    frame.size.height += heightChange;
996    frame.origin.y -= heightChange;
997       
998    fStatusBarVisible = !fStatusBarVisible;
999   
1000    //reloads stats
1001    [self updateUI: nil];
1002   
1003    //set views to not autoresize
1004    unsigned int statsMask = [fStatusBar autoresizingMask];
1005    unsigned int scrollMask = [fScrollView autoresizingMask];
1006    [fStatusBar setAutoresizingMask: 0];
1007    [fScrollView setAutoresizingMask: 0];
1008   
1009    [fWindow setFrame: frame display: YES animate: animate];
1010   
1011    //re-enable autoresize
1012    [fStatusBar setAutoresizingMask: statsMask];
1013    [fScrollView setAutoresizingMask: scrollMask];
1014   
1015    //change min size
1016    NSSize minSize = [fWindow contentMinSize];
1017    minSize.height += heightChange;
1018    [fWindow setContentMinSize: minSize];
1019}
1020
1021- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
1022    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
1023{
1024    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
1025
1026    if( [ident isEqualToString: TOOLBAR_OPEN] )
1027    {
1028        [item setLabel: @"Open"];
1029        [item setPaletteLabel: @"Open Torrent Files"];
1030        [item setToolTip: @"Open torrent files"];
1031        [item setImage: [NSImage imageNamed: @"Open.png"]];
1032        [item setTarget: self];
1033        [item setAction: @selector( openShowSheet: )];
1034    }
1035    else if( [ident isEqualToString: TOOLBAR_REMOVE] )
1036    {
1037        [item setLabel: @"Remove"];
1038        [item setPaletteLabel: @"Remove Selected"];
1039        [item setToolTip: @"Remove selected transfers"];
1040        [item setImage: [NSImage imageNamed: @"Remove.png"]];
1041        [item setTarget: self];
1042        [item setAction: @selector( removeNoDelete: )];
1043    }
1044    else if( [ident isEqualToString: TOOLBAR_INFO] )
1045    {
1046        [item setLabel: @"Inspector"];
1047        [item setPaletteLabel: @"Show/Hide Inspector"];
1048        [item setToolTip: @"Display torrent inspector"];
1049        [item setImage: [NSImage imageNamed: @"Info.png"]];
1050        [item setTarget: self];
1051        [item setAction: @selector( showInfo: )];
1052    }
1053    else if( [ident isEqualToString: TOOLBAR_PAUSE_ALL] )
1054    {
1055        [item setLabel: @"Pause All"];
1056        [item setPaletteLabel: [item label]];
1057        [item setToolTip: @"Pause all transfers"];
1058        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
1059        [item setTarget: self];
1060        [item setAction: @selector( stopAllTorrents: )];
1061    }
1062    else if( [ident isEqualToString: TOOLBAR_RESUME_ALL] )
1063    {
1064        [item setLabel: @"Resume All"];
1065        [item setPaletteLabel: [item label]];
1066        [item setToolTip: @"Resume all transfers"];
1067        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
1068        [item setTarget: self];
1069        [item setAction: @selector( resumeAllTorrents: )];
1070    }
1071    else if( [ident isEqualToString: TOOLBAR_PAUSE_SELECTED] )
1072    {
1073        [item setLabel: @"Pause"];
1074        [item setPaletteLabel: @"Pause Selected"];
1075        [item setToolTip: @"Pause selected transfers"];
1076        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
1077        [item setTarget: self];
1078        [item setAction: @selector( stopTorrent: )];
1079    }
1080    else if( [ident isEqualToString: TOOLBAR_RESUME_SELECTED] )
1081    {
1082        [item setLabel: @"Resume"];
1083        [item setPaletteLabel: @"Resume Selected"];
1084        [item setToolTip: @"Resume selected transfers"];
1085        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
1086        [item setTarget: self];
1087        [item setAction: @selector( resumeTorrent: )];
1088    }
1089    else
1090    {
1091        [item release];
1092        return nil;
1093    }
1094
1095    return item;
1096}
1097
1098- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
1099{
1100    return [NSArray arrayWithObjects:
1101            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1102            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
1103            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
1104            TOOLBAR_INFO,
1105            NSToolbarSeparatorItemIdentifier,
1106            NSToolbarSpaceItemIdentifier,
1107            NSToolbarFlexibleSpaceItemIdentifier,
1108            NSToolbarCustomizeToolbarItemIdentifier, nil];
1109}
1110
1111- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
1112{
1113    return [NSArray arrayWithObjects:
1114            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1115            NSToolbarSeparatorItemIdentifier,
1116            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
1117            NSToolbarFlexibleSpaceItemIdentifier,
1118            TOOLBAR_INFO, nil];
1119}
1120
1121- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
1122{
1123    NSString * ident = [toolbarItem itemIdentifier];
1124
1125    //enable remove item
1126    if ([ident isEqualToString: TOOLBAR_REMOVE])
1127        return [fTableView numberOfSelectedRows] > 0;
1128
1129    //enable pause all item
1130    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
1131    {
1132        Torrent * torrent;
1133        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1134        while( ( torrent = [enumerator nextObject] ) )
1135            if( [torrent isActive] )
1136                return YES;
1137        return NO;
1138    }
1139
1140    //enable resume all item
1141    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
1142    {
1143        Torrent * torrent;
1144        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1145        while( ( torrent = [enumerator nextObject] ) )
1146            if( [torrent isPaused] )
1147                return YES;
1148        return NO;
1149    }
1150
1151    //enable pause item
1152    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
1153    {
1154        Torrent * torrent;
1155        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1156        unsigned int i;
1157       
1158        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1159        {
1160            torrent = [fTorrents objectAtIndex: i];
1161            if ([torrent isActive])
1162                return YES;
1163        }
1164        return NO;
1165    }
1166   
1167    //enable resume item
1168    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
1169    {
1170        Torrent * torrent;
1171        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1172        unsigned int i;
1173       
1174        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1175        {
1176            torrent = [fTorrents objectAtIndex: i];
1177            if ([torrent isPaused])
1178                return YES;
1179        }
1180        return NO;
1181    }
1182
1183    return YES;
1184}
1185
1186- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
1187{
1188    SEL action = [menuItem action];
1189
1190    //only enable some items if it is in a context menu or the window is useable
1191    BOOL canUseMenu = [[[menuItem menu] title] isEqualToString: @"Context"]
1192                        || [fWindow isKeyWindow];
1193
1194    //enable show info
1195    if (action == @selector(showInfo:))
1196    {
1197        NSString * title = [[fInfoController window] isVisible] ? @"Hide Inspector" : @"Show Inspector";
1198        if (![[menuItem title] isEqualToString: title])
1199                [menuItem setTitle: title];
1200
1201        return YES;
1202    }
1203   
1204    if (action == @selector(setInfoTab:))
1205        return [[fInfoController window] isVisible];
1206   
1207    //enable toggle status bar
1208    if (action == @selector(toggleStatusBar:))
1209    {
1210        NSString * title = fStatusBarVisible ? @"Hide Status Bar" : @"Show Status Bar";
1211        if (![[menuItem title] isEqualToString: title])
1212                [menuItem setTitle: title];
1213
1214        return canUseMenu;
1215    }
1216
1217    //enable resume all item
1218    if (action == @selector(resumeAllTorrents:))
1219    {
1220        Torrent * torrent;
1221        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1222        while( ( torrent = [enumerator nextObject] ) )
1223            if( [torrent isPaused] )
1224                return YES;
1225        return NO;
1226    }
1227
1228    //enable pause all item
1229    if (action == @selector(stopAllTorrents:))
1230    {
1231        Torrent * torrent;
1232        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1233        while( ( torrent = [enumerator nextObject] ) )
1234            if( [torrent isActive] )
1235                return YES;
1236        return NO;
1237    }
1238
1239    if (action == @selector(revealFile:))
1240    {
1241        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1242    }
1243
1244    //enable remove items
1245    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
1246        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteBoth:))
1247    {
1248        BOOL active = NO,
1249            canDelete = !(action == @selector(removeDeleteTorrent:)
1250                            || action == @selector(removeDeleteBoth:));
1251        Torrent * torrent;
1252        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1253        unsigned int i;
1254       
1255        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1256        {
1257            torrent = [fTorrents objectAtIndex: i];
1258            if (!active && [torrent isActive])
1259            {
1260                active = YES;
1261                if (canDelete)
1262                    break;
1263            }
1264            if (!canDelete && [torrent publicTorrent])
1265            {
1266                canDelete = YES;
1267                if (active)
1268                    break;
1269            }
1270        }
1271   
1272        //append or remove ellipsis when needed
1273        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
1274        if (active && [fDefaults boolForKey: @"CheckRemove"])
1275        {
1276            if (![title hasSuffix: ellipsis])
1277                [menuItem setTitle: [title stringByAppendingEllipsis]];
1278        }
1279        else
1280        {
1281            if ([title hasSuffix: ellipsis])
1282                [menuItem setTitle: [title substringToIndex:
1283                            [title rangeOfString: ellipsis].location]];
1284        }
1285       
1286        return canUseMenu && canDelete && [fTableView numberOfSelectedRows] > 0;
1287    }
1288
1289    //enable pause item
1290    if( action == @selector(stopTorrent:) )
1291    {
1292        if (!canUseMenu)
1293            return NO;
1294   
1295        Torrent * torrent;
1296        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1297        unsigned int i;
1298       
1299        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1300        {
1301            torrent = [fTorrents objectAtIndex: i];
1302            if ([torrent isActive])
1303                return YES;
1304        }
1305        return NO;
1306    }
1307   
1308    //enable resume item
1309    if( action == @selector(resumeTorrent:) )
1310    {
1311        if (!canUseMenu)
1312            return NO;
1313   
1314        Torrent * torrent;
1315        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1316        unsigned int i;
1317       
1318        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1319        {
1320            torrent = [fTorrents objectAtIndex: i];
1321            if ([torrent isPaused])
1322                return YES;
1323        }
1324        return NO;
1325    }
1326   
1327    //enable resume item
1328    if (action == @selector(setSort:) || (action == @selector(advancedChanged:)))
1329        return canUseMenu;
1330   
1331    //enable copy torrent file item
1332    if( action == @selector(copyTorrentFile:) )
1333    {
1334        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1335    }
1336
1337    return YES;
1338}
1339
1340- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument
1341{
1342    NSEnumerator * enumerator;
1343    Torrent * torrent;
1344    BOOL active;
1345
1346    switch( messageType )
1347    {
1348        case kIOMessageSystemWillSleep:
1349            /* Close all connections before going to sleep and remember
1350               we should resume when we wake up */
1351            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
1352
1353            /* Wait for torrents to stop (5 seconds timeout) */
1354            NSDate * start = [NSDate date];
1355            enumerator = [fTorrents objectEnumerator];
1356            while( ( torrent = [enumerator nextObject] ) )
1357            {
1358                while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
1359                        ![torrent isPaused] )
1360                {
1361                    usleep( 100000 );
1362                    [torrent update];
1363                }
1364            }
1365
1366            IOAllowPowerChange( fRootPort, (long) messageArgument );
1367            break;
1368
1369        case kIOMessageCanSystemSleep:
1370            /* Prevent idle sleep unless all paused */
1371            active = NO;
1372            enumerator = [fTorrents objectEnumerator];
1373            while ((torrent = [enumerator nextObject]))
1374                if ([torrent isActive])
1375                {
1376                    active = YES;
1377                    break;
1378                }
1379
1380            if (active)
1381                IOCancelPowerChange( fRootPort, (long) messageArgument );
1382            else
1383                IOAllowPowerChange( fRootPort, (long) messageArgument );
1384            break;
1385
1386        case kIOMessageSystemHasPoweredOn:
1387            /* Resume download after we wake up */
1388            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
1389            break;
1390    }
1391}
1392
1393- (NSRect) windowWillUseStandardFrame: (NSWindow *) w defaultFrame: (NSRect) defaultFrame
1394{
1395    NSRect windowRect = [fWindow frame];
1396    float newHeight = windowRect.size.height - [fScrollView frame].size.height
1397        + [fTorrents count] * ([fTableView rowHeight] + [fTableView intercellSpacing].height) + 30.0;
1398
1399    float minHeight = [fWindow minSize].height;
1400    if (newHeight < minHeight)
1401        newHeight = minHeight;
1402
1403    windowRect.origin.y -= (newHeight - windowRect.size.height);
1404    windowRect.size.height = newHeight;
1405
1406    return windowRect;
1407}
1408
1409- (void) showMainWindow: (id) sender
1410{
1411    [fWindow makeKeyAndOrderFront: nil];
1412}
1413
1414- (void) windowDidBecomeKey: (NSNotification *) notification
1415{
1416    fCompleted = 0;
1417}
1418
1419- (void) linkHomepage: (id) sender
1420{
1421    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
1422}
1423
1424- (void) linkForums: (id) sender
1425{
1426    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
1427}
1428
1429- (void) notifyGrowl: (NSString * ) file
1430{
1431    if (!fHasGrowl)
1432        return;
1433
1434    NSString * growlScript = [NSString stringWithFormat:
1435        @"tell application \"System Events\"\n"
1436         "  if exists application process \"GrowlHelperApp\" then\n"
1437         "    tell application \"GrowlHelperApp\"\n "
1438         "      notify with name \"Download Complete\""
1439         "        title \"Download Complete\""
1440         "        description \"%@\""
1441         "        application name \"Transmission\"\n"
1442         "    end tell\n"
1443         "  end if\n"
1444         "end tell", file];
1445   
1446    NSAppleScript * appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1447    NSDictionary * error;
1448    if (![appleScript executeAndReturnError: &error])
1449        NSLog(@"Growl notify failed");
1450    [appleScript release];
1451}
1452
1453- (void) growlRegister
1454{
1455    if (!fHasGrowl)
1456        return;
1457
1458    NSString * growlScript = [NSString stringWithFormat:
1459        @"tell application \"System Events\"\n"
1460         "  if exists application process \"GrowlHelperApp\" then\n"
1461         "    tell application \"GrowlHelperApp\"\n"
1462         "      register as application \"Transmission\" "
1463         "        all notifications {\"Download Complete\"}"
1464         "        default notifications {\"Download Complete\"}"
1465         "        icon of application \"Transmission\"\n"
1466         "    end tell\n"
1467         "  end if\n"
1468         "end tell"];
1469
1470    NSAppleScript * appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1471    NSDictionary * error;
1472    if (![appleScript executeAndReturnError: &error])
1473        NSLog(@"Growl registration failed");
1474    [appleScript release];
1475}
1476
1477- (void) checkUpdate: (id) sender
1478{
1479    [fPrefsController checkUpdate];
1480}
1481
1482- (void) prepareForUpdate: (NSNotification *) notification
1483{
1484    fUpdateInProgress = YES;
1485}
1486
1487@end
Note: See TracBrowser for help on using the repository browser.