// // KMFontWell.swift // PDF Master // // Created by tangchao on 2023/11/7. // import Cocoa class KMFontWellCell: NSButtonCell { var textColor: NSColor? var hasTextColor: Bool = false private let FONTNAME_KEY = "fontName" private let FONTSIZE_KEY = "fontSize" private let TEXTCOLOR_KEY = "textColor" private let HASTEXTCOLOR_KEY = "hasTextColor" deinit { KMPrint("KMFontWellCell deinit.") } override init(textCell string: String) { super.init(textCell: string) self.commonInit() } required init(coder: NSCoder) { super.init(coder: coder) self.textColor = coder.decodeObject(forKey: TEXTCOLOR_KEY) as? NSColor self.hasTextColor = coder.decodeBool(forKey: HASTEXTCOLOR_KEY) self.commonInit() } func commonInit() { if self.textColor == nil { self.textColor = .black } self.bezelStyle = .shadowlessSquare self.setButtonType(.pushOnPushOff) self.state = .off } override func encode(with coder: NSCoder) { super.encode(with: coder) coder.encodeConditionalObject(self.textColor, forKey: TEXTCOLOR_KEY) coder.encode(self.hasTextColor, forKey: HASTEXTCOLOR_KEY) } override func copy(with zone: NSZone? = nil) -> Any { let cell = super.copy(with: zone) as? KMFontWellCell cell?.textColor = self.textColor?.copy(with: zone) as? NSColor cell?.hasTextColor = self.hasTextColor return cell as Any } override func drawBezel(withFrame frame: NSRect, in controlView: NSView) { KMDrawTextFieldBezel(frame, controlView) if (self.state == .on) { NSGraphicsContext.saveGraphicsState() NSColor.selectedControlColor.setFill() frame.fill(using: .plusDarker) NSGraphicsContext.restoreGraphicsState() } if (self.isHighlighted) { NSGraphicsContext.saveGraphicsState() NSColor(calibratedWhite: 0, alpha: 0.1).setFill() frame.frame(withWidth: 1, using: .plusDarker) NSGraphicsContext.restoreGraphicsState() } if (self.showsFirstResponder) { NSGraphicsContext.saveGraphicsState() NSFocusRingPlacement.only.set() _ = CGRect.fill(frame) NSGraphicsContext.restoreGraphicsState() } } override var attributedTitle: NSAttributedString { set { super.attributedTitle = newValue } get { if (self.hasTextColor) { var attrString = super.attributedTitle.mutableCopy() as? NSMutableAttributedString attrString?.addAttribute(.foregroundColor, value: self.textColor ?? NSColor.black, range: NSMakeRange(0, attrString?.length ?? 0)) if let data = self.textColor?.usingColorSpaceName(.calibratedRGB)?.brightnessComponent, data > 0.8 { let shade = NSShadow() shade.shadowColor = .black shade.shadowBlurRadius = 1 attrString?.addAttribute(.shadow, value: shade, range: NSMakeRange(0, attrString?.length ?? 0)) } return attrString! } else { return super.attributedTitle } } } } private let SKFontWellWillBecomeActiveNotification = "SKFontWellWillBecomeActiveNotification" private let ACTION_KEY = "action" private let TARGET_KEY = "target" class KMFontWell: NSButton { var isActive = false var fontName: String? var fontSize: CGFloat = 0 var textColor: NSColor = .black var hasTextColor = false /* @interface SKFontWell : NSButton { id target; SEL action; NSMutableDictionary *bindingInfo; } - (void)activate; - (void)deactivate; - (void)changeFontFromFontManager:(id)sender; - (void)changeAttributesFromFontManager:(id)sender; @end */ override class var cellClass: AnyClass? { set { super.cellClass = newValue } get { return KMFontWellCell.self } } override init(frame frameRect: NSRect) { super.init(frame: frameRect) self.commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) let oldCell = self.cell as? NSButtonCell if let data = oldCell?.isKind(of: Self.cellClass!), !data { let newCell = KMFontWellCell(textCell: "") newCell.alignment = oldCell!.alignment newCell.isEditable = oldCell!.isEditable newCell.target = oldCell?.target newCell.action = oldCell?.action self.cell = newCell } action = NSSelectorFromString(coder.decodeObject(forKey: ACTION_KEY) as? String ?? "") target = coder.decodeObject(forKey: TARGET_KEY) as AnyObject? self.commonInit() } override func encode(with coder: NSCoder) { super.encode(with: coder) coder.encode(NSStringFromSelector(action!), forKey: ACTION_KEY) coder.encodeConditionalObject(target, forKey: TARGET_KEY) } func commonInit() { if (self.font == nil) { self.font = .systemFont(ofSize: 0.0) } self._fontChanged() super.action = #selector(_changeActive) super.target = self // bindingInfo = [[NSMutableDictionary alloc] init]; // [self registerForDraggedTypes:[NSArray arrayWithObjects:SKNSFontPanelDescriptorsPboardType, SKNSFontPanelFamiliesPboardType, NSPasteboardTypeColor, nil]]; } override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) // Drawing code here. } /* #define SKNSFontPanelDescriptorsPboardType @"NSFontPanelDescriptorsPboardType" #define SKNSFontPanelFamiliesPboardType @"NSFontPanelFamiliesPboardType" #define SKNSFontCollectionFontDescriptors @"NSFontCollectionFontDescriptors" #define FONT_KEY @"font" static char SKFontWellFontNameObservationContext; static char SKFontWellFontSizeObservationContext; @interface SKFontWell (SKPrivate) - (void)changeActive:(id)sender; - (void)fontChanged; @end @implementation SKFontWell @dynamic isActive, fontName, fontSize, textColor, hasTextColor; + (void)initialize { // SKINITIALIZE; [self exposeBinding:FONTNAME_KEY]; [self exposeBinding:FONTSIZE_KEY]; [self exposeBinding:TEXTCOLOR_KEY]; } + (NSSet *)keyPathsForValuesAffectingFontName { return [NSSet setWithObjects:FONT_KEY, nil]; } + (NSSet *)keyPathsForValuesAffectingFontSize { return [NSSet setWithObjects:FONT_KEY, nil]; } - (Class)valueClassForBinding:(NSString *)binding { if ([binding isEqualToString:FONTNAME_KEY]) return [NSString class]; else if ([binding isEqualToString:FONTNAME_KEY]) return [NSNumber class]; else if ([binding isEqualToString:TEXTCOLOR_KEY]) return [NSColor class]; else return [super valueClassForBinding:binding]; } - (void)dealloc { // SKENSURE_MAIN_THREAD( [self unbind:FONTNAME_KEY]; [self unbind:FONTSIZE_KEY]; // ); [[NSNotificationCenter defaultCenter] removeObserver:self]; // SKDESTROY(bindingInfo); // [super dealloc]; } - (void)viewWillMoveToWindow:(NSWindow *)newWindow { [self deactivate]; [super viewWillMoveToWindow:newWindow]; } - (void)fontPickerWillBecomeActive:(NSNotification *)notification { id sender = [notification object]; if (sender != self && [self isActive]) { [self deactivate]; } } - (void)fontPanelWillClose:(NSNotification *)notification { [self deactivate]; } - (void)notifyFontBinding { NSDictionary *info = [self infoForBinding:FONTNAME_KEY]; [[info objectForKey:NSObservedObjectKey] setValue:[self fontName] forKeyPath:[info objectForKey:NSObservedKeyPathKey]]; info = [self infoForBinding:FONTSIZE_KEY]; [[info objectForKey:NSObservedObjectKey] setValue:[NSNumber numberWithDouble:[self fontSize]] forKeyPath:[info objectForKey:NSObservedKeyPathKey]]; } - (void)notifyTextColorBinding { NSDictionary *info = [self infoForBinding:TEXTCOLOR_KEY]; if (info) { id value = [self textColor]; NSString *transformerName = [[info objectForKey:NSOptionsKey] objectForKey:NSValueTransformerNameBindingOption]; if (transformerName && [transformerName isEqual:[NSNull null]] == NO) { NSValueTransformer *valueTransformer = [NSValueTransformer valueTransformerForName:transformerName]; value = [valueTransformer reverseTransformedValue:value]; } [[info objectForKey:NSObservedObjectKey] setValue:value forKeyPath:[info objectForKey:NSObservedKeyPathKey]]; } } - (void)changeFontFromFontManager:(id)sender { if ([self isActive]) { [self setFont:[sender convertFont:[self font]]]; [self notifyFontBinding]; [self sendAction:[self action] to:[self target]]; } } - (void)changeAttributesFromFontManager:(id)sender { if ([self isActive] && [self hasTextColor]) { [self setTextColor:[[sender convertAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[self textColor], NSForegroundColorAttributeName, nil]] valueForKey:NSForegroundColorAttributeName]]; [self notifyTextColorBinding]; [self sendAction:[self action] to:[self target]]; } } #pragma mark Accessors - (SEL)action { return action; } - (void)setAction:(SEL)newAction { action = newAction; } - (id)target { return target; } - (void)setTarget:(id)newTarget { target = newTarget; } - (BOOL)isActive { return [self state] == NSOnState; } - (void)setFont:(NSFont *)newFont { BOOL didChange = [[self font] isEqual:newFont] == NO; [super setFont:newFont]; if (didChange) [self fontChanged]; } - (NSString *)fontName { return [[self font] fontName]; } - (void)setFontName:(NSString *)fontName { NSFont *newFont = [NSFont fontWithName:fontName size:[[self font] pointSize]]; if (newFont) [self setFont:newFont]; } - (CGFloat)fontSize { return [[self font] pointSize]; } - (void)setFontSize:(CGFloat)pointSize { NSFont *newFont = [NSFont fontWithName:[[self font] fontName] size:pointSize]; if (newFont) [self setFont:newFont]; } - (NSColor *)textColor { return [[self cell] textColor]; } - (void)setTextColor:(NSColor *)newTextColor { BOOL didChange = [[self textColor] isEqual:newTextColor] == NO; [[self cell] setTextColor:newTextColor]; if (didChange) [self setNeedsDisplay:YES]; } - (BOOL)hasTextColor { return [[self cell] hasTextColor]; } - (void)setHasTextColor:(BOOL)newHasTextColor { if ([self hasTextColor] != newHasTextColor) { [[self cell] setHasTextColor:newHasTextColor]; [self setNeedsDisplay:YES]; } } #pragma mark Binding support - (void)bind:(NSString *)bindingName toObject:(id)observableController withKeyPath:(NSString *)keyPath options:(NSDictionary *)options { if ([bindingName isEqualToString:FONTNAME_KEY] || [bindingName isEqualToString:FONTSIZE_KEY]) { if ([bindingInfo objectForKey:bindingName]) [self unbind:bindingName]; // NSDictionary *bindingsData = [NSDictionary dictionaryWithObjectsAndKeys:observableController, NSObservedObjectKey, [[keyPath copy] autorelease], NSObservedKeyPathKey, [[options copy] autorelease], NSOptionsKey, nil]; // [bindingInfo setObject:bindingsData forKey:bindingName]; void *context = NULL; if ([bindingName isEqualToString:FONTNAME_KEY]) context = &SKFontWellFontNameObservationContext; else if ([bindingName isEqualToString:FONTSIZE_KEY]) context = &SKFontWellFontSizeObservationContext; [observableController addObserver:self forKeyPath:keyPath options:0 context:context]; [self observeValueForKeyPath:keyPath ofObject:observableController change:nil context:context]; } else { [super bind:bindingName toObject:observableController withKeyPath:keyPath options:options]; } [self setNeedsDisplay:YES]; } - (void)unbind:(NSString *)bindingName { if ([bindingName isEqualToString:FONTNAME_KEY] || [bindingName isEqualToString:FONTSIZE_KEY]) { NSDictionary *info = [self infoForBinding:bindingName]; [[info objectForKey:NSObservedObjectKey] removeObserver:self forKeyPath:[info objectForKey:NSObservedKeyPathKey]]; [bindingInfo removeObjectForKey:bindingName]; } else { [super unbind:bindingName]; } [self setNeedsDisplay:YES]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSString *key = nil; if (context == &SKFontWellFontNameObservationContext) key = FONTNAME_KEY; else if (context == &SKFontWellFontSizeObservationContext) key = FONTSIZE_KEY; if (key) { NSDictionary *info = [self infoForBinding:key]; id value = [[info objectForKey:NSObservedObjectKey] valueForKeyPath:[info objectForKey:NSObservedKeyPathKey]]; if (NSIsControllerMarker(value) == NO) [self setValue:value forKey:key]; } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } - (NSDictionary *)infoForBinding:(NSString *)bindingName { return [bindingInfo objectForKey:bindingName] ?: [super infoForBinding:bindingName]; } #pragma mark NSDraggingDestination protocol - (NSDragOperation)draggingEntered:(id )sender { if ([self isEnabled] && [sender draggingSource] != self && [[sender draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:SKNSFontPanelDescriptorsPboardType, SKNSFontPanelFamiliesPboardType, ([self hasTextColor] ? NSPasteboardTypeColor : nil), nil]]) { [[self cell] setHighlighted:YES]; [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]]; [self setNeedsDisplay:YES]; return NSDragOperationGeneric; } else return NSDragOperationNone; } - (void)draggingExited:(id )sender { if ([self isEnabled] && [sender draggingSource] != self && [[sender draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:SKNSFontPanelDescriptorsPboardType, SKNSFontPanelFamiliesPboardType, ([self hasTextColor] ? NSPasteboardTypeColor : nil), nil]]) { [[self cell] setHighlighted:NO]; [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]]; [self setNeedsDisplay:YES]; } } - (BOOL)prepareForDragOperation:(id )sender { return [self isEnabled] && [sender draggingSource] != self && [[sender draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:SKNSFontPanelDescriptorsPboardType, SKNSFontPanelFamiliesPboardType, ([self hasTextColor] ? NSPasteboardTypeColor : nil), nil]]; } - (BOOL)performDragOperation:(id )sender{ NSPasteboard *pboard = [sender draggingPasteboard]; NSString *type = [pboard availableTypeFromArray:[NSArray arrayWithObjects:SKNSFontPanelDescriptorsPboardType, SKNSFontPanelFamiliesPboardType, ([self hasTextColor] ? NSPasteboardTypeColor : nil), nil]]; NSFont *droppedFont = nil; NSColor *droppedColor = nil; @try { if ([type isEqualToString:SKNSFontPanelDescriptorsPboardType]) { NSData *data = [pboard dataForType:type]; NSDictionary *dict = [data isKindOfClass:[NSData class]] ? [NSKeyedUnarchiver unarchiveObjectWithData:data] : nil; if ([dict isKindOfClass:[NSDictionary class]]) { NSArray *fontDescriptors = [dict objectForKey:SKNSFontCollectionFontDescriptors]; NSFontDescriptor *fontDescriptor = ([fontDescriptors isKindOfClass:[NSArray class]] && [fontDescriptors count]) ? [fontDescriptors objectAtIndex:0] : nil; if ([fontDescriptor isKindOfClass:[NSFontDescriptor class]]) { NSNumber *size = [[fontDescriptor fontAttributes] objectForKey:NSFontSizeAttribute] ?: [dict objectForKey:NSFontSizeAttribute]; CGFloat fontSize = [size respondsToSelector:@selector(doubleValue)] ? [size doubleValue] : [self fontSize]; droppedFont = [NSFont fontWithDescriptor:fontDescriptor size:fontSize]; } } } else if ([type isEqualToString:SKNSFontPanelFamiliesPboardType]) { NSArray *families = [pboard propertyListForType:type]; NSString *family = ([families isKindOfClass:[NSArray class]] && [families count]) ? [families objectAtIndex:0] : nil; if ([family isKindOfClass:[NSString class]]) droppedFont = [[NSFontManager sharedFontManager] convertFont:[self font] toFamily:family]; } else if ([type isEqualToString:NSPasteboardTypeColor]) { droppedColor = [NSColor colorFromPasteboard:pboard]; } } @catch (id exception) { NSLog(@"Ignoring exception %@ when dropping on SKFontWell failed", exception); } if (droppedFont) { [self setFont:droppedFont]; [self notifyFontBinding]; [self sendAction:[self action] to:[self target]]; } if (droppedColor) { [self setTextColor:droppedColor]; [self notifyTextColorBinding]; [self sendAction:[self action] to:[self target]]; } [[self cell] setHighlighted:NO]; [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]]; [self setNeedsDisplay:YES]; return droppedFont != nil || droppedColor != nil; } @end */ } // MARK: - Private Methods extension KMFontWell { private func _fontChanged() { if self.isActive { NSFontManager.shared.setSelectedFont(self.font ?? .systemFont(ofSize: 0), isMultiple: false) } self.title = String(format: "%@ %li", self.font?.displayName ?? "", self.fontSize) self.needsDisplay = true } @objc private func _changeActive(_ sender: AnyObject?) { if (self.isEnabled) { if (self.isActive) { self._activate() } else { self._deactivate() } } } private func _activate() { let nc = NotificationCenter.default let fm = NSFontManager.shared nc.post(name: NSNotification.Name(rawValue: SKFontWellWillBecomeActiveNotification), object: self) fm.setSelectedFont(self.font ?? .systemFont(ofSize: 0), isMultiple: false) fm.orderFrontFontPanel(self) // [nc addObserver:self selector:@selector(fontPickerWillBecomeActive:) // name:SKFontWellWillBecomeActiveNotification object:nil]; // [nc addObserver:self selector:@selector(fontPanelWillClose:) // name:NSWindowWillCloseNotification object:[fm fontPanel:YES]]; self.state = .on self.setKeyboardFocusRingNeedsDisplay(self.bounds) self.needsDisplay = true } private func _deactivate() { NotificationCenter.default.removeObserver(self) self.state = .off self.setKeyboardFocusRingNeedsDisplay(self.bounds) self.needsDisplay = true } /* */ }