mirror of
https://github.com/transmission/transmission.git
synced 2025-12-20 02:18:42 +00:00
macos: View-based FileOutlineView (#7760)
Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
This commit is contained in:
committed by
GitHub
parent
909fdad807
commit
d1985b05c6
@@ -16,7 +16,6 @@
|
||||
2856E0656A49F2665D69E760 /* benc.h in Headers */ = {isa = PBXBuildFile; fileRef = 2856E0656A49F2665D69E761 /* benc.h */; };
|
||||
2B9BA6C508B488FE586A0AB0 /* torrents.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2B9BA6C508B488FE586A0AB1 /* torrents.cc */; };
|
||||
2B9BA6C508B488FE586A0AB2 /* torrents.h in Headers */ = {isa = PBXBuildFile; fileRef = 2B9BA6C508B488FE586A0AB3 /* torrents.h */; };
|
||||
35F373030C2DA89000DAA8F2 /* FilePriorityCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35F373010C2DA88F00DAA8F2 /* FilePriorityCell.mm */; };
|
||||
37D5E15E2AEFE47B00D1ADB3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
|
||||
3C7A11970D0B2EE300B5701F /* getgateway.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C7A11910D0B2EE300B5701F /* getgateway.c */; };
|
||||
3C7A11980D0B2EE300B5701F /* getgateway.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C7A11920D0B2EE300B5701F /* getgateway.h */; };
|
||||
@@ -125,7 +124,6 @@
|
||||
A222E9870E6B21D9009FB003 /* BlocklistDownloaderViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = A222E9860E6B21D9009FB003 /* BlocklistDownloaderViewController.mm */; };
|
||||
A222EA7B0E6C32C4009FB003 /* BlocklistScheduler.mm in Sources */ = {isa = PBXBuildFile; fileRef = A222EA7A0E6C32C4009FB003 /* BlocklistScheduler.mm */; };
|
||||
A225A4C0187E369C00CDE823 /* ShareToolbarItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = A225A4BF187E369C00CDE823 /* ShareToolbarItem.mm */; };
|
||||
A2265F420B5EF5F40093DDA5 /* FileNameCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = A2265F400B5EF5F40093DDA5 /* FileNameCell.mm */; };
|
||||
A226FDAC0D0CDF20005A7F71 /* libnatpmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C7A118D0D0B2EB800B5701F /* libnatpmp.a */; };
|
||||
A22A8D560AEEAFA5007E9CB9 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = A22A8D540AEEAFA5007E9CB9 /* Localizable.strings */; };
|
||||
A22B00B2116A9E9F003315FC /* connecthostport.h in Headers */ = {isa = PBXBuildFile; fileRef = A22B00AF116A9E90003315FC /* connecthostport.h */; };
|
||||
@@ -458,6 +456,9 @@
|
||||
ED5E0F0F2CD31BC20071433B /* NSStringAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4DE5CC9C0980656F00BE280E /* NSStringAdditions.mm */; };
|
||||
ED67FB422B70FCE400D8A037 /* settings.cc in Sources */ = {isa = PBXBuildFile; fileRef = ED67FB402B70FCE400D8A037 /* settings.cc */; };
|
||||
ED67FB432B70FCE400D8A037 /* settings.h in Headers */ = {isa = PBXBuildFile; fileRef = ED67FB412B70FCE400D8A037 /* settings.h */; };
|
||||
ED6F16B52EB8F1EB007CD864 /* FileNameCellView.mm in Sources */ = {isa = PBXBuildFile; fileRef = ED6F16B22EB8F1EB007CD864 /* FileNameCellView.mm */; };
|
||||
ED6F16B62EB8F1EB007CD864 /* FilePriorityCellView.mm in Sources */ = {isa = PBXBuildFile; fileRef = ED6F16B42EB8F1EB007CD864 /* FilePriorityCellView.mm */; };
|
||||
ED6F16B72EB8F1EB007CD864 /* FileCheckCellView.mm in Sources */ = {isa = PBXBuildFile; fileRef = ED6F16B02EB8F1EB007CD864 /* FileCheckCellView.mm */; };
|
||||
ED86936F2ADAE34D00342B1A /* DefaultAppHelper.mm in Sources */ = {isa = PBXBuildFile; fileRef = ED86936E2ADAE34D00342B1A /* DefaultAppHelper.mm */; };
|
||||
ED8A16412735A8AA000D61F9 /* peer-mgr-wishlist.h in Headers */ = {isa = PBXBuildFile; fileRef = ED8A163D2735A8AA000D61F9 /* peer-mgr-wishlist.h */; };
|
||||
ED8A16422735A8AA000D61F9 /* peer-mgr-wishlist.cc in Sources */ = {isa = PBXBuildFile; fileRef = ED8A163E2735A8AA000D61F9 /* peer-mgr-wishlist.cc */; };
|
||||
@@ -686,8 +687,6 @@
|
||||
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
||||
2B9BA6C508B488FE586A0AB1 /* torrents.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = torrents.cc; sourceTree = "<group>"; };
|
||||
2B9BA6C508B488FE586A0AB3 /* torrents.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = torrents.h; sourceTree = "<group>"; };
|
||||
35F373000C2DA88F00DAA8F2 /* FilePriorityCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FilePriorityCell.h; sourceTree = "<group>"; };
|
||||
35F373010C2DA88F00DAA8F2 /* FilePriorityCell.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FilePriorityCell.mm; sourceTree = "<group>"; };
|
||||
3C7A118D0D0B2EB800B5701F /* libnatpmp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libnatpmp.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3C7A11910D0B2EE300B5701F /* getgateway.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = getgateway.c; sourceTree = "<group>"; };
|
||||
3C7A11920D0B2EE300B5701F /* getgateway.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = getgateway.h; sourceTree = "<group>"; };
|
||||
@@ -921,8 +920,6 @@
|
||||
A223AA800D220CEB00840069 /* nl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
A225A4BE187E369C00CDE823 /* ShareToolbarItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShareToolbarItem.h; sourceTree = "<group>"; };
|
||||
A225A4BF187E369C00CDE823 /* ShareToolbarItem.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ShareToolbarItem.mm; sourceTree = "<group>"; };
|
||||
A2265F3F0B5EF5F40093DDA5 /* FileNameCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileNameCell.h; sourceTree = "<group>"; };
|
||||
A2265F400B5EF5F40093DDA5 /* FileNameCell.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FileNameCell.mm; sourceTree = "<group>"; };
|
||||
A22B00AE116A9E90003315FC /* connecthostport.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = connecthostport.c; sourceTree = "<group>"; };
|
||||
A22B00AF116A9E90003315FC /* connecthostport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = connecthostport.h; sourceTree = "<group>"; };
|
||||
A22BAE261388040500FB022F /* NSMutableArrayAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSMutableArrayAdditions.h; sourceTree = "<group>"; };
|
||||
@@ -1450,6 +1447,12 @@
|
||||
ED5E0F0E2CD3164D0071433B /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
ED67FB402B70FCE400D8A037 /* settings.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = settings.cc; sourceTree = "<group>"; };
|
||||
ED67FB412B70FCE400D8A037 /* settings.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = settings.h; sourceTree = "<group>"; };
|
||||
ED6F16AF2EB8F1EB007CD864 /* FileCheckCellView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileCheckCellView.h; sourceTree = "<group>"; };
|
||||
ED6F16B02EB8F1EB007CD864 /* FileCheckCellView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FileCheckCellView.mm; sourceTree = "<group>"; };
|
||||
ED6F16B12EB8F1EB007CD864 /* FileNameCellView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileNameCellView.h; sourceTree = "<group>"; };
|
||||
ED6F16B22EB8F1EB007CD864 /* FileNameCellView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FileNameCellView.mm; sourceTree = "<group>"; };
|
||||
ED6F16B32EB8F1EB007CD864 /* FilePriorityCellView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FilePriorityCellView.h; sourceTree = "<group>"; };
|
||||
ED6F16B42EB8F1EB007CD864 /* FilePriorityCellView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FilePriorityCellView.mm; sourceTree = "<group>"; };
|
||||
ED86936D2ADAE34D00342B1A /* DefaultAppHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DefaultAppHelper.h; sourceTree = "<group>"; };
|
||||
ED86936E2ADAE34D00342B1A /* DefaultAppHelper.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DefaultAppHelper.mm; sourceTree = "<group>"; };
|
||||
ED8A163D2735A8AA000D61F9 /* peer-mgr-wishlist.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "peer-mgr-wishlist.h"; sourceTree = "<group>"; };
|
||||
@@ -2142,10 +2145,12 @@
|
||||
A26AF2190D2DA35A00FF7140 /* FileOutlineController.mm */,
|
||||
A2AF1C360A3D0F6200F1575D /* FileOutlineView.h */,
|
||||
A2AF1C370A3D0F6200F1575D /* FileOutlineView.mm */,
|
||||
A2265F3F0B5EF5F40093DDA5 /* FileNameCell.h */,
|
||||
A2265F400B5EF5F40093DDA5 /* FileNameCell.mm */,
|
||||
35F373000C2DA88F00DAA8F2 /* FilePriorityCell.h */,
|
||||
35F373010C2DA88F00DAA8F2 /* FilePriorityCell.mm */,
|
||||
ED6F16AF2EB8F1EB007CD864 /* FileCheckCellView.h */,
|
||||
ED6F16B02EB8F1EB007CD864 /* FileCheckCellView.mm */,
|
||||
ED6F16B12EB8F1EB007CD864 /* FileNameCellView.h */,
|
||||
ED6F16B22EB8F1EB007CD864 /* FileNameCellView.mm */,
|
||||
ED6F16B32EB8F1EB007CD864 /* FilePriorityCellView.h */,
|
||||
ED6F16B42EB8F1EB007CD864 /* FilePriorityCellView.mm */,
|
||||
);
|
||||
name = "File Outline View";
|
||||
sourceTree = "<group>";
|
||||
@@ -3545,14 +3550,15 @@
|
||||
A2AA579D0ADFCAB400CA59F6 /* PiecesView.mm in Sources */,
|
||||
A25E74650AF5097C006F11AE /* ExpandedPathToPathTransformer.mm in Sources */,
|
||||
A25E74660AF5097D006F11AE /* ExpandedPathToIconTransformer.mm in Sources */,
|
||||
A2265F420B5EF5F40093DDA5 /* FileNameCell.mm in Sources */,
|
||||
A2A1CB7A0BF29D5500AE959F /* PeerProgressIndicatorCell.mm in Sources */,
|
||||
457AF8EB28604AFC00BCF74F /* Toolbar.mm in Sources */,
|
||||
A2385DD40BFE06C800B24EF6 /* DragOverlayWindow.mm in Sources */,
|
||||
A2FB057F0BFEB6800095564D /* DragOverlayView.mm in Sources */,
|
||||
E138A9780C04D88F00C5426C /* ProgressGradients.mm in Sources */,
|
||||
ED6F16B52EB8F1EB007CD864 /* FileNameCellView.mm in Sources */,
|
||||
ED6F16B62EB8F1EB007CD864 /* FilePriorityCellView.mm in Sources */,
|
||||
ED6F16B72EB8F1EB007CD864 /* FileCheckCellView.mm in Sources */,
|
||||
A2DF37070C220D03006523C1 /* CreatorWindowController.mm in Sources */,
|
||||
35F373030C2DA89000DAA8F2 /* FilePriorityCell.mm in Sources */,
|
||||
A2085DDC0C53BC74000BC3B7 /* AboutWindowController.mm in Sources */,
|
||||
A21282A80CA6C66800EAEE0F /* StatusBarView.mm in Sources */,
|
||||
A257C1820CAD3003004E121C /* PeerTableView.mm in Sources */,
|
||||
|
||||
@@ -309,7 +309,7 @@ typedef NS_ENUM(NSUInteger, PopupPriority) {
|
||||
{
|
||||
//check buttons
|
||||
//keep synced with identical code in InfoFileViewController.m
|
||||
NSInteger const filesCheckState = [self.torrent
|
||||
NSControlStateValue const filesCheckState = [self.torrent
|
||||
checkForFiles:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.torrent.fileCount)]];
|
||||
self.fCheckAllButton.enabled = filesCheckState != NSControlStateValueOn; //if anything is unchecked
|
||||
self.fUncheckAllButton.enabled = !self.torrent.allDownloaded; //if there are any checked files that aren't finished
|
||||
@@ -352,7 +352,7 @@ typedef NS_ENUM(NSUInteger, PopupPriority) {
|
||||
{
|
||||
[self.torrent update];
|
||||
|
||||
[self.fFileController refresh];
|
||||
[self.fFileController reloadVisibleRows];
|
||||
|
||||
[self updateCheckButtons:nil]; //call in case button state changed by checking
|
||||
|
||||
|
||||
@@ -75,14 +75,16 @@ target_sources(${TR_NAME}-mac
|
||||
ExpandedPathToPathTransformer.mm
|
||||
FileListNode.h
|
||||
FileListNode.mm
|
||||
FileNameCell.h
|
||||
FileNameCell.mm
|
||||
FileCheckCellView.h
|
||||
FileCheckCellView.mm
|
||||
FileNameCellView.h
|
||||
FileNameCellView.mm
|
||||
FileOutlineController.h
|
||||
FileOutlineController.mm
|
||||
FileOutlineView.h
|
||||
FileOutlineView.mm
|
||||
FilePriorityCell.h
|
||||
FilePriorityCell.mm
|
||||
FilePriorityCellView.h
|
||||
FilePriorityCellView.mm
|
||||
FileRenameSheetController.h
|
||||
FileRenameSheetController.mm
|
||||
FilterBarController.h
|
||||
|
||||
13
macosx/FileCheckCellView.h
Normal file
13
macosx/FileCheckCellView.h
Normal file
@@ -0,0 +1,13 @@
|
||||
// This file Copyright © Transmission authors and contributors.
|
||||
// It may be used under the MIT (SPDX: MIT) license.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
@class FileListNode;
|
||||
|
||||
@interface FileCheckCellView : NSTableCellView
|
||||
|
||||
@property(nonatomic, weak) FileListNode* node;
|
||||
|
||||
@end
|
||||
113
macosx/FileCheckCellView.mm
Normal file
113
macosx/FileCheckCellView.mm
Normal file
@@ -0,0 +1,113 @@
|
||||
// This file Copyright © Transmission authors and contributors.
|
||||
// It may be used under the MIT (SPDX: MIT) license.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#import "FileCheckCellView.h"
|
||||
#import "FileListNode.h"
|
||||
#import "Torrent.h"
|
||||
|
||||
@interface FileCheckCellView ()
|
||||
@property(nonatomic, weak) NSButton* checkButton;
|
||||
@end
|
||||
|
||||
@implementation FileCheckCellView
|
||||
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect
|
||||
{
|
||||
if ((self = [super initWithFrame:frameRect]))
|
||||
{
|
||||
// Create checkbox button
|
||||
NSButton* checkButton = [[NSButton alloc] initWithFrame:NSZeroRect];
|
||||
checkButton.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[checkButton setButtonType:NSButtonTypeSwitch];
|
||||
checkButton.title = @"";
|
||||
checkButton.allowsMixedState = YES;
|
||||
checkButton.target = self;
|
||||
checkButton.action = @selector(checkButtonClicked:);
|
||||
[self addSubview:checkButton];
|
||||
_checkButton = checkButton;
|
||||
|
||||
// Setup constraints
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[checkButton.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
|
||||
[checkButton.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
|
||||
]];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setNode:(FileListNode*)node
|
||||
{
|
||||
_node = node;
|
||||
[self updateDisplay];
|
||||
}
|
||||
|
||||
- (void)updateDisplay
|
||||
{
|
||||
if (!self.node)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FileListNode* node = self.node;
|
||||
Torrent* torrent = node.torrent;
|
||||
|
||||
// Update checkbox state
|
||||
self.checkButton.state = [torrent checkForFiles:node.indexes];
|
||||
self.checkButton.enabled = [torrent canChangeDownloadCheckForFiles:node.indexes];
|
||||
|
||||
// Update tooltip
|
||||
[self updateTooltip];
|
||||
}
|
||||
|
||||
- (void)updateTooltip
|
||||
{
|
||||
if (!self.node)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NSString* tooltip = nil;
|
||||
switch (self.checkButton.state)
|
||||
{
|
||||
case NSControlStateValueOff:
|
||||
tooltip = NSLocalizedString(@"Don't Download", "files tab -> tooltip");
|
||||
break;
|
||||
case NSControlStateValueOn:
|
||||
tooltip = NSLocalizedString(@"Download", "files tab -> tooltip");
|
||||
break;
|
||||
case NSControlStateValueMixed:
|
||||
tooltip = NSLocalizedString(@"Download Some", "files tab -> tooltip");
|
||||
break;
|
||||
}
|
||||
self.checkButton.toolTip = tooltip;
|
||||
}
|
||||
|
||||
- (void)checkButtonClicked:(NSButton*)sender
|
||||
{
|
||||
if (!self.node)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FileListNode* node = self.node;
|
||||
Torrent* torrent = node.torrent;
|
||||
|
||||
NSIndexSet* indexSet;
|
||||
if (NSEvent.modifierFlags & NSEventModifierFlagOption)
|
||||
{
|
||||
indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, torrent.fileCount)];
|
||||
}
|
||||
else
|
||||
{
|
||||
indexSet = node.indexes;
|
||||
}
|
||||
|
||||
[torrent setFileCheckState:sender.state != NSControlStateValueOff ? NSControlStateValueOn : NSControlStateValueOff
|
||||
forIndexes:indexSet];
|
||||
|
||||
// Notify that we need to refresh
|
||||
[NSNotificationCenter.defaultCenter postNotificationName:@"UpdateUI" object:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,17 +0,0 @@
|
||||
// This file Copyright © Transmission authors and contributors.
|
||||
// It may be used under the MIT (SPDX: MIT) license.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, AttributesStyle) {
|
||||
AttributesStyleNormal,
|
||||
AttributesStyleEmphasized,
|
||||
AttributesStyleDisabled,
|
||||
};
|
||||
|
||||
@interface FileNameCell : NSActionCell
|
||||
|
||||
- (NSRect)imageRectForBounds:(NSRect)bounds;
|
||||
|
||||
@end
|
||||
@@ -1,218 +0,0 @@
|
||||
// This file Copyright © Transmission authors and contributors.
|
||||
// It may be used under the MIT (SPDX: MIT) license.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include <libtransmission/transmission.h>
|
||||
#include <libtransmission/utils.h>
|
||||
|
||||
#import "FileNameCell.h"
|
||||
#import "FileOutlineView.h"
|
||||
#import "Torrent.h"
|
||||
#import "FileListNode.h"
|
||||
#import "NSStringAdditions.h"
|
||||
|
||||
static CGFloat const kPaddingHorizontal = 2.0;
|
||||
static CGFloat const kImageFolderSize = 16.0;
|
||||
static CGFloat const kImageIconSize = 32.0;
|
||||
static CGFloat const kPaddingBetweenImageAndTitle = 4.0;
|
||||
static CGFloat const kPaddingAboveTitleFile = 2.0;
|
||||
static CGFloat const kPaddingBelowStatusFile = 2.0;
|
||||
static CGFloat const kPaddingBetweenNameAndFolderStatus = 4.0;
|
||||
static CGFloat const kPaddingExpansionFrame = 2.0;
|
||||
|
||||
static NSMutableParagraphStyle* sParagraphStyle()
|
||||
{
|
||||
NSMutableParagraphStyle* paragraphStyle = [NSParagraphStyle.defaultParagraphStyle mutableCopy];
|
||||
paragraphStyle.lineBreakMode = NSLineBreakByTruncatingMiddle;
|
||||
return paragraphStyle;
|
||||
}
|
||||
static NSMutableParagraphStyle* sStatusParagraphStyle()
|
||||
{
|
||||
NSMutableParagraphStyle* paragraphStyle = [NSParagraphStyle.defaultParagraphStyle mutableCopy];
|
||||
paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
return paragraphStyle;
|
||||
}
|
||||
|
||||
static NSDictionary<NSAttributedStringKey, id>* const kTitleAttributes = @{
|
||||
NSFontAttributeName : [NSFont messageFontOfSize:12.0],
|
||||
NSParagraphStyleAttributeName : sParagraphStyle(),
|
||||
NSForegroundColorAttributeName : NSColor.controlTextColor
|
||||
};
|
||||
static NSDictionary<NSAttributedStringKey, id>* const kStatusAttributes = @{
|
||||
NSFontAttributeName : [NSFont messageFontOfSize:9.0],
|
||||
NSParagraphStyleAttributeName : sStatusParagraphStyle(),
|
||||
NSForegroundColorAttributeName : NSColor.secondaryLabelColor
|
||||
};
|
||||
static NSDictionary<NSAttributedStringKey, id>* const kTitleEmphasizedAttributes = @{
|
||||
NSFontAttributeName : [NSFont messageFontOfSize:12.0],
|
||||
NSParagraphStyleAttributeName : sParagraphStyle(),
|
||||
NSForegroundColorAttributeName : NSColor.whiteColor
|
||||
};
|
||||
static NSDictionary<NSAttributedStringKey, id>* const kStatusEmphasizedAttributes = @{
|
||||
NSFontAttributeName : [NSFont messageFontOfSize:9.0],
|
||||
NSParagraphStyleAttributeName : sStatusParagraphStyle(),
|
||||
NSForegroundColorAttributeName : NSColor.whiteColor
|
||||
};
|
||||
static NSDictionary<NSAttributedStringKey, id>* const kTitleDisabledAttributes = @{
|
||||
NSFontAttributeName : [NSFont messageFontOfSize:12.0],
|
||||
NSParagraphStyleAttributeName : sParagraphStyle(),
|
||||
NSForegroundColorAttributeName : NSColor.disabledControlTextColor
|
||||
};
|
||||
static NSDictionary<NSAttributedStringKey, id>* const kStatusDisabledAttributes = @{
|
||||
NSFontAttributeName : [NSFont messageFontOfSize:9.0],
|
||||
NSParagraphStyleAttributeName : sStatusParagraphStyle(),
|
||||
NSForegroundColorAttributeName : NSColor.disabledControlTextColor
|
||||
};
|
||||
|
||||
@implementation FileNameCell
|
||||
|
||||
- (NSImage*)image
|
||||
{
|
||||
FileListNode* node = (FileListNode*)self.objectValue;
|
||||
return node.icon;
|
||||
}
|
||||
|
||||
- (NSRect)imageRectForBounds:(NSRect)bounds
|
||||
{
|
||||
NSRect result = bounds;
|
||||
|
||||
result.origin.x += kPaddingHorizontal;
|
||||
|
||||
CGFloat const IMAGE_SIZE = ((FileListNode*)self.objectValue).isFolder ? kImageFolderSize : kImageIconSize;
|
||||
result.origin.y += (result.size.height - IMAGE_SIZE) * 0.5;
|
||||
result.size = NSMakeSize(IMAGE_SIZE, IMAGE_SIZE);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView
|
||||
{
|
||||
//icon
|
||||
[self.image drawInRect:[self imageRectForBounds:cellFrame] fromRect:NSZeroRect operation:NSCompositingOperationSourceOver
|
||||
fraction:1.0
|
||||
respectFlipped:YES
|
||||
hints:nil];
|
||||
|
||||
FileListNode* node = self.objectValue;
|
||||
AttributesStyle style;
|
||||
if (self.backgroundStyle == NSBackgroundStyleEmphasized)
|
||||
{
|
||||
style = AttributesStyleEmphasized;
|
||||
}
|
||||
else if ([node.torrent checkForFiles:node.indexes] == NSControlStateValueOff)
|
||||
{
|
||||
style = AttributesStyleDisabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
style = AttributesStyleNormal;
|
||||
}
|
||||
|
||||
//title
|
||||
NSAttributedString* titleString = [self attributedTitleWithStyle:style];
|
||||
NSRect titleRect = [self rectForTitleWithStringSize:[titleString size] inBounds:cellFrame];
|
||||
[titleString drawInRect:titleRect];
|
||||
|
||||
//status
|
||||
NSAttributedString* statusString = [self attributedStatusWithStyle:style];
|
||||
NSRect statusRect = [self rectForStatusWithString:statusString withTitleRect:titleRect inBounds:cellFrame];
|
||||
[statusString drawInRect:statusRect];
|
||||
}
|
||||
|
||||
- (NSRect)expansionFrameWithFrame:(NSRect)cellFrame inView:(NSView*)view
|
||||
{
|
||||
NSAttributedString* titleString = [self attributedTitleWithStyle:AttributesStyleNormal];
|
||||
NSRect realRect = [self rectForTitleWithStringSize:[titleString size] inBounds:cellFrame];
|
||||
|
||||
if ([titleString size].width > NSWidth(realRect) &&
|
||||
NSMouseInRect([view convertPoint:view.window.mouseLocationOutsideOfEventStream fromView:nil], realRect, view.flipped))
|
||||
{
|
||||
realRect.size.width = [titleString size].width;
|
||||
return NSInsetRect(realRect, -kPaddingExpansionFrame, -kPaddingExpansionFrame);
|
||||
}
|
||||
|
||||
return NSZeroRect;
|
||||
}
|
||||
|
||||
- (void)drawWithExpansionFrame:(NSRect)cellFrame inView:(NSView*)view
|
||||
{
|
||||
cellFrame.origin.x += kPaddingExpansionFrame;
|
||||
cellFrame.origin.y += kPaddingExpansionFrame;
|
||||
|
||||
NSAttributedString* titleString = [self attributedTitleWithStyle:AttributesStyleNormal];
|
||||
[titleString drawInRect:cellFrame];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (NSRect)rectForTitleWithStringSize:(NSSize)stringSize inBounds:(NSRect)bounds
|
||||
{
|
||||
NSSize const titleSize = stringSize;
|
||||
|
||||
//no right padding, so that there's not too much space between this and the priority image
|
||||
NSRect result;
|
||||
if (!((FileListNode*)self.objectValue).isFolder)
|
||||
{
|
||||
result.origin.x = NSMinX(bounds) + kPaddingHorizontal + kImageIconSize + kPaddingBetweenImageAndTitle;
|
||||
result.origin.y = NSMinY(bounds) + kPaddingAboveTitleFile;
|
||||
result.size.width = NSMaxX(bounds) - NSMinX(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.origin.x = NSMinX(bounds) + kPaddingHorizontal + kImageFolderSize + kPaddingBetweenImageAndTitle;
|
||||
result.origin.y = NSMidY(bounds) - titleSize.height * 0.5;
|
||||
result.size.width = MIN(titleSize.width, NSMaxX(bounds) - NSMinX(result));
|
||||
}
|
||||
result.size.height = titleSize.height;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSRect)rectForStatusWithString:(NSAttributedString*)string withTitleRect:(NSRect)titleRect inBounds:(NSRect)bounds
|
||||
{
|
||||
NSSize const statusSize = [string size];
|
||||
|
||||
NSRect result;
|
||||
if (!((FileListNode*)self.objectValue).isFolder)
|
||||
{
|
||||
result.origin.x = NSMinX(titleRect);
|
||||
result.origin.y = NSMaxY(bounds) - kPaddingBelowStatusFile - statusSize.height;
|
||||
result.size.width = NSWidth(titleRect);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.origin.x = NSMaxX(titleRect) + kPaddingBetweenNameAndFolderStatus;
|
||||
result.origin.y = NSMaxY(titleRect) - statusSize.height - 1.0;
|
||||
result.size.width = NSMaxX(bounds) - NSMaxX(titleRect);
|
||||
}
|
||||
result.size.height = statusSize.height;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSAttributedString*)attributedTitleWithStyle:(AttributesStyle)style
|
||||
{
|
||||
NSString* title = ((FileListNode*)self.objectValue).name;
|
||||
return [[NSAttributedString alloc] initWithString:title attributes:style == AttributesStyleEmphasized ? kTitleEmphasizedAttributes :
|
||||
style == AttributesStyleDisabled ? kTitleDisabledAttributes :
|
||||
kTitleAttributes];
|
||||
}
|
||||
|
||||
- (NSAttributedString*)attributedStatusWithStyle:(AttributesStyle)style
|
||||
{
|
||||
FileListNode* node = (FileListNode*)self.objectValue;
|
||||
Torrent* torrent = node.torrent;
|
||||
|
||||
CGFloat const progress = [torrent fileProgress:node];
|
||||
NSString* percentString = [NSString percentString:progress longDecimals:YES];
|
||||
|
||||
NSString* status = [NSString stringWithFormat:NSLocalizedString(@"%@ of %@", "Inspector -> Files tab -> file status string"),
|
||||
percentString,
|
||||
[NSString stringForFileSize:node.size]];
|
||||
|
||||
return [[NSAttributedString alloc] initWithString:status attributes:style == AttributesStyleEmphasized ? kStatusEmphasizedAttributes :
|
||||
style == AttributesStyleDisabled ? kStatusDisabledAttributes :
|
||||
kStatusAttributes];
|
||||
}
|
||||
|
||||
@end
|
||||
13
macosx/FileNameCellView.h
Normal file
13
macosx/FileNameCellView.h
Normal file
@@ -0,0 +1,13 @@
|
||||
// This file Copyright © Transmission authors and contributors.
|
||||
// It may be used under the MIT (SPDX: MIT) license.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
@class FileListNode;
|
||||
|
||||
@interface FileNameCellView : NSTableCellView
|
||||
|
||||
@property(nonatomic, weak) FileListNode* node;
|
||||
|
||||
@end
|
||||
228
macosx/FileNameCellView.mm
Normal file
228
macosx/FileNameCellView.mm
Normal file
@@ -0,0 +1,228 @@
|
||||
// This file Copyright © Transmission authors and contributors.
|
||||
// It may be used under the MIT (SPDX: MIT) license.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include <libtransmission/transmission.h>
|
||||
#include <libtransmission/utils.h>
|
||||
|
||||
#import "FileNameCellView.h"
|
||||
#import "FileListNode.h"
|
||||
#import "Torrent.h"
|
||||
#import "NSStringAdditions.h"
|
||||
|
||||
static CGFloat const kPaddingHorizontal = 2.0;
|
||||
static CGFloat const kImageFolderSize = 16.0;
|
||||
static CGFloat const kImageIconSize = 32.0;
|
||||
static CGFloat const kPaddingBetweenImageAndTitle = 4.0;
|
||||
static CGFloat const kPaddingAboveTitleFile = 2.0;
|
||||
static CGFloat const kPaddingBelowStatusFile = 2.0;
|
||||
static CGFloat const kPaddingBetweenNameAndFolderStatus = 4.0;
|
||||
|
||||
@interface FileNameCellView ()
|
||||
@property(nonatomic, weak) NSImageView* iconView;
|
||||
@property(nonatomic, weak) NSTextField* nameField;
|
||||
@property(nonatomic, weak) NSTextField* statusField;
|
||||
@property(nonatomic, strong) NSArray<NSLayoutConstraint*>* dynamicConstraints;
|
||||
@end
|
||||
|
||||
@implementation FileNameCellView
|
||||
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect
|
||||
{
|
||||
if ((self = [super initWithFrame:frameRect]))
|
||||
{
|
||||
// Create icon view
|
||||
NSImageView* iconView = [[NSImageView alloc] initWithFrame:NSZeroRect];
|
||||
iconView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
iconView.imageScaling = NSImageScaleProportionallyDown;
|
||||
[self addSubview:iconView];
|
||||
_iconView = iconView;
|
||||
|
||||
// Create name field
|
||||
NSTextField* nameField = [[NSTextField alloc] initWithFrame:NSZeroRect];
|
||||
nameField.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
nameField.editable = NO;
|
||||
nameField.selectable = NO;
|
||||
nameField.bordered = NO;
|
||||
nameField.backgroundColor = NSColor.clearColor;
|
||||
nameField.font = [NSFont messageFontOfSize:12.0];
|
||||
nameField.lineBreakMode = NSLineBreakByTruncatingMiddle;
|
||||
[self addSubview:nameField];
|
||||
_nameField = nameField;
|
||||
self.textField = nameField;
|
||||
|
||||
// Create status field
|
||||
NSTextField* statusField = [[NSTextField alloc] initWithFrame:NSZeroRect];
|
||||
statusField.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
statusField.editable = NO;
|
||||
statusField.selectable = NO;
|
||||
statusField.bordered = NO;
|
||||
statusField.backgroundColor = NSColor.clearColor;
|
||||
statusField.font = [NSFont messageFontOfSize:9.0];
|
||||
statusField.textColor = NSColor.secondaryLabelColor;
|
||||
statusField.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
[self addSubview:statusField];
|
||||
_statusField = statusField;
|
||||
|
||||
// Setup constraints
|
||||
[self setupConstraints];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupConstraints
|
||||
{
|
||||
NSImageView* iconView = self.iconView;
|
||||
NSTextField* nameField = self.nameField;
|
||||
|
||||
// Fixed constraints that don't change
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
// Icon view constraints
|
||||
[iconView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:kPaddingHorizontal],
|
||||
[iconView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
|
||||
[iconView.widthAnchor constraintEqualToConstant:kImageIconSize],
|
||||
[iconView.heightAnchor constraintEqualToConstant:kImageIconSize],
|
||||
|
||||
// Name field leading constraint
|
||||
[nameField.leadingAnchor constraintEqualToAnchor:iconView.trailingAnchor constant:kPaddingBetweenImageAndTitle],
|
||||
]];
|
||||
|
||||
self.dynamicConstraints = @[];
|
||||
}
|
||||
|
||||
- (void)setNode:(FileListNode*)node
|
||||
{
|
||||
_node = node;
|
||||
[self updateDisplay];
|
||||
}
|
||||
|
||||
- (void)updateDisplay
|
||||
{
|
||||
if (!self.node)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FileListNode* node = self.node;
|
||||
|
||||
// Update icon
|
||||
self.iconView.image = node.icon;
|
||||
|
||||
// Update icon size constraints based on folder/file
|
||||
CGFloat const imageSize = node.isFolder ? kImageFolderSize : kImageIconSize;
|
||||
for (NSLayoutConstraint* constraint in self.iconView.constraints)
|
||||
{
|
||||
if (constraint.firstAttribute == NSLayoutAttributeWidth || constraint.firstAttribute == NSLayoutAttributeHeight)
|
||||
{
|
||||
constraint.constant = imageSize;
|
||||
}
|
||||
}
|
||||
|
||||
// Update name
|
||||
self.nameField.stringValue = node.name;
|
||||
|
||||
// Update status
|
||||
Torrent* torrent = node.torrent;
|
||||
CGFloat const progress = [torrent fileProgress:node];
|
||||
NSString* percentString = [NSString percentString:progress longDecimals:YES];
|
||||
|
||||
NSString* status = [NSString stringWithFormat:NSLocalizedString(@"%@ of %@", "Inspector -> Files tab -> file status string"),
|
||||
percentString,
|
||||
[NSString stringForFileSize:node.size]];
|
||||
self.statusField.stringValue = status;
|
||||
|
||||
// Update layout constraints based on folder vs file
|
||||
[NSLayoutConstraint deactivateConstraints:self.dynamicConstraints];
|
||||
|
||||
NSTextField* nameField = self.nameField;
|
||||
NSTextField* statusField = self.statusField;
|
||||
|
||||
if (node.isFolder)
|
||||
{
|
||||
// For folders, status appears next to name, both centered
|
||||
self.statusField.hidden = NO;
|
||||
self.dynamicConstraints = @[
|
||||
[nameField.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
|
||||
[nameField.trailingAnchor constraintLessThanOrEqualToAnchor:statusField.leadingAnchor
|
||||
constant:-kPaddingBetweenNameAndFolderStatus],
|
||||
|
||||
[statusField.leadingAnchor constraintEqualToAnchor:nameField.trailingAnchor constant:kPaddingBetweenNameAndFolderStatus],
|
||||
[statusField.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
|
||||
[statusField.trailingAnchor constraintLessThanOrEqualToAnchor:self.trailingAnchor],
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
// For files, status appears below name
|
||||
self.statusField.hidden = NO;
|
||||
self.dynamicConstraints = @[
|
||||
[nameField.topAnchor constraintEqualToAnchor:self.topAnchor constant:kPaddingAboveTitleFile],
|
||||
[nameField.trailingAnchor constraintLessThanOrEqualToAnchor:self.trailingAnchor],
|
||||
|
||||
[statusField.leadingAnchor constraintEqualToAnchor:nameField.leadingAnchor],
|
||||
[statusField.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
|
||||
[statusField.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-kPaddingBelowStatusFile],
|
||||
];
|
||||
}
|
||||
|
||||
[NSLayoutConstraint activateConstraints:self.dynamicConstraints];
|
||||
|
||||
// Update colors based on background style and check state
|
||||
[self updateColors];
|
||||
|
||||
// Update tooltip
|
||||
[self updateTooltip];
|
||||
}
|
||||
|
||||
- (void)updateTooltip
|
||||
{
|
||||
if (!self.node)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FileListNode* node = self.node;
|
||||
Torrent* torrent = node.torrent;
|
||||
|
||||
NSString* path = [torrent fileLocation:node];
|
||||
if (!path)
|
||||
{
|
||||
path = [node.path stringByAppendingPathComponent:node.name];
|
||||
}
|
||||
self.toolTip = path;
|
||||
}
|
||||
|
||||
- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle
|
||||
{
|
||||
[super setBackgroundStyle:backgroundStyle];
|
||||
[self updateColors];
|
||||
}
|
||||
|
||||
- (void)updateColors
|
||||
{
|
||||
if (!self.node)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FileListNode* node = self.node;
|
||||
Torrent* torrent = node.torrent;
|
||||
|
||||
if (self.backgroundStyle == NSBackgroundStyleEmphasized)
|
||||
{
|
||||
self.nameField.textColor = NSColor.whiteColor;
|
||||
self.statusField.textColor = NSColor.whiteColor;
|
||||
}
|
||||
else if ([torrent checkForFiles:node.indexes] == NSControlStateValueOff)
|
||||
{
|
||||
self.nameField.textColor = NSColor.disabledControlTextColor;
|
||||
self.statusField.textColor = NSColor.disabledControlTextColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
self.nameField.textColor = NSColor.controlTextColor;
|
||||
self.statusField.textColor = NSColor.secondaryLabelColor;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -13,7 +13,7 @@
|
||||
@property(nonatomic) Torrent* torrent;
|
||||
@property(nonatomic) NSString* filterText;
|
||||
|
||||
- (void)refresh;
|
||||
- (void)reloadVisibleRows;
|
||||
|
||||
- (void)setCheck:(id)sender;
|
||||
- (void)setOnlySelectedCheck:(id)sender;
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
#import "Torrent.h"
|
||||
#import "FileListNode.h"
|
||||
#import "FileOutlineView.h"
|
||||
#import "FilePriorityCell.h"
|
||||
#import "FileNameCellView.h"
|
||||
#import "FilePriorityCellView.h"
|
||||
#import "FileCheckCellView.h"
|
||||
#import "FileRenameSheetController.h"
|
||||
#import "NSMutableArrayAdditions.h"
|
||||
#import "NSStringAdditions.h"
|
||||
@@ -24,7 +26,7 @@ typedef NS_ENUM(NSUInteger, FilePriorityMenuTag) { //
|
||||
FilePriorityMenuTagLow
|
||||
};
|
||||
|
||||
@interface FileOutlineController ()
|
||||
@interface FileOutlineController ()<NSOutlineViewDelegate, NSOutlineViewDataSource, NSMenuItemValidation>
|
||||
|
||||
@property(nonatomic) NSMutableArray<FileListNode*>* fFileList;
|
||||
|
||||
@@ -186,18 +188,18 @@ typedef NS_ENUM(NSUInteger, FilePriorityMenuTag) { //
|
||||
_filterText = text;
|
||||
}
|
||||
|
||||
- (void)refresh
|
||||
- (void)reloadVisibleRows
|
||||
{
|
||||
self.fOutline.needsDisplay = YES;
|
||||
NSRect visibleRect = self.fOutline.visibleRect;
|
||||
NSRange range = [self.fOutline rowsInRect:visibleRect];
|
||||
|
||||
NSIndexSet* rowIndexes = [NSIndexSet indexSetWithIndexesInRange:range];
|
||||
NSIndexSet* columnIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.fOutline.numberOfColumns)];
|
||||
|
||||
[self.fOutline reloadDataForRowIndexes:rowIndexes columnIndexes:columnIndexes];
|
||||
}
|
||||
|
||||
- (void)outlineViewSelectionDidChange:(NSNotification*)notification
|
||||
{
|
||||
if ([QLPreviewPanel sharedPreviewPanelExists] && [QLPreviewPanel sharedPreviewPanel].visible)
|
||||
{
|
||||
[[QLPreviewPanel sharedPreviewPanel] reloadData];
|
||||
}
|
||||
}
|
||||
#pragma mark - NSOutlineViewDataSource
|
||||
|
||||
- (NSInteger)outlineView:(NSOutlineView*)outlineView numberOfChildrenOfItem:(id)item
|
||||
{
|
||||
@@ -222,61 +224,51 @@ typedef NS_ENUM(NSUInteger, FilePriorityMenuTag) { //
|
||||
return (item ? ((FileListNode*)item).children : self.fFileList)[index];
|
||||
}
|
||||
|
||||
- (id)outlineView:(NSOutlineView*)outlineView objectValueForTableColumn:(NSTableColumn*)tableColumn byItem:(id)item
|
||||
{
|
||||
if ([tableColumn.identifier isEqualToString:@"Check"])
|
||||
{
|
||||
return @([self.torrent checkForFiles:((FileListNode*)item).indexes]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
#pragma mark - NSOutlineViewDelegate
|
||||
|
||||
- (void)outlineView:(NSOutlineView*)outlineView
|
||||
willDisplayCell:(id)cell
|
||||
forTableColumn:(NSTableColumn*)tableColumn
|
||||
item:(id)item
|
||||
- (NSView*)outlineView:(NSOutlineView*)outlineView viewForTableColumn:(NSTableColumn*)tableColumn item:(id)item
|
||||
{
|
||||
NSString* identifier = tableColumn.identifier;
|
||||
if ([identifier isEqualToString:@"Check"])
|
||||
FileListNode* node = (FileListNode*)item;
|
||||
|
||||
if ([identifier isEqualToString:@"Name"])
|
||||
{
|
||||
[cell setEnabled:[self.torrent canChangeDownloadCheckForFiles:((FileListNode*)item).indexes]];
|
||||
FileNameCellView* cellView = [outlineView makeViewWithIdentifier:@"NameCell" owner:self];
|
||||
if (!cellView)
|
||||
{
|
||||
cellView = [[FileNameCellView alloc] initWithFrame:NSZeroRect];
|
||||
cellView.identifier = @"NameCell";
|
||||
}
|
||||
cellView.node = node;
|
||||
|
||||
return cellView;
|
||||
}
|
||||
else if ([identifier isEqualToString:@"Priority"])
|
||||
{
|
||||
[cell setRepresentedObject:item];
|
||||
FilePriorityCellView* cellView = [outlineView makeViewWithIdentifier:@"PriorityCell" owner:self];
|
||||
if (!cellView)
|
||||
{
|
||||
cellView = [[FilePriorityCellView alloc] initWithFrame:NSZeroRect];
|
||||
cellView.identifier = @"PriorityCell";
|
||||
}
|
||||
cellView.node = node;
|
||||
|
||||
NSInteger hoveredRow = self.fOutline.hoveredRow;
|
||||
((FilePriorityCell*)cell).hovered = hoveredRow != -1 && hoveredRow == [self.fOutline rowForItem:item];
|
||||
return cellView;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)outlineView:(NSOutlineView*)outlineView
|
||||
setObjectValue:(id)object
|
||||
forTableColumn:(NSTableColumn*)tableColumn
|
||||
byItem:(id)item
|
||||
{
|
||||
NSString* identifier = tableColumn.identifier;
|
||||
if ([identifier isEqualToString:@"Check"])
|
||||
else if ([identifier isEqualToString:@"Check"])
|
||||
{
|
||||
NSIndexSet* indexSet;
|
||||
if (NSEvent.modifierFlags & NSEventModifierFlagOption)
|
||||
FileCheckCellView* cellView = [outlineView makeViewWithIdentifier:@"CheckCell" owner:self];
|
||||
if (!cellView)
|
||||
{
|
||||
indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.torrent.fileCount)];
|
||||
}
|
||||
else
|
||||
{
|
||||
indexSet = ((FileListNode*)item).indexes;
|
||||
cellView = [[FileCheckCellView alloc] initWithFrame:NSZeroRect];
|
||||
cellView.identifier = @"CheckCell";
|
||||
}
|
||||
cellView.node = node;
|
||||
|
||||
[self.torrent setFileCheckState:[object intValue] != NSControlStateValueOff ? NSControlStateValueOn : NSControlStateValueOff
|
||||
forIndexes:indexSet];
|
||||
self.fOutline.needsDisplay = YES;
|
||||
|
||||
[NSNotificationCenter.defaultCenter postNotificationName:@"UpdateUI" object:nil];
|
||||
return cellView;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString*)outlineView:(NSOutlineView*)outlineView typeSelectStringForTableColumn:(NSTableColumn*)tableColumn item:(id)item
|
||||
@@ -284,60 +276,13 @@ typedef NS_ENUM(NSUInteger, FilePriorityMenuTag) { //
|
||||
return ((FileListNode*)item).name;
|
||||
}
|
||||
|
||||
- (NSString*)outlineView:(NSOutlineView*)outlineView
|
||||
toolTipForCell:(NSCell*)cell
|
||||
rect:(NSRectPointer)rect
|
||||
tableColumn:(NSTableColumn*)tableColumn
|
||||
item:(id)item
|
||||
mouseLocation:(NSPoint)mouseLocation
|
||||
- (void)outlineViewSelectionDidChange:(NSNotification*)notification
|
||||
{
|
||||
NSString* ident = tableColumn.identifier;
|
||||
if ([ident isEqualToString:@"Name"])
|
||||
[self reloadVisibleRows];
|
||||
if ([QLPreviewPanel sharedPreviewPanelExists] && [QLPreviewPanel sharedPreviewPanel].visible)
|
||||
{
|
||||
NSString* path = [self.torrent fileLocation:item];
|
||||
if (!path)
|
||||
{
|
||||
FileListNode* node = (FileListNode*)item;
|
||||
path = [node.path stringByAppendingPathComponent:node.name];
|
||||
}
|
||||
return path;
|
||||
[[QLPreviewPanel sharedPreviewPanel] reloadData];
|
||||
}
|
||||
else if ([ident isEqualToString:@"Check"])
|
||||
{
|
||||
switch (cell.state)
|
||||
{
|
||||
case NSControlStateValueOff:
|
||||
return NSLocalizedString(@"Don't Download", "files tab -> tooltip");
|
||||
case NSControlStateValueOn:
|
||||
return NSLocalizedString(@"Download", "files tab -> tooltip");
|
||||
case NSControlStateValueMixed:
|
||||
return NSLocalizedString(@"Download Some", "files tab -> tooltip");
|
||||
}
|
||||
}
|
||||
else if ([ident isEqualToString:@"Priority"])
|
||||
{
|
||||
NSSet* priorities = [self.torrent filePrioritiesForIndexes:((FileListNode*)item).indexes];
|
||||
switch (priorities.count)
|
||||
{
|
||||
case 0:
|
||||
return NSLocalizedString(@"Priority Not Available", "files tab -> tooltip");
|
||||
case 1:
|
||||
switch ([[priorities anyObject] intValue])
|
||||
{
|
||||
case TR_PRI_LOW:
|
||||
return NSLocalizedString(@"Low Priority", "files tab -> tooltip");
|
||||
case TR_PRI_HIGH:
|
||||
return NSLocalizedString(@"High Priority", "files tab -> tooltip");
|
||||
case TR_PRI_NORMAL:
|
||||
return NSLocalizedString(@"Normal Priority", "files tab -> tooltip");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return NSLocalizedString(@"Multiple Priorities", "files tab -> tooltip");
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CGFloat)outlineView:(NSOutlineView*)outlineView heightOfRowByItem:(id)item
|
||||
@@ -352,9 +297,11 @@ typedef NS_ENUM(NSUInteger, FilePriorityMenuTag) { //
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)setCheck:(id)sender
|
||||
{
|
||||
NSInteger state = [sender tag] == FileCheckMenuTagUncheck ? NSControlStateValueOff : NSControlStateValueOn;
|
||||
NSControlStateValue state = [sender tag] == FileCheckMenuTagUncheck ? NSControlStateValueOff : NSControlStateValueOn;
|
||||
|
||||
NSIndexSet* indexSet = self.fOutline.selectedRowIndexes;
|
||||
NSMutableIndexSet* itemIndexes = [NSMutableIndexSet indexSet];
|
||||
@@ -365,7 +312,8 @@ typedef NS_ENUM(NSUInteger, FilePriorityMenuTag) { //
|
||||
}
|
||||
|
||||
[self.torrent setFileCheckState:state forIndexes:itemIndexes];
|
||||
self.fOutline.needsDisplay = YES;
|
||||
|
||||
[self reloadVisibleRows];
|
||||
}
|
||||
|
||||
- (void)setOnlySelectedCheck:(id)sender
|
||||
@@ -384,21 +332,23 @@ typedef NS_ENUM(NSUInteger, FilePriorityMenuTag) { //
|
||||
[remainingItemIndexes removeIndexes:itemIndexes];
|
||||
[self.torrent setFileCheckState:NSControlStateValueOff forIndexes:remainingItemIndexes];
|
||||
|
||||
self.fOutline.needsDisplay = YES;
|
||||
[self reloadVisibleRows];
|
||||
}
|
||||
|
||||
- (void)checkAll
|
||||
{
|
||||
NSIndexSet* indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.torrent.fileCount)];
|
||||
[self.torrent setFileCheckState:NSControlStateValueOn forIndexes:indexSet];
|
||||
self.fOutline.needsDisplay = YES;
|
||||
|
||||
[self reloadVisibleRows];
|
||||
}
|
||||
|
||||
- (void)uncheckAll
|
||||
{
|
||||
NSIndexSet* indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.torrent.fileCount)];
|
||||
[self.torrent setFileCheckState:NSControlStateValueOff forIndexes:indexSet];
|
||||
self.fOutline.needsDisplay = YES;
|
||||
|
||||
[self reloadVisibleRows];
|
||||
}
|
||||
|
||||
- (void)setPriority:(id)sender
|
||||
@@ -429,7 +379,8 @@ typedef NS_ENUM(NSUInteger, FilePriorityMenuTag) { //
|
||||
}
|
||||
|
||||
[self.torrent setFilePriority:priority forIndexes:itemIndexes];
|
||||
self.fOutline.needsDisplay = YES;
|
||||
|
||||
[self reloadVisibleRows];
|
||||
}
|
||||
|
||||
- (void)revealFile:(id)sender
|
||||
@@ -480,6 +431,8 @@ typedef NS_ENUM(NSUInteger, FilePriorityMenuTag) { //
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - NSMenuItemValidation
|
||||
|
||||
#warning make real view controller (Leopard-only) so that Command-R will work
|
||||
- (BOOL)validateMenuItem:(NSMenuItem*)menuItem
|
||||
{
|
||||
@@ -518,7 +471,7 @@ typedef NS_ENUM(NSUInteger, FilePriorityMenuTag) { //
|
||||
[itemIndexes addIndexes:node.indexes];
|
||||
}
|
||||
|
||||
NSInteger state = (menuItem.tag == FileCheckMenuTagCheck) ? NSControlStateValueOn : NSControlStateValueOff;
|
||||
NSControlStateValue state = (menuItem.tag == FileCheckMenuTagCheck) ? NSControlStateValueOn : NSControlStateValueOff;
|
||||
return [self.torrent checkForFiles:itemIndexes] != state && [self.torrent canChangeDownloadCheckForFiles:itemIndexes];
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
@interface FileOutlineView : NSOutlineView
|
||||
|
||||
@property(nonatomic, readonly) NSInteger hoveredRow;
|
||||
|
||||
- (NSRect)iconRectForRow:(NSInteger)row;
|
||||
|
||||
@end
|
||||
|
||||
@@ -4,15 +4,13 @@
|
||||
|
||||
#import "InfoWindowController.h"
|
||||
#import "FileListNode.h"
|
||||
#import "FileNameCell.h"
|
||||
#import "FileNameCellView.h"
|
||||
#import "FileOutlineView.h"
|
||||
#import "FilePriorityCell.h"
|
||||
#import "FilePriorityCellView.h"
|
||||
#import "Torrent.h"
|
||||
|
||||
@interface FileOutlineView ()
|
||||
|
||||
@property(nonatomic) NSInteger hoveredRow;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FileOutlineView
|
||||
@@ -20,16 +18,9 @@
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
[super awakeFromNib];
|
||||
FileNameCell* nameCell = [[FileNameCell alloc] init];
|
||||
[self tableColumnWithIdentifier:@"Name"].dataCell = nameCell;
|
||||
|
||||
FilePriorityCell* priorityCell = [[FilePriorityCell alloc] init];
|
||||
[self tableColumnWithIdentifier:@"Priority"].dataCell = priorityCell;
|
||||
|
||||
self.autoresizesOutlineColumn = NO;
|
||||
self.indentationPerLevel = 14.0;
|
||||
|
||||
self.hoveredRow = -1;
|
||||
}
|
||||
|
||||
- (void)mouseDown:(NSEvent*)event
|
||||
@@ -59,61 +50,22 @@
|
||||
|
||||
- (NSRect)iconRectForRow:(NSInteger)row
|
||||
{
|
||||
FileNameCell* cell = (FileNameCell*)[self preparedCellAtColumn:[self columnWithIdentifier:@"Name"] row:row];
|
||||
NSRect iconRect = [cell imageRectForBounds:[self rectOfRow:row]];
|
||||
NSView* view = [self viewAtColumn:[self columnWithIdentifier:@"Name"] row:row makeIfNecessary:NO];
|
||||
if (![view isKindOfClass:[FileNameCellView class]])
|
||||
{
|
||||
return NSZeroRect;
|
||||
}
|
||||
|
||||
FileNameCellView* cellView = (FileNameCellView*)view;
|
||||
NSImageView* iconView = [cellView valueForKey:@"iconView"];
|
||||
if (!iconView)
|
||||
{
|
||||
return NSZeroRect;
|
||||
}
|
||||
|
||||
NSRect iconRect = [self convertRect:iconView.frame fromView:cellView];
|
||||
iconRect.origin.x += self.indentationPerLevel * (CGFloat)([self levelForRow:row] + 1);
|
||||
return iconRect;
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
[super updateTrackingAreas];
|
||||
|
||||
for (NSTrackingArea* area in self.trackingAreas)
|
||||
{
|
||||
if (area.owner == self && area.userInfo[@"Row"])
|
||||
{
|
||||
[self removeTrackingArea:area];
|
||||
}
|
||||
}
|
||||
|
||||
NSRange visibleRows = [self rowsInRect:self.visibleRect];
|
||||
if (visibleRows.length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NSPoint mouseLocation = [self convertPoint:self.window.mouseLocationOutsideOfEventStream fromView:nil];
|
||||
|
||||
for (NSInteger row = visibleRows.location, col = [self columnWithIdentifier:@"Priority"]; (NSUInteger)row < NSMaxRange(visibleRows); row++)
|
||||
{
|
||||
FilePriorityCell* cell = (FilePriorityCell*)[self preparedCellAtColumn:col row:row];
|
||||
|
||||
NSDictionary* userInfo = @{ @"Row" : @(row) };
|
||||
[cell addTrackingAreasForView:self inRect:[self frameOfCellAtColumn:col row:row] withUserInfo:userInfo
|
||||
mouseLocation:mouseLocation];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(NSEvent*)event
|
||||
{
|
||||
NSNumber* row;
|
||||
if ((row = ((NSDictionary*)event.userData)[@"Row"]))
|
||||
{
|
||||
self.hoveredRow = row.intValue;
|
||||
[self setNeedsDisplayInRect:[self frameOfCellAtColumn:[self columnWithIdentifier:@"Priority"] row:self.hoveredRow]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent*)event
|
||||
{
|
||||
NSNumber* row;
|
||||
if ((row = ((NSDictionary*)event.userData)[@"Row"]))
|
||||
{
|
||||
[self setNeedsDisplayInRect:[self frameOfCellAtColumn:[self columnWithIdentifier:@"Priority"] row:row.intValue]];
|
||||
self.hoveredRow = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
// This file Copyright © Transmission authors and contributors.
|
||||
// It may be used under the MIT (SPDX: MIT) license.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
@interface FilePriorityCell : NSSegmentedCell
|
||||
|
||||
@property(nonatomic) BOOL hovered;
|
||||
|
||||
- (void)addTrackingAreasForView:(NSView*)controlView
|
||||
inRect:(NSRect)cellFrame
|
||||
withUserInfo:(NSDictionary*)userInfo
|
||||
mouseLocation:(NSPoint)mouseLocation;
|
||||
|
||||
@end
|
||||
@@ -1,171 +0,0 @@
|
||||
// This file Copyright © Transmission authors and contributors.
|
||||
// It may be used under the MIT (SPDX: MIT) license.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#import "FilePriorityCell.h"
|
||||
#import "FileOutlineView.h"
|
||||
#import "FileListNode.h"
|
||||
#import "NSImageAdditions.h"
|
||||
#import "Torrent.h"
|
||||
|
||||
static CGFloat const kImageOverlap = 1.0;
|
||||
|
||||
@implementation FilePriorityCell
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
self.trackingMode = NSSegmentSwitchTrackingSelectAny;
|
||||
self.controlSize = NSControlSizeMini;
|
||||
self.segmentCount = 3;
|
||||
|
||||
for (NSInteger i = 0; i < self.segmentCount; i++)
|
||||
{
|
||||
[self setLabel:@"" forSegment:i];
|
||||
[self setWidth:9.0f forSegment:i]; //9 is minimum size to get proper look
|
||||
}
|
||||
|
||||
[self setImage:[NSImage imageNamed:@"PriorityControlLow"] forSegment:0];
|
||||
[self setImage:[NSImage imageNamed:@"PriorityControlNormal"] forSegment:1];
|
||||
[self setImage:[NSImage imageNamed:@"PriorityControlHigh"] forSegment:2];
|
||||
|
||||
_hovered = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone*)zone
|
||||
{
|
||||
FilePriorityCell* copy = [super copyWithZone:zone];
|
||||
[copy setRepresentedObject:self.representedObject];
|
||||
return copy;
|
||||
}
|
||||
|
||||
- (void)setSelected:(BOOL)flag forSegment:(NSInteger)segment
|
||||
{
|
||||
[super setSelected:flag forSegment:segment];
|
||||
|
||||
//only for when clicking manually
|
||||
tr_priority_t priority;
|
||||
switch (segment)
|
||||
{
|
||||
case 0:
|
||||
priority = TR_PRI_LOW;
|
||||
break;
|
||||
case 1:
|
||||
priority = TR_PRI_NORMAL;
|
||||
break;
|
||||
case 2:
|
||||
priority = TR_PRI_HIGH;
|
||||
break;
|
||||
default:
|
||||
NSAssert1(NO, @"Unknown segment: %ld", segment);
|
||||
return;
|
||||
}
|
||||
|
||||
FileListNode* node = self.representedObject;
|
||||
Torrent* torrent = node.torrent;
|
||||
[torrent setFilePriority:priority forIndexes:node.indexes];
|
||||
|
||||
FileOutlineView* controlView = (FileOutlineView*)self.controlView;
|
||||
controlView.needsDisplay = YES;
|
||||
}
|
||||
|
||||
- (void)addTrackingAreasForView:(NSView*)controlView
|
||||
inRect:(NSRect)cellFrame
|
||||
withUserInfo:(NSDictionary*)userInfo
|
||||
mouseLocation:(NSPoint)mouseLocation
|
||||
{
|
||||
NSTrackingAreaOptions options = NSTrackingEnabledDuringMouseDrag | NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways;
|
||||
|
||||
if (NSMouseInRect(mouseLocation, cellFrame, controlView.flipped))
|
||||
{
|
||||
options |= NSTrackingAssumeInside;
|
||||
[controlView setNeedsDisplayInRect:cellFrame];
|
||||
}
|
||||
|
||||
NSTrackingArea* area = [[NSTrackingArea alloc] initWithRect:cellFrame options:options owner:controlView userInfo:userInfo];
|
||||
[controlView addTrackingArea:area];
|
||||
}
|
||||
|
||||
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView
|
||||
{
|
||||
FileListNode* node = self.representedObject;
|
||||
Torrent* torrent = node.torrent;
|
||||
NSSet* priorities = [torrent filePrioritiesForIndexes:node.indexes];
|
||||
|
||||
NSUInteger const count = priorities.count;
|
||||
if (self.hovered && count > 0)
|
||||
{
|
||||
[super setSelected:[priorities containsObject:@(TR_PRI_LOW)] forSegment:0];
|
||||
[super setSelected:[priorities containsObject:@(TR_PRI_NORMAL)] forSegment:1];
|
||||
[super setSelected:[priorities containsObject:@(TR_PRI_HIGH)] forSegment:2];
|
||||
|
||||
[super drawWithFrame:cellFrame inView:controlView];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSMutableArray* images = [NSMutableArray arrayWithCapacity:MAX(count, 1u)];
|
||||
CGFloat totalWidth;
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
//if ([self backgroundStyle] != NSBackgroundStyleEmphasized)
|
||||
{
|
||||
NSImage* image = [[NSImage imageNamed:@"PriorityNormalTemplate"] imageWithColor:NSColor.lightGrayColor];
|
||||
[images addObject:image];
|
||||
totalWidth = image.size.width;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NSColor* priorityColor = self.backgroundStyle == NSBackgroundStyleEmphasized ? NSColor.whiteColor : NSColor.darkGrayColor;
|
||||
|
||||
totalWidth = 0.0;
|
||||
if ([priorities containsObject:@(TR_PRI_LOW)])
|
||||
{
|
||||
NSImage* image = [[NSImage imageNamed:@"PriorityLowTemplate"] imageWithColor:priorityColor];
|
||||
[images addObject:image];
|
||||
totalWidth += image.size.width;
|
||||
}
|
||||
if ([priorities containsObject:@(TR_PRI_NORMAL)])
|
||||
{
|
||||
NSImage* image = [[NSImage imageNamed:@"PriorityNormalTemplate"] imageWithColor:priorityColor];
|
||||
[images addObject:image];
|
||||
totalWidth += image.size.width;
|
||||
}
|
||||
if ([priorities containsObject:@(TR_PRI_HIGH)])
|
||||
{
|
||||
NSImage* image = [[NSImage imageNamed:@"PriorityHighTemplate"] imageWithColor:priorityColor];
|
||||
[images addObject:image];
|
||||
totalWidth += image.size.width;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 1)
|
||||
{
|
||||
totalWidth -= kImageOverlap * (count - 1);
|
||||
}
|
||||
|
||||
CGFloat currentWidth = floor(NSMidX(cellFrame) - totalWidth * 0.5);
|
||||
|
||||
for (NSImage* image in images)
|
||||
{
|
||||
NSSize const imageSize = image.size;
|
||||
NSRect const imageRect = NSMakeRect(
|
||||
currentWidth,
|
||||
floor(NSMidY(cellFrame) - imageSize.height * 0.5),
|
||||
imageSize.width,
|
||||
imageSize.height);
|
||||
|
||||
[image drawInRect:imageRect fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0
|
||||
respectFlipped:YES
|
||||
hints:nil];
|
||||
|
||||
currentWidth += imageSize.width - kImageOverlap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
14
macosx/FilePriorityCellView.h
Normal file
14
macosx/FilePriorityCellView.h
Normal file
@@ -0,0 +1,14 @@
|
||||
// This file Copyright © Transmission authors and contributors.
|
||||
// It may be used under the MIT (SPDX: MIT) license.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
@class FileListNode;
|
||||
|
||||
@interface FilePriorityCellView : NSTableCellView
|
||||
|
||||
@property(nonatomic, weak) FileListNode* node;
|
||||
@property(nonatomic) BOOL hovered;
|
||||
|
||||
@end
|
||||
313
macosx/FilePriorityCellView.mm
Normal file
313
macosx/FilePriorityCellView.mm
Normal file
@@ -0,0 +1,313 @@
|
||||
// This file Copyright © Transmission authors and contributors.
|
||||
// It may be used under the MIT (SPDX: MIT) license.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#import "FilePriorityCellView.h"
|
||||
#import "FileListNode.h"
|
||||
#import "NSImageAdditions.h"
|
||||
#import "Torrent.h"
|
||||
|
||||
static CGFloat const kImageOverlap = 1.0;
|
||||
|
||||
@interface FilePriorityCellView ()
|
||||
@property(nonatomic, weak) NSSegmentedControl* segmentedControl;
|
||||
@property(nonatomic, weak) NSView* iconsContainerView;
|
||||
@property(nonatomic, strong) NSTrackingArea* trackingArea;
|
||||
@end
|
||||
|
||||
@implementation FilePriorityCellView
|
||||
|
||||
- (instancetype)initWithFrame:(NSRect)frameRect
|
||||
{
|
||||
if ((self = [super initWithFrame:frameRect]))
|
||||
{
|
||||
// Create segmented control for hover state
|
||||
NSSegmentedControl* segmentedControl = [[NSSegmentedControl alloc] initWithFrame:NSZeroRect];
|
||||
segmentedControl.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
segmentedControl.trackingMode = NSSegmentSwitchTrackingSelectAny;
|
||||
segmentedControl.controlSize = NSControlSizeMini;
|
||||
segmentedControl.segmentCount = 3;
|
||||
|
||||
for (NSInteger i = 0; i < segmentedControl.segmentCount; i++)
|
||||
{
|
||||
[segmentedControl setLabel:@"" forSegment:i];
|
||||
[segmentedControl setWidth:9.0f forSegment:i];
|
||||
}
|
||||
|
||||
[segmentedControl setImage:[NSImage imageNamed:@"PriorityControlLow"] forSegment:0];
|
||||
[segmentedControl setImage:[NSImage imageNamed:@"PriorityControlNormal"] forSegment:1];
|
||||
[segmentedControl setImage:[NSImage imageNamed:@"PriorityControlHigh"] forSegment:2];
|
||||
|
||||
segmentedControl.target = self;
|
||||
segmentedControl.action = @selector(segmentedControlClicked:);
|
||||
segmentedControl.hidden = YES;
|
||||
|
||||
[self addSubview:segmentedControl];
|
||||
_segmentedControl = segmentedControl;
|
||||
|
||||
// Create container view for priority icons
|
||||
NSView* iconsContainerView = [[NSView alloc] initWithFrame:NSZeroRect];
|
||||
iconsContainerView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self addSubview:iconsContainerView];
|
||||
_iconsContainerView = iconsContainerView;
|
||||
|
||||
// Setup constraints
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[segmentedControl.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
|
||||
[segmentedControl.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
|
||||
|
||||
[iconsContainerView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
|
||||
[iconsContainerView.centerYAnchor constraintEqualToAnchor:self.centerYAnchor],
|
||||
[iconsContainerView.widthAnchor constraintLessThanOrEqualToAnchor:self.widthAnchor],
|
||||
[iconsContainerView.heightAnchor constraintLessThanOrEqualToAnchor:self.heightAnchor],
|
||||
]];
|
||||
|
||||
_hovered = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setNode:(FileListNode*)node
|
||||
{
|
||||
_node = node;
|
||||
|
||||
[self updateDisplay];
|
||||
}
|
||||
|
||||
- (void)setHovered:(BOOL)hovered
|
||||
{
|
||||
_hovered = hovered;
|
||||
|
||||
[self updateDisplay];
|
||||
}
|
||||
|
||||
- (void)updateDisplay
|
||||
{
|
||||
if (!self.node)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FileListNode* node = self.node;
|
||||
Torrent* torrent = node.torrent;
|
||||
NSSet* priorities = [torrent filePrioritiesForIndexes:node.indexes];
|
||||
|
||||
NSUInteger const count = priorities.count;
|
||||
if (self.hovered && count > 0)
|
||||
{
|
||||
// Show segmented control
|
||||
self.segmentedControl.hidden = NO;
|
||||
self.iconsContainerView.hidden = YES;
|
||||
|
||||
[self.segmentedControl setSelected:[priorities containsObject:@(TR_PRI_LOW)] forSegment:0];
|
||||
[self.segmentedControl setSelected:[priorities containsObject:@(TR_PRI_NORMAL)] forSegment:1];
|
||||
[self.segmentedControl setSelected:[priorities containsObject:@(TR_PRI_HIGH)] forSegment:2];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Show static priority icons
|
||||
self.segmentedControl.hidden = YES;
|
||||
self.iconsContainerView.hidden = NO;
|
||||
|
||||
[self updatePriorityIcons:priorities];
|
||||
}
|
||||
|
||||
// Update tooltip
|
||||
[self updateTooltip];
|
||||
}
|
||||
|
||||
- (void)updatePriorityIcons:(NSSet*)priorities
|
||||
{
|
||||
// Remove all existing image views
|
||||
for (NSView* subview in self.iconsContainerView.subviews)
|
||||
{
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
|
||||
NSUInteger const count = priorities.count;
|
||||
NSMutableArray* images = [NSMutableArray arrayWithCapacity:MAX(count, 1u)];
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
NSImage* image = [[NSImage imageNamed:@"PriorityNormalTemplate"] imageWithColor:NSColor.lightGrayColor];
|
||||
[images addObject:image];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSColor* priorityColor = self.backgroundStyle == NSBackgroundStyleEmphasized ? NSColor.whiteColor : NSColor.darkGrayColor;
|
||||
|
||||
if ([priorities containsObject:@(TR_PRI_LOW)])
|
||||
{
|
||||
NSImage* image = [[NSImage imageNamed:@"PriorityLowTemplate"] imageWithColor:priorityColor];
|
||||
[images addObject:image];
|
||||
}
|
||||
if ([priorities containsObject:@(TR_PRI_NORMAL)])
|
||||
{
|
||||
NSImage* image = [[NSImage imageNamed:@"PriorityNormalTemplate"] imageWithColor:priorityColor];
|
||||
[images addObject:image];
|
||||
}
|
||||
if ([priorities containsObject:@(TR_PRI_HIGH)])
|
||||
{
|
||||
NSImage* image = [[NSImage imageNamed:@"PriorityHighTemplate"] imageWithColor:priorityColor];
|
||||
[images addObject:image];
|
||||
}
|
||||
}
|
||||
|
||||
NSView* previousView = nil;
|
||||
|
||||
for (NSImage* image in images)
|
||||
{
|
||||
NSImageView* imageView = [[NSImageView alloc] initWithFrame:NSZeroRect];
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
imageView.image = image;
|
||||
[self.iconsContainerView addSubview:imageView];
|
||||
|
||||
NSSize const imageSize = image.size;
|
||||
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[imageView.widthAnchor constraintEqualToConstant:imageSize.width],
|
||||
[imageView.heightAnchor constraintEqualToConstant:imageSize.height],
|
||||
[imageView.centerYAnchor constraintEqualToAnchor:self.iconsContainerView.centerYAnchor],
|
||||
]];
|
||||
|
||||
if (previousView == nil)
|
||||
{
|
||||
[imageView.leadingAnchor constraintEqualToAnchor:self.iconsContainerView.leadingAnchor].active = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
[imageView.leadingAnchor constraintEqualToAnchor:previousView.trailingAnchor constant:-kImageOverlap].active = YES;
|
||||
}
|
||||
|
||||
previousView = imageView;
|
||||
}
|
||||
|
||||
if (previousView)
|
||||
{
|
||||
[previousView.trailingAnchor constraintEqualToAnchor:self.iconsContainerView.trailingAnchor].active = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)segmentedControlClicked:(NSSegmentedControl*)sender
|
||||
{
|
||||
NSInteger segment = sender.selectedSegment;
|
||||
if (segment == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tr_priority_t priority;
|
||||
switch (segment)
|
||||
{
|
||||
case 0:
|
||||
priority = TR_PRI_LOW;
|
||||
break;
|
||||
case 1:
|
||||
priority = TR_PRI_NORMAL;
|
||||
break;
|
||||
case 2:
|
||||
priority = TR_PRI_HIGH;
|
||||
break;
|
||||
default:
|
||||
NSAssert1(NO, @"Unknown segment: %ld", segment);
|
||||
return;
|
||||
}
|
||||
|
||||
FileListNode* node = self.node;
|
||||
Torrent* torrent = node.torrent;
|
||||
[torrent setFilePriority:priority forIndexes:node.indexes];
|
||||
|
||||
// Notify that we need to refresh
|
||||
[NSNotificationCenter.defaultCenter postNotificationName:@"UpdateUI" object:nil];
|
||||
}
|
||||
|
||||
- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle
|
||||
{
|
||||
[super setBackgroundStyle:backgroundStyle];
|
||||
[self updateDisplay];
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
[super updateTrackingAreas];
|
||||
|
||||
if (self.trackingArea)
|
||||
{
|
||||
[self removeTrackingArea:self.trackingArea];
|
||||
}
|
||||
|
||||
NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInActiveApp;
|
||||
|
||||
// Check if mouse is currently inside the bounds
|
||||
NSPoint mouseLocation = [self.window mouseLocationOutsideOfEventStream];
|
||||
NSPoint localPoint = [self convertPoint:mouseLocation fromView:nil];
|
||||
if (NSPointInRect(localPoint, self.bounds))
|
||||
{
|
||||
options |= NSTrackingAssumeInside;
|
||||
if (!self.hovered)
|
||||
{
|
||||
self.hovered = YES;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Mouse is not inside, reset hovered state
|
||||
if (self.hovered)
|
||||
{
|
||||
self.hovered = NO;
|
||||
}
|
||||
}
|
||||
|
||||
self.trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:options owner:self userInfo:nil];
|
||||
[self addTrackingArea:self.trackingArea];
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(NSEvent*)event
|
||||
{
|
||||
self.hovered = YES;
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent*)event
|
||||
{
|
||||
self.hovered = NO;
|
||||
}
|
||||
|
||||
- (void)updateTooltip
|
||||
{
|
||||
if (!self.node)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FileListNode* node = self.node;
|
||||
Torrent* torrent = node.torrent;
|
||||
NSSet* priorities = [torrent filePrioritiesForIndexes:node.indexes];
|
||||
|
||||
NSString* tooltip = nil;
|
||||
switch (priorities.count)
|
||||
{
|
||||
case 0:
|
||||
tooltip = NSLocalizedString(@"Priority Not Available", "files tab -> tooltip");
|
||||
break;
|
||||
case 1:
|
||||
switch ([[priorities anyObject] intValue])
|
||||
{
|
||||
case TR_PRI_LOW:
|
||||
tooltip = NSLocalizedString(@"Low Priority", "files tab -> tooltip");
|
||||
break;
|
||||
case TR_PRI_HIGH:
|
||||
tooltip = NSLocalizedString(@"High Priority", "files tab -> tooltip");
|
||||
break;
|
||||
case TR_PRI_NORMAL:
|
||||
tooltip = NSLocalizedString(@"Normal Priority", "files tab -> tooltip");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
tooltip = NSLocalizedString(@"Multiple Priorities", "files tab -> tooltip");
|
||||
break;
|
||||
}
|
||||
self.toolTip = tooltip;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="24412" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="24412"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -20,8 +20,8 @@
|
||||
<customView clipsToBounds="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2" userLabel="Files">
|
||||
<rect key="frame" x="0.0" y="0.0" width="340" height="365"/>
|
||||
<subviews>
|
||||
<searchField wantsLayer="YES" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3">
|
||||
<rect key="frame" x="12" y="12" width="110" height="19"/>
|
||||
<searchField wantsLayer="YES" focusRingType="none" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3">
|
||||
<rect key="frame" x="12" y="12" width="110" height="20"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="110" id="sKS-V5-H9b"/>
|
||||
</constraints>
|
||||
@@ -35,19 +35,19 @@
|
||||
</connections>
|
||||
</searchField>
|
||||
<scrollView horizontalLineScroll="36" horizontalPageScroll="10" verticalLineScroll="36" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4">
|
||||
<rect key="frame" x="12" y="39" width="316" height="314"/>
|
||||
<rect key="frame" x="12" y="40" width="316" height="313"/>
|
||||
<clipView key="contentView" id="l96-jk-uz9">
|
||||
<rect key="frame" x="1" y="1" width="314" height="312"/>
|
||||
<rect key="frame" x="1" y="1" width="301" height="311"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnReordering="NO" columnResizing="NO" autosaveColumns="NO" rowHeight="34" indentationPerLevel="16" autoresizesOutlineColumn="YES" outlineTableColumn="10" id="7" customClass="FileOutlineView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="314" height="312"/>
|
||||
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" autosaveColumns="NO" rowHeight="34" rowSizeStyle="automatic" viewBased="YES" indentationPerLevel="16" autoresizesOutlineColumn="YES" outlineTableColumn="10" id="7" customClass="FileOutlineView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="301" height="311"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableColumns>
|
||||
<tableColumn identifier="Name" editable="NO" width="231" minWidth="38.599119999999999" maxWidth="1000" id="10">
|
||||
<tableColumn identifier="Name" editable="NO" width="218" minWidth="38.599119999999999" maxWidth="1000" id="10">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Name">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" white="0.33333299" alpha="1" colorSpace="calibratedWhite"/>
|
||||
@@ -58,6 +58,26 @@
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="ddm-cs-nIR">
|
||||
<rect key="frame" x="1" y="1" width="223" height="34"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="HIK-di-9tP">
|
||||
<rect key="frame" x="0.0" y="9" width="223" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="D7f-0h-h8A">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<connections>
|
||||
<outlet property="textField" destination="HIK-di-9tP" id="bHT-uH-6AK"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="Priority" editable="NO" width="34" minWidth="10" maxWidth="1000" id="8">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Rank">
|
||||
@@ -69,6 +89,26 @@
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="4RH-Kf-WF8">
|
||||
<rect key="frame" x="227" y="1" width="34" height="34"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DCs-8X-Mcb">
|
||||
<rect key="frame" x="0.0" y="9" width="34" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="Doc-xO-gx2">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<connections>
|
||||
<outlet property="textField" destination="DCs-8X-Mcb" id="ad1-R3-pcd"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="Check" editable="NO" width="31" minWidth="10" maxWidth="1000" id="9">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="DL">
|
||||
@@ -79,6 +119,26 @@
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="cellTitle"/>
|
||||
</buttonCell>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="l8k-SJ-e7E">
|
||||
<rect key="frame" x="264" y="1" width="35" height="34"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="CkN-iw-rOX">
|
||||
<rect key="frame" x="0.0" y="9" width="35" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="a2Y-Cf-rdu">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<connections>
|
||||
<outlet property="textField" destination="CkN-iw-rOX" id="Thn-jw-ARn"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
</tableColumns>
|
||||
<connections>
|
||||
@@ -98,12 +158,12 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" controlSize="small" horizontal="NO" id="6">
|
||||
<rect key="frame" x="301" y="1" width="14" height="312"/>
|
||||
<rect key="frame" x="302" y="1" width="13" height="311"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="25">
|
||||
<rect key="frame" x="286" y="12" width="42" height="17"/>
|
||||
<rect key="frame" x="280" y="12" width="48" height="20"/>
|
||||
<buttonCell key="cell" type="roundRect" title="None" bezelStyle="roundedRect" alignment="center" controlSize="small" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="26">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
@@ -113,7 +173,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="27">
|
||||
<rect key="frame" x="236" y="12" width="42" height="17"/>
|
||||
<rect key="frame" x="224" y="12" width="48" height="20"/>
|
||||
<buttonCell key="cell" type="roundRect" title="All" bezelStyle="roundedRect" alignment="center" controlSize="small" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="28">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
||||
@@ -88,13 +88,13 @@
|
||||
|
||||
if (self.fTorrents.count == 1)
|
||||
{
|
||||
[self.fFileController refresh];
|
||||
[self.fFileController reloadVisibleRows];
|
||||
|
||||
#warning use TorrentFileCheckChange notification as well
|
||||
Torrent* torrent = self.fTorrents[0];
|
||||
if (torrent.folder)
|
||||
{
|
||||
NSInteger const filesCheckState = [torrent
|
||||
NSControlStateValue const filesCheckState = [torrent
|
||||
checkForFiles:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, torrent.fileCount)]];
|
||||
self.fCheckAllButton.enabled = filesCheckState != NSControlStateValueOn; //if anything is unchecked
|
||||
self.fUncheckAllButton.enabled = !torrent.allDownloaded; //if there are any checked files that aren't finished
|
||||
|
||||
@@ -195,8 +195,8 @@ extern NSString* const kTorrentDidChangeGroupNotification;
|
||||
- (CGFloat)fileProgress:(FileListNode*)node;
|
||||
- (BOOL)canChangeDownloadCheckForFile:(NSUInteger)index;
|
||||
- (BOOL)canChangeDownloadCheckForFiles:(NSIndexSet*)indexSet;
|
||||
- (NSInteger)checkForFiles:(NSIndexSet*)indexSet;
|
||||
- (void)setFileCheckState:(NSInteger)state forIndexes:(NSIndexSet*)indexSet;
|
||||
- (NSControlStateValue)checkForFiles:(NSIndexSet*)indexSet;
|
||||
- (void)setFileCheckState:(NSControlStateValue)state forIndexes:(NSIndexSet*)indexSet;
|
||||
- (void)setFilePriority:(tr_priority_t)priority forIndexes:(NSIndexSet*)indexSet;
|
||||
- (BOOL)hasFilePriority:(tr_priority_t)priority forIndexes:(NSIndexSet*)indexSet;
|
||||
- (NSSet*)filePrioritiesForIndexes:(NSIndexSet*)indexSet;
|
||||
|
||||
@@ -1547,7 +1547,7 @@ bool trashDataFile(char const* filename, void* /*user_data*/, tr_error* error)
|
||||
return canChange;
|
||||
}
|
||||
|
||||
- (NSInteger)checkForFiles:(NSIndexSet*)indexSet
|
||||
- (NSControlStateValue)checkForFiles:(NSIndexSet*)indexSet
|
||||
{
|
||||
BOOL onState = NO, offState = NO;
|
||||
for (NSUInteger index = indexSet.firstIndex; index != NSNotFound; index = [indexSet indexGreaterThanIndex:index])
|
||||
@@ -1570,7 +1570,7 @@ bool trashDataFile(char const* filename, void* /*user_data*/, tr_error* error)
|
||||
return onState ? NSControlStateValueOn : NSControlStateValueOff;
|
||||
}
|
||||
|
||||
- (void)setFileCheckState:(NSInteger)state forIndexes:(NSIndexSet*)indexSet
|
||||
- (void)setFileCheckState:(NSControlStateValue)state forIndexes:(NSIndexSet*)indexSet
|
||||
{
|
||||
NSUInteger count = indexSet.count;
|
||||
tr_file_index_t* files = static_cast<tr_file_index_t*>(malloc(count * sizeof(tr_file_index_t)));
|
||||
|
||||
Reference in New Issue
Block a user