mirror of
https://github.com/transmission/transmission.git
synced 2025-12-20 10:28:32 +00:00
314 lines
9.3 KiB
Plaintext
314 lines
9.3 KiB
Plaintext
// 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
|