source: trunk/macosx/Controller.m @ 450

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

Update the inspector when necessary through notifications, which will help once the wait check is added to the inspector.

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