source: trunk/macosx/Controller.m @ 554

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

Have start and stop use actual torrents instead of indexes...this could be useful later.

  • Property svn:keywords set to Date Rev Author Id
File size: 62.3 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 554 2006-07-08 20:49:54Z 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) removeWithIndex: (NSIndexSet *) indexSet
534        deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
535{
536    NSArray * torrents = [[self torrentsAtIndexes: indexSet] 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 confirmRemove: 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 confirmRemove: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
603    else
604        [torrents release];
605}
606
607- (void) confirmRemove: (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]], * currentOrederValue;
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        currentOrederValue = [torrent orderValue];
629        if ([lowestOrderValue compare: currentOrederValue] == NSOrderedDescending)
630            lowestOrderValue = currentOrederValue;
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 removeWithIndex: [fTableView selectedRowIndexes] deleteData: NO deleteTorrent: NO];
661}
662
663- (void) removeDeleteData: (id) sender
664{
665    [self removeWithIndex: [fTableView selectedRowIndexes] deleteData: YES deleteTorrent: NO];
666}
667
668- (void) removeDeleteTorrent: (id) sender
669{
670    [self removeWithIndex: [fTableView selectedRowIndexes] deleteData: NO deleteTorrent: YES];
671}
672
673- (void) removeDeleteBoth: (id) sender
674{
675    [self removeWithIndex: [fTableView selectedRowIndexes] deleteData: YES deleteTorrent: YES];
676}
677
678- (void) copyTorrentFile: (id) sender
679{
680    [self copyTorrentFileForTorrents: [[NSMutableArray alloc] initWithArray:
681            [self torrentsAtIndexes: [fTableView selectedRowIndexes]]]];
682}
683
684- (void) copyTorrentFileForTorrents: (NSMutableArray *) torrents
685{
686    if ([torrents count] == 0)
687    {
688        [torrents release];
689        return;
690    }
691
692    Torrent * torrent = [torrents objectAtIndex: 0];
693
694    //warn user if torrent file can't be found
695    if (![[NSFileManager defaultManager] fileExistsAtPath: [torrent torrentLocation]])
696    {
697        NSAlert * alert = [[NSAlert alloc] init];
698        [alert addButtonWithTitle: @"OK"];
699        [alert setMessageText: [NSString stringWithFormat:
700                @"Copy of \"%@\" Cannot Be Created", [torrent name]]];
701        [alert setInformativeText: [NSString stringWithFormat:
702                @"The torrent file (%@) cannot be found.", [torrent torrentLocation]]];
703        [alert setAlertStyle: NSWarningAlertStyle];
704       
705        [alert runModal];
706       
707        [torrents removeObjectAtIndex: 0];
708        [self copyTorrentFileForTorrents: torrents];
709    }
710    else
711    {
712        NSSavePanel * panel = [NSSavePanel savePanel];
713        [panel setRequiredFileType: @"torrent"];
714        [panel setCanSelectHiddenExtension: YES];
715       
716        [panel beginSheetForDirectory: nil file: [torrent name]
717            modalForWindow: fWindow modalDelegate: self didEndSelector:
718            @selector(saveTorrentCopySheetClosed:returnCode:contextInfo:) contextInfo: torrents];
719    }
720}
721
722- (void) saveTorrentCopySheetClosed: (NSSavePanel *) panel returnCode: (int) code
723    contextInfo: (NSMutableArray *) torrents
724{
725    //if save successful, copy torrent to new location with name of data file
726    if (code == NSOKButton)
727        [[NSFileManager defaultManager] copyPath: [[torrents objectAtIndex: 0] torrentLocation]
728                toPath: [panel filename] handler: nil];
729   
730    [torrents removeObjectAtIndex: 0];
731    [self performSelectorOnMainThread: @selector(copyTorrentFileForTorrents:)
732                withObject: torrents waitUntilDone: NO];
733}
734
735- (void) revealFile: (id) sender
736{
737    NSIndexSet * indexSet = [fTableView selectedRowIndexes];
738    unsigned int i;
739   
740    for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
741        [[fTorrents objectAtIndex: i] revealData];
742}
743
744- (void) showPreferenceWindow: (id) sender
745{
746    NSWindow * window = [fPrefsController window];
747    if (![window isVisible])
748        [window center];
749
750    [window makeKeyAndOrderFront: nil];
751}
752
753- (void) showInfo: (id) sender
754{
755    if ([[fInfoController window] isVisible])
756        [fInfoController close];
757    else
758    {
759        [fInfoController updateInfoStats];
760        [[fInfoController window] orderFront: nil];
761    }
762}
763
764- (void) setInfoTab: (id) sender
765{
766    if (sender == fNextInfoTabItem)
767        [fInfoController setNextTab];
768    else
769        [fInfoController setPreviousTab];
770}
771
772- (void) updateUI: (NSTimer *) t
773{
774    NSEnumerator * enumerator = [fTorrents objectEnumerator];
775    Torrent * torrent;
776    while ((torrent = [enumerator nextObject]))
777    {
778        [torrent update];
779
780        if ([torrent justFinished])
781        {
782            [self checkWaitingForFinished: torrent];
783       
784            //notifications
785            [self notifyGrowl: @"Download Complete" message: [[torrent name] stringByAppendingString:
786                                                    @" finished downloading"] identifier: @"Download Complete"];
787            if (![fWindow isKeyWindow])
788                fCompleted++;
789        }
790    }
791
792    if ([fSortType isEqualToString: @"Progress"] || [fSortType isEqualToString: @"State"])
793        [self sortTorrents];
794    else
795        [fTableView reloadData];
796   
797    //update the global DL/UL rates
798    float downloadRate, uploadRate;
799    tr_torrentRates(fLib, & downloadRate, & uploadRate);
800    if (fStatusBarVisible)
801    {
802        [fTotalDLField setStringValue: [NSString stringForSpeed: downloadRate]];
803        [fTotalULField setStringValue: [NSString stringForSpeed: uploadRate]];
804    }
805
806    if ([[fInfoController window] isVisible])
807        [fInfoController updateInfoStats];
808
809    //badge dock
810    [fBadger updateBadgeWithCompleted: fCompleted
811        uploadRate: uploadRate downloadRate: downloadRate];
812}
813
814- (void) updateTorrentHistory
815{
816    NSMutableArray * history = [NSMutableArray
817        arrayWithCapacity: [fTorrents count]];
818
819    NSEnumerator * enumerator = [fTorrents objectEnumerator];
820    Torrent * torrent;
821    while( ( torrent = [enumerator nextObject] ) )
822        [history addObject: [torrent history]];
823
824    [fDefaults setObject: history forKey: @"History"];
825    [fDefaults synchronize];
826}
827
828- (void) sortTorrents
829{
830    //remember selected rows if needed
831    NSArray * selectedTorrents = nil;
832    int numSelected = [fTableView numberOfSelectedRows];
833    if (numSelected > 0 && numSelected < [fTorrents count])
834        selectedTorrents = [self torrentsAtIndexes: [fTableView selectedRowIndexes]];
835
836    NSSortDescriptor * nameDescriptor = [[[NSSortDescriptor alloc] initWithKey:
837                                            @"name" ascending: YES] autorelease],
838                    * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
839                                            @"orderValue" ascending: YES] autorelease];
840
841    NSArray * descriptors;
842    if ([fSortType isEqualToString: @"Name"])
843        descriptors = [[NSArray alloc] initWithObjects: nameDescriptor, orderDescriptor, nil];
844    else if ([fSortType isEqualToString: @"State"])
845    {
846        NSSortDescriptor * stateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
847                                                @"stateSortKey" ascending: NO] autorelease],
848                        * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
849                                            @"progressSortKey" ascending: NO] autorelease];
850       
851        descriptors = [[NSArray alloc] initWithObjects: stateDescriptor, progressDescriptor,
852                                                            nameDescriptor, orderDescriptor, nil];
853    }
854    else if ([fSortType isEqualToString: @"Progress"])
855    {
856        NSSortDescriptor * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
857                                            @"progressSortKey" ascending: YES] autorelease];
858       
859        descriptors = [[NSArray alloc] initWithObjects: progressDescriptor, nameDescriptor, orderDescriptor, nil];
860    }
861    else if ([fSortType isEqualToString: @"Date"])
862    {
863        NSSortDescriptor * dateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
864                                            @"date" ascending: YES] autorelease];
865   
866        descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, orderDescriptor, nil];
867    }
868    else
869        descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
870
871    [fTorrents sortUsingDescriptors: descriptors];
872   
873    [descriptors release];
874   
875    [fTableView reloadData];
876   
877    //set selected rows if needed
878    if (selectedTorrents)
879    {
880        Torrent * torrent;
881        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
882        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
883        while ((torrent = [enumerator nextObject]))
884            [indexSet addIndex: [fTorrents indexOfObject: torrent]];
885       
886        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
887        [indexSet release];
888    }
889}
890
891- (void) setSort: (id) sender
892{
893    NSMenuItem * prevSortItem;
894    if ([fSortType isEqualToString: @"Name"])
895        prevSortItem = fNameSortItem;
896    else if ([fSortType isEqualToString: @"State"])
897        prevSortItem = fStateSortItem;
898    else if ([fSortType isEqualToString: @"Progress"])
899        prevSortItem = fProgressSortItem;
900    else if ([fSortType isEqualToString: @"Date"])
901        prevSortItem = fDateSortItem;
902    else
903        prevSortItem = fOrderSortItem;
904   
905    if (sender != prevSortItem)
906    {
907        [prevSortItem setState: NSOffState];
908        [sender setState: NSOnState];
909
910        [fSortType release];
911        if (sender == fNameSortItem)
912            fSortType = [[NSString alloc] initWithString: @"Name"];
913        else if (sender == fStateSortItem)
914            fSortType = [[NSString alloc] initWithString: @"State"];
915        else if (sender == fProgressSortItem)
916            fSortType = [[NSString alloc] initWithString: @"Progress"];
917        else if (sender == fDateSortItem)
918            fSortType = [[NSString alloc] initWithString: @"Date"];
919        else
920            fSortType = [[NSString alloc] initWithString: @"Order"];
921           
922        [fDefaults setObject: fSortType forKey: @"Sort"];
923    }
924
925    [self sortTorrents];
926}
927
928- (void) toggleSpeedLimit: (id) sender
929{
930    int state = [fSpeedLimitItem state] ? NSOffState : NSOnState;
931
932    [fSpeedLimitItem setState: state];
933    [fSpeedLimitDockItem setState: state];
934    [fSpeedLimitButton setState: state];
935   
936    [fPrefsController enableSpeedLimit: state];
937}
938
939- (void) setLimitGlobalEnabled: (id) sender
940{
941    [fPrefsController setLimitEnabled: (sender == fUploadLimitItem || sender == fDownloadLimitItem)
942        type: (sender == fUploadLimitItem || sender == fUploadNoLimitItem) ? @"Upload" : @"Download"];
943}
944
945- (void) setQuickLimitGlobal: (id) sender
946{
947    [fPrefsController setQuickLimit: [[sender title] intValue]
948        type: [sender menu] == fUploadMenu ? @"Upload" : @"Download"];
949}
950
951- (void) limitGlobalChange: (NSNotification *) notification
952{
953    NSDictionary * dict = [notification object];
954   
955    NSMenuItem * limitItem, * noLimitItem;
956    if ([[dict objectForKey: @"Type"] isEqualToString: @"Upload"])
957    {
958        limitItem = fUploadLimitItem;
959        noLimitItem = fUploadNoLimitItem;
960    }
961    else
962    {
963        limitItem = fDownloadLimitItem;
964        noLimitItem = fDownloadNoLimitItem;
965    }
966   
967    BOOL enable = [[dict objectForKey: @"Enable"] boolValue];
968    [limitItem setState: enable ? NSOnState : NSOffState];
969    [noLimitItem setState: !enable ? NSOnState : NSOffState];
970   
971    [limitItem setTitle: [NSString stringWithFormat: @"Limit (%d KB/s)",
972                            [[dict objectForKey: @"Limit"] intValue]]];
973
974    [dict release];
975}
976
977- (void) setRatioGlobalEnabled: (id) sender
978{
979    [fPrefsController setRatioEnabled: sender == fRatioSetItem];
980}
981
982- (void) setQuickRatioGlobal: (id) sender
983{
984    [fPrefsController setQuickRatio: [[sender title] floatValue]];
985}
986
987- (void) ratioGlobalChange: (NSNotification *) notification
988{
989    NSDictionary * dict = [notification object];
990   
991    BOOL enable = [[dict objectForKey: @"Enable"] boolValue];
992    [fRatioSetItem setState: enable ? NSOnState : NSOffState];
993    [fRatioNotSetItem setState: !enable ? NSOnState : NSOffState];
994   
995    [fRatioSetItem setTitle: [NSString stringWithFormat: @"Stop at Ratio (%.2f)",
996                            [[dict objectForKey: @"Ratio"] floatValue]]];
997
998    [dict release];
999}
1000
1001- (void) checkWaitingForStopped: (NSNotification *) notification
1002{
1003    [self checkWaitingForFinished: [notification object]];
1004}
1005
1006- (void) checkWaitingForFinished: (Torrent *) finishedTorrent
1007{
1008    //don't try to start a transfer if there should be none waiting
1009    if (![[fDefaults stringForKey: @"StartSetting"] isEqualToString: @"Wait"])
1010        return;
1011
1012    int desiredActive = [fDefaults integerForKey: @"WaitToStartNumber"];
1013   
1014    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1015    Torrent * torrent, * torrentToStart = nil;
1016    while ((torrent = [enumerator nextObject]))
1017    {
1018        //ignore the torrent just stopped
1019        if (torrent == finishedTorrent)
1020            continue;
1021   
1022        if ([torrent isActive])
1023        {
1024            if (![torrent isSeeding])
1025            {
1026                desiredActive--;
1027                if (desiredActive <= 0)
1028                    return;
1029            }
1030        }
1031        else
1032        {
1033            //use as next if it is waiting to start and either no previous or order value is lower
1034            if ([torrent waitingToStart] && (!torrentToStart
1035                || [[torrentToStart orderValue] compare: [torrent orderValue]] == NSOrderedDescending))
1036                torrentToStart = torrent;
1037        }
1038    }
1039   
1040    //since it hasn't returned, the queue amount has not been met
1041    if (torrentToStart)
1042    {
1043        [torrentToStart startTransfer];
1044       
1045        [self updateUI: nil];
1046        [fInfoController updateInfoStatsAndSettings];
1047        [self updateTorrentHistory];
1048    }
1049}
1050
1051- (void) torrentStartSettingChange: (NSNotification *) notification
1052{
1053    [self attemptToStartMultipleAuto: [notification object]];
1054
1055    [self updateUI: nil];
1056    [fInfoController updateInfoStatsAndSettings];
1057    [self updateTorrentHistory];
1058}
1059
1060- (void) globalStartSettingChange: (NSNotification *) notification
1061{
1062    [self attemptToStartMultipleAuto: fTorrents];
1063   
1064    [self updateUI: nil];
1065    [fInfoController updateInfoStatsAndSettings];
1066    [self updateTorrentHistory];
1067}
1068
1069- (void) torrentStoppedForRatio: (NSNotification *) notification
1070{
1071    [fInfoController updateInfoStatsAndSettings];
1072   
1073    [self notifyGrowl: @"Seeding Complete" message: [[[notification object] name] stringByAppendingString:
1074                                                        @" finished seeding"] identifier: @"Seeding Complete"];
1075}
1076
1077- (void) attemptToStartAuto: (Torrent *) torrent
1078{
1079    [self attemptToStartMultipleAuto: [NSArray arrayWithObject: torrent]];
1080}
1081
1082//will try to start, taking into consideration the start preference
1083- (void) attemptToStartMultipleAuto: (NSArray *) torrents
1084{
1085    NSString * startSetting = [fDefaults stringForKey: @"StartSetting"];
1086    if ([startSetting isEqualToString: @"Start"])
1087    {
1088        NSEnumerator * enumerator = [torrents objectEnumerator];
1089        Torrent * torrent;
1090        while ((torrent = [enumerator nextObject]))
1091            if ([torrent waitingToStart])
1092                [torrent startTransfer];
1093       
1094        return;
1095    }
1096    else if (![startSetting isEqualToString: @"Wait"])
1097        return;
1098    else;
1099   
1100    //determine the number of downloads needed to start
1101    int desiredActive = [fDefaults integerForKey: @"WaitToStartNumber"];
1102           
1103    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1104    Torrent * torrent;
1105    while ((torrent = [enumerator nextObject]))
1106        if ([torrent isActive] && ![torrent isSeeding])
1107        {
1108            desiredActive--;
1109            if (desiredActive <= 0)
1110                break;
1111        }
1112   
1113    //sort torrents by order value
1114    NSArray * sortedTorrents;
1115    if ([torrents count] > 1 && desiredActive > 0)
1116    {
1117        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1118                                                    @"orderValue" ascending: YES] autorelease];
1119        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1120       
1121        sortedTorrents = [torrents sortedArrayUsingDescriptors: descriptors];
1122        [descriptors release];
1123    }
1124    else
1125        sortedTorrents = torrents;
1126
1127    enumerator = [sortedTorrents objectEnumerator];
1128    while ((torrent = [enumerator nextObject]))
1129    {
1130        if ([torrent progress] >= 1.0)
1131            [torrent startTransfer];
1132        else
1133        {
1134            if ([torrent waitingToStart] && desiredActive > 0)
1135            {
1136                [torrent startTransfer];
1137                desiredActive--;
1138            }
1139        }
1140       
1141        [torrent update];
1142    }
1143}
1144
1145- (void) reloadInspectorSettings: (NSNotification *) notification
1146{
1147    [fInfoController updateInfoStatsAndSettings];
1148}
1149
1150- (void) checkAutoImportDirectory: (NSTimer *) timer
1151{
1152    if (![fDefaults boolForKey: @"AutoImport"])
1153        return;
1154       
1155    NSString * path = [[fDefaults stringForKey: @"AutoImportDirectory"] stringByExpandingTildeInPath];
1156   
1157    //if folder cannot be found or the contents hasn't changed simply give up
1158    NSArray * allFileNames;
1159    if (!(allFileNames = [[NSFileManager defaultManager] directoryContentsAtPath: path])
1160            || [allFileNames isEqualToArray: fAutoImportedNames])
1161        return;
1162
1163    //try to add files that haven't already been added
1164    NSMutableArray * newFileNames = [NSMutableArray arrayWithArray: allFileNames];
1165    [newFileNames removeObjectsInArray: fAutoImportedNames];
1166   
1167    //save the current list of files
1168    [fAutoImportedNames release];
1169    fAutoImportedNames = [allFileNames retain];
1170   
1171    NSEnumerator * enumerator = [newFileNames objectEnumerator];
1172    NSString * file;
1173    unsigned oldCount;
1174    while ((file = [enumerator nextObject]))
1175        if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame)
1176        {
1177            oldCount = [fTorrents count];
1178            [self openFromSheet: [NSArray arrayWithObject: [path stringByAppendingPathComponent: file]]];
1179           
1180            //import only actually happened if the torrent array is larger
1181            if (oldCount < [fTorrents count])
1182                [self notifyGrowl: @"Torrent File Auto Added" message: [file stringByAppendingString:
1183                                                    @" added to Transmission"] identifier: @"Torrent Auto Added"];
1184        }
1185}
1186
1187- (void) autoImportChange: (NSNotification *) notification
1188{
1189    [fAutoImportedNames release];
1190    fAutoImportedNames = [[NSArray alloc] init];
1191   
1192    [self checkAutoImportDirectory: nil];
1193}
1194
1195- (int) numberOfRowsInTableView: (NSTableView *) t
1196{
1197    return [fTorrents count];
1198}
1199
1200- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell
1201    forTableColumn: (NSTableColumn *) tableColumn row: (int) row
1202{
1203    [cell setTorrent: [fTorrents objectAtIndex: row]];
1204}
1205
1206- (BOOL) tableView: (NSTableView *) tableView writeRowsWithIndexes: (NSIndexSet *) indexes
1207    toPasteboard: (NSPasteboard *) pasteboard
1208{
1209    if ([fSortType isEqualToString: @"Order"])
1210    {
1211        [pasteboard declareTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE] owner: self];
1212        [pasteboard setData: [NSKeyedArchiver archivedDataWithRootObject: indexes]
1213                                forType: TORRENT_TABLE_VIEW_DATA_TYPE];
1214        return YES;
1215    }
1216    return NO;
1217}
1218
1219- (NSDragOperation) tableView: (NSTableView *) t validateDrop: (id <NSDraggingInfo>) info
1220    proposedRow: (int) row proposedDropOperation: (NSTableViewDropOperation) operation
1221{
1222    NSPasteboard * pasteboard = [info draggingPasteboard];
1223    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
1224    {
1225        //check if any files to add have "torrent" as an extension
1226        NSEnumerator * enumerator = [[pasteboard propertyListForType: NSFilenamesPboardType] objectEnumerator];
1227        NSString * file;
1228        while ((file = [enumerator nextObject]))
1229            if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame)
1230            {
1231                [fTableView setDropRow: -1 dropOperation: NSTableViewDropOn];
1232                return NSDragOperationGeneric;
1233            }
1234    }
1235    else if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
1236    {
1237        [fTableView setDropRow: row dropOperation: NSTableViewDropAbove];
1238        return NSDragOperationGeneric;
1239    }
1240    else;
1241   
1242    return NSDragOperationNone;
1243}
1244
1245- (BOOL) tableView: (NSTableView *) t acceptDrop: (id <NSDraggingInfo>) info
1246    row: (int) newRow dropOperation: (NSTableViewDropOperation) operation
1247{
1248    NSPasteboard * pasteboard = [info draggingPasteboard];
1249    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
1250    {
1251        //create an array of files with the "torrent" extension
1252        NSMutableArray * filesToOpen = [[NSMutableArray alloc] init];
1253        NSEnumerator * enumerator = [[pasteboard propertyListForType: NSFilenamesPboardType] objectEnumerator];
1254        NSString * file;
1255        while ((file = [enumerator nextObject]))
1256            if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] == NSOrderedSame)
1257                [filesToOpen addObject: file];
1258   
1259        [self application: NSApp openFiles: filesToOpen];
1260        [filesToOpen release];
1261    }
1262    else
1263    {
1264        //remember selected rows if needed
1265        NSArray * selectedTorrents = nil;
1266        int numSelected = [fTableView numberOfSelectedRows];
1267        if (numSelected > 0 && numSelected < [fTorrents count])
1268            selectedTorrents = [self torrentsAtIndexes: [fTableView selectedRowIndexes]];
1269   
1270        NSIndexSet * indexes = [NSKeyedUnarchiver unarchiveObjectWithData:
1271                                [pasteboard dataForType: TORRENT_TABLE_VIEW_DATA_TYPE]];
1272       
1273        //move torrent in array
1274        NSArray * movingTorrents = [[self torrentsAtIndexes: indexes] retain];
1275        [fTorrents removeObjectsInArray: movingTorrents];
1276       
1277        //determine the insertion index now that transfers to move have been removed
1278        int i, decrease = 0;
1279        for (i = [indexes firstIndex]; i < newRow && i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
1280            decrease++;
1281       
1282        //insert objects at new location
1283        for (i = 0; i < [movingTorrents count]; i++)
1284            [fTorrents insertObject: [movingTorrents objectAtIndex: i] atIndex: newRow - decrease + i];
1285       
1286        [movingTorrents release];
1287       
1288        //redo order values
1289        int low = [indexes firstIndex], high = [indexes lastIndex];
1290        if (newRow < low)
1291            low = newRow;
1292        else if (newRow > high + 1)
1293            high = newRow - 1;
1294        else;
1295       
1296        for (i = low; i <= high; i++)
1297            [[fTorrents objectAtIndex: i] setOrderValue: i];
1298       
1299        [fTableView reloadData];
1300       
1301        //set selected rows if needed
1302        if (selectedTorrents)
1303        {
1304            Torrent * torrent;
1305            NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1306            NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1307            while ((torrent = [enumerator nextObject]))
1308                [indexSet addIndex: [fTorrents indexOfObject: torrent]];
1309           
1310            [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1311            [indexSet release];
1312        }
1313    }
1314   
1315    return YES;
1316}
1317
1318- (void) tableViewSelectionDidChange: (NSNotification *) notification
1319{
1320    [fInfoController updateInfoForTorrents: [self torrentsAtIndexes: [fTableView selectedRowIndexes]]];
1321}
1322
1323- (void) toggleSmallView: (id) sender
1324{
1325    BOOL makeSmall = ![fDefaults boolForKey: @"SmallView"];
1326   
1327    [fTableView setRowHeight: makeSmall ? ROW_HEIGHT_SMALL : ROW_HEIGHT_REGULAR];
1328    [fSmallViewItem setState: makeSmall];
1329   
1330    [fDefaults setBool: makeSmall forKey: @"SmallView"];
1331}
1332
1333- (void) toggleStatusBar: (id) sender
1334{
1335    [self showStatusBar: !fStatusBarVisible animate: YES];
1336    [fDefaults setBool: fStatusBarVisible forKey: @"StatusBar"];
1337}
1338
1339- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
1340{
1341    if (show == fStatusBarVisible)
1342        return;
1343
1344    NSRect frame = [fWindow frame];
1345    float heightChange = [fStatusBar frame].size.height;
1346    if (!show)
1347        heightChange *= -1;
1348
1349    frame.size.height += heightChange;
1350    frame.origin.y -= heightChange;
1351       
1352    fStatusBarVisible = !fStatusBarVisible;
1353   
1354    [self updateUI: nil];
1355   
1356    //set views to not autoresize
1357    unsigned int statsMask = [fStatusBar autoresizingMask];
1358    unsigned int scrollMask = [fScrollView autoresizingMask];
1359    [fStatusBar setAutoresizingMask: 0];
1360    [fScrollView setAutoresizingMask: 0];
1361   
1362    [fWindow setFrame: frame display: YES animate: animate];
1363   
1364    //re-enable autoresize
1365    [fStatusBar setAutoresizingMask: statsMask];
1366    [fScrollView setAutoresizingMask: scrollMask];
1367   
1368    //change min size
1369    NSSize minSize = [fWindow contentMinSize];
1370    minSize.height += heightChange;
1371    [fWindow setContentMinSize: minSize];
1372}
1373
1374- (void) toggleAdvancedBar: (id) sender
1375{
1376    int state = ![fAdvancedBarItem state];
1377    [fAdvancedBarItem setState: state];
1378    [fDefaults setBool: state forKey: @"UseAdvancedBar"];
1379
1380    [fTableView display];
1381}
1382
1383- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
1384    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
1385{
1386    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
1387
1388    if ([ident isEqualToString: TOOLBAR_OPEN])
1389    {
1390        [item setLabel: @"Open"];
1391        [item setPaletteLabel: @"Open Torrent Files"];
1392        [item setToolTip: @"Open torrent files"];
1393        [item setImage: [NSImage imageNamed: @"Open.png"]];
1394        [item setTarget: self];
1395        [item setAction: @selector(openShowSheet:)];
1396    }
1397    else if ([ident isEqualToString: TOOLBAR_REMOVE])
1398    {
1399        [item setLabel: @"Remove"];
1400        [item setPaletteLabel: @"Remove Selected"];
1401        [item setToolTip: @"Remove selected transfers"];
1402        [item setImage: [NSImage imageNamed: @"Remove.png"]];
1403        [item setTarget: self];
1404        [item setAction: @selector(removeNoDelete:)];
1405    }
1406    else if ([ident isEqualToString: TOOLBAR_INFO])
1407    {
1408        [item setLabel: @"Inspector"];
1409        [item setPaletteLabel: @"Show/Hide Inspector"];
1410        [item setToolTip: @"Display torrent inspector"];
1411        [item setImage: [NSImage imageNamed: @"Info.png"]];
1412        [item setTarget: self];
1413        [item setAction: @selector(showInfo:)];
1414    }
1415    else if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
1416    {
1417        [item setLabel: @"Pause All"];
1418        [item setPaletteLabel: [item label]];
1419        [item setToolTip: @"Pause all transfers"];
1420        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
1421        [item setTarget: self];
1422        [item setAction: @selector(stopAllTorrents:)];
1423    }
1424    else if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
1425    {
1426        [item setLabel: @"Resume All"];
1427        [item setPaletteLabel: [item label]];
1428        [item setToolTip: @"Resume all transfers"];
1429        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
1430        [item setTarget: self];
1431        [item setAction: @selector(resumeAllTorrents:)];
1432    }
1433    else if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
1434    {
1435        [item setLabel: @"Pause"];
1436        [item setPaletteLabel: @"Pause Selected"];
1437        [item setToolTip: @"Pause selected transfers"];
1438        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
1439        [item setTarget: self];
1440        [item setAction: @selector(stopSelectedTorrents:)];
1441    }
1442    else if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
1443    {
1444        [item setLabel: @"Resume"];
1445        [item setPaletteLabel: @"Resume Selected"];
1446        [item setToolTip: @"Resume selected transfers"];
1447        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
1448        [item setTarget: self];
1449        [item setAction: @selector(resumeSelectedTorrents:)];
1450    }
1451    else
1452    {
1453        [item release];
1454        return nil;
1455    }
1456
1457    return item;
1458}
1459
1460- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
1461{
1462    return [NSArray arrayWithObjects:
1463            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1464            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
1465            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL, TOOLBAR_INFO,
1466            NSToolbarSeparatorItemIdentifier,
1467            NSToolbarSpaceItemIdentifier,
1468            NSToolbarFlexibleSpaceItemIdentifier,
1469            NSToolbarCustomizeToolbarItemIdentifier, nil];
1470}
1471
1472- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
1473{
1474    return [NSArray arrayWithObjects:
1475            TOOLBAR_OPEN, TOOLBAR_REMOVE,
1476            NSToolbarSeparatorItemIdentifier,
1477            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
1478            NSToolbarFlexibleSpaceItemIdentifier,
1479            TOOLBAR_INFO, nil];
1480}
1481
1482- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
1483{
1484    NSString * ident = [toolbarItem itemIdentifier];
1485
1486    //enable remove item
1487    if ([ident isEqualToString: TOOLBAR_REMOVE])
1488        return [fTableView numberOfSelectedRows] > 0;
1489
1490    //enable pause all item
1491    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
1492    {
1493        Torrent * torrent;
1494        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1495        while ((torrent = [enumerator nextObject]))
1496            if ([torrent isActive])
1497                return YES;
1498        return NO;
1499    }
1500
1501    //enable resume all item
1502    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
1503    {
1504        Torrent * torrent;
1505        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1506        while ((torrent = [enumerator nextObject]))
1507            if ([torrent isPaused])
1508                return YES;
1509        return NO;
1510    }
1511
1512    //enable pause item
1513    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
1514    {
1515        Torrent * torrent;
1516        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1517        unsigned int i;
1518       
1519        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1520            if ([[fTorrents objectAtIndex: i] isActive])
1521                return YES;
1522        return NO;
1523    }
1524   
1525    //enable resume item
1526    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
1527    {
1528        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1529        unsigned int i;
1530       
1531        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1532            if ([[fTorrents objectAtIndex: i] isPaused])
1533                return YES;
1534        return NO;
1535    }
1536
1537    return YES;
1538}
1539
1540- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
1541{
1542    SEL action = [menuItem action];
1543
1544    //only enable some items if it is in a context menu or the window is useable
1545    BOOL canUseMenu = [fWindow isKeyWindow] || [[[menuItem menu] title] isEqualToString: @"Context"];
1546
1547    //enable show info
1548    if (action == @selector(showInfo:))
1549    {
1550        NSString * title = [[fInfoController window] isVisible] ? @"Hide Inspector" : @"Show Inspector";
1551        if (![[menuItem title] isEqualToString: title])
1552                [menuItem setTitle: title];
1553
1554        return YES;
1555    }
1556   
1557    //enable prev/next inspector tab
1558    if (action == @selector(setInfoTab:))
1559        return [[fInfoController window] isVisible];
1560   
1561    //enable toggle status bar
1562    if (action == @selector(toggleStatusBar:))
1563    {
1564        NSString * title = fStatusBarVisible ? @"Hide Status Bar" : @"Show Status Bar";
1565        if (![[menuItem title] isEqualToString: title])
1566                [menuItem setTitle: title];
1567
1568        return canUseMenu;
1569    }
1570
1571    //enable resume all item
1572    if (action == @selector(resumeAllTorrents:))
1573    {
1574        Torrent * torrent;
1575        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1576        while ((torrent = [enumerator nextObject]))
1577            if ([torrent isPaused])
1578                return YES;
1579        return NO;
1580    }
1581
1582    //enable pause all item
1583    if (action == @selector(stopAllTorrents:))
1584    {
1585        Torrent * torrent;
1586        NSEnumerator * enumerator = [fTorrents objectEnumerator];
1587        while ((torrent = [enumerator nextObject]))
1588            if ([torrent isActive])
1589                return YES;
1590        return NO;
1591    }
1592
1593    //enable reveal in finder
1594    if (action == @selector(revealFile:))
1595        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1596
1597    //enable remove items
1598    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
1599        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteBoth:))
1600    {
1601        BOOL warning = NO,
1602            onlyDownloading = [fDefaults boolForKey: @"CheckRemoveDownloading"],
1603            canDelete = action != @selector(removeDeleteTorrent:) && action != @selector(removeDeleteBoth:);
1604        Torrent * torrent;
1605        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1606        unsigned int i;
1607       
1608        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1609        {
1610            torrent = [fTorrents objectAtIndex: i];
1611            if (!warning && [torrent isActive])
1612            {
1613                warning = onlyDownloading ? ![torrent isSeeding] : YES;
1614                if (warning && canDelete)
1615                    break;
1616            }
1617            if (!canDelete && [torrent publicTorrent])
1618            {
1619                canDelete = YES;
1620                if (warning)
1621                    break;
1622            }
1623        }
1624   
1625        //append or remove ellipsis when needed
1626        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
1627        if (warning && [fDefaults boolForKey: @"CheckRemove"])
1628        {
1629            if (![title hasSuffix: ellipsis])
1630                [menuItem setTitle: [title stringByAppendingEllipsis]];
1631        }
1632        else
1633        {
1634            if ([title hasSuffix: ellipsis])
1635                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
1636        }
1637       
1638        return canUseMenu && canDelete && [fTableView numberOfSelectedRows] > 0;
1639    }
1640
1641    //enable pause item
1642    if (action == @selector(stopSelectedTorrents:))
1643    {
1644        if (!canUseMenu)
1645            return NO;
1646   
1647        Torrent * torrent;
1648        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1649        unsigned int i;
1650       
1651        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1652        {
1653            torrent = [fTorrents objectAtIndex: i];
1654            if ([torrent isActive])
1655                return YES;
1656        }
1657        return NO;
1658    }
1659   
1660    //enable resume item
1661    if (action == @selector(resumeSelectedTorrents:))
1662    {
1663        if (!canUseMenu)
1664            return NO;
1665   
1666        Torrent * torrent;
1667        NSIndexSet * indexSet = [fTableView selectedRowIndexes];
1668        unsigned int i;
1669       
1670        for (i = [indexSet firstIndex]; i != NSNotFound; i = [indexSet indexGreaterThanIndex: i])
1671        {
1672            torrent = [fTorrents objectAtIndex: i];
1673            if ([torrent isPaused])
1674                return YES;
1675        }
1676        return NO;
1677    }
1678   
1679    //enable sort and advanced bar items
1680    if (action == @selector(setSort:) || action == @selector(toggleAdvancedBar:)
1681            || action == @selector(toggleSmallView:))
1682        return canUseMenu;
1683   
1684    //enable copy torrent file item
1685    if (action == @selector(copyTorrentFile:))
1686    {
1687        return canUseMenu && [fTableView numberOfSelectedRows] > 0;
1688    }
1689
1690    return YES;
1691}
1692
1693- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument
1694{
1695    NSEnumerator * enumerator;
1696    Torrent * torrent;
1697    BOOL active;
1698
1699    switch (messageType)
1700    {
1701        case kIOMessageSystemWillSleep:
1702            //close all connections before going to sleep and remember we should resume when we wake up
1703            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
1704
1705            //wait for running transfers to stop (5 second timeout)
1706            NSDate * start = [NSDate date];
1707            BOOL timeUp = NO;
1708           
1709            NSEnumerator * enumerator = [fTorrents objectEnumerator];
1710            Torrent * torrent;
1711            while (!timeUp && (torrent = [enumerator nextObject]))
1712                while (![torrent isPaused] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
1713                {
1714                    usleep(100000);
1715                    [torrent update];
1716                }
1717
1718            IOAllowPowerChange(fRootPort, (long) messageArgument);
1719            break;
1720
1721        case kIOMessageCanSystemSleep:
1722            /* Prevent idle sleep unless all paused */
1723            active = NO;
1724            enumerator = [fTorrents objectEnumerator];
1725            while ((torrent = [enumerator nextObject]))
1726                if ([torrent isActive])
1727                {
1728                    active = YES;
1729                    break;
1730                }
1731
1732            if (active)
1733                IOCancelPowerChange(fRootPort, (long) messageArgument);
1734            else
1735                IOAllowPowerChange(fRootPort, (long) messageArgument);
1736            break;
1737
1738        case kIOMessageSystemHasPoweredOn:
1739            //resume sleeping transfers after we wake up
1740            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
1741            break;
1742    }
1743}
1744
1745- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
1746{
1747    NSRect windowRect = [fWindow frame];
1748    float newHeight = windowRect.size.height - [fScrollView frame].size.height
1749        + [fTorrents count] * ([fTableView rowHeight] + [fTableView intercellSpacing].height) + 30.0;
1750
1751    float minHeight = [fWindow minSize].height;
1752    if (newHeight < minHeight)
1753        newHeight = minHeight;
1754
1755    windowRect.origin.y -= (newHeight - windowRect.size.height);
1756    windowRect.size.height = newHeight;
1757
1758    return windowRect;
1759}
1760
1761- (void) showMainWindow: (id) sender
1762{
1763    [fWindow makeKeyAndOrderFront: nil];
1764}
1765
1766- (void) windowDidBecomeKey: (NSNotification *) notification
1767{
1768    fCompleted = 0;
1769}
1770
1771- (void) linkHomepage: (id) sender
1772{
1773    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
1774}
1775
1776- (void) linkForums: (id) sender
1777{
1778    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
1779}
1780
1781- (void) notifyGrowl: (NSString *) title message: (NSString *) message identifier: (NSString *) ident
1782{
1783    if (!fHasGrowl)
1784        return;
1785
1786    NSString * growlScript = [NSString stringWithFormat:
1787        @"tell application \"System Events\"\n"
1788         "  if exists application process \"GrowlHelperApp\" then\n"
1789         "    tell application \"GrowlHelperApp\"\n "
1790         "      notify with name \"%@\""
1791         "        title \"%@\""
1792         "        description \"%@\""
1793         "        application name \"Transmission\"\n"
1794         "    end tell\n"
1795         "  end if\n"
1796         "end tell", ident, title, message];
1797   
1798    NSAppleScript * appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1799    NSDictionary * error;
1800    if (![appleScript executeAndReturnError: & error])
1801        NSLog(@"Growl notify failed");
1802    [appleScript release];
1803}
1804
1805- (void) growlRegister
1806{
1807    if (!fHasGrowl)
1808        return;
1809
1810    NSString * growlScript = @"tell application \"System Events\"\n"
1811         "  if exists application process \"GrowlHelperApp\" then\n"
1812         "    tell application \"GrowlHelperApp\"\n"
1813         "      register as application \"Transmission\" "
1814         "        all notifications {\"Download Complete\", \"Seeding Complete\", \"Torrent Auto Added\"}"
1815         "        default notifications {\"Download Complete\", \"Seeding Complete\", \"Torrent Auto Added\"}"
1816         "        icon of application \"Transmission\"\n"
1817         "    end tell\n"
1818         "  end if\n"
1819         "end tell";
1820
1821    NSAppleScript * appleScript = [[NSAppleScript alloc] initWithSource: growlScript];
1822    NSDictionary * error;
1823    if (![appleScript executeAndReturnError: & error])
1824        NSLog(@"Growl registration failed");
1825    [appleScript release];
1826}
1827
1828- (void) checkUpdate: (id) sender
1829{
1830    [fPrefsController checkUpdate];
1831}
1832
1833- (void) prepareForUpdate: (NSNotification *) notification
1834{
1835    fUpdateInProgress = YES;
1836}
1837
1838@end
Note: See TracBrowser for help on using the repository browser.