// // KMNPopAnnotationWindowController.swift // PDF Reader Pro // // Created by 丁林圭 on 2024/12/2. // import Cocoa import KMComponentLibrary let popOffSet:CGFloat = 8.0 typealias PaneCallback = (_ isOpenPane: Bool) -> () typealias UpdatePDFViewCallback = () -> () class KMNPopOperationWindow: NSWindow { override var canBecomeKey: Bool { return true } override var canBecomeMain: Bool { return true } } class KMNPopAnnotationWindowController: KMNBaseWindowController { public var popType:ListViewPopType = .popTypeNone { didSet { rightOffsetConstraint.constant = 49.0 operationHeightConstraint.constant = 32.0 paneBox.isHidden = false switch popType { case .generaAnnotation : generaPopView.layoutSubtreeIfNeeded() operationWidthConstraint.constant = generaPopView.bounds.width self.window?.display() //需刷新约束才会有值,不然会变化 operationBox.contentView = generaPopView congfigGeneraPopUI() break case .freeTextAnnotation: fontPopView.layoutSubtreeIfNeeded() operationWidthConstraint.constant = fontPopView.bounds.width self.window?.display() //需刷新约束才会有值,不然会变化 operationBox.contentView = fontPopView break case .shapeAnnotation: var isContainSelfDot = false for i in 0.. CGRectGetMaxY(listViewWindRect) { positionRect.origin.y = positionNew.y - popOffSet - position.height - positionRect.size.height } if CGRectGetMinX(positionRect) < CGRectGetMinX(listViewWindRect) { positionRect.origin.x = CGRectGetMinX(listViewWindRect) } if CGRectGetMaxX(positionRect) > CGRectGetMaxX(listViewWindRect) { positionRect.origin.x = CGRectGetMaxX(listViewWindRect) - positionRect.width } self.window?.setFrame(positionRect, display: true) updateUILanguage() } else { } } } public func closeWindow(listView:CPDFListView?) { if self.window?.isVisible == true { listView?.window?.removeChildWindow(self.window ?? NSWindow()) self.window?.close() } if ObjectPopover?.isShown == true { ObjectPopover?.close() } if fontPopover?.isShown == true { fontPopover?.close() } } //MARK: - private private func congfigGeneraPopUI() { var colors = KMAnnotationPropertiesColorManager.manager.markHighlightColors if(annotationPopMode.annotation is CPDFMarkupAnnotation) { let markupAnnotation:CPDFMarkupAnnotation = annotationPopMode.annotation as! CPDFMarkupAnnotation if markupAnnotation.markupType() == .highlight { colors = KMAnnotationPropertiesColorManager.manager.markHighlightColors } else { colors = KMAnnotationPropertiesColorManager.manager.markOtherColors } } else if (annotationPopMode.annotation is CPDFInkAnnotation) { colors = KMAnnotationPropertiesColorManager.manager.inkColors } else if (annotationPopMode.annotation is CPDFTextAnnotation) { colors = KMAnnotationPropertiesColorManager.manager.noteColors } let colorAProperty = ComponentCColorProperty(colorType: .color, state: .normal, isCustom: false, color: colors[0]) let colorBProperty = ComponentCColorProperty(colorType: .color, state: .normal, isCustom: false, color: colors[1]) let colorCProperty = ComponentCColorProperty(colorType: .color, state: .normal, isCustom: false, color: colors[2]) let colorDProperty = ComponentCColorProperty(colorType: .color, state: .normal, isCustom: false, color: colors[3]) let colorEProperty = ComponentCColorProperty(colorType: .color, state: .normal, isCustom: true, color: colors[4]) generaColorGroup.setUpWithColorPropertys([colorAProperty, colorBProperty, colorCProperty, colorDProperty], customItemProperty: colorEProperty) generaColorGroup.delegate = self } private func congfigFontPopUI() { fontNameSelect.properties = ComponentSelectProperties(size: .s, state: .normal, isDisabled: false, isError: false, leftIcon: false, placeholder: nil, errorText: nil, creatable: false, text: NSLocalizedString("", comment: "")) fontNameSelect.delegate = self var menuItemArr: [ComponentMenuitemProperty] = [] for familyName in CPDFFont.familyNames { let fontNames = CPDFFont.fontNames(forFamilyName: familyName) let itemProperty: ComponentMenuitemProperty = ComponentMenuitemProperty(multipleSelect: false, itemSelected: false, isDisabled: false, keyEquivalent: nil, text: familyName,identifier: "1") var subMenuItemArr: [ComponentMenuitemProperty] = [] for fontName in fontNames { let subItemProperty: ComponentMenuitemProperty = ComponentMenuitemProperty(multipleSelect: false, itemSelected: false, isDisabled: false, keyEquivalent: nil, text: fontName,identifier: "2") subMenuItemArr.append(subItemProperty) } itemProperty.subPropertys = subMenuItemArr menuItemArr.append(itemProperty) } fontNameSelect.updateMenuItemsArr(menuItemArr) fontColorItem.properties = ComponentCColorProperty(colorType: .color, state: .normal, isCustom: true, color: NSColor.red) fontColorItem.delegate = self fontColorItem.reloadData() } private func congfigShapePopUI() { var colors = KMAnnotationPropertiesColorManager.manager.inkColors let annotation = annotationPopMode.annotation ?? CPDFAnnotation() if annotation.isKind(of: CPDFInkAnnotation.self) { colors = KMAnnotationPropertiesColorManager.manager.inkColors } else if annotation.isKind(of: CPDFCircleAnnotation.self) || annotation.isKind(of: CPDFSquareAnnotation.self) { colors = KMAnnotationPropertiesColorManager.manager.inkColors } else if annotation.isKind(of: CPDFLineAnnotation.self) { if let lineAnnotation:CPDFLineAnnotation = annotation as? CPDFLineAnnotation { if lineAnnotation.isMeasure { colors = KMAnnotationPropertiesColorManager.manager.measure_Border_Colors } else { if lineAnnotation.endLineStyle == .none && lineAnnotation.startPoint == .none { colors = KMAnnotationPropertiesColorManager.manager.lineColors } else { colors = KMAnnotationPropertiesColorManager.manager.arrowColors } } } } else if (annotation.isKind(of: CPDFPolygonAnnotation.self) || annotation.isKind(of: CPDFPolylineAnnotation.self)) { colors = KMAnnotationPropertiesColorManager.manager.measure_Border_Colors } else if annotation.isKind(of: CPDFTextAnnotation.self) { colors = KMAnnotationPropertiesColorManager.manager.noteColors } else if annotation.isKind(of: CSelfSignAnnotation.self) { colors = KMAnnotationPropertiesColorManager.manager.fill_tickColors } let colorAProperty = ComponentCColorProperty(colorType: .color, state: .normal, isCustom: false, color: colors[0]) let colorBProperty = ComponentCColorProperty(colorType: .color, state: .normal, isCustom: false, color: colors[1]) let colorCProperty = ComponentCColorProperty(colorType: .color, state: .normal, isCustom: false, color: colors[2]) let colorDProperty = ComponentCColorProperty(colorType: .color, state: .normal, isCustom: false, color: colors[3]) let colorEProperty = ComponentCColorProperty(colorType: .color, state: .normal, isCustom: true, color: colors[4]) shapeColorGroup.setUpWithColorPropertys([colorAProperty, colorBProperty, colorCProperty, colorDProperty], customItemProperty: colorEProperty) shapeColorGroup.delegate = self } private func configFormPopUI() { let inputFiedProperty: ComponentInputProperty = ComponentInputProperty(size: .s, state:.pressed , isError: false, showPrefix: false, showSuffix: false, showClear: false, isDisabled: false, placeholder: "File Name", text: "") fildNameInput.properties = inputFiedProperty fildNameInput.delegate = self let inputGropProperty: ComponentInputProperty = ComponentInputProperty(size: .s, state:.pressed , isError: false, showPrefix: false, showSuffix: false, showClear: false, isDisabled: false, placeholder: "Grop Name", text: "") gropNameInput.properties = inputGropProperty gropNameInput.delegate = self } private func reloadPageUrlData() { if let destination = linkAnnotation.destination() { if let page = destination.page() { paginationView.properties.currentIndex = Int(page.pageIndex() + 1) paginationView.reloadData() return } } paginationView.properties.currentIndex = Int(((listView?.currentPageIndex ?? 0) + 1)) paginationView.reloadData() } private func refreshPageThum() { guard let pdfView = listView else { return } let thumbnail = KMNThumbnail.init(document: pdfView.document, currentPageIndex:Int(paginationView.properties.currentIndex - 1)) thumbnail.generateThumImage {[weak self] image in self?.pageThumImageView.image = image } } //MARK: - Action @objc func paneButtonClicked(_ sender: NSView) { isOpenPane = !isOpenPane paneCallback?(isOpenPane) } @objc func fontSizeZoomOutButtonClicked(_ sender: NSView) { if let freeText = annotationPopMode.annotation as? CPDFFreeTextAnnotation { if listView?.isEdit(withCurrentFreeText: freeText) == true { listView?.commitEditAnnotationFreeText(freeText) } if annotationPopMode.zoomOutFontSize() { updatePDFViewCallback?() } } } @objc func fontSizeZoomInButtonClicked(_ sender: NSView) { if let freeText = annotationPopMode.annotation as? CPDFFreeTextAnnotation { if listView?.isEdit(withCurrentFreeText: freeText) == true { listView?.commitEditAnnotationFreeText(freeText) } if annotationPopMode.zoomInFontSize() { updatePDFViewCallback?() } } } @objc func fontAlightButtonClicked(_ sender: NSView) { if fontPopover?.isShown == true { fontPopover?.close() } else { let vc = KMNPopDetailsViewController(nibName: "KMNPopDetailsViewController", 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) vc.detailPopType = .freeTextAlight vc.fontChangeAlighCallback = { [weak self] alight in if let freeText = self?.annotationPopMode.annotation as? CPDFFreeTextAnnotation { if self?.listView?.isEdit(withCurrentFreeText: freeText) == true { self?.listView?.commitEditAnnotationFreeText(freeText) } } self?.annotationPopMode.setAnnotationAlignment(annotationAlignment: alight) self?.updatePDFViewCallback?() self?.fontAlight = alight } fontPopover = createFilePopover vc.fontAlight = fontAlight } } @objc func widthZoomOutButtonClicked(_ sender: NSView) { if annotationPopMode.zoomOutShapeWidth() { updatePDFViewCallback?() } } @objc func widthZoomInButtonClicked(_ sender: NSView) { if annotationPopMode.zoomInShapeWidth() { updatePDFViewCallback?() } } @objc func pageLinkButtonClicked(_ sender: NSView) { linkType = .page } @objc func urlLinkButtonClicked(_ sender: NSView) { linkType = .url } @objc func emailLinkButtonClicked(_ sender: NSView) { linkType = .email } @objc func alightButtonClicked(_ sender: NSView) { if ObjectPopover?.isShown == true { ObjectPopover?.close() } else { let vc = KMNPopDetailsViewController(nibName: "KMNPopDetailsViewController", 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) if (annotationPopMode.annotations.count == 2) { vc.detailPopType = .freeTextDoubleAlight } else if (annotationPopMode.annotations.count > 2) { vc.detailPopType = .freeTextMultpleAlight } vc.objectChangeAlighCallback = { [weak self] alight in self?.objectAlignType = alight self?.updatePDFViewCallback?() } ObjectPopover = createFilePopover vc.objectAlignType = objectAlignType } } @objc func linkGoButtonClicked(_ sender: NSView) { if linkType == .url { guard let webString = linkAnnotation.url() else { return } if let data = URL(string: webString) { NSWorkspace.shared.open(data) } } else if linkType == .email { guard let emailString = linkAnnotation.url() else { let alert = NSAlert() alert.alertStyle = .critical alert.messageText = NSLocalizedString("Invalid Email. Please try again.", comment: "") alert.runModal() return } if KMNTools.isValidateEmail(emailString) { let alert = NSAlert() alert.alertStyle = .critical alert.messageText = NSLocalizedString("Invalid Email. Please try again.", comment: "") alert.runModal() } else { NSWorkspace.shared.open(URL(string: emailString)!) } } else if linkType == .page { if let destination = linkAnnotation.destination() { listView?.go(to: destination) closeWindow(listView: listView) } } } @objc func linkBackButtonClicked(_ sender: NSView) { linkType = .linkSetting } @objc func fontColorChange(_ sender: Any) { if let color = (sender as? NSColorPanel)?.color { if let freeText = annotationPopMode.annotation as? CPDFFreeTextAnnotation { if listView?.isEdit(withCurrentFreeText: freeText) == true { listView?.commitEditAnnotationFreeText(freeText) } } annotationPopMode.setAnnotationFontColor(annotationFontColor: color) fontColor = color updatePDFViewCallback?() } } } //MARK: - ComponentCColorDelegate extension KMNPopAnnotationWindowController: ComponentCColorDelegate { func componentCColorDidChooseColor(_ view: NSView, _ color: NSColor?) { if(view == generaColorGroup) { self.annotationPopMode.setAnnotationColor(annotationColor: color) updatePDFViewCallback?() } else if (view == shapeColorGroup) { self.annotationPopMode.setAnnotationColor(annotationColor: color) updatePDFViewCallback?() } else if (view == fontColorItem) { fontColorItem.properties?.state = .normal fontColorItem.reloadData() let colorPanel = NSColorPanel.shared colorPanel.setTarget(self) colorPanel.showsAlpha = false colorPanel.setAction(#selector(self.fontColorChange(_:))) colorPanel.orderFront(nil) } } } //MARK: - ComponentInputDelegate extension KMNPopAnnotationWindowController: ComponentInputDelegate { func componentInputDidChanged(inputView: ComponentInput) { if (inputView == fildNameInput) { for i in 0 ..< annotationPopMode.annotations.count { let annotation = annotationPopMode.annotations[i] if let widgt = annotation as? CPDFWidgetAnnotation { widgt.setFieldName(inputView.properties.text) } } updatePDFViewCallback?() } else if (inputView == gropNameInput) { for i in 0 ..< annotationPopMode.annotations.count { let annotation = annotationPopMode.annotations[i] if let button = annotation as? CPDFButtonWidgetAnnotation { button.setButtonWidgetStateString(inputView.properties.text) } } updatePDFViewCallback?() } else if (inputView == urlInput) { if linkType == .email { let emailString = urlInput.properties.text if KMNTools.isValidateEmail(emailString) { linkAnnotation.setDestination(nil) let linkUrlPath = KMNTools.judgeEmailURL(emailString) linkAnnotation.setURL(linkUrlPath) updatePDFViewCallback?() } } else if linkType == .url { let webString = urlInput.properties.text linkAnnotation.setDestination(nil) let linkUrlPath = KMNTools.judgeWebURL(webString) linkAnnotation.setURL(linkUrlPath) updatePDFViewCallback?() } } } func componentInputDidEndEditing(inputView: ComponentInput) { if (inputView == fildNameInput) { } else if (inputView == gropNameInput) { } else if (inputView == urlInput) { } } } //MARK: - ComponentSelectDelegate extension KMNPopAnnotationWindowController: ComponentSelectDelegate { func componentSelectDidSelect(view: ComponentSelect?, menuItemProperty: ComponentMenuitemProperty?) { if(view == fontNameSelect) { if menuItemProperty?.identifier == "1" { let familyName = fontNameSelect.properties.text ?? "Helvetica" let styles = CPDFFont.fontNames(forFamilyName: familyName) compdfFont = CPDFFont(familyName: familyName, fontStyle: styles.first ?? "") annotationPopMode.setAnnotationFont(font: compdfFont ?? CPDFFont.init(familyName: "Helvetica", fontStyle: "")) updatePDFViewCallback?() } else if menuItemProperty?.identifier == "2"{ //字体样式 compdfFont = CPDFFont(familyName: compdfFont?.familyName ?? "Helvetica", fontStyle: menuItemProperty?.text ?? "") annotationPopMode.setAnnotationFont(font: compdfFont ?? CPDFFont.init(familyName: "Helvetica", fontStyle: "")) updatePDFViewCallback?() } } } } //MARK: - ComponentSelectDelegate extension KMNPopAnnotationWindowController: NSPopoverDelegate { func popoverWillClose(_ notification: Notification) { if fontPopover == (notification.object as? NSPopover) { if(fontAlightButton.properties.state == .pressed) { fontAlightButton.properties.state = .normal fontAlightButton.reloadData() } } else if ObjectPopover == (notification.object as? NSPopover) { if(alightButton.properties.state == .pressed) { alightButton.properties.state = .normal alightButton.reloadData() } } } } //MARK: - ComponentPaginationDelegate extension KMNPopAnnotationWindowController: ComponentPaginationDelegate { public func componentPaginationDidValueChanged(pagination: ComponentPagination) { let pageIndex = pagination.properties.currentIndex if let page = listView?.document.page(at: UInt(pageIndex)) { linkAnnotation.setURL(nil) let destination = CPDFDestination(document: listView?.document ?? CPDFDocument(), pageIndex: Int(paginationView.properties.currentIndex - 1)) linkAnnotation.setDestination(destination) refreshPageThum() reloadPageUrlData() updatePDFViewCallback?() } } }