// // KMLineWell.swift // PDF Reader Pro // // Created by tangchao on 2023/11/6. // import Cocoa private let SKPasteboardTypeLineStyle = "net.sourceforge.skim-app.pasteboard.line-style" private let SKLineWellLineWidthKey = "lineWidth" private let SKLineWellStyleKey = "style" private let SKLineWellDashPatternKey = "dashPattern" private let SKLineWellStartLineStyleKey = "startLineStyle" private let SKLineWellEndLineStyleKey = "endLineStyle" private let ACTION_KEY = "action" private let TARGET_KEY = "target" private let DISPLAYSTYLE_KEY = "lwFlagsdisplayStyle" private let ACTIVE_KEY = "active" private let SKLineWellWillBecomeActiveNotification = "SKLineWellWillBecomeActiveNotification" private let EXCLUSIVE_KEY = "exclusive" enum KMLineWellDisplayStyle: Int { case line = 0 case simpleLine case rectangle case oval } class KMLineWell: NSControl { public static let widthKey = SKLineWellLineWidthKey public static let styleKey = SKLineWellStyleKey public static let dashPatternKey = SKLineWellDashPatternKey public static let startLineStyleKey = SKLineWellStartLineStyleKey public static let endLineStyleKey = SKLineWellEndLineStyleKey public static let lineStylePboardTypeString = SKPasteboardTypeLineStyle public static let lineStylePboardType = NSPasteboard.PasteboardType(rawValue: SKPasteboardTypeLineStyle) private var _lineWidth: CGFloat = 2 var lineWidth: CGFloat { get { return self._lineWidth } set { if (abs(self._lineWidth - newValue) > 0.00001) { self._lineWidth = newValue self._changedValue(forKey: SKLineWellLineWidthKey) } } } private var _style: CPDFBorderStyle = .solid var style: CPDFBorderStyle { get { return self._style } set { if (newValue != self._style) { self._style = newValue self._changedValue(forKey: SKLineWellStyleKey) } } } private var _startLineStyle: CPDFLineStyle = .none var startLineStyle: CPDFLineStyle { get { return self._startLineStyle } set { if (newValue != self._startLineStyle) { self._startLineStyle = newValue self._changedValue(forKey: SKLineWellStartLineStyleKey) } } } private var _endLineStyle: CPDFLineStyle = .none var endLineStyle: CPDFLineStyle { get { return self._endLineStyle } set { if (newValue != self._endLineStyle) { self._endLineStyle = newValue self._changedValue(forKey: SKLineWellEndLineStyleKey) } } } private var _dashPattern: NSArray? var dashPattern: NSArray? { get { return self._dashPattern } set { var _pattern: NSArray? = newValue if (NSIsControllerMarker(_pattern)) { _pattern = nil } if _pattern?.isEqual(to: self._dashPattern) == false && _pattern != nil || self._dashPattern != nil { self._dashPattern = _pattern self._changedValue(forKey: SKLineWellDashPatternKey) } } } var displayStyle: KMLineWellDisplayStyle { get { return KMLineWellDisplayStyle(rawValue: Int(__lwFlags.displayStyle)) ?? .line } set { if __lwFlags.displayStyle != newValue.rawValue { __lwFlags.displayStyle = UInt8(newValue.rawValue) self.needsDisplay = true } } } private struct _lwFlags { var displayStyle: UInt8 = 0 var active = false var canActivate = false var existsActiveLineWell: UInt8 = 0 var highlighted: Bool = false } private var __lwFlags = _lwFlags() var canActivate: Bool { get { return __lwFlags.canActivate } set { if __lwFlags.canActivate != newValue { __lwFlags.canActivate = newValue if self.isActive && __lwFlags.canActivate == false { self._deactivate() } } } } var isActive: Bool { get { return __lwFlags.active } } override var isHighlighted: Bool { get { return self.__lwFlags.highlighted } set { if self.__lwFlags.highlighted != newValue { self.__lwFlags.highlighted = newValue } } } private var _pri_target: AnyObject? private var _pri_action: Selector? deinit { KMPrint("KMLineWell deinit.") NotificationCenter.default.removeObserver(self) } override init(frame frameRect: NSRect) { super.init(frame: frameRect) lineWidth = 1.0 style = .solid dashPattern = nil; startLineStyle = .none endLineStyle = .none __lwFlags.displayStyle = UInt8(KMLineWellDisplayStyle.line.rawValue) __lwFlags.active = false __lwFlags.canActivate = false __lwFlags.existsActiveLineWell = 0 self.target = nil self.action = nil self._commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) lineWidth = coder.decodeDouble(forKey: SKLineWellLineWidthKey) style = CPDFBorderStyle(rawValue: coder.decodeInteger(forKey: SKLineWellStyleKey)) ?? .solid dashPattern = coder.decodeObject(forKey: SKLineWellDashPatternKey) as? NSArray startLineStyle = CPDFLineStyle(rawValue: coder.decodeInteger(forKey: SKLineWellStartLineStyleKey)) ?? .none endLineStyle = CPDFLineStyle(rawValue: coder.decodeInteger(forKey: SKLineWellEndLineStyleKey)) ?? .none __lwFlags.displayStyle = UInt8(coder.decodeInteger(forKey: DISPLAYSTYLE_KEY)) __lwFlags.active = coder.decodeBool(forKey: ACTIVE_KEY) 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(lineWidth, forKey: SKLineWellLineWidthKey) coder.encode(style.rawValue, forKey: SKLineWellStyleKey) coder.encode(dashPattern, forKey: SKLineWellDashPatternKey) coder.encode(startLineStyle.rawValue, forKey: SKLineWellStartLineStyleKey) coder.encode(endLineStyle.rawValue, forKey: SKLineWellEndLineStyleKey) coder.encode(__lwFlags.displayStyle, forKey: DISPLAYSTYLE_KEY) coder.encode(__lwFlags.active, forKey: ACTIVE_KEY) if let data = self.action { coder.encode(NSStringFromSelector(data), forKey: ACTION_KEY) } coder.encode(target, forKey: TARGET_KEY) } override var isOpaque: Bool { return true } override var acceptsFirstResponder: Bool { return self.canActivate } override func acceptsFirstMouse(for event: NSEvent?) -> Bool { return self.canActivate } override func viewWillMove(toWindow newWindow: NSWindow?) { self._deactivate() super.viewWillMove(toWindow: newWindow) } override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) let bounds = self.bounds if KMAppearance.isDarkMode() { KMDrawTextFieldBezel(bounds, self, .white) } else { KMDrawTextFieldBezel(bounds, self) } if (self.isActive) { NSGraphicsContext.saveGraphicsState() NSColor.selectedControlColor.setFill() bounds.fill(using: .plusDarker) NSGraphicsContext.restoreGraphicsState() } if (self.isHighlighted) { NSGraphicsContext.saveGraphicsState() NSColor(calibratedWhite: 0, alpha: 0.1).setFill() self.bounds.frame(withWidth: 1, using: .plusDarker) NSGraphicsContext.restoreGraphicsState() } if (lineWidth > 0.0) { NSGraphicsContext.saveGraphicsState() NSBezierPath(rect: NSInsetRect(bounds, 2.0, 2.0)).addClip() NSColor.black.setStroke() self._path().stroke() NSGraphicsContext.restoreGraphicsState() } if self.refusesFirstResponder == false && NSApp.isActive && self.isEqual(to: self.window?.firstResponder) { if let data = self.window?.isKeyWindow, data { NSGraphicsContext.saveGraphicsState() NSFocusRingPlacement.only.set() bounds.fill() NSGraphicsContext.restoreGraphicsState() } } } override func mouseDown(with event: NSEvent) { if (self.isEnabled) { self.setKeyboardFocusRingNeedsDisplay(self.bounds) self.needsDisplay = true // NSUInteger modifiers = [theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask; var modifiers = event.modifierFlags // modifiers.insert(.deviceIndependentFlagsMask) if modifiers.contains(.deviceIndependentFlagsMask) { modifiers.remove(.deviceIndependentFlagsMask) } var keepOn = true var _event: NSEvent = event while (keepOn) { if let data = self.window?.nextEvent(matching: [.leftMouseUp, .leftMouseDragged]) { _event = data switch (_event.type) { case .leftMouseDragged: var dict = [SKLineWellLineWidthKey : NSNumber(value: self.lineWidth), SKLineWellStyleKey : NSNumber(value: self.style.rawValue), SKLineWellDashPatternKey : self.dashPattern ?? []] if self.displayStyle == .line { dict[SKLineWellStartLineStyleKey] = NSNumber(value: self.startLineStyle.rawValue) dict[SKLineWellEndLineStyleKey] = NSNumber(value: self.endLineStyle.rawValue) } let pboard = NSPasteboard(name: .drag) pboard.clearContents() pboard.setPropertyList(dict, forType: Self.lineStylePboardType) let bounds = self.bounds /*beginDraggingSessionWithItems:event:source: instead*/ // [self dragImage:[self dragImage] at:bounds.origin offset:NSZeroSize event:theEvent pasteboard:pboard source:self slideBack:YES]; keepOn = false break // } case .leftMouseUp: if (self.isActive) { self._deactivate() } else { self._activate(modifiers.contains(.shift) == false) } keepOn = false break default: break } } } } } override func performClick(_ sender: Any?) { if (self.isEnabled) { if (self.isActive) { self._deactivate() } else { self._activate(true) } } } override var target: AnyObject? { get { return self._pri_target } set { self._pri_target = newValue } } override var action: Selector? { get { return self._pri_action } set { self._pri_action = newValue } } func takeValue(for key: String, from object: AnyObject) { if let inspector = object as? KMLineInspector { if key == Self.widthKey { self.lineWidth = inspector.lineWidth } else if key == Self.styleKey { self.style = CPDFBorderStyle(rawValue: inspector.style) ?? .solid } else if key == Self.startLineStyleKey { self.startLineStyle = CPDFLineStyle(rawValue: inspector.startLineStyle) ?? .none } else if key == Self.endLineStyleKey { self.endLineStyle = CPDFLineStyle(rawValue: inspector.endLineStyle) ?? .none } else if key == Self.dashPatternKey { self.dashPattern = inspector.dashPattern as NSArray } } } // MARK: - NSDraggingDestination // - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal { // return NSDragOperationGeneric; // } override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { if self.isEnabled && self.isEqual(to: sender.draggingSource) == false && sender.draggingPasteboard.canReadItem(withDataConformingToTypes: [Self.lineStylePboardTypeString]) { self.isHighlighted = true self.setKeyboardFocusRingNeedsDisplay(self.bounds) self.needsDisplay = true return .every } return NSDragOperation(rawValue: 0) } override func draggingExited(_ sender: NSDraggingInfo?) { if self.isEnabled && self.isEqual(to: sender?.draggingSource) == false { if let pboard = sender?.draggingPasteboard, pboard.canReadItem(withDataConformingToTypes: [Self.lineStylePboardTypeString]) { self.isHighlighted = false self.setKeyboardFocusRingNeedsDisplay(self.bounds) self.needsDisplay = true } } } override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool { if self.isEnabled == false { return self.isEnabled } if self.isEqual(to: sender.draggingSource) == false { return false } return sender.draggingPasteboard.canReadItem(withDataConformingToTypes: [Self.lineStylePboardTypeString]) } override func performDragOperation(_ sender: NSDraggingInfo) -> Bool { let pboard = sender.draggingPasteboard _ = pboard.types let dict = pboard.propertyList(forType: Self.lineStylePboardType) as? [String : Any] if let data = dict?[Self.widthKey] as? NSNumber { self.lineWidth = data.floatValue.cgFloat } else if let data = dict?[Self.styleKey] as? NSNumber { self.style = CPDFBorderStyle(rawValue: data.intValue) ?? .solid } else if let data = dict?[Self.dashPatternKey] { self.dashPattern = data as? NSArray ?? [] } else { if self.displayStyle == .line || self.displayStyle == .simpleLine { if let data = dict?[Self.startLineStyleKey] as? NSNumber { self.startLineStyle = CPDFLineStyle(rawValue: data.intValue) ?? .none } else if let data = dict?[Self.endLineStyleKey] as? NSNumber { self.endLineStyle = CPDFLineStyle(rawValue: data.intValue) ?? .none } } } self.sendAction(self.action, to: self.target) self.isHighlighted = false self.setKeyboardFocusRingNeedsDisplay(self.bounds) self.needsDisplay = true return dict != nil } // - (void)takeValueForKey:(NSString *)key from:(id)object { // [self setValue:[object valueForKey:key] forKey:key]; // NSDictionary *info = [self infoForBinding:key]; // [[info objectForKey:NSObservedObjectKey] setValue:[self valueForKey:key] forKeyPath:[info objectForKey:NSObservedKeyPathKey]]; // } /* + (void)initialize { // SKINITIALIZE; [self exposeBinding:SKLineWellLineWidthKey]; [self exposeBinding:SKLineWellStyleKey]; [self exposeBinding:SKLineWellDashPatternKey]; [self exposeBinding:SKLineWellStartLineStyleKey]; [self exposeBinding:SKLineWellEndLineStyleKey]; } - (Class)valueClassForBinding:(NSString *)binding { if ([binding isEqualToString:SKLineWellDashPatternKey]) return [NSArray class]; else return [NSNumber class]; } - (void)dealloc { // SKENSURE_MAIN_THREAD( [self unbind:SKLineWellLineWidthKey]; [self unbind:SKLineWellStyleKey]; [self unbind:SKLineWellDashPatternKey]; [self unbind:SKLineWellStartLineStyleKey]; [self unbind:SKLineWellEndLineStyleKey]; // ); [[NSNotificationCenter defaultCenter] removeObserver:self]; // SKDESTROY(dashPattern); // [super dealloc]; } #pragma mark Accessors - (SEL)action { return action; } - (void)setAction:(SEL)newAction { action = newAction; } - (id)target { return target; } - (void)setTarget:(id)newTarget { target = newTarget; } - (void)setNilValueForKey:(NSString *)key { if ([key isEqualToString:SKLineWellLineWidthKey] || [key isEqualToString:SKLineWellStyleKey] || [key isEqualToString:SKLineWellStartLineStyleKey] || [key isEqualToString:SKLineWellEndLineStyleKey]) { [self setValue:[NSNumber numberWithInteger:0] forKey:key]; } else { [super setNilValueForKey:key]; } } #pragma mark Notification handlers #pragma mark NSDraggingSource protocol - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal { return NSDragOperationGeneric; } #pragma mark NSDraggingDestination protocol - (NSDragOperation)draggingEntered:(id )sender { if ([self isEnabled] && [sender draggingSource] != self && [[sender draggingPasteboard] canReadItemWithDataConformingToTypes:[NSArray arrayWithObjects:SKPasteboardTypeLineStyle, nil]]) { [self setHighlighted:YES]; [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]]; [self setNeedsDisplay:YES]; return NSDragOperationEvery; } else return NSDragOperationNone; } - (void)draggingExited:(id )sender { if ([self isEnabled] && [sender draggingSource] != self && [[sender draggingPasteboard] canReadItemWithDataConformingToTypes:[NSArray arrayWithObjects:SKPasteboardTypeLineStyle, nil]]) { [self setHighlighted:NO]; [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]]; [self setNeedsDisplay:YES]; } } - (BOOL)prepareForDragOperation:(id )sender { return [self isEnabled] && [sender draggingSource] != self && [[sender draggingPasteboard] canReadItemWithDataConformingToTypes:[NSArray arrayWithObjects:SKPasteboardTypeLineStyle, nil]]; } - (BOOL)performDragOperation:(id )sender{ NSPasteboard *pboard = [sender draggingPasteboard]; [pboard types]; NSDictionary *dict = [pboard propertyListForType:SKPasteboardTypeLineStyle]; if ([dict objectForKey:SKLineWellLineWidthKey]) [self takeValueForKey:SKLineWellLineWidthKey from:dict]; if ([dict objectForKey:SKLineWellStyleKey]) [self takeValueForKey:SKLineWellStyleKey from:dict]; [self takeValueForKey:SKLineWellDashPatternKey from:dict]; if ([self displayStyle] == SKLineWellDisplayStyleLine) { if ([dict objectForKey:SKLineWellStartLineStyleKey]) [self takeValueForKey:SKLineWellStartLineStyleKey from:dict]; if ([dict objectForKey:SKLineWellEndLineStyleKey]) [self takeValueForKey:SKLineWellEndLineStyleKey from:dict]; } [self sendAction:[self action] to:[self target]]; [self setHighlighted:NO]; [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]]; [self setNeedsDisplay:YES]; return dict != nil; } #pragma mark Accessibility - (NSArray *)accessibilityAttributeNames { static NSArray *attributes = nil; if (attributes == nil) { attributes = [[NSArray alloc] initWithObjects: NSAccessibilityRoleAttribute, NSAccessibilityRoleDescriptionAttribute, NSAccessibilityValueAttribute, NSAccessibilityTitleAttribute, NSAccessibilityHelpAttribute, NSAccessibilityFocusedAttribute, NSAccessibilityParentAttribute, NSAccessibilityWindowAttribute, NSAccessibilityTopLevelUIElementAttribute, NSAccessibilityPositionAttribute, NSAccessibilitySizeAttribute, nil]; } return attributes; } - (id)accessibilityAttributeValue:(NSString *)attribute { if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) { return NSAccessibilityCheckBoxRole; } else if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) { return NSAccessibilityRoleDescription(NSAccessibilityCheckBoxRole, nil); } else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) { return [NSNumber numberWithInteger:[self isActive]]; } else if ([attribute isEqualToString:NSAccessibilityTitleAttribute]) { return [NSString stringWithFormat:@"%@ %ld", NSLocalizedString(@"line width", @"Accessibility description"), (long)[self lineWidth]]; } else if ([attribute isEqualToString:NSAccessibilityHelpAttribute]) { return [self toolTip]; } else if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { // Just check if the app thinks we're focused. id focusedElement = [NSApp accessibilityAttributeValue:NSAccessibilityFocusedUIElementAttribute]; return [NSNumber numberWithBool:[focusedElement isEqual:self]]; } else if ([attribute isEqualToString:NSAccessibilityParentAttribute]) { return NSAccessibilityUnignoredAncestor([self superview]); } else if ([attribute isEqualToString:NSAccessibilityWindowAttribute]) { // We're in the same window as our parent. return [NSAccessibilityUnignoredAncestor([self superview]) accessibilityAttributeValue:NSAccessibilityWindowAttribute]; } else if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute]) { // We're in the same top level element as our parent. return [NSAccessibilityUnignoredAncestor([self superview]) accessibilityAttributeValue:NSAccessibilityTopLevelUIElementAttribute]; } else if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) { return [NSValue valueWithPoint:[[self window] convertBaseToScreen:[self convertPoint:[self bounds].origin toView:nil]]]; } else if ([attribute isEqualToString:NSAccessibilitySizeAttribute]) { return [NSValue valueWithSize:[self convertSize:[self bounds].size toView:nil]]; } else { return nil; } } - (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute { if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { return [self canActivate]; } else { return NO; } } - (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute { if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { [[self window] makeFirstResponder:self]; } } // actions - (NSArray *)accessibilityActionNames { return [NSArray arrayWithObject:NSAccessibilityPressAction]; } - (NSString *)accessibilityActionDescription:(NSString *)anAction { return NSAccessibilityActionDescription(anAction); } - (void)accessibilityPerformAction:(NSString *)anAction { if ([anAction isEqualToString:NSAccessibilityPressAction]) [self performClick:self]; } // misc - (BOOL)accessibilityIsIgnored { return NO; } - (id)accessibilityHitTest:(NSPoint)point { return NSAccessibilityUnignoredAncestor(self); } - (id)accessibilityFocusedUIElement { return NSAccessibilityUnignoredAncestor(self); } @end */ } // MARK: - Private Methods extension KMLineWell { private func _commonInit() { __lwFlags.canActivate = true __lwFlags.existsActiveLineWell = 0 self.isEnabled = true self.registerForDraggedTypes([Self.lineStylePboardType]) } private func _deactivate() { if self.isActive { NotificationCenter.default.removeObserver(self) __lwFlags.active = false self.setKeyboardFocusRingNeedsDisplay(self.bounds) self.needsDisplay = true } } private func _changedValue(forKey key: String) { if (self.isActive) { // [[SKLineInspector sharedLineInspector] setValue:[self valueForKey:key] forKey:key]; } self.needsDisplay = true } private func _dragImage() -> NSImage? { let bounds = self.bounds let path = self.lineWidth > 0.0 ? self._path() : nil // CGFloat scale = [self backingScale]; let scale = 1.0 let image = NSImage.bitmapImage(with: bounds.size, scale: scale) { rect in KMContextSetAlpha(NSGraphicsContext.current?.cgContext, 0.7) NSColor.darkGray.setFill() _ = NSRect.fill(NSInsetRect(rect, 1.0, 1.0)) NSColor.controlBackgroundColor.setFill() _ = NSRect.fill(NSInsetRect(rect, 2.0, 2.0)) NSColor.black.setStroke() path?.stroke() } return image } private func _path() -> NSBezierPath { let path = NSBezierPath() let bounds = self.bounds if self.displayStyle == .line { let offset = 0.5 * lineWidth - floor(0.5 * lineWidth) let startPoint = NSMakePoint(NSMinX(bounds) + ceil(0.5 * NSHeight(bounds)), round(NSMidY(bounds)) - offset) let endPoint = NSMakePoint(NSMaxX(bounds) - ceil(0.5 * NSHeight(bounds)), round(NSMidY(bounds)) - offset) switch (self.startLineStyle) { case .none: break case .square: path.appendRect(NSMakeRect(startPoint.x - 1.5 * lineWidth, startPoint.y - 1.5 * lineWidth, 3 * lineWidth, 3 * lineWidth)) break case .circle: path.appendOval(in: NSMakeRect(startPoint.x - 1.5 * lineWidth, startPoint.y - 1.5 * lineWidth, 3 * lineWidth, 3 * lineWidth)) break case .diamond: path.move(to: NSMakePoint(startPoint.x - 1.5 * lineWidth, startPoint.y)) path.line(to: NSMakePoint(startPoint.x, startPoint.y + 1.5 * lineWidth)) path.line(to: NSMakePoint(startPoint.x + 1.5 * lineWidth, startPoint.y)) path.line(to: NSMakePoint(startPoint.x, startPoint.y - 1.5 * lineWidth)) path.close() break case .openArrow: path.move(to: NSMakePoint(startPoint.x + 3.0 * lineWidth, startPoint.y - 1.5 * lineWidth)) path.line(to: NSMakePoint(startPoint.x, startPoint.y)) path.line(to: NSMakePoint(startPoint.x + 3.0 * lineWidth, startPoint.y + 1.5 * lineWidth)) break case .closedArrow: path.move(to: NSMakePoint(startPoint.x + 3.0 * lineWidth, startPoint.y - 1.5 * lineWidth)) path.line(to: NSMakePoint(startPoint.x, startPoint.y)) path.line(to: NSMakePoint(startPoint.x + 3.0 * lineWidth, startPoint.y + 1.5 * lineWidth)) path.close() break @unknown default: fatalError() } path.move(to: startPoint) path.line(to: endPoint) switch (self.endLineStyle) { case .none: break case .square: path.appendRect(NSMakeRect(endPoint.x - 1.5 * lineWidth, endPoint.y - 1.5 * lineWidth, 3 * lineWidth, 3 * lineWidth)) break case .circle: path.appendOval(in: NSMakeRect(endPoint.x - 1.5 * lineWidth, endPoint.y - 1.5 * lineWidth, 3 * lineWidth, 3 * lineWidth)) break case .diamond: path.move(to: NSMakePoint(endPoint.x + 1.5 * lineWidth, endPoint.y)) path.line(to: NSMakePoint(endPoint.x, endPoint.y + 1.5 * lineWidth)) path.line(to: NSMakePoint(endPoint.x - 1.5 * lineWidth, endPoint.y)) path.line(to: NSMakePoint(endPoint.x, endPoint.y - 1.5 * lineWidth)) path.close() break case .openArrow: path.move(to: NSMakePoint(endPoint.x - 3.0 * lineWidth, endPoint.y + 1.5 * lineWidth)) path.line(to: NSMakePoint(endPoint.x, endPoint.y)) path.line(to: NSMakePoint(endPoint.x - 3.0 * lineWidth, endPoint.y - 1.5 * lineWidth)) break case .closedArrow: path.move(to: NSMakePoint(endPoint.x - 3.0 * lineWidth, endPoint.y + 1.5 * lineWidth)) path.line(to: NSMakePoint(endPoint.x, endPoint.y)) path.line(to: NSMakePoint(endPoint.x - 3.0 * lineWidth, endPoint.y - 1.5 * lineWidth)) path.close() break @unknown default: fatalError() } } else if self.displayStyle == .simpleLine { let offset = 0.5 * lineWidth - floor(0.5 * lineWidth) path.move(to: NSMakePoint(NSMinX(bounds) + ceil(0.5 * NSHeight(bounds)), round(NSMidY(bounds)) - offset)) path.line(to: NSMakePoint(NSMaxX(bounds) - ceil(0.5 * NSHeight(bounds)), round(NSMidY(bounds)) - offset)) } else if self.displayStyle == .rectangle { let inset = 7.0 + 0.5 * lineWidth path.appendRect(NSInsetRect(bounds, inset, inset)) } else { let inset = 7.0 + 0.5 * lineWidth path.appendOval(in: NSInsetRect(bounds, inset, inset)) } path.lineWidth = self.lineWidth if self.style == .dashed { path.setLineDash(self.dashPattern as? [CGFloat], count: self.dashPattern?.count ?? 0, phase: 0) } return path } private func _activate(_ exclusive: Bool) { if (self.canActivate) { let nc = NotificationCenter.default let inspector = KMLineInspector.shared _ = inspector.window __lwFlags.existsActiveLineWell = 0 nc.post(name: NSNotification.Name(rawValue: SKLineWellWillBecomeActiveNotification), object: self, userInfo: [EXCLUSIVE_KEY : NSNumber(value: exclusive)]) if (__lwFlags.existsActiveLineWell != 0) { self.takeValue(for: SKLineWellLineWidthKey, from: inspector) self.takeValue(for: SKLineWellDashPatternKey, from: inspector) self.takeValue(for: SKLineWellStyleKey, from: inspector) if (self.displayStyle == .line) { self.takeValue(for: SKLineWellStartLineStyleKey, from: inspector) self.takeValue(for: SKLineWellEndLineStyleKey, from: inspector) } } else { inspector.lineWidth = self.lineWidth inspector.dashPattern = self.dashPattern as? [CGFloat] ?? [] inspector.style = self.style.rawValue if (self.displayStyle == .line) { inspector.startLineStyle = self.startLineStyle.rawValue inspector.endLineStyle = self.endLineStyle.rawValue } } inspector.window?.orderFront(self) nc.addObserver(self, selector: #selector(_lineWellWillBecomeActive), name: NSNotification.Name(SKLineWellWillBecomeActiveNotification), object: nil) nc.addObserver(self, selector: #selector(_lineInspectorWindowWillClose), name: NSWindow.willCloseNotification, object: inspector.window) nc.addObserver(self, selector: #selector(_lineInspectorLineAttributeChanged), name: KMLineInspector.lineAttributeDidChangeNotification, object: inspector) __lwFlags.active = true self.setKeyboardFocusRingNeedsDisplay(self.bounds) self.needsDisplay = true } } @objc private func _lineWellWillBecomeActive(_ notification: NSNotification) { let sender = notification.object if (self.isEqual(to: sender) == false && self.isActive) { if let data = notification.userInfo?[EXCLUSIVE_KEY] as? NSNumber, data.boolValue { self._deactivate() } else { (sender as? KMLineWell)?._existsActiveLineWell() } } } @objc private func _lineInspectorWindowWillClose(_ notification: Notification) { self._deactivate() } @objc private func _lineInspectorLineAttributeChanged(_ notification: Notification) { guard let inspector = notification.object as? KMLineInspector else { return } let action = inspector.currentLineChangeAction var key: String? if action == .lineWidth { key = Self.widthKey } else if action == .style { key = Self.styleKey } else if action == .dashPattern { key = Self.dashPatternKey } else if action == .startLineStyle { key = Self.startLineStyleKey } else if action == .endLineStyle { key = Self.endLineStyleKey } if let _key = key { self.takeValue(for: _key, from: inspector) self.sendAction(self._pri_action, to: self._pri_target) } } private func _existsActiveLineWell() { __lwFlags.existsActiveLineWell = 1 } }