#import "FileItemTableCellView.h" #import "FileItem.h" #import #import @interface FileItemSelectBasenameCell : NSTextFieldCell @end @implementation FileItemSelectBasenameCell - (void)selectWithFrame:(NSRect)aRect inView:(NSView*)aView editor:(NSText*)aText delegate:(id)someDelegate start:(NSInteger)start length:(NSInteger)length { NSString* path = self.stringValue; if([self.objectValue respondsToSelector:@selector(firstObject)]) path = [self.objectValue firstObject]; NSString* basename = [path stringByDeletingPathExtension]; [super selectWithFrame:aRect inView:aView editor:aText delegate:someDelegate start:start length:(start == 0 && basename ? MIN(basename.length, length) : length)]; } @end @implementation FileItem (FileItemWrapper) + (NSSet*)keyPathsForValuesAffectingEditingAndDisplayName { return [NSSet setWithObjects:@"URL", @"displayName", nil]; } - (NSArray*)editingAndDisplayName { return @[ self.URL.lastPathComponent ?: @"", self.displayName ]; } - (void)setEditingAndDisplayName:(NSArray*)unused { // Because ‘editingAndDisplayName’ is bound to our text field then we receive updates when user edits the text field } @end @interface FileItemFormatter : NSFormatter @property (nonatomic, weak) NSTableCellView* tableCellView; @end @implementation FileItemFormatter - (instancetype)initWithTableCellView:(NSTableCellView*)tableCellView { if(self = [super init]) _tableCellView = tableCellView; return self; } - (NSString*)stringForObjectValue:(id)aValue { return [_tableCellView.objectValue editingAndDisplayName].lastObject; } - (NSString*)editingStringForObjectValue:(id)aValue { return [_tableCellView.objectValue editingAndDisplayName].firstObject; } - (BOOL)getObjectValue:(id*)valueRef forString:(NSString*)aString errorDescription:(NSString**)errorRef { *valueRef = aString; return YES; } @end @interface FileItemFinderTagsView : NSView @property (nonatomic) NSArray* finderTags; @property (nonatomic) NSBackgroundStyle backgroundStyle; @property (nonatomic) BOOL rightPadding; @end @interface FileItemTableCellView () @property (nonatomic) FileItemFinderTagsView* finderTagsView; @end @implementation FileItemTableCellView - (instancetype)init { if((self = [super initWithFrame:NSZeroRect])) { _openButton = [[NSButton alloc] initWithFrame:NSZeroRect]; _openButton.refusesFirstResponder = YES; _openButton.buttonType = NSButtonTypeMomentaryChange; _openButton.bordered = NO; _openButton.imagePosition = NSImageOnly; NSTextField* textField = OakCreateLabel(@"", [NSFont controlContentFontOfSize:0]); textField.cell = [[FileItemSelectBasenameCell alloc] initTextCell:@""]; [textField.cell setWraps:NO]; [textField.cell setLineBreakMode:NSLineBreakByTruncatingMiddle]; textField.formatter = [[FileItemFormatter alloc] initWithTableCellView:self]; _finderTagsView = [[FileItemFinderTagsView alloc] initWithFrame:NSZeroRect]; _closeButton = OakCreateCloseButton(); _closeButton.refusesFirstResponder = YES; NSStackView* stackView = [NSStackView stackViewWithViews:@[ _openButton, textField, _finderTagsView, _closeButton ]]; stackView.spacing = 4; [self addSubview:stackView]; [stackView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant: 4].active = YES; [stackView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-8].active = YES; [stackView.topAnchor constraintEqualToAnchor:self.topAnchor constant: 0].active = YES; [stackView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant: 0].active = YES; [_openButton bind:NSImageBinding toObject:self withKeyPath:@"objectValue.image" options:nil]; [textField bind:NSValueBinding toObject:self withKeyPath:@"objectValue.editingAndDisplayName" options:nil]; [textField bind:NSEditableBinding toObject:self withKeyPath:@"objectValue.canRename" options:nil]; [textField bind:NSToolTipBinding toObject:self withKeyPath:@"objectValue.toolTip" options:nil]; [_finderTagsView bind:@"finderTags" toObject:self withKeyPath:@"objectValue.finderTags" options:nil]; [_finderTagsView bind:@"rightPadding" toObject:self withKeyPath:@"objectValue.open" options:@{ NSValueTransformerNameBindingOption: NSNegateBooleanTransformerName }]; [_closeButton bind:NSHiddenBinding toObject:self withKeyPath:@"objectValue.open" options:@{ NSValueTransformerNameBindingOption: NSNegateBooleanTransformerName }]; self.textField = textField; } return self; } - (void)setBackgroundStyle:(NSBackgroundStyle)newBackgroundStyle { [_finderTagsView setBackgroundStyle:newBackgroundStyle]; [super setBackgroundStyle:newBackgroundStyle]; } - (void)dealloc { [_openButton unbind:NSImageBinding]; [self.textField unbind:NSValueBinding]; [self.textField unbind:NSEditableBinding]; [self.textField unbind:NSToolTipBinding]; [_finderTagsView unbind:@"finderTags"]; [_closeButton unbind:NSHiddenBinding]; } - (void)resetCursorRects { [self addCursorRect:_openButton.frame cursor:NSCursor.pointingHandCursor]; } @end @implementation FileItemFinderTagsView - (void)setFinderTags:(NSArray*)newFinderTags { if([_finderTags isEqual:newFinderTags]) return; _finderTags = newFinderTags; self.hidden = _finderTags.count == 0; [self setNeedsDisplay:YES]; } - (void)setBackgroundStyle:(NSBackgroundStyle)newBackgroundStyle { _backgroundStyle = newBackgroundStyle; [self setNeedsDisplay:YES]; } - (void)setRightPadding:(BOOL)flag { _rightPadding = flag; [self invalidateIntrinsicContentSize]; } - (void)drawRect:(NSRect)aRect { NSArray* tagsWithLabelColor = [_finderTags filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"hasLabelColor == YES"]]; auto fillAndStrokePath = ^(NSBezierPath* path, OakFinderTag* tag){ NSColor* borderColor = tag.labelColor; NSColor* fillColor = nil; if(borderColor) { NSColor* rgbColor = [borderColor colorUsingColorSpace:NSColorSpace.sRGBColorSpace]; CGFloat factor = 0.8; CGFloat r = 1 - factor*(1 - rgbColor.redComponent); CGFloat g = 1 - factor*(1 - rgbColor.greenComponent); CGFloat b = 1 - factor*(1 - rgbColor.blueComponent); fillColor = [NSColor colorWithSRGBRed:r green:g blue:b alpha:1.0]; } else { borderColor = [NSColor secondaryLabelColor]; fillColor = [NSColor clearColor]; } [fillColor set]; [path fill]; if(_backgroundStyle == NSBackgroundStyleEmphasized) [NSColor.whiteColor set]; else [borderColor set]; [path stroke]; }; auto drawCrestent = ^(NSPoint center1, NSPoint center2, OakFinderTag* tag){ [NSGraphicsContext saveGraphicsState]; NSBezierPath* clippingPath = [NSBezierPath bezierPath]; [clippingPath appendBezierPathWithArcWithCenter:center2 radius:5.0 startAngle:-100 endAngle:100]; [clippingPath appendBezierPathWithArcWithCenter:center1 radius:5.5 startAngle:60 endAngle:300 clockwise:YES]; [clippingPath addClip]; NSBezierPath* path = [NSBezierPath bezierPath]; [path appendBezierPathWithArcWithCenter:center2 radius:4.0 startAngle:0 endAngle:360]; [path closePath]; fillAndStrokePath(path, tag); [NSGraphicsContext restoreGraphicsState]; }; NSRect r = [self bounds]; r.size.width -= _rightPadding ? 16 : 0; switch([tagsWithLabelColor count]) { case 0: return; case 1: { NSBezierPath* path = [NSBezierPath bezierPath]; [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMidX(r), NSMidY(r)) radius:4.0 startAngle:0 endAngle:360]; fillAndStrokePath(path, tagsWithLabelColor[0]); return; } case 2: { NSPoint center = NSMakePoint(NSMidX(r), NSMidY(r)); NSPoint center1 = NSMakePoint(center.x - 2.0, center.y); NSPoint center2 = NSMakePoint(center.x + 2.0, center.y); drawCrestent(center1, center2, tagsWithLabelColor[0]); NSBezierPath* path = [NSBezierPath bezierPath]; [path appendBezierPathWithArcWithCenter:center1 radius:4.0 startAngle:0 endAngle:360]; fillAndStrokePath(path, tagsWithLabelColor[1]); return; } default: { NSPoint center = NSMakePoint(NSMidX(r), NSMidY(r)); NSPoint center1 = NSMakePoint(center.x - 4.0, center.y); NSPoint center2 = NSMakePoint(center.x, center.y); NSPoint center3 = NSMakePoint(center.x + 4.0, center.y); NSUInteger lastIndex = [tagsWithLabelColor count] - 1; drawCrestent(center2, center3, tagsWithLabelColor[lastIndex - 2]); drawCrestent(center1, center2, tagsWithLabelColor[lastIndex - 1]); NSBezierPath* path = [NSBezierPath bezierPath]; [path appendBezierPathWithArcWithCenter:center1 radius:4.0 startAngle:0 endAngle:360]; fillAndStrokePath(path, tagsWithLabelColor[lastIndex]); return; } } } - (NSSize)intrinsicContentSize { return NSMakeSize((_rightPadding ? 16 : 0) + 20, 10); } @end