source: trunk/macosx/Controller.m @ 4150

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

attempt to fix sizing issue on tiger

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