source: trunk/macosx/Controller.m @ 4128

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

use 10.5's built-in ability to generate the textured bottom border instead of a custom view with images

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