source: trunk/macosx/Controller.m @ 3097

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

update the status bar to look better on Leopard...if anyone can do the background image better please get in touch with me

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