source: trunk/macosx/Controller.m @ 4264

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

use an x for "no group"

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