1/******************************************************************************
2 * $Id: InfoWindowController.m 13434 2012-08-13 00:52:04Z livings124 $
3 *
4 * Copyright (c) 2006-2012 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 "InfoWindowController.h"
26#import "InfoViewController.h"
27#import "InfoGeneralViewController.h"
28#import "InfoActivityViewController.h"
29#import "InfoTrackersViewController.h"
30#import "InfoPeersViewController.h"
31#import "InfoFileViewController.h"
32#import "InfoOptionsViewController.h"
33#import "InfoTabButtonCell.h"
34#import "NSApplicationAdditions.h"
35#import "NSStringAdditions.h"
36#import "Torrent.h"
37
38#define TAB_INFO_IDENT @"Info"
39#define TAB_ACTIVITY_IDENT @"Activity"
40#define TAB_TRACKER_IDENT @"Tracker"
41#define TAB_PEERS_IDENT @"Peers"
42#define TAB_FILES_IDENT @"Files"
43#define TAB_OPTIONS_IDENT @"Options"
44
45#define TAB_MIN_HEIGHT 250
46
47#define INVALID -99
48
49typedef enum
50{
51    TAB_GENERAL_TAG = 0,
52    TAB_ACTIVITY_TAG = 1,
53    TAB_TRACKERS_TAG = 2,
54    TAB_PEERS_TAG = 3,
55    TAB_FILE_TAG = 4,
56    TAB_OPTIONS_TAG = 5
57} tabTag;
58
59@interface InfoWindowController (Private)
60
61- (void) resetInfo;
62- (void) resetInfoForTorrent: (NSNotification *) notification;
63
64@end
65
66@implementation InfoWindowController
67
68- (id) init
69{
70    self = [super initWithWindowNibName: @"InfoWindow"];
71    return self;
72}
73
74- (void) awakeFromNib
75{
76    [fNoneSelectedField setStringValue: NSLocalizedString(@"No Torrents Selected", "Inspector -> selected torrents")];
77    
78    //window location and size
79    NSPanel * window = (NSPanel *)[self window];
80    
81    [window setFloatingPanel: NO];
82    
83    const CGFloat windowHeight = NSHeight([window frame]);
84    
85    [window setFrameAutosaveName: @"InspectorWindow"];
86    [window setFrameUsingName: @"InspectorWindow"];
87    
88    NSRect windowRect = [window frame];
89    windowRect.origin.y -= windowHeight - NSHeight(windowRect);
90    windowRect.size.height = windowHeight;
91    [window setFrame: windowRect display: NO];
92    
93    [window setBecomesKeyOnlyIfNeeded: YES];
94    
95    //set tab tooltips
96    [fTabMatrix setToolTip: NSLocalizedString(@"General Info", "Inspector -> tab") forCell: [fTabMatrix cellWithTag: TAB_GENERAL_TAG]];
97    [fTabMatrix setToolTip: NSLocalizedString(@"Activity", "Inspector -> tab") forCell: [fTabMatrix cellWithTag: TAB_ACTIVITY_TAG]];
98    [fTabMatrix setToolTip: NSLocalizedString(@"Trackers", "Inspector -> tab") forCell: [fTabMatrix cellWithTag: TAB_TRACKERS_TAG]];
99    [fTabMatrix setToolTip: NSLocalizedString(@"Peers", "Inspector -> tab") forCell: [fTabMatrix cellWithTag: TAB_PEERS_TAG]];
100    [fTabMatrix setToolTip: NSLocalizedString(@"Files", "Inspector -> tab") forCell: [fTabMatrix cellWithTag: TAB_FILE_TAG]];
101    [fTabMatrix setToolTip: NSLocalizedString(@"Options", "Inspector -> tab") forCell: [fTabMatrix cellWithTag: TAB_OPTIONS_TAG]];
102    
103    //set selected tab
104    fCurrentTabTag = INVALID;
105    NSString * identifier = [[NSUserDefaults standardUserDefaults] stringForKey: @"InspectorSelected"];
106    NSInteger tag;
107    if ([identifier isEqualToString: TAB_INFO_IDENT])
108        tag = TAB_GENERAL_TAG;
109    else if ([identifier isEqualToString: TAB_ACTIVITY_IDENT])
110        tag = TAB_ACTIVITY_TAG;
111    else if ([identifier isEqualToString: TAB_TRACKER_IDENT])
112        tag = TAB_TRACKERS_TAG;
113    else if ([identifier isEqualToString: TAB_PEERS_IDENT])
114        tag = TAB_PEERS_TAG;
115    else if ([identifier isEqualToString: TAB_FILES_IDENT])
116        tag = TAB_FILE_TAG;
117    else if ([identifier isEqualToString: TAB_OPTIONS_IDENT])
118        tag = TAB_OPTIONS_TAG;
119    else //safety
120    {
121        [[NSUserDefaults standardUserDefaults] setObject: TAB_INFO_IDENT forKey: @"InspectorSelected"];
122        tag = TAB_GENERAL_TAG;
123    }
124    [fTabMatrix selectCellWithTag: tag];
125    [self setTab: nil];
126    
127    //set blank inspector
128    [self setInfoForTorrents: [NSArray array]];
129    
130    //allow for update notifications
131    NSNotificationCenter * nc = [NSNotificationCenter defaultCenter];
132    [nc addObserver: self selector: @selector(resetInfoForTorrent:) name: @"ResetInspector" object: nil];
133    [nc addObserver: self selector: @selector(updateInfoStats) name: @"UpdateStats" object: nil];
134    [nc addObserver: self selector: @selector(updateOptions) name: @"UpdateOptions" object: nil];
135}
136
137- (void) dealloc
138{
139    [[NSNotificationCenter defaultCenter] removeObserver: self];
140    
141    if ([fViewController respondsToSelector: @selector(saveViewSize)])
142        [fViewController saveViewSize];
143    
144    [fGeneralViewController release];
145    [fActivityViewController release];
146    [fTrackersViewController release];
147    [fPeersViewController release];
148    [fFileViewController release];
149    [fOptionsViewController release];
150    
151    [fTorrents release];
152    
153    [super dealloc];
154}
155
156- (void) setInfoForTorrents: (NSArray *) torrents
157{
158    if (fTorrents && [fTorrents isEqualToArray: torrents])
159        return;
160    
161    [fTorrents release];
162    fTorrents = [torrents retain];
163    
164    [self resetInfo];
165}
166
167- (NSRect) windowWillUseStandardFrame: (NSWindow *) window defaultFrame: (NSRect) defaultFrame
168{
169    NSRect windowRect = [window frame];
170    windowRect.size.width = [window minSize].width;
171    return windowRect;
172}
173
174- (NSSize) windowWillResize: (NSWindow *) window toSize: (NSSize) proposedFrameSize
175{
176    //this is an edge-case - just stop the animation
177    [fPeersViewController stopWebSeedAnimation];
178    
179    return proposedFrameSize;
180}
181
182- (void) windowWillClose: (NSNotification *) notification
183{
184    if (fCurrentTabTag == TAB_FILE_TAG && ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]))
185        [[QLPreviewPanel sharedPreviewPanel] reloadData];
186}
187
188- (void) setTab: (id) sender
189{
190    const NSInteger oldTabTag = fCurrentTabTag;
191    fCurrentTabTag = [fTabMatrix selectedTag];
192    if (fCurrentTabTag == oldTabTag)
193        return;
194    
195    //take care of old view
196    CGFloat oldHeight = 0;
197    if (oldTabTag != INVALID)
198    {
199        //deselect old tab item
200        [(InfoTabButtonCell *)[fTabMatrix cellWithTag: oldTabTag] setSelectedTab: NO];
201        
202        if ([fViewController respondsToSelector: @selector(saveViewSize)])
203            [fViewController saveViewSize];
204        
205        if ([fViewController respondsToSelector: @selector(clearView)])
206            [fViewController clearView];
207        
208        NSView * oldView = [fViewController view];
209        oldHeight = NSHeight([oldView frame]);
210        
211        //remove old view
212        [oldView removeFromSuperview];
213    }
214    
215    //set new tab item
216    NSString * identifier;
217    switch (fCurrentTabTag)
218    {
219        case TAB_GENERAL_TAG:
220            if (!fGeneralViewController)
221            {
222                fGeneralViewController = [[InfoGeneralViewController alloc] init];
223                [fGeneralViewController setInfoForTorrents: fTorrents];
224            }
225            
226            fViewController = fGeneralViewController;
227            identifier = TAB_INFO_IDENT;
228            break;
229        case TAB_ACTIVITY_TAG:
230            if (!fActivityViewController)
231            {
232                fActivityViewController = [[InfoActivityViewController alloc] init];
233                [fActivityViewController setInfoForTorrents: fTorrents];
234            }
235            
236            fViewController = fActivityViewController;
237            identifier = TAB_ACTIVITY_IDENT;
238            break;
239        case TAB_TRACKERS_TAG:
240            if (!fTrackersViewController)
241            {
242                fTrackersViewController = [[InfoTrackersViewController alloc] init];
243                [fTrackersViewController setInfoForTorrents: fTorrents];
244            }
245            
246            fViewController = fTrackersViewController;
247            identifier = TAB_TRACKER_IDENT;
248            break;
249        case TAB_PEERS_TAG:
250            if (!fPeersViewController)
251            {
252                fPeersViewController = [[InfoPeersViewController alloc] init];
253                [fPeersViewController setInfoForTorrents: fTorrents];
254            }
255            
256            fViewController = fPeersViewController;
257            identifier = TAB_PEERS_IDENT;
258            break;
259        case TAB_FILE_TAG:
260            if (!fFileViewController)
261            {
262                fFileViewController = [[InfoFileViewController alloc] init];
263                [fFileViewController setInfoForTorrents: fTorrents];
264            }
265            
266            fViewController = fFileViewController;
267            identifier = TAB_FILES_IDENT;
268            break;
269        case TAB_OPTIONS_TAG:
270            if (!fOptionsViewController)
271            {
272                fOptionsViewController = [[InfoOptionsViewController alloc] init];
273                [fOptionsViewController setInfoForTorrents: fTorrents];
274            }
275            
276            fViewController = fOptionsViewController;
277            identifier = TAB_OPTIONS_IDENT;
278            break;
279        default:
280            NSAssert1(NO, @"Unknown info tab selected: %ld", fCurrentTabTag);
281            return;
282    }
283    
284    [[NSUserDefaults standardUserDefaults] setObject: identifier forKey: @"InspectorSelected"];
285    
286    NSWindow * window = [self window];
287    
288    [window setTitle: [NSString stringWithFormat: @"%@ - %@", [fViewController title],
289                        NSLocalizedString(@"Torrent Inspector", "Inspector -> title")]];
290    
291    //selected tab item
292    [(InfoTabButtonCell *)[fTabMatrix selectedCell] setSelectedTab: YES];
293    
294    NSView * view = [fViewController view];
295    
296    [fViewController updateInfo];
297    
298    NSRect windowRect = [window frame], viewRect = [view frame];
299    
300    const CGFloat difference = (NSHeight(viewRect) - oldHeight) * [window userSpaceScaleFactor];
301    windowRect.origin.y -= difference;
302    windowRect.size.height += difference;
303    
304    if ([fViewController respondsToSelector: @selector(saveViewSize)]) //a little bit hacky, but avoids requiring an extra method
305    {
306        if ([window screen])
307        {
308            const CGFloat screenHeight = NSHeight([[window screen] visibleFrame]);
309            if (NSHeight(windowRect) > screenHeight)
310            {
311                const CGFloat difference = (screenHeight - NSHeight(windowRect)) * [window userSpaceScaleFactor];
312                windowRect.origin.y -= difference;
313                windowRect.size.height += difference;
314                
315                viewRect.size.height += difference;
316            }
317        }
318        
319        [window setMinSize: NSMakeSize([window minSize].width, NSHeight(windowRect) - NSHeight(viewRect) + TAB_MIN_HEIGHT)];
320        [window setMaxSize: NSMakeSize(FLT_MAX, FLT_MAX)];
321    }
322    else
323    {
324        [window setMinSize: NSMakeSize([window minSize].width, NSHeight(windowRect))];
325        [window setMaxSize: NSMakeSize(FLT_MAX, NSHeight(windowRect))];
326    }
327    
328    viewRect.size.width = NSWidth(windowRect);
329    [view setFrame: viewRect];
330    
331    [window setFrame: windowRect display: YES animate: oldTabTag != INVALID];
332    [[window contentView] addSubview: view];
333    
334    if ((fCurrentTabTag == TAB_FILE_TAG || oldTabTag == TAB_FILE_TAG)
335        && ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]))
336        [[QLPreviewPanel sharedPreviewPanel] reloadData];
337}
338
339- (void) setNextTab
340{
341    NSInteger tag = [fTabMatrix selectedTag]+1;
342    if (tag >= [fTabMatrix numberOfColumns])
343        tag = 0;
344    
345    [fTabMatrix selectCellWithTag: tag];
346    [self setTab: nil];
347}
348
349- (void) setPreviousTab
350{
351    NSInteger tag = [fTabMatrix selectedTag]-1;
352    if (tag < 0)
353        tag = [fTabMatrix numberOfColumns]-1;
354    
355    [fTabMatrix selectCellWithTag: tag];
356    [self setTab: nil];
357}
358
359- (void) swipeWithEvent: (NSEvent *) event
360{
361    if ([event deltaX] < 0.0)
362        [self setNextTab];
363    else if ([event deltaX] > 0.0)
364        [self setPreviousTab];
365}
366
367- (void) updateInfoStats
368{
369    [fViewController updateInfo];
370}
371
372- (void) updateOptions
373{
374    [fOptionsViewController updateOptions];
375}
376
377- (NSArray *) quickLookURLs
378{
379    return [fFileViewController quickLookURLs];
380}
381
382- (BOOL) canQuickLook
383{
384    if (fCurrentTabTag != TAB_FILE_TAG || ![[self window] isVisible])
385        return NO;
386    
387    return [fFileViewController canQuickLook];
388}
389
390- (NSRect) quickLookSourceFrameForPreviewItem: (id <QLPreviewItem>) item
391{
392    return [fFileViewController quickLookSourceFrameForPreviewItem: item];
393}
394
395@end
396
397@implementation InfoWindowController (Private)
398
399- (void) resetInfo
400{
401    const NSUInteger numberSelected = [fTorrents count];
402    if (numberSelected != 1)
403    {
404        if (numberSelected > 0)
405        {
406            [fImageView setImage: [NSImage imageNamed: NSImageNameMultipleDocuments]];
407            
408            [fNameField setStringValue: [NSString stringWithFormat: NSLocalizedString(@"%@ Torrents Selected",
409                                            "Inspector -> selected torrents"),
410                                            [NSString formattedUInteger: numberSelected]]];
411            [fNameField setHidden: NO];
412        
413            uint64_t size = 0;
414            NSUInteger fileCount = 0, magnetCount = 0;
415            for (Torrent * torrent in fTorrents)
416            {
417                size += [torrent size];
418                fileCount += [torrent fileCount];
419                if ([torrent isMagnet])
420                    ++magnetCount;
421            }
422            
423            NSMutableArray * fileStrings = [NSMutableArray arrayWithCapacity: 2];
424            if (fileCount > 0)
425            {
426                NSString * fileString;
427                if (fileCount == 1)
428                    fileString = NSLocalizedString(@"1 file", "Inspector -> selected torrents");
429                else
430                    fileString = [NSString stringWithFormat: NSLocalizedString(@"%@ files", "Inspector -> selected torrents"),
431                                    [NSString formattedUInteger: fileCount]];
432                [fileStrings addObject: fileString];
433            }
434            if (magnetCount > 0)
435            {
436                NSString * magnetString;
437                if (magnetCount == 1)
438                    magnetString = NSLocalizedString(@"1 magnetized transfer", "Inspector -> selected torrents");
439                else
440                    magnetString = [NSString stringWithFormat: NSLocalizedString(@"%@ magnetized transfers",
441                                    "Inspector -> selected torrents"), [NSString formattedUInteger: magnetCount]];
442                [fileStrings addObject: magnetString];
443            }
444            
445            NSString * fileString = [fileStrings componentsJoinedByString: @" + "];
446            
447            if (magnetCount < numberSelected)
448            {
449                [fBasicInfoField setStringValue: [NSString stringWithFormat: @"%@, %@", fileString,
450                    [NSString stringWithFormat: NSLocalizedString(@"%@ total", "Inspector -> selected torrents"),
451                        [NSString stringForFileSize: size]]]];
452                
453                NSString * byteString;
454                if ([NSApp isOnMountainLionOrBetter])
455                {
456                    NSByteCountFormatter * formatter = [[NSByteCountFormatterMtLion alloc] init];
457                    [formatter setAllowedUnits: NSByteCountFormatterUseBytes];
458                    byteString = [formatter stringFromByteCount: size];
459                    [formatter release];
460                }
461                else
462                    byteString = [NSString stringWithFormat: NSLocalizedString(@"%@ bytes", "Inspector -> selected torrents"), [NSString formattedUInteger: size]];
463                [fBasicInfoField setToolTip: byteString];
464            }
465            else
466            {
467                [fBasicInfoField setStringValue: fileString];
468                [fBasicInfoField setToolTip: nil];
469            }
470            [fBasicInfoField setHidden: NO];
471            
472            [fNoneSelectedField setHidden: YES];
473        }
474        else
475        {
476            [fImageView setImage: [NSImage imageNamed: NSImageNameApplicationIcon]];
477            [fNoneSelectedField setHidden: NO];
478            
479            [fNameField setHidden: YES];
480            [fBasicInfoField setHidden: YES];
481        }
482        
483        [fNameField setToolTip: nil];
484    }
485    else
486    {
487        Torrent * torrent = [fTorrents objectAtIndex: 0];
488        
489        [fImageView setImage: [torrent icon]];
490        
491        NSString * name = [torrent name];
492        [fNameField setStringValue: name];
493        [fNameField setToolTip: name];
494        [fNameField setHidden: NO];
495        
496        if (![torrent isMagnet])
497        {
498            NSString * basicString = [NSString stringForFileSize: [torrent size]];
499            if ([torrent isFolder])
500            {
501                NSString * fileString;
502                const NSUInteger fileCount = [torrent fileCount];
503                if (fileCount == 1)
504                    fileString = NSLocalizedString(@"1 file", "Inspector -> selected torrents");
505                else
506                    fileString= [NSString stringWithFormat: NSLocalizedString(@"%@ files", "Inspector -> selected torrents"),
507                                    [NSString formattedUInteger: fileCount]];
508                basicString = [NSString stringWithFormat: @"%@, %@", fileString, basicString];
509            }
510            [fBasicInfoField setStringValue: basicString];
511            
512            NSString * byteString;
513            if ([NSApp isOnMountainLionOrBetter])
514            {
515                NSByteCountFormatter * formatter = [[NSByteCountFormatterMtLion alloc] init];
516                [formatter setAllowedUnits: NSByteCountFormatterUseBytes];
517                byteString = [formatter stringFromByteCount: [torrent size]];
518                [formatter release];
519            }
520            else
521                byteString = [NSString stringWithFormat: NSLocalizedString(@"%@ bytes", "Inspector -> selected torrents"), [NSString formattedUInteger: [torrent size]]];
522            [fBasicInfoField setToolTip: byteString];
523        }
524        else
525        {
526            [fBasicInfoField setStringValue: NSLocalizedString(@"Magnetized transfer", "Inspector -> selected torrents")];
527            [fBasicInfoField setToolTip: nil];
528        }
529        [fBasicInfoField setHidden: NO];
530        
531        [fNoneSelectedField setHidden: YES];
532    }
533    
534    [fGeneralViewController setInfoForTorrents: fTorrents];
535    [fActivityViewController setInfoForTorrents: fTorrents];
536    [fTrackersViewController setInfoForTorrents: fTorrents];
537    [fPeersViewController setInfoForTorrents: fTorrents];
538    [fFileViewController setInfoForTorrents: fTorrents];
539    [fOptionsViewController setInfoForTorrents: fTorrents];
540    
541    [fViewController updateInfo];
542}
543
544- (void) resetInfoForTorrent: (NSNotification *) notification
545{
546    if (fTorrents && [fTorrents containsObject: [notification object]])
547        [self resetInfo];
548}
549
550@end
551