// // KMLeftSideViewController+Note.swift // PDF Reader Pro // // Created by tangchao on 2023/12/23. // import Foundation extension KMLeftSideViewController.Key { static let noteAscendSortKey = "KMLeftSideViewAscendSortBoolKey" static let noteSortTypeKey = "KMLeftSideViewNoteSortTypeKey" static let noteTableColumn = "KMNoteOutlineViewTableColumnKey" static let noteFilterPage = "kKMNoteFilterAnnotationPageKey" static let noteFilterTime = "kKMNoteFilterAnnotationTimeKey" static let noteFilterAuther = "kKMNoteFilterAnnotationAutherKey" } // MARK: - Action extension KMLeftSideViewController { func note_initSubViews() { noteOutlineView.delegate = self noteOutlineView.dataSource = self noteOutlineView.botaDelegate = self noteOutlineView.botaDataSource = self noteOutlineView.noteDelegate = self noteOutlineView.menu = NSMenu() noteOutlineView.menu?.delegate = self noteOutlineView.typeSelectHelper = SKTypeSelectHelper(matchOption: .SKSubstringMatch) noteOutlineView.enclosingScrollView?.scrollerStyle = .legacy noteOutlineView.enclosingScrollView?.autohidesScrollers = true noteOutlineView.registerForDraggedTypes(NSColor.readableTypes(for: NSPasteboard(name: .drag))) noteOutlineView.target = self noteOutlineView.doubleAction = #selector(selectSelectedNote) } func note_initDefalutValue() { let sud = UserDefaults.standard if let dict = sud.dictionary(forKey: Self.Key.noteTableColumn) { self.noteTypeDict = dict } else { self.noteTypeDict = [Self.Key.noteFilterPage : false, Self.Key.noteFilterTime : false, Self.Key.noteFilterAuther : false] sud.sync_setValue(self.noteTypeDict, forKey: Self.Key.noteTableColumn) } self.caseInsensitiveNoteSearch = sud.bool(forKey: SKCaseInsensitiveNoteSearchKey) self.isAscendSort = KMDataManager.ud_bool(forKey: Self.Key.noteAscendSortKey) var sortType = 0 if let data = self.listView?.document?.documentURL.path { sortType = KMDataManager.udExtension_object(forKey: Self.Key.noteSortTypeKey + data) as? Int ?? 0 } if (sortType == 1) { noteSortType = KMNoteSortType(rawValue: sortType) ?? .none } else { noteSortType = .page } noteOutlineView.autoresizesOutlineColumn = false noteOutlineView.indentationPerLevel = 0 } func annoListIsShowPage() -> Bool { return !(self.noteTypeDict[Self.Key.noteFilterPage] as? Bool ?? false) } func annoListIsShowTime() -> Bool { return !(self.noteTypeDict[Self.Key.noteFilterTime] as? Bool ?? false) } func annoListIsShowAnther() -> Bool { return !(self.noteTypeDict[Self.Key.noteFilterAuther] as? Bool ?? false) } } // MARK: - Menu extension KMLeftSideViewController { func annoListMenu(_ menu: NSMenu) { var item: NSMenuItem? var items: NSArray? var rowIndexes = self.noteOutlineView.selectedRowIndexes let row = self.noteOutlineView.clickedRow if row == -1 { _ = self._addDeleteAllAnnoItem(menu) return } if rowIndexes.contains(row) == false { rowIndexes = IndexSet(integer: row) } items = self.noteOutlineView.itemsAtRowIndexes(rowIndexes) as NSArray guard let model = self.fetchAnnoModel(for: row) else { return } let isFold = model.isFold() item = menu.addItem(title: KMLocalizedString("Expand"), action: #selector(unfoldNoteAction), target: self) item?.state = isFold ? .off : .on item?.representedObject = items item = menu.addItem(title: KMLocalizedString("Collapse"), action: #selector(foldNoteAction), target: self) item?.state = isFold ? .on : .off item?.representedObject = items menu.addItem(.separator()) let hideNotes = self.hideNotes() if hideNotes == false && (items?.count ?? 0) == 1 { } if menu.numberOfItems > 0 { if self.outlineView(self.noteOutlineView, canDeleteItems: items as? [Any] ?? []) { item = menu.addItem(title: KMLocalizedString("Delete", comment: "Menu item title"), action: #selector(deleteNotes), target: self) item?.representedObject = items } _ = self._addDeleteAllAnnoItem(menu) } } private func _addExportPDFMenu(_ menu: NSMenu) -> NSMenu { var item = menu.addItem(title: KMLocalizedString("Export Annotations…"), action: nil, target: self) let subMenu = NSMenu() item?.submenu = subMenu item = subMenu.addItem(title: KMLocalizedString("PDF", comment: ""), action: #selector(exportAnnotationNotes), target: self) item?.tag = 0 item = subMenu.addItem(title: KMLocalizedString("PDF Bundle"), action: #selector(exportAnnotationNotes), target: self) item?.tag = 1 item = subMenu.addItem(title: KMLocalizedString("PDF Reader Pro Edition Notes"), action: #selector(exportAnnotationNotes), target: self) item?.tag = 2 item = subMenu.addItem(title: KMLocalizedString("Notes as Text"), action: #selector(exportAnnotationNotes), target: self) item?.tag = 3 item = subMenu.addItem(title: KMLocalizedString("Notes as RTF"), action: #selector(exportAnnotationNotes), target: self) item?.tag = 4 item = subMenu.addItem(title: KMLocalizedString("Notes as RTFD"), action: #selector(exportAnnotationNotes), target: self) item?.tag = 5 item = subMenu.addItem(title: KMLocalizedString("Notes as FDF"), action: #selector(exportAnnotationNotes), target: self) item?.tag = 6 return menu } private func _addDeleteAllAnnoItem(_ menu: NSMenu) -> NSMenuItem? { return menu.addItem(title: KMLocalizedString("Remove All Annotations"), action: #selector(removeAllAnnotations), target: self) } private func _addDeleteAllReplyAnnoItem(_ menu: NSMenu) -> NSMenuItem? { return menu.addItem(title: KMLocalizedString("Delete All Reply"), action: #selector(removeAllReplyAnnotations), target: self) } @IBAction func note_expandAllComments(_ sender: AnyObject?) { guard let model = self.annoListModel else { return } for secM in self.annoListModel?.datas ?? [] { secM.isExpand = true } noteOutlineView.reloadData() } @IBAction func note_foldAllComments(_ sender: AnyObject?) { guard let model = self.annoListModel else { return } for secM in self.annoListModel?.datas ?? [] { secM.isExpand = false } self.noteOutlineView.reloadData() } @IBAction func noteShowNoteAction(_ sender: AnyObject?) { let item = sender as? NSMenuItem let tag = item?.tag ?? 0 if (tag == 100) { } else if (tag == 101) { let isPage = !self.annoListIsShowPage() self.noteTypeDict[Self.Key.noteFilterPage] = !isPage } else if (tag == 102) { let isTime = !self.annoListIsShowTime() self.noteTypeDict[Self.Key.noteFilterTime] = !isTime } else if (tag == 103) { let isAuther = !self.annoListIsShowAnther() self.noteTypeDict[Self.Key.noteFilterAuther] = !isAuther } UserDefaults.standard.sync_setValue(self.noteTypeDict, forKey: Self.Key.noteTableColumn) // 更新数据 var models: [KMBotaAnnotationModel] = [] if self.noteSearchMode { for model in self.noteSearchArray { guard let data = model as? KMBotaAnnotationModel else { continue } models.append(data) } } else { let selModels = self.annoListModel?.datas ?? [] for selModel in selModels { for item in selModel.items { if let data = item as? KMBotaAnnotationModel { models.append(data) } } } } for model in models { model.showPage = self.annoListIsShowPage() model.showTime = self.annoListIsShowTime() model.showAuthor = self.annoListIsShowAnther() } let selectRow = self.noteOutlineView.selectedRow self.noteOutlineView.reloadData() self.noteOutlineView.selectRowIndexes(IndexSet(integer: selectRow), byExtendingSelection: false) } @objc func importNotes(_ sender: NSMenuItem?) { let panel = NSOpenPanel() panel.allowedFileTypes = ["xfdf"] panel.allowsMultipleSelection = false panel.beginSheetModal(for: self.view.window!) { resp in if resp != .OK { return } if let result = self.pdfDocument()?.importAnnotation(fromXFDFPath: panel.url?.path), result { self.reloadAnnotation() self.listView?.setNeedsDisplayForVisiblePages() } } } @objc func exportNotes(_ sender: NSMenuItem?) { guard let cnt = self.listView?.notes?.count, cnt > 0 else { NSSound.beep() return } let fileName = "\(self.pdfDocument()?.documentURL.deletingPathExtension().lastPathComponent ?? "")" + "_xfdf" let panel = NSSavePanel() panel.directoryURL = self.pdfDocument()?.documentURL.deletingLastPathComponent() panel.allowedFileTypes = ["xfdf"] panel.nameFieldStringValue = fileName panel.beginSheetModal(for: self.view.window!) { resp in if resp != .OK { return } let filePath = panel.url?.path if let success = self.pdfDocument()?.exportAnnotation(toXFDFPath: filePath), success { NSWorkspace.shared.selectFile(filePath, inFileViewerRootedAtPath: "") } else { Task { _ = await KMAlertTool.runModel(message: KMLocalizedString("Export Failure!"), buttons: [KMLocalizedString("OK")]) } } } } @objc func exportAnnotationNotes(_ sender: AnyObject?) { let doc = self.view.window?.windowController?.document as? NSDocument doc?.saveTo(sender) } // 展开 @objc func unfoldNoteAction(_ sender: NSMenuItem) { if sender.state == .on { return } let row = self.noteOutlineView.clickedRow guard let model = self.fetchAnnoModel(for: row) else { return } model.foldType = .unfold model.isExpand = true let viewS = self.noteOutlineView.view(atColumn: 0, row: row, makeIfNecessary: true) (viewS as? KMNoteTableViewCell)?.isFold = false model.footerModel?.isExpand = true // self.noteOutlineView.reloadItem(model.footerModel) self.noteOutlineView.reloadData() } @objc func foldNoteAction(_ sender: NSMenuItem) { if sender.state == .on { return } let row = self.noteOutlineView.clickedRow guard let model = self.fetchAnnoModel(for: row) else { return } model.foldType = .fold model.isExpand = false let viewS = self.noteOutlineView.view(atColumn: 0, row: row, makeIfNecessary: true) (viewS as? KMNoteTableViewCell)?.isFold = true model.footerModel?.isExpand = false // self.noteOutlineView.reloadItem(model.footerModel) self.noteOutlineView.reloadData() } @objc func deleteNotes(_ sender: NSMenuItem) { self.outlineView(self.noteOutlineView, deleteItems: sender.representedObject as? [Any] ?? []) } @objc func removeAllAnnotations(_ sender: AnyObject?) { guard let doc = self.pdfDocument() else { return } Task { let response = await KMAlertTool.runModel(message: KMLocalizedString("This will permanently remove all annotations. Are you sure to continue?"), buttons: [KMLocalizedString("Yes"), KMLocalizedString("No")]) if response == .alertFirstButtonReturn { self.dataUpdating = true for model in self.annoListModel?.datas ?? [] { for item in model.items { if let anno = item.anno { if let data = anno as? CPDFFreeTextAnnotation { self.listView?.commitEditAnnotationFreeText(data) } self.listView?.remove(anno) } } } self.annoListModel?.datas.removeAll() self.dataUpdating = false self.note_refrshUIIfNeed() } } } @objc func removeAllReplyAnnotations(_ sender: NSMenuItem?) { Task { let response = await KMAlertTool.runModel(message: KMLocalizedString("Are you sure to delete all comment replies?"), buttons: [KMLocalizedString("Yes"), KMLocalizedString("No")]) if response == .alertFirstButtonReturn { self.dataUpdating = true var hasAnno = false for model in self.annoListModel?.datas ?? [] { for item in model.items { guard let annoM = item as? KMBotaAnnotationModel else { continue } for replyM in annoM.replyAnnos { replyM.replyAnno?.page.removeAnnotation(replyM.replyAnno) hasAnno = true } annoM.replyAnnos.removeAll() } } self.dataUpdating = false self.note_refrshUIIfNeed() if let data = self.view.window, hasAnno { KMTools.setDocumentEditedState(window: data) } } } } @objc func editNoteTextFromTable(_ sender: NSMenuItem) { guard let annotation = sender.representedObject as? CPDFAnnotation else { return } self.listView?.scrollAnnotationToVisible(annotation) } @objc func editThisAnnotation(_ sender: AnyObject?) { guard let annotation = (sender as? NSMenuItem)?.representedObject as? CPDFAnnotation else { NSSound.beep() return } self.listView?.edit(annotation) } @objc func editNoteFromTable(_ sender: AnyObject?) { guard let annotation = (sender as? NSMenuItem)?.representedObject as? CPDFAnnotation else { NSSound.beep() return } let model = fetchAnnoModel(for: annotation) let row = self.noteOutlineView.row(forItem: model) self.noteOutlineView.km_safe_selectRowIndexes(.init(integer: row), byExtendingSelection: false) let noteIndex = self.noteOutlineView.column(withIdentifier: .init("note")) if (noteIndex >= 0) { self.noteOutlineView.scrollColumnToVisible(noteIndex) // self.isRenameNoteOutline = true let viewS = self.noteOutlineView.view(atColumn: 0, row: row, makeIfNecessary: true) as? KMNoteTableViewCell viewS!.isFold = false let targrtTextField = viewS?.noteContentLabel self.editNoteTextField = targrtTextField self.editNote = annotation targrtTextField?.delegate = self targrtTextField?.isEditable = true targrtTextField?.becomeFirstResponder() } } @IBAction func noteSortAction(_ sender: AnyObject?) { // if (self.isAscendSort) { // self.isAscendSort = false // self.noteSortButton.image = NSImage(named: KMImageNameBtnSidebarRankPositive) // self.noteSortButton.toolTip = KMLocalizedString("descending sort", nil) // } else { // self.isAscendSort = true // self.noteSortButton.image = NSImage(named: KMImageNameBtnSidebarRankReverse) // self.noteSortButton.toolTip = KMLocalizedString("ascending sort", nil) // } KMDataManager.ud_set(self.isAscendSort, forKey: Self.Key.noteAscendSortKey) if self.noteSearchMode { reloadNoteForSearchMode() } else { reloadNoteForSortMode() } } @IBAction func noteSearchAction(_ sender: NSButton) { // self.noteTitleLabel.isHidden = true // self.noteSearchButton.isHidden = true // self.noteDoneButton.isHidden = false // self.noteFilterButton.isHidden = true // self.noteMoreButton.isHidden = true self.noteFilterButtonHoverView.isHidden = true } @IBAction func noteFilterAction(_ sender: AnyObject?) { let button = sender as? NSButton if self.noteFilterSelected == false { } let menu = NSMenu() self.noteFilterMenu = menu let filterViewController = KMNoteOutlineFilterViewController() filterViewController.listView = self.listView filterViewController.view.layer?.backgroundColor = .clear var states: [CPDFAnnotationState] = [] for anno in self.allAnnotations { if let reviewS = self.noteReplyHanddler.fetchReviewState(anno) { if states.contains(reviewS) == false { states.append(reviewS) } } else { if states.contains(.none) == false { states.append(.none) } } if let markS = self.noteReplyHanddler.fetchAnnoState(anno) { if states.contains(markS) == false { states.append(markS) } } else { if states.contains(.unMarked) == false { states.append(.unMarked) } } } filterViewController.updateStates(states: states) filterViewController.setNotesArray(self.allAnnotations as NSArray) filterViewController.applyFilterCallback = { [weak self] typeArr, colorArr, authorArr, isEmpty in menu.cancelTracking() self?.headerView.sortButton.properties.state = .normal self?.headerView.sortButton.reloadData() self?.reloadAnnotation() } filterViewController.cancelCallback = {[weak self] isCancel in if (isCancel) { self?.headerView.sortButton.properties.state = .normal self?.headerView.sortButton.reloadData() menu.cancelTracking() } } let item = menu.addItem(withTitle: "", action: nil, keyEquivalent: "") item.target = self item.representedObject = filterViewController item.view = filterViewController.view menu.delegate = self menu.popUp(positioning: nil, at: NSMakePoint(-130, 30), in: self.headerView.sortButton) } func fetchNote(for index: Int) -> CPDFAnnotation? { return self.fetchAnnoModel(for: index)?.anno } func fetchAnnoModel(for index: Int) -> KMBotaAnnotationModel? { if self.noteSearchMode { // 搜索模式 return self.noteSearchArray.safe_element(for: index) as? KMBotaAnnotationModel } else { // 常规模式(非搜索) // let model = self.annoListModel?.datas.safe_element(for: index) let model = self.noteOutlineView.item(atRow: index) if let data = model as? KMBotaAnnotationSectionModel { return nil } if let data = model as? KMBotaAnnotationModel { return data } if let data = model as? KMBotaAnnotationFooterModel { } if let data = model as? KMBotaAnnotationReplyModel { } return nil } } func fetchAnnoModel(for anno: CPDFAnnotation) -> KMBotaAnnotationModel? { if self.noteSearchMode { // 搜索模式 for model in self.noteSearchArray { guard let data = model as? KMBotaAnnotationModel else { continue } if anno.isEqual(to: data.anno) { return data } } } else { // 常规模式(非搜索) for model in self.annoListModel?.datas ?? [] { for item in model.items { if let data = item as? KMBotaAnnotationModel { if anno.isEqual(to: data.anno) { return data } } } } } return nil } func showNoteEmptyView() { self.emptyView.isHidden = false } func hideNoteEmptyView() { self.emptyView.isHidden = true } } // MARK: - Note extension KMLeftSideViewController { public func refreshUIForAddAnnotation(annos: [CPDFAnnotation]?, page: CPDFPage?) { let need = self._annoList_needRefreshUI(annos: annos) if need == false { return } self.updateThumbnail(at: Int(page?.pageIndex() ?? 0)) self.note_reloadDataIfNeed() } public func refreshUIForAnnoAttributeDidChange(_ anno: CPDFAnnotation?, attributes: [String : Any]?) { self.updateThumbnail(at: Int(anno?.page?.pageIndex() ?? 0)) let need = self._annoList_needRefreshUI(annos: anno != nil ? [anno!] : []) if need == false { return } if let data = anno { self.note_reloadDataForAnnoIfNeed(anno: data) } } public func annoList_refreshUIForDeleteAnnotations(annos: [CPDFAnnotation]?, page: CPDFPage?) { self.updateThumbnail(at: Int(page?.pageIndex() ?? 0)) let need = self._annoList_needRefreshUI(annos: annos) if need == false { return } for anno in annos ?? [] { if let model = self.fetchAnnoModel(for: anno) { self.noteSearchArray.removeObject(model) model.sectionModel?.items.removeObject(model) if let footer = model.footerModel { model.sectionModel?.items.removeObject(footer) } if let cnt = model.sectionModel?.items.count, cnt == 0 { self.annoListModel?.datas.removeObject(model.sectionModel!) } if self.allAnnotations.contains(anno) { self.allAnnotations.removeObject(anno) } } } if self.dataUpdating == false { self.note_refrshUIIfNeed() } } func note_refrshUIIfNeed() { Task { @MainActor in self.noteOutlineView?.reloadData() } } func note_reloadDataIfNeed() { self.reloadAnnotation() } func note_reloadDataForAnnoIfNeed(anno: CPDFAnnotation) { if anno is CPDFLineAnnotation || anno is CPDFSquareAnnotation || anno is CPDFCircleAnnotation || anno is CPDFInkAnnotation { // 形状注释 + Ink 需要显示框住的内容【刷新】 for item in self.annoListModel?.datas ?? [] { for itemM in item.items { if anno.isEqual(to: itemM.anno) { self.noteOutlineView.reloadItem(itemM) break } } } } else { for item in self.annoListModel?.datas ?? [] { for itemM in item.items { if anno.isEqual(to: itemM.anno) { self.noteOutlineView.reloadItem(itemM) break } } } } } func reloadAnnotation() { if self.listView != nil { let filterKey = self.pdfDocument()?.documentURL.path ?? "" let typeArr: [String] = KMBotaTools.noteFilterAnnoTypes(key: filterKey) let colorArr: [Any] = KMBotaTools.noteFilterColors(key: filterKey) let authorArr: [Any] = KMBotaTools.noteFilterAuthors(key: filterKey) var stateArr: [NSNumber] = KMBotaTools.noteFilterStates(key: filterKey) var hasMark = false var hasReview = false for data in stateArr { let state = data.intValue if state == CPDFAnnotationState.marked.rawValue || state == CPDFAnnotationState.unMarked.rawValue { hasMark = true } if state == CPDFAnnotationState.none.rawValue || state == CPDFAnnotationState.accepted.rawValue || state == CPDFAnnotationState.rejected.rawValue || state == CPDFAnnotationState.canceled.rawValue || state == CPDFAnnotationState.completed.rawValue { hasReview = true } } if hasMark == false { stateArr.append(NSNumber(value: CPDFAnnotationState.marked.rawValue)) stateArr.append(NSNumber(value: CPDFAnnotationState.unMarked.rawValue)) } if hasReview == false { stateArr.append(NSNumber(value: CPDFAnnotationState.none.rawValue)) stateArr.append(NSNumber(value: CPDFAnnotationState.accepted.rawValue)) stateArr.append(NSNumber(value: CPDFAnnotationState.rejected.rawValue)) stateArr.append(NSNumber(value: CPDFAnnotationState.canceled.rawValue)) stateArr.append(NSNumber(value: CPDFAnnotationState.completed.rawValue)) } var annotationArray: [CPDFAnnotation] = [] var allAnnotation: [CPDFAnnotation] = [] for i in 0 ..< self.pageCount() { let page = self.pdfDocument()?.page(at: UInt(i)) var annos: [CPDFAnnotation] = [] // 处理过滤 let types = ["Highlight","Underline","Strikeout","Squiggly","Freehand","FreeText","Note","Square","Circle","Line","Stamp","Arrow","Image","Sign"/*, "table"*/,"Polyline","Polygon"] if typeArr.count == 0 && colorArr.count == 0 && authorArr.count == 0 && stateArr.count == 0 { annos = KMOCToolClass.filterAnnotation(annotations: page?.annotations ?? [],types: types) as? [CPDFAnnotation] ?? [] annotationArray += annos } else { var filterAnnos: [CPDFAnnotation] = page?.annotations ?? [] let allAnnos = KMOCToolClass.filterAnnotation(annotations: filterAnnos,types: types) as? [CPDFAnnotation] ?? [] annotationArray += allAnnos if typeArr.count > 0 { var theTypes = typeArr if typeArr.contains("Measure") { //包含测量的话 theTypes.append("Polygon") theTypes.append("Polyline") theTypes.append(CPDFAnnotation.kType.measureArrow) } if theTypes.contains(CPDFAnnotation.kType.measureArrow) && theTypes.contains(CPDFAnnotation.kType.arrow) == false { theTypes.append(CPDFAnnotation.kType.arrow) } filterAnnos = (KMOCToolClass.filterAnnotation(annotations: filterAnnos, types: theTypes) as? [CPDFAnnotation]) ?? [] } if (colorArr.count > 0) { filterAnnos = (KMOCToolClass.filterAnnotation(annotations: filterAnnos,colors: colorArr) as? [CPDFAnnotation]) ?? [] } if (authorArr.count > 0) { filterAnnos = (KMOCToolClass.filterAnnotation(annotations: filterAnnos,authors: authorArr) as? [CPDFAnnotation]) ?? [] } if typeArr.isEmpty == false { if typeArr.contains("Measure") { if typeArr.contains(CPDFAnnotation.kType.arrow) == false { for anno in filterAnnos { if let data = anno as? CPDFLineAnnotation, data.type == CPDFAnnotation.kType.arrow && data.isMeasure == false { filterAnnos.removeObject(anno) } } } } else { for anno in filterAnnos { if let data = anno as? CPDFLineAnnotation, data.isMeasure { filterAnnos.removeObject(anno) } } } } if stateArr.count > 0 { for anno in filterAnnos { let markState = self.noteReplyHanddler.fetchAnnoState(anno) ?? .unMarked let reviewState = self.noteReplyHanddler.fetchReviewState(anno) ?? .none if stateArr.contains(NSNumber(value: markState.rawValue)) == false || stateArr.contains(NSNumber(value: reviewState.rawValue)) == false { filterAnnos.removeObject(anno) } } } annos = filterAnnos } //添加签名注释 for annotation in page?.annotations ?? [] { if annotation.isKind(of: CPDFSignatureAnnotation.self) { annos.append(annotation) annotationArray.append(annotation) } } for annotation in annos { if annotation.isKind(of: KMTableAnnotation.self) { annos.removeObject(annotation) if annotationArray.contains(annotation) { annotationArray.removeObject(annotation) } } else if annotation.annotationShouldDisplay() == false { annos.removeObject(annotation) if annotationArray.contains(annotation) { annotationArray.removeObject(annotation) } } else if annotation.isKind(of: CPDFLinkAnnotation.self) { annos.removeObject(annotation) if annotationArray.contains(annotation) { annotationArray.removeObject(annotation) } } else if annotation.isKind(of: CPDFRedactAnnotation.self) { annos.removeObject(annotation) if annotationArray.contains(annotation) { annotationArray.removeObject(annotation) } } else if annotation.isForm() { annos.removeObject(annotation) if annotationArray.contains(annotation) { annotationArray.removeObject(annotation) } } } // 添加刷选后的注释 allAnnotation += annos } // 处理排序 if self.noteSortType == .page { /// 排序(升序) if self.isAscendSort { allAnnotation.sort { let idx0 = $0.page?.pageIndex() ?? 0 let idx1 = $1.page?.pageIndex() ?? 0 return idx0 <= idx1 } } else { allAnnotation.sort { let idx0 = $0.page?.pageIndex() ?? 0 let idx1 = $1.page?.pageIndex() ?? 0 return idx0 > idx1 } } } else if self.noteSortType == .time { /// 排序(升序) if self.isAscendSort { allAnnotation.sort { if $0.modificationDate() == nil { return false } if $1.modificationDate() == nil { return false } return $0.modificationDate() <= $1.modificationDate() } } else { allAnnotation.sort { if $0.modificationDate() == nil { return false } if $1.modificationDate() == nil { return false } return $0.modificationDate() > $1.modificationDate() } } } // 数据模型化 let model = KMAnnotationListModel() var datas: [KMBotaAnnotationModel] = [] var prePageIdx: Int = NSNotFound var preDate: Date? var secM: KMBotaAnnotationSectionModel? for anno in allAnnotation { let item = KMBotaAnnotationModel() item.anno = anno item.showPage = self.annoListIsShowPage() item.showTime = self.annoListIsShowTime() item.showAuthor = self.annoListIsShowAnther() if self.noteSortType == .page { let pageIdx = Int(anno.pageIndex()) if pageIdx != prePageIdx { // 不是同一个页面 secM = KMBotaAnnotationSectionModel() model.datas.append(secM!) } secM?.items.append(item) prePageIdx = Int(anno.pageIndex()) } else { // time let date = anno.modificationDate() if let same = date?.isSameDay(other: preDate), same == false { // 不是同一天 secM = KMBotaAnnotationSectionModel() model.datas.append(secM!) } secM?.items.append(item) preDate = date } item.sectionModel = secM let replyAnnos = self.noteReplyHanddler.fetchReplyAnnotations(anno) ?? [] for replyAnno in replyAnnos { let replyM = KMBotaAnnotationReplyModel() replyM.anno = anno replyM.replyAnno = replyAnno // secM?.items.append(replyM) replyM.annoModel = item item.replyAnnos.append(replyM) } let footerI = KMBotaAnnotationFooterModel() footerI.anno = anno secM?.items.append(footerI) item.footerModel = footerI footerI.annoModel = item } // model.datas = datas self.annoListModel = model // 转换对象,用于数据显示 self.allAnnotations = annotationArray // self.noteFilterButton.isEnabled = self.allAnnotations.count >= 1 } self.note_refrshUIIfNeed() } func reloadNoteForSearchMode() { if self.noteSearchMode == false { return } // 处理排序 if self.noteSortType == .page { if self.isAscendSort { /// 排序(升序) self.noteSearchArray.sort { let idx0 = $0.anno?.page?.pageIndex() ?? 0 let idx1 = $1.anno?.page?.pageIndex() ?? 0 return idx0 <= idx1 } } else { self.noteSearchArray.sort { let idx0 = $0.anno?.page?.pageIndex() ?? 0 let idx1 = $1.anno?.page?.pageIndex() ?? 0 return idx0 > idx1 } } } else if self.noteSortType == .time { if self.isAscendSort { /// 排序(升序) self.noteSearchArray.sort { if $0.anno?.modificationDate() == nil { return false } if $1.anno?.modificationDate() == nil { return false } return $0.anno!.modificationDate() <= $1.anno!.modificationDate() } } else { self.noteSearchArray.sort { if $0.anno?.modificationDate() == nil { return false } if $1.anno?.modificationDate() == nil { return false } return $0.anno!.modificationDate() > $1.anno!.modificationDate() } } } self.note_refrshUIIfNeed() } func reloadNoteForSortMode() { // 处理排序 var models: [KMBotaAnnotationModel] = [] for selM in self.annoListModel?.datas ?? [] { for model in selM.items { guard let annoModel = model as? KMBotaAnnotationModel else { continue } models.append(annoModel) } } if self.noteSortType == .page { if self.isAscendSort { /// 排序(升序) models.sort { let idx0 = $0.anno?.page?.pageIndex() ?? 0 let idx1 = $1.anno?.page?.pageIndex() ?? 0 return idx0 <= idx1 } } else { models.sort { let idx0 = $0.anno?.page?.pageIndex() ?? 0 let idx1 = $1.anno?.page?.pageIndex() ?? 0 return idx0 > idx1 } } } else if self.noteSortType == .time { if self.isAscendSort { /// 排序(升序) models.sort { if $0.anno?.modificationDate() == nil { return false } if $1.anno?.modificationDate() == nil { return false } return $0.anno!.modificationDate() <= $1.anno!.modificationDate() } } else { models.sort { if $0.anno?.modificationDate() == nil { return false } if $1.anno?.modificationDate() == nil { return false } return $0.anno!.modificationDate() > $1.anno!.modificationDate() } } } // 数据模型\化 let listModel = KMAnnotationListModel() var prePageIdx: Int = NSNotFound var preDate: Date? var secM: KMBotaAnnotationSectionModel? for itemM in models { guard let anno = itemM.anno else { continue } if self.noteSortType == .page { let pageIdx = Int(anno.pageIndex()) if pageIdx != prePageIdx { // 不是同一个页面 secM = KMBotaAnnotationSectionModel() listModel.datas.append(secM!) secM?.isExpand = itemM.sectionModel?.isExpand ?? true } secM?.items.append(itemM) prePageIdx = Int(anno.pageIndex()) } else { // time let date = anno.modificationDate() if let same = date?.isSameDay(other: preDate), same == false { // 不是同一天 secM = KMBotaAnnotationSectionModel() listModel.datas.append(secM!) secM?.isExpand = itemM.sectionModel?.isExpand ?? true } secM?.items.append(itemM) preDate = date } var tmpSelM = itemM.sectionModel itemM.sectionModel = secM let footerI = KMBotaAnnotationFooterModel() footerI.anno = anno secM?.items.append(footerI) footerI.isExpand = itemM.footerModel?.isExpand ?? false itemM.footerModel = footerI footerI.annoModel = itemM } self.annoListModel = listModel self.note_refrshUIIfNeed() } // 搜索 Action func updateNoteFilterPredicate(searchString: String) { var stringValue = searchString // 清空数据 self.noteSearchArray.removeAll() if stringValue.isEmpty { for model in self.annoListModel?.datas ?? [] { for item in model.items { guard let _ = item.anno else { continue } guard let data = item as? KMBotaAnnotationModel else { continue } self.noteSearchArray.append(item) } } } else { // 忽略大小写 let caseInsensite = self.caseInsensitiveNoteSearch if caseInsensite { stringValue = stringValue.lowercased() } for model in self.annoListModel?.datas ?? [] { for item in model.items { guard let note = item.anno else { continue } var noteString = "" if let anno = note as? CPDFMarkupAnnotation { noteString = anno.markupContent() } else { noteString = KMBOTAAnnotationTool.fetchContentLabelString(annotation: note) } if caseInsensite { noteString = noteString.lowercased() } guard let data = item as? KMBotaAnnotationModel else { continue } if noteString.contains(stringValue) { self.noteSearchArray.append(item) } } } } self.note_refrshUIIfNeed() } @objc func selectSelectedNote(_ sender: AnyObject?) { if self.hideNotes() == false { let selectedNotes = self.selectedNotes() if selectedNotes.count == 1 { let annotation = selectedNotes.last! self.listView?.go(to: annotation.bounds, on: annotation.page, animated: true) self.listView?.updateActiveAnnotations([annotation]) self.listView?.setNeedsDisplayAnnotationViewForVisiblePages() if annotation is CPDFPolygonAnnotation || annotation is CPDFPolylineAnnotation { self.listView?.pdfListViewDelegate.pdfListViewAnnotationMeasureInfoChange?(self.listView, with: annotation) } else if let anno = annotation as? CPDFLineAnnotation { if anno.isMeasure { self.listView?.pdfListViewDelegate.pdfListViewAnnotationMeasureInfoChange?(self.listView, with: annotation) } } } } } func selectedNotes() -> [CPDFAnnotation] { var selectedNotes: [CPDFAnnotation] = [] let rowIndexes = self.noteOutlineView.selectedRowIndexes for row in rowIndexes { let item = self.noteOutlineView.item(atRow: row) if item is KMBotaAnnotationModel { if let anno = (item as! KMBotaAnnotationModel).anno { if selectedNotes.contains(anno) == false { selectedNotes.append(anno) } } } } return selectedNotes } func selectedObjcNotes() -> [Any] { var selectedNotes: [Any] = [] let rowIndexes = self.noteOutlineView.selectedRowIndexes for row in rowIndexes { let item = self.noteOutlineView.item(atRow: row) selectedNotes.append(item) } return selectedNotes } func clearAnnotationFilterData() { if let _key = self.pdfDocument()?.documentURL?.path { let userDefaults = UserDefaults.standard let typeData = try?NSKeyedArchiver.archivedData(withRootObject: [Any](), requiringSecureCoding: false) userDefaults.set(typeData, forKey: NoteFilterVC.filterSelectTypeKey + _key) let colorData = try?NSKeyedArchiver.archivedData(withRootObject: [Any](), requiringSecureCoding: false) userDefaults.set(colorData, forKey: NoteFilterVC.filterSelectColorKey + _key) let authorData = try?NSKeyedArchiver.archivedData(withRootObject: [Any](), requiringSecureCoding: false) userDefaults.set(authorData, forKey: NoteFilterVC.filterSelectAuthorKey + _key) userDefaults.synchronize() } } private func _annoList_needRefreshUI(annos: [CPDFAnnotation]?) -> Bool { guard let data = annos else { return false } var need = false for anno in data { if anno.isForm() { continue } if anno is CPDFLinkAnnotation { continue } need = true } return need } } extension KMLeftSideViewController: NSTextFieldDelegate { func controlTextDidBeginEditing(_ obj: Notification) { if self.noteOutlineView.isEqual(to: obj.object) { } } func controlTextDidEndEditing(_ obj: Notification) { if let data = self.editNoteTextField, data.isEqual(to: obj.object) { if let data = self.editNote as? CPDFMarkupAnnotation { data.setMarkupText(self.editNoteTextField?.stringValue ?? "") } else if let data = self.editNote as? CPDFTextAnnotation { data.contents = self.editNoteTextField?.stringValue ?? "" } } } } extension KMLeftSideViewController: KMNoteOutlineViewDelegate { func outlineView(_ anOutlineView: NSOutlineView, canResizeRowByItem item: AnyObject?) -> Bool? { if anOutlineView.isEqual(to: self.noteOutlineView) { return true } return false } func outlineView(_ anOutlineView: NSOutlineView, setHeight newHeight: CGFloat, ofRowByItem item: AnyObject?) { } func outlineView(_ anOutlineView: NSOutlineView, didChangeHiddenOfTableColumn aTableColumn: NSTableColumn) { } func outlineViewCommandKeyPressedDuringNavigation(_ anOutlineView: NSOutlineView) { } } // MARK: - NSMenuDelegate extension KMLeftSideViewController: NSMenuDelegate { func menuNeedsUpdate(_ menu: NSMenu) { menu.removeAllItems() } func menuDidClose(_ menu: NSMenu) { if(menu == self.noteFilterMenu) { headerView.sortButton.properties.state = .normal headerView.sortButton.reloadData() } } }