// // KMNoteReplyHanddler.swift // PDF Reader Pro // // Created by User-Tangchao on 2024/9/19. // import Cocoa // 注释回复处理类 func KMPDFAnnotationStateGetString(state: CPDFAnnotationState) -> String? { if state == .none { return KMLocalizedString("None") } else if state == .unMarked { return KMLocalizedString("Unmarked") } else if state == .marked { return KMLocalizedString("Marked") } else if state == .accepted { return KMLocalizedString("Accepted") } else if state == .rejected { return KMLocalizedString("Rejected") } else if state == .canceled { return KMLocalizedString("Cancelled") } else if state == .completed { return KMLocalizedString("Completed") } else if state == .error { return KMLocalizedString("Error") } return nil } func KMPDFAnnotationStateGetIcon(state: CPDFAnnotationState) -> String? { if state == .none { return "KMNImageNameBotaNoteStateNone" } else if state == .unMarked { return "KMNImageNameBotaNoteMarkup" } else if state == .marked { return "KMNImageNameBotaNoteMarkSelected" } else if state == .accepted { return "KMNImageNameBotaNoteStateAccepted" } else if state == .rejected { return "KMNImageNameBotaNoteStateRejected" } else if state == .canceled { return "KMNImageNameBotaNoteStateCancelled" } else if state == .completed { return "KMNImageNameBotaNoteStateCompleted" } else if state == .error { } return nil } class KMNoteReplyPopController: KMHomePopViewController { override func updateUI() { customBox.fillColor = background var widthMax: Float = 0; let subViews: [NSView] = self.customBox.contentView!.subviews for subView in subViews { subView.removeFromSuperview() } for string in self.dataArr ?? [] { if !(string as AnyObject).isEqual(to: KMHorizontalLine) { let width = self.cellContentAdaptiveWidth(string) if widthMax < width { widthMax = width } } } var formTopFloat: Float = 4.0 for string in dataArr?.reversed() ?? [] { if (string as AnyObject).isEqual(to: KMHorizontalLine) { self.createHonrizontalLineWithFrame(CGRect(x: 12.0, y: CGFloat(formTopFloat), width: CGFloat(widthMax)+23, height: 11)) formTopFloat += 11 } else { popCellViewDownString = string createPopViewCellLabelWithFrame(formTopFloat, stringValue: string) formTopFloat += 32; } } customBoxWidthLayoutConstraint.constant = CGFloat(widthMax+47+60) customBoxHeightLayoutConstraint.constant = CGFloat(formTopFloat+4.0) } override func createPopViewCellLabelWithFrame(_ mas_top: Float, stringValue: String) { var isDisabled = false if disItems.contains(stringValue) { isDisabled = true } var isSelected = false if (isDisabled == false && self.selectedItems.contains(stringValue)) { isSelected = true } let box: KMBox = KMBox(frame: NSZeroRect) box.boxType = .custom box.borderWidth = 0.0 box.contentViewMargins = NSMakeSize(0, 0) box.cornerRadius = 4.0 customBox.contentView?.addSubview(box) box.mas_makeConstraints { (make) in make?.leading.equalTo()(12.0) make?.trailing.equalTo()(-12) make?.height.equalTo()(32.0) make?.top.equalTo()(customBox.mas_top)?.offset()(CGFloat(mas_top)) } let iv = NSImageView() box.addSubview(iv) iv.mas_makeConstraints { make in make?.leading.equalTo()(4) make?.size.equalTo()(NSSize(width: 20, height: 20)) make?.centerY.equalTo()(0.0) } if stringValue == KMLocalizedString("Accepted") { iv.image = NSImage(named: "KMNImageNameBotaNoteStateAccepted") } else if stringValue == KMLocalizedString("Rejected") { iv.image = NSImage(named: "KMNImageNameBotaNoteStateRejected") } else if stringValue == KMLocalizedString("Cancelled") { iv.image = NSImage(named: "KMNImageNameBotaNoteStateCancelled") } else if stringValue == KMLocalizedString("Completed") { iv.image = NSImage(named: "KMNImageNameBotaNoteStateCompleted") } else if stringValue == KMLocalizedString("None") { iv.image = NSImage(named: "KMNImageNameBotaNoteStateNone") } let boxLabel: NSTextField = NSTextField.init() boxLabel.isEditable = false boxLabel.isBordered = false boxLabel.stringValue = stringValue boxLabel.font = NSFont.systemFont(ofSize: 14.0) boxLabel.translatesAutoresizingMaskIntoConstraints = false boxLabel.backgroundColor = NSColor.clear boxLabel.textColor = textColor//NSColor.km_init(hex: "#252629") box.contentView?.addSubview(boxLabel) boxLabel.mas_makeConstraints { (make) in make?.centerY.equalTo()(0.0) make?.leading.equalTo()(28.0) } box.moveCallback = {(mouseEntered: Bool, mouseBox: KMBox) -> Void in if !isDisabled { if isSelected { // 选中没有 hover 效果 return } if mouseEntered { mouseBox.fillColor = self.enterFillColor } else { mouseBox.fillColor = NSColor.clear } } } box.downCallback = {(downEntered, mouseBox, event) -> Void in if !isDisabled { if downEntered { if let callback = self.downCallback { callback(true, stringValue) } } else { } } } let idx = self.dataArr?.index(of: stringValue) ?? 0 self.viewWillShow?(box, idx) } } class KMNoteReplyHanddler: NSObject { weak var viewC: KMLeftSideViewController? private weak var popover_: NSPopover? func showStatePopView(sender: NSView, anno: CPDFAnnotation?) { if let _ = self.popover_ { return } let datas = [KMLocalizedString("Accepted"), KMLocalizedString("Rejected"), KMLocalizedString("Cancelled"), KMLocalizedString("Completed", comment: ""), KMLocalizedString("None")] let vc = KMNoteReplyPopController(nibName: "KMHomePopViewController", bundle: nil) _ = vc.initWithPopViewDataArr(datas) vc.background = KMAppearance.Layout.bgColor() vc.textColor = KMAppearance.Layout.h0Color() vc.enterFillColor = KMAppearance.Interactive.s0Color() let state = self.fetchReviewState(anno) ?? .none let stateStr = KMPDFAnnotationStateGetString(state: state) ?? KMLocalizedString("None") vc.selectedItems = [stateStr] vc.downCallback = { [weak self] result, data in self?.popover_?.close() if data == KMLocalizedString("Accepted") { self?.updateAnnoState(anno: anno, state: .accepted) } else if data == KMLocalizedString("Rejected") { self?.updateAnnoState(anno: anno, state: .rejected) } else if data == KMLocalizedString("Cancelled") { self?.updateAnnoState(anno: anno, state: .canceled) } else if data == KMLocalizedString("Completed") { self?.updateAnnoState(anno: anno, state: .completed) } else if data == KMLocalizedString("None") { self?.updateAnnoState(anno: anno, state: .none) } self?.viewC?.noteOutlineView.reloadData() } let popover = NSPopover() popover.contentViewController = vc popover.animates = true popover.behavior = .semitransient popover.setValue(true, forKey: "shouldHideAnchor") popover.delegate = self popover.show(relativeTo: sender.bounds, of: sender, preferredEdge: .maxY) self.popover_ = popover } func showReplyMorePopView(sender: NSView, replyModel: KMBotaAnnotationReplyModel?) { if let _ = self.popover_ { return } // let datas = [KMLocalizedString("Edit"), KMLocalizedString("Delete")] let vc = KMHomePopViewController(nibName: "KMHomePopViewController", bundle: nil) _ = vc.initWithPopViewDataArr(datas) vc.background = KMAppearance.Layout.bgColor() vc.textColor = KMAppearance.Layout.h0Color() vc.enterFillColor = KMAppearance.Interactive.s0Color() vc.downCallback = { [weak self] result, data in self?.popover_?.close() if data == KMLocalizedString("Edit") { self?.editReplyAnnotation(replyModel: replyModel) DispatchQueue.main.async { } } else if data == KMLocalizedString("Delete") { if let model = replyModel { self?.removeReplyAnnotation(model.replyAnno) model.annoModel?.replyAnnos.removeObject(model) } } } let popover = NSPopover() popover.contentViewController = vc popover.animates = true popover.behavior = .semitransient popover.setValue(true, forKey: "shouldHideAnchor") popover.delegate = self popover.show(relativeTo: sender.bounds, of: sender, preferredEdge: .maxY) self.popover_ = popover } func markAnnotation(_ anno: CPDFAnnotation?) { guard let replyA = self.fetchMarkAnnotation(anno) else { anno?.createReplyStateAnnotation(.marked) return } if replyA.getAnnotState() == .unMarked { replyA.setAnnotState(.marked) } } func unMarkAnnotation(_ anno: CPDFAnnotation?) { guard let replyA = self.fetchMarkAnnotation(anno) else { return } if replyA.getAnnotState() == .marked { replyA.setAnnotState(.unMarked) } } func updateAnnoState(anno: CPDFAnnotation?, state: CPDFAnnotationState) { guard let theAnno = self.fetchReviewAnnotation(anno) else { anno?.createReplyStateAnnotation(state) return } theAnno.setAnnotState(state) } func createReplyAnnotation(_ anno: CPDFAnnotation?, content: String?, userName: String?) -> CPDFAnnotation? { guard let theAnno = anno else { return nil } let a = theAnno.createReply() a?.contents = content ?? "" a?.setUserName(userName ?? "") a?.bounds = .zero return a } func editReplyAnnotation(replyModel: KMBotaAnnotationReplyModel?) { guard let model = replyModel else { return } model.annoModel?.footerModel?.isExpand = true model.annoModel?.footerModel?.replyModel = model } func editAnnotation(annotationModel: KMBotaAnnotationModel?) { guard let model = annotationModel else { return } model.footerModel?.isExpand = true model.footerModel?.editAnnoModel = annotationModel } func removeReplyAnnotation(_ anno: CPDFAnnotation?) { guard let theAnno = anno else { return } theAnno.page.removeAnnotation(theAnno) } func fetchReviewState(_ anno: CPDFAnnotation?) -> CPDFAnnotationState? { guard let theAnno = self.fetchReviewAnnotation(anno) else { return nil } return theAnno.getAnnotState() } func fetchAnnoState(_ anno: CPDFAnnotation?) -> CPDFAnnotationState? { guard let replyA = self.fetchMarkAnnotation(anno) else { return nil } return replyA.getAnnotState() } func fetchReplyAnnotations(_ anno: CPDFAnnotation?) -> [CPDFAnnotation]? { guard let theAnno = anno else { return nil } var annos: [CPDFAnnotation] = [] for a in theAnno.replyAnnotations ?? [] { if a.replyAnnotationType == .reply { annos.append(a) } } return annos } func fetchReviewAnnotation(_ anno: CPDFAnnotation?) -> CPDFAnnotation? { guard let theAnno = anno else { return nil } for a in theAnno.replyAnnotations ?? [] { if a.replyAnnotationType == .review { return a } } return nil } func fetchMarkAnnotation(_ anno: CPDFAnnotation?) -> CPDFAnnotation? { guard let theAnno = anno else { return nil } for replyA in theAnno.replyAnnotations ?? [] { if replyA.replyAnnotationType == .mark { return replyA } } return nil } } extension KMNoteReplyHanddler: NSPopoverDelegate { func popoverWillClose(_ notification: Notification) { if let data = self.popover_?.isEqual(to: notification.object), data { self.popover_ = nil } } }