Files
transmission/macosx/InfoWindowController.mm
Dzmitry Neviadomski c38ee22f7f [macOS] Update Info window.
Use SF Symbols on macOS 11+

Use NSSegmentedControl instead of custom view.

Add NSImage extension to fallback loading from
assets on macOS 10.15 and older.

Also should fix sizing issues when changing pane from Options
to Files.

Fixes #2247
2021-12-12 08:24:16 +03:00

593 lines
19 KiB
Plaintext

/******************************************************************************
* Copyright (c) 2006-2012 Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#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.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