source: trunk/macosx/Controller.m @ 4225

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

initial group filtering

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