source: trunk/macosx/Controller.m @ 310

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

Add support to libtransmission and the MacOS X GUI for saving private copies of

torrent files.

  • Property svn:keywords set to Date Rev Author Id
File size: 45.6 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 310 2006-06-09 19:53:35Z 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                     deleteData: (BOOL) deleteData
528{
529    NSArray * torrents = [[self torrentsAtIndexes: indexSet] retain];
530    int active = 0;
531
532    Torrent * torrent;
533    NSEnumerator * enumerator = [torrents objectEnumerator];
534    while ((torrent = [enumerator nextObject]))
535        if ([torrent isActive])
536            active++;
537
538    if( active > 0 && [fDefaults boolForKey: @"CheckRemove"] )
539    {
540        NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
541            torrents, @"Torrents",
542            [NSNumber numberWithBool: deleteData], @"DeleteData",
543            nil];
544
545        NSString * title, * message;
546       
547        int selected = [fTableView numberOfSelectedRows];
548        if (selected == 1)
549        {
550            title = [NSString stringWithFormat: @"Comfirm Removal of \"%@\"",
551                        [[fTorrents objectAtIndex: [fTableView selectedRow]] name]];
552            message = @"This transfer is active."
553                        " Onced removed, continuing the transfer will require the torrent file."
554                        " 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 transfers.", active];
562            else
563                message = [NSString stringWithFormat:
564                    @"There are %d transfers (%d active).", selected, active];
565            message = [message stringByAppendingString:
566                @" Onced removed, continuing the transfers will require the torrent files."
567                " Do you really want to remove them?"];
568        }
569
570        NSBeginAlertSheet(title,
571            @"Remove", @"Cancel", nil, fWindow, self,
572            @selector(removeSheetDidEnd:returnCode:contextInfo:),
573            nil, dict, message);
574    }
575    else
576    {
577        [self confirmRemoveTorrents: torrents
578                deleteData: deleteData];
579    }
580}
581
582- (void) removeSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode
583                        contextInfo: (NSDictionary *) dict
584{
585    [NSApp stopModal];
586
587    NSArray * torrents = [dict objectForKey: @"Torrents"];
588    BOOL deleteData = [[dict objectForKey: @"DeleteData"] boolValue];
589    [dict release];
590   
591    if (returnCode == NSAlertDefaultReturn)
592    {
593        [self confirmRemoveTorrents: torrents
594            deleteData: deleteData];
595    }
596    else
597        [torrents release];
598}
599
600- (void) confirmRemoveTorrents: (NSArray *) torrents
601            deleteData: (BOOL) deleteData
602{
603    Torrent * torrent;
604    NSEnumerator * enumerator = [torrents objectEnumerator];
605    while ((torrent = [enumerator nextObject]))
606    {
607        [torrent stop];
608
609        if( deleteData )
610            [torrent trashData];
611
612        [torrent removeForever];
613        [fTorrents removeObject: torrent];
614    }
615    [torrents release];
616   
617    [self torrentNumberChanged];
618    [fTableView deselectAll: nil];
619    [self updateUI: nil];
620    [self updateTorrentHistory];
621}
622
623- (void) removeTorrent: (id) sender
624{
625    [self removeTorrentWithIndex: [fTableView selectedRowIndexes] deleteData: NO];
626}
627
628- (void) removeTorrentDeleteData: (id) sender
629{
630    [self removeTorrentWithIndex: [fTableView selectedRowIndexes] deleteData: YES];
631}
632
633- (void) revealFile: (id) sender
634{
635    Torrent * torrent;
636    NSIndexSet * indexSet = [fTableView selectedRowIndexes];
637    unsigned int i;
638   
639    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
640    {
641        torrent = [fTorrents objectAtIndex: i];
642        [torrent reveal];
643    }
644}
645
646- (void) showPreferenceWindow: (id) sender
647{
648    NSWindow * window = [fPrefsController window];
649    if (![window isVisible])
650        [window center];
651
652    [window makeKeyAndOrderFront: nil];
653}
654
655- (void) showInfo: (id) sender
656{
657    if ([[fInfoController window] isVisible])
658        [[fInfoController window] performClose: nil];
659    else
660    {
661        [fInfoController updateInfoStats];
662        [[fInfoController window] orderFront: nil];
663    }
664}
665
666- (void) setInfoTab: (id) sender
667{
668    if (sender == fNextInfoTabItem)
669        [fInfoController setNextTab];
670    else
671        [fInfoController setPreviousTab];
672}
673
674- (void) updateUI: (NSTimer *) t
675{
676    NSEnumerator * enumerator = [fTorrents objectEnumerator];
677    Torrent * torrent;
678    while( ( torrent = [enumerator nextObject] ) )
679    {
680        [torrent update];
681
682        if( [torrent justFinished] )
683        {
684            /* Notifications */
685            [self notifyGrowl: [torrent name]];
686            if( ![fWindow isKeyWindow] )
687                fCompleted++;
688           
689            if ([fSortType isEqualToString: @"State"])
690                [self sortTorrents];
691        }
692    }
693
694    if ([fSortType isEqualToString: @"Progress"])
695        [self sortTorrents];
696    else
697        [fTableView reloadData];
698   
699    //Update the global DL/UL rates
700    float downloadRate, uploadRate;
701    tr_torrentRates(fLib, & downloadRate, & uploadRate);
702    if (fStatusBarVisible)
703    {
704        [fTotalDLField setStringValue: [NSString stringForSpeed: downloadRate]];
705        [fTotalULField setStringValue: [NSString stringForSpeed: uploadRate]];
706    }
707
708    if ([[fInfoController window] isVisible])
709        [fInfoController updateInfoStats];
710
711    //badge dock
712    [fBadger updateBadgeWithCompleted: fCompleted
713        uploadRate: uploadRate downloadRate: downloadRate];
714}
715
716- (void) updateTorrentHistory
717{
718    NSMutableArray * history = [NSMutableArray
719        arrayWithCapacity: [fTorrents count]];
720
721    NSEnumerator * enumerator = [fTorrents objectEnumerator];
722    Torrent * torrent;
723    while( ( torrent = [enumerator nextObject] ) )
724        [history addObject: [torrent history]];
725
726    [fDefaults setObject: history forKey: @"History"];
727}
728
729- (void) sortTorrents
730{
731    //remember selected rows if needed
732    NSArray * selectedTorrents = nil;
733    int numSelected = [fTableView numberOfSelectedRows];
734    if (numSelected > 0 && numSelected < [fTorrents count])
735        selectedTorrents = [self torrentsAtIndexes: [fTableView selectedRowIndexes]];
736
737    NSSortDescriptor * nameDescriptor = [[[NSSortDescriptor alloc] initWithKey:
738                                            @"name" ascending: YES] autorelease],
739                    * dateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
740                                            @"date" ascending: YES] autorelease];
741    NSArray * descriptors;
742    if ([fSortType isEqualToString: @"Name"])
743        descriptors = [[NSArray alloc] initWithObjects: nameDescriptor, dateDescriptor, nil];
744    else if ([fSortType isEqualToString: @"State"])
745    {
746        NSSortDescriptor * stateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
747                                                @"stateSortKey" ascending: NO] autorelease];
748        descriptors = [[NSArray alloc] initWithObjects: stateDescriptor, nameDescriptor, dateDescriptor, nil];
749    }
750    else if ([fSortType isEqualToString: @"Progress"])
751    {
752        NSSortDescriptor * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
753                                                @"progressSortKey" ascending: YES] autorelease];
754        descriptors = [[NSArray alloc] initWithObjects: progressDescriptor, nameDescriptor, dateDescriptor, nil];
755    }
756    else
757        descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, nameDescriptor, nil];
758
759    [fTorrents sortUsingDescriptors: descriptors];
760   
761    [descriptors release];
762   
763    [fTableView reloadData];
764   
765    //set selected rows if needed
766    if (selectedTorrents)
767    {
768        Torrent * torrent;
769        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
770        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
771        while ((torrent = [enumerator nextObject]))
772            [indexSet addIndex: [fTorrents indexOfObject: torrent]];
773       
774        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
775        [indexSet release];
776    }
777}
778
779- (void) setSort: (id) sender
780{
781    NSMenuItem * prevSortItem;
782    if ([fSortType isEqualToString: @"Name"])
783        prevSortItem = fNameSortItem;
784    else if ([fSortType isEqualToString: @"State"])
785        prevSortItem = fStateSortItem;
786    else if ([fSortType isEqualToString: @"Progress"])
787        prevSortItem = fProgressSortItem;
788    else
789        prevSortItem = fDateSortItem;
790   
791    if (sender == prevSortItem)
792        return;
793   
794    [prevSortItem setState: NSOffState];
795    [sender setState: NSOnState];
796
797    [fSortType release];
798    if (sender == fNameSortItem)
799        fSortType = [[NSString alloc] initWithString: @"Name"];
800    else if (sender == fStateSortItem)
801        fSortType = [[NSString alloc] initWithString: @"State"];
802    else if (sender == fProgressSortItem)
803        fSortType = [[NSString alloc] initWithString: @"Progress"];
804    else
805        fSortType = [[NSString alloc] initWithString: @"Date"];
806       
807    [fDefaults setObject: fSortType forKey: @"Sort"];
808
809    [self sortTorrents];
810}
811
812- (void) setLimitGlobalEnabled: (id) sender
813{
814    [fPrefsController setLimitEnabled: (sender == fUploadLimitItem || sender == fDownloadLimitItem)
815                        type: (sender == fUploadLimitItem || sender == fUploadNoLimitItem)
816                            ? @"Upload" : @"Download"];
817}
818
819- (void) setQuickLimitGlobal: (id) sender
820{
821    NSString * title = [sender title];
822    [fPrefsController setQuickLimit: [[title substringToIndex: [title length]
823                                                    - [@" KB/s" length]] intValue]
824                    type: [sender menu] == fUploadMenu ? @"Upload" : @"Download"];
825}
826
827- (void) limitGlobalChange: (NSNotification *) notification
828{
829    NSDictionary * dict = [notification object];
830   
831    BOOL enable = [[dict objectForKey: @"Enable"] boolValue];
832    int limit = [[dict objectForKey: @"Limit"] intValue];
833   
834    NSMenuItem * limitItem, * noLimitItem;
835    if ([[dict objectForKey: @"Type"] isEqualToString: @"Upload"])
836    {
837        limitItem = fUploadLimitItem;
838        noLimitItem = fUploadNoLimitItem;
839    }
840    else
841    {
842        limitItem = fDownloadLimitItem;
843        noLimitItem = fDownloadNoLimitItem;
844    }
845    [limitItem setState: enable ? NSOnState : NSOffState];
846    [noLimitItem setState: !enable ? NSOnState : NSOffState];
847   
848    [limitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
849                            [[dict objectForKey: @"Limit"] intValue]]];
850
851    [dict release];
852}
853
854- (void) setRatioGlobalEnabled: (id) sender
855{
856    [fPrefsController setRatioEnabled: sender == fRatioSetItem];
857}
858
859- (void) setQuickRatioGlobal: (id) sender
860{
861    [fPrefsController setQuickRatio: [[sender title] floatValue]];
862}
863
864- (void) ratioGlobalChange: (NSNotification *) notification
865{
866    NSDictionary * dict = [notification object];
867   
868    BOOL enable = [[dict objectForKey: @"Enable"] boolValue];
869    [fRatioSetItem setState: enable ? NSOnState : NSOffState];
870    [fRatioNotSetItem setState: !enable ? NSOnState : NSOffState];
871   
872    [fRatioSetItem setTitle: [NSString stringWithFormat: @"Stop at Ratio (%.2f)",
873                            [[dict objectForKey: @"Ratio"] floatValue]]];
874
875    [dict release];
876}
877
878- (void) ratioSingleChange: (NSNotification *) notification
879{
880    if ([fSortType isEqualToString: @"State"])
881        [self sortTorrents];
882   
883    //update info for changed ratio setting
884    NSArray * torrents = [self torrentsAtIndexes: [fTableView selectedRowIndexes]];
885    if ([torrents containsObject: [notification object]])
886        [fInfoController updateInfoForTorrents: torrents];
887}
888
889- (int) numberOfRowsInTableView: (NSTableView *) t
890{
891    return [fTorrents count];
892}
893
894- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell
895    forTableColumn: (NSTableColumn *) tableColumn row: (int) row
896{
897    [cell setTorrent: [fTorrents objectAtIndex: row]];
898}
899
900- (BOOL) tableView: (NSTableView *) t acceptDrop:
901    (id <NSDraggingInfo>) info row: (int) row dropOperation:
902    (NSTableViewDropOperation) operation
903{
904    [self application: NSApp openFiles: [[[info draggingPasteboard]
905        propertyListForType: NSFilenamesPboardType]
906        pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]];
907    return YES;
908}
909
910- (NSDragOperation) tableView: (NSTableView *) t validateDrop:
911    (id <NSDraggingInfo>) info proposedRow: (int) row
912    proposedDropOperation: (NSTableViewDropOperation) operation
913{
914    NSPasteboard * pasteboard = [info draggingPasteboard];
915    if (![[pasteboard types] containsObject: NSFilenamesPboardType]
916            || [[[pasteboard propertyListForType: NSFilenamesPboardType]
917                pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]
918                count] == 0)
919        return NSDragOperationNone;
920
921    [fTableView setDropRow: [fTableView numberOfRows] dropOperation: NSTableViewDropAbove];
922    return NSDragOperationGeneric;
923}
924
925- (void) tableViewSelectionDidChange: (NSNotification *) notification
926{
927    [fInfoController updateInfoForTorrents: [self torrentsAtIndexes:
928                                    [fTableView selectedRowIndexes]]];
929}
930
931- (void) toggleStatusBar: (id) sender
932{
933    [self showStatusBar: !fStatusBarVisible animate: YES];
934    [fDefaults setBool: fStatusBarVisible forKey: @"StatusBar"];
935}
936
937- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
938{
939    if (show == fStatusBarVisible)
940        return;
941
942    NSRect frame = [fWindow frame];
943    float heightChange = [fStatusBar frame].size.height;
944    if (!show)
945        heightChange *= -1;
946
947    frame.size.height += heightChange;
948    frame.origin.y -= heightChange;
949       
950    fStatusBarVisible = !fStatusBarVisible;
951   
952    //reloads stats
953    [self torrentNumberChanged];
954    [self updateUI: nil];
955   
956    //set views to not autoresize
957    unsigned int statsMask = [fStatusBar autoresizingMask];
958    unsigned int scrollMask = [fScrollView autoresizingMask];
959    [fStatusBar setAutoresizingMask: 0];
960    [fScrollView setAutoresizingMask: 0];
961   
962    [fWindow setFrame: frame display: YES animate: animate];
963   
964    //re-enable autoresize
965    [fStatusBar setAutoresizingMask: statsMask];
966    [fScrollView setAutoresizingMask: scrollMask];
967   
968    //change min size
969    NSSize minSize = [fWindow contentMinSize];
970    minSize.height += heightChange;
971    [fWindow setContentMinSize: minSize];
972}
973
974- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
975    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
976{
977    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
978
979    if( [ident isEqualToString: TOOLBAR_OPEN] )
980    {
981        [item setLabel: @"Open"];
982        [item setPaletteLabel: @"Open Torrent Files"];
983        [item setToolTip: @"Open torrent files"];
984        [item setImage: [NSImage imageNamed: @"Open.png"]];
985        [item setTarget: self];
986        [item setAction: @selector( openShowSheet: )];
987    }
988    else if( [ident isEqualToString: TOOLBAR_REMOVE] )
989    {
990        [item setLabel: @"Remove"];
991        [item setPaletteLabel: @"Remove Selected"];
992        [item setToolTip: @"Remove selected torrents"];
993        [item setImage: [NSImage imageNamed: @"Remove.png"]];
994        [item setTarget: self];
995        [item setAction: @selector( removeTorrent: )];
996    }
997    else if( [ident isEqualToString: TOOLBAR_INFO] )
998    {
999        [item setLabel: @"Inspector"];
1000        [item setPaletteLabel: @"Show/Hide Inspector"];
1001        [item setToolTip: @"Display torrent inspector"];
1002        [item setImage: [NSImage imageNamed: @"Info.png"]];
1003        [item setTarget: self];
1004        [item setAction: @selector( showInfo: )];
1005    }
1006    else if( [ident isEqualToString: TOOLBAR_PAUSE_ALL] )
1007    {
1008        [item setLabel: @"Pause All"];
1009        [item setPaletteLabel: [item label]];
1010        [item setToolTip: @"Pause all torrents"];
1011        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
1012        [item setTarget: self];
1013        [item setAction: @selector( stopAllTorrents: )];
1014    }
1015    else if( [ident isEqualToString: TOOLBAR_RESUME_ALL] )
1016    {
1017        [item setLabel: @"Resume All"];
1018        [item setPaletteLabel: [item label]];
1019        [item setToolTip: @"Resume all torrents"];
1020        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
1021        [item setTarget: self];
1022        [item setAction: @selector( resumeAllTorrents: )];
1023    }
1024    else if( [ident isEqualToString: TOOLBAR_PAUSE_SELECTED] )
1025    {
1026        [item setLabel: @"Pause"];
1027        [item setPaletteLabel: @"Pause Selected"];
1028        [item setToolTip: @"Pause selected torrents"];
1029        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
1030        [item setTarget: self];
1031        [item setAction: @selector( stopTorrent: )];
1032    }
1033    else if( [ident isEqualToString: TOOLBAR_RESUME_SELECTED] )
1034    {
1035        [item setLabel: @"Resume"];
1036        [item setPaletteLabel: @"Resume Selected"];
1037        [item setToolTip: @"Resume selected torrents"];
1038        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
1039        [item setTarget: self];
1040        [item setAction: @selector( resumeTorrent: )];
1041    }
1042    else
1043    {
1044        [item release];
1045        return nil;
1046    }
1047
1048    return item;
1049}
1050
1051- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
1052{
1053    return [NSArray arrayWithObjects:
1054            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1055            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
1056            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
1057            TOOLBAR_INFO,
1058            NSToolbarSeparatorItemIdentifier,
1059            NSToolbarSpaceItemIdentifier,
1060            NSToolbarFlexibleSpaceItemIdentifier,
1061            NSToolbarCustomizeToolbarItemIdentifier, nil];
1062}
1063
1064- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
1065{
1066    return [NSArray arrayWithObjects:
1067            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1068            NSToolbarSeparatorItemIdentifier,
1069            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
1070            NSToolbarFlexibleSpaceItemIdentifier,
1071            TOOLBAR_INFO, nil];
1072}
1073
1074- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
1075{
1076    SEL action = [toolbarItem action];
1077
1078    //enable remove item
1079    if (action == @selector(removeTorrent:))
1080        return [fTableView numberOfSelectedRows] > 0;
1081
1082    //enable pause all item
1083    if (action == @selector(stopAllTorrents:))
1084    {
1085        Torrent * torrent;
1086        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1087        while( ( torrent = [enumerator nextObject] ) )
1088            if( [torrent isActive] )
1089                return YES;
1090        return NO;
1091    }
1092
1093    //enable resume all item
1094    if (action == @selector(resumeAllTorrents:))
1095    {
1096        Torrent * torrent;
1097        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1098        while( ( torrent = [enumerator nextObject] ) )
1099            if( [torrent isPaused] )
1100                return YES;
1101        return NO;
1102    }
1103
1104    //enable pause item
1105    if( action == @selector(stopTorrent:) )
1106    {
1107        Torrent * torrent;
1108        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1109        unsigned int i;
1110       
1111        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1112        {
1113            torrent = [fTorrents objectAtIndex: i];
1114            if ([torrent isActive])
1115                return YES;
1116        }
1117        return NO;
1118    }
1119   
1120    //enable resume item
1121    if( action == @selector(resumeTorrent:) )
1122    {
1123        Torrent * torrent;
1124        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1125        unsigned int i;
1126       
1127        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1128        {
1129            torrent = [fTorrents objectAtIndex: i];
1130            if ([torrent isPaused])
1131                return YES;
1132        }
1133        return NO;
1134    }
1135
1136    return YES;
1137}
1138
1139- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
1140{
1141    SEL action = [menuItem action];
1142
1143    //only enable some menus if window is useable
1144    BOOL canUseWindow = [fWindow isKeyWindow] && ![fToolbar customizationPaletteIsRunning];
1145
1146    //enable show info
1147    if (action == @selector(showInfo:))
1148    {
1149        NSString * title = [[fInfoController window] isVisible] ? @"Hide Inspector" : @"Show Inspector";
1150        if (![[menuItem title] isEqualToString: title])
1151                [menuItem setTitle: title];
1152
1153        return YES;
1154    }
1155   
1156    if (action == @selector(setInfoTab:))
1157        return [[fInfoController window] isVisible];
1158   
1159    //enable toggle status bar
1160    if (action == @selector(toggleStatusBar:))
1161    {
1162        NSString * title = fStatusBarVisible ? @"Hide Status Bar" : @"Show Status Bar";
1163        if (![[menuItem title] isEqualToString: title])
1164                [menuItem setTitle: title];
1165
1166        return canUseWindow;
1167    }
1168
1169    //enable resume all item
1170    if (action == @selector(resumeAllTorrents:))
1171    {
1172        Torrent * torrent;
1173        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1174        while( ( torrent = [enumerator nextObject] ) )
1175            if( [torrent isPaused] )
1176                return YES;
1177        return NO;
1178    }
1179
1180    //enable pause all item
1181    if (action == @selector(stopAllTorrents:))
1182    {
1183        Torrent * torrent;
1184        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1185        while( ( torrent = [enumerator nextObject] ) )
1186            if( [torrent isActive] )
1187                return YES;
1188        return NO;
1189    }
1190
1191    if (action == @selector(revealFile:))
1192    {
1193        return canUseWindow && [fTableView numberOfSelectedRows] > 0;
1194    }
1195
1196    //enable remove items
1197    if (action == @selector(removeTorrent:) || action == @selector(removeTorrentDeleteData:))
1198    {
1199        BOOL active = NO;
1200        Torrent * torrent;
1201        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1202        unsigned int i;
1203       
1204        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1205        {
1206            torrent = [fTorrents objectAtIndex: i];
1207            if ([torrent isActive])
1208            {
1209                active = YES;
1210                break;
1211            }
1212        }
1213   
1214        //append or remove ellipsis when needed
1215        NSString * title = [menuItem title];
1216        if (active && [fDefaults boolForKey: @"CheckRemove"])
1217        {
1218            if (![title hasSuffix: NS_ELLIPSIS])
1219                [menuItem setTitle: [title stringByAppendingString: NS_ELLIPSIS]];
1220        }
1221        else
1222        {
1223            if ([title hasSuffix: NS_ELLIPSIS])
1224                [menuItem setTitle: [title substringToIndex:
1225                            [title rangeOfString: NS_ELLIPSIS].location]];
1226        }
1227        return canUseWindow && [fTableView numberOfSelectedRows] > 0;
1228    }
1229
1230    //enable pause item
1231    if( action == @selector(stopTorrent:) )
1232    {
1233        if (!canUseWindow)
1234            return NO;
1235   
1236        Torrent * torrent;
1237        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1238        unsigned int i;
1239       
1240        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1241        {
1242            torrent = [fTorrents objectAtIndex: i];
1243            if ([torrent isActive])
1244                return YES;
1245        }
1246        return NO;
1247    }
1248   
1249    //enable resume item
1250    if( action == @selector(resumeTorrent:) )
1251    {
1252        if (!canUseWindow)
1253            return NO;
1254   
1255        Torrent * torrent;
1256        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1257        unsigned int i;
1258       
1259        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1260        {
1261            torrent = [fTorrents objectAtIndex: i];
1262            if ([torrent isPaused])
1263                return YES;
1264        }
1265        return NO;
1266    }
1267   
1268    //enable resume item
1269    if (action == @selector(setSort:) || (action == @selector(advancedChanged:)))
1270        return canUseWindow;
1271
1272    return YES;
1273}
1274
1275- (void) sleepCallBack: (natural_t) messageType argument:
1276                          (void *) messageArgument
1277{
1278    NSEnumerator * enumerator;
1279    Torrent * torrent;
1280    BOOL active;
1281
1282    switch( messageType )
1283    {
1284        case kIOMessageSystemWillSleep:
1285            /* Close all connections before going to sleep and remember
1286               we should resume when we wake up */
1287            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
1288
1289            /* Wait for torrents to stop (5 seconds timeout) */
1290            NSDate * start = [NSDate date];
1291            enumerator = [fTorrents objectEnumerator];
1292            while( ( torrent = [enumerator nextObject] ) )
1293            {
1294                while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
1295                        ![torrent isPaused] )
1296                {
1297                    usleep( 100000 );
1298                    [torrent update];
1299                }
1300            }
1301
1302            IOAllowPowerChange( fRootPort, (long) messageArgument );
1303            break;
1304
1305        case kIOMessageCanSystemSleep:
1306            /* Prevent idle sleep unless all paused */
1307            active = NO;
1308            enumerator = [fTorrents objectEnumerator];
1309            while ((torrent = [enumerator nextObject]))
1310                if ([torrent isActive])
1311                {
1312                    active = YES;
1313                    break;
1314                }
1315
1316            if (active)
1317                IOCancelPowerChange( fRootPort, (long) messageArgument );
1318            else
1319                IOAllowPowerChange( fRootPort, (long) messageArgument );
1320            break;
1321
1322        case kIOMessageSystemHasPoweredOn:
1323            /* Resume download after we wake up */
1324            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
1325            break;
1326    }
1327}
1328
1329- (NSRect) windowWillUseStandardFrame: (NSWindow *) w
1330    defaultFrame: (NSRect) defaultFrame
1331{
1332    NSRect windowRect = [fWindow frame];
1333    int count = [fTorrents count];
1334    float newHeight = windowRect.size.height - [fScrollView frame].size.height
1335            + count * ([fTableView rowHeight] + [fTableView intercellSpacing].height) + 30.0;
1336
1337    float minHeight = [fWindow minSize].height;
1338    if (newHeight < minHeight)
1339        newHeight = minHeight;
1340
1341    windowRect.origin.y -= (newHeight - windowRect.size.height);
1342    windowRect.size.height = newHeight;
1343
1344    return windowRect;
1345}
1346
1347- (void) showMainWindow: (id) sender
1348{
1349    [fWindow makeKeyAndOrderFront: nil];
1350}
1351
1352- (void) windowDidBecomeKey: (NSNotification *) notification
1353{
1354    fCompleted = 0;
1355}
1356
1357- (void) linkHomepage: (id) sender
1358{
1359    [[NSWorkspace sharedWorkspace] openURL: [NSURL
1360        URLWithString: WEBSITE_URL]];
1361}
1362
1363- (void) linkForums: (id) sender
1364{
1365    [[NSWorkspace sharedWorkspace] openURL: [NSURL
1366        URLWithString: FORUM_URL]];
1367}
1368
1369- (void) notifyGrowl: (NSString * ) file
1370{
1371    NSString * growlScript;
1372    NSAppleScript * appleScript;
1373    NSDictionary * error;
1374
1375    if( !fHasGrowl )
1376        return;
1377
1378    growlScript = [NSString stringWithFormat:
1379        @"tell application \"System Events\"\n"
1380         "  if exists application process \"GrowlHelperApp\" then\n"
1381         "    tell application \"GrowlHelperApp\"\n "
1382         "      notify with name \"Download Complete\""
1383         "        title \"Download Complete\""
1384         "        description \"%@\""
1385         "        application name \"Transmission\"\n"
1386         "    end tell\n"
1387         "  end if\n"
1388         "end tell", file];
1389    appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1390    if( ![appleScript executeAndReturnError: &error] )
1391    {
1392        NSLog( @"Growl notify failed" );
1393    }
1394    [appleScript release];
1395}
1396
1397- (void) growlRegister: (id) sender
1398{
1399    NSString * growlScript;
1400    NSAppleScript * appleScript;
1401    NSDictionary * error;
1402
1403    if( !fHasGrowl )
1404        return;
1405
1406    growlScript = [NSString stringWithFormat:
1407        @"tell application \"System Events\"\n"
1408         "  if exists application process \"GrowlHelperApp\" then\n"
1409         "    tell application \"GrowlHelperApp\"\n"
1410         "      register as application \"Transmission\" "
1411         "        all notifications {\"Download Complete\"}"
1412         "        default notifications {\"Download Complete\"}"
1413         "        icon of application \"Transmission\"\n"
1414         "    end tell\n"
1415         "  end if\n"
1416         "end tell"];
1417
1418    appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1419    if( ![appleScript executeAndReturnError: &error] )
1420    {
1421        NSLog( @"Growl registration failed" );
1422    }
1423    [appleScript release];
1424}
1425
1426- (void) checkUpdate: (id) sender
1427{
1428    [fPrefsController checkUpdate];
1429}
1430
1431- (void) prepareForUpdate: (NSNotification *) notification
1432{
1433    fUpdateInProgress = YES;
1434}
1435
1436@end
Note: See TracBrowser for help on using the repository browser.