// // KMEditImageController.swift // PDF Reader Pro // // Created by Niehaoyu on 2024/11/14. // import Cocoa import KMComponentLibrary class KMEditImageController: NSViewController { @IBOutlet var scrollView: NSScrollView! @IBOutlet var contendView: NSView! @IBOutlet var sizeBGView: NSView! @IBOutlet var sizeLabel: NSTextField! @IBOutlet var sizeSyncButton: ComponentButton! @IBOutlet var sizeWidthInput: ComponentInputNumber! @IBOutlet var sizeHeightInput: ComponentInputNumber! @IBOutlet var rotateBGView: NSView! @IBOutlet var rotateLabel: NSTextField! @IBOutlet var rotateSelect: ComponentSelect! @IBOutlet var rotateLeftButton: ComponentButton! @IBOutlet var rotateRightButton: ComponentButton! @IBOutlet var flipVerticalButton: ComponentButton! @IBOutlet var flipHorizontalButton: ComponentButton! @IBOutlet var opacityBGView: NSView! @IBOutlet var opacityLabel: NSTextField! @IBOutlet var opacitySlider: ComponentSlider! @IBOutlet var opacitySelect: ComponentSelect! @IBOutlet var cropButton: ComponentButton! @IBOutlet var replaceButton: ComponentButton! @IBOutlet var extractButton: ComponentButton! @IBOutlet var extractBtnTopConst: NSLayoutConstraint! @IBOutlet var alignmentBGView: NSView! private var syncChangeBounds: Bool = true //同步修改宽高 private var groupView: ComponentGroup! private var alignmentController: KMNAlignmentController? var pdfView: CPDFListView? { didSet { reloadData() } } var areas: [CPDFEditImageArea] = [] //MARK: - func override func viewDidAppear() { super.viewDidAppear() opacitySlider.reloadData() } override func viewDidLoad() { super.viewDidLoad() setupProperty() } func setupProperty() { //Size sizeLabel.stringValue = KMLocalizedString("Size") sizeLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/2") sizeLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-s-medium") sizeSyncButton.properties = ComponentButtonProperty(type: .text_gray, size: .xxs, onlyIcon: true, icon: NSImage(named: "sync_Change_unlock"), keepPressState: true) sizeSyncButton.properties.propertyInfo.leftIcon_press = NSImage(named: "sync_Change_lock") sizeSyncButton.setTarget(self, action: #selector(sizeSyncButtonClicked(_:))) sizeSyncButton.reloadData() sizeWidthInput.properties = ComponentInputNumberProperty(alignment: .center, size: .s, state: .normal, showPrefix: true, leftIcon: NSImage(named: "w_icon"), showSuffix: false, minSize: 1, maxSize: 1000, text:"100") sizeWidthInput.delegate = self sizeHeightInput.properties = ComponentInputNumberProperty(alignment: .center, size: .s, state: .normal, showPrefix: true, leftIcon: NSImage(named: "h_icon"), showSuffix: false, minSize: 1, maxSize: 1000, text:"100") sizeHeightInput.delegate = self //Rotate rotateLabel.stringValue = KMLocalizedString("Rotate & Flip") rotateLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/2") rotateLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-s-medium") rotateSelect.properties = ComponentSelectProperties(size: .s, state: .normal, creatable: true, text: "100", textUnit: "°", regexString: "0123456789-") if true { var opacityItems: [ComponentMenuitemProperty] = [] for string in ["0°", "45°", "-45°", "90°", "-90°"] { let item = ComponentMenuitemProperty(type: .normal, text: string) opacityItems.append(item) } rotateSelect.updateMenuItemsArr(opacityItems) } rotateSelect.delegate = self rotateLeftButton.properties = ComponentButtonProperty(type: .text_gray, size: .s, onlyIcon: true, icon: NSImage(named: "pageEdit_rotateLeft"), keepPressState: false) rotateLeftButton.setTarget(self, action: #selector(buttonClicked(_:))) rotateRightButton.properties = ComponentButtonProperty(type: .text_gray, size: .s, onlyIcon: true, icon: NSImage(named: "pageEdit_rotateRight"), keepPressState: false) rotateRightButton.setTarget(self, action: #selector(buttonClicked(_:))) flipVerticalButton.properties = ComponentButtonProperty(type: .text_gray, size: .s, onlyIcon: true, icon: NSImage(named: "flipVertical"), keepPressState: false) flipVerticalButton.setTarget(self, action: #selector(buttonClicked(_:))) flipHorizontalButton.properties = ComponentButtonProperty(type: .text_gray, size: .s, onlyIcon: true, icon: NSImage(named: "flipHorizontal"), keepPressState: false) flipHorizontalButton.setTarget(self, action: #selector(buttonClicked(_:))) //Opacity opacityLabel.stringValue = KMLocalizedString("Opacity") opacityLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/2") opacityLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-s-medium") opacitySlider.properties = ComponentSliderProperty(size: .m, percent: 1) opacitySlider.delegate = self opacitySelect.properties = ComponentSelectProperties(size: .s, state: .normal, creatable: true, text: "100", textUnit: "%", regexString: "0123456789%") if true { var opacityItems: [ComponentMenuitemProperty] = [] for string in ["25%", "50%", "75%", "100%"] { let item = ComponentMenuitemProperty(type: .normal, text: string) opacityItems.append(item) } opacitySelect.updateMenuItemsArr(opacityItems) } opacitySelect.delegate = self cropButton.properties = ComponentButtonProperty(type: .default_tertiary, size: .s, buttonText: KMLocalizedString("Crop"), keepPressState: false) cropButton.setTarget(self, action: #selector(buttonClicked(_:))) replaceButton.properties = ComponentButtonProperty(type: .default_tertiary, size: .s, buttonText: KMLocalizedString("Replace"), keepPressState: false) replaceButton.setTarget(self, action: #selector(buttonClicked(_:))) extractButton.properties = ComponentButtonProperty(type: .default_tertiary, size: .s, buttonText: KMLocalizedString("Extract"), keepPressState: false) extractButton.setTarget(self, action: #selector(buttonClicked(_:))) if alignmentController == nil { alignmentController = KMNAlignmentController.init() } alignmentController?.view.frame = alignmentBGView.bounds alignmentController?.view.autoresizingMask = [.width, .height] alignmentController?.delegate = self alignmentBGView.addSubview(alignmentController!.view) } func reloadData() { areas = pdfView?.km_editingImageAreas() ?? [] if areas.count == 0 { } else if areas.count == 1 { cropButton.isHidden = false replaceButton.isHidden = false extractBtnTopConst.constant = 112 alignmentBGView.isHidden = true } else if areas.count > 1 { cropButton.isHidden = true replaceButton.isHidden = true extractBtnTopConst.constant = 16 alignmentBGView.isHidden = false if areas.count == 2 { alignmentController?.updateMulti(false) } else { alignmentController?.updateMulti(true) } } sizeWidthInput.properties.isDisabled = pdfView?.isEditImage == true sizeHeightInput.properties.isDisabled = pdfView?.isEditImage == true sizeSyncButton.properties.isDisabled = pdfView?.isEditImage == true rotateSelect.properties.isDisabled = pdfView?.isEditImage == true rotateLeftButton.properties.isDisabled = pdfView?.isEditImage == true rotateLeftButton.reloadData() rotateRightButton.properties.isDisabled = pdfView?.isEditImage == true rotateRightButton.reloadData() flipVerticalButton.properties.isDisabled = pdfView?.isEditImage == true flipVerticalButton.reloadData() flipHorizontalButton.properties.isDisabled = pdfView?.isEditImage == true flipHorizontalButton.reloadData() opacitySlider.properties.isDisabled = pdfView?.isEditImage == true opacitySlider.reloadData() opacitySelect.properties.isDisabled = pdfView?.isEditImage == true replaceButton.properties.isDisabled = pdfView?.isEditImage == true replaceButton.reloadData() extractButton.properties.isDisabled = pdfView?.isEditImage == true extractButton.reloadData() if let area = areas.first { //Size if syncChangeBounds { sizeSyncButton.properties.state = .pressed } else { sizeSyncButton.properties.state = .normal } sizeSyncButton.reloadData() if areas.count > 1 { sizeWidthInput.properties.text = "-" sizeWidthInput.properties.multiState = true sizeHeightInput.properties.text = "-" sizeHeightInput.properties.multiState = true } else { let areaFrame = area.bounds sizeWidthInput.properties.text = String(format: "%.1f", areaFrame.size.width) sizeWidthInput.properties.multiState = false sizeHeightInput.properties.text = String(format: "%.1f", areaFrame.size.height) sizeHeightInput.properties.multiState = false } sizeWidthInput.properties.maxSize = Int(area.page.bounds.size.width) sizeHeightInput.properties.maxSize = Int(area.page.bounds.size.height) sizeWidthInput.reloadData() sizeHeightInput.reloadData() //Rotate if let rotates = pdfView?.km_editAreasRotates([area]) { if rotates.count > 0 { let rotate = rotates.first ?? 1 rotateSelect.properties.text = String(format: "%.0f%@", rotate, "°") rotateSelect.reloadData() } } //Opacity if let opacitys = pdfView?.km_editAreasOpacitys([area]) { if opacitys.count > 0 { var opacity = opacitys.first ?? 1 opacity = min(1, opacity) opacity = max(0, opacity) opacitySelect.properties.text = String(format: "%.0f%@", opacity*100, "%") opacitySelect.reloadData() opacitySlider.properties.percent = opacity opacitySlider.reloadData() } } if pdfView?.isEditImage == true { cropButton.properties.buttonText = KMLocalizedString("Cancel Crop") } else { cropButton.properties.buttonText = KMLocalizedString("Crop") } cropButton.reloadData() } } //MARK: - Action @objc func sizeSyncButtonClicked(_ sender: ComponentButton) { syncChangeBounds = !syncChangeBounds if syncChangeBounds { sizeSyncButton.properties.state = .pressed } else { sizeSyncButton.properties.state = .normal } sizeSyncButton.reloadData() } @objc func buttonClicked(_ sender: ComponentButton) { if sender == rotateLeftButton { self.pdfView?.leftRotateAction() } else if sender == rotateRightButton { self.pdfView?.rightRotateAction() } else if sender == flipVerticalButton { self.pdfView?.reverseYAction() } else if sender == flipHorizontalButton { self.pdfView?.reverseXAction() } else if sender == cropButton { if pdfView?.isEditImage == true { pdfView?.cropCancelAction() } else { pdfView?.cropAction() } } else if sender == replaceButton { if let areas = self.pdfView?.km_editingImageAreas() { let panel = NSOpenPanel() panel.allowsMultipleSelection = false panel.allowedFileTypes = ["png","jpg"] panel.beginSheetModal(for: NSApp.mainWindow!) { response in if response == .OK { let openPath = panel.url?.path for area in areas { self.pdfView?.replace(area, imagePath: openPath!) } } } } } else if sender == extractButton { extractAction() } reloadData() } func extractAction() { var menuItemArr: [ComponentMenuitemProperty] = [] let items: [(String, String)] = [("jpg", "export_jpg_Key"), ("png", "export_png_Key"), ("pdf", "export_pdf_Key")] for (i, value) in items { let properties_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(multipleSelect: false, itemSelected: false, keyEquivalent: nil, text: KMLocalizedString(i), identifier: value) menuItemArr.append(properties_Menuitem) } if groupView == nil { groupView = ComponentGroup.createFromNib(in: ComponentLibrary.shared.componentBundle()) } groupView.groupDelegate = self groupView?.frame = CGRectMake(0, 0, extractButton.frame.size.width, 36*3+8) groupView.updateGroupInfo(menuItemArr) if let point: CGPoint = extractButton.superview?.convert(extractButton.frame.origin, to: self.view.window?.contentView) { groupView.showWithPoint(CGPoint(x: point.x, y: point.y - CGRectGetHeight(groupView.frame)-4), relativeTo: self.view) } } //MARK: - MouseEvent override func mouseDown(with event: NSEvent) { super.mouseDown(with: event) view.window?.makeFirstResponder(nil) } } //MARK: - ComponentInputNumberDelegate extension KMEditImageController: ComponentInputNumberDelegate { func componentInputNumberDidValueChanged(inputNumber: ComponentInputNumber?) { if areas.count > 1 || areas.count == 0 { return } var textValue = inputNumber?.properties.text?.stringToCGFloat() ?? 0 if inputNumber == sizeWidthInput { let areas = self.pdfView?.km_editingImageAreas() ?? [] for area in areas { pdfView?.updateArea(area, newWidth: textValue, syncChangeBounds) } } else if inputNumber == sizeHeightInput { let areas = self.pdfView?.km_editingImageAreas() ?? [] for area in areas { pdfView?.updateArea(area, newHeight: textValue, syncChangeBounds) } } reloadData() } func componentInputNumberDidIncrease(inputNumber: ComponentInputNumber?) { if areas.count == 1 || areas.count == 0 { return } //只处理多选状态 var textValue = inputNumber?.properties.text?.stringToCGFloat() ?? 0 if inputNumber == sizeWidthInput { let areas = self.pdfView?.km_editingImageAreas() ?? [] for area in areas { textValue = area.bounds.size.width + 1 pdfView?.updateArea(area, newWidth: textValue, syncChangeBounds) } } else if inputNumber == sizeHeightInput { let areas = self.pdfView?.km_editingImageAreas() ?? [] for area in areas { textValue = area.bounds.size.height + 1 pdfView?.updateArea(area, newHeight: textValue, syncChangeBounds) } } reloadData() } func componentInputNumberDidDecrease(inputNumber: ComponentInputNumber?) { if areas.count == 1 || areas.count == 0 { return } //只处理多选状态 var textValue = inputNumber?.properties.text?.stringToCGFloat() ?? 0 if inputNumber == sizeWidthInput { let areas = self.pdfView?.km_editingImageAreas() ?? [] for area in areas { textValue = area.bounds.size.width - 1 pdfView?.updateArea(area, newWidth: textValue, syncChangeBounds) } } else if inputNumber == sizeHeightInput { let areas = self.pdfView?.km_editingImageAreas() ?? [] for area in areas { textValue = area.bounds.size.height - 1 pdfView?.updateArea(area, newHeight: textValue, syncChangeBounds) } } reloadData() } } //MARK: - ComponentSelectDelegate extension KMEditImageController: ComponentSelectDelegate { func componentSelectDidSelect(view: ComponentSelect?, menuItemProperty: ComponentMenuitemProperty?) { if view == opacitySelect { if let text = opacitySelect.properties.text { let result = text.stringByDeleteCharString("%") let opacity = result.stringToCGFloat()/100 pdfView?.setEditingAreasOpacity(opacity) reloadData() } } else if view == rotateSelect { if let text = rotateSelect.properties.text { let result = text.stringByDeleteCharString("°") let value = result.stringToCGFloat() pdfView?.rotateEditingAreas(value) reloadData() } } } func componentSelectTextDidEndEditing(_ view: ComponentSelect) { if view == opacitySelect { if let text = opacitySelect.properties.text { let result = text.stringByDeleteCharString("%") let opacity = result.stringToCGFloat()/100 pdfView?.setEditingAreasOpacity(opacity) reloadData() } } else if view == rotateSelect { if let text = rotateSelect.properties.text { let result = text.stringByDeleteCharString("°") let value = result.stringToCGFloat() pdfView?.rotateEditingAreas(value) reloadData() } } } } //MARK: - ComponentCColorDelegate extension KMEditImageController: ComponentCColorDelegate { } //MARK: - ComponentSliderDelegate extension KMEditImageController: ComponentSliderDelegate { func componentSliderDidUpdate(_ view: ComponentSlider) { let percent = view.properties.percent self.pdfView?.setEditingAreasOpacity(percent) reloadData() } } //MARK: - ComponentGroupDelegate extension KMEditImageController: ComponentGroupDelegate { func componentGroupDidSelect(group: ComponentGroup?, menuItemProperty: ComponentMenuitemProperty?) { if menuItemProperty?.identifier == "export_jpg_Key" { pdfView?.exportEditingImageAreasAction(format: "jpg") } else if menuItemProperty?.identifier == "export_png_Key" { pdfView?.exportEditingImageAreasAction(format: "png") } else if menuItemProperty?.identifier == "export_pdf_Key" { pdfView?.exportEditingImageAreasAction(format: "pdf") } } } //MARK: - KMNAlignmentControllerDelegate extension KMEditImageController: KMNAlignmentControllerDelegate { func alignmentControllerDidClick(_ controller: KMNAlignmentController, _ alignmentType: KMPDFActiveFormsAlignType) { pdfView?.changeEditingAreas(alignmentType) } }