// // KMLineInspector.swift // PDF Reader Pro // // Created by tangchao on 2023/11/10. // import Cocoa enum KMLineChangeAction: Int { case no = 0 case lineWidth case style case dashPattern case startLineStyle case endLineStyle } @objcMembers class KMLineInspector: NSWindowController { private let LINEWIDTH_KEY = "lineWidth" private let STYLE_KEY = "style" private let DASHPATTERN_KEY = "dashPattern" private let STARTLINESTYLE_KEY = "startLineStyle" private let ENDLINESTYLE_KEY = "endLineStyle" private let ACTION_KEY = "action" private let SKLineInspectorFrameAutosaveName = "SKLineInspector" public static let lineAttributeDidChangeNotification = NSNotification.Name("SKLineInspectorLineAttributeDidChangeNotification") /* + (BOOL)sharedLineInspectorExists; */ private var _currentLineChangeAction: KMLineChangeAction = .lineWidth var currentLineChangeAction: KMLineChangeAction { get { return self._currentLineChangeAction } } @IBOutlet var lineWidthSlider: NSSlider! @IBOutlet var lineWidthField: NSTextField! @IBOutlet var dashPatternField: NSTextField! @IBOutlet var styleButton: NSSegmentedControl! @IBOutlet var startLineStyleButton: NSSegmentedControl! @IBOutlet var endLineStyleButton: NSSegmentedControl! @IBOutlet var lineWidthLabelField: NSTextField! @IBOutlet var styleLabelField: NSTextField! @IBOutlet var dashPatternLabelField: NSTextField! @IBOutlet var startLineStyleLabelField: NSTextField! @IBOutlet var endLineStyleLabelField: NSTextField! @IBOutlet var lineWell: KMLineWell! @IBOutlet weak var labelField1: NSTextField! @IBOutlet weak var labelField2: NSTextField! @IBOutlet weak var labelField3: NSTextField! @IBOutlet weak var labelField4: NSTextField! @IBOutlet weak var labelField5: NSTextField! @IBOutlet weak var LineborderStyleLable: NSTextField! @IBOutlet weak var lineEndingStyleLabel: NSTextField! var labelFields: [NSTextField] = [] private var _lineWidth: CGFloat = 0 var lineWidth: CGFloat { get { return self._lineWidth } set { if (abs(self.lineWidth - newValue) > 0.00001) { self._lineWidth = newValue self.lineWell?.lineWidth = newValue self.lineWidthField.stringValue = String(format: "%.0f", self.lineWidth) self.lineWidthSlider.doubleValue = self.lineWidth self._notifyChangeAction(.lineWidth) } } } private var _style: Int = CPDFBorderStyle.solid.rawValue var style: Int { get { return self._style } set { if (newValue != self._style) { self._style = newValue self.lineWell?.style = CPDFBorderStyle(rawValue: newValue) ?? .solid self.styleButton.selectedSegment = self.style self._notifyChangeAction(.style) } } } private var _startLineStyle: Int = CPDFLineStyle.none.rawValue var startLineStyle: Int { get { return self._startLineStyle } set { if (newValue != self.startLineStyle) { self._startLineStyle = newValue self.lineWell?.startLineStyle = CPDFLineStyle(rawValue: newValue) ?? .none self._notifyChangeAction(.startLineStyle) } } } var _endLineStyle: Int = CPDFLineStyle.none.rawValue var endLineStyle: Int { get { return self._endLineStyle } set { if (newValue != self.endLineStyle) { self._endLineStyle = newValue self.lineWell?.endLineStyle = CPDFLineStyle(rawValue: newValue) ?? .none self._notifyChangeAction(.endLineStyle) } } } private var _dashPattern: [CGFloat] = [] var dashPattern: [CGFloat] { get { return self._dashPattern } set { if newValue != self._dashPattern && (newValue.isEmpty == false || self._dashPattern.isEmpty == false) { self._dashPattern = newValue self.lineWell?.dashPattern = newValue as NSArray self.dashPatternField.stringValue = "\(self.dashPattern.count)" self._notifyChangeAction(.dashPattern) } else { self._dashPattern = [] self.lineWell?.dashPattern = newValue as NSArray self.dashPatternField.stringValue = "\(self.dashPattern.count)" self._notifyChangeAction(.dashPattern) } } } static let shared = KMLineInspector(windowNibName: "LineInspector") override func windowDidLoad() { super.windowDidLoad() self._initDefalutValue() self._initLayoutUI() self._initValues() self._initActions() self._initStyleImages() self._initStartImages() self._initEndImages() } private func _initLayoutUI() { let dw = KMAutoSizeLabelFields(labelFields, [lineWidthSlider, lineWidthField, styleButton, dashPatternField, startLineStyleButton, endLineStyleButton], false) if (abs(dw) > 0.0) { KMResizeWindow(self.window!, dw) } } private func _initDefalutValue() { self.window?.title = NSLocalizedString("Lines", comment: "") self.styleButton.setHelp(KMLocalizedString("Solid line style", "Tool tip message"), for: CPDFBorderStyle.solid.rawValue) self.styleButton.setHelp(KMLocalizedString("Dashed line style", "Tool tip message"), for: CPDFBorderStyle.dashed.rawValue) self.styleButton.setHelp(KMLocalizedString("Beveled line style", "Tool tip message"), for: CPDFBorderStyle.beveled.rawValue) self.styleButton.setHelp(KMLocalizedString("Inset line style", "Tool tip message"), for: CPDFBorderStyle.inset.rawValue) self.styleButton.setHelp(KMLocalizedString("Underline line style", "Tool tip message"), for: CPDFBorderStyle.underline.rawValue) self.startLineStyleButton.setHelp(KMLocalizedString("No start line style", "Tool tip message"), for: CPDFLineStyle.none.rawValue) self.startLineStyleButton.setHelp(KMLocalizedString("Square start line style", "Tool tip message"), for: CPDFLineStyle.square.rawValue) self.startLineStyleButton.setHelp(KMLocalizedString("Circle start line style", "Tool tip message"), for: CPDFLineStyle.circle.rawValue) self.startLineStyleButton.setHelp(KMLocalizedString("Diamond start line style", "Tool tip message"), for: CPDFLineStyle.diamond.rawValue) self.startLineStyleButton.setHelp(KMLocalizedString("Open arrow start line style", "Tool tip message"), for: CPDFLineStyle.openArrow.rawValue) self.startLineStyleButton.setHelp(KMLocalizedString("Closed arrow start line style", "Tool tip message"), for: CPDFLineStyle.closedArrow.rawValue) self.endLineStyleButton.setHelp(KMLocalizedString("No end line style", "Tool tip message"), for: CPDFLineStyle.none.rawValue) self.endLineStyleButton.setHelp(KMLocalizedString("Square end line style", "Tool tip message"), for: CPDFLineStyle.square.rawValue) self.endLineStyleButton.setHelp(KMLocalizedString("Circle end line style", "Tool tip message"), for: CPDFLineStyle.circle.rawValue) self.endLineStyleButton.setHelp(KMLocalizedString("Diamond end line style", "Tool tip message"), for: CPDFLineStyle.diamond.rawValue) self.endLineStyleButton.setHelp(KMLocalizedString("Open arrow end line style", "Tool tip message"), for: CPDFLineStyle.openArrow.rawValue) self.endLineStyleButton.setHelp(KMLocalizedString("Closed arrow end line style", "Tool tip message"), for: CPDFLineStyle.closedArrow.rawValue) self.LineborderStyleLable.stringValue = NSLocalizedString("Line and Border Style", comment: "") self.lineEndingStyleLabel.stringValue = NSLocalizedString("Line Ending Style", comment: "") self.lineWidthLabelField.stringValue = NSLocalizedString("Line Width:", comment: "") self.styleLabelField.stringValue = NSLocalizedString("Line Style:", comment: "") self.dashPatternLabelField.stringValue = NSLocalizedString("Dash Pattern:", comment: "") self.startLineStyleLabelField.stringValue = NSLocalizedString("Start:", comment: "") self.endLineStyleLabelField.stringValue = NSLocalizedString("End:", comment: "") self.windowFrameAutosaveName = SKLineInspectorFrameAutosaveName self._currentLineChangeAction = .no self.lineWidthField.formatter = NumberFormatter() self.dashPatternField.formatter = NumberFormatter() } private func _initValues() { self.labelFields = [self.labelField1, self.labelField2, self.labelField3, self.labelField4, self.labelField5] self.lineWell.canActivate = false self.lineWell.lineWidth = self.lineWidth self.lineWell.style = CPDFBorderStyle(rawValue: self.style) ?? .solid self.lineWell.dashPattern = self.dashPattern as NSArray self.lineWell.startLineStyle = CPDFLineStyle(rawValue: self.startLineStyle) ?? .none self.lineWell.endLineStyle = CPDFLineStyle(rawValue: self.endLineStyle) ?? .none self.lineWidthSlider.floatValue = Float(self.lineWidth) self.lineWidthField.stringValue = "\(self.lineWidth)" self.styleButton.selectedSegment = self.style self.dashPatternField.stringValue = "\(self.dashPattern.count)" self.startLineStyleButton.selectedSegment = self.startLineStyle self.endLineStyleButton.selectedSegment = self.endLineStyle } private func _initActions() { self.lineWidthSlider.target = self self.lineWidthSlider.action = #selector(lineWidthSliderAction) self.styleButton.target = self self.styleButton.action = #selector(styleAction) self.startLineStyleButton.target = self self.startLineStyleButton.action = #selector(startLineStyleAction) self.endLineStyleButton.target = self self.endLineStyleButton.action = #selector(endLineStyleAction) self.lineWidthField.delegate = self self.dashPatternField.delegate = self } private func _initStyleImages() { var image: NSImage? var size = NSMakeSize(29.0, 12.0) image = NSImage.image(with: size, drawingHandler: { rect in let path = NSBezierPath(rect: NSMakeRect(6.0, 3.0, 17.0, 6.0)) path.lineWidth = 2.0 NSColor.black.setStroke() path.stroke() return true }) self.styleButton.setImage(image, forSegment: CPDFBorderStyle.solid.rawValue) image = NSImage.image(with: size, drawingHandler: { dstRect in let path = NSBezierPath() path.move(to: NSMakePoint(6.0, 5.0)) path.line(to: NSMakePoint(6.0, 3.0)) path.line(to: NSMakePoint(9.0, 3.0)) path.move(to: NSMakePoint(12.0, 3.0)) path.line(to: NSMakePoint(17.0, 3.0)) path.move(to: NSMakePoint(20.0, 3.0)) path.line(to: NSMakePoint(23.0, 3.0)) path.line(to: NSMakePoint(23.0, 5.0)) path.move(to: NSMakePoint(23.0, 7.0)) path.line(to: NSMakePoint(23.0, 9.0)) path.line(to: NSMakePoint(20.0, 9.0)) path.move(to: NSMakePoint(17.0, 9.0)) path.line(to: NSMakePoint(12.0, 9.0)) path.move(to: NSMakePoint(9.0, 9.0)) path.line(to: NSMakePoint(6.0, 9.0)) path.line(to: NSMakePoint(6.0, 7.0)) path.lineWidth = 2.0 NSColor.black.setStroke() path.stroke() return true }) self.styleButton.setImage(image, forSegment: CPDFBorderStyle.dashed.rawValue) image = NSImage.image(with: size, drawingHandler: { dstRect in var path = NSBezierPath(rect: NSMakeRect(6.0, 3.0, 17.0, 6.0)) path.lineWidth = 2.0 NSColor(calibratedWhite: 0, alpha: 0.25).setStroke() path.stroke() path = NSBezierPath() path.move(to: NSMakePoint(7.0, 3.0)) path.line(to: NSMakePoint(23.0, 3.0)) path.line(to: NSMakePoint(23.0, 8.0)) path.lineWidth = 2.0 NSColor(calibratedWhite: 0, alpha: 0.35).set() path.stroke() path = NSBezierPath() path.move(to: NSMakePoint(5.0, 2.0)) path.line(to: NSMakePoint(7.0, 4.0)) path.line(to: NSMakePoint(7.0, 2.0)) path.close() path.move(to: NSMakePoint(24.0, 10.0)) path.line(to: NSMakePoint(22.0, 8.0)) path.line(to: NSMakePoint(24.0, 8.0)) path.close() path.fill() return true }) self.styleButton.setImage(image, forSegment: CPDFBorderStyle.beveled.rawValue) image = NSImage.image(with: size, drawingHandler: { dstRect in var path = NSBezierPath(rect: NSMakeRect(6.0, 3.0, 17.0, 6.0)) path.lineWidth = 2.0 NSColor(calibratedWhite: 0, alpha: 0.25).setStroke() path.stroke() path = NSBezierPath() path.move(to: NSMakePoint(6.0, 4.0)) path.line(to: NSMakePoint(6.0, 9.0)) path.line(to: NSMakePoint(22.0, 9.0)) path.lineWidth = 2.0 NSColor(calibratedWhite: 0, alpha: 0.35).set() path.stroke() path = NSBezierPath() path.move(to: NSMakePoint(5.0, 2.0)) path.line(to: NSMakePoint(7.0, 4.0)) path.line(to: NSMakePoint(5.0, 4.0)) path.close() path.move(to: NSMakePoint(24.0, 10.0)) path.line(to: NSMakePoint(22.0, 8.0)) path.line(to: NSMakePoint(22.0, 10.0)) path.close() path.fill() return true }) self.styleButton.setImage(image, forSegment: CPDFBorderStyle.inset.rawValue) image = NSImage.image(with: size, drawingHandler: { dstRect in let path = NSBezierPath() path.move(to: NSMakePoint(6.0, 3.0)) path.line(to: NSMakePoint(23.0, 3.0)) path.lineWidth = 2.0 NSColor(calibratedWhite: 0, alpha: 0.5).setStroke() path.stroke() return true }) self.styleButton.setImage(image, forSegment: CPDFBorderStyle.underline.rawValue) } private func _initStartImages() { var image: NSImage? var size = NSMakeSize(24.0, 12.0) image = NSImage.image(with: size, drawingHandler: { dstRect in let path = NSBezierPath() path.move(to: NSMakePoint(20.0, 6.0)) path.line(to: NSMakePoint(8.0, 6.0)) path.lineWidth = 2.0 NSColor.black.setStroke() path.stroke() return true }) self.startLineStyleButton.setImage(image, forSegment: CPDFLineStyle.none.rawValue) image = NSImage.image(with: size, drawingHandler: { rect in let path = NSBezierPath() path.move(to: NSMakePoint(20.0, 6.0)) path.line(to: NSMakePoint(8.0, 6.0)) path.appendRect(NSMakeRect(5.0, 3.0, 6.0, 6.0)) path.lineWidth = 2.0 NSColor.black.setStroke() path.stroke() return true }) // CPDFLineStyle.square.rawValue self.startLineStyleButton.setImage(image, forSegment: 1) image = NSImage.image(with: size, drawingHandler: { dstRect in let path = NSBezierPath() path.move(to: NSMakePoint(20.0, 6.0)) path.line(to: NSMakePoint(8.0, 6.0)) path.appendOval(in: NSMakeRect(5.0, 3.0, 6.0, 6.0)) path.lineWidth = 2.0 NSColor.black.setStroke() path.stroke() return true }) // CPDFLineStyle.circle.rawValue self.startLineStyleButton.setImage(image, forSegment: 2) image = NSImage.image(with: size, drawingHandler: { dstRect in let path = NSBezierPath() path.move(to: NSMakePoint(20.0, 6.0)) path.line(to: NSMakePoint(8.0, 6.0)) path.move(to: NSMakePoint(12.0, 6.0)) path.line(to: NSMakePoint(8.0, 10.0)) path.line(to: NSMakePoint(4.0, 6.0)) path.line(to: NSMakePoint(8.0, 2.0)) path.close() path.lineWidth = 2.0 path.stroke() return true }) // CPDFLineStyle.diamond.rawValue self.startLineStyleButton.setImage(image, forSegment: 3) image = NSImage.image(with: size, drawingHandler: { dstRect in let path = NSBezierPath() path.move(to: NSMakePoint(20.0, 6.0)) path.line(to: NSMakePoint(8.0, 6.0)) path.move(to: NSMakePoint(14.0, 3.0)) path.line(to: NSMakePoint(8.0, 6.0)) path.line(to: NSMakePoint(14.0, 9.0)) path.lineWidth = 2.0 NSColor.black.setStroke() path.stroke() return true }) // CPDFLineStyle.openArrow.rawValue self.startLineStyleButton.setImage(image, forSegment: 4) image = NSImage.image(with: size, drawingHandler: { dstRect in let path = NSBezierPath() path.move(to: NSMakePoint(20.0, 6.0)) path.line(to: NSMakePoint(8.0, 6.0)) path.move(to: NSMakePoint(14.0, 3.0)) path.line(to: NSMakePoint(8.0, 6.0)) path.line(to: NSMakePoint(14.0, 9.0)) path.close() path.lineWidth = 2 NSColor.black.setStroke() path.stroke() return true }) // CPDFLineStyle.closedArrow.rawValue self.startLineStyleButton.setImage(image, forSegment: 5) } private func _initEndImages() { var image: NSImage? var size = NSMakeSize(24.0, 12.0) image = NSImage.image(with: size, drawingHandler: { dstRect in let path = NSBezierPath() path.move(to: NSMakePoint(4.0, 6.0)) path.line(to: NSMakePoint(16.0, 6.0)) path.lineWidth = 2.0 NSColor.black.setStroke() path.stroke() return true }) self.endLineStyleButton.setImage(image, forSegment: CPDFLineStyle.none.rawValue) image = NSImage.image(with: size, drawingHandler: { dstRect in let path = NSBezierPath() path.move(to: NSMakePoint(4.0, 6.0)) path.line(to: NSMakePoint(16.0, 6.0)) path.appendRect(NSMakeRect(13.0, 3.0, 6.0, 6.0)) path.lineWidth = 2.0 NSColor.black.setStroke() path.stroke() return true }) // CPDFLineStyle.square.rawValue self.endLineStyleButton.setImage(image, forSegment: 1) image = NSImage.image(with: size, drawingHandler: { dstRect in let path = NSBezierPath() path.move(to: NSMakePoint(4.0, 6.0)) path.line(to: NSMakePoint(16.0, 6.0)) path.appendOval(in: NSMakeRect(13.0, 3.0, 6.0, 6.0)) path.lineWidth = 2.0 NSColor.black.setStroke() path.stroke() return true }) // CPDFLineStyle.circle.rawValue self.endLineStyleButton.setImage(image, forSegment: 2) image = NSImage.image(with: size, drawingHandler: { dstRect in let path = NSBezierPath() path.move(to: NSMakePoint(4.0, 6.0)) path.line(to: NSMakePoint(16.0, 6.0)) path.move(to: NSMakePoint(12.0, 6.0)) path.line(to: NSMakePoint(16.0, 10.0)) path.line(to: NSMakePoint(20.0, 6.0)) path.line(to: NSMakePoint(16.0, 2.0)) path.close() path.lineWidth = 2.0 path.stroke() return true }) // CPDFLineStyle.diamond.rawValue self.endLineStyleButton.setImage(image, forSegment: 3) image = NSImage.image(with: size, drawingHandler: { dstRect in let path = NSBezierPath() path.move(to: NSMakePoint(4.0, 6.0)) path.line(to: NSMakePoint(16.0, 6.0)) path.move(to: NSMakePoint(10.0, 3.0)) path.line(to: NSMakePoint(16.0, 6.0)) path.line(to: NSMakePoint(10.0, 9.0)) path.lineWidth = 2 NSColor.black.setStroke() path.stroke() return true }) // CPDFLineStyle.openArrow.rawValue self.endLineStyleButton.setImage(image, forSegment: 4) image = NSImage.image(with: size, drawingHandler: { dstRect in let path = NSBezierPath() path.move(to: NSMakePoint(4.0, 6.0)) path.line(to: NSMakePoint(16.0, 6.0)) path.move(to: NSMakePoint(10.0, 3.0)) path.line(to: NSMakePoint(16.0, 6.0)) path.line(to: NSMakePoint(10.0, 9.0)) path.close() path.lineWidth = 2 NSColor.black.setStroke() path.stroke() return true }) // CPDFLineStyle.closedArrow.rawValue self.endLineStyleButton.setImage(image, forSegment: 5) } override func setNilValueForKey(_ key: String) { if key == LINEWIDTH_KEY { self.setValue(0.0, forKey: key) } else if key == STYLE_KEY || key == STARTLINESTYLE_KEY || key == ENDLINESTYLE_KEY { self.setValue(0, forKey: key) } else { super.setNilValueForKey(key) } } override func value(forUndefinedKey key: String) -> Any? { KMPrint("forUndefinedKey: \(key)") } func setAnnotationStyle(_ annotation: CPDFAnnotation) { let type = annotation.type if type == SKNFreeTextString || type == CPDFAnnotation.kCircleType || type == CPDFAnnotation.kSquareType || type == CPDFAnnotation.kArrowType || type == CPDFAnnotation.kLineType || type == SKNInkString { if let model = CPDFAnnotationModel(pdfAnnotations: [annotation]) { self.lineWidth = model.lineWidth() self.style = model.style().rawValue self.dashPattern = model.dashPattern() as? [CGFloat] ?? [] } } if type == CPDFAnnotation.kArrowType || type == CPDFAnnotation.kLineType { if let anno = annotation as? CPDFLineAnnotation { self.startLineStyle = anno.startLineStyle.rawValue self.endLineStyle = anno.endLineStyle.rawValue } } } // MARK: - Actions @objc func lineWidthSliderAction(_ sender: NSSlider) { self.lineWidth = sender.floatValue.cgFloat } @objc func styleAction(_ sender: NSSegmentedControl) { self.style = sender.selectedSegment } @objc func startLineStyleAction(_ sender: NSSegmentedControl) { let index = sender.selectedSegment if index == 0 { self.startLineStyle = CPDFLineStyle.none.rawValue } else if index == 1 { self.startLineStyle = CPDFLineStyle.square.rawValue } else if index == 2 { self.startLineStyle = CPDFLineStyle.circle.rawValue } else if index == 3 { self.startLineStyle = CPDFLineStyle.diamond.rawValue } else if index == 4 { self.startLineStyle = CPDFLineStyle.openArrow.rawValue } else if index == 5 { self.startLineStyle = CPDFLineStyle.closedArrow.rawValue } } @objc func endLineStyleAction(_ sender: NSSegmentedControl) { let index = sender.selectedSegment if index == 0 { self.endLineStyle = CPDFLineStyle.none.rawValue } else if index == 1 { self.endLineStyle = CPDFLineStyle.square.rawValue } else if index == 2 { self.endLineStyle = CPDFLineStyle.circle.rawValue } else if index == 3 { self.endLineStyle = CPDFLineStyle.diamond.rawValue } else if index == 4 { self.endLineStyle = CPDFLineStyle.openArrow.rawValue } else if index == 5 { self.endLineStyle = CPDFLineStyle.closedArrow.rawValue } } } // MARK: - Private Methods extension KMLineInspector { private func _notifyChangeAction(_ action: KMLineChangeAction) { self._currentLineChangeAction = action let selector = NSSelectorFromString("changeLineAttribute:") let mainWindow = NSApp.mainWindow var responder = mainWindow?.firstResponder while (responder != nil && responder!.responds(to: selector) == false) { responder = responder!.nextResponder } responder?.perform(selector, with: self) let userInfo = [ACTION_KEY : NSNumber(value: action.rawValue)] NotificationCenter.default.post(name: Self.lineAttributeDidChangeNotification, object: self, userInfo: userInfo) self._currentLineChangeAction = .no } } extension KMLineInspector: NSTextFieldDelegate { func controlTextDidChange(_ obj: Notification) { if self.lineWidthField.isEqual(to: obj.object) { let value = self.lineWidthField.doubleValue if value < self.lineWidthSlider.minValue { self.lineWidth = self.lineWidthSlider.minValue } else if value > self.lineWidthSlider.maxValue { self.lineWidth = self.lineWidthSlider.maxValue } else { self.lineWidth = value } } else if self.dashPatternField.isEqual(to: obj.object) { let cnt = self.dashPatternField.integerValue let data = [CGFloat](repeating: 3.0, count: cnt) self.dashPattern = data } } }