source: trunk/macosx/Controller.m @ 459

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

Getting closer to a perfected queue.

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