// // KMEditPDFPopToolBarController.swift // PDF Reader Pro // // Created by tangchao on 2024/6/18. // import Cocoa @objc enum KMEditPDFToolbarItemKey: Int { case none case color case fontStyle case fontAdd case fontReduce case fontBold case fontItalic case textAlignment case leftRotate case rightRotate case reverseX // 左右翻转 case reverseY // 上下翻转 case crop case replace case export case alignmentLeft case alignmentRight case alignmentCenterX case alignmentjustifiedX // 左右两端对齐 case alignmentTop case alignmentBottom case alignmentCenterY case alignmentjustifiedY // 上下两端对齐 case separator // 分割线 func isRotate() -> Bool { return self == .leftRotate || self == .rightRotate } func isReverse() -> Bool { return self == .reverseX || self == .reverseY } func isAlign() -> Bool { return self.isHorAlign() || self.isVerAlign() } func isHorAlign() -> Bool { return self == .alignmentLeft || self == .alignmentCenterX || self == .alignmentRight || self == .alignmentjustifiedX } func isVerAlign() -> Bool { return self == .alignmentTop || self == .alignmentCenterY || self == .alignmentBottom || self == .alignmentjustifiedY } } class KMEditPDFToolbarModel: NSObject { var itemKey: KMEditPDFToolbarItemKey = .none var isEnabled = true var isSelected = false var fontName: String? var state: NSControl.StateValue = .mixed var textAlign: NSTextAlignment = .center } class KMSeparatorLineView: NSView { var strokeColor: NSColor = .black { didSet { self.needsDisplay = true } } override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) let rect = self.bounds let ctx = NSGraphicsContext.current?.cgContext ctx?.saveGState() ctx?.setLineWidth(1) ctx?.setStrokeColor(self.strokeColor.cgColor) let startP = NSPoint(x: rect.size.width*0.5, y: 6+2) ctx?.move(to: startP) let endP = NSPoint(x: rect.size.width*0.5, y: rect.size.height-8) ctx?.addLine(to: endP) ctx?.strokePath() ctx?.restoreGState() } } struct KMEditPDFToolbarStyle: OptionSet { let rawValue: Int static var text = KMEditPDFToolbarStyle(rawValue: 1 << 0) static var image = KMEditPDFToolbarStyle(rawValue: 1 << 1) } class KMEditPDFPopToolBarController: NSViewController { deinit { KMPrint("KMEditPDFPopToolBarController deinit.") } convenience init() { self.init(nibName: "KMEditPDFPopToolBarController", bundle: nil) } var toolbarView: KMEditPDFToolbarView? var itemKeys: [KMEditPDFToolbarItemKey] = [] { didSet { // self.toolbarView?.reloadData() } } var datas: [KMEditPDFToolbarModel] = [] { didSet { self.toolbarView?.reloadData() } } var fontColor: NSColor? var fontName: String? var areaCount: Int = 0 var itemClick: ((KMEditPDFToolbarItemKey, Any?)->Void)? private weak var popover_: NSPopover? override func viewWillAppear() { super.viewWillAppear() self.interfaceThemeDidChanged(NSApp.appearance?.name ?? .aqua) } override func viewDidLoad() { super.viewDidLoad() // Do view setup here. // self.interfaceThemeDidChanged(NSApp.appearance?.name ?? .aqua) let toolbarView = KMEditPDFToolbarView() self.toolbarView = toolbarView toolbarView.frame = self.view.bounds toolbarView.autoresizingMask = [.width, .height] self.view.addSubview(toolbarView) toolbarView.delegate = self toolbarView.inset = .init(top: 6, left: 4, bottom: 0, right: 0) toolbarView.reloadData() } override func viewDidDisappear() { super.viewDidDisappear() let itemView = (self.toolbarView?.itemViews.safe_element(for: 1) as? KMEditPDFToolbarItemView) (itemView?.obj as? KMDesignSelect)?.createFilePopover.close() self.popover_?.close() } @objc func _buttonClick(_ sender: NSButton) { let idx = sender.tag if idx >= 0 && idx < self.itemKeys.count { let key = self.itemKeys[idx] self.itemClick?(key, sender) let itemView = (self.toolbarView?.itemViews.safe_element(for: idx) as? KMEditPDFToolbarItemView) let viewC = (itemView?.obj as? KMDesignButton) if key == .fontAdd || key == .fontReduce { } else { let state = viewC?.state ?? .None if state == .Act { viewC?.state = .Norm } else { viewC?.state = .Act } } self._trackEvent(key: key, style: self._fetchStyle()) } } @objc func alignmentItemClick(_ sender: NSButton) { self._trackEvent(key: .alignmentTop, style: self._fetchStyle()) let key = self.itemKeys.safe_element(for: sender.tag) as? KMEditPDFToolbarItemKey ?? .none let isHor = (key == .alignmentLeft || key == .alignmentCenterX || key == .alignmentRight || key == .alignmentjustifiedX) let vc = KMAlignmentController(withIsHor: isHor, areaCount: self.areaCount) let createFilePopover: NSPopover = NSPopover.init() createFilePopover.contentViewController = vc // createFilePopover.delegate = self createFilePopover.animates = true createFilePopover.behavior = .semitransient createFilePopover.setValue(true, forKey: "shouldHideAnchor") createFilePopover.show(relativeTo: CGRect(x: sender.bounds.origin.x, y: 20, width: sender.bounds.size.width, height: sender.bounds.size.height), of: sender, preferredEdge: .maxY) self.popover_ = createFilePopover vc.itemAction = { [weak self] idx, _ in var key: KMEditPDFToolbarItemKey = .none if idx == 0 { key = isHor ? .alignmentLeft : .alignmentTop } else if idx == 1 { key = isHor ? .alignmentCenterX : .alignmentCenterY } else if idx == 2 { key = isHor ? .alignmentRight : .alignmentBottom } else if idx == 3 { key = isHor ? .alignmentjustifiedX : .alignmentjustifiedY } self?.itemClick?(key, nil) let model = self?.datas.safe_element(for: sender.tag) as? KMEditPDFToolbarModel model?.itemKey = key self?.toolbarView?.reloadData() self?.popover_?.close() } } @objc func textAlignmentItemClick(_ sender: NSButton) { self._trackEvent(key: .textAlignment) let itemView = (self.toolbarView?.itemViews.safe_element(for: sender.tag) as? KMEditPDFToolbarItemView) let viewC = (itemView?.obj as? KMDesignButton) viewC?.state = .Act let vc = KMTextAlignmentController(nibName: "KMTextAlignmentController", bundle: nil) let createFilePopover: NSPopover = NSPopover.init() createFilePopover.contentViewController = vc // createFilePopover.delegate = self createFilePopover.animates = true createFilePopover.behavior = .semitransient createFilePopover.setValue(true, forKey: "shouldHideAnchor") createFilePopover.show(relativeTo: CGRect(x: sender.bounds.origin.x, y: 20, width: sender.bounds.size.width, height: sender.bounds.size.height), of: sender, preferredEdge: .maxY) let model = self.datas.safe_element(for: sender.tag) as? KMEditPDFToolbarModel vc.align = model?.textAlign ?? .center self.popover_ = createFilePopover vc.itemAction = { [weak self] idx, _ in viewC?.state = .Norm var data: NSTextAlignment = .left if idx == 0 { data = .left } else if idx == 1 { data = .center } else if idx == 2 { data = .right } self?.itemClick?(.textAlignment, data) self?.popover_?.close() let model = self?.datas.safe_element(for: sender.tag) as? KMEditPDFToolbarModel model?.textAlign = data // self?.toolbarView?.reloadData() if data == .left { viewC?.image = NSImage(named: "KMImageNameEditPDFAlignLeftSelect")! } else if data == .center { viewC?.image = NSImage(named: "KMImageNameEditPDFAlignCenterSelect")! } else if data == .right { viewC?.image = NSImage(named: "KMImageNameEditPDFAlignRightSelect")! } } } @objc func colorPanelAction(_ sender: NSColorPanel) { let color = sender.color let colorView = (self.toolbarView?.itemViews.first as? KMEditPDFToolbarItemView)?.view as? KMEditPDFColorView colorView?.colorBtn.image = nil colorView?.colorBtn.layer?.backgroundColor = color.cgColor self.itemClick?(.color, color) } override func interfaceThemeDidChanged(_ appearance: NSAppearance.Name) { super.interfaceThemeDidChanged(appearance) self.view.wantsLayer = true if appearance == .darkAqua { self.view.layer?.backgroundColor = NSColor(red: 37/255.0, green: 37/255.0, blue: 38/255.0, alpha: 1.0).cgColor } else { self.view.layer?.backgroundColor = .white } Task { @MainActor in self.toolbarView?.reloadData() } } // MARK: - Track Events private func _trackEvent(key: KMEditPDFToolbarItemKey, style: KMEditPDFToolbarStyle = []) { let catetoryString = "SubTbr_EditPDF" let label_text = "SubTbr_EditText" let label_image = "SubTbr_EditImage" // Text if key == .color { let eventString = "SubTbr_EditText_FloatBar_Color" kTrackEventManager.trackEvent(event: catetoryString, withProperties: [label_text : eventString]) } else if key == .fontStyle { let eventString = "SubTbr_EditText_FloatBar_FontStyle" kTrackEventManager.trackEvent(event: catetoryString, withProperties: [label_text : eventString]) } else if key == .fontAdd || key == .fontReduce { let eventString = "SubTbr_EditText_FloatBar_FontSize" kTrackEventManager.trackEvent(event: catetoryString, withProperties: [label_text : eventString]) } else if key == .fontBold { let eventString = "SubTbr_EditText_FloatBar_Bold" kTrackEventManager.trackEvent(event: catetoryString, withProperties: [label_text : eventString]) } else if key == .fontItalic { let eventString = "SubTbr_EditText_FloatBar_Italic" kTrackEventManager.trackEvent(event: catetoryString, withProperties: [label_text : eventString]) } else if key == .textAlignment { let eventString = "SubTbr_EditText_FloatBar_AlignText" kTrackEventManager.trackEvent(event: catetoryString, withProperties: [label_text : eventString]) } // Image else if key.isRotate() { let eventString = "SubTbr_EditImage_FloatBar_Rotate" kTrackEventManager.trackEvent(event: catetoryString, withProperties: [label_image : eventString]) } else if key.isReverse() { let eventString = "SubTbr_EditImage_FloatBar_Flip" kTrackEventManager.trackEvent(event: catetoryString, withProperties: [label_image : eventString]) } else if key == .crop { let eventString = "SubTbr_EditImage_FloatBar_Crop" kTrackEventManager.trackEvent(event: catetoryString, withProperties: [label_image : eventString]) } else if key == .replace { let eventString = "SubTbr_EditImage_FloatBar_Replace" kTrackEventManager.trackEvent(event: catetoryString, withProperties: [label_image : eventString]) } else if key == .export { let eventString = "SubTbr_EditImage_FloatBar_Export" kTrackEventManager.trackEvent(event: catetoryString, withProperties: [label_image : eventString]) } // 对齐 else if key.isAlign() { if style.contains(.text) && style.contains(.image) { let eventString = "SubTbr_EditContent_AlignContent" kTrackEventManager.trackEvent(event: catetoryString, withProperties: [label_text : eventString]) } else if style.contains(.text) { let eventString = "SubTbr_EditText_FloatBar_AlignTextBoxes" kTrackEventManager.trackEvent(event: catetoryString, withProperties: [label_text : eventString]) } else if style.contains(.image) { let eventString = "SubTbr_EditImage_FloatBar_Align" kTrackEventManager.trackEvent(event: catetoryString, withProperties: [label_image : eventString]) } } } private func _fetchStyle() -> KMEditPDFToolbarStyle { var style: KMEditPDFToolbarStyle = [] if let data = self.itemKeys.first { if data == .color { style.insert(.text) } else if data.isRotate() { style.insert(.image) } else if data.isAlign() { style.insert(.text) style.insert(.image) } } return style } } extension KMEditPDFPopToolBarController: KMEditPDFToolbarViewDelegate { func numberOfItems(in toolbarView: KMEditPDFToolbarView) -> Int { return self.datas.count } func toolbarView(_ toolbarView: KMEditPDFToolbarView, viewFor index: Int) -> NSView? { let model = self.datas[index] let itemKey = model.itemKey if itemKey == .color { let colorView = KMEditPDFToolbarItemView() let view = KMEditPDFColorView() // view.isEnabled = model.isEnabled if model.isEnabled { view.colorBtn.layer?.backgroundColor = self.fontColor?.cgColor } else { view.colorBtn.layer?.backgroundColor = .clear view.colorBtn.image = NSImage(named: "KMImageNameEditPDFColorDisabled") } view.itemClick = { [weak self] idx, _ in self?._trackEvent(key: .color) let panel = NSColorPanel.shared panel.setTarget(self) panel.setAction(#selector(self?.colorPanelAction)) panel.orderFront(nil) } colorView.view = view return colorView } else if itemKey == .fontStyle { let fontStyleView = KMEditPDFToolbarItemView() let viewC = KMDesignSelect.init(withType: .Combox) viewC.isScrollPop = true viewC.showVerticalScroller = true fontStyleView.view = viewC.view fontStyleView.obj = viewC viewC.borderColor = KMAppearance.Interactive.s0Color() viewC.background = KMAppearance.Layout.l1Color() viewC.background_hov = KMAppearance.Layout.l1Color() viewC.background_focus = KMAppearance.Layout.l1Color() viewC.textColor = KMAppearance.Layout.h1Color() viewC.textColor_hov = KMAppearance.Layout.h1Color() viewC.textColor_focus = KMAppearance.Layout.h1Color() viewC.popViewControllerBackground = KMAppearance.Layout.bgColor() viewC.popViewControllerTextColor = KMAppearance.Layout.h0Color() viewC.popViewControllerEnterFillColor = KMAppearance.Interactive.s0Color() viewC.updateUI() let familyNames = CPDFFont.familyNames viewC.removeAllItems() viewC.addItems(withObjectValues: familyNames) if let data = model.fontName { viewC.stringValue = data } else { // viewC.selectItem(at: 0) viewC.stringValue = "--" } viewC.delete = self return fontStyleView } else if itemKey == .separator { let colorView = KMEditPDFToolbarItemView() let view = KMSeparatorLineView() view.strokeColor = KMAppearance.separatorLineColor() colorView.view = view return colorView } else if itemKey == .textAlignment { let colorView = KMEditPDFToolbarItemView() let viewC = KMDesignButton(withType: .Image) colorView.view = viewC.view colorView.obj = viewC viewC.pagination() if KMAppearance.isDarkMode() { viewC.background_hov = NSColor(red: 71/255, green: 72/255, blue: 75/255, alpha: 1) viewC.background_sel = KMAppearance.Interactive.m1Color() } else { viewC.background_hov = NSColor(hex: "#EDEEF0") } viewC.tag = index viewC.target = self viewC.action = #selector(textAlignmentItemClick) if model.textAlign == .left { viewC.image = NSImage(named: "KMImageNameEditPDFAlignLeftSelect")! } else if model.textAlign == .center { viewC.image = NSImage(named: "KMImageNameEditPDFAlignCenterSelect")! } else if model.textAlign == .right { viewC.image = NSImage(named: "KMImageNameEditPDFAlignRightSelect")! } viewC.initDefaultValue() viewC.borderWidth = 0 viewC.borderWidth_hov = 0 viewC.borderWidth_act = 0 if KMAppearance.isDarkMode() { viewC.background = NSColor.clear viewC.background_hov = NSColor(red: 71/255, green: 72/255, blue: 75/255, alpha: 1) viewC.background_act = KMAppearance.Interactive.m1Color() } else { } DispatchQueue.main.async { viewC.state = model.isSelected ? .Act : .Norm } return colorView } else if itemKey.isAlign() { let itemView = KMEditPDFToolbarItemView() let viewC = KMDesignButton(withType: .Image) itemView.view = viewC.view itemView.obj = viewC viewC.pagination() if KMAppearance.isDarkMode() { viewC.background_hov = NSColor(red: 71/255, green: 72/255, blue: 75/255, alpha: 1) viewC.background_sel = KMAppearance.Interactive.m1Color() } else { viewC.background_hov = NSColor(hex: "#EDEEF0") } viewC.tag = index viewC.state = model.isEnabled ? .Norm : .Disabled viewC.button.isEnabled = model.isEnabled viewC.target = self viewC.background_disabled = .clear viewC.borderColor_disabled = .clear let style = self._fetchStyle() if style.contains(.text) && style.contains(.image) { viewC.action = #selector(_buttonClick) } else { viewC.action = #selector(alignmentItemClick) } let hasTip = style.contains(.text) && style.contains(.image) if itemKey == .alignmentLeft { viewC.image = NSImage(named: "KMImageNameEditPDFImage45_1")! if hasTip { itemView.popToolTip = NSLocalizedString("Align left", comment: "") } } else if itemKey == .alignmentCenterX { viewC.image = NSImage(named: "KMImageNameEditPDFImage46_1")! if hasTip { itemView.popToolTip = NSLocalizedString("Align center", comment: "") } } else if itemKey == .alignmentRight { viewC.image = NSImage(named: "KMImageNameEditPDFImage47_1")! if hasTip { itemView.popToolTip = NSLocalizedString("Align right", comment: "") } } else if itemKey == .alignmentjustifiedX { viewC.image = NSImage(named: "KMImageNameEditPDFImage21_1")! viewC._image_disabled = NSImage(named: "KMImageNameEditPDFImage21")! if hasTip { itemView.popToolTip = NSLocalizedString("Align both ends", comment: "") } } else if itemKey == .alignmentTop { viewC.image = NSImage(named: "KMImageNameEditPDFImage48_1")! if hasTip { itemView.popToolTip = NSLocalizedString("Align top", comment: "") } } else if itemKey == .alignmentCenterY { viewC.image = NSImage(named: "KMImageNameEditPDFImage49_1")! if hasTip { itemView.popToolTip = NSLocalizedString("Align center in vertical", comment: "") } } else if itemKey == .alignmentBottom { viewC.image = NSImage(named: "KMImageNameEditPDFImage50_1")! if hasTip { itemView.popToolTip = NSLocalizedString("Align bottom", comment: "") } } else if itemKey == .alignmentjustifiedY { viewC.image = NSImage(named: "KMImageNameEditPDFImage20_1")! viewC._image_disabled = NSImage(named: "KMImageNameEditPDFImage20")! if hasTip { itemView.popToolTip = NSLocalizedString("Align ends in vertical", comment: "") } } viewC.updateUI() return itemView } let colorView = KMEditPDFToolbarItemView() let viewC = KMDesignButton(withType: .Image) colorView.view = viewC.view colorView.obj = viewC viewC.pagination() if KMAppearance.isDarkMode() { viewC.background_hov = NSColor(red: 71/255, green: 72/255, blue: 75/255, alpha: 1) } else { // viewC.background_hov = NSColor(hex: "#365898") } viewC.tag = index viewC.state = model.isEnabled ? .Norm : .Disabled viewC.button.isEnabled = model.isEnabled viewC.target = self viewC.action = #selector(_buttonClick) viewC.background_disabled = .clear viewC.borderColor_disabled = .clear if itemKey == .fontAdd { viewC.image = NSImage(named: "KMImageNameEditPDFFontAdd")! viewC.image_disabled = NSImage(named: "KMImageNameEditPDFFontAddDisabled")! colorView.popToolTip = NSLocalizedString("Larger font size", comment: "") } else if itemKey == .fontReduce { viewC.image = NSImage(named: "KMImageNameEditPDFFontReduce")! viewC.image_disabled = NSImage(named: "KMImageNameEditPDFFontReduceDisabled")! colorView.popToolTip = NSLocalizedString("Reduce font size", comment: "") } else if itemKey == .fontBold { viewC.image = NSImage(named: "KMImageNameEditPDFFontBold")! colorView.popToolTip = NSLocalizedString("Bold font", comment: "") // viewC.button.keyEquivalent = "b" // viewC.button.keyEquivalentModifierMask = [.control] viewC.initDefaultValue() viewC.borderWidth = 0 viewC.borderWidth_hov = 0 viewC.borderWidth_act = 0 DispatchQueue.main.async { if model.isEnabled { viewC.state = model.isSelected ? .Act : .Norm } else { viewC.state = .Disabled } } if KMAppearance.isDarkMode() { viewC.background = NSColor.clear viewC.background_hov = NSColor(red: 71/255, green: 72/255, blue: 75/255, alpha: 1) viewC.background_act = KMAppearance.Interactive.m1Color() } else { } } else if itemKey == .fontItalic { viewC.image = NSImage(named: "KMImageNameEditPDFFontItalic")! colorView.popToolTip = NSLocalizedString("Italic font", comment: "") viewC.initDefaultValue() viewC.borderWidth = 0 viewC.borderWidth_hov = 0 viewC.borderWidth_act = 0 DispatchQueue.main.async { if model.isEnabled { viewC.state = model.isSelected ? .Act : .Norm } else { viewC.state = .Disabled } } if KMAppearance.isDarkMode() { viewC.background = NSColor.clear viewC.background_hov = NSColor(red: 71/255, green: 72/255, blue: 75/255, alpha: 1) viewC.background_act = KMAppearance.Interactive.m1Color() } else { } } else if itemKey == .textAlignment { viewC.image = NSImage(named: "KMImageNameEditPDFAlignCenterSelect")! colorView.popToolTip = NSLocalizedString("Center text", comment: "") } // 图片 else if itemKey == .leftRotate { viewC.image = NSImage(named: "KMImageNameEditPDFLeftRotateNew")! colorView.popToolTip = NSLocalizedString("Rotate left", comment: "") } else if itemKey == .rightRotate { viewC.image = NSImage(named: "KMImageNameEditPDFRightRotateNew")! colorView.popToolTip = NSLocalizedString("Rotate right", comment: "") } else if itemKey == .reverseX { viewC.image = NSImage(named: "KMImageNameEditPDFReverseX")! colorView.popToolTip = NSLocalizedString("Flip horizontal", comment: "") } else if itemKey == .reverseY { viewC.image = NSImage(named: "KMImageNameEditPDFReverseY")! colorView.popToolTip = NSLocalizedString("Flip vertical", comment: "") } else if itemKey == .crop { viewC.image = NSImage(named: "KMImageNameEditPDFCrop")! viewC.image_disabled = NSImage(named: "KMImageNameEditPDFCropDisabled")! colorView.popToolTip = NSLocalizedString("Crop", comment: "") } else if itemKey == .replace { viewC.image = NSImage(named: "KMImageNameEditPDFReplace")! viewC.image_disabled = NSImage(named: "KMImageNameEditPDFReplaceDisabled")! colorView.popToolTip = NSLocalizedString("Replace", comment: "") } else if itemKey == .export { viewC.image = NSImage(named: "KMImageNameEditPDFExport")! colorView.popToolTip = NSLocalizedString("Export file", comment: "") } viewC.updateUI() return colorView } func toolbarView(_ toolbarView: KMEditPDFToolbarView, sizeForItemAt index: Int) -> NSSize { let itemKey = self.itemKeys[index] if itemKey == .color { return NSMakeSize(56, 32) } else if itemKey == .fontStyle { return NSMakeSize(148, 32) } else if itemKey == .separator { return NSMakeSize(16, 32) } return NSMakeSize(32, 32) } } extension KMEditPDFPopToolBarController: KMSelectPopButtonDelegate { func km_SelectPopoverWillShow(_ obj: KMDesignSelect) { self._trackEvent(key: .fontStyle) } func km_comboBoxSelectionDidChange(_ obj: KMDesignSelect) { self.itemClick?(.fontStyle, obj.stringValue) } func km_controlTextDidEndEditing(_ obj: KMDesignSelect) { } }