source: trunk/macosx/Controller.m @ 291

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

Initial support of moving torrent files in Mac version. Some things are incomplete and it's not yet guaranteed to work (in other words, only I should be even trying to use it).

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