source: trunk/macosx/Controller.m @ 556

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

Align turtle to text better.
Rename a method.

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