source: trunk/macosx/Controller.m @ 292

Last change on this file since 292 was 292, checked in by joshe, 16 years ago

Revert the last commit, it wasn't supposed to go on the trunk.

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