source: trunk/macosx/Controller.m @ 449

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

A little bit more thorough check to avoid excess work.

  • Property svn:keywords set to Date Rev Author Id
File size: 52.9 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 449 2006-06-23 17:46:53Z 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(ratioSingleChange:)
207                    name: @"TorrentRatioChanged" object: nil];
208   
209    [nc addObserver: self selector: @selector(limitGlobalChange:)
210                    name: @"LimitGlobalChange" object: nil];
211   
212    [nc addObserver: self selector: @selector(ratioGlobalChange:)
213                    name: @"RatioGlobalChange" object: nil];
214   
215    [nc addObserver: self selector: @selector(checkWaitingForFinished:)
216                    name: @"TorrentFinishedDownloading" object: nil];
217   
218    [nc addObserver: self selector: @selector(startSettingChange:)
219                    name: @"StartSettingChange" 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    [fInfoController updateInfoForTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]];
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) ratioSingleChange: (NSNotification *) notification
942{
943    //update info for changed ratio setting
944    NSArray * torrents = [self torrentsAtIndexes: [fTableView selectedRowIndexes]];
945    if ([torrents containsObject: [notification object]])
946        [fInfoController updateInfoForTorrents: torrents];
947}
948
949- (void) checkWaitingForFinished: (NSNotification *) notification
950{
951    //don't try to start a transfer if there should be none waiting
952    if (![[fDefaults stringForKey: @"StartSetting"] isEqualToString: @"Wait"])
953        return;
954
955    int desiredActive = [fDefaults integerForKey: @"WaitToStartNumber"], active = 0;
956   
957    NSEnumerator * enumerator = [fTorrents objectEnumerator];
958    Torrent * torrent, * torrentToStart = nil;
959    while ((torrent = [enumerator nextObject]))
960    {
961        //ignore the torrent just stopped; for some reason it is not marked instantly as not active
962        if (torrent == [notification object])
963            continue;
964   
965        if ([torrent isActive])
966        {
967            if (![torrent isSeeding])
968            {
969                active++;
970                if (active >= desiredActive)
971                    return;
972            }
973        }
974        else
975        {
976            //use as next if it is waiting to start and either no previous is or the previous has later date
977            if ([torrent waitingToStart] && (!torrentToStart
978                || [[torrentToStart date] compare: [torrent date]] == NSOrderedDescending))
979                torrentToStart = torrent;
980        }
981    }
982   
983    //since it hasn't returned, the queue amount has not been met
984    if (torrentToStart)
985    {
986        [torrentToStart startTransfer];
987        [self updateUI: nil];
988    }
989}
990
991- (void) startSettingChange: (NSNotification *) notification
992{
993    NSString * startSetting = [fDefaults stringForKey: @"StartSetting"];
994   
995    if ([startSetting isEqualToString: @"Start"])
996    {
997        NSEnumerator * enumerator = [fTorrents objectEnumerator];
998        Torrent * torrent;
999        while ((torrent = [enumerator nextObject]))
1000            if ([torrent waitingToStart])
1001                [torrent startTransfer];
1002    }
1003    else if ([startSetting isEqualToString: @"Wait"])
1004    {
1005        NSMutableArray * waitingTorrents = [[NSMutableArray alloc] initWithCapacity: [fTorrents count]];
1006   
1007        int amountToStart = [fDefaults integerForKey: @"WaitToStartNumber"];
1008       
1009        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1010        Torrent * torrent;
1011        while ((torrent = [enumerator nextObject]))
1012        {
1013            if ([torrent isActive])
1014            {
1015                if (![torrent isSeeding])
1016                {
1017                    amountToStart--;
1018                    if (amountToStart <= 0)
1019                        break;
1020                }
1021            }
1022            else if ([torrent waitingToStart])
1023                [waitingTorrents addObject: torrent];
1024            else;
1025        }
1026       
1027        int waitingCount = [waitingTorrents count];
1028       
1029        if (amountToStart > 0 && waitingCount > 0)
1030        {
1031            if (amountToStart > waitingCount)
1032                amountToStart = waitingCount;
1033           
1034            //sort torrents by date to start earliest added
1035            if (amountToStart < waitingCount)
1036            {
1037                NSSortDescriptor * dateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1038                                                            @"date" ascending: YES] autorelease];
1039                NSArray * descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, nil];
1040               
1041                [waitingTorrents sortUsingDescriptors: descriptors];
1042                [descriptors release];
1043            }
1044           
1045            int i;
1046            for (i = 0; i < amountToStart; i++)
1047                [[waitingTorrents objectAtIndex: i] startTransfer];
1048        }
1049       
1050        [waitingTorrents release];
1051    }
1052    else;
1053   
1054    [self updateUI: nil];
1055    #warning reload inspector
1056}
1057
1058- (int) numberOfRowsInTableView: (NSTableView *) t
1059{
1060    return [fTorrents count];
1061}
1062
1063- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell
1064    forTableColumn: (NSTableColumn *) tableColumn row: (int) row
1065{
1066    [cell setTorrent: [fTorrents objectAtIndex: row]];
1067}
1068
1069- (BOOL) tableView: (NSTableView *) t acceptDrop:
1070    (id <NSDraggingInfo>) info row: (int) row dropOperation:
1071    (NSTableViewDropOperation) operation
1072{
1073    [self application: NSApp openFiles: [[[info draggingPasteboard]
1074        propertyListForType: NSFilenamesPboardType]
1075        pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]];
1076    return YES;
1077}
1078
1079- (NSDragOperation) tableView: (NSTableView *) t validateDrop:
1080    (id <NSDraggingInfo>) info proposedRow: (int) row
1081    proposedDropOperation: (NSTableViewDropOperation) operation
1082{
1083    NSPasteboard * pasteboard = [info draggingPasteboard];
1084    if (![[pasteboard types] containsObject: NSFilenamesPboardType]
1085            || [[[pasteboard propertyListForType: NSFilenamesPboardType]
1086        pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]] count] == 0)
1087        return NSDragOperationNone;
1088
1089    [fTableView setDropRow: [fTableView numberOfRows] dropOperation: NSTableViewDropAbove];
1090    return NSDragOperationGeneric;
1091}
1092
1093- (void) tableViewSelectionDidChange: (NSNotification *) notification
1094{
1095    [fInfoController updateInfoForTorrents: [self torrentsAtIndexes:
1096                                    [fTableView selectedRowIndexes]]];
1097}
1098
1099- (void) toggleStatusBar: (id) sender
1100{
1101    [self showStatusBar: !fStatusBarVisible animate: YES];
1102    [fDefaults setBool: fStatusBarVisible forKey: @"StatusBar"];
1103}
1104
1105- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
1106{
1107    if (show == fStatusBarVisible)
1108        return;
1109
1110    NSRect frame = [fWindow frame];
1111    float heightChange = [fStatusBar frame].size.height;
1112    if (!show)
1113        heightChange *= -1;
1114
1115    frame.size.height += heightChange;
1116    frame.origin.y -= heightChange;
1117       
1118    fStatusBarVisible = !fStatusBarVisible;
1119   
1120    //reloads stats
1121    [self updateUI: nil];
1122   
1123    //set views to not autoresize
1124    unsigned int statsMask = [fStatusBar autoresizingMask];
1125    unsigned int scrollMask = [fScrollView autoresizingMask];
1126    [fStatusBar setAutoresizingMask: 0];
1127    [fScrollView setAutoresizingMask: 0];
1128   
1129    [fWindow setFrame: frame display: YES animate: animate];
1130   
1131    //re-enable autoresize
1132    [fStatusBar setAutoresizingMask: statsMask];
1133    [fScrollView setAutoresizingMask: scrollMask];
1134   
1135    //change min size
1136    NSSize minSize = [fWindow contentMinSize];
1137    minSize.height += heightChange;
1138    [fWindow setContentMinSize: minSize];
1139}
1140
1141- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
1142    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
1143{
1144    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
1145
1146    if( [ident isEqualToString: TOOLBAR_OPEN] )
1147    {
1148        [item setLabel: @"Open"];
1149        [item setPaletteLabel: @"Open Torrent Files"];
1150        [item setToolTip: @"Open torrent files"];
1151        [item setImage: [NSImage imageNamed: @"Open.png"]];
1152        [item setTarget: self];
1153        [item setAction: @selector( openShowSheet: )];
1154    }
1155    else if( [ident isEqualToString: TOOLBAR_REMOVE] )
1156    {
1157        [item setLabel: @"Remove"];
1158        [item setPaletteLabel: @"Remove Selected"];
1159        [item setToolTip: @"Remove selected transfers"];
1160        [item setImage: [NSImage imageNamed: @"Remove.png"]];
1161        [item setTarget: self];
1162        [item setAction: @selector( removeNoDelete: )];
1163    }
1164    else if( [ident isEqualToString: TOOLBAR_INFO] )
1165    {
1166        [item setLabel: @"Inspector"];
1167        [item setPaletteLabel: @"Show/Hide Inspector"];
1168        [item setToolTip: @"Display torrent inspector"];
1169        [item setImage: [NSImage imageNamed: @"Info.png"]];
1170        [item setTarget: self];
1171        [item setAction: @selector( showInfo: )];
1172    }
1173    else if( [ident isEqualToString: TOOLBAR_PAUSE_ALL] )
1174    {
1175        [item setLabel: @"Pause All"];
1176        [item setPaletteLabel: [item label]];
1177        [item setToolTip: @"Pause all transfers"];
1178        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
1179        [item setTarget: self];
1180        [item setAction: @selector( stopAllTorrents: )];
1181    }
1182    else if( [ident isEqualToString: TOOLBAR_RESUME_ALL] )
1183    {
1184        [item setLabel: @"Resume All"];
1185        [item setPaletteLabel: [item label]];
1186        [item setToolTip: @"Resume all transfers"];
1187        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
1188        [item setTarget: self];
1189        [item setAction: @selector( resumeAllTorrents: )];
1190    }
1191    else if( [ident isEqualToString: TOOLBAR_PAUSE_SELECTED] )
1192    {
1193        [item setLabel: @"Pause"];
1194        [item setPaletteLabel: @"Pause Selected"];
1195        [item setToolTip: @"Pause selected transfers"];
1196        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
1197        [item setTarget: self];
1198        [item setAction: @selector( stopTorrent: )];
1199    }
1200    else if( [ident isEqualToString: TOOLBAR_RESUME_SELECTED] )
1201    {
1202        [item setLabel: @"Resume"];
1203        [item setPaletteLabel: @"Resume Selected"];
1204        [item setToolTip: @"Resume selected transfers"];
1205        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
1206        [item setTarget: self];
1207        [item setAction: @selector( resumeTorrent: )];
1208    }
1209    else
1210    {
1211        [item release];
1212        return nil;
1213    }
1214
1215    return item;
1216}
1217
1218- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
1219{
1220    return [NSArray arrayWithObjects:
1221            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1222            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
1223            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
1224            TOOLBAR_INFO,
1225            NSToolbarSeparatorItemIdentifier,
1226            NSToolbarSpaceItemIdentifier,
1227            NSToolbarFlexibleSpaceItemIdentifier,
1228            NSToolbarCustomizeToolbarItemIdentifier, nil];
1229}
1230
1231- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
1232{
1233    return [NSArray arrayWithObjects:
1234            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1235            NSToolbarSeparatorItemIdentifier,
1236            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
1237            NSToolbarFlexibleSpaceItemIdentifier,
1238            TOOLBAR_INFO, nil];
1239}
1240
1241- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
1242{
1243    NSString * ident = [toolbarItem itemIdentifier];
1244
1245    //enable remove item
1246    if ([ident isEqualToString: TOOLBAR_REMOVE])
1247        return [fTableView numberOfSelectedRows] > 0;
1248
1249    //enable pause all item
1250    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
1251    {
1252        Torrent * torrent;
1253        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1254        while ((torrent = [enumerator nextObject]))
1255            if ([torrent isActive])
1256                return YES;
1257        return NO;
1258    }
1259
1260    //enable resume all item
1261    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
1262    {
1263        Torrent * torrent;
1264        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1265        while ((torrent = [enumerator nextObject]))
1266            if ([torrent isPaused])
1267                return YES;
1268        return NO;
1269    }
1270
1271    //enable pause item
1272    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
1273    {
1274        Torrent * torrent;
1275        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1276        unsigned int i;
1277       
1278        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1279            if ([[fTorrents objectAtIndex: i] isActive])
1280                return YES;
1281        return NO;
1282    }
1283   
1284    //enable resume item
1285    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
1286    {
1287        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1288        unsigned int i;
1289       
1290        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1291            if ([[fTorrents objectAtIndex: i] isPaused])
1292                return YES;
1293        return NO;
1294    }
1295
1296    return YES;
1297}
1298
1299- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
1300{
1301    SEL action = [menuItem action];
1302
1303    //only enable some items if it is in a context menu or the window is useable
1304    BOOL canUseMenu = [[[menuItem menu] title] isEqualToString: @"Context"]
1305                        || [fWindow isKeyWindow];
1306
1307    //enable show info
1308    if (action == @selector(showInfo:))
1309    {
1310        NSString * title = [[fInfoController window] isVisible] ? @"Hide Inspector" : @"Show Inspector";
1311        if (![[menuItem title] isEqualToString: title])
1312                [menuItem setTitle: title];
1313
1314        return YES;
1315    }
1316   
1317    if (action == @selector(setInfoTab:))
1318        return [[fInfoController window] isVisible];
1319   
1320    //enable toggle status bar
1321    if (action == @selector(toggleStatusBar:))
1322    {
1323        NSString * title = fStatusBarVisible ? @"Hide Status Bar" : @"Show Status Bar";
1324        if (![[menuItem title] isEqualToString: title])
1325                [menuItem setTitle: title];
1326
1327        return canUseMenu;
1328    }
1329
1330    //enable resume all item
1331    if (action == @selector(resumeAllTorrents:))
1332    {
1333        Torrent * torrent;
1334        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1335        while ((torrent = [enumerator nextObject]))
1336            if ([torrent isPaused])
1337                return YES;
1338        return NO;
1339    }
1340
1341    //enable pause all item
1342    if (action == @selector(stopAllTorrents:))
1343    {
1344        Torrent * torrent;
1345        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1346        while ((torrent = [enumerator nextObject]))
1347            if ([torrent isActive])
1348                return YES;
1349        return NO;
1350    }
1351
1352    if (action == @selector(revealFile:))
1353    {
1354        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1355    }
1356
1357    //enable remove items
1358    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
1359        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteBoth:))
1360    {
1361        BOOL active = NO,
1362            canDelete = action != @selector(removeDeleteTorrent:) && action != @selector(removeDeleteBoth:);
1363        Torrent * torrent;
1364        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1365        unsigned int i;
1366       
1367        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1368        {
1369            torrent = [fTorrents objectAtIndex: i];
1370            if (!active && [torrent isActive])
1371            {
1372                active = YES;
1373                if (canDelete)
1374                    break;
1375            }
1376            if (!canDelete && [torrent publicTorrent])
1377            {
1378                canDelete = YES;
1379                if (active)
1380                    break;
1381            }
1382        }
1383   
1384        //append or remove ellipsis when needed
1385        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
1386        if (active && [fDefaults boolForKey: @"CheckRemove"])
1387        {
1388            if (![title hasSuffix: ellipsis])
1389                [menuItem setTitle: [title stringByAppendingEllipsis]];
1390        }
1391        else
1392        {
1393            if ([title hasSuffix: ellipsis])
1394                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
1395        }
1396       
1397        return canUseMenu && canDelete && [fTableView numberOfSelectedRows] > 0;
1398    }
1399
1400    //enable pause item
1401    if( action == @selector(stopTorrent:) )
1402    {
1403        if (!canUseMenu)
1404            return NO;
1405   
1406        Torrent * torrent;
1407        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1408        unsigned int i;
1409       
1410        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1411        {
1412            torrent = [fTorrents objectAtIndex: i];
1413            if ([torrent isActive])
1414                return YES;
1415        }
1416        return NO;
1417    }
1418   
1419    //enable resume item
1420    if( action == @selector(resumeTorrent:) )
1421    {
1422        if (!canUseMenu)
1423            return NO;
1424   
1425        Torrent * torrent;
1426        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1427        unsigned int i;
1428       
1429        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1430        {
1431            torrent = [fTorrents objectAtIndex: i];
1432            if ([torrent isPaused])
1433                return YES;
1434        }
1435        return NO;
1436    }
1437   
1438    //enable resume item
1439    if (action == @selector(setSort:) || (action == @selector(advancedChanged:)))
1440        return canUseMenu;
1441   
1442    //enable copy torrent file item
1443    if( action == @selector(copyTorrentFile:) )
1444    {
1445        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1446    }
1447
1448    return YES;
1449}
1450
1451- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument
1452{
1453    NSEnumerator * enumerator;
1454    Torrent * torrent;
1455    BOOL active;
1456
1457    switch( messageType )
1458    {
1459        case kIOMessageSystemWillSleep:
1460            /* Close all connections before going to sleep and remember
1461               we should resume when we wake up */
1462            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
1463
1464            /* Wait for torrents to stop (5 seconds timeout) */
1465            NSDate * start = [NSDate date];
1466            enumerator = [fTorrents objectEnumerator];
1467            while( ( torrent = [enumerator nextObject] ) )
1468            {
1469                while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
1470                        ![torrent isPaused] )
1471                {
1472                    usleep( 100000 );
1473                    [torrent update];
1474                }
1475            }
1476
1477            IOAllowPowerChange( fRootPort, (long) messageArgument );
1478            break;
1479
1480        case kIOMessageCanSystemSleep:
1481            /* Prevent idle sleep unless all paused */
1482            active = NO;
1483            enumerator = [fTorrents objectEnumerator];
1484            while ((torrent = [enumerator nextObject]))
1485                if ([torrent isActive])
1486                {
1487                    active = YES;
1488                    break;
1489                }
1490
1491            if (active)
1492                IOCancelPowerChange( fRootPort, (long) messageArgument );
1493            else
1494                IOAllowPowerChange( fRootPort, (long) messageArgument );
1495            break;
1496
1497        case kIOMessageSystemHasPoweredOn:
1498            /* Resume download after we wake up */
1499            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
1500            break;
1501    }
1502}
1503
1504- (NSRect) windowWillUseStandardFrame: (NSWindow *) w defaultFrame: (NSRect) defaultFrame
1505{
1506    NSRect windowRect = [fWindow frame];
1507    float newHeight = windowRect.size.height - [fScrollView frame].size.height
1508        + [fTorrents count] * ([fTableView rowHeight] + [fTableView intercellSpacing].height) + 30.0;
1509
1510    float minHeight = [fWindow minSize].height;
1511    if (newHeight < minHeight)
1512        newHeight = minHeight;
1513
1514    windowRect.origin.y -= (newHeight - windowRect.size.height);
1515    windowRect.size.height = newHeight;
1516
1517    return windowRect;
1518}
1519
1520- (void) showMainWindow: (id) sender
1521{
1522    [fWindow makeKeyAndOrderFront: nil];
1523}
1524
1525- (void) windowDidBecomeKey: (NSNotification *) notification
1526{
1527    fCompleted = 0;
1528}
1529
1530- (void) linkHomepage: (id) sender
1531{
1532    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
1533}
1534
1535- (void) linkForums: (id) sender
1536{
1537    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
1538}
1539
1540- (void) notifyGrowl: (NSString * ) file
1541{
1542    if (!fHasGrowl)
1543        return;
1544
1545    NSString * growlScript = [NSString stringWithFormat:
1546        @"tell application \"System Events\"\n"
1547         "  if exists application process \"GrowlHelperApp\" then\n"
1548         "    tell application \"GrowlHelperApp\"\n "
1549         "      notify with name \"Download Complete\""
1550         "        title \"Download Complete\""
1551         "        description \"%@\""
1552         "        application name \"Transmission\"\n"
1553         "    end tell\n"
1554         "  end if\n"
1555         "end tell", file];
1556   
1557    NSAppleScript * appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1558    NSDictionary * error;
1559    if (![appleScript executeAndReturnError: & error])
1560        NSLog(@"Growl notify failed");
1561    [appleScript release];
1562}
1563
1564- (void) growlRegister
1565{
1566    if (!fHasGrowl)
1567        return;
1568
1569    NSString * growlScript = [NSString stringWithFormat:
1570        @"tell application \"System Events\"\n"
1571         "  if exists application process \"GrowlHelperApp\" then\n"
1572         "    tell application \"GrowlHelperApp\"\n"
1573         "      register as application \"Transmission\" "
1574         "        all notifications {\"Download Complete\"}"
1575         "        default notifications {\"Download Complete\"}"
1576         "        icon of application \"Transmission\"\n"
1577         "    end tell\n"
1578         "  end if\n"
1579         "end tell"];
1580
1581    NSAppleScript * appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1582    NSDictionary * error;
1583    if (![appleScript executeAndReturnError: & error])
1584        NSLog(@"Growl registration failed");
1585    [appleScript release];
1586}
1587
1588- (void) checkUpdate: (id) sender
1589{
1590    [fPrefsController checkUpdate];
1591}
1592
1593- (void) prepareForUpdate: (NSNotification *) notification
1594{
1595    fUpdateInProgress = YES;
1596}
1597
1598@end
Note: See TracBrowser for help on using the repository browser.