source: trunk/macosx/Controller.m @ 458

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

Use the order value for queueing rather than the date.

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