source: trunk/macosx/Controller.m @ 510

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

swap these 2 checks

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