// // 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() { self.noteSearchField.backgroundColor = KMAppearance.Layout.l_1Color() self.noteSearchField.wantsLayer = true self.noteSearchField.layer?.backgroundColor = KMAppearance.Layout.l_1Color().cgColor self.noteSearchField.layer?.borderWidth = 1.0 self.noteMoreButton.target = self self.noteMoreButton.tag = 304 self.noteMoreButton.action = #selector(leftSideViewMoreButtonAction) self.moreButtonLayer = KMButtonLayer() self.noteMoreButton.layer?.addSublayer(self.moreButtonLayer!) self.moreButtonLayer?.frame = CGRectMake(0, 0, CGRectGetWidth(self.noteMoreButton.bounds), CGRectGetHeight(self.noteMoreButton.bounds)) self.noteFilterButton.target = self self.noteFilterButton.action = #selector(noteFilterAction) self.filterButtonLayer = NSView() self.noteFilterButton.addSubview(self.filterButtonLayer!) self.filterButtonLayer?.frame = CGRectMake(14, 2, 8, 8) self.noteDoneButton.action = #selector(leftSideViewDoneButtonAction) self.noteDoneButton.target = self self.noteDoneButton.tag = 311 self.noteDoneButton.isHidden = true self.noteSearchField.delegate = self self.noteSearchField.isHidden = true self.noteSearchField.endEditCallBack = { [unowned self] isEndEdit in // if (isEndEdit) { // self.noteSearchField.isHidden = true // self.noteSearchButton.isHidden = false // self.noteTitleLabel.isHidden = false // } } self.sortTypeBox.downCallback = { [unowned self] downEntered, mouseBox, _ in if (downEntered) { let menu = NSMenu() let timeItem = menu.addItem(title: KMLocalizedString("Time", nil), action: #selector(sortTypeAction), target: self) timeItem?.representedObject = self timeItem?.tag = 0 let pageItem = menu.addItem(title: KMLocalizedString("Page", nil), action: #selector(sortTypeAction), target: self) pageItem?.representedObject = self timeItem?.tag = 1 if (self.noteSortType == .time) { timeItem?.state = .on pageItem?.state = .off } else if (self.noteSortType == .page) { timeItem?.state = .off pageItem?.state = .on } menu.popUp(positioning: nil, at: CGPointMake(-10, 0), in: self.sortTypeBox) } } self.noteOutlineView.delegate = self self.noteOutlineView.dataSource = self self.noteOutlineView.botaDelegate = self self.noteOutlineView.botaDataSource = self self.noteOutlineView.noteDelegate = self self.noteOutlineView.menu = NSMenu() self.noteOutlineView.menu?.delegate = self self.noteOutlineView.typeSelectHelper = SKTypeSelectHelper(matchOption: .SKSubstringMatch) self.noteOutlineView.registerForDraggedTypes(NSColor.readableTypes(for: NSPasteboard(name: .drag))) self.noteOutlineView.target = self self.noteOutlineView.doubleAction = #selector(selectSelectedNote) } func note_initDefalutValue() { self.noteView.wantsLayer = true self.noteView.layer?.backgroundColor = KMAppearance.Layout.l0Color().cgColor 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) self.noteTitleLabel.stringValue = KMLocalizedString("Notes", nil); self.noteTitleLabel.textColor = KMAppearance.Layout.h0Color() self.noteSearchField.layer?.borderColor = KMAppearance.Interactive.a0Color().cgColor self.noteMoreButton.wantsLayer = true self.moreButtonLayer?.layerType = .none self.moreButtonLayer?.isHidden = true self.noteFilterButton.toolTip = KMLocalizedString("Sort", nil) self.noteFilterButton.wantsLayer = true self.filterButtonLayer?.isHidden = true self.filterButtonLayer?.wantsLayer = true self.filterButtonLayer?.layer?.backgroundColor = KMAppearance.Interactive.a0Color().cgColor self.filterButtonLayer?.layer?.cornerRadius = 4.0 if (self.isAscendSort) { self.noteSortButton.image = NSImage(named: KMImageNameBtnSidebarRankReverse) self.noteSortButton.toolTip = KMLocalizedString("ascending sort", nil) } else { self.noteSortButton.image = NSImage(named: KMImageNameBtnSidebarRankPositive) self.noteSortButton.toolTip = KMLocalizedString("descending sort", nil) } self.noteSearchButton.toolTip = KMLocalizedString("Search", nil) self.noteDoneButton.title = KMLocalizedString("Done", nil) self.noteDoneButton.toolTip = KMLocalizedString("Done", nil) self.noteDoneButton.setTitleColor(KMAppearance.Layout.w0Color()) self.noteDoneButton.wantsLayer = true self.noteDoneButton.layer?.backgroundColor = KMAppearance.Interactive.a0Color().cgColor self.noteDoneButton.layer?.cornerRadius = 4.0 self.noteHeaderView.wantsLayer = true self.noteHeaderView.layer?.backgroundColor = KMAppearance.Else.textTagColor().cgColor self.noteHeaderView.layer?.cornerRadius = 1.0 let sortType = KMDataManager.ud_integer(forKey: Self.Key.noteSortTypeKey) if (sortType == 1) { self.noteSortType = KMNoteSortType(rawValue: sortType) ?? .none if (self.noteSortType == .time) { self.sortTypeLabel.stringValue = KMLocalizedString("Time", nil) self.sortTypeBox.toolTip = KMLocalizedString("Time", nil) } else if (self.noteSortType == .page) { self.sortTypeLabel.stringValue = KMLocalizedString("Page", nil) self.sortTypeBox.toolTip = KMLocalizedString("Page", nil) } } else { self.noteSortType = .time self.sortTypeLabel.stringValue = KMLocalizedString("Time", nil) } self.sortTypeLabel.textColor = KMAppearance.Layout.h1Color() self.noteOutlineView.backgroundColor = KMAppearance.Layout.l0Color() self.noteOutlineView.autoresizesOutlineColumn = false self.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._addExportPDFMenu(menu) _ = 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", nil), action: #selector(unfoldNoteAction), target: self) item?.state = isFold ? .off : .on item?.representedObject = items item = menu.addItem(title: KMLocalizedString("Collapse", nil), 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 { let annotation = self.noteItems(items!).lastObject as? CPDFAnnotation if let data = annotation?.isEditable(), data { if annotation?.type == nil { let isNote = annotation?.isNote() ?? false if isNote { // [NSLocalizedString(@"Edit", @"Menu item title") stringByAppendingEllipsis] item = menu.addItem(title: KMLocalizedString("Edit", "Menu item title"), action: #selector(editNoteTextFromTable), target: self) item?.representedObject = annotation } } else if let data = self.noteOutlineView.tableColumn(withIdentifier: NSUserInterfaceItemIdentifier("note"))?.isHidden, data { // [NSLocalizedString(@"Edit", @"Menu item title") stringByAppendingEllipsis] item = menu.addItem(title: KMLocalizedString("Edit", "Menu item title"), action: #selector(editThisAnnotation), target: self) item?.representedObject = annotation } else { item = menu.addItem(title: KMLocalizedString("Edit", "Menu item title"), action: #selector(editNoteFromTable), target: self) item?.representedObject = annotation item = menu.addItem(title: KMLocalizedString("Edit", "Menu item title"), action: #selector(editThisAnnotation), target: self) item?.representedObject = annotation item?.keyEquivalentModifierMask = [.option] item?.isAlternate = true } } } if menu.numberOfItems > 0 { _ = self._addExportPDFMenu(menu) menu.addItem(.separator()) if self.outlineView(self.noteOutlineView, canDeleteItems: items as? [Any] ?? []) { item = menu.addItem(title: KMLocalizedString("Delete", "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: NSLocalizedString("Export Annotations…", tableName: "", comment: ""), action: nil, target: self) let subMenu = NSMenu() item?.submenu = subMenu item = subMenu.addItem(title: NSLocalizedString("PDF", tableName: "", comment: ""), action: #selector(exportAnnotationNotes), target: self) item?.tag = 0 item = subMenu.addItem(title: NSLocalizedString("PDF Bundle", tableName: "", comment: ""), action: #selector(exportAnnotationNotes), target: self) item?.tag = 1 item = subMenu.addItem(title: NSLocalizedString("PDF Reader Pro Edition Notes", tableName: "", comment: ""), action: #selector(exportAnnotationNotes), target: self) item?.tag = 2 item = subMenu.addItem(title: NSLocalizedString("Notes as Text", tableName: "", comment: ""), action: #selector(exportAnnotationNotes), target: self) item?.tag = 3 item = subMenu.addItem(title: NSLocalizedString("Notes as RTF", tableName: "", comment: ""), action: #selector(exportAnnotationNotes), target: self) item?.tag = 4 item = subMenu.addItem(title: NSLocalizedString("Notes as RTFD", tableName: "", comment: ""), action: #selector(exportAnnotationNotes), target: self) item?.tag = 5 item = subMenu.addItem(title: NSLocalizedString("Notes as FDF", tableName: "", comment: ""), action: #selector(exportAnnotationNotes), target: self) item?.tag = 6 return menu } private func _addDeleteAllAnnoItem(_ menu: NSMenu) -> NSMenuItem? { return menu.addItem(title: NSLocalizedString("Remove All Annotations", tableName: "", comment: ""), action: #selector(removeAllAnnotations), target: self) } func annoListMoreMenu(_ view: NSButton) { let menu = NSMenu() let object = KMPopupMenuObject() object.menuTag = 1001 menu.delegate = object object.enterControllerCallback = { [weak self] isEnter in if (isEnter) { self?.moreButtonLayer?.isHidden = false } else { self?.moreButtonLayer?.isHidden = true } } let expandAllItem = menu.addItem(title: KMLocalizedString("Expand All", nil), action: #selector(note_expandAllComments), target: self) expandAllItem?.representedObject = self.noteOutlineView let foldAllItem = menu.addItem(title: KMLocalizedString("Collapse All", nil), action: #selector(note_foldAllComments), target: self) foldAllItem?.representedObject = self.noteOutlineView let type = self.annoListModel?.foldType ?? .none expandAllItem?.state = type == .unfold ? .on : .off foldAllItem?.state = type == .fold ? .on : .off let showItem = menu.addItem(title: KMLocalizedString("Show Note", nil), action: nil, target: self) let subMenu = NSMenu() let pageItem = subMenu.addItem(title: KMLocalizedString("Page", nil), action: #selector(noteShowNoteAction), target: self) pageItem?.state = self.annoListIsShowPage() ? .on : .off pageItem?.representedObject = self.noteOutlineView pageItem?.tag = 101 let timeItem = subMenu.addItem(title: KMLocalizedString("Time", nil) , action: #selector(noteShowNoteAction), target: self) timeItem?.state = self.annoListIsShowTime() ? .on : .off timeItem?.representedObject = self.noteOutlineView timeItem?.tag = 102 let authorItem = subMenu.addItem(title: KMLocalizedString("Author", nil) , action: #selector(noteShowNoteAction), target: self) authorItem?.state = self.annoListIsShowAnther() ? .on : .off authorItem?.representedObject = self.noteOutlineView authorItem?.tag = 103 showItem?.submenu = subMenu menu.addItem(.separator()) _ = self._addExportPDFMenu(menu) menu.addItem(.separator()) _ = self._addDeleteAllAnnoItem(menu) if let data = NSApp.currentEvent { NSMenu.popUpContextMenu(menu, with: data, for: view, with: nil) } } func annoListValidateMenuItem(_ menuItem: NSMenuItem) -> Bool { let action = menuItem.action if (action == #selector(note_expandAllComments) || action == #selector(note_foldAllComments) || action == #selector(exportAnnotationNotes) || action == #selector(removeAllAnnotations)) { let cnt = self.annoListModel?.datas.count ?? 0 return cnt > 0 } else if (action == #selector(unfoldNoteAction) || action == #selector(foldNoteAction)) { let row = self.noteOutlineView.clickedRow let foldNote = self.fetchNote(for: row) // SKNPDFAnnotationNote if foldNote is CPDFMarkupAnnotation || foldNote is CPDFTextAnnotation { return true } else { return false } } else if (action == #selector(editNoteFromTable)) { let row = self.noteOutlineView.clickedRow let foldNote = self.fetchNote(for: row) // if (@available(macOS 10.13, *)) { // if ([foldNote.widgetFieldType isEqualToString:PDFAnnotationWidgetSubtypeSignature]) { // return NO; // } // } if foldNote is CPDFStampAnnotation || foldNote is KMAnnotationStamp || foldNote is CPDFListStampAnnotation { return false } else { return true } } return true } @IBAction func note_expandAllComments(_ sender: AnyObject?) { guard let model = self.annoListModel else { return } if (model.foldType == .unfold) { // 已全部展开 return } model.foldType = .unfold self.noteOutlineView.reloadData() } @IBAction func note_foldAllComments(_ sender: AnyObject?) { guard let model = self.annoListModel else { return } if (model.foldType == .fold) { return } model.foldType = .fold 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 { models = self.noteSearchArray } else { models = self.annoListModel?.datas ?? [] } 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 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 let viewS = self.noteOutlineView.view(atColumn: 0, row: row, makeIfNecessary: true) (viewS as? KMNoteTableViewCell)?.isFold = false } @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 let viewS = self.noteOutlineView.view(atColumn: 0, row: row, makeIfNecessary: true) (viewS as? KMNoteTableViewCell)?.isFold = true } @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?", nil), buttons: [KMLocalizedString("Yes", nil), KMLocalizedString("No", nil)]) if response == .alertFirstButtonReturn { // var annos: [CPDFAnnotation] = [] // for i in 0 ..< doc.pageCount { // let page = self.pdfDocument()?.page(at: i) // for anno in page?.annotations ?? [] { // if anno is CPDFTextWidgetAnnotation || anno is CPDFButtonWidgetAnnotation || anno is CPDFChoiceWidgetAnnotation { // continue // } // // if ([annotation.widgetFieldType isEqualToString:PDFAnnotationWidgetSubtypeSignature]) { // // continue; // // } // if anno is CPDFLinkAnnotation { // continue // } // annos.append(anno) // } // } self.dataUpdating = true // for anno in annos { // self.listView?.remove(anno) // } for model in self.annoListModel?.datas ?? [] { if let anno = model.anno { self.listView?.remove(anno) } } self.dataUpdating = false self.note_refrshUIIfNeed() } } } @objc func editNoteTextFromTable(_ sender: NSMenuItem) { // PDFAnnotation *annotation = [sender representedObject]; guard let annotation = sender.representedObject as? CPDFAnnotation else { return } self.listView?.scrollAnnotationToVisible(annotation) // self.listView.activeAnnotation = annotation // [self showNote:annotation]; // SKNoteWindowController *noteController = (SKNoteWindowController *)[self windowControllerForNote:annotation]; // [[noteController window] makeFirstResponder:[noteController textView]]; // [[noteController textView] selectAll:nil]; } @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 // self.renamePDFOutline = [rightSideController.noteOutlineView itemAtRow:rightSideController.noteOutlineView.clickedRow]; let viewS = self.noteOutlineView.view(atColumn: 0, row: row, makeIfNecessary: true) as? KMNoteTableViewCell 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 { self.reloadNoteForSearchMode() } else { self.reloadAnnotation() } } @IBAction func noteSearchAction(_ sender: NSButton) { self.noteSearchField.isHidden = false self.noteTitleLabel.isHidden = true self.noteSearchButton.isHidden = true self.noteDoneButton.isHidden = false self.noteFilterButton.isHidden = true self.noteMoreButton.isHidden = true self.noteSearchField.becomeFirstResponder() } @IBAction func noteFilterAction(_ sender: AnyObject?) { let button = sender as? NSButton let menu = NSMenu() let filterViewController = KMNoteOutlineFilterViewController() filterViewController.listView = self.listView filterViewController.view.layer?.backgroundColor = .clear filterViewController.setNotesArray(self.allAnnotations as NSArray) filterViewController.applyFilterCallback = { [weak self] typeArr, colorArr, authorArr, isEmpty in menu.cancelTracking() if (isEmpty) { self?.filterButtonLayer?.isHidden = true } else { self?.filterButtonLayer?.isHidden = false } self?.reloadAnnotation() } filterViewController.cancelCallback = { isCancel in if (isCancel) { menu.cancelTracking() } } let item = menu.addItem(withTitle: "", action: nil, keyEquivalent: "") item.target = self item.representedObject = filterViewController item.view = filterViewController.view menu.popUp(positioning: nil, at: CGPointMake(-130, 30), in: button) } 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 { // 常规模式(非搜索) return self.annoListModel?.datas.safe_element(for: index) as? KMBotaAnnotationModel } } func fetchAnnoModel(for anno: CPDFAnnotation) -> KMBotaAnnotationModel? { if self.noteSearchMode { // 搜索模式 for model in self.noteSearchArray { if anno.isEqual(to: model.anno) { return model } } } else { // 常规模式(非搜索) for model in self.annoListModel?.datas ?? [] { if anno.isEqual(to: model.anno) { return model } } } return nil } @IBAction @objc func sortTypeAction(_ sender: NSMenuItem) { let item = sender let tag = item.tag if (item.state == .on) { item.state = .off } else { item.state = .on } if (tag == 0) { self.noteSortType = .page self.sortTypeLabel.stringValue = KMLocalizedString("Page", nil) self.sortTypeBox.toolTip = KMLocalizedString("Page", nil) } else if (tag == 1) { self.noteSortType = .time self.sortTypeLabel.stringValue = KMLocalizedString("Time", nil) self.sortTypeBox.toolTip = KMLocalizedString("Time", nil) } KMDataManager.ud_set(self.noteSortType.rawValue, forKey: Self.Key.noteSortTypeKey) if self.noteSearchMode { self.reloadNoteForSearchMode() } else { self.reloadAnnotation() } } func showNoteEmptyView() { let view = self.noteOutlineView.enclosingScrollView?.documentView let viewFrame = view?.frame ?? .zero let emptyVcSize = self.leftSideEmptyVC.emptyAnnotationView.frame.size self.leftSideEmptyVC.emptyAnnotationView.frame = NSMakeRect((viewFrame.size.width-emptyVcSize.width)/2.0,(viewFrame.size.height-emptyVcSize.height)/2.0, emptyVcSize.width, emptyVcSize.height) self.leftSideEmptyVC.emptyAnnotationView.autoresizingMask = [.minXMargin, .maxXMargin, .minYMargin, .maxYMargin] self.noteOutlineView.enclosingScrollView?.documentView?.addSubview(self.leftSideEmptyVC.emptyAnnotationView) self.leftSideEmptyVC.exportAnnotationBtn.isEnabled = false self.leftSideEmptyVC.deleteAnnotationBtn.isEnabled = false if (self.leftView.segmentedControl.selectedSegment == KMSelectedSegmentType.annotation.rawValue) { self.noteHeaderView.isHidden = true self.toolButtonBoxLayoutConstraint.constant = 40.0 } } func hideNoteEmptyView() { self.leftSideEmptyVC.emptyAnnotationView.removeFromSuperview() self.leftSideEmptyVC.exportAnnotationBtn.isEnabled = true self.leftSideEmptyVC.deleteAnnotationBtn.isEnabled = true if (self.leftView.segmentedControl.selectedSegment == KMSelectedSegmentType.annotation.rawValue) { self.noteHeaderView.isHidden = false self.toolButtonBoxLayoutConstraint.constant = 64.0 } } } // 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]?) { 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?) { if self.type.methodType != .Annotation { return } 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) self.annoListModel?.datas.removeObject(model) } } if self.dataUpdating == false { self.note_refrshUIIfNeed() } } func note_refrshUIIfNeed() { if self.type.methodType != .Annotation { return } Task { @MainActor in self.noteOutlineView.reloadData() } } func note_reloadDataIfNeed() { if self.type.methodType != .Annotation { return } self.reloadAnnotation() } func note_reloadDataForAnnoIfNeed(anno: CPDFAnnotation) { if self.type.methodType != .Annotation { return } if anno is CPDFLineAnnotation || anno is CPDFSquareAnnotation || anno is CPDFCircleAnnotation || anno is CPDFInkAnnotation { // 形状注释 + Ink 需要显示框住的内容【刷新】 for item in self.annoListModel?.datas ?? [] { if anno.isEqual(to: item.anno) { self.noteOutlineView.reloadItem(item) break } } } else { for item in self.annoListModel?.datas ?? [] { if anno.isEqual(to: item.anno) { self.noteOutlineView.reloadItem(item) break } } } } func reloadAnnotation() { if self.listView != nil { let filterKey = self.pdfDocument()?.documentURL.path ?? "" let typeArr: [Any] = KMBotaTools.noteFilterAnnoTypes(key: filterKey) let colorArr: [Any] = KMBotaTools.noteFilterColors(key: filterKey) let authorArr: [Any] = KMBotaTools.noteFilterAuthors(key: filterKey) if typeArr.count == 0 && colorArr.count == 0 && authorArr.count == 0 { // self.filtrateButton.image = NSImage(named: "KMImageNameAnnotationsFiltrate") self.filterButtonLayer?.isHidden = true } else { // self.filtrateButton.image = NSImage(named: "icon_annotation_screening_select")self.filterButtonLayer?.isHidden = true self.filterButtonLayer?.isHidden = false } 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","Freehand","FreeText","Note","Square","Circle","Line","Stamp","Arrow","Image","Redact","Sign"/*, "table"*/] if typeArr.count == 0 && colorArr.count == 0 && authorArr.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 { filterAnnos = (KMOCToolClass.filterAnnotation(annotations: filterAnnos, types: typeArr) 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]) ?? [] } 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) // } else if annotation.annotationShouldDisplay() == false { annos.removeObject(annotation) } else if annotation.isKind(of: CPDFLinkAnnotation.self) { annos.removeObject(annotation) } } // 添加刷选后的注释 allAnnotation += annos //添加所有annotation 用于筛选 // annotationArray += (page?.annotations ?? []) // annotationArray += 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] = [] for anno in allAnnotation { let item = KMBotaAnnotationModel() item.anno = anno item.showPage = self.annoListIsShowPage() item.showTime = self.annoListIsShowTime() item.showAuthor = self.annoListIsShowAnther() datas.append(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() } // 搜索 Action func updateNoteFilterPredicate() { var stringValue = self.noteSearchField.stringValue // 清空数据 self.noteSearchArray.removeAll() if stringValue.isEmpty { for model in self.annoListModel?.datas ?? [] { guard let _ = model.anno else { continue } self.noteSearchArray.append(model) } } else { // 忽略大小写 let caseInsensite = self.caseInsensitiveNoteSearch if caseInsensite { stringValue = stringValue.lowercased() } for model in self.annoListModel?.datas ?? [] { guard let note = model.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() } if noteString.contains(stringValue) { self.noteSearchArray.append(model) } } } 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) // [pdfView scrollAnnotationToVisible:annotation]; // [pdfView setActiveAnnotation:annotation]; self.listView?.updateActiveAnnotations([annotation]) self.listView?.setNeedsDisplayAnnotationViewForVisiblePages() // } } // NSInteger column = [sender clickedColumn]; // if (column != -1) { // NSString *colID = [[[sender tableColumns] objectAtIndex:column] identifier]; // // if ([colID isEqualToString:@"color"]){ // for (PDFAnnotation *annotation in self.pdfView.activeAnnotations) { // if (![annotation isKindOfClass:[PDFAnnotationChoiceWidget class]] && // ![annotation isKindOfClass:[PDFAnnotationButtonWidget class]] && // ![annotation isKindOfClass:[PDFAnnotationTextWidget class]]) { // [[NSColorPanel sharedColorPanel] orderFront:nil]; // break; // } // // } // } // } } } 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 anno.type == nil { // item = [(SKNoteText *)item note]; // } if selectedNotes.contains(anno) == false { selectedNotes.append(anno) } } } } 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 } // for anno in data { // if anno.isKind(of: KMTableAnnotation.self) == false { // return true // } // } return true } }