source: trunk/macosx/Controller.m @ 4935

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

initial saving of collapsed/expanded groups when switching filters, adding torrents, etc

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