source: trunk/macosx/Controller.m @ 4243

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

better resizing of the filter buttons

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