source: trunk/macosx/Controller.m @ 4129

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

attempt to put the arrow commands into interface builder

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