// // KMLeftSideViewController.swift // PDF Reader Pro // // Created by lxy on 2022/10/10. // import Cocoa import KMComponentLibrary // MARK: - KMBotaAnnotationHeaderView class KMBotaAnnotationHeaderView: NSView { private lazy var titleLabel_: NSTextField = { let view = NSTextField(labelWithString: "") view.font = ComponentLibrary.shared.getFontFromKey("mac/body-m-bold") view.textColor = KMNColorTools.colorText_2() return view }() private lazy var searchButton_: ComponentButton = { let view = ComponentButton() let prop = ComponentButtonProperty() prop.type = .text_gray prop.size = .xxs prop.onlyIcon = true prop.icon = NSImage(named: "KMImageNameOutlineSearch") view.properties = prop return view }() private lazy var sortButton_: ComponentButton = { let view = ComponentButton() let prop = ComponentButtonProperty() prop.type = .text_gray prop.size = .xxs prop.onlyIcon = true prop.icon = NSImage(named: "KMImageNameBotaSearch") view.properties = prop return view }() private lazy var moreButton_: ComponentButton = { let view = ComponentButton() let prop = ComponentButtonProperty() prop.type = .text_gray prop.size = .xxs prop.onlyIcon = true prop.icon = NSImage(named: "KMImageNameOutlineMore") view.properties = prop return view }() private lazy var bottomLine_: NSView = { let view = NSView() view.wantsLayer = true return view }() var titleLabel: NSTextField { get { return titleLabel_ } } var searchButton: ComponentButton { get { return searchButton_ } } var sortButton: ComponentButton { get { return sortButton_ } } var moreButton: ComponentButton { get { return moreButton_ } } var bottomLine: NSView { get { return bottomLine_ } } var itemClick: KMCommonClickBlock? convenience init() { self.init(frame: .init(x: 0, y: 0, width: 300, height: 40)) initSubviews() } override func awakeFromNib() { super.awakeFromNib() initSubviews() } func initSubviews() { let margin: CGFloat = 12 addSubview(titleLabel_) titleLabel_.km_add_leading_constraint(constant: margin) titleLabel_.km_add_centerY_constraint() let vspace: CGFloat = 8 let wh: CGFloat = 24 addSubview(moreButton_) moreButton_.km_add_size_constraint(size: .init(width: wh, height: wh)) moreButton_.km_add_trailing_constraint(constant: -margin) moreButton_.km_add_centerY_constraint() moreButton_.setTarget(self, action: #selector(moreAction)) addSubview(sortButton_) sortButton_.km_add_size_constraint(size: .init(width: wh, height: wh)) sortButton_.km_add_trailing_constraint(equalTo: moreButton_, attribute: .leading, constant: -vspace) sortButton_.km_add_centerY_constraint() sortButton_.setTarget(self, action: #selector(sortAction)) addSubview(searchButton_) searchButton_.km_add_size_constraint(size: .init(width: wh, height: wh)) searchButton_.km_add_trailing_constraint(equalTo: sortButton_, attribute: .leading, constant: -vspace) searchButton_.km_add_centerY_constraint() searchButton_.setTarget(self, action: #selector(searchAction)) addSubview(bottomLine_) bottomLine_.km_add_leading_constraint() bottomLine_.km_add_trailing_constraint() bottomLine_.km_add_bottom_constraint() bottomLine_.km_add_height_constraint(constant: 1) } @objc func searchAction() { itemClick?(1) } @objc func sortAction() { itemClick?(2) } @objc func moreAction() { itemClick?(3) } } // MARK: - KMLeftSideViewControllerDelegate @objc protocol KMLeftSideViewControllerDelegate { @objc optional func controlStateChange(_ obj: KMLeftSideViewController,show:Bool) @objc optional func enterEditMode(_ obj: KMLeftSideViewController, _ pages: [Int]) @objc optional func searchAction(searchString:String, isCase:Bool) @objc optional func controller(controller: KMLeftSideViewController, itemClick item: Any?, itemKey: KMItemKey, params: Any?) @objc optional func controller(controller: KMLeftSideViewController, rotateType: KMRotateType) @objc optional func controller(controller: KMLeftSideViewController, listViewSelectionDidChange object: Any?, info: [String : Any]?) @objc optional func listViewThumPageIndeDidChange(controller: KMLeftSideViewController, pageIndexs: IndexSet) } extension KMLeftSideViewController.Key { static let disableTableToolTipsKey = "SKDisableTableToolTips" } class KMLeftSideViewController: KMSideViewController { @IBOutlet var searchViewController: KMBotaSearchViewController! @IBOutlet weak var toolButtonBox: NSBox! @IBOutlet weak var toolButtonBoxLayoutConstraint: NSLayoutConstraint! @IBOutlet var noteOutlineView: KMNoteOutlineView! var bookmarkViewC: KMBookMarkViewController? var noteFilterButtonHoverView = KMHoverView() var noteFilterSelected = false var stopRepeatLoad: Bool = false var dataUpdating = false @IBOutlet weak var leftListView: NSView! @IBOutlet weak var toolHeaderBox: NSBox! var noteFilterMenu:NSMenu? var isFirst = false //打开阅读页初始化 var isShowPanel : Bool = false weak var delegate: KMLeftSideViewControllerDelegate? let noteColumnId = NSUserInterfaceItemIdentifier(rawValue: "note") let authorColumnId = NSUserInterfaceItemIdentifier(rawValue: "author") struct Key {} var mwcFlags: MwcFlags = MwcFlags() let scalingIncrement: Float = 0.1 var filterButtonLayer: NSView? var moreButtonLayer: KMButtonLayer? // 注释 var noteTypeDict: [String : Any] = [:] private var _noteSortType: KMNoteSortType = .none var noteSortType: KMNoteSortType { get { return self._noteSortType } set { self._noteSortType = newValue NotificationCenter.default.post(name: NSNotification.Name(rawValue: "KMAnnotationSortTypeKeyNotification"), object: self) } } var isAscendSort = false { didSet { NotificationCenter.default.post(name: NSNotification.Name(rawValue: "KMAnnotationSortTypeKeyNotification"), object: self) } } var headerView: KMNBotaAnnotationHeaderView = { let view = KMNBotaAnnotationHeaderView() return view }() var isRenameNoteOutline = false var currentItem: Any? // 所有注释 var allAnnotations: [CPDFAnnotation] = [] // 注释搜索模式标记 var noteSearchMode = false // 注释搜索数组 var noteSearchArray: [KMBotaAnnotationBaseModel] = [] // 注释搜索 忽略大小写标识 var caseInsensitiveNoteSearch = false // 注释列表数据源 var annoListModel: KMAnnotationListModel? var editNoteTextField: NSTextField? weak var editNote: CPDFAnnotation? var leftMargin: CGFloat = 0 var dragIn = false private var deletePages_ = Set() let userFbHanddler = KMUserFeekbackHanddler() let noteReplyHanddler = KMNoteReplyHanddler() var searchGroupView: ComponentGroup? var searchGroupTarget: ComponentButton? var moreGroupView: ComponentGroup? var rightGroupView: ComponentGroup? var emptyView: ComponentEmpty = { let view = ComponentEmpty() view.properties = ComponentEmptyProperty(emptyType: .noAnnotation, state: .normal, image: NSImage(named: "KMImageNameEmptyAnnotate"), text: KMLocalizedString("No Annotations"), subText: "") return view }() private lazy var headerSearchView_: KMNBotaHeaderSearchView? = { let view = KMNBotaHeaderSearchView.createFromNib() return view }() var headerSearchView: KMNBotaHeaderSearchView? { get { return headerSearchView_ } } deinit { KMPrint("KMLeftSideViewController deinit.") NotificationCenter.default.removeObserver(self) DistributedNotificationCenter.default().removeObserver(self) } override var nibName: NSNib.Name? { return "LeftSideView" } override func viewDidLoad() { super.viewDidLoad() noteReplyHanddler.viewC = self } override func loadView() { super.loadView() searchViewController.loadView() note_initSubViews() note_initDefalutValue() displayEmptyViewAnimating(false) displayNoteViewAnimating(false) hideHeaderSearch() toolHeaderBox.borderWidth = 0 toolHeaderBox.contentView = headerView headerView.titleLabel.textColor = KMNColorTools.colorText_2() headerView.itemClick = { [weak self] idx, _ in if idx == 1 { self?.showHeaderSearch() } else if idx == 2 { self?.showFilterController() } else if idx == 3 { self?.showMoreGroupView() } } headerSearchView?.itemClick = { [weak self] idx, params in if idx == 1 { // 显示搜索限制条件 guard let button = params.first as? ComponentButton else { return } self?.showSearchGroupView(sender: button) } else if idx == 2 { // 关闭搜索 self?.hideHeaderSearch() self?.noteSearchMode = false self?.reloadAnnotation() } } headerSearchView?.valueDidChange = { [weak self] sender, info in let value = info?[.newKey] as? String ?? "" self?.noteSearchMode = !value.isEmpty self?.updateNoteFilterPredicate(searchString: value) } } override func updateUILanguage() { super.updateUILanguage() KMMainThreadExecute { self.headerView.titleLabel.stringValue = KMLocalizedString("Annotation") } } override func updateUIThemeColor() { super.updateUIThemeColor() KMMainThreadExecute { self.headerView.searchButton.properties.icon = NSImage(named: "KMImageNameOutlineSearch") self.headerView.searchButton.reloadData() self.headerView.sortButton.properties.icon = NSImage(named: "KMImagenameBotaAnnotationFilter") self.headerView.sortButton.reloadData() self.headerView.moreButton.properties.icon = NSImage(named: "KMImageNameOutlineMore") self.headerView.moreButton.reloadData() self.headerView.bottomLine.layer?.backgroundColor = KMNColorTools.colorBorder_divider().cgColor } } func showHeaderSearch() { toolHeaderBox.contentView = headerSearchView headerView.searchButton.properties.state = .normal headerView.searchButton.reloadData() } func hideHeaderSearch() { toolHeaderBox.contentView = headerView } func updateMarkState(model:KMBotaAnnotationModel){ // Markup let anno = model.anno guard let state = self.noteReplyHanddler.fetchAnnoState(anno), state == .marked else { self.noteReplyHanddler.markAnnotation(anno) self.noteOutlineView.reloadItem(model) return } self.noteReplyHanddler.unMarkAnnotation(anno) self.noteOutlineView.reloadItem(model) } func updateReplaState(model:KMBotaAnnotationModel,state:CPDFAnnotationState){ let anno = model.anno noteReplyHanddler.updateAnnoState(anno: anno, state: state) self.noteOutlineView.reloadData() } func updateExpand (model:KMBotaAnnotationModel,isExpand:Bool,item: Any) { // 将折叠状态记录到模型 model.foldType = isExpand ? .unfold : .fold model.footerModel?.isExpand = isExpand model.isExpand = isExpand self.noteOutlineView.reloadData() DispatchQueue.main.async { let row = self.noteOutlineView.row(forItem: item) self.noteOutlineView.scrollRowToVisible(row) } } // MARK: - Filter func showFilterController() { noteFilterAction(nil) } // MARK: - Group View func showMoreGroupView() { let groupView = ComponentGroup.createFromNib(in: ComponentLibrary.shared.componentBundle()) var expand_MenuString = KMLocalizedString("Expand All") for secM in self.annoListModel?.datas ?? [] { if secM.isExpand == false { expand_MenuString = KMLocalizedString("Collapse All") break } } let expand_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: expand_MenuString, identifier: BOTAMenuIdentifier_Annotation_Expand) let sort_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Sort"), identifier: BOTAMenuIdentifier_Annotation_Sort) var subMenuItemArr: [ComponentMenuitemProperty] = [] let pageProperty: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("By Page"),identifier: BOTAMenuIdentifier_Annotation_SortPage) let timeAscProperty: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("By Time - Ascending"),identifier: BOTAMenuIdentifier_Annotation_SortAscTime) let timeDecProperty: ComponentMenuitemProperty = ComponentMenuitemProperty( text: KMLocalizedString("By Time - Descending"),identifier: BOTAMenuIdentifier_Annotation_SortDesTime) subMenuItemArr.append(pageProperty) subMenuItemArr.append(timeAscProperty) subMenuItemArr.append(timeDecProperty) sort_Menuitem.subPropertys = subMenuItemArr let improt_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Import Annotations"), identifier: BOTAMenuIdentifier_Annotation_Improt) let exprot_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Import Annotations"), identifier: BOTAMenuIdentifier_Annotation_Improt) let rem_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Remove All Annotations"), identifier: BOTAMenuIdentifier_Annotation_RemoveAll) let delete_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Delete All Reply"), identifier: BOTAMenuIdentifier_Annotation_DeleteRep) var menuItemArr: [ComponentMenuitemProperty] = [] menuItemArr.append(expand_Menuitem) menuItemArr.append(sort_Menuitem) menuItemArr.append(improt_Menuitem) menuItemArr.append(exprot_Menuitem) menuItemArr.append(rem_Menuitem) menuItemArr.append(delete_Menuitem) moreGroupView = groupView groupView?.groupDelegate = self groupView?.frame = CGRectMake(310, 0, 200, 36.0 * CGFloat(menuItemArr.count)) groupView?.updateGroupInfo(menuItemArr) var point = NSPoint(x: CGRectGetMaxX(headerView.moreButton.frame), y: CGRectGetMidY(headerView.moreButton.frame)) point = headerView.convert(point, to: nil) point.y -= (groupView?.frame.size.height ?? 0)/2 groupView?.showWithPoint(point, relativeTo: headerView.moreButton) } func showRightGropView(raw: Int,event:NSEvent,view:NSView?,item:Any) { let groupView = ComponentGroup.createFromNib(in: ComponentLibrary.shared.componentBundle()) currentItem = item var menuItemArr: [ComponentMenuitemProperty] = [] let rowIndexes = self.selectedObjcNotes() var viewHeight = 0.0 if(rowIndexes.count == 1) { let dataItem = rowIndexes.first if let data = dataItem as? KMBotaAnnotationModel { if let an = data.anno as? CPDFAnnotation { if an.isKind(of: CPDFFreeTextAnnotation.self) == true || an.isKind(of: CPDFTextAnnotation.self) == true { } else { let note_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Notes"), identifier: BOTAMenuIdentifier_Annotation_EditNote,representedObject: [data]) menuItemArr.append(note_Menuitem) viewHeight += 36.0 } let reply_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Reply"), identifier: BOTAMenuIdentifier_Annotation_AddRep,representedObject: [data]) menuItemArr.append(reply_Menuitem) viewHeight += 36.0 let state = self.noteReplyHanddler.fetchAnnoState(an) ?? .unMarked var markString = "" if state == .unMarked { markString = KMLocalizedString("Marked") } else { markString = KMLocalizedString("Unmarked") } let mark_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: markString, identifier: BOTAMenuIdentifier_Annotation_AddMark,representedObject: [data]) menuItemArr.append(mark_Menuitem) viewHeight += 36.0 let state_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Status"), identifier: BOTAMenuIdentifier_Annotation_RepState) var subMenuItemArr: [ComponentMenuitemProperty] = [] let property0: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("None"),identifier: BOTAMenuIdentifier_Annotation_RepStateNone,representedObject: [data]) let property1: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Accepted"),identifier: BOTAMenuIdentifier_Annotation_RepStateAccepted,representedObject: [data]) let property2: ComponentMenuitemProperty = ComponentMenuitemProperty( text: KMLocalizedString("Rejected"),identifier: BOTAMenuIdentifier_Annotation_RepStateRejected,representedObject: [data]) let property3: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Cancelled"),identifier: BOTAMenuIdentifier_Annotation_RepStateCancelled,representedObject: [data]) let property4: ComponentMenuitemProperty = ComponentMenuitemProperty( text: KMLocalizedString("Completed"),identifier: BOTAMenuIdentifier_Annotation_RepStateCompleted,representedObject: [data]) subMenuItemArr.append(property0) subMenuItemArr.append(property1) subMenuItemArr.append(property2) subMenuItemArr.append(property3) subMenuItemArr.append(property4) let reviewState = noteReplyHanddler.fetchReviewState(an) ?? .none if reviewState == .none { property0.righticon = NSImage(named: "KMNImageNameMenuSelect") } else if reviewState == .completed { property4.righticon = NSImage(named: "KMNImageNameMenuSelect") } else if reviewState == .canceled { property3.righticon = NSImage(named: "KMNImageNameMenuSelect") } else if reviewState == .accepted { property1.righticon = NSImage(named: "KMNImageNameMenuSelect") } else if reviewState == .rejected { property2.righticon = NSImage(named: "KMNImageNameMenuSelect") } state_Menuitem.subPropertys = subMenuItemArr menuItemArr.append(state_Menuitem) viewHeight += 36.0 menuItemArr.append(ComponentMenuitemProperty.divider()) viewHeight += 8.0 var showString = "" if data.isExpand { showString = KMLocalizedString("Collapse") } else { showString = KMLocalizedString("Expand") } let show_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: showString, identifier: PDFViewMenuIdentifier_Normal_ShowPopUI,representedObject: [data]) menuItemArr.append(show_Menuitem) viewHeight += 36.0 menuItemArr.append(ComponentMenuitemProperty.divider()) viewHeight += 8.0 if an.isKind(of: CPDFFreeTextAnnotation.self) == true || an.isKind(of: CPDFTextAnnotation.self) == true || an.isKind(of: CPDFMarkupAnnotation.self) == true { let copy_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Copy Text"), identifier: PDFViewMenuIdentifier_Normal_Copy,representedObject: [data]) copy_Menuitem.keyEquivalent = "⌘ C" menuItemArr.append(copy_Menuitem) viewHeight += 36.0 } let delete_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Delete"), identifier: PDFViewMenuIdentifier_Normal_Delete,representedObject: [data]) delete_Menuitem.keyEquivalent = "⌘ " + "⌫" menuItemArr.append(delete_Menuitem) viewHeight += 36.0 } } else if let replyModel = dataItem as? KMBotaAnnotationReplyModel { let note_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Notes"), identifier: BOTAMenuIdentifier_Annotation_EditNote,representedObject: [replyModel]) menuItemArr.append(note_Menuitem) viewHeight += 36.0 let copy_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Copy Text"), identifier: PDFViewMenuIdentifier_Normal_CopyText,representedObject: [replyModel]) copy_Menuitem.keyEquivalent = "⌘ C" menuItemArr.append(copy_Menuitem) viewHeight += 36.0 let delete_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Delete"), identifier: BOTAMenuIdentifier_Annotation_DeleteSignRep,representedObject: [replyModel]) delete_Menuitem.keyEquivalent = "⌘ " + "⌫" menuItemArr.append(delete_Menuitem) viewHeight += 36.0 } } else if rowIndexes.count > 1 { let delete_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Delete"), identifier: BOTAMenuIdentifier_Annotation_DeleteMuteRep,representedObject: rowIndexes) delete_Menuitem.keyEquivalent = "⌘ " + "⌫" menuItemArr.append(delete_Menuitem) viewHeight += 36.0 } rightGroupView = groupView groupView?.groupDelegate = self groupView?.frame = CGRectMake(310, 0, 200, viewHeight) groupView?.updateGroupInfo(menuItemArr) var point = NSPoint(x: CGRectGetMaxX(view?.frame ?? CGRectZero), y: CGRectGetMidY(view?.frame ?? CGRectZero)) point = noteOutlineView.convert(point, to: nil) point.y -= (groupView?.frame.size.height ?? 0)/2 groupView?.showWithPoint(point, relativeTo: view) } func showSearchGroupView(sender: ComponentButton) { var viewHeight: CGFloat = 8 var menuItemArr: [ComponentMenuitemProperty] = [] let titles = ["Whole Words","Case Sensitive"] for i in titles { let menuI = ComponentMenuitemProperty(text: KMLocalizedString(i)) menuItemArr.append(menuI) viewHeight += 36 } if let info = menuItemArr.first { if KMDataManager.ud_bool(forKey: KMNSearchKey.wholeWords.annotation) { info.righticon = NSImage(named: "KMNImageNameMenuSelect") } } if let info = menuItemArr.last { if KMDataManager.ud_bool(forKey: KMNSearchKey.caseSensitive.annotation) { info.righticon = NSImage(named: "KMNImageNameMenuSelect") } } let groupView = ComponentGroup.createFromNib(in: ComponentLibrary.shared.componentBundle()) searchGroupView = groupView groupView?.groupDelegate = self groupView?.frame = CGRectMake(310, 0, 200, viewHeight) groupView?.updateGroupInfo(menuItemArr) var point = sender.convert(sender.frame.origin, to: nil) point.y -= viewHeight groupView?.showWithPoint(point, relativeTo: sender) searchGroupTarget = sender } func displayNoteViewAnimating(_ animate: Bool) { searchViewController.contentView = nil if let data = noteOutlineView.enclosingScrollView { replaceSideView(data, animate: animate) } var frame = noteOutlineView.enclosingScrollView?.frame ?? .zero frame.origin.y = 0 frame.size.height = noteOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0 noteOutlineView.enclosingScrollView?.frame = frame reloadAnnotation() } func displayEmptyViewAnimating(_ animate: Bool) { leftListView.addSubview(emptyView) emptyView.km_add_top_constraint(constant: 232) emptyView.km_add_bottom_constraint() emptyView.km_add_leading_constraint() emptyView.km_add_trailing_constraint() emptyView.isHidden = true } } //MARK: Cache extension KMLeftSideViewController { func clearNotification() {} } // MARK: - Private Methods extension KMLeftSideViewController { func updateThumbnail(at index: Int) { var indexs = IndexSet() indexs.insert(index) self.delegate?.listViewThumPageIndeDidChange?(controller: self, pageIndexs: indexs) } } // MARK: - NSOutlineViewDelegate, NSOutlineViewDataSource extension KMLeftSideViewController: NSOutlineViewDelegate, NSOutlineViewDataSource { func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { if outlineView.isEqual(to: noteOutlineView) { if self.noteSearchMode { var cnt = 0 for model in noteSearchArray { cnt += 2 guard let data = model as? KMBotaAnnotationModel else { continue } if data.isExpand == false { continue } for replyI in data.replyAnnos { cnt += 1 } } return cnt } var cnt = 0 for sectionM in annoListModel?.datas ?? [] { if sectionM.items.count > 0 { cnt += 1 if sectionM.isExpand == false { continue } for item in sectionM.items { cnt += 1 if let annoItem = item as? KMBotaAnnotationModel { if annoItem.isExpand == false { continue } for replyItem in annoItem.replyAnnos { cnt += 1 } } } } } self.noteOutlineView.usesAlternatingRowBackgroundColors = false let hasAnno = self.allAnnotations.count >= 1 if(!hasAnno) { headerView.sortButton.properties.isDisabled = true headerView.searchButton.properties.isDisabled = true headerView.moreButton.properties.isDisabled = true } else { headerView.sortButton.properties.isDisabled = false headerView.searchButton.properties.isDisabled = false headerView.moreButton.properties.isDisabled = false } headerView.sortButton.reloadData() headerView.searchButton.reloadData() headerView.moreButton.reloadData() if cnt < 1 { self.showNoteEmptyView() } else { self.hideNoteEmptyView() } return cnt } return 0 } func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { if outlineView.isEqual(to: self.noteOutlineView) { if self.noteSearchMode { var idx = 0 var models: [KMBotaAnnotationBaseModel] = [] for model in self.noteSearchArray { models.append(model) if let data = (model as? KMBotaAnnotationModel)?.footerModel { models.append(data) } } for model in models { idx += 1 if index + 1 == idx { return model } guard let data = model as? KMBotaAnnotationModel else { continue } if data.isExpand == false { continue } for replyI in data.replyAnnos { idx += 1 if index + 1 == idx { return replyI } } } return 0 } var idx = 0 var flagSectionM: KMBotaAnnotationSectionModel? var flagItem: KMBotaAnnotationBaseModel? var replyIten: KMBotaAnnotationReplyModel? var isSection = false var isReply = false for sectionM in self.annoListModel?.datas ?? [] { idx += 1 if index + 1 == idx { flagSectionM = sectionM isSection = true break } if sectionM.isExpand == false { continue } for item in sectionM.items { idx += 1 if index + 1 == idx { flagSectionM = sectionM flagItem = item break } guard let annoItem = item as? KMBotaAnnotationModel else { continue } if annoItem.isExpand == false { continue } for replyI in annoItem.replyAnnos { idx += 1 if index + 1 == idx { flagSectionM = sectionM flagItem = item replyIten = replyI isReply = true break } } } } if isSection { return flagSectionM } if isReply { return replyIten } return flagItem } return item as Any } func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { if outlineView.isEqual(to: noteOutlineView) { if let data = item as? KMBotaAnnotationSectionModel { var cell = outlineView.makeView(withIdentifier: KMAnnotationSectionCellView.km_identifier, owner: nil) as? KMAnnotationSectionCellView if cell == nil { cell = KMAnnotationSectionCellView.createFromNib() } cell?.isExpand = data.isExpand if self.noteSortType == .page { let pageIndex = data.items.first?.anno?.page.pageIndex() ?? 0 cell?.titleLabel.stringValue = KMLocalizedString("Page") + " \(pageIndex + 1)" } else { if let date = data.items.first?.anno?.modificationDate() { let string = KMTools.timeString(timeDate: date, formatString: "yyyy-MM-dd") cell?.titleLabel.stringValue = string } else { cell?.titleLabel.stringValue = "" } } cell?.countLabel.stringValue = "\(data.itemCount / 2)" cell?.itemClick = {[weak self] idx, _ in if idx == 1 { // 收取 & 展开 data.isExpand = !data.isExpand self?.noteOutlineView.reloadData() } } return cell } if let data = item as? KMBotaAnnotationFooterModel { var cell = outlineView.makeView(withIdentifier: KMNoteFooterCellView.km_identifier, owner: self) as? KMNoteFooterCellView if cell == nil { cell = KMNoteFooterCellView.createFromNib() } cell?.model = data let state = noteReplyHanddler.fetchReviewState(data.anno) ?? .none if state == .none { cell?.operationIv.image = NSImage(named: "KMNImageNameBotaNoteStateNone") } else if state == .completed { cell?.operationIv.image = NSImage(named: "KMNImageNameBotaNoteStateCompleted") } else if state == .canceled { cell?.operationIv.image = NSImage(named: "KMNImageNameBotaNoteStateCancelled") } else if state == .accepted { cell?.operationIv.image = NSImage(named: "KMNImageNameBotaNoteStateAccepted") } else if state == .rejected { cell?.operationIv.image = NSImage(named: "KMNImageNameBotaNoteStateRejected") } cell?.operationBox.toolTip = KMPDFAnnotationStateGetString(state: state) if let con = data.replyModel?.replyAnno?.contents, con.isEmpty == false { cell?.inputTextF.stringValue = con DispatchQueue.main.async { self.view.window?.makeFirstResponder(cell?.inputTextF) } } else if let con = data.editAnnoModel?.anno?.contents, con.isEmpty == false { cell?.inputTextF.stringValue = con DispatchQueue.main.async { self.view.window?.makeFirstResponder(cell?.inputTextF) } } else { if let cont = data.inputContent { cell?.inputTextF.stringValue = cont } else { cell?.inputTextF.stringValue = "" } } cell?.updateUI(expand: data.isExpand, animated: data.animated) data.animated = false if data.isFirstResp { DispatchQueue.main.async { self.view.window?.makeFirstResponder(cell?.inputTextF) } data.isFirstResp = false } cell?.inputDidChanged = { value, _ in data.inputContent = value as? String ?? "" } cell?.itemClick = { [weak self] idx, param in if idx == 1 { // comment data.isExpand = !data.isExpand data.animated = true data.isFirstResp = true self?.noteOutlineView.reloadItem(data) } else if idx == 2 { // self?.noteReplyHanddler.showStatePopView(sender: param.first as! NSView, anno: data.anno) } else if idx == 3 { // reply let content = param.first as? String ?? "" if content.isEmpty { return } if let con = data.replyModel?.replyAnno?.contents, con.isEmpty == false { // 编辑 let model = data.replyModel model?.replyAnno?.contents = content model?.replyAnno?.setUserName(KMPreference.shared.author) model?.replyAnno?.setModificationDate(Date()) // 置空编辑状态 data.replyModel = nil data.inputContent = nil self?.noteOutlineView.reloadData() if let row = self?.noteOutlineView.row(forItem: data) { self?.noteOutlineView.scrollRowToVisible(row) } return } if let con = data.editAnnoModel?.anno?.contents, con.isEmpty == false { // 编辑 let model = data.editAnnoModel model?.anno?.contents = content model?.anno?.setUserName(KMPreference.shared.author) // 置空编辑状态 data.replyModel = nil data.inputContent = nil data.editAnnoModel = nil self?.noteOutlineView.reloadData() if let row = self?.noteOutlineView.row(forItem: data) { self?.noteOutlineView.scrollRowToVisible(row) } return } if let replyAnno = self?.noteReplyHanddler.createReplyAnnotation(data.anno, content: content, userName: KMPreference.shared.author) { let model = KMBotaAnnotationReplyModel() model.anno = data.anno model.replyAnno = replyAnno model.annoModel = data.annoModel data.annoModel?.replyAnnos.append(model) data.inputContent = nil self?.noteOutlineView.reloadData() if let row = self?.noteOutlineView.row(forItem: data) { self?.noteOutlineView.scrollRowToVisible(row) } } } else if idx == 4 { // cancel data.isExpand = false data.replyModel = nil data.inputContent = nil self?.noteOutlineView.reloadItem(data) } } return cell } if let data = item as? KMBotaAnnotationReplyModel { var cell = outlineView.makeView(withIdentifier: KMNoteReplyCellView.km_identifier, owner: self) as? KMNoteReplyCellView if cell == nil { cell = KMNoteReplyCellView.createFromNib() } cell?.model = data cell?.titleLabel.stringValue = data.replyAnno?.userName() ?? "" if let data = data.replyAnno?.modificationDate() { cell?.timeLabel.stringValue = KMTools.timeString(timeDate: data) } else { cell?.timeLabel.stringValue = "" } cell?.contentLabel.stringValue = data.replyAnno?.contents ?? "" cell?.itemClick = { [weak self] idx, params in if idx == 1 { // 更多 self?.noteReplyHanddler.showReplyMorePopView(sender: params.first as! NSView, replyModel: data) } } return cell } let model = item as? KMBotaAnnotationModel let note = (item as? KMBotaAnnotationModel)?.anno let cell = outlineView.makeView(withIdentifier: KMNoteTableViewCell.km_identifier, owner: self) as! KMNoteTableViewCell cell.cellNote = note cell.model = model let state = self.noteReplyHanddler.fetchAnnoState(note) ?? .unMarked cell.state = state if let data = note { if data.isKind(of: CPDFStampAnnotation.self) && data.isKind(of: CSelfSignAnnotation.self) == false { } else { if data.isKind(of: CPDFMarkupAnnotation.self) { if (!cell.isFold) { } else { cell.imageViewHeightConstraint.constant = 18.0 + 8 } } else { cell.imageViewHeightConstraint.constant = 18.0 + 8 } } } cell.itemClick = { [weak self] idx , _ in if idx == 1 { // Markup if model != nil { self?.updateMarkState(model: model!) } } } cell.isUnFoldNote = { [weak self] cellNote, isUnfold in if model != nil { self?.updateExpand(model: model!, isExpand: isUnfold, item: item) } } return cell } return nil } func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat { if outlineView.isEqual(self.noteOutlineView) { if let model = item as? KMBotaAnnotationModel { if model.isExpand == false { return model.foldH } if let anno = model.anno { return KMBOTAAnnotationTool.fetchCellHeight(annotation: anno, maxSize: CGSize(width: 260+40 - 16, height: 1000)) } } return 30 } return outlineView.rowHeight } func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { if outlineView.isEqual(to: self.noteOutlineView) { return false } return false } func outlineView(_ outlineView: NSOutlineView, rowViewForItem item: Any) -> NSTableRowView? { if outlineView.isEqual(self.noteOutlineView) { let itemView = KMBotaTableRowView() if let data = item as? KMBotaAnnotationBaseModel { itemView.isSelected = data.isSelected } let rowIndexes = self.noteOutlineView.selectedRowIndexes itemView.rightMouseCallback = { [weak self] (view, event) in if let data = item as? KMBotaAnnotationFooterModel { } else { if !KMOCToolClass.arrayContains(array: self?.selectedObjcNotes(), annotation: item) || self?.selectedObjcNotes().count == 1 { let index = self?.noteOutlineView.row(forItem: item) self?.noteOutlineView.selectRowIndexes(IndexSet(integer: IndexSet.Element(index ?? 0)), byExtendingSelection: false) } let row = outlineView.row(forItem: item) if outlineView.rowView(atRow: row, makeIfNecessary: false) != nil { let rowView = outlineView.rowView(atRow: row, makeIfNecessary: false) self?.showRightGropView(raw: row, event: event, view: rowView,item: item) } } } itemView.selectCallback = { theView in let isSelected = theView.isSelected if theView.numberOfColumns > 0 { let cellView = theView.view(atColumn: 0) if let data = cellView as? KMAnnotationSectionCellView { return } var model: KMBotaAnnotationModel? var indexs = IndexSet() if let data = cellView as? KMNoteFooterCellView { model = data.model?.annoModel model?.isSelected = isSelected self.view.window?.makeFirstResponder(data.inputTextF) } if let data = cellView as? KMNoteReplyCellView { model = data.model?.annoModel model?.isSelected = isSelected } if let data = cellView as? KMNoteTableViewCell { model = data.model model?.isSelected = isSelected } if let data = model { let row = self.noteOutlineView.row(forItem: data) indexs.insert(row) var i = 1 for item in model?.replyAnnos ?? [] { indexs.insert(row+i) i += 1 } indexs.insert(row+i) } } } return itemView; } return nil } func outlineView(_ outlineView: NSOutlineView, selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet) -> IndexSet { if outlineView.isEqual(to: self.noteOutlineView) { for i in proposedSelectionIndexes { let item = self.noteOutlineView.item(atRow: i) if let data = item as? KMBotaAnnotationFooterModel { return IndexSet() } } } return proposedSelectionIndexes } func outlineViewSelectionDidChange(_ notification: Notification) { 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) } } } else if(selectedNotes.count > 1){ let lastSelectedNote = selectedNotes.last! let firstSelectedNote = selectedNotes.first! self.listView?.go(to: lastSelectedNote.bounds, on: lastSelectedNote.page, animated: true) if lastSelectedNote.page == firstSelectedNote.page {//同页的话支持多选选中 var activeAnnotations:[CPDFAnnotation] = listView?.activeAnnotations as! [CPDFAnnotation] activeAnnotations.append(lastSelectedNote) self.listView?.updateActiveAnnotations(activeAnnotations) } else { self.listView?.updateActiveAnnotations([lastSelectedNote]) let rowIndexes = self.noteOutlineView.selectedRowIndexes for i in 0 ..< rowIndexes.count - 1 { noteOutlineView.deselectRow(i) } } self.listView?.setNeedsDisplayAnnotationViewForVisiblePages() if lastSelectedNote is CPDFPolygonAnnotation || lastSelectedNote is CPDFPolylineAnnotation { self.listView?.pdfListViewDelegate.pdfListViewAnnotationMeasureInfoChange?(self.listView, with: lastSelectedNote) } else if let anno = lastSelectedNote as? CPDFLineAnnotation { if anno.isMeasure { self.listView?.pdfListViewDelegate.pdfListViewAnnotationMeasureInfoChange?(self.listView, with: lastSelectedNote) } } } } } func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation { var dragOp = NSDragOperation(rawValue: 0) if outlineView.isEqual(to: self.noteOutlineView) { let pboard = info.draggingPasteboard if pboard.canReadObject(forClasses: [NSColor.self], options: [:]) && index == NSOutlineViewDropOnItemIndex { if let note = item as? CPDFAnnotation, note.type != nil { dragOp = .every } } } return dragOp } func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool { if outlineView.isEqual(to: noteOutlineView) { let pboard = info.draggingPasteboard if pboard.canReadObject(forClasses: [NSColor.self], options: [:]) { let isShift = NSEvent.modifierFlags.contains(.shift) let isAlt = NSEvent.modifierFlags.contains(.option) guard let note = item as? CPDFAnnotation else { NSSound.beep() return false } if let color = NSColor(from: pboard) { note.setColor(color, alternate: isAlt, updateDefaults: isShift) return true } return false } } return false } func outlineView(_ outlineView: NSOutlineView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, byItem item: Any?) { if outlineView.isEqual(to: noteOutlineView) { var note: CPDFAnnotation? if let data = item as? CPDFAnnotation { note = data } else if let data = item as? KMBotaAnnotationModel { note = data.anno } if let data = note?.type, data.isEmpty == false { if tableColumn?.identifier.rawValue == self.noteColumnId.rawValue { let string1 = (object as? String) ?? "" let string2 = note?.string() ?? "" if string1 != string2 { note?.setString(string1) } } else if tableColumn?.identifier.rawValue == self.authorColumnId.rawValue { let string1 = (object as? String) ?? "" let string2 = note?.userName() ?? "" if string1 != string2 { note?.setUserName(string1) } } } } } func outlineView(_ outlineView: NSOutlineView, dataCellFor tableColumn: NSTableColumn?, item: Any) -> NSCell? { if noteOutlineView.isEqual(to: outlineView) { if tableColumn == nil { if let anno = item as? CPDFAnnotation, anno.type == nil { return outlineView.tableColumn(withIdentifier: self.noteColumnId)?.dataCell(forRow: outlineView.row(forItem: item)) as? NSCell } } } return tableColumn?.dataCell(forRow: outlineView.row(forItem: item)) as? NSCell } func outlineView(_ outlineView: NSOutlineView, shouldEdit tableColumn: NSTableColumn?, item: Any) -> Bool { if outlineView.isEqual(to: noteOutlineView) { if (tableColumn == nil) { if self.hideNotes() == false { if let anno = item as? CPDFAnnotation, anno.isNote() { self.listView?.scrollAnnotationToVisible(anno) self.listView?.updateActiveAnnotations([anno]) } } return false } else if tableColumn?.identifier.rawValue == self.noteColumnId.rawValue || tableColumn?.identifier.rawValue == self.authorColumnId.rawValue { return true } } return false } func outlineView(_ outlineView: NSOutlineView, toolTipFor cell: NSCell, rect: NSRectPointer, tableColumn: NSTableColumn?, item: Any, mouseLocation: NSPoint) -> String { if outlineView.isEqual(to: self.noteOutlineView) { if tableColumn == nil || tableColumn?.identifier.rawValue == self.noteColumnId.rawValue { return (item as? CPDFAnnotation)?.string() ?? "" } } return "" } func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItems draggedItems: [Any]) { self.dragIn = true } func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) { self.dragIn = false } func noteItems(_ items: NSArray) -> NSArray { let noteItems = NSMutableArray() for item in items { guard let anno = (item as? KMBotaAnnotationModel)?.anno else { continue } if anno.type == nil { } if noteItems.contains(anno) == false { noteItems.add(anno) } } return noteItems } } // MARK: - KMCustomOutlineViewDelegate, KMCustomOutlineViewDataSource extension KMLeftSideViewController: KMCustomOutlineViewDelegate, KMCustomOutlineViewDataSource { func outlineView(_ anOutlineView: NSOutlineView, canDeleteItems items: [Any]) -> Bool { if anOutlineView.isEqual(to: noteOutlineView) { return self.hideNotes() == false && items.count > 0 } return false } func outlineView(_ anOutlineView: NSOutlineView, deleteItems items: [Any]) { if anOutlineView.isEqual(to: noteOutlineView) { if (items.isEmpty) { return } self.dataUpdating = true for item in self.noteItems(items as NSArray) { guard let anno = item as? CPDFAnnotation else { continue } if let data = anno as? CPDFFreeTextAnnotation { self.listView?.commitEditAnnotationFreeText(data) } self.listView?.remove(anno) } for item in items { if let data = item as? KMBotaAnnotationModel { data.sectionModel?.items.removeObject(data) if let footerModel = data.footerModel { data.sectionModel?.items.removeObject(footerModel) } } } self.dataUpdating = false self.listView?.undoManager?.setActionName(KMLocalizedString("Remove Note", comment: "Undo action name")) self.note_refrshUIIfNeed() } } func outlineView(_ anOutlineView: NSOutlineView, canCopyItems items: [Any]) -> Bool { if anOutlineView.isEqual(to: noteOutlineView) { if (items.count == 1) { } return items.count > 0 } return false } func outlineView(_ anOutlineView: NSOutlineView, copyItems items: [Any]) { if anOutlineView.isEqual(to: self.noteOutlineView) && items.isEmpty == false { } } func outlineView(_ anOutlineView: NSOutlineView, typeSelectHelperSelectionStrings aTypeSelectHelper: SKTypeSelectHelper) -> NSArray { if noteOutlineView.isEqual(to: anOutlineView) { let count = self.noteOutlineView.numberOfRows let texts = NSMutableArray(capacity: count) for i in 0 ..< count { let item = self.noteOutlineView.item(atRow: i) let string = (item as? CPDFAnnotation)?.string() ?? "" texts.add(string) } return texts } return [] } } // MARK: - Other extension KMLeftSideViewController { func fileNameWithSelectedPages(_ itemIndexes: IndexSet) -> String { var pagesName = "" if (itemIndexes.count > 1) { pagesName.append(" pages") } else { pagesName.append(" page") } let docmentName = self.pdfDocument()?.documentURL.deletingPathExtension().lastPathComponent ?? "" let tFileName = String(format: "%@ %@", pagesName,KMTools.parseIndexSet(indexSet: itemIndexes)) return String(format: "%@%@", docmentName,tFileName) } func draggedFileName(for page: CPDFPage) -> String { let pageIndex = "\(page.pageIndex() + 1)" var fileName = "" if let doc = self.view.window?.windowController?.document as? NSDocument { fileName = doc.fileURL?.deletingPathExtension().lastPathComponent ?? (MainBundle.nameSpaceName ?? "") } return "\(fileName)-Page \(pageIndex)" } func updateTableFont() { } } // MARK: - ComponentGroupDelegate extension KMLeftSideViewController: ComponentGroupDelegate { func componentGroupDidDismiss(group: ComponentGroup?) { if group == searchGroupView { searchGroupTarget?.properties.state = .normal searchGroupTarget?.reloadData() searchGroupTarget = nil } else if group == moreGroupView { headerView.moreButton.properties.state = .normal headerView.moreButton.reloadData() } } func componentGroupDidSelect(group: ComponentGroup?, menuItemProperty: ComponentMenuitemProperty?) { if group == searchGroupView { guard let menuI = menuItemProperty else { return } let idx = group?.menuItemArr.firstIndex(of: menuI) if idx == 0 { let key = KMNSearchKey.wholeWords.annotation let value = KMDataManager.ud_bool(forKey: key) KMDataManager.ud_set(!value, forKey: key) } else if idx == 1 { let key = KMNSearchKey.caseSensitive.annotation let value = KMDataManager.ud_bool(forKey: key) KMDataManager.ud_set(!value, forKey: key) } } else { if menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_Expand { var isExpandAllItem = true for secM in self.annoListModel?.datas ?? [] { if secM.isExpand == false { isExpandAllItem = false break } } if isExpandAllItem == true { note_foldAllComments(nil) } else { note_expandAllComments(nil) } } else if menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_SortPage { self.noteSortType = .page if let data = self.listView?.document?.documentURL.path { KMDataManager.udExtension_set(self.noteSortType.rawValue, forKey: Self.Key.noteSortTypeKey + data) } if self.noteSearchMode { self.reloadNoteForSearchMode() } else { self.reloadAnnotation() } } else if menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_SortAscTime { self.noteSortType = .time self.isAscendSort = true if let data = self.listView?.document?.documentURL.path { KMDataManager.udExtension_set(self.noteSortType.rawValue, forKey: Self.Key.noteSortTypeKey + data) } if self.noteSearchMode { self.reloadNoteForSearchMode() } else { self.reloadAnnotation() } } else if menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_SortDesTime { self.noteSortType = .time self.isAscendSort = false if let data = self.listView?.document?.documentURL.path { KMDataManager.udExtension_set(self.noteSortType.rawValue, forKey: Self.Key.noteSortTypeKey + data) } if self.noteSearchMode { self.reloadNoteForSearchMode() } else { self.reloadAnnotation() } } else if menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_Improt { importNotes(nil) } else if menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_Export { exportNotes(nil) } else if menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_RemoveAll { removeAllAnnotations(nil) } else if menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_DeleteRep { removeAllReplyAnnotations(nil) } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_EditNote) { if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] { if models.first != nil { let model = models.first if model != nil { updateExpand(model: model!, isExpand: true, item: currentItem) noteReplyHanddler.editAnnotation(annotationModel: model!) DispatchQueue.main.async { let row = self.noteOutlineView.row(forItem: model?.footerModel) self.noteOutlineView.scrollRowToVisible(row) } self.noteOutlineView.reloadData() } } } else if let anModels = menuItemProperty?.representedObject as? [KMBotaAnnotationReplyModel] { if anModels.first != nil { let model = anModels.first noteReplyHanddler.editReplyAnnotation(replyModel: model) DispatchQueue.main.async { let row = self.noteOutlineView.row(forItem: model?.annoModel?.footerModel) self.noteOutlineView.scrollRowToVisible(row) } self.noteOutlineView.reloadData() } } } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_AddRep) { if let anModels = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] { if anModels.first != nil { if let model = anModels.first { self.updateExpand(model: model, isExpand: true, item: currentItem) if let footMode = model.footerModel { let row = self.noteOutlineView.row(forItem: footMode) let rowView = self.noteOutlineView.rowView(atRow: row, makeIfNecessary: true) if(row != nil) { for subview in rowView!.subviews { if let cell = subview as? KMNoteFooterCellView { cell.inputTextF.becomeFirstResponder() } } } } } } } } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_AddMark) { if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] { if models.first != nil { updateMarkState(model: models.first!) } } } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_RepStateNone) { if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] { if models.first != nil { updateReplaState(model:models.first! , state:.none) } } } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_RepStateAccepted) { if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] { if models.first != nil { updateReplaState(model:models.first! , state:.accepted) } } } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_RepStateRejected) { if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] { if models.first != nil { updateReplaState(model:models.first! , state:.rejected) } } } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_RepStateCancelled) { if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] { if models.first != nil { updateReplaState(model:models.first! , state:.canceled) } } } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_RepStateCompleted) { if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] { if models.first != nil { updateReplaState(model:models.first! , state:.completed) } } } else if (menuItemProperty?.identifier == PDFViewMenuIdentifier_Normal_ShowPopUI) { if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] { if models.first != nil { let model = models.first updateExpand(model: model!, isExpand: !(model?.isExpand == true), item: currentItem) } } } else if (menuItemProperty?.identifier == PDFViewMenuIdentifier_Normal_Copy) { if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationReplyModel] { if models.first != nil { let model = models.first let an = model?.anno var copyText:String = "" if an?.isKind(of: CPDFMarkupAnnotation.self) == true { if let markupAn = an as? CPDFMarkupAnnotation { copyText = markupAn.markupContent() } } else if an?.isKind(of: CPDFFreeTextAnnotation.self) == true || an?.isKind(of: CPDFTextAnnotation.self) == true { copyText = an?.contents ?? "" let pboard = NSPasteboard.general if copyText.isEmpty == false { pboard.clearContents() pboard.writeObjects([copyText as NSPasteboardWriting]) } } } } } else if (menuItemProperty?.identifier == PDFViewMenuIdentifier_Normal_Delete) { if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] { if models.first != nil { let model = models.first self.outlineView(self.noteOutlineView, deleteItems:[model!]) } } } else if (menuItemProperty?.identifier == PDFViewMenuIdentifier_Normal_CopyText) { if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationReplyModel] { if models.first != nil { let model = models.first var copyText:String = "" copyText = model?.replyAnno?.contents ?? "" let pboard = NSPasteboard.general if copyText.isEmpty == false { pboard.clearContents() pboard.writeObjects([copyText as NSPasteboardWriting]) } } } } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_DeleteMuteRep) { if let models = menuItemProperty?.representedObject as? [Any] { for i in 0 ..< models.count { let model = models[i] if let replyModel = model as? KMBotaAnnotationReplyModel { noteReplyHanddler.removeReplyAnnotation(replyModel.replyAnno) replyModel.annoModel?.replyAnnos.removeObject(replyModel) } else if let anModel = model as? KMBotaAnnotationModel { self.outlineView(self.noteOutlineView, deleteItems:[anModel]) } } self.noteOutlineView.reloadData() } } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_DeleteSignRep) { if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationReplyModel] { if models.first != nil { let model = models.first if model != nil { noteReplyHanddler.removeReplyAnnotation(model?.replyAnno) model?.annoModel?.replyAnnos.removeObject(model!) } self.noteOutlineView.reloadData() } } } } } }