source: trunk/macosx/Controller.m @ 4871

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

simplify the code for adding group dividers

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