source: trunk/macosx/Controller.m @ 515

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

Don't reload the whole inspector when only settings need to be changed.

  • Property svn:keywords set to Date Rev Author Id
File size: 58.3 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 515 2006-07-04 17:26:18Z livings124 $
3 *
4 * Copyright (c) 2005-2006 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import <IOKit/IOMessage.h>
26
27#import "Controller.h"
28#import "Torrent.h"
29#import "TorrentCell.h"
30#import "TorrentTableView.h"
31#import "StringAdditions.h"
32
33#import <Sparkle/Sparkle.h>
34
35#define TOOLBAR_OPEN            @"Toolbar Open"
36#define TOOLBAR_REMOVE          @"Toolbar Remove"
37#define TOOLBAR_INFO            @"Toolbar Info"
38#define TOOLBAR_PAUSE_ALL       @"Toolbar Pause All"
39#define TOOLBAR_RESUME_ALL      @"Toolbar Resume All"
40#define TOOLBAR_PAUSE_SELECTED  @"Toolbar Pause Selected"
41#define TOOLBAR_RESUME_SELECTED @"Toolbar Resume Selected"
42
43#define TORRENT_TABLE_VIEW_DATA_TYPE    @"TorrentTableViewDataType"
44
45#define WEBSITE_URL @"http://transmission.m0k.org/"
46#define FORUM_URL   @"http://transmission.m0k.org/forum/"
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        fBadger = [[Badger alloc] init];
70       
71        //check and register Growl if it is installed for this user or all users
72        NSFileManager * manager = [NSFileManager defaultManager];
73        fHasGrowl = [manager fileExistsAtPath: GROWL_PATH]
74            || [manager fileExistsAtPath: [NSHomeDirectory() stringByAppendingPathComponent: GROWL_PATH]];
75        [self growlRegister];
76    }
77    return self;
78}
79
80- (void) dealloc
81{
82    [[NSNotificationCenter defaultCenter] removeObserver: self];
83
84    [fTorrents release];
85    [fToolbar release];
86    [fInfoController release];
87    [fBadger release];
88    [fSortType release];
89   
90    tr_close( fLib );
91    [super dealloc];
92}
93
94- (void) awakeFromNib
95{
96    [fPrefsController setPrefs: fLib];
97   
98    [fAdvancedBarItem setState: [fDefaults boolForKey: @"UseAdvancedBar"]];
99
100    fToolbar = [[NSToolbar alloc] initWithIdentifier: @"Transmission Toolbar"];
101    [fToolbar setDelegate: self];
102    [fToolbar setAllowsUserCustomization: YES];
103    [fToolbar setAutosavesConfiguration: YES];
104    [fWindow setToolbar: fToolbar];
105    [fWindow setDelegate: self];
106   
107    //window min height
108    NSSize contentMinSize = [fWindow contentMinSize];
109    contentMinSize.height = [[fWindow contentView] frame].size.height - [fScrollView frame].size.height
110                                + [fTableView rowHeight] + [fTableView intercellSpacing].height;
111    [fWindow setContentMinSize: contentMinSize];
112   
113    //set info keyboard shortcuts
114    unichar ch = NSRightArrowFunctionKey;
115    [fNextInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & ch length: 1]];
116    ch = NSLeftArrowFunctionKey;
117    [fPrevInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & ch length: 1]];
118   
119    //set up status bar
120    NSRect statusBarFrame = [fStatusBar frame];
121    statusBarFrame.size.width = [fWindow frame].size.width;
122    [fStatusBar setFrame: statusBarFrame];
123   
124    NSView * contentView = [fWindow contentView];
125    [contentView addSubview: fStatusBar];
126    [fStatusBar setFrameOrigin: NSMakePoint(0, [fScrollView frame].origin.y + [fScrollView frame].size.height)];
127    [self showStatusBar: [fDefaults boolForKey: @"StatusBar"] animate: NO];
128   
129    //set speed limit
130    if ([fDefaults boolForKey: @"SpeedLimit"])
131    {
132        [fSpeedLimitItem setState: NSOnState];
133        [fSpeedLimitDockItem setState: NSOnState];
134        [fSpeedLimitButton setState: NSOnState];
135    }
136
137    [fActionButton setToolTip: @"Shortcuts for changing global settings."];
138    [fSpeedLimitButton setToolTip: @"Speed Limit overrides the total bandwidth limits with its own limits."];
139
140    [fTableView setTorrents: fTorrents];
141    [[fTableView tableColumnWithIdentifier: @"Torrent"] setDataCell: [[TorrentCell alloc] init]];
142
143    [fTableView registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType,
144                                                        TORRENT_TABLE_VIEW_DATA_TYPE, nil]];
145
146    //register for sleep notifications
147    IONotificationPortRef notify;
148    io_object_t iterator;
149    if (fRootPort = IORegisterForSystemPower(self, & notify, sleepCallBack, & iterator))
150        CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify),
151                            kCFRunLoopCommonModes);
152    else
153        NSLog(@"Could not IORegisterForSystemPower");
154
155    //load torrents from history
156    Torrent * torrent;
157    NSDictionary * historyItem;
158    NSEnumerator * enumerator = [[fDefaults arrayForKey: @"History"] objectEnumerator];
159    while ((historyItem = [enumerator nextObject]))
160        if ((torrent = [[Torrent alloc] initWithHistory: historyItem lib: fLib]))
161        {
162            [fTorrents addObject: torrent];
163            [torrent release];
164        }
165   
166    [self torrentNumberChanged];
167   
168    //set sort
169    fSortType = [[fDefaults stringForKey: @"Sort"] retain];
170   
171    NSMenuItem * currentSortItem;
172    if ([fSortType isEqualToString: @"Name"])
173        currentSortItem = fNameSortItem;
174    else if ([fSortType isEqualToString: @"State"])
175        currentSortItem = fStateSortItem;
176    else if ([fSortType isEqualToString: @"Progress"])
177        currentSortItem = fProgressSortItem;
178    else if ([fSortType isEqualToString: @"Date"])
179        currentSortItem = fDateSortItem;
180    else
181        currentSortItem = fOrderSortItem;
182    [currentSortItem setState: NSOnState];
183   
184    //set upload limit action button
185    [fUploadLimitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
186                    [fDefaults integerForKey: @"UploadLimit"]]];
187    if ([fDefaults boolForKey: @"CheckUpload"])
188        [fUploadLimitItem setState: NSOnState];
189    else
190        [fUploadNoLimitItem setState: NSOnState];
191
192        //set download limit action menu
193    [fDownloadLimitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
194                    [fDefaults integerForKey: @"DownloadLimit"]]];
195    if ([fDefaults boolForKey: @"CheckDownload"])
196        [fDownloadLimitItem setState: NSOnState];
197    else
198        [fDownloadNoLimitItem setState: NSOnState];
199   
200    //set ratio action menu
201    [fRatioSetItem setTitle: [NSString stringWithFormat: @"Stop at Ratio (%.2f)",
202                                [fDefaults floatForKey: @"RatioLimit"]]];
203    if ([fDefaults boolForKey: @"RatioCheck"])
204        [fRatioSetItem setState: NSOnState];
205    else
206        [fRatioNotSetItem setState: NSOnState];
207   
208    //observe notifications
209    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
210   
211    [nc addObserver: self selector: @selector(prepareForUpdate:)
212                    name: SUUpdaterWillRestartNotification object: nil];
213    fUpdateInProgress = NO;
214   
215    [nc addObserver: self selector: @selector(limitGlobalChange:)
216                    name: @"LimitGlobalChange" object: nil];
217   
218    [nc addObserver: self selector: @selector(ratioGlobalChange:)
219                    name: @"RatioGlobalChange" object: nil];
220   
221    //check to start another because of stopped torrent
222    [nc addObserver: self selector: @selector(checkWaitingForStopped:)
223                    name: @"StoppedDownloading" object: nil];
224   
225    //check all torrents for starting
226    [nc addObserver: self selector: @selector(globalStartSettingChange:)
227                    name: @"GlobalStartSettingChange" object: nil];
228
229    //check if torrent should now start
230    [nc addObserver: self selector: @selector(torrentStartSettingChange:)
231                    name: @"TorrentStartSettingChange" object: nil];
232   
233    //change that just impacts the inspector
234    [nc addObserver: self selector: @selector(reloadInspector:)
235                    name: @"TorrentSettingChange" object: nil];
236
237    //timer to update the interface
238    fCompleted = 0;
239    [self updateUI: nil];
240    fTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self
241        selector: @selector( updateUI: ) userInfo: nil repeats: YES];
242    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSModalPanelRunLoopMode];
243    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSEventTrackingRunLoopMode];
244   
245    [self sortTorrents];
246   
247    [fWindow makeKeyAndOrderFront: nil];
248
249    [self reloadInspector: nil];
250    if ([fDefaults boolForKey: @"InfoVisible"])
251        [self showInfo: nil];
252}
253
254- (BOOL) applicationShouldHandleReopen: (NSApplication *) app hasVisibleWindows: (BOOL) visibleWindows
255{
256    if (![fWindow isVisible] && ![[fPrefsController window] isVisible])
257        [fWindow makeKeyAndOrderFront: nil];
258    return NO;
259}
260
261- (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication *) sender
262{
263    if (!fUpdateInProgress && [fDefaults boolForKey: @"CheckQuit"])
264    {
265        int active = 0, downloading = 0;
266        Torrent * torrent;
267        NSEnumerator * enumerator = [fTorrents objectEnumerator];
268        while ((torrent = [enumerator nextObject]))
269            if ([torrent isActive])
270            {
271                active++;
272                if (![torrent isSeeding])
273                    downloading++;
274            }
275
276        BOOL shouldAsk = [fDefaults boolForKey: @"CheckQuitDownloading"] ? downloading > 0 : active > 0;
277        if (shouldAsk)
278        {
279            NSString * message = active == 1
280                ? @"There is an active transfer. Do you really want to quit?"
281                : [NSString stringWithFormat:
282                    @"There are %d active transfers. Do you really want to quit?", active];
283
284            NSBeginAlertSheet(@"Confirm Quit", @"Quit", @"Cancel", nil, fWindow, self,
285                                @selector(quitSheetDidEnd:returnCode:contextInfo:),
286                                nil, nil, message);
287            return NSTerminateLater;
288        }
289    }
290
291    return NSTerminateNow;
292}
293
294- (void) quitSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode
295    contextInfo: (void *) contextInfo
296{
297    [NSApp stopModal];
298    [NSApp replyToApplicationShouldTerminate: returnCode == NSAlertDefaultReturn];
299}
300
301- (void) applicationWillTerminate: (NSNotification *) notification
302{
303    //stop updating the interface
304    [fTimer invalidate];
305   
306    //save history and stop running torrents
307    [self updateTorrentHistory];
308    [fTorrents makeObjectsPerformSelector: @selector(stopTransferForQuit)];
309   
310    //remember window states
311    [fDefaults setBool: [[fInfoController window] isVisible] forKey: @"InfoVisible"];
312    [fInfoController close];
313    [fPrefsController close];
314    [fWindow close];
315    [self showStatusBar: NO animate: NO];
316   
317    //clear badge
318    [fBadger clearBadge];
319
320    //end quickly if updated version will open
321    if (fUpdateInProgress)
322        return;
323
324    //wait for running transfers to stop (5 seconds timeout)
325    NSDate * start = [NSDate date];
326    BOOL timeUp = NO;
327   
328    NSEnumerator * enumerator = [fTorrents objectEnumerator];
329    Torrent * torrent;
330    while (!timeUp && (torrent = [enumerator nextObject]))
331        while (![torrent isPaused] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
332        {
333            usleep(100000);
334            [torrent update];
335        }
336   
337    [fTorrents release];
338}
339
340- (void) folderChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code
341    contextInfo: (Torrent *) torrent
342{
343    if (code == NSOKButton)
344    {
345        [torrent setDownloadFolder: [[openPanel filenames] objectAtIndex: 0]];
346        [torrent update];
347        [self attemptToStartAuto: torrent];
348        [fTorrents addObject: torrent];
349       
350        [self torrentNumberChanged];
351    }
352   
353    [NSApp stopModal];
354}
355
356- (void) application: (NSApplication *) sender openFiles: (NSArray *) filenames
357{
358    NSString * downloadChoice = [fDefaults stringForKey: @"DownloadChoice"], * torrentPath;
359    Torrent * torrent;
360    NSEnumerator * enumerator = [filenames objectEnumerator];
361    while ((torrentPath = [enumerator nextObject]))
362    {
363        if (!(torrent = [[Torrent alloc] initWithPath: torrentPath lib: fLib]))
364            continue;
365
366        //add it to the "File > Open Recent" menu
367        [[NSDocumentController sharedDocumentController]
368            noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
369
370        if ([downloadChoice isEqualToString: @"Ask"])
371        {
372            NSOpenPanel * panel = [NSOpenPanel openPanel];
373
374            [panel setPrompt: @"Select Download Folder"];
375            [panel setAllowsMultipleSelection: NO];
376            [panel setCanChooseFiles: NO];
377            [panel setCanChooseDirectories: YES];
378
379            [panel setMessage: [@"Select the download folder for "
380                    stringByAppendingString: [torrentPath lastPathComponent]]];
381
382            [panel beginSheetForDirectory: nil file: nil types: nil
383                modalForWindow: fWindow modalDelegate: self didEndSelector:
384                @selector( folderChoiceClosed:returnCode:contextInfo: )
385                contextInfo: torrent];
386            [NSApp runModalForWindow: panel];
387        }
388        else
389        {
390            NSString * folder = [downloadChoice isEqualToString: @"Constant"]
391                ? [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath]
392                : [torrentPath stringByDeletingLastPathComponent];
393
394            [torrent setDownloadFolder: folder];
395            [torrent update];
396            [self attemptToStartAuto: torrent];
397            [fTorrents addObject: torrent];
398        }
399       
400        [torrent release];
401    }
402
403    [self torrentNumberChanged];
404
405    [self updateUI: nil];
406    [self sortTorrents];
407    [self updateTorrentHistory];
408}
409
410- (NSArray *) torrentsAtIndexes: (NSIndexSet *) indexSet
411{
412    if ([fTorrents respondsToSelector: @selector(objectsAtIndexes:)])
413        return [fTorrents objectsAtIndexes: indexSet];
414    else
415    {
416        NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [indexSet count]];
417        unsigned int i;
418        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
419            [torrents addObject: [fTorrents objectAtIndex: i]];
420
421        return torrents;
422    }
423}
424
425- (void) torrentNumberChanged
426{
427    int count = [fTorrents count];
428    [fTotalTorrentsField setStringValue: [NSString stringWithFormat:
429        @"%d Transfer%s", count, count == 1 ? "" : "s"]];
430}
431
432//called on by applescript
433- (void) open: (NSArray *) files
434{
435    [self performSelectorOnMainThread: @selector(openFromSheet:) withObject: files waitUntilDone: NO];
436}
437
438- (void) openShowSheet: (id) sender
439{
440    NSOpenPanel * panel = [NSOpenPanel openPanel];
441    NSArray * fileTypes = [NSArray arrayWithObject: @"torrent"];
442
443    [panel setAllowsMultipleSelection: YES];
444    [panel setCanChooseFiles: YES];
445    [panel setCanChooseDirectories: NO];
446
447    [panel beginSheetForDirectory: nil file: nil types: fileTypes
448        modalForWindow: fWindow modalDelegate: self didEndSelector:
449        @selector(openSheetClosed:returnCode:contextInfo:) contextInfo: nil];
450}
451
452- (void) openFromSheet: (NSArray *) filenames
453{
454    [self application: NSApp openFiles: filenames];
455}
456
457- (void) openSheetClosed: (NSOpenPanel *) panel returnCode: (int) code
458    contextInfo: (void *) info
459{
460    if (code == NSOKButton)
461        [self performSelectorOnMainThread: @selector(openFromSheet:)
462                    withObject: [panel filenames] waitUntilDone: NO];
463}
464
465- (void) resumeTorrent: (id) sender
466{
467    [self resumeTorrentWithIndex: [fTableView selectedRowIndexes]];
468}
469
470- (void) resumeAllTorrents: (id) sender
471{
472    [self resumeTorrentWithIndex: [NSIndexSet indexSetWithIndexesInRange:
473                                    NSMakeRange(0, [fTorrents count])]];
474}
475
476- (void) resumeTorrentWithIndex: (NSIndexSet *) indexSet
477{
478    unsigned int i;
479    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
480        [[fTorrents objectAtIndex: i] startTransfer];
481   
482    [self updateUI: nil];
483    [fInfoController updateInfoSettings];
484    [self updateTorrentHistory];
485}
486
487- (void) stopTorrent: (id) sender
488{
489    [self stopTorrentWithIndex: [fTableView selectedRowIndexes]];
490}
491
492- (void) stopAllTorrents: (id) sender
493{
494    [self stopTorrentWithIndex: [NSIndexSet indexSetWithIndexesInRange:
495                                    NSMakeRange(0, [fTorrents count])]];
496}
497
498- (void) stopTorrentWithIndex: (NSIndexSet *) indexSet
499{
500    //don't want any of these starting then stopping
501    unsigned int i;
502    Torrent * torrent;
503    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
504    {
505        torrent = [fTorrents objectAtIndex: i];
506        [torrent setWaitToStart: NO];
507    }
508
509    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
510        [[fTorrents objectAtIndex: i] stopTransfer];
511   
512    [self updateUI: nil];
513    [fInfoController updateInfoSettings];
514    [self updateTorrentHistory];
515}
516
517- (void) removeWithIndex: (NSIndexSet *) indexSet
518        deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
519{
520    NSArray * torrents = [[self torrentsAtIndexes: indexSet] retain];
521    int active = 0, downloading = 0;
522
523    if ([fDefaults boolForKey: @"CheckRemove"])
524    {
525        Torrent * torrent;
526        NSEnumerator * enumerator = [torrents objectEnumerator];
527        while ((torrent = [enumerator nextObject]))
528            if ([torrent isActive])
529            {
530                active++;
531                if (![torrent isSeeding])
532                    downloading++;
533            }
534    }
535
536    BOOL shouldAsk = [fDefaults boolForKey: @"CheckRemoveDownloading"] ? downloading > 0 : active > 0;
537    if (shouldAsk)
538    {
539        NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
540            torrents, @"Torrents",
541            [NSNumber numberWithBool: deleteData], @"DeleteData",
542            [NSNumber numberWithBool: deleteTorrent], @"DeleteTorrent", nil];
543
544        NSString * title, * message;
545       
546        int selected = [fTableView numberOfSelectedRows];
547        if (selected == 1)
548        {
549            title = [NSString stringWithFormat: @"Comfirm Removal of \"%@\"",
550                        [[fTorrents objectAtIndex: [fTableView selectedRow]] name]];
551            message = @"This transfer is active."
552                        " Once removed, continuing the transfer will require the torrent file."
553                        " Do you really want to remove it?";
554        }
555        else
556        {
557            title = [NSString stringWithFormat: @"Comfirm Removal of %d Transfers", selected];
558            if (selected == active)
559                message = [NSString stringWithFormat:
560                    @"There are %d active transfers.", active];
561            else
562                message = [NSString stringWithFormat:
563                    @"There are %d transfers (%d active).", selected, active];
564            message = [message stringByAppendingString:
565                @" Once removed, continuing the transfers will require the torrent files."
566                " Do you really want to remove them?"];
567        }
568
569        NSBeginAlertSheet(title, @"Remove", @"Cancel", nil, fWindow, self,
570            @selector(removeSheetDidEnd:returnCode:contextInfo:), nil, dict, message);
571    }
572    else
573        [self confirmRemove: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
574}
575
576- (void) removeSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode
577                        contextInfo: (NSDictionary *) dict
578{
579    [NSApp stopModal];
580
581    NSArray * torrents = [dict objectForKey: @"Torrents"];
582    BOOL deleteData = [[dict objectForKey: @"DeleteData"] boolValue],
583        deleteTorrent = [[dict objectForKey: @"DeleteTorrent"] boolValue];
584    [dict release];
585   
586    if (returnCode == NSAlertDefaultReturn)
587        [self confirmRemove: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
588    else
589        [torrents release];
590}
591
592- (void) confirmRemove: (NSArray *) torrents
593        deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
594{
595    //don't want any of these starting then stopping
596    NSEnumerator * enumerator = [torrents objectEnumerator];
597    Torrent * torrent;
598    while ((torrent = [enumerator nextObject]))
599        [torrent setWaitToStart: NO];
600
601    NSNumber * lowestOrderValue = [NSNumber numberWithInt: [torrents count]], * currentOrederValue;
602
603    enumerator = [torrents objectEnumerator];
604    while ((torrent = [enumerator nextObject]))
605    {
606        [torrent stopTransfer];
607
608        if (deleteData)
609            [torrent trashData];
610        if (deleteTorrent)
611            [torrent trashTorrent];
612       
613        //determine lowest order value
614        currentOrederValue = [torrent orderValue];
615        if ([lowestOrderValue compare: currentOrederValue] == NSOrderedDescending)
616            lowestOrderValue = currentOrederValue;
617
618        [torrent removeForever];
619        [fTorrents removeObject: torrent];
620    }
621    [torrents release];
622
623    //reset the order values if necessary
624    if ([lowestOrderValue intValue] < [fTorrents count])
625    {
626        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
627                                                @"orderValue" ascending: YES] autorelease];
628        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
629
630        NSArray * tempTorrents = [fTorrents sortedArrayUsingDescriptors: descriptors];
631        [descriptors release];
632
633        int i;
634        for (i = [lowestOrderValue intValue]; i < [tempTorrents count]; i++)
635            [[tempTorrents objectAtIndex: i] setOrderValue: i];
636    }
637   
638    [self torrentNumberChanged];
639    [fTableView deselectAll: nil];
640    [self updateUI: nil];
641    [self updateTorrentHistory];
642}
643
644- (void) removeNoDelete: (id) sender
645{
646    [self removeWithIndex: [fTableView selectedRowIndexes] deleteData: NO deleteTorrent: NO];
647}
648
649- (void) removeDeleteData: (id) sender
650{
651    [self removeWithIndex: [fTableView selectedRowIndexes] deleteData: YES deleteTorrent: NO];
652}
653
654- (void) removeDeleteTorrent: (id) sender
655{
656    [self removeWithIndex: [fTableView selectedRowIndexes] deleteData: NO deleteTorrent: YES];
657}
658
659- (void) removeDeleteBoth: (id) sender
660{
661    [self removeWithIndex: [fTableView selectedRowIndexes] deleteData: YES deleteTorrent: YES];
662}
663
664- (void) copyTorrentFile: (id) sender
665{
666    [self copyTorrentFileForTorrents: [[NSMutableArray alloc] initWithArray:
667            [self torrentsAtIndexes: [fTableView selectedRowIndexes]]]];
668}
669
670- (void) copyTorrentFileForTorrents: (NSMutableArray *) torrents
671{
672    if ([torrents count] == 0)
673    {
674        [torrents release];
675        return;
676    }
677
678    Torrent * torrent = [torrents objectAtIndex: 0];
679
680    //warn user if torrent file can't be found
681    if (![[NSFileManager defaultManager] fileExistsAtPath: [torrent torrentLocation]])
682    {
683        NSAlert * alert = [[NSAlert alloc] init];
684        [alert addButtonWithTitle: @"OK"];
685        [alert setMessageText: [NSString stringWithFormat:
686                @"Copy of \"%@\" Cannot Be Created", [torrent name]]];
687        [alert setInformativeText: [NSString stringWithFormat:
688                @"The torrent file (%@) cannot be found.", [torrent torrentLocation]]];
689        [alert setAlertStyle: NSWarningAlertStyle];
690       
691        [alert runModal];
692       
693        [torrents removeObjectAtIndex: 0];
694        [self copyTorrentFileForTorrents: torrents];
695    }
696    else
697    {
698        NSSavePanel * panel = [NSSavePanel savePanel];
699        [panel setRequiredFileType: @"torrent"];
700        [panel setCanSelectHiddenExtension: NO];
701        [panel setExtensionHidden: NO];
702       
703        [panel beginSheetForDirectory: nil file: [torrent name]
704            modalForWindow: fWindow modalDelegate: self didEndSelector:
705            @selector( saveTorrentCopySheetClosed:returnCode:contextInfo: )
706            contextInfo: torrents];
707    }
708}
709
710- (void) saveTorrentCopySheetClosed: (NSSavePanel *) panel returnCode: (int) code
711    contextInfo: (NSMutableArray *) torrents
712{
713    //if save successful, copy torrent to new location with name of data file
714    if (code == NSOKButton)
715        [[NSFileManager defaultManager] copyPath: [[torrents objectAtIndex: 0] torrentLocation]
716                toPath: [panel filename] handler: nil];
717   
718    [torrents removeObjectAtIndex: 0];
719    [self performSelectorOnMainThread: @selector(copyTorrentFileForTorrents:)
720                withObject: torrents waitUntilDone: NO];
721}
722
723- (void) revealFile: (id) sender
724{
725    NSIndexSet * indexSet = [fTableView selectedRowIndexes];
726    unsigned int i;
727   
728    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
729        [[fTorrents objectAtIndex: i] revealData];
730}
731
732- (void) showPreferenceWindow: (id) sender
733{
734    NSWindow * window = [fPrefsController window];
735    if (![window isVisible])
736        [window center];
737
738    [window makeKeyAndOrderFront: nil];
739}
740
741- (void) showInfo: (id) sender
742{
743    if ([[fInfoController window] isVisible])
744        [fInfoController close];
745    else
746    {
747        [fInfoController updateInfoStats];
748        [[fInfoController window] orderFront: nil];
749    }
750}
751
752- (void) setInfoTab: (id) sender
753{
754    if (sender == fNextInfoTabItem)
755        [fInfoController setNextTab];
756    else
757        [fInfoController setPreviousTab];
758}
759
760- (void) updateUI: (NSTimer *) t
761{
762    NSEnumerator * enumerator = [fTorrents objectEnumerator];
763    Torrent * torrent;
764    while ((torrent = [enumerator nextObject]))
765    {
766        [torrent update];
767
768        if ([torrent justFinished])
769        {
770            [self checkWaitingForFinished: torrent];
771       
772            //notifications
773            [self notifyGrowl: [torrent name]];
774            if (![fWindow isKeyWindow])
775                fCompleted++;
776        }
777    }
778
779    if ([fSortType isEqualToString: @"Progress"] || [fSortType isEqualToString: @"State"])
780        [self sortTorrents];
781    else
782        [fTableView reloadData];
783   
784    //update the global DL/UL rates
785    float downloadRate, uploadRate;
786    tr_torrentRates(fLib, & downloadRate, & uploadRate);
787    if (fStatusBarVisible)
788    {
789        [fTotalDLField setStringValue: [NSString stringForSpeed: downloadRate]];
790        [fTotalULField setStringValue: [NSString stringForSpeed: uploadRate]];
791    }
792
793    if ([[fInfoController window] isVisible])
794        [fInfoController updateInfoStats];
795
796    //badge dock
797    [fBadger updateBadgeWithCompleted: fCompleted
798        uploadRate: uploadRate downloadRate: downloadRate];
799}
800
801- (void) updateTorrentHistory
802{
803    NSMutableArray * history = [NSMutableArray
804        arrayWithCapacity: [fTorrents count]];
805
806    NSEnumerator * enumerator = [fTorrents objectEnumerator];
807    Torrent * torrent;
808    while( ( torrent = [enumerator nextObject] ) )
809        [history addObject: [torrent history]];
810
811    [fDefaults setObject: history forKey: @"History"];
812    [fDefaults synchronize];
813}
814
815- (void) sortTorrents
816{
817    //remember selected rows if needed
818    NSArray * selectedTorrents = nil;
819    int numSelected = [fTableView numberOfSelectedRows];
820    if (numSelected > 0 && numSelected < [fTorrents count])
821        selectedTorrents = [self torrentsAtIndexes: [fTableView selectedRowIndexes]];
822
823    NSSortDescriptor * nameDescriptor = [[[NSSortDescriptor alloc] initWithKey:
824                                            @"name" ascending: YES] autorelease],
825                    * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
826                                            @"orderValue" ascending: YES] autorelease];
827
828    NSArray * descriptors;
829    if ([fSortType isEqualToString: @"Name"])
830        descriptors = [[NSArray alloc] initWithObjects: nameDescriptor, orderDescriptor, nil];
831    else if ([fSortType isEqualToString: @"State"])
832    {
833        NSSortDescriptor * stateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
834                                                @"stateSortKey" ascending: NO] autorelease],
835                        * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
836                                            @"progressSortKey" ascending: NO] autorelease];
837       
838        descriptors = [[NSArray alloc] initWithObjects: stateDescriptor, progressDescriptor,
839                                                            nameDescriptor, orderDescriptor, nil];
840    }
841    else if ([fSortType isEqualToString: @"Progress"])
842    {
843        NSSortDescriptor * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
844                                            @"progressSortKey" ascending: YES] autorelease];
845       
846        descriptors = [[NSArray alloc] initWithObjects: progressDescriptor, nameDescriptor, orderDescriptor, nil];
847    }
848    else if ([fSortType isEqualToString: @"Date"])
849    {
850        NSSortDescriptor * dateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
851                                            @"date" ascending: YES] autorelease];
852   
853        descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, orderDescriptor, nil];
854    }
855    else
856        descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
857
858    [fTorrents sortUsingDescriptors: descriptors];
859   
860    [descriptors release];
861   
862    [fTableView reloadData];
863   
864    //set selected rows if needed
865    if (selectedTorrents)
866    {
867        Torrent * torrent;
868        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
869        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
870        while ((torrent = [enumerator nextObject]))
871            [indexSet addIndex: [fTorrents indexOfObject: torrent]];
872       
873        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
874        [indexSet release];
875    }
876}
877
878- (void) setSort: (id) sender
879{
880    NSMenuItem * prevSortItem;
881    if ([fSortType isEqualToString: @"Name"])
882        prevSortItem = fNameSortItem;
883    else if ([fSortType isEqualToString: @"State"])
884        prevSortItem = fStateSortItem;
885    else if ([fSortType isEqualToString: @"Progress"])
886        prevSortItem = fProgressSortItem;
887    else if ([fSortType isEqualToString: @"Date"])
888        prevSortItem = fDateSortItem;
889    else
890        prevSortItem = fOrderSortItem;
891   
892    if (sender != prevSortItem)
893    {
894        [prevSortItem setState: NSOffState];
895        [sender setState: NSOnState];
896
897        [fSortType release];
898        if (sender == fNameSortItem)
899            fSortType = [[NSString alloc] initWithString: @"Name"];
900        else if (sender == fStateSortItem)
901            fSortType = [[NSString alloc] initWithString: @"State"];
902        else if (sender == fProgressSortItem)
903            fSortType = [[NSString alloc] initWithString: @"Progress"];
904        else if (sender == fDateSortItem)
905            fSortType = [[NSString alloc] initWithString: @"Date"];
906        else
907            fSortType = [[NSString alloc] initWithString: @"Order"];
908           
909        [fDefaults setObject: fSortType forKey: @"Sort"];
910    }
911
912    [self sortTorrents];
913}
914
915- (void) toggleSpeedLimit: (id) sender
916{
917    int state = [fSpeedLimitItem state] ? NSOffState : NSOnState;
918
919    [fSpeedLimitItem setState: state];
920    [fSpeedLimitDockItem setState: state];
921    [fSpeedLimitButton setState: state];
922   
923    [fPrefsController enableSpeedLimit: state];
924}
925
926- (void) setLimitGlobalEnabled: (id) sender
927{
928    [fPrefsController setLimitEnabled: (sender == fUploadLimitItem || sender == fDownloadLimitItem)
929        type: (sender == fUploadLimitItem || sender == fUploadNoLimitItem) ? @"Upload" : @"Download"];
930}
931
932- (void) setQuickLimitGlobal: (id) sender
933{
934    [fPrefsController setQuickLimit: [[sender title] intValue]
935        type: [sender menu] == fUploadMenu ? @"Upload" : @"Download"];
936}
937
938- (void) limitGlobalChange: (NSNotification *) notification
939{
940    NSDictionary * dict = [notification object];
941   
942    NSMenuItem * limitItem, * noLimitItem;
943    if ([[dict objectForKey: @"Type"] isEqualToString: @"Upload"])
944    {
945        limitItem = fUploadLimitItem;
946        noLimitItem = fUploadNoLimitItem;
947    }
948    else
949    {
950        limitItem = fDownloadLimitItem;
951        noLimitItem = fDownloadNoLimitItem;
952    }
953   
954    BOOL enable = [[dict objectForKey: @"Enable"] boolValue];
955    [limitItem setState: enable ? NSOnState : NSOffState];
956    [noLimitItem setState: !enable ? NSOnState : NSOffState];
957   
958    [limitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
959                            [[dict objectForKey: @"Limit"] intValue]]];
960
961    [dict release];
962}
963
964- (void) setRatioGlobalEnabled: (id) sender
965{
966    [fPrefsController setRatioEnabled: sender == fRatioSetItem];
967}
968
969- (void) setQuickRatioGlobal: (id) sender
970{
971    [fPrefsController setQuickRatio: [[sender title] floatValue]];
972}
973
974- (void) ratioGlobalChange: (NSNotification *) notification
975{
976    NSDictionary * dict = [notification object];
977   
978    BOOL enable = [[dict objectForKey: @"Enable"] boolValue];
979    [fRatioSetItem setState: enable ? NSOnState : NSOffState];
980    [fRatioNotSetItem setState: !enable ? NSOnState : NSOffState];
981   
982    [fRatioSetItem setTitle: [NSString stringWithFormat: @"Stop at Ratio (%.2f)",
983                            [[dict objectForKey: @"Ratio"] floatValue]]];
984
985    [dict release];
986}
987
988- (void) checkWaitingForStopped: (NSNotification *) notification
989{
990    [self checkWaitingForFinished: [notification object]];
991}
992
993- (void) checkWaitingForFinished: (Torrent *) finishedTorrent
994{
995    //don't try to start a transfer if there should be none waiting
996    if (![[fDefaults stringForKey: @"StartSetting"] isEqualToString: @"Wait"])
997        return;
998
999    int desiredActive = [fDefaults integerForKey: @"WaitToStartNumber"];
1000   
1001    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1002    Torrent * torrent, * torrentToStart = nil;
1003    while ((torrent = [enumerator nextObject]))
1004    {
1005        //ignore the torrent just stopped
1006        if (torrent == finishedTorrent)
1007            continue;
1008   
1009        if ([torrent isActive])
1010        {
1011            if (![torrent isSeeding])
1012            {
1013                desiredActive--;
1014                if (desiredActive <= 0)
1015                    return;
1016            }
1017        }
1018        else
1019        {
1020            //use as next if it is waiting to start and either no previous or order value is lower
1021            if ([torrent waitingToStart] && (!torrentToStart
1022                || [[torrentToStart orderValue] compare: [torrent orderValue]] == NSOrderedDescending))
1023                torrentToStart = torrent;
1024        }
1025    }
1026   
1027    //since it hasn't returned, the queue amount has not been met
1028    if (torrentToStart)
1029    {
1030        [torrentToStart startTransfer];
1031       
1032        [self updateUI: nil];
1033        [fInfoController updateInfoSettings];
1034        [self updateTorrentHistory];
1035    }
1036}
1037
1038- (void) globalStartSettingChange: (NSNotification *) notification
1039{
1040    [self attemptToStartMultipleAuto: fTorrents];
1041   
1042    [self updateUI: nil];
1043    [fInfoController updateInfoSettings];
1044    [self updateTorrentHistory];
1045}
1046
1047- (void) torrentStartSettingChange: (NSNotification *) notification
1048{
1049    [self attemptToStartMultipleAuto: [notification object]];
1050
1051    [self updateUI: nil];
1052    [fInfoController updateInfoSettings];
1053    [self updateTorrentHistory];
1054}
1055
1056- (void) attemptToStartAuto: (Torrent *) torrent
1057{
1058    [self attemptToStartMultipleAuto: [NSArray arrayWithObject: torrent]];
1059}
1060
1061//will try to start, taking into consideration the start preference
1062- (void) attemptToStartMultipleAuto: (NSArray *) torrents
1063{
1064    NSString * startSetting = [fDefaults stringForKey: @"StartSetting"];
1065    if ([startSetting isEqualToString: @"Start"])
1066    {
1067        NSEnumerator * enumerator = [torrents objectEnumerator];
1068        Torrent * torrent;
1069        while ((torrent = [enumerator nextObject]))
1070            if ([torrent waitingToStart])
1071                [torrent startTransfer];
1072       
1073        return;
1074    }
1075    else if (![startSetting isEqualToString: @"Wait"])
1076        return;
1077    else;
1078   
1079    //determine the number of downloads needed to start
1080    int desiredActive = [fDefaults integerForKey: @"WaitToStartNumber"];
1081           
1082    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1083    Torrent * torrent;
1084    while ((torrent = [enumerator nextObject]))
1085        if ([torrent isActive] && ![torrent isSeeding])
1086        {
1087            desiredActive--;
1088            if (desiredActive <= 0)
1089                break;
1090        }
1091   
1092    //sort torrents by order value
1093    NSArray * sortedTorrents;
1094    if ([torrents count] > 1 && desiredActive > 0)
1095    {
1096        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1097                                                    @"orderValue" ascending: YES] autorelease];
1098        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1099       
1100        sortedTorrents = [torrents sortedArrayUsingDescriptors: descriptors];
1101        [descriptors release];
1102    }
1103    else
1104        sortedTorrents = torrents;
1105
1106    enumerator = [sortedTorrents objectEnumerator];
1107    while ((torrent = [enumerator nextObject]))
1108    {
1109        if ([torrent progress] >= 1.0)
1110            [torrent startTransfer];
1111        else
1112        {
1113            if ([torrent waitingToStart] && desiredActive > 0)
1114            {
1115                [torrent startTransfer];
1116                desiredActive--;
1117            }
1118        }
1119       
1120        [torrent update];
1121    }
1122}
1123
1124- (void) reloadInspector: (NSNotification *) notification
1125{
1126    [fInfoController updateInfoForTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]];
1127}
1128
1129- (int) numberOfRowsInTableView: (NSTableView *) t
1130{
1131    return [fTorrents count];
1132}
1133
1134- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell
1135    forTableColumn: (NSTableColumn *) tableColumn row: (int) row
1136{
1137    [cell setTorrent: [fTorrents objectAtIndex: row]];
1138}
1139
1140- (BOOL) tableView: (NSTableView *) tableView writeRowsWithIndexes: (NSIndexSet *) indexes
1141    toPasteboard: (NSPasteboard *) pasteboard
1142{
1143    if ([fSortType isEqualToString: @"Order"])
1144    {
1145        [pasteboard declareTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE] owner: self];
1146        [pasteboard setData: [NSKeyedArchiver archivedDataWithRootObject: indexes]
1147                                forType: TORRENT_TABLE_VIEW_DATA_TYPE];
1148        return YES;
1149    }
1150    return NO;
1151}
1152
1153- (NSDragOperation) tableView: (NSTableView *) t validateDrop: (id <NSDraggingInfo>) info
1154    proposedRow: (int) row proposedDropOperation: (NSTableViewDropOperation) operation
1155{
1156    NSPasteboard * pasteboard = [info draggingPasteboard];
1157    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
1158    {
1159        if ([[[pasteboard propertyListForType: NSFilenamesPboardType]
1160            pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]] count] > 0)
1161        {
1162            [fTableView setDropRow: -1 dropOperation: NSTableViewDropOn];
1163            return NSDragOperationGeneric;
1164        }
1165    }
1166    else if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
1167    {
1168        [fTableView setDropRow: row dropOperation: NSTableViewDropAbove];
1169        return NSDragOperationGeneric;
1170    }
1171    else;
1172   
1173    return NSDragOperationNone;
1174}
1175
1176- (BOOL) tableView: (NSTableView *) t acceptDrop: (id <NSDraggingInfo>) info
1177    row: (int) newRow dropOperation: (NSTableViewDropOperation) operation
1178{
1179    NSPasteboard * pasteboard = [info draggingPasteboard];
1180    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
1181        [self application: NSApp openFiles: [[[info draggingPasteboard] propertyListForType: NSFilenamesPboardType]
1182            pathsMatchingExtensions: [NSArray arrayWithObject: @"torrent"]]];
1183    else
1184    {
1185        //remember selected rows if needed
1186        NSArray * selectedTorrents = nil;
1187        int numSelected = [fTableView numberOfSelectedRows];
1188        if (numSelected > 0 && numSelected < [fTorrents count])
1189            selectedTorrents = [self torrentsAtIndexes: [fTableView selectedRowIndexes]];
1190   
1191        NSIndexSet * indexes = [NSKeyedUnarchiver unarchiveObjectWithData:
1192                                [pasteboard dataForType: TORRENT_TABLE_VIEW_DATA_TYPE]];
1193       
1194        //move torrent in array
1195        NSArray * movingTorrents = [[self torrentsAtIndexes: indexes] retain];
1196        [fTorrents removeObjectsInArray: movingTorrents];
1197       
1198        //determine the insertion index now that transfers to move have been removed
1199        int i, decrease = 0;
1200        for (i = [indexes firstIndex]; i < newRow && i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
1201            decrease++;
1202       
1203        //insert objects at new location
1204        for (i = 0; i < [movingTorrents count]; i++)
1205            [fTorrents insertObject: [movingTorrents objectAtIndex: i] atIndex: newRow - decrease + i];
1206       
1207        [movingTorrents release];
1208       
1209        //redo order values
1210        int low = [indexes firstIndex], high = [indexes lastIndex];
1211        if (newRow < low)
1212            low = newRow;
1213        else if (newRow > high + 1)
1214            high = newRow - 1;
1215        else;
1216       
1217        for (i = low; i <= high; i++)
1218            [[fTorrents objectAtIndex: i] setOrderValue: i];
1219       
1220        [fTableView reloadData];
1221       
1222        //set selected rows if needed
1223        if (selectedTorrents)
1224        {
1225            Torrent * torrent;
1226            NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1227            NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1228            while ((torrent = [enumerator nextObject]))
1229                [indexSet addIndex: [fTorrents indexOfObject: torrent]];
1230           
1231            [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1232            [indexSet release];
1233        }
1234    }
1235   
1236    return YES;
1237}
1238
1239- (void) tableViewSelectionDidChange: (NSNotification *) notification
1240{
1241    [self reloadInspector: nil];
1242}
1243
1244- (void) toggleStatusBar: (id) sender
1245{
1246    [self showStatusBar: !fStatusBarVisible animate: YES];
1247    [fDefaults setBool: fStatusBarVisible forKey: @"StatusBar"];
1248}
1249
1250- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
1251{
1252    if (show == fStatusBarVisible)
1253        return;
1254
1255    NSRect frame = [fWindow frame];
1256    float heightChange = [fStatusBar frame].size.height;
1257    if (!show)
1258        heightChange *= -1;
1259
1260    frame.size.height += heightChange;
1261    frame.origin.y -= heightChange;
1262       
1263    fStatusBarVisible = !fStatusBarVisible;
1264   
1265    [self updateUI: nil];
1266   
1267    //set views to not autoresize
1268    unsigned int statsMask = [fStatusBar autoresizingMask];
1269    unsigned int scrollMask = [fScrollView autoresizingMask];
1270    [fStatusBar setAutoresizingMask: 0];
1271    [fScrollView setAutoresizingMask: 0];
1272   
1273    [fWindow setFrame: frame display: YES animate: animate];
1274   
1275    //re-enable autoresize
1276    [fStatusBar setAutoresizingMask: statsMask];
1277    [fScrollView setAutoresizingMask: scrollMask];
1278   
1279    //change min size
1280    NSSize minSize = [fWindow contentMinSize];
1281    minSize.height += heightChange;
1282    [fWindow setContentMinSize: minSize];
1283}
1284
1285- (void) toggleAdvancedBar: (id) sender
1286{
1287    [fAdvancedBarItem setState: ![fAdvancedBarItem state]];
1288    [fDefaults setBool: [fAdvancedBarItem state] forKey: @"UseAdvancedBar"];
1289
1290    [fTableView display];
1291}
1292
1293- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
1294    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
1295{
1296    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
1297
1298    if( [ident isEqualToString: TOOLBAR_OPEN] )
1299    {
1300        [item setLabel: @"Open"];
1301        [item setPaletteLabel: @"Open Torrent Files"];
1302        [item setToolTip: @"Open torrent files"];
1303        [item setImage: [NSImage imageNamed: @"Open.png"]];
1304        [item setTarget: self];
1305        [item setAction: @selector( openShowSheet: )];
1306    }
1307    else if( [ident isEqualToString: TOOLBAR_REMOVE] )
1308    {
1309        [item setLabel: @"Remove"];
1310        [item setPaletteLabel: @"Remove Selected"];
1311        [item setToolTip: @"Remove selected transfers"];
1312        [item setImage: [NSImage imageNamed: @"Remove.png"]];
1313        [item setTarget: self];
1314        [item setAction: @selector( removeNoDelete: )];
1315    }
1316    else if( [ident isEqualToString: TOOLBAR_INFO] )
1317    {
1318        [item setLabel: @"Inspector"];
1319        [item setPaletteLabel: @"Show/Hide Inspector"];
1320        [item setToolTip: @"Display torrent inspector"];
1321        [item setImage: [NSImage imageNamed: @"Info.png"]];
1322        [item setTarget: self];
1323        [item setAction: @selector( showInfo: )];
1324    }
1325    else if( [ident isEqualToString: TOOLBAR_PAUSE_ALL] )
1326    {
1327        [item setLabel: @"Pause All"];
1328        [item setPaletteLabel: [item label]];
1329        [item setToolTip: @"Pause all transfers"];
1330        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
1331        [item setTarget: self];
1332        [item setAction: @selector( stopAllTorrents: )];
1333    }
1334    else if( [ident isEqualToString: TOOLBAR_RESUME_ALL] )
1335    {
1336        [item setLabel: @"Resume All"];
1337        [item setPaletteLabel: [item label]];
1338        [item setToolTip: @"Resume all transfers"];
1339        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
1340        [item setTarget: self];
1341        [item setAction: @selector( resumeAllTorrents: )];
1342    }
1343    else if( [ident isEqualToString: TOOLBAR_PAUSE_SELECTED] )
1344    {
1345        [item setLabel: @"Pause"];
1346        [item setPaletteLabel: @"Pause Selected"];
1347        [item setToolTip: @"Pause selected transfers"];
1348        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
1349        [item setTarget: self];
1350        [item setAction: @selector( stopTorrent: )];
1351    }
1352    else if( [ident isEqualToString: TOOLBAR_RESUME_SELECTED] )
1353    {
1354        [item setLabel: @"Resume"];
1355        [item setPaletteLabel: @"Resume Selected"];
1356        [item setToolTip: @"Resume selected transfers"];
1357        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
1358        [item setTarget: self];
1359        [item setAction: @selector( resumeTorrent: )];
1360    }
1361    else
1362    {
1363        [item release];
1364        return nil;
1365    }
1366
1367    return item;
1368}
1369
1370- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
1371{
1372    return [NSArray arrayWithObjects:
1373            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1374            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
1375            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL, TOOLBAR_INFO,
1376            NSToolbarSeparatorItemIdentifier,
1377            NSToolbarSpaceItemIdentifier,
1378            NSToolbarFlexibleSpaceItemIdentifier,
1379            NSToolbarCustomizeToolbarItemIdentifier, nil];
1380}
1381
1382- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
1383{
1384    return [NSArray arrayWithObjects:
1385            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1386            NSToolbarSeparatorItemIdentifier,
1387            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
1388            NSToolbarFlexibleSpaceItemIdentifier,
1389            TOOLBAR_INFO, nil];
1390}
1391
1392- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
1393{
1394    NSString * ident = [toolbarItem itemIdentifier];
1395
1396    //enable remove item
1397    if ([ident isEqualToString: TOOLBAR_REMOVE])
1398        return [fTableView numberOfSelectedRows] > 0;
1399
1400    //enable pause all item
1401    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
1402    {
1403        Torrent * torrent;
1404        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1405        while ((torrent = [enumerator nextObject]))
1406            if ([torrent isActive])
1407                return YES;
1408        return NO;
1409    }
1410
1411    //enable resume all item
1412    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
1413    {
1414        Torrent * torrent;
1415        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1416        while ((torrent = [enumerator nextObject]))
1417            if ([torrent isPaused])
1418                return YES;
1419        return NO;
1420    }
1421
1422    //enable pause item
1423    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
1424    {
1425        Torrent * torrent;
1426        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1427        unsigned int i;
1428       
1429        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1430            if ([[fTorrents objectAtIndex: i] isActive])
1431                return YES;
1432        return NO;
1433    }
1434   
1435    //enable resume item
1436    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
1437    {
1438        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1439        unsigned int i;
1440       
1441        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1442            if ([[fTorrents objectAtIndex: i] isPaused])
1443                return YES;
1444        return NO;
1445    }
1446
1447    return YES;
1448}
1449
1450- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
1451{
1452    SEL action = [menuItem action];
1453
1454    //only enable some items if it is in a context menu or the window is useable
1455    BOOL canUseMenu = [fWindow isKeyWindow] || [[[menuItem menu] title] isEqualToString: @"Context"];
1456
1457    //enable show info
1458    if (action == @selector(showInfo:))
1459    {
1460        NSString * title = [[fInfoController window] isVisible] ? @"Hide Inspector" : @"Show Inspector";
1461        if (![[menuItem title] isEqualToString: title])
1462                [menuItem setTitle: title];
1463
1464        return YES;
1465    }
1466   
1467    //enable prev/next inspector tab
1468    if (action == @selector(setInfoTab:))
1469        return [[fInfoController window] isVisible];
1470   
1471    //enable toggle status bar
1472    if (action == @selector(toggleStatusBar:))
1473    {
1474        NSString * title = fStatusBarVisible ? @"Hide Status Bar" : @"Show Status Bar";
1475        if (![[menuItem title] isEqualToString: title])
1476                [menuItem setTitle: title];
1477
1478        return canUseMenu;
1479    }
1480
1481    //enable resume all item
1482    if (action == @selector(resumeAllTorrents:))
1483    {
1484        Torrent * torrent;
1485        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1486        while ((torrent = [enumerator nextObject]))
1487            if ([torrent isPaused])
1488                return YES;
1489        return NO;
1490    }
1491
1492    //enable pause all item
1493    if (action == @selector(stopAllTorrents:))
1494    {
1495        Torrent * torrent;
1496        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1497        while ((torrent = [enumerator nextObject]))
1498            if ([torrent isActive])
1499                return YES;
1500        return NO;
1501    }
1502
1503    //enable reveal in finder
1504    if (action == @selector(revealFile:))
1505        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1506
1507    //enable remove items
1508    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
1509        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteBoth:))
1510    {
1511        BOOL warning = NO,
1512            onlyDownloading = [fDefaults boolForKey: @"CheckRemoveDownloading"],
1513            canDelete = action != @selector(removeDeleteTorrent:) && action != @selector(removeDeleteBoth:);
1514        Torrent * torrent;
1515        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1516        unsigned int i;
1517       
1518        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1519        {
1520            torrent = [fTorrents objectAtIndex: i];
1521            if (!warning && [torrent isActive])
1522            {
1523                warning = onlyDownloading ? ![torrent isSeeding] : YES;
1524                if (warning && canDelete)
1525                    break;
1526            }
1527            if (!canDelete && [torrent publicTorrent])
1528            {
1529                canDelete = YES;
1530                if (warning)
1531                    break;
1532            }
1533        }
1534   
1535        //append or remove ellipsis when needed
1536        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
1537        if (warning && [fDefaults boolForKey: @"CheckRemove"])
1538        {
1539            if (![title hasSuffix: ellipsis])
1540                [menuItem setTitle: [title stringByAppendingEllipsis]];
1541        }
1542        else
1543        {
1544            if ([title hasSuffix: ellipsis])
1545                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
1546        }
1547       
1548        return canUseMenu && canDelete && [fTableView numberOfSelectedRows] > 0;
1549    }
1550
1551    //enable pause item
1552    if( action == @selector(stopTorrent:) )
1553    {
1554        if (!canUseMenu)
1555            return NO;
1556   
1557        Torrent * torrent;
1558        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1559        unsigned int i;
1560       
1561        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1562        {
1563            torrent = [fTorrents objectAtIndex: i];
1564            if ([torrent isActive])
1565                return YES;
1566        }
1567        return NO;
1568    }
1569   
1570    //enable resume item
1571    if( action == @selector(resumeTorrent:) )
1572    {
1573        if (!canUseMenu)
1574            return NO;
1575   
1576        Torrent * torrent;
1577        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1578        unsigned int i;
1579       
1580        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1581        {
1582            torrent = [fTorrents objectAtIndex: i];
1583            if ([torrent isPaused])
1584                return YES;
1585        }
1586        return NO;
1587    }
1588   
1589    //enable sort and advanced bar items
1590    if (action == @selector(setSort:) || (action == @selector(toggleAdvancedBar:)))
1591        return canUseMenu;
1592   
1593    //enable copy torrent file item
1594    if( action == @selector(copyTorrentFile:) )
1595    {
1596        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1597    }
1598
1599    return YES;
1600}
1601
1602- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument
1603{
1604    NSEnumerator * enumerator;
1605    Torrent * torrent;
1606    BOOL active;
1607
1608    switch( messageType )
1609    {
1610        case kIOMessageSystemWillSleep:
1611            /* Close all connections before going to sleep and remember
1612               we should resume when we wake up */
1613            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
1614
1615            /* Wait for torrents to stop (5 seconds timeout) */
1616            NSDate * start = [NSDate date];
1617            enumerator = [fTorrents objectEnumerator];
1618            while( ( torrent = [enumerator nextObject] ) )
1619            {
1620                while( [[NSDate date] timeIntervalSinceDate: start] < 5 &&
1621                        ![torrent isPaused] )
1622                {
1623                    usleep( 100000 );
1624                    [torrent update];
1625                }
1626            }
1627
1628            IOAllowPowerChange( fRootPort, (long) messageArgument );
1629            break;
1630
1631        case kIOMessageCanSystemSleep:
1632            /* Prevent idle sleep unless all paused */
1633            active = NO;
1634            enumerator = [fTorrents objectEnumerator];
1635            while ((torrent = [enumerator nextObject]))
1636                if ([torrent isActive])
1637                {
1638                    active = YES;
1639                    break;
1640                }
1641
1642            if (active)
1643                IOCancelPowerChange( fRootPort, (long) messageArgument );
1644            else
1645                IOAllowPowerChange( fRootPort, (long) messageArgument );
1646            break;
1647
1648        case kIOMessageSystemHasPoweredOn:
1649            /* Resume download after we wake up */
1650            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
1651            break;
1652    }
1653}
1654
1655- (NSRect) windowWillUseStandardFrame: (NSWindow *) w defaultFrame: (NSRect) defaultFrame
1656{
1657    NSRect windowRect = [fWindow frame];
1658    float newHeight = windowRect.size.height - [fScrollView frame].size.height
1659        + [fTorrents count] * ([fTableView rowHeight] + [fTableView intercellSpacing].height) + 30.0;
1660
1661    float minHeight = [fWindow minSize].height;
1662    if (newHeight < minHeight)
1663        newHeight = minHeight;
1664
1665    windowRect.origin.y -= (newHeight - windowRect.size.height);
1666    windowRect.size.height = newHeight;
1667
1668    return windowRect;
1669}
1670
1671- (void) showMainWindow: (id) sender
1672{
1673    [fWindow makeKeyAndOrderFront: nil];
1674}
1675
1676- (void) windowDidBecomeKey: (NSNotification *) notification
1677{
1678    fCompleted = 0;
1679}
1680
1681- (void) linkHomepage: (id) sender
1682{
1683    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
1684}
1685
1686- (void) linkForums: (id) sender
1687{
1688    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
1689}
1690
1691- (void) notifyGrowl: (NSString * ) file
1692{
1693    if (!fHasGrowl)
1694        return;
1695
1696    NSString * growlScript = [NSString stringWithFormat:
1697        @"tell application \"System Events\"\n"
1698         "  if exists application process \"GrowlHelperApp\" then\n"
1699         "    tell application \"GrowlHelperApp\"\n "
1700         "      notify with name \"Download Complete\""
1701         "        title \"Download Complete\""
1702         "        description \"%@\""
1703         "        application name \"Transmission\"\n"
1704         "    end tell\n"
1705         "  end if\n"
1706         "end tell", file];
1707   
1708    NSAppleScript * appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1709    NSDictionary * error;
1710    if (![appleScript executeAndReturnError: & error])
1711        NSLog(@"Growl notify failed");
1712    [appleScript release];
1713}
1714
1715- (void) growlRegister
1716{
1717    if (!fHasGrowl)
1718        return;
1719
1720    NSString * growlScript = [NSString stringWithFormat:
1721        @"tell application \"System Events\"\n"
1722         "  if exists application process \"GrowlHelperApp\" then\n"
1723         "    tell application \"GrowlHelperApp\"\n"
1724         "      register as application \"Transmission\" "
1725         "        all notifications {\"Download Complete\"}"
1726         "        default notifications {\"Download Complete\"}"
1727         "        icon of application \"Transmission\"\n"
1728         "    end tell\n"
1729         "  end if\n"
1730         "end tell"];
1731
1732    NSAppleScript * appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1733    NSDictionary * error;
1734    if (![appleScript executeAndReturnError: & error])
1735        NSLog(@"Growl registration failed");
1736    [appleScript release];
1737}
1738
1739- (void) checkUpdate: (id) sender
1740{
1741    [fPrefsController checkUpdate];
1742}
1743
1744- (void) prepareForUpdate: (NSNotification *) notification
1745{
1746    fUpdateInProgress = YES;
1747}
1748
1749@end
Note: See TracBrowser for help on using the repository browser.