source: trunk/macosx/Controller.m @ 497

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

Auto-start check in the inspector now can be applied to multiple torrents.

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