source: trunk/macosx/Controller.m @ 3083

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

this better work

  • Property svn:keywords set to Date Rev Author Id
File size: 126.6 KB
Line 
1/******************************************************************************
2 * $Id: Controller.m 3083 2007-09-16 01:02:06Z 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#warning bindings?
1713- (void) setSort: (id) sender
1714{
1715    NSString * oldSortType = [fDefaults stringForKey: @"Sort"];
1716   
1717    //get checked items
1718    NSMenuItem * prevSortItem, * prevSortActionItem;
1719    if ([oldSortType isEqualToString: SORT_NAME])
1720    {
1721        prevSortItem = fNameSortItem;
1722        prevSortActionItem = fNameSortActionItem;
1723    }
1724    else if ([oldSortType isEqualToString: SORT_STATE])
1725    {
1726        prevSortItem = fStateSortItem;
1727        prevSortActionItem = fStateSortActionItem;
1728    }
1729    else if ([oldSortType isEqualToString: SORT_PROGRESS])
1730    {
1731        prevSortItem = fProgressSortItem;
1732        prevSortActionItem = fProgressSortActionItem;
1733    }
1734    else if ([oldSortType isEqualToString: SORT_TRACKER])
1735    {
1736        prevSortItem = fTrackerSortItem;
1737        prevSortActionItem = fTrackerSortActionItem;
1738    }
1739    else if ([oldSortType isEqualToString: SORT_ORDER])
1740    {
1741        prevSortItem = fOrderSortItem;
1742        prevSortActionItem = fOrderSortActionItem;
1743    }
1744    else
1745    {
1746        prevSortItem = fDateSortItem;
1747        prevSortActionItem = fDateSortActionItem;
1748    }
1749   
1750    if (sender != prevSortItem && sender != prevSortActionItem)
1751    {
1752        //get new items to check
1753        NSMenuItem * currentSortItem, * currentSortActionItem;
1754        NSString * sortType;
1755        if (sender == fNameSortItem || sender == fNameSortActionItem)
1756        {
1757            currentSortItem = fNameSortItem;
1758            currentSortActionItem = fNameSortActionItem;
1759            sortType = SORT_NAME;
1760        }
1761        else if (sender == fStateSortItem || sender == fStateSortActionItem)
1762        {
1763            currentSortItem = fStateSortItem;
1764            currentSortActionItem = fStateSortActionItem;
1765            sortType = SORT_STATE;
1766        }
1767        else if (sender == fProgressSortItem || sender == fProgressSortActionItem)
1768        {
1769            currentSortItem = fProgressSortItem;
1770            currentSortActionItem = fProgressSortActionItem;
1771            sortType = SORT_PROGRESS;
1772        }
1773        else if (sender == fTrackerSortItem || sender == fTrackerSortActionItem)
1774        {
1775            currentSortItem = fTrackerSortItem;
1776            currentSortActionItem = fTrackerSortActionItem;
1777            sortType = SORT_TRACKER;
1778        }
1779        else if (sender == fOrderSortItem || sender == fOrderSortActionItem)
1780        {
1781            currentSortItem = fOrderSortItem;
1782            currentSortActionItem = fOrderSortActionItem;
1783            sortType = SORT_ORDER;
1784           
1785            [fDefaults setBool: NO forKey: @"SortReverse"];
1786        }
1787        else
1788        {
1789            currentSortItem = fDateSortItem;
1790            currentSortActionItem = fDateSortActionItem;
1791            sortType = SORT_DATE;
1792        }
1793       
1794        [fDefaults setObject: sortType forKey: @"Sort"];
1795   
1796        [prevSortItem setState: NSOffState];
1797        [prevSortActionItem setState: NSOffState];
1798        [currentSortItem setState: NSOnState];
1799        [currentSortActionItem setState: NSOnState];
1800    }
1801
1802    [self sortTorrents];
1803}
1804
1805- (void) setSortReverse: (id) sender
1806{
1807    [self sortTorrents];
1808}
1809
1810- (void) applyFilter: (id) sender
1811{
1812    //remember selected rows if needed
1813    NSArray * selectedTorrents = [fTableView numberOfSelectedRows] > 0
1814                ? [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] : nil;
1815
1816    NSMutableArray * tempTorrents = [[NSMutableArray alloc] initWithCapacity: [fTorrents count]];
1817   
1818    NSString * filterType = [fDefaults stringForKey: @"Filter"];
1819   
1820    int downloading = 0, seeding = 0, paused = 0;
1821    BOOL isDownload = [filterType isEqualToString: FILTER_DOWNLOAD],
1822            isSeed = [filterType isEqualToString: FILTER_SEED],
1823            isPause = [filterType isEqualToString: FILTER_PAUSE];
1824    BOOL filtering = isDownload || isSeed || isPause;
1825   
1826    //get count of each type
1827    NSEnumerator * enumerator = [fTorrents objectEnumerator];
1828    Torrent * torrent;
1829    while ((torrent = [enumerator nextObject]))
1830    {
1831        if ([torrent isActive])
1832        {
1833            if ([torrent isSeeding])
1834            {
1835                seeding++;
1836                if (isSeed)
1837                    [tempTorrents addObject: torrent];
1838            }
1839            else
1840            {
1841                downloading++;
1842                if (isDownload)
1843                    [tempTorrents addObject: torrent];
1844            }
1845        }
1846        else
1847        {
1848            paused++;
1849            if (isPause)
1850                [tempTorrents addObject: torrent];
1851        }
1852    }
1853   
1854    if (!filtering)
1855        [tempTorrents setArray: fTorrents];
1856   
1857    //set buttons with counts
1858    [fNoFilterButton setCount: [fTorrents count]];
1859    [fDownloadFilterButton setCount: downloading];
1860    [fSeedFilterButton setCount: seeding];
1861    [fPauseFilterButton setCount: paused];
1862   
1863    NSString * searchString = [fSearchFilterField stringValue];
1864    if ([searchString length] > 0)
1865    {
1866        filtering = YES;
1867       
1868        #warning check multiple trackers
1869        NSString * filterType = [fDefaults stringForKey: @"FilterSearchType"];
1870        NSString * fullString;
1871        Torrent * torrent;
1872       
1873        int i;
1874        for (i = [tempTorrents count]-1; i >= 0; i--)
1875        {
1876            torrent = [tempTorrents objectAtIndex: i];
1877            if ([filterType isEqualToString: FILTER_TYPE_TRACKER])
1878                fullString = [torrent trackerAddress];
1879            else
1880                fullString = [torrent name];
1881           
1882            if ([fullString rangeOfString: searchString options: NSCaseInsensitiveSearch].location == NSNotFound)
1883                [tempTorrents removeObjectAtIndex: i];
1884        }
1885    }
1886   
1887    [fDisplayedTorrents setArray: tempTorrents];
1888    [tempTorrents release];
1889   
1890    [self sortTorrentsIgnoreSelected];
1891   
1892    //set selected rows if needed
1893    if (selectedTorrents)
1894    {
1895        Torrent * torrent;
1896        NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
1897        NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
1898        unsigned index;
1899        while ((torrent = [enumerator nextObject]))
1900            if ((index = [fDisplayedTorrents indexOfObject: torrent]) != NSNotFound)
1901                [indexSet addIndex: index];
1902       
1903        [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
1904        [indexSet release];
1905    }
1906   
1907    //set status bar torrent count text
1908    NSMutableString * totalTorrentsString = [[NSMutableString alloc] initWithString: @""];
1909    if (filtering)
1910        [totalTorrentsString appendFormat: @"%d/", [fDisplayedTorrents count]];
1911   
1912    int totalCount = [fTorrents count];
1913    if (totalCount != 1)
1914        [totalTorrentsString appendFormat: NSLocalizedString(@"%d Transfers", "Status bar transfer count"), totalCount];
1915    else
1916        [totalTorrentsString appendString: NSLocalizedString(@"1 Transfer", "Status bar transfer count")];
1917   
1918    [fTotalTorrentsField setStringValue: totalTorrentsString];
1919    [totalTorrentsString release];
1920
1921    [self setWindowSizeToFit];
1922}
1923
1924//resets filter and sorts torrents
1925- (void) setFilter: (id) sender
1926{
1927    NSString * oldFilterType = [fDefaults stringForKey: @"Filter"];
1928   
1929    FilterBarButton * prevFilterButton;
1930    if ([oldFilterType isEqualToString: FILTER_PAUSE])
1931        prevFilterButton = fPauseFilterButton;
1932    else if ([oldFilterType isEqualToString: FILTER_SEED])
1933        prevFilterButton = fSeedFilterButton;
1934    else if ([oldFilterType isEqualToString: FILTER_DOWNLOAD])
1935        prevFilterButton = fDownloadFilterButton;
1936    else
1937        prevFilterButton = fNoFilterButton;
1938   
1939    if (sender != prevFilterButton)
1940    {
1941        [prevFilterButton setState: NSOffState];
1942        [sender setState: NSOnState];
1943
1944        NSString * filterType;
1945        if (sender == fDownloadFilterButton)
1946            filterType = FILTER_DOWNLOAD;
1947        else if (sender == fPauseFilterButton)
1948            filterType = FILTER_PAUSE;
1949        else if (sender == fSeedFilterButton)
1950            filterType = FILTER_SEED;
1951        else
1952            filterType = FILTER_NONE;
1953
1954        [fDefaults setObject: filterType forKey: @"Filter"];
1955    }
1956
1957    [self applyFilter: nil];
1958}
1959
1960- (void) setFilterSearchType: (id) sender
1961{
1962    NSString * oldFilterType = [fDefaults stringForKey: @"FilterSearchType"];
1963   
1964    int prevTag, currentTag = [sender tag];
1965    if ([oldFilterType isEqualToString: FILTER_TYPE_TRACKER])
1966        prevTag = FILTER_TYPE_TAG_TRACKER;
1967    else
1968        prevTag = FILTER_TYPE_TAG_NAME;
1969   
1970    if (currentTag != prevTag)
1971    {
1972        NSString * filterType;
1973        if (currentTag == FILTER_TYPE_TAG_TRACKER)
1974            filterType = FILTER_TYPE_TRACKER;
1975        else
1976            filterType = FILTER_TYPE_NAME;
1977       
1978        [fDefaults setObject: filterType forKey: @"FilterSearchType"];
1979       
1980        [[fSearchFilterField cell] setPlaceholderString: [sender title]];
1981    }
1982   
1983    [self applyFilter: nil];
1984}
1985
1986- (void) switchFilter: (id) sender
1987{
1988    NSString * filterType = [fDefaults stringForKey: @"Filter"];
1989   
1990    FilterBarButton * button;
1991    if ([filterType isEqualToString: FILTER_NONE])
1992        button = sender == fNextFilterItem ? fDownloadFilterButton : fPauseFilterButton;
1993    else if ([filterType isEqualToString: FILTER_DOWNLOAD])
1994        button = sender == fNextFilterItem ? fSeedFilterButton : fNoFilterButton;
1995    else if ([filterType isEqualToString: FILTER_SEED])
1996        button = sender == fNextFilterItem ? fPauseFilterButton : fDownloadFilterButton;
1997    else if ([filterType isEqualToString: FILTER_PAUSE])
1998        button = sender == fNextFilterItem ? fNoFilterButton : fSeedFilterButton;
1999    else
2000        button = fNoFilterButton;
2001   
2002    [self setFilter: button];
2003}
2004
2005- (void) updateControlTint: (NSNotification *) notification
2006{
2007    if ([fDefaults boolForKey: @"SpeedLimit"])
2008        [fSpeedLimitButton setImage: [NSColor currentControlTint] == NSBlueControlTint
2009            ? [NSImage imageNamed: @"SpeedLimitButtonBlue.png"] : [NSImage imageNamed: @"SpeedLimitButtonGraphite.png"]];
2010}
2011
2012- (void) applySpeedLimit: (id) sender
2013{
2014    [fPrefsController applySpeedSettings: nil];
2015}
2016
2017- (void) toggleSpeedLimit: (id) sender
2018{
2019    [fDefaults setBool: ![fDefaults boolForKey: @"SpeedLimit"] forKey: @"SpeedLimit"];
2020    [self applySpeedLimit: nil];
2021}
2022
2023- (void) autoSpeedLimitChange: (NSNotification *) notification
2024{
2025    if (![fDefaults boolForKey: @"SpeedLimitAuto"])
2026        return;
2027 
2028    NSCalendarDate * onDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
2029                        [[fDefaults objectForKey: @"SpeedLimitAutoOnDate"] timeIntervalSinceReferenceDate]],
2030        * offDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
2031                        [[fDefaults objectForKey: @"SpeedLimitAutoOffDate"] timeIntervalSinceReferenceDate]],
2032        * nowDate = [NSCalendarDate calendarDate];
2033   
2034    //check if should be on if within range
2035    BOOL shouldBeOn;
2036   
2037    int onTime = [onDate hourOfDay] * 60 + [onDate minuteOfHour],
2038        offTime = [offDate hourOfDay] * 60 + [offDate minuteOfHour],
2039        nowTime = [nowDate hourOfDay] * 60 + [nowDate minuteOfHour];
2040   
2041    if (onTime == offTime)
2042        shouldBeOn = NO;
2043    else if (onTime < offTime)
2044        shouldBeOn = onTime <= nowTime && nowTime < offTime;
2045    else
2046        shouldBeOn = onTime <= nowTime || nowTime < offTime;
2047   
2048    if ([fDefaults boolForKey: @"SpeedLimit"] != shouldBeOn)
2049        [self toggleSpeedLimit: nil];
2050}
2051
2052- (void) autoSpeedLimit
2053{
2054    if (![fDefaults boolForKey: @"SpeedLimitAuto"])
2055        return;
2056   
2057    //only toggle if within first few seconds of minutes
2058    NSCalendarDate * nowDate = [NSCalendarDate calendarDate];
2059    if ([nowDate secondOfMinute] > AUTO_SPEED_LIMIT_SECONDS)
2060        return;
2061   
2062    NSCalendarDate * offDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
2063                        [[fDefaults objectForKey: @"SpeedLimitAutoOffDate"] timeIntervalSinceReferenceDate]];
2064   
2065    BOOL toggle;
2066    if ([fDefaults boolForKey: @"SpeedLimit"])
2067        toggle = [nowDate hourOfDay] == [offDate hourOfDay] && [nowDate minuteOfHour] == [offDate minuteOfHour];
2068    else
2069    {
2070        NSCalendarDate * onDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:
2071                        [[fDefaults objectForKey: @"SpeedLimitAutoOnDate"] timeIntervalSinceReferenceDate]];
2072        toggle = ([nowDate hourOfDay] == [onDate hourOfDay] && [nowDate minuteOfHour] == [onDate minuteOfHour])
2073                    && !([onDate hourOfDay] == [offDate hourOfDay] && [onDate minuteOfHour] == [offDate minuteOfHour]);
2074    }
2075   
2076    if (toggle)
2077    {
2078        [self toggleSpeedLimit: nil];
2079       
2080        [GrowlApplicationBridge notifyWithTitle: [fDefaults boolForKey: @"SpeedLimit"]
2081                ? NSLocalizedString(@"Speed Limit Auto Enabled", "Growl notification title")
2082                : NSLocalizedString(@"Speed Limit Auto Disabled", "Growl notification title")
2083            description: NSLocalizedString(@"Bandwidth settings changed", "Growl notification description")
2084            notificationName: GROWL_AUTO_SPEED_LIMIT iconData: nil priority: 0 isSticky: NO clickContext: nil];
2085    }
2086}
2087
2088- (void) setLimitGlobalEnabled: (id) sender
2089{
2090    [fPrefsController applySpeedSettings: nil];
2091}
2092
2093- (void) setQuickLimitGlobal: (id) sender
2094{
2095    [fDefaults setInteger: [[sender title] intValue] forKey: [sender menu] == fUploadMenu ? @"UploadLimit" : @"DownloadLimit"];
2096    [fDefaults setBool: YES forKey: [sender menu] == fUploadMenu ? @"CheckUpload" : @"CheckDownload"];
2097   
2098    [fPrefsController updateLimitFields];
2099    [fPrefsController applySpeedSettings: nil];
2100}
2101
2102- (void) setQuickRatioGlobal: (id) sender
2103{
2104    [fDefaults setBool: YES forKey: @"RatioCheck"];
2105    [fDefaults setFloat: [[sender title] floatValue] forKey: @"RatioLimit"];
2106   
2107    [fPrefsController updateRatioStopField];
2108}
2109
2110- (void) torrentStoppedForRatio: (NSNotification *) notification
2111{
2112    Torrent * torrent = [notification object];
2113   
2114    [self updateTorrentsInQueue];
2115    [fInfoController updateInfoStats];
2116   
2117    if ([fDefaults boolForKey: @"PlaySeedingSound"])
2118    {
2119        NSSound * sound;
2120        if ((sound = [NSSound soundNamed: [fDefaults stringForKey: @"SeedingSound"]]))
2121            [sound play];
2122    }
2123   
2124    NSDictionary * clickContext = [NSDictionary dictionaryWithObjectsAndKeys: GROWL_SEEDING_COMPLETE, @"Type",
2125                                    [torrent dataLocation], @"Location", nil];
2126    [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Seeding Complete", "Growl notification title")
2127                        description: [torrent name] notificationName: GROWL_SEEDING_COMPLETE
2128                        iconData: nil priority: 0 isSticky: NO clickContext: clickContext];
2129}
2130
2131-(void) watcher: (id<UKFileWatcher>) watcher receivedNotification: (NSString *) notification forPath: (NSString *) path
2132{
2133    if ([notification isEqualToString: UKFileWatcherWriteNotification])
2134    {
2135        if (![fDefaults boolForKey: @"AutoImport"] || ![fDefaults stringForKey: @"AutoImportDirectory"])
2136            return;
2137       
2138        if (fAutoImportTimer)
2139        {
2140            if ([fAutoImportTimer isValid])
2141                [fAutoImportTimer invalidate];
2142            [fAutoImportTimer release];
2143            fAutoImportTimer = nil;
2144        }
2145       
2146        //check again in 10 seconds in case torrent file wasn't complete
2147        fAutoImportTimer = [[NSTimer scheduledTimerWithTimeInterval: 10.0 target: self
2148            selector: @selector(checkAutoImportDirectory) userInfo: nil repeats: NO] retain];
2149       
2150        [self checkAutoImportDirectory];
2151    }
2152}
2153
2154- (void) changeAutoImport
2155{
2156    if (fAutoImportTimer)
2157    {
2158        if ([fAutoImportTimer isValid])
2159            [fAutoImportTimer invalidate];
2160        [fAutoImportTimer release];
2161        fAutoImportTimer = nil;
2162    }
2163   
2164    if (fAutoImportedNames)
2165        [fAutoImportedNames removeAllObjects];
2166    [self checkAutoImportDirectory];
2167}
2168
2169- (void) checkAutoImportDirectory
2170{
2171    NSString * path;
2172    if (![fDefaults boolForKey: @"AutoImport"] || !(path = [fDefaults stringForKey: @"AutoImportDirectory"]))
2173        return;
2174   
2175    path = [path stringByExpandingTildeInPath];
2176   
2177    NSArray * importedNames;
2178    if (!(importedNames = [[NSFileManager defaultManager] directoryContentsAtPath: path]))
2179        return;
2180   
2181    //only check files that have not been checked yet
2182    NSMutableArray * newNames = [importedNames mutableCopy];
2183   
2184    if (fAutoImportedNames)
2185        [newNames removeObjectsInArray: fAutoImportedNames];
2186    else
2187        fAutoImportedNames = [[NSMutableArray alloc] init];
2188    [fAutoImportedNames setArray: importedNames];
2189   
2190    NSString * file;
2191    int i;
2192    for (i = [newNames count] - 1; i >= 0; i--)
2193    {
2194        file = [newNames objectAtIndex: i];
2195        if ([[file pathExtension] caseInsensitiveCompare: @"torrent"] != NSOrderedSame)
2196            [newNames removeObjectAtIndex: i];
2197        else
2198            [newNames replaceObjectAtIndex: i withObject: [path stringByAppendingPathComponent: file]];
2199    }
2200   
2201    BOOL ask = [[fDefaults stringForKey: @"DownloadChoice"] isEqualToString: @"Ask"];
2202   
2203    NSEnumerator * enumerator = [newNames objectEnumerator];
2204    int canAdd, count;
2205    while ((file = [enumerator nextObject]))
2206    {
2207        canAdd = tr_torrentParse(fLib, [file UTF8String], NULL, NULL);
2208        if (canAdd == TR_OK)
2209        {
2210            if (!ask)
2211                count = [fTorrents count];
2212            [self openFiles: [NSArray arrayWithObject: file]];
2213           
2214            //check if torrent was opened
2215            if (!ask && [fTorrents count] > count)
2216                [GrowlApplicationBridge notifyWithTitle: NSLocalizedString(@"Torrent File Auto Added",
2217                    "Growl notification title") description: [file lastPathComponent]
2218                    notificationName: GROWL_AUTO_ADD iconData: nil priority: 0 isSticky: NO clickContext: nil];
2219        }
2220        else if (canAdd == TR_EINVALID)
2221            [fAutoImportedNames removeObject: [file lastPathComponent]];
2222        else;
2223    }
2224   
2225    [newNames release];
2226}
2227
2228- (void) beginCreateFile: (NSNotification *) notification
2229{
2230    if (![fDefaults boolForKey: @"AutoImport"])
2231        return;
2232   
2233    NSString * location = [notification object],
2234            * path = [fDefaults stringForKey: @"AutoImportDirectory"];
2235   
2236    if (location && path && [[[location stringByDeletingLastPathComponent] stringByExpandingTildeInPath]
2237                                    isEqualToString: [path stringByExpandingTildeInPath]])
2238        [fAutoImportedNames addObject: [location lastPathComponent]];
2239}
2240
2241- (int) numberOfRowsInTableView: (NSTableView *) tableview
2242{
2243    return [fDisplayedTorrents count];
2244}
2245
2246- (BOOL) tableView: (NSTableView *) tableView writeRowsWithIndexes: (NSIndexSet *) indexes
2247    toPasteboard: (NSPasteboard *) pasteboard
2248{
2249    //only allow reordering of rows if sorting by order with no filter
2250    if ([[fDefaults stringForKey: @"Sort"] isEqualToString: @"Order"]
2251        && [[fDefaults stringForKey: @"Filter"] isEqualToString: @"None"]
2252        && [[fSearchFilterField stringValue] length] == 0)
2253    {
2254        [pasteboard declareTypes: [NSArray arrayWithObject: TORRENT_TABLE_VIEW_DATA_TYPE] owner: self];
2255        [pasteboard setData: [NSKeyedArchiver archivedDataWithRootObject: indexes]
2256                                forType: TORRENT_TABLE_VIEW_DATA_TYPE];
2257        return YES;
2258    }
2259    return NO;
2260}
2261
2262- (NSDragOperation) tableView: (NSTableView *) t validateDrop: (id <NSDraggingInfo>) info
2263    proposedRow: (int) row proposedDropOperation: (NSTableViewDropOperation) operation
2264{
2265    NSPasteboard * pasteboard = [info draggingPasteboard];
2266    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2267    {
2268        [fTableView setDropRow: row dropOperation: NSTableViewDropAbove];
2269        return NSDragOperationGeneric;
2270    }
2271    else;
2272   
2273    return NSDragOperationNone;
2274}
2275
2276- (BOOL) tableView: (NSTableView *) t acceptDrop: (id <NSDraggingInfo>) info
2277    row: (int) newRow dropOperation: (NSTableViewDropOperation) operation
2278{
2279    NSPasteboard * pasteboard = [info draggingPasteboard];
2280    if ([[pasteboard types] containsObject: TORRENT_TABLE_VIEW_DATA_TYPE])
2281    {
2282        //remember selected rows if needed
2283        NSArray * selectedTorrents = nil;
2284        int numSelected = [fTableView numberOfSelectedRows];
2285        if (numSelected > 0 && numSelected < [fDisplayedTorrents count])
2286            selectedTorrents = [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]];
2287   
2288        NSIndexSet * indexes = [NSKeyedUnarchiver unarchiveObjectWithData:
2289                                [pasteboard dataForType: TORRENT_TABLE_VIEW_DATA_TYPE]];
2290       
2291        //move torrent in array
2292        NSArray * movingTorrents = [[fDisplayedTorrents objectsAtIndexes: indexes] retain];
2293        [fDisplayedTorrents removeObjectsInArray: movingTorrents];
2294       
2295        //determine the insertion index now that transfers to move have been removed
2296        int i, decrease = 0;
2297        for (i = [indexes firstIndex]; i < newRow && i != NSNotFound; i = [indexes indexGreaterThanIndex: i])
2298            decrease++;
2299       
2300        //insert objects at new location
2301        for (i = 0; i < [movingTorrents count]; i++)
2302            [fDisplayedTorrents insertObject: [movingTorrents objectAtIndex: i] atIndex: newRow - decrease + i];
2303       
2304        [movingTorrents release];
2305       
2306        //redo order values
2307        int low = [indexes firstIndex], high = [indexes lastIndex];
2308        if (newRow < low)
2309            low = newRow;
2310        else if (newRow > high + 1)
2311            high = newRow - 1;
2312        else;
2313       
2314        for (i = low; i <= high; i++)
2315            [[fDisplayedTorrents objectAtIndex: i] setOrderValue: i];
2316       
2317        [fTableView reloadData];
2318       
2319        //set selected rows if needed
2320        if (selectedTorrents)
2321        {
2322            Torrent * torrent;
2323            NSEnumerator * enumerator = [selectedTorrents objectEnumerator];
2324            NSMutableIndexSet * indexSet = [[NSMutableIndexSet alloc] init];
2325            while ((torrent = [enumerator nextObject]))
2326                [indexSet addIndex: [fDisplayedTorrents indexOfObject: torrent]];
2327           
2328            [fTableView selectRowIndexes: indexSet byExtendingSelection: NO];
2329            [indexSet release];
2330        }
2331    }
2332    else;
2333   
2334    return YES;
2335}
2336
2337- (void) torrentTableViewSelectionDidChange: (NSNotification *) notification
2338{
2339    [fInfoController updateInfoForTorrents: [fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]]];
2340}
2341
2342- (NSDragOperation) draggingEntered: (id <NSDraggingInfo>) info
2343{
2344    NSPasteboard * pasteboard = [info draggingPasteboard];
2345    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2346    {
2347        //check if any torrent files can be added
2348        NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
2349        NSEnumerator * enumerator = [files objectEnumerator];
2350        NSString * file;
2351        BOOL torrent = NO;
2352        int canAdd;
2353        while ((file = [enumerator nextObject]))
2354        {
2355            canAdd = tr_torrentParse(fLib, [file UTF8String], NULL, NULL);
2356            if (canAdd == TR_OK)
2357            {
2358                if (!fOverlayWindow)
2359                    fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2360                [fOverlayWindow setTorrents: files];
2361               
2362                return NSDragOperationCopy;
2363            }
2364            else if (canAdd == TR_EDUPLICATE)
2365                torrent = YES;
2366            else;
2367        }
2368       
2369        //create a torrent file if a single file
2370        if (!torrent && [files count] == 1)
2371        {
2372            if (!fOverlayWindow)
2373                fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2374            [fOverlayWindow setFile: [[files objectAtIndex: 0] lastPathComponent]];
2375           
2376            return NSDragOperationCopy;
2377        }
2378    }
2379    else if ([[pasteboard types] containsObject: NSURLPboardType])
2380    {
2381        if (!fOverlayWindow)
2382            fOverlayWindow = [[DragOverlayWindow alloc] initWithLib: fLib forWindow: fWindow];
2383        [fOverlayWindow setURL: [[NSURL URLFromPasteboard: pasteboard] relativeString]];
2384       
2385        return NSDragOperationCopy;
2386    }
2387    else;
2388   
2389    return NSDragOperationNone;
2390}
2391
2392- (void) draggingExited: (id <NSDraggingInfo>) info
2393{
2394    if (fOverlayWindow)
2395        [fOverlayWindow fadeOut];
2396}
2397
2398- (BOOL) performDragOperation: (id <NSDraggingInfo>) info
2399{
2400    if (fOverlayWindow)
2401        [fOverlayWindow fadeOut];
2402   
2403    NSPasteboard * pasteboard = [info draggingPasteboard];
2404    if ([[pasteboard types] containsObject: NSFilenamesPboardType])
2405    {
2406        BOOL torrent = NO, accept = YES;
2407       
2408        //create an array of files that can be opened
2409        NSMutableArray * filesToOpen = [[NSMutableArray alloc] init];
2410        NSArray * files = [pasteboard propertyListForType: NSFilenamesPboardType];
2411        NSEnumerator * enumerator = [files objectEnumerator];
2412        NSString * file;
2413        int canAdd;
2414        while ((file = [enumerator nextObject]))
2415        {
2416            canAdd = tr_torrentParse(fLib, [file UTF8String], NULL, NULL);
2417            if (canAdd == TR_OK)
2418            {
2419                [filesToOpen addObject: file];
2420                torrent = YES;
2421            }
2422            else if (canAdd == TR_EDUPLICATE)
2423                torrent = YES;
2424            else;
2425        }
2426       
2427        if ([filesToOpen count] > 0)
2428            [self application: NSApp openFiles: filesToOpen];
2429        else
2430        {
2431            if (!torrent && [files count] == 1)
2432                [CreatorWindowController createTorrentFile: fLib forFile: [files objectAtIndex: 0]];
2433            else
2434                accept = NO;
2435        }
2436        [filesToOpen release];
2437       
2438        return accept;
2439    }
2440    else if ([[pasteboard types] containsObject: NSURLPboardType])
2441    {
2442        NSURL * url;
2443        if ((url = [NSURL URLFromPasteboard: pasteboard]))
2444        {
2445            [self openURL: url];
2446            return YES;
2447        }
2448    }
2449    else;
2450   
2451    return NO;
2452}
2453
2454- (void) toggleSmallView: (id) sender
2455{
2456    BOOL makeSmall = [fDefaults boolForKey: @"SmallView"];
2457   
2458    [fTableView setRowHeight: makeSmall ? ROW_HEIGHT_SMALL : ROW_HEIGHT_REGULAR];
2459   
2460    //window min height
2461    NSSize contentMinSize = [fWindow contentMinSize],
2462            contentSize = [[fWindow contentView] frame].size;
2463    contentMinSize.height = contentSize.height - [fScrollView frame].size.height
2464                            + [fTableView rowHeight] + [fTableView intercellSpacing].height;
2465    [fWindow setContentMinSize: contentMinSize];
2466   
2467    //resize for larger min height if not set to auto size
2468    if (![fDefaults boolForKey: @"AutoSize"])
2469    {
2470        if (!makeSmall && contentSize.height < contentMinSize.height)
2471        {
2472            NSRect frame = [fWindow frame];
2473            float heightChange = contentMinSize.height - contentSize.height;
2474            frame.size.height += heightChange;
2475            frame.origin.y -= heightChange;
2476           
2477            [fWindow setFrame: frame display: YES];
2478            [fTableView reloadData];
2479        }
2480    }
2481    else
2482        [self setWindowSizeToFit];
2483}
2484
2485- (void) toggleStatusBar: (id) sender
2486{
2487    [self showStatusBar: [fStatusBar isHidden] animate: YES];
2488    [fDefaults setBool: ![fStatusBar isHidden] forKey: @"StatusBar"];
2489}
2490
2491- (NSRect) windowFrameByAddingHeight: (float) height checkLimits: (BOOL) check
2492{
2493    //convert pixels to points
2494    NSRect windowFrame = [fWindow frame];
2495    NSSize windowSize = [fScrollView convertSize: windowFrame.size fromView: nil];
2496    windowSize.height += height;
2497   
2498    if (check)
2499    {
2500        NSSize minSize = [fScrollView convertSize: [fWindow minSize] fromView: nil];
2501       
2502        if (windowSize.height < minSize.height)
2503            windowSize.height = minSize.height;
2504        else
2505        {
2506            NSSize maxSize = [fScrollView convertSize: [[fWindow screen] visibleFrame].size fromView: nil];
2507            if ([fStatusBar isHidden])
2508                maxSize.height -= [fStatusBar frame].size.height;
2509            if ([fFilterBar isHidden])
2510                maxSize.height -= [fFilterBar frame].size.height;
2511            if (windowSize.height > maxSize.height)
2512                windowSize.height = maxSize.height;
2513        }
2514    }
2515
2516    //convert points to pixels
2517    windowSize = [fScrollView convertSize: windowSize toView: nil];
2518
2519    windowFrame.origin.y -= (windowSize.height - windowFrame.size.height);
2520    windowFrame.size.height = windowSize.height;
2521    return windowFrame;
2522}
2523
2524- (void) showStatusBar: (BOOL) show animate: (BOOL) animate
2525{
2526    if (show != [fStatusBar isHidden])
2527        return;
2528
2529    if (show)
2530        [fStatusBar setHidden: NO];
2531
2532    NSRect frame;
2533    float heightChange = [fStatusBar frame].size.height;
2534    if (!show)
2535        heightChange *= -1;
2536   
2537    //allow bar to show even if not enough room
2538    if (show && ![fDefaults boolForKey: @"AutoSize"])
2539    {
2540        frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2541        float change = [[fWindow screen] visibleFrame].size.height - frame.size.height;
2542        if (change < 0.0)
2543        {
2544            frame = [fWindow frame];
2545            frame.size.height += change;
2546            frame.origin.y -= change;
2547            [fWindow setFrame: frame display: NO animate: NO];
2548        }
2549    }
2550
2551    [self updateUI];
2552   
2553    //set views to not autoresize
2554    unsigned int statsMask = [fStatusBar autoresizingMask];
2555    unsigned int filterMask = [fFilterBar autoresizingMask];
2556    unsigned int scrollMask = [fScrollView autoresizingMask];
2557    [fStatusBar setAutoresizingMask: NSViewNotSizable];
2558    [fFilterBar setAutoresizingMask: NSViewNotSizable];
2559    [fScrollView setAutoresizingMask: NSViewNotSizable];
2560   
2561    frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2562    [fWindow setFrame: frame display: YES animate: animate];
2563   
2564    //re-enable autoresize
2565    [fStatusBar setAutoresizingMask: statsMask];
2566    [fFilterBar setAutoresizingMask: filterMask];
2567    [fScrollView setAutoresizingMask: scrollMask];
2568   
2569    //change min size
2570    NSSize minSize = [fWindow contentMinSize];
2571    minSize.height += heightChange;
2572    [fWindow setContentMinSize: minSize];
2573   
2574    if (!show)
2575        [fStatusBar setHidden: YES];
2576}
2577
2578- (void) toggleFilterBar: (id) sender
2579{
2580    //disable filtering when hiding
2581    if (![fFilterBar isHidden])
2582    {
2583        [fSearchFilterField setStringValue: @""];
2584        [self setFilter: fNoFilterButton];
2585    }
2586
2587    [self showFilterBar: [fFilterBar isHidden] animate: YES];
2588    [fDefaults setBool: ![fFilterBar isHidden] forKey: @"FilterBar"];
2589}
2590
2591- (void) showFilterBar: (BOOL) show animate: (BOOL) animate
2592{
2593    if (show != [fFilterBar isHidden])
2594        return;
2595
2596    if (show)
2597        [fFilterBar setHidden: NO];
2598
2599    NSRect frame;
2600    float heightChange = [fFilterBar frame].size.height;
2601    if (!show)
2602        heightChange *= -1;
2603   
2604    //allow bar to show even if not enough room
2605    if (show && ![fDefaults boolForKey: @"AutoSize"])
2606    {
2607        frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2608        float change = [[fWindow screen] visibleFrame].size.height - frame.size.height;
2609        if (change < 0.0)
2610        {
2611            frame = [fWindow frame];
2612            frame.size.height += change;
2613            frame.origin.y -= change;
2614            [fWindow setFrame: frame display: NO animate: NO];
2615        }
2616    }
2617
2618    //set views to not autoresize
2619    unsigned int filterMask = [fFilterBar autoresizingMask];
2620    unsigned int scrollMask = [fScrollView autoresizingMask];
2621    [fFilterBar setAutoresizingMask: NSViewNotSizable];
2622    [fScrollView setAutoresizingMask: NSViewNotSizable];
2623   
2624    frame = [self windowFrameByAddingHeight: heightChange checkLimits: NO];
2625    [fWindow setFrame: frame display: YES animate: animate];
2626   
2627    //re-enable autoresize
2628    [fFilterBar setAutoresizingMask: filterMask];
2629    [fScrollView setAutoresizingMask: scrollMask];
2630   
2631    //change min size
2632    NSSize minSize = [fWindow contentMinSize];
2633    minSize.height += heightChange;
2634    [fWindow setContentMinSize: minSize];
2635   
2636    if (!show)
2637    {
2638        [fFilterBar setHidden: YES];
2639        [fWindow makeFirstResponder: fTableView];
2640    }
2641}
2642
2643- (void) toggleAdvancedBar: (id) sender
2644{
2645    [fTableView display];
2646}
2647
2648- (void) doNothing: (id) sender {}
2649
2650- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier: (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag
2651{
2652    NSToolbarItem * item = [[NSToolbarItem alloc] initWithItemIdentifier: ident];
2653
2654    if ([ident isEqualToString: TOOLBAR_CREATE])
2655    {
2656        [item setLabel: NSLocalizedString(@"Create", "Create toolbar item -> label")];
2657        [item setPaletteLabel: NSLocalizedString(@"Create Torrent File", "Create toolbar item -> palette label")];
2658        [item setToolTip: NSLocalizedString(@"Create torrent file", "Create toolbar item -> tooltip")];
2659        [item setImage: [NSImage imageNamed: @"Create.png"]];
2660        [item setTarget: self];
2661        [item setAction: @selector(createFile:)];
2662        [item setAutovalidates: NO];
2663    }
2664    else if ([ident isEqualToString: TOOLBAR_OPEN])
2665    {
2666        [item setLabel: NSLocalizedString(@"Open", "Open toolbar item -> label")];
2667        [item setPaletteLabel: NSLocalizedString(@"Open Torrent Files", "Open toolbar item -> palette label")];
2668        [item setToolTip: NSLocalizedString(@"Open torrent files", "Open toolbar item -> tooltip")];
2669        [item setImage: [NSImage imageNamed: @"Open.png"]];
2670        [item setTarget: self];
2671        [item setAction: @selector(openShowSheet:)];
2672        [item setAutovalidates: NO];
2673    }
2674    else if ([ident isEqualToString: TOOLBAR_REMOVE])
2675    {
2676        [item setLabel: NSLocalizedString(@"Remove", "Remove toolbar item -> label")];
2677        [item setPaletteLabel: NSLocalizedString(@"Remove Selected", "Remove toolbar item -> palette label")];
2678        [item setToolTip: NSLocalizedString(@"Remove selected transfers", "Remove toolbar item -> tooltip")];
2679        [item setImage: [NSImage imageNamed: @"Remove.png"]];
2680        [item setTarget: self];
2681        [item setAction: @selector(removeNoDelete:)];
2682    }
2683    else if ([ident isEqualToString: TOOLBAR_INFO])
2684    {
2685        [item setLabel: NSLocalizedString(@"Inspector", "Inspector toolbar item -> label")];
2686        [item setPaletteLabel: NSLocalizedString(@"Toggle Inspector", "Inspector toolbar item -> palette label")];
2687        [item setToolTip: NSLocalizedString(@"Toggle the torrent inspector", "Inspector toolbar item -> tooltip")];
2688        [item setImage: [NSImage imageNamed: @"Info.png"]];
2689        [item setTarget: self];
2690        [item setAction: @selector(showInfo:)];
2691        [item setAutovalidates: NO];
2692    }
2693    else if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
2694    {
2695        [item setLabel: NSLocalizedString(@"Pause All", "Pause All toolbar item -> label")];
2696        [item setPaletteLabel: [item label]];
2697        [item setToolTip: NSLocalizedString(@"Pause all transfers", "Pause All toolbar item -> tooltip")];
2698        [item setImage: [NSImage imageNamed: @"PauseAll.png"]];
2699        [item setTarget: self];
2700        [item setAction: @selector(stopAllTorrents:)];
2701    }
2702    else if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
2703    {
2704        [item setLabel: NSLocalizedString(@"Resume All", "Resume All toolbar item -> label")];
2705        [item setPaletteLabel: [item label]];
2706        [item setToolTip: NSLocalizedString(@"Resume all transfers", "Resume All toolbar item -> tooltip")];
2707        [item setImage: [NSImage imageNamed: @"ResumeAll.png"]];
2708        [item setTarget: self];
2709        [item setAction: @selector(resumeAllTorrents:)];
2710    }
2711    else if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
2712    {
2713        [item setLabel: NSLocalizedString(@"Pause", "Pause toolbar item -> label")];
2714        [item setPaletteLabel: NSLocalizedString(@"Pause Selected", "Pause toolbar item -> palette label")];
2715        [item setToolTip: NSLocalizedString(@"Pause selected transfers", "Pause toolbar item -> tooltip")];
2716        [item setImage: [NSImage imageNamed: @"PauseSelected.png"]];
2717        [item setTarget: self];
2718        [item setAction: @selector(stopSelectedTorrents:)];
2719    }
2720    else if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
2721    {
2722        [item setLabel: NSLocalizedString(@"Resume", "Resume toolbar item -> label")];
2723        [item setPaletteLabel: NSLocalizedString(@"Resume Selected", "Resume toolbar item -> palette label")];
2724        [item setToolTip: NSLocalizedString(@"Resume selected transfers", "Resume toolbar item -> tooltip")];
2725        [item setImage: [NSImage imageNamed: @"ResumeSelected.png"]];
2726        [item setTarget: self];
2727        [item setAction: @selector(resumeSelectedTorrents:)];
2728    }
2729    else if ([ident isEqualToString: TOOLBAR_FILTER])
2730    {
2731        [item setLabel: NSLocalizedString(@"Filter", "Filter toolbar item -> label")];
2732        [item setPaletteLabel: NSLocalizedString(@"Toggle Filter", "Filter toolbar item -> palette label")];
2733        [item setToolTip: NSLocalizedString(@"Toggle the filter bar", "Filter toolbar item -> tooltip")];
2734        [item setImage: [NSImage imageNamed: @"Filter.png"]];
2735        [item setTarget: self];
2736        [item setAction: @selector(toggleFilterBar:)];
2737        [item setAutovalidates: NO];
2738    }
2739    else
2740    {
2741        [item release];
2742        return nil;
2743    }
2744
2745    return [item autorelease];
2746}
2747
2748- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t
2749{
2750    return [NSArray arrayWithObjects:
2751            TOOLBAR_CREATE, TOOLBAR_OPEN, TOOLBAR_REMOVE,
2752            TOOLBAR_PAUSE_SELECTED, TOOLBAR_RESUME_SELECTED,
2753            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL, TOOLBAR_FILTER, TOOLBAR_INFO,
2754            NSToolbarSeparatorItemIdentifier,
2755            NSToolbarSpaceItemIdentifier,
2756            NSToolbarFlexibleSpaceItemIdentifier,
2757            NSToolbarCustomizeToolbarItemIdentifier, nil];
2758}
2759
2760- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t
2761{
2762    return [NSArray arrayWithObjects:
2763            TOOLBAR_CREATE, TOOLBAR_OPEN, TOOLBAR_REMOVE,
2764            NSToolbarSeparatorItemIdentifier,
2765            TOOLBAR_PAUSE_ALL, TOOLBAR_RESUME_ALL,
2766            NSToolbarFlexibleSpaceItemIdentifier,
2767            TOOLBAR_FILTER, TOOLBAR_INFO, nil];
2768}
2769
2770#warning validateUserInterfaceItem: ???
2771- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem
2772{
2773    NSString * ident = [toolbarItem itemIdentifier];
2774
2775    //enable remove item
2776    if ([ident isEqualToString: TOOLBAR_REMOVE])
2777        return [fTableView numberOfSelectedRows] > 0;
2778
2779    //enable pause all item
2780    if ([ident isEqualToString: TOOLBAR_PAUSE_ALL])
2781    {
2782        Torrent * torrent;
2783        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2784        while ((torrent = [enumerator nextObject]))
2785            if ([torrent isActive] || [torrent waitingToStart])
2786                return YES;
2787        return NO;
2788    }
2789
2790    //enable resume all item
2791    if ([ident isEqualToString: TOOLBAR_RESUME_ALL])
2792    {
2793        Torrent * torrent;
2794        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2795        while ((torrent = [enumerator nextObject]))
2796            if ([torrent isPaused] && ![torrent waitingToStart])
2797                return YES;
2798        return NO;
2799    }
2800
2801    //enable pause item
2802    if ([ident isEqualToString: TOOLBAR_PAUSE_SELECTED])
2803    {
2804        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2805        Torrent * torrent;
2806        while ((torrent = [enumerator nextObject]))
2807            if ([torrent isActive] || [torrent waitingToStart])
2808                return YES;
2809        return NO;
2810    }
2811   
2812    //enable resume item
2813    if ([ident isEqualToString: TOOLBAR_RESUME_SELECTED])
2814    {
2815        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2816        Torrent * torrent;
2817        while ((torrent = [enumerator nextObject]))
2818            if ([torrent isPaused] && ![torrent waitingToStart])
2819                return YES;
2820        return NO;
2821    }
2822
2823    return YES;
2824}
2825
2826- (BOOL) validateMenuItem: (NSMenuItem *) menuItem
2827{
2828    SEL action = [menuItem action];
2829
2830    //only enable some items if it is in a context menu or the window is useable
2831    BOOL canUseTable = [fWindow isKeyWindow] || [[menuItem menu] supermenu] != [NSApp mainMenu];
2832
2833    //enable open items
2834    if (action == @selector(openShowSheet:) || action == @selector(openURLShowSheet:))
2835        return [fWindow attachedSheet] == nil;
2836   
2837    //enable sort and advanced bar items
2838    if (action == @selector(setSort:) || action == @selector(toggleAdvancedBar:) || action == @selector(toggleSmallView:))
2839        return [fWindow isVisible];
2840
2841    //enable show info
2842    if (action == @selector(showInfo:))
2843    {
2844        NSString * title = [[fInfoController window] isVisible] ? NSLocalizedString(@"Hide Inspector",
2845                            "View menu -> Inspector") : NSLocalizedString(@"Show Inspector", "View menu -> Inspector");
2846        if (![[menuItem title] isEqualToString: title])
2847                [menuItem setTitle: title];
2848
2849        return YES;
2850    }
2851   
2852    //enable prev/next inspector tab
2853    if (action == @selector(setInfoTab:))
2854        return [[fInfoController window] isVisible];
2855   
2856    //enable toggle status bar
2857    if (action == @selector(toggleStatusBar:))
2858    {
2859        NSString * title = [fStatusBar isHidden] ? NSLocalizedString(@"Show Status Bar", "View menu -> Status Bar")
2860                            : NSLocalizedString(@"Hide Status Bar", "View menu -> Status Bar");
2861        if (![[menuItem title] isEqualToString: title])
2862            [menuItem setTitle: title];
2863
2864        return [fWindow isVisible];
2865    }
2866   
2867    //enable toggle filter bar
2868    if (action == @selector(toggleFilterBar:))
2869    {
2870        NSString * title = [fFilterBar isHidden] ? NSLocalizedString(@"Show Filter Bar", "View menu -> Filter Bar")
2871                            : NSLocalizedString(@"Hide Filter Bar", "View menu -> Filter Bar");
2872        if (![[menuItem title] isEqualToString: title])
2873            [menuItem setTitle: title];
2874
2875        return [fWindow isVisible];
2876    }
2877   
2878    //enable prev/next filter button
2879    if (action == @selector(switchFilter:))
2880        return [fWindow isVisible] && ![fFilterBar isHidden];
2881
2882    //enable reveal in finder
2883    if (action == @selector(revealFile:))
2884        return canUseTable && [fTableView numberOfSelectedRows] > 0;
2885
2886    //enable remove items
2887    if (action == @selector(removeNoDelete:) || action == @selector(removeDeleteData:)
2888        || action == @selector(removeDeleteTorrent:) || action == @selector(removeDeleteDataAndTorrent:))
2889    {
2890        BOOL warning = NO,
2891            onlyDownloading = [fDefaults boolForKey: @"CheckRemoveDownloading"],
2892            canDelete = action != @selector(removeDeleteTorrent:) && action != @selector(removeDeleteDataAndTorrent:);
2893       
2894        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2895        Torrent * torrent;
2896        while ((torrent = [enumerator nextObject]))
2897        {
2898            if (!warning && [torrent isActive])
2899            {
2900                warning = onlyDownloading ? ![torrent isSeeding] : YES;
2901                if (warning && canDelete)
2902                    break;
2903            }
2904            if (!canDelete && [torrent publicTorrent])
2905            {
2906                canDelete = YES;
2907                if (warning)
2908                    break;
2909            }
2910        }
2911   
2912        //append or remove ellipsis when needed
2913        NSString * title = [menuItem title], * ellipsis = [NSString ellipsis];
2914        if (warning && [fDefaults boolForKey: @"CheckRemove"])
2915        {
2916            if (![title hasSuffix: ellipsis])
2917                [menuItem setTitle: [title stringByAppendingEllipsis]];
2918        }
2919        else
2920        {
2921            if ([title hasSuffix: ellipsis])
2922                [menuItem setTitle: [title substringToIndex: [title rangeOfString: ellipsis].location]];
2923        }
2924       
2925        return canUseTable && canDelete && [fTableView numberOfSelectedRows] > 0;
2926    }
2927
2928    //enable pause all item
2929    if (action == @selector(stopAllTorrents:))
2930    {
2931        Torrent * torrent;
2932        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2933        while ((torrent = [enumerator nextObject]))
2934            if ([torrent isActive] || [torrent waitingToStart])
2935                return YES;
2936        return NO;
2937    }
2938   
2939    //enable resume all item
2940    if (action == @selector(resumeAllTorrents:))
2941    {
2942        Torrent * torrent;
2943        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2944        while ((torrent = [enumerator nextObject]))
2945            if ([torrent isPaused] && ![torrent waitingToStart])
2946                return YES;
2947        return NO;
2948    }
2949   
2950    //enable resume all waiting item
2951    if (action == @selector(resumeWaitingTorrents:))
2952    {
2953        if (![fDefaults boolForKey: @"Queue"] && ![fDefaults boolForKey: @"QueueSeed"])
2954            return NO;
2955   
2956        Torrent * torrent;
2957        NSEnumerator * enumerator = [fTorrents objectEnumerator];
2958        while ((torrent = [enumerator nextObject]))
2959            if ([torrent isPaused] && [torrent waitingToStart])
2960                return YES;
2961        return NO;
2962    }
2963   
2964    //enable resume selected waiting item
2965    if (action == @selector(resumeSelectedTorrentsNoWait:))
2966    {
2967        if (![fDefaults boolForKey: @"Queue"] && ![fDefaults boolForKey: @"QueueSeed"])
2968            return NO;
2969   
2970        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2971        Torrent * torrent;
2972        while ((torrent = [enumerator nextObject]))
2973            if ([torrent isPaused])
2974                return YES;
2975        return NO;
2976    }
2977
2978    //enable pause item
2979    if (action == @selector(stopSelectedTorrents:))
2980    {
2981        if (!canUseTable)
2982            return NO;
2983   
2984        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2985        Torrent * torrent;
2986        while ((torrent = [enumerator nextObject]))
2987            if ([torrent isActive] || [torrent waitingToStart])
2988                return YES;
2989        return NO;
2990    }
2991   
2992    //enable resume item
2993    if (action == @selector(resumeSelectedTorrents:))
2994    {
2995        if (!canUseTable)
2996            return NO;
2997   
2998        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
2999        Torrent * torrent;
3000        while ((torrent = [enumerator nextObject]))
3001            if ([torrent isPaused] && ![torrent waitingToStart])
3002                return YES;
3003        return NO;
3004    }
3005   
3006    //enable manual announce item
3007    if (action == @selector(announceSelectedTorrents:))
3008    {
3009        if (!canUseTable)
3010            return NO;
3011       
3012        NSEnumerator * enumerator = [[fDisplayedTorrents objectsAtIndexes: [fTableView selectedRowIndexes]] objectEnumerator];
3013        Torrent * torrent;
3014        while ((torrent = [enumerator nextObject]))
3015            if ([torrent canManualAnnounce])
3016                return YES;
3017        return NO;
3018    }
3019   
3020    //enable reset cache item
3021    if (action == @selector(resetCacheForSelectedTorrents:))
3022        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3023   
3024    //enable move torrent file item
3025    if (action == @selector(moveDataFiles:))
3026        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3027   
3028    //enable copy torrent file item
3029    if (action == @selector(copyTorrentFiles:))
3030        return canUseTable && [fTableView numberOfSelectedRows] > 0;
3031   
3032    //enable reverse sort item
3033    if (action == @selector(setSortReverse:))
3034        return ![[fDefaults stringForKey: @"Sort"] isEqualToString: @"Order"];
3035   
3036    //check proper filter search item
3037    if (action == @selector(setFilterSearchType:))
3038    {
3039        NSString * filterType = [fDefaults stringForKey: @"FilterSearchType"];
3040       
3041        int tag = [menuItem tag];
3042        BOOL state;
3043        if (tag == FILTER_TYPE_TAG_TRACKER)
3044            state = [filterType isEqualToString: FILTER_TYPE_TRACKER];
3045        else
3046            state = [filterType isEqualToString: FILTER_TYPE_NAME];
3047       
3048        [menuItem setState: state ? NSOnState : NSOffState];
3049        return YES;
3050    }
3051   
3052    return YES;
3053}
3054
3055- (void) sleepCallBack: (natural_t) messageType argument: (void *) messageArgument
3056{
3057    NSEnumerator * enumerator;
3058    Torrent * torrent;
3059    BOOL active, allowSleep;
3060
3061    switch (messageType)
3062    {
3063        case kIOMessageSystemWillSleep:
3064            //close all connections before going to sleep and remember we should resume when we wake up
3065            [fTorrents makeObjectsPerformSelector: @selector(sleep)];
3066
3067            //wait for running transfers to stop (5 second timeout)
3068            NSDate * start = [NSDate date];
3069            BOOL timeUp = NO;
3070           
3071            enumerator = [fTorrents objectEnumerator];
3072            while (!timeUp && (torrent = [enumerator nextObject]))
3073                while (![torrent isPaused] && !(timeUp = [start timeIntervalSinceNow] < -5.0))
3074                {
3075                    usleep(100000);
3076                    [torrent update];
3077                }
3078
3079            IOAllowPowerChange(fRootPort, (long) messageArgument);
3080            break;
3081
3082        case kIOMessageCanSystemSleep:
3083            allowSleep = YES;
3084            if ([fDefaults boolForKey: @"SleepPrevent"])
3085            {
3086                //prevent idle sleep unless no torrents are active
3087                enumerator = [fTorrents objectEnumerator];
3088                while ((torrent = [enumerator nextObject]))
3089                    if ([torrent isActive] && ![torrent isStalled] && ![torrent isError])
3090                    {
3091                        allowSleep = NO;
3092                        break;
3093                    }
3094            }
3095
3096            if (allowSleep)
3097                IOAllowPowerChange(fRootPort, (long) messageArgument);
3098            else
3099                IOCancelPowerChange(fRootPort, (long) messageArgument);
3100            break;
3101
3102        case kIOMessageSystemHasPoweredOn:
3103            //resume sleeping transfers after we wake up
3104            [fTorrents makeObjectsPerformSelector: @selector(wakeUp)];
3105            [self autoSpeedLimitChange: nil];
3106            break;
3107    }
3108}
3109
3110- (NSMenu *) applicationDockMenu: (NSApplication *) sender
3111{
3112    int seeding = 0, downloading = 0;
3113    NSEnumerator * enumerator = [fTorrents objectEnumerator];
3114    Torrent * torrent;
3115    while ((torrent = [enumerator nextObject]))
3116    {
3117        if ([torrent isSeeding])
3118            seeding++;
3119        else if ([torrent isActive])
3120            downloading++;
3121        else;
3122    }
3123   
3124    NSMenuItem * seedingItem = [fDockMenu itemWithTag: DOCK_SEEDING_TAG],
3125            * downloadingItem = [fDockMenu itemWithTag: DOCK_DOWNLOADING_TAG];
3126   
3127    BOOL hasSeparator = seedingItem || downloadingItem;
3128   
3129    if (seeding > 0)
3130    {
3131        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Seeding",
3132                                                        "Dock item - Seeding"), seeding];
3133        if (!seedingItem)
3134        {
3135            seedingItem = [[[NSMenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""] autorelease];
3136            [seedingItem setTag: DOCK_SEEDING_TAG];
3137            [fDockMenu insertItem: seedingItem atIndex: 0];
3138        }
3139        else
3140            [seedingItem setTitle: title];
3141    }
3142    else
3143    {
3144        if (seedingItem)
3145            [fDockMenu removeItem: seedingItem];
3146    }
3147   
3148    if (downloading > 0)
3149    {
3150        NSString * title = [NSString stringWithFormat: NSLocalizedString(@"%d Downloading",
3151                                                        "Dock item - Downloading"), downloading];
3152        if (!downloadingItem)
3153        {
3154            downloadingItem = [[[NSMenuItem alloc] initWithTitle: title action: nil keyEquivalent: @""] autorelease];
3155            [downloadingItem setTag: DOCK_DOWNLOADING_TAG];
3156            [fDockMenu insertItem: downloadingItem atIndex: seeding > 0 ? 1 : 0];
3157        }
3158        else
3159            [downloadingItem setTitle: title];
3160    }
3161    else
3162    {
3163        if (downloadingItem)
3164            [fDockMenu removeItem: downloadingItem];
3165    }
3166   
3167    if (seeding > 0 || downloading > 0)
3168    {
3169        if (!hasSeparator)
3170            [fDockMenu insertItem: [NSMenuItem separatorItem] atIndex: seeding > 0 && downloading > 0 ? 2 : 1];
3171    }
3172    else
3173    {
3174        if (hasSeparator)
3175            [fDockMenu removeItemAtIndex: 0];
3176    }
3177   
3178    return fDockMenu;
3179}
3180
3181- (void) updateDockBadge: (NSNotification *) notification
3182{
3183    [fBadger updateBadge];
3184}
3185
3186- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
3187{
3188    //if auto size is enabled, the current frame shouldn't need to change
3189    NSRect frame = [fDefaults boolForKey: @"AutoSize"] ? [window frame] : [self sizedWindowFrame];
3190   
3191    frame.size.width = [fDefaults boolForKey: @"SmallView"] ? [fWindow minSize].width : WINDOW_REGULAR_WIDTH;
3192    return frame;
3193}
3194
3195- (void) setWindowSizeToFit
3196{
3197    if ([fDefaults boolForKey: @"AutoSize"])
3198    {
3199        [fScrollView setHasVerticalScroller: NO];
3200        [fWindow setFrame: [self sizedWindowFrame] display: YES animate: YES];
3201        [fScrollView setHasVerticalScroller: YES];
3202    }
3203}
3204
3205- (NSRect) sizedWindowFrame
3206{
3207    float heightChange = [fDisplayedTorrents count] * ([fTableView rowHeight] +
3208            [fTableView intercellSpacing].height) - [fScrollView frame].size.height;
3209    return [self windowFrameByAddingHeight: heightChange checkLimits: YES];
3210}
3211
3212- (void) showMainWindow: (id) sender
3213{
3214    [fWindow makeKeyAndOrderFront: nil];
3215}
3216
3217- (void) windowDidBecomeKey: (NSNotification *) notification
3218{
3219    [fBadger clearCompleted];
3220}
3221
3222- (NSSize) windowWillResize: (NSWindow *) sender toSize: (NSSize) proposedFrameSize
3223{
3224    //only resize horizontally if autosize is enabled
3225    if ([fDefaults boolForKey: @"AutoSize"])
3226        proposedFrameSize.height = [fWindow frame].size.height;
3227    return proposedFrameSize;
3228}
3229
3230- (void) windowDidResize: (NSNotification *) notification
3231{
3232    //size search filter to not overlap buttons
3233    float pointX = NSMaxX([fPauseFilterButton frame]) + 5.0;
3234    NSRect oldFrame = [fSearchFilterField frame],
3235            frame = NSMakeRect(pointX, oldFrame.origin.y, NSMaxX(oldFrame) - pointX, oldFrame.size.height);
3236   
3237    BOOL show;
3238    if (show = (frame.size.width >= SEARCH_FILTER_MIN_WIDTH))
3239    {
3240        //make sure it is not too long
3241        if (frame.size.width > SEARCH_FILTER_MAX_WIDTH)
3242        {
3243            float different = frame.size.width - SEARCH_FILTER_MAX_WIDTH;
3244            frame.origin.x += different;
3245            frame.size.width -= different;
3246        }
3247        [fSearchFilterField setFrame: frame];
3248    }
3249   
3250    //hide search filter if it overlaps filter buttons
3251    [fSearchFilterField setHidden: !show];
3252}
3253
3254- (void) linkHomepage: (id) sender
3255{
3256    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: WEBSITE_URL]];
3257}
3258
3259- (void) linkForums: (id) sender
3260{
3261    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: FORUM_URL]];
3262}
3263
3264- (void) linkDonate: (id) sender
3265{
3266    [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: DONATE_URL]];
3267}
3268
3269- (void) prepareForUpdate: (NSNotification *) notification
3270{
3271    fUpdateInProgress = YES;
3272}
3273
3274- (NSDictionary *) registrationDictionaryForGrowl
3275{
3276    NSArray * notifications = [NSArray arrayWithObjects: GROWL_DOWNLOAD_COMPLETE, GROWL_SEEDING_COMPLETE,
3277                                                            GROWL_AUTO_ADD, GROWL_AUTO_SPEED_LIMIT, nil];
3278    return [NSDictionary dictionaryWithObjectsAndKeys: notifications, GROWL_NOTIFICATIONS_ALL,
3279                                notifications, GROWL_NOTIFICATIONS_DEFAULT, nil];
3280}
3281
3282- (void) growlNotificationWasClicked: (id) clickContext
3283{
3284    if (!clickContext || ![clickContext isKindOfClass: [NSDictionary class]])
3285        return;
3286   
3287    NSString * type = [clickContext objectForKey: @"Type"], * location;
3288    if (([type isEqualToString: GROWL_DOWNLOAD_COMPLETE] || [type isEqualToString: GROWL_SEEDING_COMPLETE])
3289            && (location = [clickContext objectForKey: @"Location"]))
3290        [[NSWorkspace sharedWorkspace] selectFile: location inFileViewerRootedAtPath: nil];
3291}
3292
3293#warning move to additions
3294- (NSString *) applicationSupportFolder
3295{
3296    return [[NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex: 0]
3297                stringByAppendingPathComponent: [[NSProcessInfo processInfo] processName]];
3298}
3299
3300- (void) ipcQuit
3301{
3302    fRemoteQuit = YES;
3303    [NSApp terminate: self];
3304}
3305
3306- (NSArray *) ipcGetTorrentsByID: (NSArray *) idlist
3307{
3308    if (!idlist)
3309        return fTorrents;
3310   
3311    NSMutableArray * torrents = [NSMutableArray array];
3312   
3313    NSEnumerator * torrentEnum = [fTorrents objectEnumerator], * idEnum;
3314    int torId;
3315    Torrent * torrent;
3316    NSNumber * tempId;
3317    while ((torrent = [torrentEnum nextObject]))
3318    {
3319        torId = [torrent torrentID];
3320       
3321        idEnum = [idlist objectEnumerator];
3322        while ((tempId = [idEnum nextObject]))
3323        {
3324            if ([tempId intValue] == torId)
3325            {
3326                [torrents addObject: torrent];
3327                break;
3328            }
3329        }
3330    }
3331
3332    return torrents;
3333}
3334
3335- (NSArray *) ipcGetTorrentsByHash: (NSArray *) hashlist
3336{
3337    if (!hashlist)
3338        return fTorrents;
3339   
3340    NSMutableArray * torrents = [NSMutableArray array];
3341   
3342    NSEnumerator * torrentEnum = [fTorrents objectEnumerator], * hashEnum;
3343    NSString * torHash, * tempHash;
3344    Torrent * torrent;
3345    while ((torrent = [torrentEnum nextObject]))
3346    {
3347        torHash = [torrent hashString];
3348       
3349        hashEnum = [hashlist objectEnumerator];
3350        while ((tempHash = [hashEnum nextObject]))
3351        {
3352            if ([torHash caseInsensitiveCompare: tempHash] == NSOrderedSame)
3353            {
3354                [torrents addObject: torrent];
3355                break;
3356            }
3357        }
3358    }
3359   
3360    return torrents;
3361}
3362
3363- (BOOL) ipcAddTorrents: (NSArray *) torrents
3364{
3365    int oldCount = [fTorrents count];
3366   
3367    [self openFiles: torrents];
3368   
3369    return [fTorrents count] > oldCount;
3370}
3371
3372- (BOOL) ipcAddTorrentFile: (NSString *) path directory: (NSString *) directory
3373{
3374    int oldCount = [fTorrents count];
3375   
3376    [self openFiles: [NSArray arrayWithObject: path] forcePath: directory ignoreDownloadFolder: NO
3377            deleteTorrentFile: TORRENT_FILE_DEFAULT];
3378   
3379    return [fTorrents count] > oldCount;
3380}
3381
3382- (BOOL) ipcAddTorrentFileAutostart: (NSString *) path directory: (NSString *) directory autostart: (BOOL) autostart
3383{
3384    /* 'path' is path to torrent file, 'dir' is the directory it
3385       should download it's files to and may be nil, 'autostart' is a
3386       boolean indicating if the torrent should be automatically
3387       started (or queued to start, I guess), should return NO if
3388       torrent fails to load */
3389    return NO;
3390}
3391
3392- (BOOL) ipcAddTorrentData: (NSData *) data directory: (NSString *) directory
3393{
3394    /* 'data' is the contents of a torrent file, 'directory' is the
3395       directory it should download it's files to and may be nil,
3396       should return NO if torrent fails to load */
3397    return NO;
3398}
3399
3400- (BOOL) ipcAddTorrentDataAutostart: (NSData *) path directory: (NSString *) directory autostart: (BOOL) autostart
3401{
3402    /* 'data' is the contents of a torrent file, 'directory' is the
3403       directory it should download it's files to and may be nil,
3404       'autostart' is a boolean indicating if the torrent should be
3405       automatically started (or queued to start, I guess), should
3406       return NO if torrent fails to load */
3407    return NO;
3408}
3409
3410- (BOOL) ipcStartTorrents: (NSArray *) torrents
3411{
3412    if (!torrents)
3413        [self resumeAllTorrents: self];
3414    else
3415        [self resumeTorrents: torrents];
3416
3417    return YES;
3418}
3419
3420- (BOOL) ipcStopTorrents: (NSArray *) torrents
3421{
3422    if (!torrents)
3423        [self stopAllTorrents: self];
3424    else
3425        [self stopTorrents: torrents];
3426
3427    return YES;
3428}
3429
3430- (BOOL) ipcRemoveTorrents: (NSArray *) torrents
3431{
3432    if (!torrents)
3433        torrents = [NSArray arrayWithArray: fTorrents];
3434
3435    [self confirmRemoveTorrents: torrents deleteData: NO deleteTorrent: NO];
3436
3437    return YES;
3438}
3439
3440@end
Note: See TracBrowser for help on using the repository browser.