source: trunk/macosx/Controller.m @ 272

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

First commit on my own 8-)

Sorting by progress, sorting no longer causes crashes.
New look for the table.
Info is now inspector: can set individual and multiple ratio limits, shows listing of files, more info in general, resizes when changing tabs, can change tabs with cmd-left and cmd-right.
Menu items moved to "Transfers" menu.
Sliding status bar in its own view.
Prefs moved into their own nib.
Toolbar items for pause and resume selected (many wanted this, but it needs better icons)
New icons for Transfers and General.
A lot of tweaking of main window to fix alignment etc.
Sparkle used for updated (still needs to be added to website to work).

And a lot more tweaking and changes that I'm too lazy to list.

...now let's hope I commit this right

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