source: trunk/macosx/Controller.m @ 2729

Last change on this file since 2729 was 2729, checked in by livings124, 15 years ago

(trunk) fix bug where downloads would not be added when dragging the url onto the window without a constant download location

  • Property svn:keywords set to Date Rev Author Id
File size: 120.3 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 2729 2007-08-13 03:17:18Z livings124 $
3 *
4 * Copyright (c) 2005-2007 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 "CreatorWindowController.h"
32#import "AboutWindowController.h"
33#import "StringAdditions.h"
34#import "UKKQueue.h"
35#import "ActionMenuSpeedToDisplayLimitTransformer.h"
36#import "ActionMenuRatioToDisplayRatioTransformer.h"
37#import "ExpandedPathToPathTransformer.h"
38#import "ExpandedPathToIconTransformer.h"
39#import "SpeedLimitToTurtleIconTransformer.h"
40
41#import <Sparkle/Sparkle.h>
42
43#define TOOLBAR_CREATE          @"Toolbar Create"
44#define TOOLBAR_OPEN            @"Toolbar Open"
45#define TOOLBAR_REMOVE          @"Toolbar Remove"
46#define TOOLBAR_INFO            @"Toolbar Info"
47#define TOOLBAR_PAUSE_ALL       @"Toolbar Pause All"
48#define TOOLBAR_RESUME_ALL      @"Toolbar Resume All"
49#define TOOLBAR_PAUSE_SELECTED  @"Toolbar Pause Selected"
50#define TOOLBAR_RESUME_SELECTED @"Toolbar Resume Selected"
51#define TOOLBAR_FILTER          @"Toolbar Toggle Filter"
52
53#define GROWL_DOWNLOAD_COMPLETE @"Download Complete"
54#define GROWL_SEEDING_COMPLETE  @"Seeding Complete"
55#define GROWL_AUTO_ADD          @"Torrent Auto Added"
56#define GROWL_AUTO_SPEED_LIMIT  @"Speed Limit Auto Changed"
57
58#define TORRENT_TABLE_VIEW_DATA_TYPE    @"TorrentTableViewDataType"
59
60#define ROW_HEIGHT_REGULAR      65.0
61#define ROW_HEIGHT_SMALL        40.0
62#define WINDOW_REGULAR_WIDTH    468.0
63
64#define SEARCH_FILTER_MIN_WIDTH 55.0
65#define SEARCH_FILTER_MAX_WIDTH 115.0
66
67#define UPDATE_UI_SECONDS           1.0
68#define AUTO_SPEED_LIMIT_SECONDS    5.0
69
70#define DOCK_SEEDING_TAG        101
71#define DOCK_DOWNLOADING_TAG    102
72
73#define WEBSITE_URL @"http://transmission.m0k.org/"
74#define FORUM_URL   @"http://transmission.m0k.org/forum/"
75#define DONATE_URL   @"http://transmission.m0k.org/donate.php"
76
77static void sleepCallBack(void * controller, io_service_t y, natural_t messageType, void * messageArgument)
78{
79    Controller * c = controller;
80    [c sleepCallBack: messageType argument: messageArgument];
81}
82
83@implementation Controller
84
85+ (void) initialize
86{
87    //make sure system requirements are met
88    if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_3)
89    {
90        NSAlert * alert = [[NSAlert alloc] init];
91        [alert addButtonWithTitle: NSLocalizedString(@"Quit", "OS update needed -> button")];
92        [alert setMessageText: NSLocalizedString(@"Transmission requires Mac OS X 10.4 or greater.",
93                                                "OS update needed -> title")];
94        [alert setInformativeText: NSLocalizedString(@"Transmission has features that require a more "
95            "up-to-date operating system. The operating system must be updated before this version can run.",
96            "OS update needed -> message")];
97        [alert setAlertStyle: NSWarningAlertStyle];
98       
99        [alert runModal];
100        [alert release];
101       
102        exit(0);
103    }
104   
105    //make sure another Transmission.app isn't running already
106    NSString * bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
107    int processIdentifier = [[NSProcessInfo processInfo] processIdentifier];
108
109    NSDictionary * dic;
110    NSEnumerator * enumerator = [[[NSWorkspace sharedWorkspace] launchedApplications] objectEnumerator];
111    while ((dic = [enumerator nextObject]))
112    {
113        if ([[dic objectForKey: @"NSApplicationBundleIdentifier"] isEqualToString: bundleIdentifier]
114            && [[dic objectForKey: @"NSApplicationProcessIdentifier"] intValue] != processIdentifier)
115        {
116            NSAlert * alert = [[NSAlert alloc] init];
117            [alert addButtonWithTitle: NSLocalizedString(@"Quit", "Transmission already running alert -> button")];
118            [alert setMessageText: NSLocalizedString(@"Transmission is already running.",
119                                                    "Transmission already running alert -> title")];
120            [alert setInformativeText: NSLocalizedString(@"There is already a copy of Transmission running. "
121                "This copy cannot be opened until that instance is quit.", "Transmission already running alert -> message")];
122            [alert setAlertStyle: NSWarningAlertStyle];
123           
124            [alert runModal];
125            [alert release];
126           
127            //kill ourselves right away
128            exit(0);
129        }
130    }
131   
132    [[NSUserDefaults standardUserDefaults] registerDefaults: [NSDictionary dictionaryWithContentsOfFile:
133        [[NSBundle mainBundle] pathForResource: @"Defaults" ofType: @"plist"]]];
134   
135    //set custom value transformers
136    ActionMenuSpeedToDisplayLimitTransformer * limitTransformer =
137                        [[[ActionMenuSpeedToDisplayLimitTransformer alloc] init] autorelease];
138    [NSValueTransformer setValueTransformer: limitTransformer forName: @"ActionMenuSpeedToDisplayLimitTransformer"];
139   
140    ActionMenuRatioToDisplayRatioTransformer * ratioTransformer =
141                        [[[ActionMenuRatioToDisplayRatioTransformer alloc] init] autorelease];
142    [NSValueTransformer setValueTransformer: ratioTransformer forName: @"ActionMenuRatioToDisplayRatioTransformer"];
143   
144    ExpandedPathToPathTransformer * pathTransformer =
145                        [[[ExpandedPathToPathTransformer alloc] init] autorelease];
146    [NSValueTransformer setValueTransformer: pathTransformer forName: @"ExpandedPathToPathTransformer"];
147   
148    ExpandedPathToIconTransformer * iconTransformer =
149                        [[[ExpandedPathToIconTransformer alloc] init] autorelease];
150    [NSValueTransformer setValueTransformer: iconTransformer forName: @"ExpandedPathToIconTransformer"];
151   
152    SpeedLimitToTurtleIconTransformer * speedLimitIconTransformer =
153                        [[[SpeedLimitToTurtleIconTransformer alloc] init] autorelease];
154    [NSValueTransformer setValueTransformer: speedLimitIconTransformer forName: @"SpeedLimitToTurtleIconTransformer"];
155}
156
157- (id) init
158{
159    if ((self = [super init]))
160    {
161        fLib = tr_init("macosx");
162       
163        fTorrents = [[NSMutableArray alloc] init];
164        fDisplayedTorrents = [[NSMutableArray alloc] init];
165       
166        fDefaults = [NSUserDefaults standardUserDefaults];
167       
168        fMessageController = [[MessageWindowController alloc] initWithWindowNibName: @"MessageWindow"];
169        fInfoController = [[InfoWindowController alloc] initWithWindowNibName: @"InfoWindow"];
170        fPrefsController = [[PrefsController alloc] initWithWindowNibName: @"PrefsWindow" handle: fLib];
171       
172        fBadger = [[Badger alloc] initWithLib: fLib];
173       
174        fIPCController = [[IPCController alloc] init];
175        [fIPCController setDelegate: self];
176        fRemoteQuit = NO;
177       
178        [GrowlApplicationBridge setGrowlDelegate: self];
179        [[UKKQueue sharedFileWatcher] setDelegate: self];
180    }
181    return self;
182}
183
184- (void) dealloc
185{
186    [[NSNotificationCenter defaultCenter] removeObserver: self];
187   
188    [fInfoController release];
189    [fMessageController release];
190    [fPrefsController release];
191   
192    [fTorrents release];
193    [fDisplayedTorrents release];
194   
195    [fBadger release];
196    [fOverlayWindow release];
197    [fIPCController release];
198   
199    [fAutoImportedNames release];
200    [fPendingTorrentDownloads release];
201    [fTempTorrentFiles release];
202   
203    tr_close(fLib);
204    [super dealloc];
205}
206
207- (void) awakeFromNib
208{
209    [fBottomBar setBackgroundImage: [NSImage imageNamed: @"BottomBorder.png"]];
210    [fStatusBar setBackgroundImage: [NSImage imageNamed: @"StatusBarBackground.png"]];
211    [fFilterBar setBackgroundImage: [NSImage imageNamed: @"FilterBarBackground.png"]];
212   
213    NSToolbar * toolbar = [[NSToolbar alloc] initWithIdentifier: @"Transmission Toolbar"];
214    [toolbar setDelegate: self];
215    [toolbar setAllowsUserCustomization: YES];
216    [toolbar setAutosavesConfiguration: YES];
217    [fWindow setToolbar: toolbar];
218    [toolbar release];
219   
220    [fWindow setDelegate: self]; //do manually to avoid placement issue
221   
222    [fWindow makeFirstResponder: fTableView];
223    [fWindow setExcludedFromWindowsMenu: YES];
224    [fWindow setAcceptsMouseMovedEvents: YES]; //ensure filter buttons display correctly
225   
226    //set table size
227    if ([fDefaults boolForKey: @"SmallView"])
228        [fTableView setRowHeight: ROW_HEIGHT_SMALL];
229   
230    //window min height
231    NSSize contentMinSize = [fWindow contentMinSize];
232    contentMinSize.height = [[fWindow contentView] frame].size.height - [fScrollView frame].size.height
233                                + [fTableView rowHeight] + [fTableView intercellSpacing].height;
234    [fWindow setContentMinSize: contentMinSize];
235   
236    //set info and filter keyboard shortcuts
237    unichar rightChar = NSRightArrowFunctionKey, leftChar = NSLeftArrowFunctionKey;
238    [fNextInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & rightChar length: 1]];
239    [fPrevInfoTabItem setKeyEquivalent: [NSString stringWithCharacters: & leftChar length: 1]];
240   
241    [fNextFilterItem setKeyEquivalent: [NSString stringWithCharacters: & rightChar length: 1]];
242    [fNextFilterItem setKeyEquivalentModifierMask: NSCommandKeyMask + NSAlternateKeyMask];
243    [fPrevFilterItem setKeyEquivalent: [NSString stringWithCharacters: & leftChar length: 1]];
244    [fPrevFilterItem setKeyEquivalentModifierMask: NSCommandKeyMask + NSAlternateKeyMask];
245   
246    //set up filter bar
247    NSView * contentView = [fWindow contentView];
248    NSSize windowSize = [contentView convertSize: [fWindow frame].size fromView: nil];
249    [fFilterBar setHidden: YES];
250   
251    NSRect filterBarFrame = [fFilterBar frame];
252    filterBarFrame.size.width = windowSize.width;
253    [fFilterBar setFrame: filterBarFrame];
254   
255    [contentView addSubview: fFilterBar];
256    [fFilterBar setFrameOrigin: NSMakePoint(0, NSMaxY([contentView frame]))];
257
258    [self showFilterBar: [fDefaults boolForKey: @"FilterBar"] animate: NO];
259   
260    //set up status bar
261    [fStatusBar setHidden: YES];
262   
263    NSRect statusBarFrame = [fStatusBar frame];
264    statusBarFrame.size.width = windowSize.width;
265    [fStatusBar setFrame: statusBarFrame];
266
267    [contentView addSubview: fStatusBar];
268    [fStatusBar setFrameOrigin: NSMakePoint(0, NSMaxY([contentView frame]))];
269    [self showStatusBar: [fDefaults boolForKey: @"StatusBar"] animate: NO];
270
271    [fActionButton setToolTip: NSLocalizedString(@"Shortcuts for changing global settings.",
272                                "Main window -> 1st bottom left button (action) tooltip")];
273    [fSpeedLimitButton setToolTip: NSLocalizedString(@"Speed Limit overrides the total bandwidth limits with its own limits.",
274                                "Main window -> 2nd bottom left button (turtle) tooltip")];
275   
276    [fPrefsController setUpdater: fUpdater];
277   
278    [fTableView setTorrents: fDisplayedTorrents];
279    [[fTableView tableColumnWithIdentifier: @"Torrent"] setDataCell: [[TorrentCell alloc] init]];
280
281    [fTableView registerForDraggedTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE]];
282    [fWindow registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType, NSURLPboardType, nil]];
283
284    //register for sleep notifications
285    IONotificationPortRef notify;
286    io_object_t iterator;
287    if (fRootPort = IORegisterForSystemPower(self, & notify, sleepCallBack, & iterator))
288        CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notify), kCFRunLoopCommonModes);
289    else
290        NSLog(@"Could not IORegisterForSystemPower");
291
292    //load previous transfers
293    NSArray * history = [[NSArray alloc] initWithContentsOfFile: [[self applicationSupportFolder]
294                                                stringByAppendingPathComponent: @"Transfers.plist"]];
295   
296    //old version saved transfer info in prefs file
297    if (!history)
298    {
299        if ((history = [fDefaults arrayForKey: @"History"]))
300            [history retain];
301        [fDefaults removeObjectForKey: @"History"];
302    }
303   
304    if (history)
305    {
306        Torrent * torrent;
307        NSDictionary * historyItem;
308        NSEnumerator * enumerator = [history objectEnumerator];
309        while ((historyItem = [enumerator nextObject]))
310            if ((torrent = [[Torrent alloc] initWithHistory: historyItem lib: fLib]))
311            {
312                [fTorrents addObject: torrent];
313                [torrent release];
314            }
315       
316        [history release];
317    }
318   
319    //set sort
320    NSString * sortType = [fDefaults stringForKey: @"Sort"];
321   
322    NSMenuItem * currentSortItem, * currentSortActionItem;
323    if ([sortType isEqualToString: @"Name"])
324    {
325        currentSortItem = fNameSortItem;
326        currentSortActionItem = fNameSortActionItem;
327    }
328    else if ([sortType isEqualToString: @"State"])
329    {
330        currentSortItem = fStateSortItem;
331        currentSortActionItem = fStateSortActionItem;
332    }
333    else if ([sortType isEqualToString: @"Progress"])
334    {
335        currentSortItem = fProgressSortItem;
336        currentSortActionItem = fProgressSortActionItem;
337    }
338    else if ([sortType isEqualToString: @"Order"])
339    {
340        currentSortItem = fOrderSortItem;
341        currentSortActionItem = fOrderSortActionItem;
342    }
343    else
344    {
345        //safety
346        if (![sortType isEqualToString: @"Date"])
347            [fDefaults setObject: @"Date" forKey: @"Sort"];
348       
349        currentSortItem = fDateSortItem;
350        currentSortActionItem = fDateSortActionItem;
351    }
352    [currentSortItem setState: NSOnState];
353    [currentSortActionItem setState: NSOnState];
354   
355    //set filter
356    NSString * filterType = [fDefaults stringForKey: @"Filter"];
357   
358    FilterBarButton * currentFilterButton;
359    if ([filterType isEqualToString: @"Pause"])
360        currentFilterButton = fPauseFilterButton;
361    else if ([filterType isEqualToString: @"Seed"])
362        currentFilterButton = fSeedFilterButton;
363    else if ([filterType isEqualToString: @"Download"])
364        currentFilterButton = fDownloadFilterButton;
365    else
366    {
367        //safety
368        if (![filterType isEqualToString: @"None"])
369            [fDefaults setObject: @"None" forKey: @"Filter"];
370       
371        currentFilterButton = fNoFilterButton;
372    }
373   
374    [currentFilterButton setState: NSOnState];
375   
376    //observe notifications
377    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
378   
379    [nc addObserver: self selector: @selector(updateUI)
380                    name: @"UpdateUI" object: nil];
381   
382    [nc addObserver: fInfoController selector: @selector(updateInfoSettings)
383                    name: @"UpdateInfoSettings" object: nil];
384   
385    [nc addObserver: self selector: @selector(torrentFinishedDownloading:)
386                    name: @"TorrentFinishedDownloading" object: nil];
387   
388    [nc addObserver: self selector: @selector(torrentRestartedDownloading:)
389                    name: @"TorrentRestartedDownloading" object: nil];
390   
391    //avoids need of setting delegate
392    [nc addObserver: self selector: @selector(torrentTableViewSelectionDidChange:)
393                    name: NSTableViewSelectionDidChangeNotification object: fTableView];
394   
395    [nc addObserver: self selector: @selector(updateControlTint:)
396                    name: NSControlTintDidChangeNotification object: nil];
397   
398    [nc addObserver: self selector: @selector(prepareForUpdate:)
399                    name: SUUpdaterWillRestartNotification object: nil];
400    fUpdateInProgress = NO;
401   
402    [nc addObserver: self selector: @selector(autoSpeedLimitChange:)
403                    name: @"AutoSpeedLimitChange" object: nil];
404   
405    [nc addObserver: self selector: @selector(changeAutoImport)
406                    name: @"AutoImportSettingChange" object: nil];
407   
408    [nc addObserver: self selector: @selector(setWindowSizeToFit)
409                    name: @"AutoSizeSettingChange" object: nil];
410   
411    [nc addObserver: fWindow selector: @selector(makeKeyWindow)
412                    name: @"MakeWindowKey" object: nil];
413   
414    //check if torrent should now start
415    [nc addObserver: self selector: @selector(torrentStoppedForRatio:)
416                    name: @"TorrentStoppedForRatio" object: nil];
417   
418    [nc addObserver: self selector: @selector(updateTorrentsInQueue)
419                    name: @"UpdateQueue" object: nil];
420   
421    //change that just impacts the dock badge
422    [nc addObserver: self selector: @selector(updateDockBadge:)
423                    name: @"DockBadgeChange" object: nil];
424   
425    //open newly created torrent file
426    [nc addObserver: self selector: @selector(beginCreateFile:)
427                    name: @"BeginCreateTorrentFile" object: nil];
428   
429    //open newly created torrent file
430    [nc addObserver: self selector: @selector(openCreatedFile:)
431                    name: @"OpenCreatedTorrentFile" object: nil];
432
433    //timer to update the interface every second
434    [self updateUI];
435    fTimer = [NSTimer scheduledTimerWithTimeInterval: UPDATE_UI_SECONDS target: self
436        selector: @selector(updateUI) userInfo: nil repeats: YES];
437    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSModalPanelRunLoopMode];
438    [[NSRunLoop currentRunLoop] addTimer: fTimer forMode: NSEventTrackingRunLoopMode];
439   
440    [self applyFilter: nil];
441   
442    [fWindow makeKeyAndOrderFront: nil];
443   
444    if ([fDefaults boolForKey: @"InfoVisible"])
445        [self showInfo: nil];
446   
447    //timer to auto toggle speed limit
448    [self autoSpeedLimitChange: nil];
449    fSpeedLimitTimer = [NSTimer scheduledTimerWithTimeInterval: AUTO_SPEED_LIMIT_SECONDS target: self
450        selector: @selector(autoSpeedLimit) userInfo: nil repeats: YES];
451}
452
453- (void) applicationDidFinishLaunching: (NSNotification *) notification
454{
455    [NSApp setServicesProvider: self];
456   
457    //register for dock icon drags
458    [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self
459        andSelector: @selector(handleOpenContentsEvent:replyEvent:)
460        forEventClass: kCoreEventClass andEventID: kAEOpenContents];
461   
462    //auto importing
463    [self checkAutoImportDirectory];
464   
465    //debug warning
466    if ([fDefaults boolForKey: @"WarningDebug"] && [fDefaults integerForKey: @"MessageLevel"] == TR_MSG_DBG)
467    {
468        NSAlert * alert = [[NSAlert alloc] init];
469        [alert setMessageText: NSLocalizedString(@"The Message Log is set to \"Debug\"", "Debug log alert -> message")];
470        [alert setInformativeText: NSLocalizedString(@"Continuous use at this level might increase memory usage."
471                                    " This setting can be changed in the Message Log window (accessible from the Window menu).",
472                                    "Debug log alert -> informative message")];
473        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Debug log alert -> button")];
474        [alert addButtonWithTitle: NSLocalizedString(@"Don't Alert Again", "Debug log alert -> button")];
475       
476        if ([alert runModal] == NSAlertSecondButtonReturn)
477            [fDefaults setBool: NO forKey: @"WarningDebug"];
478        [alert release];
479    }
480}
481
482- (BOOL) applicationShouldHandleReopen: (NSApplication *) app hasVisibleWindows: (BOOL) visibleWindows
483{
484    if (![fWindow isVisible] && ![[fPrefsController window] isVisible])
485        [fWindow makeKeyAndOrderFront: nil];
486    return NO;
487}
488
489- (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication *) sender
490{
491    if (!fUpdateInProgress && !fRemoteQuit && [fDefaults boolForKey: @"CheckQuit"])
492    {
493        int active = 0, downloading = 0;
494        Torrent * torrent;
495        NSEnumerator * enumerator = [fTorrents objectEnumerator];
496        while ((torrent = [enumerator nextObject]))
497            if ([torrent isActive])
498            {
499                active++;
500                if (![torrent isSeeding])
501                    downloading++;
502            }
503
504        if ([fDefaults boolForKey: @"CheckQuitDownloading"] ? downloading > 0 : active > 0)
505        {
506            NSString * message = active == 1
507                ? NSLocalizedString(@"There is an active transfer. Do you really want to quit?",
508                    "Confirm Quit panel -> message")
509                : [NSString stringWithFormat: NSLocalizedString(@"There are %d active transfers. Do you really want to quit?",
510                    "Confirm Quit panel -> message"), active];
511
512            NSBeginAlertSheet(NSLocalizedString(@"Confirm Quit", "Confirm Quit panel -> title"),
513                                NSLocalizedString(@"Quit", "Confirm Quit panel -> button"),
514                                NSLocalizedString(@"Cancel", "Confirm Quit panel -> button"), nil, fWindow, self,
515                                @selector(quitSheetDidEnd:returnCode:contextInfo:), nil, nil, message);
516            return NSTerminateLater;
517        }
518    }
519
520    return NSTerminateNow;
521}
522
523- (void) quitSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo
524{
525    [NSApp replyToApplicationShouldTerminate: returnCode == NSAlertDefaultReturn];
526}
527
528- (void) applicationWillTerminate: (NSNotification *) notification
529{
530    //remove all torrent downloads
531    if (fPendingTorrentDownloads)
532    {
533        NSEnumerator * downloadEnumerator = [[fPendingTorrentDownloads allValues] objectEnumerator];
534        NSDictionary * downloadDict;
535        NSURLDownload * download;
536        while ((downloadDict = [downloadEnumerator nextObject]))
537        {
538            download = [downloadDict objectForKey: @"Download"];
539            [download cancel];
540            [download release];
541        }
542        [fPendingTorrentDownloads removeAllObjects];
543    }
544   
545    //remove all torrent files in the temporary directory
546    if (fTempTorrentFiles)
547    {
548        NSEnumerator * torrentEnumerator = [fTempTorrentFiles objectEnumerator];
549        NSString * path;
550        while ((path = [torrentEnumerator nextObject]))
551            [[NSFileManager defaultManager] removeFileAtPath: path handler: nil];
552        [fTempTorrentFiles removeAllObjects];
553    }
554   
555    //stop timers
556    [fTimer invalidate];
557    [fSpeedLimitTimer invalidate];
558    if (fAutoImportTimer)
559    {   
560        if ([fAutoImportTimer isValid])
561            [fAutoImportTimer invalidate];
562        [fAutoImportTimer release];
563    }
564   
565    //remember window states and close all windows
566    [fDefaults setBool: [[fInfoController window] isVisible] forKey: @"InfoVisible"];
567    [[NSApp windows] makeObjectsPerformSelector: @selector(close)];
568    [self showStatusBar: NO animate: NO];
569    [self showFilterBar: NO animate: NO];
570   
571    //save history
572    [self updateTorrentHistory];
573   
574    //make sure torrents are closed
575    NSEnumerator * enumerator = [fTorrents objectEnumerator];
576    Torrent * torrent;
577    while ((torrent = [enumerator nextObject]))
578        [torrent closeTorrent];
579   
580    [fDisplayedTorrents removeAllObjects];
581    [fTorrents removeAllObjects];
582   
583    //disable NAT traversal
584    tr_natTraversalEnable(fLib, 0);
585   
586    //clear badge
587    [fBadger clearBadge];
588
589    //wait for running transfers to stop and for NAT to be disabled (5 second timeout)
590    NSDate * start = [NSDate date];
591    while ([start timeIntervalSinceNow] >= -5.0
592            && (tr_torrentCount(fLib) > 0 || tr_handleStatus(fLib)->natTraversalStatus != TR_NAT_TRAVERSAL_DISABLED))
593        usleep(100000);
594}
595
596- (void) handleOpenContentsEvent: (NSAppleEventDescriptor *) event replyEvent: (NSAppleEventDescriptor *) replyEvent
597{
598    NSString * urlString = nil;
599
600    NSAppleEventDescriptor * directObject = [event paramDescriptorForKeyword: keyDirectObject];
601    if ([directObject descriptorType] == typeAEList)
602    {
603        unsigned i;
604        for (i = 1; i <= [directObject numberOfItems]; i++)
605            if ((urlString = [[directObject descriptorAtIndex: i] stringValue]))
606                break;
607    }
608    else
609        urlString = [directObject stringValue];
610   
611    if (urlString)
612        [self openURL: [[[NSURL alloc] initWithString: urlString] autorelease]];
613}
614
615- (void) download: (NSURLDownload *) download decideDestinationWithSuggestedFilename: (NSString *) suggestedName
616{
617    if ([[suggestedName pathExtension] caseInsensitiveCompare: @"torrent"] != NSOrderedSame)
618    {
619        [download cancel];
620       
621        NSRunAlertPanel(NSLocalizedString(@"Torrent download failed",
622            @"Download not a torrent -> title"), [NSString stringWithFormat:
623            NSLocalizedString(@"It appears that the file \"%@\" from %@ is not a torrent file.",
624            @"Download not a torrent -> message"), suggestedName,
625            [[[[download request] URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding]],
626            NSLocalizedString(@"OK", @"Download not a torrent -> button"), nil, nil);
627       
628        [download release];
629    }
630    else
631        [download setDestination: [NSTemporaryDirectory() stringByAppendingPathComponent: [suggestedName lastPathComponent]]
632                    allowOverwrite: NO];
633}
634
635-(void) download: (NSURLDownload *) download didCreateDestination: (NSString *) path
636{
637    if (!fPendingTorrentDownloads)
638        fPendingTorrentDownloads = [[NSMutableDictionary alloc] init];
639   
640    [fPendingTorrentDownloads setObject: [NSDictionary dictionaryWithObjectsAndKeys:
641                    path, @"Path", download, @"Download", nil] forKey: [[download request] URL]];
642}
643
644- (void) download: (NSURLDownload *) download didFailWithError: (NSError *) error
645{
646    NSRunAlertPanel(NSLocalizedString(@"Torrent download failed",
647        @"Torrent download error -> title"), [NSString stringWithFormat:
648        NSLocalizedString(@"The torrent could not be downloaded from %@ because an error occurred (%@).",
649        @"Torrent download failed -> message"),
650        [[[[download request] URL] absoluteString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding],
651        [error localizedDescription]], NSLocalizedString(@"OK", @"Torrent download failed -> button"), nil, nil);
652   
653    [fPendingTorrentDownloads removeObjectForKey: [[download request] URL]];
654    [download release];
655}
656
657- (void) downloadDidFinish: (NSURLDownload *) download
658{
659    NSString * path = [[fPendingTorrentDownloads objectForKey: [[download request] URL]] objectForKey: @"Path"];
660   
661    [self openFiles: [NSArray arrayWithObject: path] forcePath: nil ignoreDownloadFolder:
662        ![[fDefaults stringForKey: @"DownloadChoice"] isEqualToString: @"Constant"] forceDeleteTorrent: YES];
663   
664    [fPendingTorrentDownloads removeObjectForKey: [[download request] URL]];
665    [download release];
666   
667    //delete temp torrent file on quit
668    if (!fTempTorrentFiles)
669        fTempTorrentFiles = [[NSMutableArray alloc] init];
670    [fTempTorrentFiles addObject: path];
671}
672
673- (void) application: (NSApplication *) app openFiles: (NSArray *) filenames
674{
675    [self openFiles: filenames forcePath: nil ignoreDownloadFolder: NO forceDeleteTorrent: NO];
676}
677
678- (void) openFiles: (NSArray *) filenames forcePath: (NSString *) path ignoreDownloadFolder: (BOOL) ignore
679        forceDeleteTorrent: (BOOL) delete
680{
681    NSString * downloadChoice = [fDefaults stringForKey: @"DownloadChoice"];
682    if (ignore || (!path && [downloadChoice isEqualToString: @"Ask"]))
683    {
684        [self openFilesAsk: [filenames mutableCopy] forceDeleteTorrent: delete];
685        return;
686    }
687   
688    if (!path && [fDefaults boolForKey: @"UseIncompleteDownloadFolder"]
689        && access([[[fDefaults stringForKey: @"IncompleteDownloadFolder"] stringByExpandingTildeInPath] UTF8String], 0))
690    {
691        NSOpenPanel * panel = [NSOpenPanel openPanel];
692       
693        [panel setPrompt: NSLocalizedString(@"Select", "Default incomplete folder cannot be found alert -> prompt")];
694        [panel setAllowsMultipleSelection: NO];
695        [panel setCanChooseFiles: NO];
696        [panel setCanChooseDirectories: YES];
697        [panel setCanCreateDirectories: YES];
698
699        [panel setMessage: NSLocalizedString(@"The incomplete folder cannot be found."
700                                        " Choose a new location or cancel for none.",
701                                        "Default incomplete folder cannot be found alert -> message")];
702       
703        NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
704                                        filenames, @"Filenames",
705                                        [NSNumber numberWithBool: ignore], @"Ignore",
706                                        [NSNumber numberWithBool: delete], @"Delete", nil];
707       
708        [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: fWindow modalDelegate: self
709                didEndSelector: @selector(incompleteChoiceClosed:returnCode:contextInfo:) contextInfo: dict];
710        return;
711    }
712    if (!path && [downloadChoice isEqualToString: @"Constant"]
713        && access([[[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath] UTF8String], 0))
714    {
715        NSOpenPanel * panel = [NSOpenPanel openPanel];
716       
717        [panel setPrompt: NSLocalizedString(@"Select", "Default folder cannot be found alert -> prompt")];
718        [panel setAllowsMultipleSelection: NO];
719        [panel setCanChooseFiles: NO];
720        [panel setCanChooseDirectories: YES];
721        [panel setCanCreateDirectories: YES];
722
723        [panel setMessage: NSLocalizedString(@"The download folder cannot be found. Choose a new location.",
724                                        "Default folder cannot be found alert -> message")];
725       
726        NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
727                                        filenames, @"Filenames",
728                                        [NSNumber numberWithBool: ignore], @"Ignore",
729                                        [NSNumber numberWithBool: delete], @"Delete", nil];
730       
731        [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: fWindow modalDelegate: self
732                didEndSelector: @selector(downloadChoiceClosed:returnCode:contextInfo:) contextInfo: dict];
733        return;
734    }
735   
736    Torrent * torrent;
737    NSString * torrentPath;
738    tr_info_t info;
739    NSEnumerator * enumerator = [filenames objectEnumerator];
740    while ((torrentPath = [enumerator nextObject]))
741    {
742        NSString * location;
743        if (path)
744            location = path;
745        else if ([downloadChoice isEqualToString: @"Constant"])
746            location = [[fDefaults stringForKey: @"DownloadFolder"] stringByExpandingTildeInPath];
747        else
748            location = [torrentPath stringByDeletingLastPathComponent];
749       
750        if (tr_torrentParse(fLib, [torrentPath UTF8String], NULL, &info) == TR_EDUPLICATE)
751        {
752            [self duplicateOpenAlert: [NSString stringWithUTF8String: info.name]];
753            tr_metainfoFree(&info);
754            continue;
755        }
756        tr_metainfoFree(&info);
757       
758        if (!(torrent = [[Torrent alloc] initWithPath: torrentPath location: location forceDeleteTorrent: delete lib: fLib]))
759            continue;
760       
761        //add it to the "File > Open Recent" menu
762        [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
763       
764        [torrent update];
765        [fTorrents addObject: torrent];
766        [torrent release];
767    }
768
769    [self updateTorrentsInQueue];
770}
771
772- (void) openCreatedFile: (NSNotification *) notification
773{
774    NSDictionary * dict = [notification userInfo];
775    [self openFiles: [NSArray arrayWithObject: [dict objectForKey: @"File"]] forcePath: [dict objectForKey: @"Path"]
776            ignoreDownloadFolder: NO forceDeleteTorrent: NO];
777    [dict release];
778}
779
780- (void) incompleteChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (NSDictionary *) dictionary
781{
782    if (code == NSOKButton)
783        [fDefaults setObject: [[openPanel filenames] objectAtIndex: 0] forKey: @"IncompleteDownloadFolder"];
784    else
785        [fDefaults setBool: NO forKey: @"UseIncompleteDownloadFolder"];
786   
787    [self performSelectorOnMainThread: @selector(openFilesWithDict:) withObject: dictionary waitUntilDone: NO];
788}
789
790- (void) downloadChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (NSDictionary *) dictionary
791{
792    if (code == NSOKButton)
793    {
794        [fDefaults setObject: [[openPanel filenames] objectAtIndex: 0] forKey: @"DownloadFolder"];
795        [self performSelectorOnMainThread: @selector(openFilesWithDict:) withObject: dictionary waitUntilDone: NO];
796    }
797    else
798        [dictionary release];
799}
800
801- (void) openFilesWithDict: (NSDictionary *) dictionary
802{
803    [self openFiles: [dictionary objectForKey: @"Filenames"] forcePath: nil
804        ignoreDownloadFolder: [[dictionary objectForKey: @"Ignore"] boolValue]
805        forceDeleteTorrent: [[dictionary objectForKey: @"Delete"] boolValue]];
806   
807    [dictionary release];
808}
809
810//called by the main open method to show sheet for choosing download location
811- (void) openFilesAsk: (NSMutableArray *) files forceDeleteTorrent: (BOOL) delete
812{
813    //determine the next file that can be opened
814    NSString * torrentPath;
815    int canAdd;
816    tr_info_t info;
817    while ([files count] > 0)
818    {
819        torrentPath = [files objectAtIndex: 0];
820        canAdd = tr_torrentParse(fLib, [torrentPath UTF8String], NULL, &info);
821        if (canAdd == TR_OK)
822            break;
823        else if (canAdd == TR_EDUPLICATE)
824            [self duplicateOpenAlert: [NSString stringWithUTF8String: info.name]];
825        else;
826       
827        tr_metainfoFree(&info);
828        [files removeObjectAtIndex: 0];
829    }
830   
831    //no files left to open
832    if ([files count] <= 0)
833    {
834        [files release];
835        [self updateTorrentHistory];
836        return;
837    }
838
839    [files removeObjectAtIndex: 0];
840    NSOpenPanel * panel = [NSOpenPanel openPanel];
841
842    [panel setPrompt: NSLocalizedString(@"Select", "Open torrent -> prompt")];
843    [panel setAllowsMultipleSelection: NO];
844    [panel setCanChooseFiles: NO];
845    [panel setCanChooseDirectories: YES];
846    [panel setCanCreateDirectories: YES];
847   
848    [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the download folder for \"%@\"",
849                        "Open torrent -> select destination folder"), [NSString stringWithUTF8String: info.name]]];
850   
851    NSDictionary * dictionary = [[NSDictionary alloc] initWithObjectsAndKeys: [torrentPath retain], @"Path",
852                                    files, @"Files", [NSNumber numberWithBool: delete], @"Delete", nil];
853    tr_metainfoFree(&info);
854   
855    [panel beginSheetForDirectory: nil file: nil types: nil modalForWindow: fWindow modalDelegate: self
856            didEndSelector: @selector(folderChoiceClosed:returnCode:contextInfo:) contextInfo: dictionary];
857}
858
859- (void) folderChoiceClosed: (NSOpenPanel *) openPanel returnCode: (int) code contextInfo: (NSDictionary *) dictionary
860{
861    if (code == NSOKButton)
862    {
863        NSString * torrentPath = [dictionary objectForKey: @"Path"];
864        Torrent * torrent = [[Torrent alloc] initWithPath: torrentPath location: [[openPanel filenames] objectAtIndex: 0]
865                            forceDeleteTorrent: [[dictionary objectForKey: @"Delete"] boolValue] lib: fLib];
866       
867        //add it to the "File > Open Recent" menu
868        [[NSDocumentController sharedDocumentController] noteNewRecentDocumentURL: [NSURL fileURLWithPath: torrentPath]];
869       
870        [torrent update];
871        [fTorrents addObject: torrent];
872        [torrent release];
873       
874        [self updateTorrentsInQueue];
875    }
876   
877    [self performSelectorOnMainThread: @selector(openFilesAskWithDict:) withObject: dictionary waitUntilDone: NO];
878}
879
880- (void) openFilesAskWithDict: (NSDictionary *) dictionary
881{
882    [self openFilesAsk: [dictionary objectForKey: @"Files"]
883            forceDeleteTorrent: [[dictionary objectForKey: @"Delete"] boolValue]];
884    [dictionary release];
885}
886
887//called on by applescript
888- (void) open: (NSArray *) files
889{
890    [self performSelectorOnMainThread: @selector(openFiles:) withObject: files waitUntilDone: NO];
891}
892
893- (void) openFiles: (NSArray *) filenames
894{
895    [self openFiles: filenames forcePath: nil ignoreDownloadFolder: NO forceDeleteTorrent: NO];
896}
897
898- (void) openShowSheet: (id) sender
899{
900    NSOpenPanel * panel = [NSOpenPanel openPanel];
901
902    [panel setAllowsMultipleSelection: YES];
903    [panel setCanChooseFiles: YES];
904    [panel setCanChooseDirectories: NO];
905
906    [panel beginSheetForDirectory: nil file: nil types: [NSArray arrayWithObject: @"torrent"]
907        modalForWindow: fWindow modalDelegate: self didEndSelector: @selector(openSheetClosed:returnCode:contextInfo:)
908        contextInfo: [NSNumber numberWithBool: sender == fOpenIgnoreDownloadFolder]];
909}
910
911- (void) openSheetClosed: (NSOpenPanel *) panel returnCode: (int) code contextInfo: (NSNumber *) ignore
912{
913    if (code == NSOKButton)
914    {
915        NSDictionary * dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:
916                                        [panel filenames], @"Files", ignore, @"Ignore", nil];
917        [self performSelectorOnMainThread: @selector(openFromSheet:) withObject: dictionary waitUntilDone: NO];
918    }
919}
920
921- (void) openFromSheet: (NSDictionary *) dictionary
922{
923    [self openFiles: [dictionary objectForKey: @"Files"] forcePath: nil
924        ignoreDownloadFolder: [[dictionary objectForKey: @"Ignore"] boolValue] forceDeleteTorrent: NO];
925   
926    [dictionary release];
927}
928
929- (void) duplicateOpenAlert: (NSString *) name
930{
931    if (![fDefaults boolForKey: @"WarningDuplicate"])
932        return;
933   
934    NSAlert * alert = [[NSAlert alloc] init];
935    [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"A transfer of \"%@\" is already running.",
936                            "Open duplicate alert -> title"), name]];
937    [alert setInformativeText:
938            NSLocalizedString(@"The torrent file cannot be opened because it is a duplicate of an already running transfer.",
939                            "Open duplicate alert -> message")];
940    [alert addButtonWithTitle: NSLocalizedString(@"OK", "Open duplicate alert -> button")];
941    [alert addButtonWithTitle: NSLocalizedString(@"Don't Alert Again", "Open duplicate alert -> button")];
942    [alert setAlertStyle: NSWarningAlertStyle];
943   
944    if ([alert runModal] == NSAlertSecondButtonReturn)
945        [fDefaults setBool: NO forKey: @"WarningDuplicate"];
946    [alert release];
947}
948
949- (void) openURL: (NSURL *) url
950{
951    [[NSURLDownload alloc] initWithRequest: [NSURLRequest requestWithURL: url] delegate: self];
952}
953
954- (void) openURLShowSheet: (id) sender
955{
956    [NSApp beginSheet: fURLSheetWindow modalForWindow: fWindow modalDelegate: self
957            didEndSelector: @selector(urlSheetDidEnd:returnCode:contextInfo:) contextInfo: nil];
958}
959
960#warning combine
961- (void) openURLEndSheet: (id) sender
962{
963    [fURLSheetWindow orderOut: sender];
964    [NSApp endSheet: fURLSheetWindow returnCode: 1];
965}
966
967- (void) openURLCancelEndSheet: (id) sender
968{
969    [fURLSheetWindow orderOut: sender];
970    [NSApp endSheet: fURLSheetWindow returnCode: 0];
971}
972
973- (void) urlSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (void *) contextInfo
974{
975    [fURLSheetTextField selectText: self];
976    if (returnCode != 1)
977        return;
978   
979    NSString * urlString = [fURLSheetTextField stringValue];
980    if (![urlString isEqualToString: @""])
981    {
982        if ([urlString rangeOfString: @"://"].location == NSNotFound)
983        {
984            if ([urlString rangeOfString: @"."].location == NSNotFound)
985            {
986                int beforeCom;
987                if ((beforeCom = [urlString rangeOfString: @"/"].location) != NSNotFound)
988                    urlString = [NSString stringWithFormat: @"http://www.%@.com/%@",
989                                    [urlString substringToIndex: beforeCom],
990                                    [urlString substringFromIndex: beforeCom + 1]];
991                else
992                    urlString = [NSString stringWithFormat: @"http://www.%@.com", urlString];
993            }
994            else
995                urlString = [@"http://" stringByAppendingString: urlString];
996        }
997       
998        NSURL * url = [NSURL URLWithString: urlString];
999        [self performSelectorOnMainThread: @selector(openURL:) withObject: url waitUntilDone: NO];
1000    }
1001}
1002
1003- (void) createFile: (id) sender
1004{
1005    [CreatorWindowController createTorrentFile: fLib];
1006}
1007
1008- (void) resumeSelectedTorrents: (id) sender
1009{
1010    [self resumeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
1011}
1012
1013- (void) resumeAllTorrents: (id) sender
1014{
1015    [self resumeTorrents: fTorrents];
1016}
1017
1018- (void) resumeTorrents: (NSArray *) torrents
1019{
1020    NSEnumerator * enumerator = [torrents objectEnumerator];
1021    Torrent * torrent;
1022    while ((torrent = [enumerator nextObject]))
1023        [torrent setWaitToStart: YES];
1024   
1025    [self updateTorrentsInQueue];
1026}
1027
1028- (void) resumeSelectedTorrentsNoWait:  (id) sender
1029{
1030    [self resumeTorrentsNoWait: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
1031}
1032
1033- (void) resumeWaitingTorrents: (id) sender
1034{
1035    NSMutableArray * torrents = [NSMutableArray arrayWithCapacity: [fTorrents count]];
1036   
1037    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1038    Torrent * torrent;
1039    while ((torrent = [enumerator nextObject]))
1040        if (![torrent isActive] && [torrent waitingToStart])
1041            [torrents addObject: torrent];
1042   
1043    [self resumeTorrentsNoWait: torrents];
1044}
1045
1046- (void) resumeTorrentsNoWait: (NSArray *) torrents
1047{
1048    //iterate through instead of all at once to ensure no conflicts
1049    NSEnumerator * enumerator = [torrents objectEnumerator];
1050    Torrent * torrent;
1051    while ((torrent = [enumerator nextObject]))
1052        [torrent startTransfer];
1053   
1054    [self updateUI];
1055    [self applyFilter: nil];
1056    [self updateTorrentHistory];
1057}
1058
1059- (void) stopSelectedTorrents: (id) sender
1060{
1061    [self stopTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
1062}
1063
1064- (void) stopAllTorrents: (id) sender
1065{
1066    [self stopTorrents: fTorrents];
1067}
1068
1069- (void) stopTorrents: (NSArray *) torrents
1070{
1071    //don't want any of these starting then stopping
1072    NSEnumerator * enumerator = [torrents objectEnumerator];
1073    Torrent * torrent;
1074    while ((torrent = [enumerator nextObject]))
1075        [torrent setWaitToStart: NO];
1076
1077    [torrents makeObjectsPerformSelector: @selector(stopTransfer)];
1078   
1079    [self updateUI];
1080    [self applyFilter: nil];
1081    [self updateTorrentHistory];
1082}
1083
1084- (void) removeTorrents: (NSArray *) torrents deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
1085{
1086    [torrents retain];
1087    int active = 0, downloading = 0;
1088
1089    if ([fDefaults boolForKey: @"CheckRemove"])
1090    {
1091        Torrent * torrent;
1092        NSEnumerator * enumerator = [torrents objectEnumerator];
1093        while ((torrent = [enumerator nextObject]))
1094            if ([torrent isActive])
1095            {
1096                active++;
1097                if (![torrent isSeeding])
1098                    downloading++;
1099            }
1100
1101        if ([fDefaults boolForKey: @"CheckRemoveDownloading"] ? downloading > 0 : active > 0)
1102        {
1103            NSDictionary * dict = [[NSDictionary alloc] initWithObjectsAndKeys:
1104                torrents, @"Torrents",
1105                [NSNumber numberWithBool: deleteData], @"DeleteData",
1106                [NSNumber numberWithBool: deleteTorrent], @"DeleteTorrent", nil];
1107
1108            NSString * title, * message;
1109           
1110            int selected = [fTableView numberOfSelectedRows];
1111            if (selected == 1)
1112            {
1113                title = [NSString stringWithFormat: NSLocalizedString(@"Confirm Removal of \"%@\"",
1114                            "Removal confirm panel -> title"),
1115                            [[fDisplayedTorrents objectAtIndex: [fTableView selectedRow]] name]];
1116                message = NSLocalizedString(@"This transfer is active."
1117                            " Once removed, continuing the transfer will require the torrent file."
1118                            " Do you really want to remove it?", "Removal confirm panel -> message");
1119            }
1120            else
1121            {
1122                title = [NSString stringWithFormat: NSLocalizedString(@"Confirm Removal of %d Transfers",
1123                            "Removal confirm panel -> title"), selected];
1124                if (selected == active)
1125                    message = [NSString stringWithFormat: NSLocalizedString(@"There are %d active transfers.",
1126                                "Removal confirm panel -> message part 1"), active];
1127                else
1128                    message = [NSString stringWithFormat: NSLocalizedString(@"There are %d transfers (%d active).",
1129                                "Removal confirm panel -> message part 1"), selected, active];
1130                message = [message stringByAppendingString:
1131                    NSLocalizedString(@" Once removed, continuing the transfers will require the torrent files."
1132                    " Do you really want to remove them?", "Removal confirm panel -> message part 2")];
1133            }
1134
1135            NSBeginAlertSheet(title, NSLocalizedString(@"Remove", "Removal confirm panel -> button"),
1136                NSLocalizedString(@"Cancel", "Removal confirm panel -> button"), nil, fWindow, self,
1137                nil, @selector(removeSheetDidEnd:returnCode:contextInfo:), dict, message);
1138            return;
1139        }
1140    }
1141   
1142    [self confirmRemoveTorrents: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
1143}
1144
1145- (void) removeSheetDidEnd: (NSWindow *) sheet returnCode: (int) returnCode contextInfo: (NSDictionary *) dict
1146{
1147    NSArray * torrents = [dict objectForKey: @"Torrents"];
1148    BOOL deleteData = [[dict objectForKey: @"DeleteData"] boolValue],
1149        deleteTorrent = [[dict objectForKey: @"DeleteTorrent"] boolValue];
1150    [dict release];
1151   
1152    if (returnCode == NSAlertDefaultReturn)
1153        [self confirmRemoveTorrents: torrents deleteData: deleteData deleteTorrent: deleteTorrent];
1154    else
1155        [torrents release];
1156}
1157
1158- (void) confirmRemoveTorrents: (NSArray *) torrents deleteData: (BOOL) deleteData deleteTorrent: (BOOL) deleteTorrent
1159{
1160    //don't want any of these starting then stopping
1161    NSEnumerator * enumerator = [torrents objectEnumerator];
1162    Torrent * torrent;
1163    while ((torrent = [enumerator nextObject]))
1164        [torrent setWaitToStart: NO];
1165
1166    NSNumber * lowestOrderValue = nil, * currentOrderValue;
1167
1168    enumerator = [torrents objectEnumerator];
1169    while ((torrent = [enumerator nextObject]))
1170    {
1171        [torrent stopTransfer];
1172
1173        if (deleteData)
1174            [torrent trashData];
1175        if (deleteTorrent)
1176            [torrent trashTorrent];
1177       
1178        //determine lowest order value
1179        currentOrderValue = [torrent orderValue];
1180        if (!lowestOrderValue || [lowestOrderValue compare: currentOrderValue] == NSOrderedDescending)
1181            lowestOrderValue = currentOrderValue;
1182       
1183        [torrent closeRemoveTorrent];
1184        [fTorrents removeObject: torrent];
1185        [fDisplayedTorrents removeObject: torrent];
1186    }
1187    [torrents release];
1188
1189    //reset the order values if necessary
1190    if ([lowestOrderValue intValue] < [fTorrents count])
1191    {
1192        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1193                                                @"orderValue" ascending: YES] autorelease];
1194        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1195
1196        NSArray * tempTorrents = [fTorrents sortedArrayUsingDescriptors: descriptors];
1197        [descriptors release];
1198
1199        int i;
1200        for (i = [lowestOrderValue intValue]; i < [tempTorrents count]; i++)
1201            [[tempTorrents objectAtIndex: i] setOrderValue: i];
1202    }
1203   
1204    [fTableView deselectAll: nil];
1205   
1206    [self updateUI];
1207    [self applyFilter: nil];
1208    [self updateTorrentHistory];
1209}
1210
1211- (void) removeNoDelete: (id) sender
1212{
1213    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1214                deleteData: NO deleteTorrent: NO];
1215}
1216
1217- (void) removeDeleteData: (id) sender
1218{
1219    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1220                deleteData: YES deleteTorrent: NO];
1221}
1222
1223- (void) removeDeleteTorrent: (id) sender
1224{
1225    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1226                deleteData: NO deleteTorrent: YES];
1227}
1228
1229- (void) removeDeleteDataAndTorrent: (id) sender
1230{
1231    [self removeTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]
1232                deleteData: YES deleteTorrent: YES];
1233}
1234
1235- (void) moveDataFiles: (id) sender
1236{
1237    NSOpenPanel * panel = [NSOpenPanel openPanel];
1238    [panel setPrompt: NSLocalizedString(@"Select", "Move torrent -> prompt")];
1239    [panel setAllowsMultipleSelection: NO];
1240    [panel setCanChooseFiles: NO];
1241    [panel setCanChooseDirectories: YES];
1242    [panel setCanCreateDirectories: YES];
1243   
1244    NSArray * torrents = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] retain];
1245    int count = [torrents count];
1246    if (count == 1)
1247        [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the new folder for \"%@\".",
1248                            "Move torrent -> select destination folder"), [[torrents objectAtIndex: 0] name]]];
1249    else
1250        [panel setMessage: [NSString stringWithFormat: NSLocalizedString(@"Select the new folder for %d data files.",
1251                            "Move torrent -> select destination folder"), count]];
1252       
1253    [panel beginSheetForDirectory: nil file: nil modalForWindow: fWindow modalDelegate: self
1254        didEndSelector: @selector(moveDataFileChoiceClosed:returnCode:contextInfo:) contextInfo: torrents];
1255}
1256
1257- (void) moveDataFileChoiceClosed: (NSOpenPanel *) panel returnCode: (int) code contextInfo: (NSArray *) torrents
1258{
1259    if (code == NSOKButton)
1260    {
1261        NSEnumerator * enumerator = [torrents objectEnumerator];
1262        Torrent * torrent;
1263        while ((torrent = [enumerator nextObject]))
1264            [torrent moveTorrentDataFileTo: [[panel filenames] objectAtIndex: 0]];
1265    }
1266   
1267    [torrents release];
1268}
1269
1270- (void) copyTorrentFiles: (id) sender
1271{
1272    [self copyTorrentFileForTorrents: [[NSMutableArray alloc] initWithArray:
1273            [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]]];
1274}
1275
1276- (void) copyTorrentFileForTorrents: (NSMutableArray *) torrents
1277{
1278    if ([torrents count] <= 0)
1279    {
1280        [torrents release];
1281        return;
1282    }
1283
1284    Torrent * torrent = [torrents objectAtIndex: 0];
1285
1286    //warn user if torrent file can't be found
1287    if (![[NSFileManager defaultManager] fileExistsAtPath: [torrent torrentLocation]])
1288    {
1289        NSAlert * alert = [[NSAlert alloc] init];
1290        [alert addButtonWithTitle: NSLocalizedString(@"OK", "Torrent file copy alert -> button")];
1291        [alert setMessageText: [NSString stringWithFormat: NSLocalizedString(@"Copy of \"%@\" Cannot Be Created",
1292                                "Torrent file copy alert -> title"), [torrent name]]];
1293        [alert setInformativeText: [NSString stringWithFormat:
1294                NSLocalizedString(@"The torrent file (%@) cannot be found.", "Torrent file copy alert -> message"),
1295                                    [torrent torrentLocation]]];
1296        [alert setAlertStyle: NSWarningAlertStyle];
1297       
1298        [alert runModal];
1299        [alert release];
1300       
1301        [torrents removeObjectAtIndex: 0];
1302        [self copyTorrentFileForTorrents: torrents];
1303    }
1304    else
1305    {
1306        NSSavePanel * panel = [NSSavePanel savePanel];
1307        [panel setRequiredFileType: @"torrent"];
1308        [panel setCanSelectHiddenExtension: YES];
1309       
1310        [panel beginSheetForDirectory: nil file: [torrent name] modalForWindow: fWindow modalDelegate: self
1311            didEndSelector: @selector(saveTorrentCopySheetClosed:returnCode:contextInfo:) contextInfo: torrents];
1312    }
1313}
1314
1315- (void) saveTorrentCopySheetClosed: (NSSavePanel *) panel returnCode: (int) code contextInfo: (NSMutableArray *) torrents
1316{
1317    //copy torrent to new location with name of data file
1318    if (code == NSOKButton)
1319        [[torrents objectAtIndex: 0] copyTorrentFileTo: [panel filename]];
1320   
1321    [torrents removeObjectAtIndex: 0];
1322    [self performSelectorOnMainThread: @selector(copyTorrentFileForTorrents:) withObject: torrents waitUntilDone: NO];
1323}
1324
1325- (void) revealFile: (id) sender
1326{
1327    NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
1328    Torrent * torrent;
1329    while ((torrent = [enumerator nextObject]))
1330        [torrent revealData];
1331}
1332
1333- (void) announceSelectedTorrents: (id) sender
1334{
1335    NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
1336    Torrent * torrent;
1337    while ((torrent = [enumerator nextObject]))
1338    {
1339        if ([torrent canManualAnnounce])
1340            [torrent manualAnnounce];
1341    }
1342}
1343
1344- (void) resetCacheForSelectedTorrents: (id) sender
1345{
1346    NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
1347    Torrent * torrent;
1348    while ((torrent = [enumerator nextObject]))
1349        [torrent resetCache];
1350   
1351    [self applyFilter: nil];
1352}
1353
1354- (void) showPreferenceWindow: (id) sender
1355{
1356    NSWindow * window = [fPrefsController window];
1357    if (![window isVisible])
1358        [window center];
1359
1360    [window makeKeyAndOrderFront: nil];
1361}
1362
1363- (void) showAboutWindow: (id) sender
1364{
1365    [[AboutWindowController aboutController] showWindow: nil];
1366}
1367
1368- (void) showInfo: (id) sender
1369{
1370    if ([[fInfoController window] isVisible])
1371        [fInfoController close];
1372    else
1373    {
1374        [fInfoController updateInfoStats];
1375        [[fInfoController window] orderFront: nil];
1376    }
1377}
1378
1379- (void) setInfoTab: (id) sender
1380{
1381    if (sender == fNextInfoTabItem)
1382        [fInfoController setNextTab];
1383    else
1384        [fInfoController setPreviousTab];
1385}
1386
1387- (void) showMessageWindow: (id) sender
1388{
1389    [fMessageController showWindow: nil];
1390}
1391
1392- (void) updateUI
1393{
1394    [fTorrents makeObjectsPerformSelector: @selector(update)];
1395
1396    //resort if necessary or just update the table
1397    NSString * sortType = [fDefaults stringForKey: @"Sort"];
1398    if ([sortType isEqualToString: @"Progress"] || [sortType isEqualToString: @"State"])
1399        [self sortTorrents];
1400    else
1401        [fTableView reloadData];
1402   
1403    //update the global DL/UL rates
1404    if (![fStatusBar isHidden])
1405    {
1406        float downloadRate, uploadRate;
1407        tr_torrentRates(fLib, & downloadRate, & uploadRate);
1408       
1409        [fTotalDLField setStringValue: [NSLocalizedString(@"Total DL: ", "Status bar -> total download")
1410                                        stringByAppendingString: [NSString stringForSpeed: downloadRate]]];
1411        [fTotalULField setStringValue: [NSLocalizedString(@"Total UL: ", "Status bar -> total upload")
1412                                        stringByAppendingString: [NSString stringForSpeed: uploadRate]]];
1413    }
1414
1415    //update non-constant parts of info window
1416    if ([[fInfoController window] isVisible])
1417        [fInfoController updateInfoStats];
1418
1419    //badge dock
1420    [fBadger updateBadge];
1421}
1422
1423- (void) updateTorrentsInQueue
1424{
1425    BOOL download = [fDefaults boolForKey: @"Queue"],
1426        seed = [fDefaults boolForKey: @"QueueSeed"];
1427   
1428    int desiredDownloadActive = [self numToStartFromQueue: YES],
1429        desiredSeedActive = [self numToStartFromQueue: NO];
1430   
1431    //sort torrents by order value
1432    NSArray * sortedTorrents;
1433    if ([fTorrents count] > 1 && (desiredDownloadActive > 0 || desiredSeedActive > 0))
1434    {
1435        NSSortDescriptor * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1436                                                @"orderValue" ascending: YES] autorelease];
1437        NSArray * descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1438       
1439        sortedTorrents = [fTorrents sortedArrayUsingDescriptors: descriptors];
1440        [descriptors release];
1441    }
1442    else
1443        sortedTorrents = fTorrents;
1444
1445    Torrent * torrent;
1446    NSEnumerator * enumerator = [sortedTorrents objectEnumerator];
1447    while ((torrent = [enumerator nextObject]))
1448    {
1449        if (![torrent isActive] && ![torrent isChecking] && [torrent waitingToStart])
1450        {
1451            if (![torrent allDownloaded])
1452            {
1453                if (!download || desiredDownloadActive > 0)
1454                {
1455                    [torrent startTransfer];
1456                    if ([torrent isActive])
1457                        desiredDownloadActive--;
1458                    [torrent update];
1459                }
1460            }
1461            else
1462            {
1463                if (!seed || desiredSeedActive > 0)
1464                {
1465                    [torrent startTransfer];
1466                    if ([torrent isActive])
1467                        desiredSeedActive--;
1468                    [torrent update];
1469                }
1470            }
1471        }
1472    }
1473   
1474    [self updateUI];
1475    [self applyFilter: nil];
1476    [self updateTorrentHistory];
1477}
1478
1479- (int) numToStartFromQueue: (BOOL) downloadQueue
1480{
1481    if (![fDefaults boolForKey: downloadQueue ? @"Queue" : @"QueueSeed"])
1482        return 0;
1483   
1484    int desired = [fDefaults integerForKey: downloadQueue ? @"QueueDownloadNumber" : @"QueueSeedNumber"];
1485       
1486    Torrent * torrent;
1487    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1488    while ((torrent = [enumerator nextObject]))
1489    {
1490        if ([torrent isChecking])
1491        {
1492            desired--;
1493            if (desired <= 0)
1494                return 0;
1495        }
1496        else if ([torrent isActive] && ![torrent isStalled] && ![torrent isError])
1497        {
1498            if ([torrent allDownloaded] != downloadQueue)
1499            {
1500                desired--;
1501                if (desired <= 0)
1502                    return 0;
1503            }
1504        }
1505        else;
1506    }
1507   
1508    return desired;
1509}
1510
1511- (void) torrentFinishedDownloading: (NSNotification *) notification
1512{
1513    Torrent * torrent = [notification object];
1514    if ([torrent isActive])
1515    {
1516        if ([fDefaults boolForKey: @"PlayDownloadSound"])
1517        {
1518            NSSound * sound;
1519            if ((sound = [NSSound soundNamed: [fDefaults stringForKey: @"DownloadSound"]]))
1520                [sound play];
1521        }
1522       
1523        NSDictionary * clickContext = [NSDictionary dictionaryWithObjectsAndKeys: GROWL_DOWNLOAD_COMPLETE, @"Type",
1524                                        [torrent dataLocation] , @"Location", nil];
1525        [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Download Complete", "Growl notification title")
1526                                    description: [torrent name] notificationName: GROWL_DOWNLOAD_COMPLETE
1527                                    iconData: nil priority: 0 isSticky: NO clickContext: clickContext];
1528       
1529        if (![fWindow isKeyWindow])
1530            [fBadger incrementCompleted];
1531       
1532        if ([fDefaults boolForKey: @"QueueSeed"] && [self numToStartFromQueue: NO] <= 0)
1533        {
1534            [torrent stopTransfer];
1535            [torrent setWaitToStart: YES];
1536        }
1537    }
1538   
1539    [self updateTorrentsInQueue];
1540}
1541
1542- (void) torrentRestartedDownloading: (NSNotification *) notification
1543{
1544    Torrent * torrent = [notification object];
1545    if ([torrent isActive])
1546    {
1547        if ([fDefaults boolForKey: @"Queue"] && [self numToStartFromQueue: YES] <= 0)
1548        {
1549            [torrent stopTransfer];
1550            [torrent setWaitToStart: YES];
1551        }
1552    }
1553   
1554    [self updateTorrentsInQueue];
1555}
1556
1557- (void) updateTorrentHistory
1558{
1559    NSMutableArray * history = [NSMutableArray arrayWithCapacity: [fTorrents count]];
1560
1561    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1562    Torrent * torrent;
1563    while ((torrent = [enumerator nextObject]))
1564        [history addObject: [torrent history]];
1565
1566    [history writeToFile: [[self applicationSupportFolder] stringByAppendingPathComponent: @"Transfers.plist"] atomically: YES];
1567}
1568
1569- (void) sortTorrents
1570{
1571    //remember selected rows if needed
1572    NSArray * selectedTorrents = nil;
1573    int numSelected = [fTableView numberOfSelectedRows];
1574    if (numSelected > 0 && numSelected < [fDisplayedTorrents count])
1575        selectedTorrents = [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]];
1576
1577    [self sortTorrentsIgnoreSelected]; //actually sort
1578   
1579    //set selected rows if needed
1580    if (selectedTorrents)
1581    {
1582        Torrent * torrent;
1583        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1584        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1585        while ((torrent = [enumerator nextObject]))
1586            [indexSet addIndex: [fDisplayedTorrents indexOfObject: torrent]];
1587       
1588        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1589        [indexSet release];
1590    }
1591}
1592
1593//doesn't remember selected rows
1594- (void) sortTorrentsIgnoreSelected
1595{
1596    NSString * sortType = [fDefaults stringForKey: @"Sort"];
1597    BOOL asc = ![fDefaults boolForKey: @"SortReverse"];
1598   
1599    NSSortDescriptor * nameDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"name"
1600                            ascending: asc selector: @selector(caseInsensitiveCompare:)] autorelease],
1601                    * orderDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"orderValue"
1602                                            ascending: asc] autorelease];
1603   
1604    NSArray * descriptors;
1605    if ([sortType isEqualToString: @"Name"])
1606        descriptors = [[NSArray alloc] initWithObjects: nameDescriptor, orderDescriptor, nil];
1607    else if ([sortType isEqualToString: @"State"])
1608    {
1609        NSSortDescriptor * stateDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1610                                                @"stateSortKey" ascending: !asc] autorelease],
1611                        * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1612                                            @"progressSortKey" ascending: !asc] autorelease],
1613                        * ratioDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1614                                            @"ratioSortKey" ascending: !asc] autorelease];
1615       
1616        descriptors = [[NSArray alloc] initWithObjects: stateDescriptor, progressDescriptor, ratioDescriptor,
1617                                                            nameDescriptor, orderDescriptor, nil];
1618    }
1619    else if ([sortType isEqualToString: @"Progress"])
1620    {
1621        NSSortDescriptor * progressDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1622                                            @"progressSortKey" ascending: asc] autorelease],
1623                        * ratioDescriptor = [[[NSSortDescriptor alloc] initWithKey:
1624                                            @"ratioSortKey" ascending: asc] autorelease];
1625       
1626        descriptors = [[NSArray alloc] initWithObjects: progressDescriptor, ratioDescriptor,
1627                                                            nameDescriptor, orderDescriptor, nil];
1628    }
1629    else if ([sortType isEqualToString: @"Order"])
1630        descriptors = [[NSArray alloc] initWithObjects: orderDescriptor, nil];
1631    else
1632    {
1633        NSSortDescriptor * dateDescriptor = [[[NSSortDescriptor alloc] initWithKey: @"dateAdded" ascending: asc] autorelease];
1634   
1635        descriptors = [[NSArray alloc] initWithObjects: dateDescriptor, orderDescriptor, nil];
1636    }
1637
1638    [fDisplayedTorrents sortUsingDescriptors: descriptors];
1639    [descriptors release];
1640   
1641    [fTableView reloadData];
1642}
1643
1644- (void) setSort: (id) sender
1645{
1646    NSString * oldSortType = [fDefaults stringForKey: @"Sort"];
1647   
1648    //get checked items
1649    NSMenuItem * prevSortItem, * prevSortActionItem;
1650    if ([oldSortType isEqualToString: @"Name"])
1651    {
1652        prevSortItem = fNameSortItem;
1653        prevSortActionItem = fNameSortActionItem;
1654    }
1655    else if ([oldSortType isEqualToString: @"State"])
1656    {
1657        prevSortItem = fStateSortItem;
1658        prevSortActionItem = fStateSortActionItem;
1659    }
1660    else if ([oldSortType isEqualToString: @"Progress"])
1661    {
1662        prevSortItem = fProgressSortItem;
1663        prevSortActionItem = fProgressSortActionItem;
1664    }
1665    else if ([oldSortType isEqualToString: @"Order"])
1666    {
1667        prevSortItem = fOrderSortItem;
1668        prevSortActionItem = fOrderSortActionItem;
1669    }
1670    else
1671    {
1672        prevSortItem = fDateSortItem;
1673        prevSortActionItem = fDateSortActionItem;
1674    }
1675   
1676    if (sender != prevSortItem && sender != prevSortActionItem)
1677    {
1678        //get new items to check
1679        NSMenuItem * currentSortItem, * currentSortActionItem;
1680        NSString * sortType;
1681        if (sender == fNameSortItem || sender == fNameSortActionItem)
1682        {
1683            currentSortItem = fNameSortItem;
1684            currentSortActionItem = fNameSortActionItem;
1685            sortType = @"Name";
1686        }
1687        else if (sender == fStateSortItem || sender == fStateSortActionItem)
1688        {
1689            currentSortItem = fStateSortItem;
1690            currentSortActionItem = fStateSortActionItem;
1691            sortType = @"State";
1692        }
1693        else if (sender == fProgressSortItem || sender == fProgressSortActionItem)
1694        {
1695            currentSortItem = fProgressSortItem;
1696            currentSortActionItem = fProgressSortActionItem;
1697            sortType = @"Progress";
1698        }
1699        else if (sender == fOrderSortItem || sender == fOrderSortActionItem)
1700        {
1701            currentSortItem = fOrderSortItem;
1702            currentSortActionItem = fOrderSortActionItem;
1703            sortType = @"Order";
1704           
1705            [fDefaults setBool: NO forKey: @"SortReverse"];
1706        }
1707        else
1708        {
1709            currentSortItem = fDateSortItem;
1710            currentSortActionItem = fDateSortActionItem;
1711            sortType = @"Date";
1712        }
1713       
1714        [fDefaults setObject: sortType forKey: @"Sort"];
1715   
1716        [prevSortItem setState: NSOffState];
1717        [prevSortActionItem setState: NSOffState];
1718        [currentSortItem setState: NSOnState];
1719        [currentSortActionItem setState: NSOnState];
1720    }
1721
1722    [self sortTorrents];
1723}
1724
1725- (void) setSortReverse: (id) sender
1726{
1727    [self sortTorrents];
1728}
1729
1730- (void) applyFilter: (id) sender
1731{
1732    //remember selected rows if needed
1733    NSArray * selectedTorrents = [fTableView numberOfSelectedRows] > 0
1734                ? [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] : nil;
1735
1736    NSMutableArray * tempTorrents = [[NSMutableArray alloc] initWithCapacity: [fTorrents count]];
1737   
1738    NSString * filterType = [fDefaults stringForKey: @"Filter"];
1739   
1740    int downloading = 0, seeding = 0, paused = 0;
1741    BOOL isDownloading = [filterType isEqualToString: @"Download"],
1742            isSeeding = [filterType isEqualToString: @"Seed"],
1743            isPaused = [filterType isEqualToString: @"Pause"];
1744    BOOL filtering = isDownloading || isSeeding || isPaused;
1745   
1746    //get count of each type
1747    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1748    Torrent * torrent;
1749    while ((torrent = [enumerator nextObject]))
1750    {
1751        if ([torrent isActive])
1752        {
1753            if ([torrent isSeeding])
1754            {
1755                seeding++;
1756                if (isSeeding)
1757                    [tempTorrents addObject: torrent];
1758            }
1759            else
1760            {
1761                downloading++;
1762                if (isDownloading)
1763                    [tempTorrents addObject: torrent];
1764            }
1765        }
1766        else
1767        {
1768            paused++;
1769            if (isPaused)
1770                [tempTorrents addObject: torrent];
1771        }
1772    }
1773   
1774    if (!filtering)
1775        [tempTorrents setArray: fTorrents];
1776   
1777    //set buttons with counts
1778    [fNoFilterButton setCount: [fTorrents count]];
1779    [fDownloadFilterButton setCount: downloading];
1780    [fSeedFilterButton setCount: seeding];
1781    [fPauseFilterButton setCount: paused];
1782   
1783    NSString * searchString = [fSearchFilterField stringValue];
1784    if ([searchString length] > 0)
1785    {
1786        filtering = YES;
1787       
1788        int i;
1789        for (i = [tempTorrents count] - 1; i >= 0; i--)
1790            if ([[[tempTorrents objectAtIndex: i] name] rangeOfString: searchString
1791                                        options: NSCaseInsensitiveSearch].location == NSNotFound)
1792                [tempTorrents removeObjectAtIndex: i];
1793    }
1794   
1795    [fDisplayedTorrents setArray: tempTorrents];
1796    [tempTorrents release];
1797   
1798    [self sortTorrentsIgnoreSelected];
1799   
1800    //set selected rows if needed
1801    if (selectedTorrents)
1802    {
1803        Torrent * torrent;
1804        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1805        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1806        unsigned index;
1807        while ((torrent = [enumerator nextObject]))
1808            if ((index = [fDisplayedTorrents indexOfObject: torrent]) != NSNotFound)
1809                [indexSet addIndex: index];
1810       
1811        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1812        [indexSet release];
1813    }
1814   
1815    //set status bar torrent count text
1816    NSMutableString * totalTorrentsString = [[NSMutableString alloc] initWithString: @""];
1817    if (filtering)
1818        [totalTorrentsString appendFormat: @"%d/", [fDisplayedTorrents count]];
1819   
1820    int totalCount = [fTorrents count];
1821    if (totalCount != 1)
1822        [totalTorrentsString appendFormat: NSLocalizedString(@"%d Transfers", "Status bar transfer count"), totalCount];
1823    else
1824        [totalTorrentsString appendFormat: NSLocalizedString(@"1 Transfer", "Status bar transfer count")];
1825   
1826    [fTotalTorrentsField setStringValue: totalTorrentsString];
1827    [totalTorrentsString release];
1828
1829    [self setWindowSizeToFit];
1830}
1831
1832//resets filter and sorts torrents
1833- (void) setFilter: (id) sender
1834{
1835    NSString * oldFilterType = [fDefaults stringForKey: @"Filter"];
1836   
1837    FilterBarButton * prevFilterButton;
1838    if ([oldFilterType isEqualToString: @"Pause"])
1839        prevFilterButton = fPauseFilterButton;
1840    else if ([oldFilterType isEqualToString: @"Seed"])
1841        prevFilterButton = fSeedFilterButton;
1842    else if ([oldFilterType isEqualToString: @"Download"])
1843        prevFilterButton = fDownloadFilterButton;
1844    else
1845        prevFilterButton = fNoFilterButton;
1846   
1847    if (sender != prevFilterButton)
1848    {
1849        [prevFilterButton setState: NSOffState];
1850        [sender setState: NSOnState];
1851
1852        NSString * filterType;
1853        if (sender == fDownloadFilterButton)
1854            filterType = @"Download";
1855        else if (sender == fPauseFilterButton)
1856            filterType = @"Pause";
1857        else if (sender == fSeedFilterButton)
1858            filterType = @"Seed";
1859        else
1860            filterType = @"None";
1861
1862        [fDefaults setObject: filterType forKey: @"Filter"];
1863    }
1864
1865    [self applyFilter: nil];
1866}
1867
1868- (void) switchFilter: (id) sender
1869{
1870    NSString * filterType = [fDefaults stringForKey: @"Filter"];
1871   
1872    NSButton * button;
1873    if ([filterType isEqualToString: @"None"])
1874        button = sender == fNextFilterItem ? fDownloadFilterButton : fPauseFilterButton;
1875    else if ([filterType isEqualToString: @"Download"])
1876        button = sender == fNextFilterItem ? fSeedFilterButton : fNoFilterButton;
1877    else if ([filterType isEqualToString: @"Seed"])
1878        button = sender == fNextFilterItem ? fPauseFilterButton : fDownloadFilterButton;
1879    else if ([filterType isEqualToString: @"Pause"])
1880        button = sender == fNextFilterItem ? fNoFilterButton : fSeedFilterButton;
1881    else
1882        button = fNoFilterButton;
1883   
1884    [self setFilter: button];
1885}
1886
1887- (void) updateControlTint: (NSNotification *) notification
1888{
1889    if ([fDefaults boolForKey: @"SpeedLimit"])
1890        [fSpeedLimitButton setImage: [NSColor currentControlTint] == NSBlueControlTint
1891            ? [NSImage imageNamed: @"SpeedLimitButtonBlue.png"] : [NSImage imageNamed: @"SpeedLimitButtonGraphite.png"]];
1892}
1893
1894- (void) applySpeedLimit: (id) sender
1895{
1896    [fPrefsController applySpeedSettings: nil];
1897}
1898
1899- (void) toggleSpeedLimit: (id) sender
1900{
1901    [fDefaults setBool: ![fDefaults boolForKey: @"SpeedLimit"] forKey: @"SpeedLimit"];
1902    [self applySpeedLimit: nil];
1903}
1904
1905- (void) autoSpeedLimitChange: (NSNotification *) notification
1906{
1907    if (![fDefaults boolForKey: @"SpeedLimitAuto"])
1908        return;
1909 
1910    NSCalendarDate * onDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
1911                        [[fDefaults objectForKey: @"SpeedLimitAutoOnDate"] timeIntervalSinceReferenceDate]],
1912        * offDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
1913                        [[fDefaults objectForKey: @"SpeedLimitAutoOffDate"] timeIntervalSinceReferenceDate]],
1914        * nowDate = [NSCalendarDate calendarDate];
1915   
1916    //check if should be on if within range
1917    BOOL shouldBeOn;
1918   
1919    int onTime = [onDate hourOfDay] * 60 + [onDate minuteOfHour],
1920        offTime = [offDate hourOfDay] * 60 + [offDate minuteOfHour],
1921        nowTime = [nowDate hourOfDay] * 60 + [nowDate minuteOfHour];
1922   
1923    if (onTime == offTime)
1924        shouldBeOn = NO;
1925    else if (onTime < offTime)
1926        shouldBeOn = onTime <= nowTime && nowTime < offTime;
1927    else
1928        shouldBeOn = onTime <= nowTime || nowTime < offTime;
1929   
1930    if ([fDefaults boolForKey: @"SpeedLimit"] != shouldBeOn)
1931        [self toggleSpeedLimit: nil];
1932}
1933
1934- (void) autoSpeedLimit
1935{
1936    if (![fDefaults boolForKey: @"SpeedLimitAuto"])
1937        return;
1938   
1939    //only toggle if within first few seconds of minutes
1940    NSCalendarDate * nowDate = [NSCalendarDate calendarDate];
1941    if ([nowDate secondOfMinute] > AUTO_SPEED_LIMIT_SECONDS)
1942        return;
1943   
1944    NSCalendarDate * offDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
1945                        [[fDefaults objectForKey: @"SpeedLimitAutoOffDate"] timeIntervalSinceReferenceDate]];
1946   
1947    BOOL toggle;
1948    if ([fDefaults boolForKey: @"SpeedLimit"])
1949        toggle = [nowDate hourOfDay] == [offDate hourOfDay] && [nowDate minuteOfHour] == [offDate minuteOfHour];
1950    else
1951    {
1952        NSCalendarDate * onDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
1953                        [[fDefaults objectForKey: @"SpeedLimitAutoOnDate"] timeIntervalSinceReferenceDate]];
1954        toggle = ([nowDate hourOfDay] == [onDate hourOfDay] && [nowDate minuteOfHour] == [onDate minuteOfHour])
1955                    && !([onDate hourOfDay] == [offDate hourOfDay] && [onDate minuteOfHour] == [offDate minuteOfHour]);
1956    }
1957   
1958    if (toggle)
1959    {
1960        [self toggleSpeedLimit: nil];
1961       
1962        [GrowlApplicationBridge notifyWithTitle: [fDefaults boolForKey: @"SpeedLimit"]
1963                ? NSLocalizedString(@"Speed Limit Auto Enabled", "Growl notification title")
1964                : NSLocalizedString(@"Speed Limit Auto Disabled", "Growl notification title")
1965            description: NSLocalizedString(@"Bandwidth settings changed", "Growl notification description")
1966            notificationName: GROWL_AUTO_SPEED_LIMIT iconData: nil priority: 0 isSticky: NO clickContext: nil];
1967    }
1968}
1969
1970- (void) setLimitGlobalEnabled: (id) sender
1971{
1972    [fPrefsController applySpeedSettings: nil];
1973}
1974
1975- (void) setQuickLimitGlobal: (id) sender
1976{
1977    [fDefaults setInteger: [[sender title] intValue] forKey: [sender menu] == fUploadMenu ? @"UploadLimit" : @"DownloadLimit"];
1978    [fDefaults setBool: YES forKey: [sender menu] == fUploadMenu ? @"CheckUpload" : @"CheckDownload"];
1979   
1980    [fPrefsController updateLimitFields];
1981    [fPrefsController applySpeedSettings: nil];
1982}
1983
1984- (void) setQuickRatioGlobal: (id) sender
1985{
1986    [fDefaults setBool: YES forKey: @"RatioCheck"];
1987    [fDefaults setFloat: [[sender title] floatValue] forKey: @"RatioLimit"];
1988   
1989    [fPrefsController updateRatioStopField];
1990}
1991
1992- (void) torrentStoppedForRatio: (NSNotification *) notification
1993{
1994    Torrent * torrent = [notification object];
1995   
1996    [self updateTorrentsInQueue];
1997    [fInfoController updateInfoStats];
1998    [fInfoController updateRatioForTorrent: torrent];
1999   
2000    if ([fDefaults boolForKey: @"PlaySeedingSound"])
2001    {
2002        NSSound * sound;
2003        if ((sound = [NSSound soundNamed: [fDefaults stringForKey: @"SeedingSound"]]))
2004            [sound play];
2005    }
2006   
2007    NSDictionary * clickContext = [NSDictionary dictionaryWithObjectsAndKeys: GROWL_SEEDING_COMPLETE, @"Type",
2008                                    [torrent dataLocation], @"Location", nil];
2009    [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Seeding Complete", "Growl notification title")
2010                        description: [torrent name] notificationName: GROWL_SEEDING_COMPLETE
2011                        iconData: nil priority: 0 isSticky: NO clickContext: clickContext];
2012}
2013
2014-(void) watcher: (id<UKFileWatcher>) watcher receivedNotification: (NSString *) notification forPath: (NSString *) path
2015{
2016    if ([notification isEqualToString: UKFileWatcherWriteNotification])
2017    {
2018        if (![fDefaults boolForKey: @"AutoImport"] || ![fDefaults stringForKey: @"AutoImportDirectory"])
2019            return;
2020       
2021        if (fAutoImportTimer)
2022        {
2023            if ([fAutoImportTimer isValid])
2024                [fAutoImportTimer invalidate];
2025            [fAutoImportTimer release];
2026            fAutoImportTimer = nil;
2027        }
2028       
2029        //check again in 10 seconds in case torrent file wasn't complete
2030        fAutoImportTimer = [[NSTimer scheduledTimerWithTimeInterval: 10.0 target: self
2031            selector: @selector(checkAutoImportDirectory) userInfo: nil repeats: NO] retain];
2032       
2033        [self checkAutoImportDirectory];
2034    }
2035}
2036
2037- (void) changeAutoImport
2038{
2039    if (fAutoImportTimer)
2040    {
2041        if ([fAutoImportTimer isValid])
2042            [fAutoImportTimer invalidate];
2043        [fAutoImportTimer release];
2044        fAutoImportTimer = nil;
2045    }
2046   
2047    if (fAutoImportedNames)
2048        [fAutoImportedNames removeAllObjects];
2049    [self checkAutoImportDirectory];
2050}
2051
2052- (void) checkAutoImportDirectory
2053{
2054    NSString * path;
2055    if (![fDefaults boolForKey: @"AutoImport"] || !(path = [fDefaults stringForKey: @"AutoImportDirectory"]))
2056        return;
2057   
2058    path = [path stringByExpandingTildeInPath];
2059   
2060    NSArray * importedNames;
2061    if (!(importedNames = [[NSFileManager defaultManager] directoryContentsAtPath: path]))
2062        return;
2063   
2064    //only check files that have not been checked yet
2065    NSMutableArray * newNames = [importedNames mutableCopy];
2066   
2067    if (fAutoImportedNames)
2068        [newNames removeObjectsInArray: fAutoImportedNames];
2069    else
2070        fAutoImportedNames = [[NSMutableArray alloc] init];
2071    [fAutoImportedNames setArray: importedNames];
2072   
2073    NSString * file;
2074    int i;
2075    for (i = [newNames count] - 1; i >= 0; i--)
2076    {
2077        file = [newNames objectAtIndex: i];
2078        if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] != NSOrderedSame)
2079            [newNames removeObjectAtIndex: i];
2080        else
2081            [newNames replaceObjectAtIndex: i withObject: [path stringByAppendingPathComponent: file]];
2082    }
2083   
2084    BOOL ask = [[fDefaults stringForKey: @"DownloadChoice"] isEqualToString: @"Ask"];
2085   
2086    NSEnumerator * enumerator = [newNames objectEnumerator];
2087    int canAdd, count;
2088    while ((file = [enumerator nextObject]))
2089    {
2090        canAdd = tr_torrentParse(fLib, [file UTF8String], NULL, NULL);
2091        if (canAdd == TR_OK)
2092        {
2093            if (!ask)
2094                count = [fTorrents count];
2095            [self openFiles: [NSArray arrayWithObject: file]];
2096           
2097            //check if torrent was opened
2098            if (!ask && [fTorrents count] > count)
2099                [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Torrent File Auto Added",
2100                    "Growl notification title") description: [file lastPathComponent]
2101                    notificationName: GROWL_AUTO_ADD iconData: nil priority: 0 isSticky: NO clickContext: nil];
2102        }
2103        else if (canAdd == TR_EINVALID)
2104            [fAutoImportedNames removeObject: [file lastPathComponent]];
2105        else;
2106    }
2107   
2108    [newNames release];
2109}
2110
2111- (void) beginCreateFile: (NSNotification *) notification
2112{
2113    if (![fDefaults boolForKey: @"AutoImport"])
2114        return;
2115   
2116    NSString * location = [notification object],
2117            * path = [fDefaults stringForKey: @"AutoImportDirectory"];
2118   
2119    if (location && path && [[[location stringByDeletingLastPathComponent] stringByExpandingTildeInPath]
2120                                    isEqualToString: [path stringByExpandingTildeInPath]])
2121        [fAutoImportedNames addObject: [location lastPathComponent]];
2122}
2123
2124- (int) numberOfRowsInTableView: (NSTableView *) tableview
2125{
2126    return [fDisplayedTorrents count];
2127}
2128
2129- (BOOL) tableView: (NSTableView *) tableView writeRowsWithIndexes: (NSIndexSet *) indexes
2130    toPasteboard: (NSPasteboard *) pasteboard
2131{
2132    //only allow reordering of rows if sorting by order with no filter
2133    if ([[fDefaults stringForKey: @"Sort"] isEqualToString: @"Order"]
2134        && [[fDefaults stringForKey: @"Filter"] isEqualToString: @"None"]
2135        && [[fSearchFilterField stringValue] length] == 0)
2136    {
2137        [pasteboard declareTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE] owner: self];
2138        [pasteboard setData: [NSKeyedArchiver archivedDataWithRootObject: indexes]
2139                                forType: TORRENT_TABLE_VIEW_DATA_TYPE];
2140        return YES;
2141    }
2142    return NO;
2143}
2144
2145- (NSDragOperation) tableView: (NSTableView *) t validateDrop: (id <NSDraggingInfo>) info
2146    proposedRow: (int) row proposedDropOperation: (NSTableViewDropOperation) operation
2147{
2148    NSPasteboard * pasteboard = [info draggingPasteboard];
2149    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2150    {
2151        [fTableView setDropRow: row dropOperation: NSTableViewDropAbove];
2152        return NSDragOperationGeneric;
2153    }
2154    else;
2155   
2156    return NSDragOperationNone;
2157}
2158
2159- (BOOL) tableView: (NSTableView *) t acceptDrop: (id <NSDraggingInfo>) info
2160    row: (int) newRow dropOperation: (NSTableViewDropOperation) operation
2161{
2162    NSPasteboard * pasteboard = [info draggingPasteboard];
2163    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2164    {
2165        //remember selected rows if needed
2166        NSArray * selectedTorrents = nil;
2167        int numSelected = [fTableView numberOfSelectedRows];
2168        if (numSelected > 0 && numSelected < [fDisplayedTorrents count])
2169            selectedTorrents = [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]];
2170   
2171        NSIndexSet * indexes = [NSKeyedUnarchiver unarchiveObjectWithData:
2172                                [pasteboard dataForType: TORRENT_TABLE_VIEW_DATA_TYPE]];
2173       
2174        //move torrent in array
2175        NSArray * movingTorrents = [[fDisplayedTorrents objectsAtIndexes: indexes] retain];
2176        [fDisplayedTorrents removeObjectsInArray: movingTorrents];
2177       
2178        //determine the insertion index now that transfers to move have been removed
2179        int i, decrease = 0;
2180        for (i = [indexes firstIndex]; i < newRow && i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
2181            decrease++;
2182       
2183        //insert objects at new location
2184        for (i = 0; i < [movingTorrents count]; i++)
2185            [fDisplayedTorrents insertObject: [movingTorrents objectAtIndex: i] atIndex: newRow - decrease + i];
2186       
2187        [movingTorrents release];
2188       
2189        //redo order values
2190        int low = [indexes firstIndex], high = [indexes lastIndex];
2191        if (newRow < low)
2192            low = newRow;
2193        else if (newRow > high + 1)
2194            high = newRow - 1;
2195        else;
2196       
2197        for (i = low; i <= high; i++)
2198            [[fDisplayedTorrents objectAtIndex: i] setOrderValue: i];
2199       
2200        [fTableView reloadData];
2201       
2202        //set selected rows if needed
2203        if (selectedTorrents)
2204        {
2205            Torrent * torrent;
2206            NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
2207            NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
2208            while ((torrent = [enumerator nextObject]))
2209                [indexSet addIndex: [fDisplayedTorrents indexOfObject: torrent]];
2210           
2211            [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
2212            [indexSet release];
2213        }
2214    }
2215    else;
2216   
2217    return YES;
2218}
2219
2220- (void) torrentTableViewSelectionDidChange: (NSNotification *) notification
2221{
2222    [fInfoController updateInfoForTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
2223}
2224
2225- (NSDragOperation) draggingEntered: (id <NSDraggingInfo>) info
2226{
2227    NSPasteboard * pasteboard = [info draggingPasteboard];
2228    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2229    {
2230        //check if any torrent files can be added
2231        NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
2232        NSEnumerator * enumerator = [files objectEnumerator];
2233        NSString * file;
2234        BOOL torrent = NO;
2235        int canAdd;
2236        while ((file = [enumerator nextObject]))
2237        {
2238            canAdd = tr_torrentParse(fLib, [file UTF8String], NULL, NULL);
2239            if (canAdd == TR_OK)
2240            {
2241                if (!fOverlayWindow)
2242                    fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2243                [fOverlayWindow setTorrents: files];
2244               
2245                return NSDragOperationCopy;
2246            }
2247            else if (canAdd == TR_EDUPLICATE)
2248                torrent = YES;
2249            else;
2250        }
2251       
2252        //create a torrent file if a single file
2253        if (!torrent && [files count] == 1)
2254        {
2255            if (!fOverlayWindow)
2256                fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2257            [fOverlayWindow setFile: [[files objectAtIndex: 0] lastPathComponent]];
2258           
2259            return NSDragOperationCopy;
2260        }
2261    }
2262    else if ([[pasteboard types] containsObject: NSURLPboardType])
2263    {
2264        if (!fOverlayWindow)
2265            fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2266        [fOverlayWindow setURL: [[NSURL URLFromPasteboard: pasteboard] relativeString]];
2267       
2268        return NSDragOperationCopy;
2269    }
2270    else;
2271   
2272    return NSDragOperationNone;
2273}
2274
2275- (void) draggingExited: (id <NSDraggingInfo>) info
2276{
2277    if (fOverlayWindow)
2278        [fOverlayWindow fadeOut];
2279}
2280
2281- (BOOL) performDragOperation: (id <NSDraggingInfo>) info
2282{
2283    if (fOverlayWindow)
2284        [fOverlayWindow fadeOut];
2285   
2286    NSPasteboard * pasteboard = [info draggingPasteboard];
2287    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2288    {
2289        BOOL torrent = NO, accept = YES;
2290       
2291        //create an array of files that can be opened
2292        NSMutableArray * filesToOpen = [[NSMutableArray alloc] init];
2293        NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
2294        NSEnumerator * enumerator = [files objectEnumerator];
2295        NSString * file;
2296        int canAdd;
2297        while ((file = [enumerator nextObject]))
2298        {
2299            canAdd = tr_torrentParse(fLib, [file UTF8String], NULL, NULL);
2300            if (canAdd == TR_OK)
2301            {
2302                [filesToOpen addObject: file];
2303                torrent = YES;
2304            }
2305            else if (canAdd == TR_EDUPLICATE)
2306                torrent = YES;
2307            else;
2308        }
2309       
2310        if ([filesToOpen count] > 0)
2311            [self application: NSApp openFiles: filesToOpen];
2312        else
2313        {
2314            if (!torrent && [files count] == 1)
2315                [CreatorWindowController createTorrentFile: fLib forFile: [files objectAtIndex: 0]];
2316            else
2317                accept = NO;
2318        }
2319        [filesToOpen release];
2320       
2321        return accept;
2322    }
2323    else if ([[pasteboard types] containsObject: NSURLPboardType])
2324    {
2325        NSURL * url;
2326        if ((url = [NSURL URLFromPasteboard: pasteboard]))
2327        {
2328            [self openURL: url];
2329            return YES;
2330        }
2331    }
2332    else;
2333   
2334    return NO;
2335}
2336
2337- (void) toggleSmallView: (id) sender
2338{
2339    BOOL makeSmall = [fDefaults boolForKey: @"SmallView"];
2340   
2341    [fTableView setRowHeight: makeSmall ? ROW_HEIGHT_SMALL : ROW_HEIGHT_REGULAR];
2342   
2343    //window min height
2344    NSSize contentMinSize = [fWindow contentMinSize],
2345            contentSize = [[fWindow contentView] frame].size;
2346    contentMinSize.height = contentSize.height - [fScrollView frame].size.height
2347                            + [fTableView rowHeight] + [fTableView intercellSpacing].height;
2348    [fWindow setContentMinSize: contentMinSize];
2349   
2350    //resize for larger min height if not set to auto size
2351    if (![fDefaults boolForKey: @"AutoSize"])
2352    {
2353        if (!makeSmall && contentSize.height < contentMinSize.height)
2354        {
2355            NSRect frame = [fWindow frame];
2356            float heightChange = contentMinSize.height - contentSize.height;
2357            frame.size.height += heightChange;
2358            frame.origin.y -= heightChange;
2359           
2360            [fWindow setFrame: frame display: YES];
2361            [fTableView reloadData];
2362        }
2363    }
2364    else
2365        [self setWindowSizeToFit];
2366}
2367
2368- (void) toggleStatusBar: (id) sender
2369{
2370    [self showStatusBar: [fStatusBar isHidden] animate: YES];
2371    [fDefaults setBool: ![fStatusBar isHidden] forKey: @"StatusBar"];
2372}
2373
2374- (NSRect) windowFrameByAddingHeight: (float) height checkLimits: (BOOL) check
2375{
2376    //convert pixels to points
2377    NSRect windowFrame = [fWindow frame];
2378    NSSize windowSize = [fScrollView convertSize: windowFrame.size fromView: nil];
2379    windowSize.height += height;
2380   
2381    if (check)
2382    {
2383        NSSize minSize = [fScrollView convertSize: [fWindow minSize] fromView: nil];
2384       
2385        if (windowSize.height < minSize.height)
2386            windowSize.height = minSize.height;
2387        else
2388        {
2389            NSSize maxSize = [fScrollView convertSize: [[fWindow screen] visibleFrame].size fromView: nil];
2390            if ([fStatusBar isHidden])
2391                maxSize.height -= [fStatusBar frame].size.height;
2392            if ([fFilterBar isHidden])
2393                maxSize.height -= [fFilterBar frame].size.height;
2394            if (windowSize.height > maxSize.height)
2395                windowSize.height = maxSize.height;
2396        }
2397    }
2398
2399    //convert points to pixels
2400    windowSize = [fScrollView convertSize: windowSize toView: nil];
2401
2402    windowFrame.origin.y -= (windowSize.height - windowFrame.size.height);
2403    windowFrame.size.height = windowSize.height;
2404    return windowFrame;
2405}
2406
2407- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
2408{
2409    if (show != [fStatusBar isHidden])
2410        return;
2411
2412    if (show)
2413        [fStatusBar setHidden: NO];
2414
2415    NSRect frame;
2416    float heightChange = [fStatusBar frame].size.height;
2417    if (!show)
2418        heightChange *= -1;
2419   
2420    //allow bar to show even if not enough room
2421    if (show && ![fDefaults boolForKey: @"AutoSize"])
2422    {
2423        frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2424        float change = [[fWindow screen] visibleFrame].size.height - frame.size.height;
2425        if (change < 0.0)
2426        {
2427            frame = [fWindow frame];
2428            frame.size.height += change;
2429            frame.origin.y -= change;
2430            [fWindow setFrame: frame display: NO animate: NO];
2431        }
2432    }
2433
2434    [self updateUI];
2435   
2436    //set views to not autoresize
2437    unsigned int statsMask = [fStatusBar autoresizingMask];
2438    unsigned int filterMask = [fFilterBar autoresizingMask];
2439    unsigned int scrollMask = [fScrollView autoresizingMask];
2440    [fStatusBar setAutoresizingMask: 0];
2441    [fFilterBar setAutoresizingMask: 0];
2442    [fScrollView setAutoresizingMask: 0];
2443   
2444    frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2445    [fWindow setFrame: frame display: YES animate: animate];
2446   
2447    //re-enable autoresize
2448    [fStatusBar setAutoresizingMask: statsMask];
2449    [fFilterBar setAutoresizingMask: filterMask];
2450    [fScrollView setAutoresizingMask: scrollMask];
2451   
2452    //change min size
2453    NSSize minSize = [fWindow contentMinSize];
2454    minSize.height += heightChange;
2455    [fWindow setContentMinSize: minSize];
2456   
2457    if (!show)
2458        [fStatusBar setHidden: YES];
2459}
2460
2461- (void) toggleFilterBar: (id) sender
2462{
2463    //disable filtering when hiding
2464    if (![fFilterBar isHidden])
2465    {
2466        [fSearchFilterField setStringValue: @""];
2467        [self setFilter: fNoFilterButton];
2468    }
2469
2470    [self showFilterBar: [fFilterBar isHidden] animate: YES];
2471    [fDefaults setBool: ![fFilterBar isHidden] forKey: @"FilterBar"];
2472}
2473
2474- (void) showFilterBar: (BOOL) show animate: (BOOL) animate
2475{
2476    if (show != [fFilterBar isHidden])
2477        return;
2478
2479    if (show)
2480        [fFilterBar setHidden: NO];
2481
2482    NSRect frame;
2483    float heightChange = [fFilterBar frame].size.height;
2484    if (!show)
2485        heightChange *= -1;
2486   
2487    //allow bar to show even if not enough room
2488    if (show && ![fDefaults boolForKey: @"AutoSize"])
2489    {
2490        frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2491        float change = [[fWindow screen] visibleFrame].size.height - frame.size.height;
2492        if (change < 0.0)
2493        {
2494            frame = [fWindow frame];
2495            frame.size.height += change;
2496            frame.origin.y -= change;
2497            [fWindow setFrame: frame display: NO animate: NO];
2498        }
2499    }
2500
2501    //set views to not autoresize
2502    unsigned int filterMask = [fFilterBar autoresizingMask];
2503    unsigned int scrollMask = [fScrollView autoresizingMask];
2504    [fFilterBar setAutoresizingMask: 0];
2505    [fScrollView setAutoresizingMask: 0];
2506   
2507    frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2508    [fWindow setFrame: frame display: YES animate: animate];
2509   
2510    //re-enable autoresize
2511    [fFilterBar setAutoresizingMask: filterMask];
2512    [fScrollView setAutoresizingMask: scrollMask];
2513   
2514    //change min size
2515    NSSize minSize = [fWindow contentMinSize];
2516    minSize.height += heightChange;
2517    [fWindow setContentMinSize: minSize];
2518   
2519    if (!show)
2520    {
2521        [fFilterBar setHidden: YES];
2522        [fWindow makeFirstResponder: fTableView];
2523    }
2524}
2525
2526- (void) toggleAdvancedBar: (id) sender
2527{
2528    [fTableView display];
2529}
2530
2531- (void) doNothing: (id) sender {}
2532
2533- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier:
2534    (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
2535{
2536    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
2537
2538    if ([ident isEqualToString: TOOLBAR_CREATE])
2539    {
2540        [item setLabel: NSLocalizedString(@"Create", "Create toolbar item -> label")];
2541        [item setPaletteLabel: NSLocalizedString(@"Create Torrent File", "Create toolbar item -> palette label")];
2542        [item setToolTip: NSLocalizedString(@"Create torrent file", "Create toolbar item -> tooltip")];
2543        [item setImage: [NSImage imageNamed: @"Create.png"]];
2544        [item setTarget: self];
2545        [item setAction: @selector(createFile:)];
2546        [item setAutovalidates: NO];
2547    }
2548    else if ([ident isEqualToString: TOOLBAR_OPEN])
2549    {
2550        [item setLabel: NSLocalizedString(@"Open", "Open toolbar item -> label")];
2551        [item setPaletteLabel: NSLocalizedString(@"Open Torrent Files", "Open toolbar item -> palette label")];
2552        [item setToolTip: NSLocalizedString(@"Open torrent files", "Open toolbar item -> tooltip")];
2553        [item setImage: [NSImage imageNamed: @"Open.png"]];
2554        [item setTarget: self];
2555        [item setAction: @selector(openShowSheet:)];
2556        [item setAutovalidates: NO];
2557    }
2558    else if ([ident isEqualToString: TOOLBAR_REMOVE])
2559    {
2560        [item setLabel: NSLocalizedString(@"Remove", "Remove toolbar item -> label")];
2561        [item setPaletteLabel: NSLocalizedString(@"Remove Selected", "Remove toolbar item -> palette label")];
2562        [item setToolTip: NSLocalizedString(@"Remove selected transfers", "Remove toolbar item -> tooltip")];
2563        [item setImage: [NSImage imageNamed: @"Remove.png"]];
2564        [item setTarget: self];
2565        [item setAction: @selector(removeNoDelete:)];
2566    }
2567    else if ([ident isEqualToString: TOOLBAR_INFO])
2568    {
2569        [item setLabel: NSLocalizedString(@"Inspector", "Inspector toolbar item -> label")];
2570        [item setPaletteLabel: NSLocalizedString(@"Toggle Inspector", "Inspector toolbar item -> palette label")];
2571        [item setToolTip: NSLocalizedString(@"Toggle the torrent inspector", "Inspector toolbar item -> tooltip")];
2572        [item setImage: [NSImage imageNamed: @"Info.png"]];
2573        [item setTarget: self];
2574        [item setAction: @selector(showInfo:)];
2575        [item setAutovalidates: NO];
2576    }
2577    else if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
2578    {
2579        [item setLabel: NSLocalizedString(@"Pause All", "Pause All toolbar item -> label")];
2580        [item setPaletteLabel: [item label]];
2581        [item setToolTip: NSLocalizedString(@"Pause all transfers", "Pause All toolbar item -> tooltip")];
2582        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
2583        [item setTarget: self];
2584        [item setAction: @selector(stopAllTorrents:)];
2585    }
2586    else if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
2587    {
2588        [item setLabel: NSLocalizedString(@"Resume All", "Resume All toolbar item -> label")];
2589        [item setPaletteLabel: [item label]];
2590        [item setToolTip: NSLocalizedString(@"Resume all transfers", "Resume All toolbar item -> tooltip")];
2591        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
2592        [item setTarget: self];
2593        [item setAction: @selector(resumeAllTorrents:)];
2594    }
2595    else if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
2596    {
2597        [item setLabel: NSLocalizedString(@"Pause", "Pause toolbar item -> label")];
2598        [item setPaletteLabel: NSLocalizedString(@"Pause Selected", "Pause toolbar item -> palette label")];
2599        [item setToolTip: NSLocalizedString(@"Pause selected transfers", "Pause toolbar item -> tooltip")];
2600        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
2601        [item setTarget: self];
2602        [item setAction: @selector(stopSelectedTorrents:)];
2603    }
2604    else if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
2605    {
2606        [item setLabel: NSLocalizedString(@"Resume", "Resume toolbar item -> label")];
2607        [item setPaletteLabel: NSLocalizedString(@"Resume Selected", "Resume toolbar item -> palette label")];
2608        [item setToolTip: NSLocalizedString(@"Resume selected transfers", "Resume toolbar item -> tooltip")];
2609        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
2610        [item setTarget: self];
2611        [item setAction: @selector(resumeSelectedTorrents:)];
2612    }
2613    else if ([ident isEqualToString: TOOLBAR_FILTER])
2614    {
2615        [item setLabel: NSLocalizedString(@"Filter", "Filter toolbar item -> label")];
2616        [item setPaletteLabel: NSLocalizedString(@"Toggle Filter", "Filter toolbar item -> palette label")];
2617        [item setToolTip: NSLocalizedString(@"Toggle the filter bar", "Filter toolbar item -> tooltip")];
2618        [item setImage: [NSImage imageNamed: @"Filter.png"]];
2619        [item setTarget: self];
2620        [item setAction: @selector(toggleFilterBar:)];
2621        [item setAutovalidates: NO];
2622    }
2623    else
2624    {
2625        [item release];
2626        return nil;
2627    }
2628
2629    return item;
2630}
2631
2632- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
2633{
2634    return [NSArray arrayWithObjects:
2635            TOOLBAR_CREATE, TOOLBAR_OPEN, TOOLBAR_REMOVE,
2636            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
2637            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL, TOOLBAR_FILTER, TOOLBAR_INFO,
2638            NSToolbarSeparatorItemIdentifier,
2639            NSToolbarSpaceItemIdentifier,
2640            NSToolbarFlexibleSpaceItemIdentifier,
2641            NSToolbarCustomizeToolbarItemIdentifier, nil];
2642}
2643
2644- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
2645{
2646    return [NSArray arrayWithObjects:
2647            TOOLBAR_CREATE, TOOLBAR_OPEN, TOOLBAR_REMOVE,
2648            NSToolbarSeparatorItemIdentifier,
2649            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
2650            NSToolbarFlexibleSpaceItemIdentifier,
2651            TOOLBAR_FILTER, TOOLBAR_INFO, nil];
2652}
2653
2654#warning validateUserInterfaceItem: ???
2655- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
2656{
2657    NSString * ident = [toolbarItem itemIdentifier];
2658
2659    //enable remove item
2660    if ([ident isEqualToString: TOOLBAR_REMOVE])
2661        return [fTableView numberOfSelectedRows] > 0;
2662
2663    //enable pause all item
2664    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
2665    {
2666        Torrent * torrent;
2667        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2668        while ((torrent = [enumerator nextObject]))
2669            if ([torrent isActive] || [torrent waitingToStart])
2670                return YES;
2671        return NO;
2672    }
2673
2674    //enable resume all item
2675    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
2676    {
2677        Torrent * torrent;
2678        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2679        while ((torrent = [enumerator nextObject]))
2680            if ([torrent isPaused] && ![torrent waitingToStart])
2681                return YES;
2682        return NO;
2683    }
2684
2685    //enable pause item
2686    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
2687    {
2688        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2689        Torrent * torrent;
2690        while ((torrent = [enumerator nextObject]))
2691        {
2692            if ([torrent isActive] || [torrent waitingToStart])
2693                return YES;
2694        }
2695        return NO;
2696    }
2697   
2698    //enable resume item
2699    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
2700    {
2701        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2702        Torrent * torrent;
2703        while ((torrent = [enumerator nextObject]))
2704        {
2705            if ([torrent isPaused] && ![torrent waitingToStart])
2706                return YES;
2707        }
2708        return NO;
2709    }
2710
2711    return YES;
2712}
2713
2714- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
2715{
2716    SEL action = [menuItem action];
2717
2718    //only enable some items if it is in a context menu or the window is useable
2719    BOOL canUseTable = [fWindow isKeyWindow] || [[menuItem menu] supermenu] != [NSApp mainMenu];
2720
2721    //enable open items
2722    if (action == @selector(openShowSheet:) || action == @selector(openURLShowSheet:))
2723        return [fWindow attachedSheet] == nil;
2724   
2725    //enable sort and advanced bar items
2726    if (action == @selector(setSort:) || action == @selector(toggleAdvancedBar:) || action == @selector(toggleSmallView:))
2727        return [fWindow isVisible];
2728
2729    //enable show info
2730    if (action == @selector(showInfo:))
2731    {
2732        NSString * title = [[fInfoController window] isVisible] ? NSLocalizedString(@"Hide Inspector",
2733                            "View menu -> Inspector") : NSLocalizedString(@"Show Inspector", "View menu -> Inspector");
2734        if (![[menuItem title] isEqualToString: title])
2735                [menuItem setTitle: title];
2736
2737        return YES;
2738    }
2739   
2740    //enable prev/next inspector tab
2741    if (action == @selector(setInfoTab:))
2742        return [[fInfoController window] isVisible];
2743   
2744    //enable toggle status bar
2745    if (action == @selector(toggleStatusBar:))
2746    {
2747        NSString * title = [fStatusBar isHidden] ? NSLocalizedString(@"Show Status Bar", "View menu -> Status Bar")
2748                            : NSLocalizedString(@"Hide Status Bar", "View menu -> Status Bar");
2749        if (![[menuItem title] isEqualToString: title])
2750            [menuItem setTitle: title];
2751
2752        return [fWindow isVisible];
2753    }
2754   
2755    //enable toggle filter bar
2756    if (action == @selector(toggleFilterBar:))
2757    {
2758        NSString * title = [fFilterBar isHidden] ? NSLocalizedString(@"Show Filter Bar", "View menu -> Filter Bar")
2759                            : NSLocalizedString(@"Hide Filter Bar", "View menu -> Filter Bar");
2760        if (![[menuItem title] isEqualToString: title])
2761            [menuItem setTitle: title];
2762
2763        return [fWindow isVisible];
2764    }
2765   
2766    //enable prev/next filter button
2767    if (action == @selector(switchFilter:))
2768        return [fWindow isVisible] && ![fFilterBar isHidden];
2769
2770    //enable reveal in finder
2771    if (action == @selector(revealFile:))
2772        return canUseTable && [fTableView numberOfSelectedRows] > 0;
2773
2774    //enable remove items
2775    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
2776        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteDataAndTorrent:))
2777    {
2778        BOOL warning = NO,
2779            onlyDownloading = [fDefaults boolForKey: @"CheckRemoveDownloading"],
2780            canDelete = action != @selector(removeDeleteTorrent:) && action != @selector(removeDeleteDataAndTorrent:);
2781       
2782        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2783        Torrent * torrent;
2784        while ((torrent = [enumerator nextObject]))
2785        {
2786            if (!warning && [torrent isActive])
2787            {
2788                warning = onlyDownloading ? ![torrent isSeeding] : YES;
2789                if (warning && canDelete)
2790                    break;
2791            }
2792            if (!canDelete && [torrent publicTorrent])
2793            {
2794                canDelete = YES;
2795                if (warning)
2796                    break;
2797            }
2798        }
2799   
2800        //append or remove ellipsis when needed
2801        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
2802        if (warning && [fDefaults boolForKey: @"CheckRemove"])
2803        {
2804            if (![title hasSuffix: ellipsis])
2805                [menuItem setTitle: [title stringByAppendingEllipsis]];
2806        }
2807        else
2808        {
2809            if ([title hasSuffix: ellipsis])
2810                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
2811        }
2812       
2813        return canUseTable && canDelete && [fTableView numberOfSelectedRows] > 0;
2814    }
2815
2816    //enable pause all item
2817    if (action == @selector(stopAllTorrents:))
2818    {
2819        Torrent * torrent;
2820        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2821        while ((torrent = [enumerator nextObject]))
2822            if ([torrent isActive] || [torrent waitingToStart])
2823                return YES;
2824        return NO;
2825    }
2826   
2827    //enable resume all item
2828    if (action == @selector(resumeAllTorrents:))
2829    {
2830        Torrent * torrent;
2831        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2832        while ((torrent = [enumerator nextObject]))
2833            if ([torrent isPaused] && ![torrent waitingToStart])
2834                return YES;
2835        return NO;
2836    }
2837   
2838    //enable resume all waiting item
2839    if (action == @selector(resumeWaitingTorrents:))
2840    {
2841        if (![fDefaults boolForKey: @"Queue"] && ![fDefaults boolForKey: @"QueueSeed"])
2842            return NO;
2843   
2844        Torrent * torrent;
2845        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2846        while ((torrent = [enumerator nextObject]))
2847            if ([torrent isPaused] && [torrent waitingToStart])
2848                return YES;
2849        return NO;
2850    }
2851   
2852    //enable resume selected waiting item
2853    if (action == @selector(resumeSelectedTorrentsNoWait:))
2854    {
2855        if (![fDefaults boolForKey: @"Queue"] && ![fDefaults boolForKey: @"QueueSeed"])
2856            return NO;
2857   
2858        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2859        Torrent * torrent;
2860        while ((torrent = [enumerator nextObject]))
2861            if ([torrent isPaused])
2862                return YES;
2863        return NO;
2864    }
2865
2866    //enable pause item
2867    if (action == @selector(stopSelectedTorrents:))
2868    {
2869        if (!canUseTable)
2870            return NO;
2871   
2872        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2873        Torrent * torrent;
2874        while ((torrent = [enumerator nextObject]))
2875            if ([torrent isActive] || [torrent waitingToStart])
2876                return YES;
2877        return NO;
2878    }
2879   
2880    //enable resume item
2881    if (action == @selector(resumeSelectedTorrents:))
2882    {
2883        if (!canUseTable)
2884            return NO;
2885   
2886        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2887        Torrent * torrent;
2888        while ((torrent = [enumerator nextObject]))
2889            if ([torrent isPaused] && ![torrent waitingToStart])
2890                return YES;
2891        return NO;
2892    }
2893   
2894    //enable manual announce item
2895    if (action == @selector(announceSelectedTorrents:))
2896    {
2897        if (!canUseTable)
2898            return NO;
2899       
2900        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2901        Torrent * torrent;
2902        while ((torrent = [enumerator nextObject]))
2903            if ([torrent canManualAnnounce])
2904                return YES;
2905        return NO;
2906    }
2907   
2908    //enable reset cache item
2909    if (action == @selector(resetCacheForSelectedTorrents:))
2910        return canUseTable && [fTableView numberOfSelectedRows] > 0;
2911   
2912    //enable move torrent file item
2913    if (action == @selector(moveDataFiles:))
2914        return canUseTable && [fTableView numberOfSelectedRows] > 0;
2915   
2916    //enable copy torrent file item
2917    if (action == @selector(copyTorrentFiles:))
2918        return canUseTable && [fTableView numberOfSelectedRows] > 0;
2919   
2920    //enable reverse sort item
2921    if (action == @selector(setSortReverse:))
2922        return ![[fDefaults stringForKey: @"Sort"] isEqualToString: @"Order"];
2923
2924    return YES;
2925}
2926
2927- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument
2928{
2929    NSEnumerator * enumerator;
2930    Torrent * torrent;
2931    BOOL active;
2932
2933    switch (messageType)
2934    {
2935        case kIOMessageSystemWillSleep:
2936            //close all connections before going to sleep and remember we should resume when we wake up
2937            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
2938
2939            //wait for running transfers to stop (5 second timeout)
2940            NSDate * start = [NSDate date];
2941            BOOL timeUp = NO;
2942           
2943            enumerator = [fTorrents objectEnumerator];
2944            while (!timeUp && (torrent = [enumerator nextObject]))
2945                while (![torrent isPaused] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
2946                {
2947                    usleep(100000);
2948                    [torrent update];
2949                }
2950
2951            IOAllowPowerChange(fRootPort, (long) messageArgument);
2952            break;
2953
2954        case kIOMessageCanSystemSleep:
2955            //pevent idle sleep unless all paused
2956            active = NO;
2957            enumerator = [fTorrents objectEnumerator];
2958            while ((torrent = [enumerator nextObject]))
2959                if ([torrent isActive])
2960                {
2961                    active = YES;
2962                    break;
2963                }
2964
2965            if (active)
2966                IOCancelPowerChange(fRootPort, (long) messageArgument);
2967            else
2968                IOAllowPowerChange(fRootPort, (long) messageArgument);
2969            break;
2970
2971        case kIOMessageSystemHasPoweredOn:
2972            //resume sleeping transfers after we wake up
2973            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
2974            [self autoSpeedLimitChange: nil];
2975            break;
2976    }
2977}
2978
2979- (NSMenu *) applicationDockMenu: (NSApplication *) sender
2980{
2981    int seeding = 0, downloading = 0;
2982    NSEnumerator * enumerator = [fTorrents objectEnumerator];
2983    Torrent * torrent;
2984    while ((torrent = [enumerator nextObject]))
2985    {
2986        if ([torrent isSeeding])
2987            seeding++;
2988        else if ([torrent isActive])
2989            downloading++;
2990        else;
2991    }
2992   
2993    NSMenuItem * seedingItem = [fDockMenu itemWithTag: DOCK_SEEDING_TAG],
2994            * downloadingItem = [fDockMenu itemWithTag: DOCK_DOWNLOADING_TAG];
2995   
2996    BOOL hasSeparator = seedingItem || downloadingItem;
2997   
2998    if (seeding > 0)
2999    {
3000        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Seeding",
3001                                                        "Dock item - Seeding"), seeding];
3002        if (!seedingItem)
3003        {
3004            seedingItem = [[[NSMenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""] autorelease];
3005            [seedingItem setTag: DOCK_SEEDING_TAG];
3006            [fDockMenu insertItem: seedingItem atIndex: 0];
3007        }
3008        else
3009            [seedingItem setTitle: title];
3010    }
3011    else
3012    {
3013        if (seedingItem)
3014            [fDockMenu removeItem: seedingItem];
3015    }
3016   
3017    if (downloading > 0)
3018    {
3019        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Downloading",
3020                                                        "Dock item - Downloading"), downloading];
3021        if (!downloadingItem)
3022        {
3023            downloadingItem = [[[NSMenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""] autorelease];
3024            [downloadingItem setTag: DOCK_DOWNLOADING_TAG];
3025            [fDockMenu insertItem: downloadingItem atIndex: seeding > 0 ? 1 : 0];
3026        }
3027        else
3028            [downloadingItem setTitle: title];
3029    }
3030    else
3031    {
3032        if (downloadingItem)
3033            [fDockMenu removeItem: downloadingItem];
3034    }
3035   
3036    if (seeding > 0 || downloading > 0)
3037    {
3038        if (!hasSeparator)
3039            [fDockMenu insertItem: [NSMenuItem separatorItem] atIndex: seeding > 0 && downloading > 0 ? 2 : 1];
3040    }
3041    else
3042    {
3043        if (hasSeparator)
3044            [fDockMenu removeItemAtIndex: 0];
3045    }
3046   
3047    return fDockMenu;
3048}
3049
3050- (void) updateDockBadge: (NSNotification *) notification
3051{
3052    [fBadger updateBadge];
3053}
3054
3055- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
3056{
3057    //if auto size is enabled, the current frame shouldn't need to change
3058    NSRect frame = [fDefaults boolForKey: @"AutoSize"] ? [window frame] : [self sizedWindowFrame];
3059   
3060    frame.size.width = [fDefaults boolForKey: @"SmallView"] ? [fWindow minSize].width : WINDOW_REGULAR_WIDTH;
3061    return frame;
3062}
3063
3064- (void) setWindowSizeToFit
3065{
3066    if ([fDefaults boolForKey: @"AutoSize"])
3067    {
3068        [fScrollView setHasVerticalScroller: NO];
3069        [fWindow setFrame: [self sizedWindowFrame] display: YES animate: YES];
3070        [fScrollView setHasVerticalScroller: YES];
3071    }
3072}
3073
3074- (NSRect) sizedWindowFrame
3075{
3076    float heightChange = [fDisplayedTorrents count] * ([fTableView rowHeight] +
3077            [fTableView intercellSpacing].height) - [fScrollView frame].size.height;
3078    return [self windowFrameByAddingHeight: heightChange checkLimits: YES];
3079}
3080
3081- (void) showMainWindow: (id) sender
3082{
3083    [fWindow makeKeyAndOrderFront: nil];
3084}
3085
3086- (void) windowDidBecomeKey: (NSNotification *) notification
3087{
3088    [fBadger clearCompleted];
3089}
3090
3091- (NSSize) windowWillResize: (NSWindow *) sender toSize: (NSSize) proposedFrameSize
3092{
3093    //only resize horizontally if autosize is enabled
3094    if ([fDefaults boolForKey: @"AutoSize"])
3095        proposedFrameSize.height = [fWindow frame].size.height;
3096    return proposedFrameSize;
3097}
3098
3099- (void) windowDidResize: (NSNotification *) notification
3100{
3101    //size search filter to not overlap buttons
3102    float pointX = NSMaxX([fPauseFilterButton frame]) + 5.0;
3103    NSRect oldFrame = [fSearchFilterField frame],
3104            frame = NSMakeRect(pointX, oldFrame.origin.y, NSMaxX(oldFrame) - pointX, oldFrame.size.height);
3105   
3106    BOOL show;
3107    if (show = (frame.size.width >= SEARCH_FILTER_MIN_WIDTH))
3108    {
3109        //make sure it is not too long
3110        if (frame.size.width > SEARCH_FILTER_MAX_WIDTH)
3111        {
3112            float different = frame.size.width - SEARCH_FILTER_MAX_WIDTH;
3113            frame.origin.x += different;
3114            frame.size.width -= different;
3115        }
3116        [fSearchFilterField setFrame: frame];
3117    }
3118   
3119    //hide search filter if it overlaps filter buttons
3120    [fSearchFilterField setHidden: !show];
3121}
3122
3123- (void) linkHomepage: (id) sender
3124{
3125    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
3126}
3127
3128- (void) linkForums: (id) sender
3129{
3130    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
3131}
3132
3133- (void) linkDonate: (id) sender
3134{
3135    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: DONATE_URL]];
3136}
3137
3138- (void) prepareForUpdate: (NSNotification *) notification
3139{
3140    fUpdateInProgress = YES;
3141}
3142
3143- (NSDictionary *) registrationDictionaryForGrowl
3144{
3145    NSArray * notifications = [NSArray arrayWithObjects: GROWL_DOWNLOAD_COMPLETE, GROWL_SEEDING_COMPLETE,
3146                                                            GROWL_AUTO_ADD, GROWL_AUTO_SPEED_LIMIT, nil];
3147    return [NSDictionary dictionaryWithObjectsAndKeys: notifications, GROWL_NOTIFICATIONS_ALL,
3148                                notifications, GROWL_NOTIFICATIONS_DEFAULT, nil];
3149}
3150
3151- (void) growlNotificationWasClicked: (id) clickContext
3152{
3153    if (!clickContext || ![clickContext isKindOfClass: [NSDictionary class]])
3154        return;
3155   
3156    NSString * type = [clickContext objectForKey: @"Type"], * location;
3157    if (([type isEqualToString: GROWL_DOWNLOAD_COMPLETE] || [type isEqualToString: GROWL_SEEDING_COMPLETE])
3158            && (location = [clickContext objectForKey: @"Location"]))
3159        [[NSWorkspace sharedWorkspace] selectFile: location inFileViewerRootedAtPath: nil];
3160}
3161
3162#warning move to additions
3163- (NSString *) applicationSupportFolder
3164{
3165    return [[NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex: 0]
3166                stringByAppendingPathComponent: [[NSProcessInfo processInfo] processName]];
3167}
3168
3169- (void) ipcQuit
3170{
3171    fRemoteQuit = YES;
3172    [NSApp terminate: self];
3173}
3174
3175- (NSArray *) ipcGetTorrentsByID: (NSArray *) idlist
3176{
3177    if (!idlist)
3178        return fTorrents;
3179   
3180    NSMutableArray * torrents = [NSMutableArray array];
3181   
3182    NSEnumerator * torrentEnum = [fTorrents objectEnumerator], * idEnum;
3183    int torId;
3184    Torrent * torrent;
3185    NSNumber * tempId;
3186    while ((torrent = [torrentEnum nextObject]))
3187    {
3188        torId = [torrent torrentID];
3189       
3190        idEnum = [idlist objectEnumerator];
3191        while ((tempId = [idEnum nextObject]))
3192        {
3193            if ([tempId intValue] == torId)
3194            {
3195                [torrents addObject: torrent];
3196                break;
3197            }
3198        }
3199    }
3200
3201    return torrents;
3202}
3203
3204- (NSArray *) ipcGetTorrentsByHash: (NSArray *) hashlist
3205{
3206    if (!hashlist)
3207        return fTorrents;
3208   
3209    NSMutableArray * torrents = [NSMutableArray array];
3210   
3211    NSEnumerator * torrentEnum = [fTorrents objectEnumerator], * hashEnum;
3212    NSString * torHash, * tempHash;
3213    Torrent * torrent;
3214    while ((torrent = [torrentEnum nextObject]))
3215    {
3216        torHash = [torrent hashString];
3217       
3218        hashEnum = [hashlist objectEnumerator];
3219        while ((tempHash = [hashEnum nextObject]))
3220        {
3221            if ([torHash caseInsensitiveCompare: tempHash] == NSOrderedSame)
3222            {
3223                [torrents addObject: torrent];
3224                break;
3225            }
3226        }
3227    }
3228   
3229    return torrents;
3230}
3231
3232- (BOOL) ipcAddTorrents: (NSArray *) torrents
3233{
3234    int oldCount = [fTorrents count];
3235   
3236    [self openFiles: torrents];
3237   
3238    return [fTorrents count] > oldCount;
3239}
3240
3241- (BOOL) ipcAddTorrentFile: (NSString *) path directory: (NSString *) directory
3242{
3243    int oldCount = [fTorrents count];
3244   
3245    [self openFiles: [NSArray arrayWithObject: path] forcePath: directory ignoreDownloadFolder: NO forceDeleteTorrent: NO];
3246   
3247    return [fTorrents count] > oldCount;
3248}
3249
3250- (BOOL) ipcAddTorrentFileAutostart: (NSString *) path directory: (NSString *) directory autostart: (BOOL) autostart
3251{
3252    /* 'path' is path to torrent file, 'dir' is the directory it
3253       should download it's files to and may be nil, 'autostart' is a
3254       boolean indicating if the torrent should be automatically
3255       started (or queued to start, I guess), should return NO if
3256       torrent fails to load */
3257    return NO;
3258}
3259
3260- (BOOL) ipcAddTorrentData: (NSData *) data directory: (NSString *) directory
3261{
3262    /* 'data' is the contents of a torrent file, 'directory' is the
3263       directory it should download it's files to and may be nil,
3264       should return NO if torrent fails to load */
3265    return NO;
3266}
3267
3268- (BOOL) ipcAddTorrentDataAutostart: (NSData *) path directory: (NSString *) directory autostart: (BOOL) autostart
3269{
3270    /* 'data' is the contents of a torrent file, 'directory' is the
3271       directory it should download it's files to and may be nil,
3272       'autostart' is a boolean indicating if the torrent should be
3273       automatically started (or queued to start, I guess), should
3274       return NO if torrent fails to load */
3275    return NO;
3276}
3277
3278- (BOOL) ipcStartTorrents: (NSArray *) torrents
3279{
3280    if (!torrents)
3281        [self resumeAllTorrents: self];
3282    else
3283        [self resumeTorrents: torrents];
3284
3285    return YES;
3286}
3287
3288- (BOOL) ipcStopTorrents: (NSArray *) torrents
3289{
3290    if (!torrents)
3291        [self stopAllTorrents: self];
3292    else
3293        [self stopTorrents: torrents];
3294
3295    return YES;
3296}
3297
3298- (BOOL) ipcRemoveTorrents: (NSArray *) torrents
3299{
3300    if (!torrents)
3301        torrents = [NSArray arrayWithArray: fTorrents];
3302
3303    [self confirmRemoveTorrents: torrents deleteData: NO deleteTorrent: NO];
3304
3305    return YES;
3306}
3307
3308@end
Note: See TracBrowser for help on using the repository browser.