transmission/macosx/InfoWindowController.mm

575 lines
18 KiB
Plaintext

// This file Copyright © 2006-2022 Transmission authors and contributors.
// It may be used under the MIT (SPDX: MIT) license.
// License text can be found in the licenses/ folder.
#import "InfoWindowController.h"
#import "InfoViewController.h"
#import "InfoGeneralViewController.h"
#import "InfoActivityViewController.h"
#import "InfoTrackersViewController.h"
#import "InfoPeersViewController.h"
#import "InfoFileViewController.h"
#import "InfoOptionsViewController.h"
#import "NSImageAdditions.h"
#import "NSApplicationAdditions.h"
#import "NSStringAdditions.h"
#import "Torrent.h"
#define TAB_INFO_IDENT @"Info"
#define TAB_ACTIVITY_IDENT @"Activity"
#define TAB_TRACKER_IDENT @"Tracker"
#define TAB_PEERS_IDENT @"Peers"
#define TAB_FILES_IDENT @"Files"
#define TAB_OPTIONS_IDENT @"Options"
#define TAB_MIN_HEIGHT 250
#define INVALID -99
typedef NS_ENUM(unsigned int, tabTag) {
TAB_GENERAL_TAG = 0,
TAB_ACTIVITY_TAG = 1,
TAB_TRACKERS_TAG = 2,
TAB_PEERS_TAG = 3,
TAB_FILE_TAG = 4,
TAB_OPTIONS_TAG = 5
};
@interface InfoWindowController (Private)
- (void)resetInfo;
- (void)resetInfoForTorrent:(NSNotification*)notification;
@end
@implementation InfoWindowController
- (instancetype)init
{
self = [super initWithWindowNibName:@"InfoWindow"];
return self;
}
- (void)awakeFromNib
{
fNoneSelectedField.stringValue = NSLocalizedString(@"No Torrents Selected", "Inspector -> selected torrents");
//window location and size
NSPanel* window = (NSPanel*)self.window;
window.floatingPanel = NO;
CGFloat const windowHeight = NSHeight(window.frame);
fMinWindowWidth = window.minSize.width;
[window setFrameAutosaveName:@"InspectorWindow"];
[window setFrameUsingName:@"InspectorWindow"];
NSRect windowRect = window.frame;
windowRect.origin.y -= windowHeight - NSHeight(windowRect);
windowRect.size.height = windowHeight;
[window setFrame:windowRect display:NO];
window.becomesKeyOnlyIfNeeded = YES;
//set tab tooltips
[fTabs.cell setToolTip:NSLocalizedString(@"General Info", "Inspector -> tab") forSegment:TAB_GENERAL_TAG];
[fTabs.cell setToolTip:NSLocalizedString(@"Activity", "Inspector -> tab") forSegment:TAB_ACTIVITY_TAG];
[fTabs.cell setToolTip:NSLocalizedString(@"Trackers", "Inspector -> tab") forSegment:TAB_TRACKERS_TAG];
[fTabs.cell setToolTip:NSLocalizedString(@"Peers", "Inspector -> tab") forSegment:TAB_PEERS_TAG];
[fTabs.cell setToolTip:NSLocalizedString(@"Files", "Inspector -> tab") forSegment:TAB_FILE_TAG];
[fTabs.cell setToolTip:NSLocalizedString(@"Options", "Inspector -> tab") forSegment:TAB_OPTIONS_TAG];
[fTabs setImage:[NSImage systemSymbol:@"info.circle"
withFallback:@"InfoGeneral"]
forSegment:TAB_GENERAL_TAG];
[fTabs setImage:[NSImage systemSymbol:@"square.grid.3x3.fill.square"
withFallback:@"InfoActivity"]
forSegment:TAB_ACTIVITY_TAG];
[fTabs setImage:[NSImage systemSymbol:@"antenna.radiowaves.left.and.right"
withFallback:@"InfoTracker"]
forSegment:TAB_TRACKERS_TAG];
[fTabs setImage:[NSImage systemSymbol:@"person.2"
withFallback:@"InfoPeers"]
forSegment:TAB_PEERS_TAG];
[fTabs setImage:[NSImage systemSymbol:@"doc.on.doc"
withFallback:@"InfoFiles"]
forSegment:TAB_FILE_TAG];
[fTabs setImage:[NSImage systemSymbol:@"gearshape"
withFallback:@"InfoOptions"]
forSegment:TAB_OPTIONS_TAG];
//set selected tab
fCurrentTabTag = INVALID;
NSString* identifier = [NSUserDefaults.standardUserDefaults stringForKey:@"InspectorSelected"];
NSInteger tag;
if ([identifier isEqualToString:TAB_INFO_IDENT])
{
tag = TAB_GENERAL_TAG;
}
else if ([identifier isEqualToString:TAB_ACTIVITY_IDENT])
{
tag = TAB_ACTIVITY_TAG;
}
else if ([identifier isEqualToString:TAB_TRACKER_IDENT])
{
tag = TAB_TRACKERS_TAG;
}
else if ([identifier isEqualToString:TAB_PEERS_IDENT])
{
tag = TAB_PEERS_TAG;
}
else if ([identifier isEqualToString:TAB_FILES_IDENT])
{
tag = TAB_FILE_TAG;
}
else if ([identifier isEqualToString:TAB_OPTIONS_IDENT])
{
tag = TAB_OPTIONS_TAG;
}
else //safety
{
[NSUserDefaults.standardUserDefaults setObject:TAB_INFO_IDENT forKey:@"InspectorSelected"];
tag = TAB_GENERAL_TAG;
}
fTabs.target = self;
fTabs.action = @selector(setTab:);
fTabs.selectedSegment = tag;
[self setTab:nil];
//set blank inspector
[self setInfoForTorrents:@[]];
//allow for update notifications
NSNotificationCenter* nc = NSNotificationCenter.defaultCenter;
[nc addObserver:self selector:@selector(resetInfoForTorrent:) name:@"ResetInspector" object:nil];
[nc addObserver:self selector:@selector(updateInfoStats) name:@"UpdateStats" object:nil];
[nc addObserver:self selector:@selector(updateOptions) name:@"UpdateOptions" object:nil];
}
- (void)dealloc
{
[NSNotificationCenter.defaultCenter removeObserver:self];
if ([fViewController respondsToSelector:@selector(saveViewSize)])
{
[fViewController saveViewSize];
}
}
- (void)setInfoForTorrents:(NSArray*)torrents
{
if (fTorrents && [fTorrents isEqualToArray:torrents])
{
return;
}
fTorrents = torrents;
[self resetInfo];
}
- (NSRect)windowWillUseStandardFrame:(NSWindow*)window defaultFrame:(NSRect)defaultFrame
{
NSRect windowRect = window.frame;
windowRect.size.width = window.minSize.width;
return windowRect;
}
- (void)windowWillClose:(NSNotification*)notification
{
if (fCurrentTabTag == TAB_FILE_TAG && ([QLPreviewPanel sharedPreviewPanelExists] && [QLPreviewPanel sharedPreviewPanel].visible))
{
[[QLPreviewPanel sharedPreviewPanel] reloadData];
}
}
- (void)setTab:(id)sender
{
NSInteger const oldTabTag = fCurrentTabTag;
fCurrentTabTag = fTabs.selectedTag;
if (fCurrentTabTag == oldTabTag)
{
return;
}
//take care of old view
CGFloat oldHeight = 0;
if (oldTabTag != INVALID)
{
if ([fViewController respondsToSelector:@selector(saveViewSize)])
{
[fViewController saveViewSize];
}
if ([fViewController respondsToSelector:@selector(clearView)])
{
[fViewController clearView];
}
NSView* oldView = fViewController.view;
oldHeight = NSHeight(oldView.frame);
//remove old view
[oldView removeFromSuperview];
}
//set new tab item
NSString* identifier;
switch (fCurrentTabTag)
{
case TAB_GENERAL_TAG:
if (!fGeneralViewController)
{
fGeneralViewController = [[InfoGeneralViewController alloc] init];
[fGeneralViewController setInfoForTorrents:fTorrents];
}
fViewController = fGeneralViewController;
identifier = TAB_INFO_IDENT;
break;
case TAB_ACTIVITY_TAG:
if (!fActivityViewController)
{
fActivityViewController = [[InfoActivityViewController alloc] init];
[fActivityViewController setInfoForTorrents:fTorrents];
}
fViewController = fActivityViewController;
identifier = TAB_ACTIVITY_IDENT;
break;
case TAB_TRACKERS_TAG:
if (!fTrackersViewController)
{
fTrackersViewController = [[InfoTrackersViewController alloc] init];
[fTrackersViewController setInfoForTorrents:fTorrents];
}
fViewController = fTrackersViewController;
identifier = TAB_TRACKER_IDENT;
break;
case TAB_PEERS_TAG:
if (!fPeersViewController)
{
fPeersViewController = [[InfoPeersViewController alloc] init];
[fPeersViewController setInfoForTorrents:fTorrents];
}
fViewController = fPeersViewController;
identifier = TAB_PEERS_IDENT;
break;
case TAB_FILE_TAG:
if (!fFileViewController)
{
fFileViewController = [[InfoFileViewController alloc] init];
[fFileViewController setInfoForTorrents:fTorrents];
}
fViewController = fFileViewController;
identifier = TAB_FILES_IDENT;
break;
case TAB_OPTIONS_TAG:
if (!fOptionsViewController)
{
fOptionsViewController = [[InfoOptionsViewController alloc] init];
[fOptionsViewController setInfoForTorrents:fTorrents];
}
fViewController = fOptionsViewController;
identifier = TAB_OPTIONS_IDENT;
break;
default:
NSAssert1(NO, @"Unknown info tab selected: %ld", fCurrentTabTag);
return;
}
[NSUserDefaults.standardUserDefaults setObject:identifier forKey:@"InspectorSelected"];
NSWindow* window = self.window;
window.title = [NSString
stringWithFormat:@"%@ - %@", fViewController.title, NSLocalizedString(@"Torrent Inspector", "Inspector -> title")];
NSView* view = fViewController.view;
[fViewController updateInfo];
NSRect windowRect = window.frame, viewRect = view.frame;
CGFloat const difference = NSHeight(viewRect) - oldHeight;
windowRect.origin.y -= difference;
windowRect.size.height += difference;
CGFloat const minWindowWidth = MAX(fMinWindowWidth, view.fittingSize.width);
windowRect.size.width = MAX(NSWidth(windowRect), minWindowWidth);
if ([fViewController respondsToSelector:@selector(saveViewSize)]) //a little bit hacky, but avoids requiring an extra method
{
if (window.screen)
{
CGFloat const screenHeight = NSHeight(window.screen.visibleFrame);
if (NSHeight(windowRect) > screenHeight)
{
CGFloat const difference = screenHeight - NSHeight(windowRect);
windowRect.origin.y -= difference;
windowRect.size.height += difference;
viewRect.size.height += difference;
}
}
window.minSize = NSMakeSize(minWindowWidth, NSHeight(windowRect) - NSHeight(viewRect) + TAB_MIN_HEIGHT);
window.maxSize = NSMakeSize(FLT_MAX, FLT_MAX);
}
else
{
window.minSize = NSMakeSize(minWindowWidth, NSHeight(windowRect));
window.maxSize = NSMakeSize(FLT_MAX, NSHeight(windowRect));
}
viewRect.size.width = NSWidth(windowRect);
view.frame = viewRect;
[window setFrame:windowRect display:YES animate:oldTabTag != INVALID];
[window.contentView addSubview:view];
[window.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[view]-0-|" options:0 metrics:nil
views:@{ @"view" : view }]];
[window.contentView
addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[tabs]-0-[view]-0-|" options:0 metrics:nil
views:@{ @"tabs" : fTabs, @"view" : view }]];
if ((fCurrentTabTag == TAB_FILE_TAG || oldTabTag == TAB_FILE_TAG) &&
([QLPreviewPanel sharedPreviewPanelExists] && [QLPreviewPanel sharedPreviewPanel].visible))
{
[[QLPreviewPanel sharedPreviewPanel] reloadData];
}
}
- (void)setNextTab
{
NSInteger tag = fTabs.selectedSegment + 1;
if (tag >= fTabs.segmentCount)
{
tag = 0;
}
fTabs.selectedSegment = tag;
[self setTab:nil];
}
- (void)setPreviousTab
{
NSInteger tag = fTabs.selectedSegment - 1;
if (tag < 0)
{
tag = fTabs.segmentCount - 1;
}
fTabs.selectedSegment = tag;
[self setTab:nil];
}
- (void)swipeWithEvent:(NSEvent*)event
{
if (event.deltaX < 0.0)
{
[self setNextTab];
}
else if (event.deltaX > 0.0)
{
[self setPreviousTab];
}
}
- (void)updateInfoStats
{
[fViewController updateInfo];
}
- (void)updateOptions
{
[fOptionsViewController updateOptions];
}
- (NSArray*)quickLookURLs
{
return fFileViewController.quickLookURLs;
}
- (BOOL)canQuickLook
{
if (fCurrentTabTag != TAB_FILE_TAG || !self.window.visible)
{
return NO;
}
return fFileViewController.canQuickLook;
}
- (NSRect)quickLookSourceFrameForPreviewItem:(id<QLPreviewItem>)item
{
return [fFileViewController quickLookSourceFrameForPreviewItem:item];
}
@end
@implementation InfoWindowController (Private)
- (void)resetInfo
{
NSUInteger const numberSelected = fTorrents.count;
if (numberSelected != 1)
{
if (numberSelected > 0)
{
fImageView.image = [NSImage imageNamed:NSImageNameMultipleDocuments];
fNameField.stringValue = [NSString stringWithFormat:NSLocalizedString(@"%@ Torrents Selected", "Inspector -> selected torrents"),
[NSString formattedUInteger:numberSelected]];
fNameField.hidden = NO;
uint64_t size = 0;
NSUInteger fileCount = 0, magnetCount = 0;
for (Torrent* torrent in fTorrents)
{
size += torrent.size;
fileCount += torrent.fileCount;
if (torrent.magnet)
{
++magnetCount;
}
}
NSMutableArray* fileStrings = [NSMutableArray arrayWithCapacity:2];
if (fileCount > 0)
{
NSString* fileString;
if (fileCount == 1)
{
fileString = NSLocalizedString(@"1 file", "Inspector -> selected torrents");
}
else
{
fileString = [NSString stringWithFormat:NSLocalizedString(@"%@ files", "Inspector -> selected torrents"),
[NSString formattedUInteger:fileCount]];
}
[fileStrings addObject:fileString];
}
if (magnetCount > 0)
{
NSString* magnetString;
if (magnetCount == 1)
{
magnetString = NSLocalizedString(@"1 magnetized transfer", "Inspector -> selected torrents");
}
else
{
magnetString = [NSString stringWithFormat:NSLocalizedString(@"%@ magnetized transfers", "Inspector -> selected torrents"),
[NSString formattedUInteger:magnetCount]];
}
[fileStrings addObject:magnetString];
}
NSString* fileString = [fileStrings componentsJoinedByString:@" + "];
if (magnetCount < numberSelected)
{
fBasicInfoField.stringValue = [NSString
stringWithFormat:@"%@, %@",
fileString,
[NSString stringWithFormat:NSLocalizedString(@"%@ total", "Inspector -> selected torrents"),
[NSString stringForFileSize:size]]];
NSByteCountFormatter* formatter = [[NSByteCountFormatter alloc] init];
formatter.allowedUnits = NSByteCountFormatterUseBytes;
fBasicInfoField.toolTip = [formatter stringFromByteCount:size];
}
else
{
fBasicInfoField.stringValue = fileString;
fBasicInfoField.toolTip = nil;
}
fBasicInfoField.hidden = NO;
fNoneSelectedField.hidden = YES;
}
else
{
fImageView.image = [NSImage imageNamed:NSImageNameApplicationIcon];
fNoneSelectedField.hidden = NO;
fNameField.hidden = YES;
fBasicInfoField.hidden = YES;
}
fNameField.toolTip = nil;
}
else
{
Torrent* torrent = fTorrents[0];
fImageView.image = torrent.icon;
NSString* name = torrent.name;
fNameField.stringValue = name;
fNameField.toolTip = name;
fNameField.hidden = NO;
if (!torrent.magnet)
{
NSString* basicString = [NSString stringForFileSize:torrent.size];
if (torrent.folder)
{
NSString* fileString;
NSUInteger const fileCount = torrent.fileCount;
if (fileCount == 1)
{
fileString = NSLocalizedString(@"1 file", "Inspector -> selected torrents");
}
else
{
fileString = [NSString stringWithFormat:NSLocalizedString(@"%@ files", "Inspector -> selected torrents"),
[NSString formattedUInteger:fileCount]];
}
basicString = [NSString stringWithFormat:@"%@, %@", fileString, basicString];
}
fBasicInfoField.stringValue = basicString;
NSByteCountFormatter* formatter = [[NSByteCountFormatter alloc] init];
formatter.allowedUnits = NSByteCountFormatterUseBytes;
fBasicInfoField.toolTip = [formatter stringFromByteCount:torrent.size];
}
else
{
fBasicInfoField.stringValue = NSLocalizedString(@"Magnetized transfer", "Inspector -> selected torrents");
fBasicInfoField.toolTip = nil;
}
fBasicInfoField.hidden = NO;
fNoneSelectedField.hidden = YES;
}
[fGeneralViewController setInfoForTorrents:fTorrents];
[fActivityViewController setInfoForTorrents:fTorrents];
[fTrackersViewController setInfoForTorrents:fTorrents];
[fPeersViewController setInfoForTorrents:fTorrents];
[fFileViewController setInfoForTorrents:fTorrents];
[fOptionsViewController setInfoForTorrents:fTorrents];
[fViewController updateInfo];
}
- (void)resetInfoForTorrent:(NSNotification*)notification
{
Torrent* torrent = notification.userInfo[@"Torrent"];
if (fTorrents && (!torrent || [fTorrents containsObject:torrent]))
{
[self resetInfo];
}
}
@end