source: trunk/macosx/Controller.m @ 518

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

More cleanup with updating inspector.

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