// // KMBotaSearchViewController.swift // PDF Reader Pro // // Created by tangchao on 2023/11/16. // import Cocoa import KMComponentLibrary extension KMNSearchKey.wholeWords { static let botaSearch = "BotaSearchWholeWordsKey" } extension KMNSearchKey.caseSensitive { static let botaSearch = "BotaSearchCaseSensitiveKey" } enum KMNBotaSearchType: Int { case none = 0 case search = 1 case replace = 2 } @objc protocol KMBotaSearchViewControllerDelegate: NSObjectProtocol { @objc optional func switchSearchPopWindow(controller: KMBotaSearchViewController) @objc optional func searchTypeDidChange(controller: KMBotaSearchViewController) } class KMBotaSearchViewController: KMNBotaBaseViewController { @IBOutlet weak var topView: NSBox! @IBOutlet weak var topHeightConst: NSLayoutConstraint! var contentView: NSView? { didSet { if let view = self.contentView { self.box.contentView = view } } } @IBOutlet weak var emptyBox: NSBox! @IBOutlet weak var box: NSBox! @IBOutlet weak var emptySearchLabel: NSTextField! // @IBOutlet weak var searchLabel: NSTextField! // @IBOutlet weak var searchTips: NSTextField! // @IBOutlet weak var pageLabel: NSTextField! @IBOutlet var scrollView: NSScrollView! @IBOutlet weak var tableView: KMBotaTableView! private lazy var topContentView_: KMNBotaSearchTopView? = { let view = KMNBotaSearchTopView.createFromNib() return view }() private var emptyView_: ComponentEmpty = { let view = ComponentEmpty() view.properties = ComponentEmptyProperty(emptyType: .noSearch, state: .normal, text: KMLocalizedString("No Results"), subText: KMLocalizedString("")) return view }() var previousButton: ComponentButton? { get { return topContentView_?.previousButton } } var nextButton: ComponentButton? { get { return topContentView_?.nextButton } } var replaceAllButton: ComponentButton? { get { return topContentView_?.replaceAllButton } } var replaceButton: ComponentButton? { get { return topContentView_?.replaceButton } } private var menuGroupView_: ComponentGroup? private var menuSections_: [CPDFSelection] = [] private var menuType_: CAnnotationType = .circle var handdler = KMNSearchHanddler() weak var delegate: KMBotaSearchViewControllerDelegate? var searchResults : [KMBotaSearchSectionModel] = [] { didSet { self.updataLeftSideFindView() } } private var datas: [Any] = [] private var searchResultIndex_: Int = -1 private var currentSel: CPDFSelection? private var currentModel_: KMSearchMode? private var finding_ = false override func loadView() { super.loadView() topView.borderWidth = 0 topView.fillColor = .clear topView.contentView = topContentView_ topContentView_?.itemClick = { [unowned self] idx, params in if idx == KMNBotaSearchTopItemKey.search.rawValue { if let data = params.first as? ComponentButton { showSearchGroupView(sender: data) } } else if idx == KMNBotaSearchTopItemKey.replace.rawValue { if handdler.type == .search { showReplaceView() } else { showSearchView() } } else if idx == KMNBotaSearchTopItemKey.switch.rawValue { delegate?.switchSearchPopWindow?(controller: self) } else if idx == KMNBotaSearchTopItemKey.previous.rawValue { if handdler.isEditing() == false { tableViewMoveUp(tableView) } else { tableViewMoveUp(tableView) _previousAction(NSButton()) } } else if idx == KMNBotaSearchTopItemKey.next.rawValue { if handdler.isEditing() == false { tableViewMoveDown(tableView) } else { tableViewMoveDown(tableView) _nextAction(NSButton()) } } else if idx == KMNBotaSearchTopItemKey.replaceText.rawValue { _replaceAction(NSButton()) } else if idx == KMNBotaSearchTopItemKey.replaceAllText.rawValue { _replaceAllAction(NSButton()) } } showSearchView() topContentView_?.valueDidChange = { [unowned self] sender, info in guard let string = info?[.newKey] as? String else { return } updateButtonStatus() currentSel = nil currentModel_ = nil self.search(keyword: string) { [unowned self] results in } } emptyBox.contentView?.addSubview(emptyView_) emptyView_.km_add_top_constraint(constant: 232) emptyView_.km_add_bottom_constraint() emptyView_.km_add_leading_constraint() emptyView_.km_add_trailing_constraint() self.emptySearchLabel.stringValue = KMLocalizedString("") self.emptySearchLabel.textColor = KMAppearance.Layout.h1Color() contentView = tableView.enclosingScrollView tableView.menuClickedAction = { [unowned self] point in let idxs = self.tableView.selectedRowIndexes.count let convertP = self.tableView.convert(point, from: nil) let row = self.tableView.row(at: convertP) if row == -1 { return NSMenu() } let hideNotes = handdler.hideNotes() let allowsNotes = handdler.allowsNotes() if hideNotes || allowsNotes == false { return NSMenu() } guard let model = self.datas[row] as? KMSearchMode else { return NSMenu() } var viewHeight: CGFloat = 0 let items: [String] = ["Add New Circle", "Add New Rectangle", "Add New Highlight", "Add New Underline", "Add New Strikethrough"] var menuItemArr: [ComponentMenuitemProperty] = [] for value in items { let properties_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(multipleSelect: false, itemSelected: false, isDisabled: false, keyEquivalent: nil, text: KMLocalizedString(value), identifier: value) menuItemArr.append(properties_Menuitem) viewHeight += 36 } self.menuGroupView_ = ComponentGroup.createFromNib(in: ComponentLibrary.shared.componentBundle()) self.menuGroupView_?.clickedAutoHide = false self.menuGroupView_?.groupDelegate = self self.menuGroupView_?.frame = CGRectMake(0, 0, 180, viewHeight) self.menuGroupView_?.updateGroupInfo(menuItemArr) self.menuSections_ = [model.selection] self.menuGroupView_?.showWithPoint(CGPoint(x: point.x, y: point.y - viewHeight), relativeTo: self.tableView) return NSMenu() } } override func viewDidLoad() { super.viewDidLoad() tableView.delegate = self tableView.dataSource = self tableView.botaDelegate = self tableView.hasImageToolTips = true updateButtonStatus() } override func viewDidAppear() { super.viewDidAppear() view.window?.makeFirstResponder(topContentView_?.searchInput) } override func updateUILanguage() { super.updateUILanguage() KMMainThreadExecute { self.topContentView_?.resultLabel.stringValue = KMLocalizedString("Result:") + " " + "\(self.handdler.searchResults.count)" self.tableView.reloadData() } } override func updateUIThemeColor() { super.updateUIThemeColor() KMMainThreadExecute { self.view.wantsLayer = true let color = KMNColorTools.colorBg_layoutMiddle() self.view.layer?.backgroundColor = color.cgColor self.tableView.backgroundColor = color self.topContentView_?.replaceInput.properties.leftIcon = NSImage(named: "KMImagenameBotaSearchInputPrefiex") self.topContentView_?.replaceInput.reloadData() self.topContentView_?.resultLabel.textColor = KMNColorTools.colorText_3() self.topContentView_?.bottomLine.layer?.backgroundColor = KMNColorTools.colorPrimary_border1().cgColor self.tableView.reloadData() } } func showSearchView() { handdler.type = .search topContentView_?.showSearch() topHeightConst.constant = topContentView_?.fetchContentHeight(type: handdler.type, hasResult: handdler.searchResults.isEmpty == false) ?? 0 delegate?.searchTypeDidChange?(controller: self) } func showReplaceView() { if IAPProductsManager.default().isAvailableAllFunction() == false { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return } handdler.type = .replace topContentView_?.showReplace() topHeightConst.constant = topContentView_?.fetchContentHeight(type: handdler.type, hasResult: handdler.searchResults.isEmpty == false) ?? 0 delegate?.searchTypeDidChange?(controller: self) if let keyword = topContentView_?.inputValue, keyword.isEmpty == false { DispatchQueue.main.async { self.search(keyword: keyword) { models in } } } } func showResult() { topContentView_?.showResult(type: handdler.type) var i = 0 var sectionCount = 0 for model in self.datas { if i > self.tableView.selectedRow { break } if let data = model as? KMBotaSearchSectionModel { sectionCount += 1 } i += 1 } let theIdx = max(i-sectionCount, 0) topContentView_?.resultLabel.stringValue = KMLocalizedString("Result:") + " \(theIdx)/" + "\(self.handdler.searchResults.count)" topHeightConst.constant = topContentView_?.fetchContentHeight(type: handdler.type, hasResult: searchResults.isEmpty == false) ?? 0 } func update(keyborad: String?, replaceKey: String?, results: [KMBotaSearchSectionModel]) { topContentView_?.inputValue = keyborad topContentView_?.replaceText = replaceKey handdler.searchKey = keyborad handdler.replaceKey = replaceKey if let data = keyborad, data.isEmpty == false { self.search(keyword: data) { models in} } updateButtonStatus() } func search(keyword: String, callback: @escaping (([KMBotaSearchSectionModel]?) -> Void)) { let isCase = KMDataManager.ud_bool(forKey: KMNSearchKey.caseSensitive.botaSearch) let isWholeWord = KMDataManager.ud_bool(forKey: KMNSearchKey.wholeWords.botaSearch) // handdler.search(keyword: keyword, isCase: isCase, isWholeWord: isWholeWord, callback: callback) let isEditing = self.handdler.pdfView?.isEditing() ?? false if isEditing == false { if self.finding_ { return } self.finding_ = true self.handdler.search(keyword: keyword, isCase: isCase, isWholeWord: isWholeWord, callback: { [weak self] datas in self?.finding_ = false self?.searchResults = datas ?? [] let sels = self?.handdler.searchResults if let sel = sels?.first?.selection { self?.handdler.showIdx = 0 self?.handdler.showSelection(sel) } // self?.searchResults.first?.isExpand = true for model in self?.searchResults ?? [] { model.isExpand = true } self?.currentModel_ = datas?.first?.items.first self?.showResult() self?.tableView.reloadData() }) } else { if self.finding_ { return } self.finding_ = true let searchS = keyword let opt = self.fetchSearchOptions() DispatchQueue.global().async { let datas = self.handdler.pdfView?.document.findEditAllPageString(searchS, with: opt) ?? [] DispatchQueue.main.async { self.finding_ = false self.currentSel = datas.first?.first if let sel = self.currentSel { self.handdler.showSelection(sel) } self.updateResults(results: datas) } } } } private func fetchSearchOptions() -> CPDFSearchOptions { var opt = CPDFSearchOptions() let isCase = KMDataManager.ud_bool(forKey: KMNSearchKey.caseSensitive.botaSearch) let isWholeWord = KMDataManager.ud_bool(forKey: KMNSearchKey.wholeWords.botaSearch) if isCase { opt.insert(.caseSensitive) } if isWholeWord { opt.insert(.matchWholeWord) } return opt } func updateButtonStatus() { let value = topContentView_?.inputValue ?? "" if value.isEmpty { previousButton?.properties.isDisabled = true previousButton?.reloadData() nextButton?.properties.isDisabled = true nextButton?.reloadData() replaceButton?.properties.isDisabled = true replaceButton?.reloadData() replaceAllButton?.properties.isDisabled = true replaceAllButton?.reloadData() } else { previousButton?.properties.isDisabled = false previousButton?.reloadData() nextButton?.properties.isDisabled = false nextButton?.reloadData() replaceButton?.properties.isDisabled = false replaceButton?.reloadData() replaceAllButton?.properties.isDisabled = false replaceAllButton?.reloadData() } } private func _showNoResultsAlert() { _ = _showAlert(style: .critical, message: KMLocalizedString("No related content found, please change keyword."), info: "", buttons: [KMLocalizedString("OK", comment: "")]) } private func _showAlert(style: NSAlert.Style, message: String, info: String, buttons: [String]) -> NSApplication.ModalResponse { let alert = NSAlert() alert.alertStyle = style alert.messageText = message alert.informativeText = info for button in buttons { alert.addButton(withTitle: button) } return alert.runModal() } private func updateResults(results: [[CPDFSelection]]?) { let theKeyword = handdler.searchKey?.decomposedStringWithCompatibilityMapping ?? "" var datas: [KMBotaSearchSectionModel] = [] var models: [KMSearchMode] = [] for sels in results ?? [] { let sectionM = KMBotaSearchSectionModel() sectionM.pageIndex = Int(sels.first?.page.pageIndex() ?? 0) for sel in sels { let mode : KMSearchMode = KMSearchMode() mode.selection = sel mode.attributedString = KMOCToolClass.getAttributedString(selection: sel, keyword: theKeyword) mode.selectionPageIndex = handdler.pdfDocument()?.index(for: sel.page) ?? 0 sectionM.items.append(mode) sel.setColor(NSColor(red: 236/255.0, green: 241/255.0, blue: 83/255.0, alpha: 0.5)) models.append(mode) } datas.append(sectionM) } handdler.searchResults = models handdler.searchSectionResults = datas self.searchResults = datas // self.searchResults.first?.isExpand = true for model in self.searchResults ?? [] { model.isExpand = true } self.currentModel_ = datas.first?.items.first self.showResult() self.tableView.reloadData() } @objc private func _previousAction(_ sender: NSButton) { let isEditing = self.handdler.pdfView?.isEditing() ?? false if isEditing == false { guard let model = self.handdler.searchResults.safe_element(for: self.handdler.showIdx-1) as? KMSearchMode else { return } self.handdler.showIdx -= 1 self.handdler.showSelection(model.selection) } else { if let _ = self.currentSel { self.currentSel = self.handdler.pdfView?.document.findForwardEditText() if let sel = self.currentSel { self.handdler.showSelection(sel) } else { _showNoResultsAlert() return } } else { if self.finding_ { return } self.finding_ = true let searchS = self.topContentView_?.inputValue ?? "" let opt = self.fetchSearchOptions() // self._beginLoading() DispatchQueue.global().async { let datas = self.handdler.pdfView?.document.startFindEditText(from: nil, with: searchS, options: opt) DispatchQueue.main.async { // self._endLoading() self.finding_ = false let sel = datas?.first?.first self.currentSel = sel self.handdler.showSelection(sel) self.updateResults(results: datas) } } } } } @objc private func _nextAction(_ sender: NSButton) { let isEditing = self.handdler.pdfView?.isEditing() ?? false if isEditing == false { guard let model = self.handdler.searchResults.safe_element(for: self.handdler.showIdx+1) as? KMSearchMode else { return } self.handdler.showIdx += 1 self.handdler.showSelection(model.selection) } else { if let _ = self.currentSel { self.currentSel = self.handdler.pdfView?.document.findBackwordEditText() if let sel = self.currentSel { self.handdler.showSelection(sel) } else { _showNoResultsAlert() } } else { if self.finding_ { return } self.finding_ = true let searchS = self.topContentView_?.inputValue ?? "" let opt = self.fetchSearchOptions() // self._beginLoading() DispatchQueue.global().async { let datas = self.handdler.pdfView?.document.startFindEditText(from: nil, with: searchS, options: opt) DispatchQueue.main.async { // self._endLoading() self.finding_ = false let sel = datas?.first?.first self.currentSel = sel self.handdler.showSelection(sel) } } } } } @objc private func _replaceAction(_ sender: NSButton) { let isEditing = self.handdler.pdfView?.isEditing() ?? false if isEditing == false { NSSound.beep() return } if let sel = self.currentSel { let searchS = self.topContentView_?.inputValue ?? "" let replaceS = self.topContentView_?.replaceText ?? "" let success = self.handdler.replace(searchS: searchS, replaceS: replaceS, sel: sel) { [weak self] newSel in self?.handdler.showSelection(newSel) if let data = newSel { self?.currentModel_?.selection = data // self?.currentModel_?.attributedString = KMOCToolClass.getAttributedString(selection: data, keyword: replaceS) self?.tableView.reloadData() } } } else { // 先查找 if self.finding_ { return } self.finding_ = true let searchS = self.topContentView_?.inputValue ?? "" let opt = self.fetchSearchOptions() DispatchQueue.global().async { let datas = self.handdler.pdfView?.document.startFindEditText(from: nil, with: searchS, options: opt) DispatchQueue.main.async { self.finding_ = false let sel = datas?.first?.first self.currentSel = sel self.handdler.showSelection(sel) self.updateResults(results: datas) } } } } @objc private func _replaceAllAction(_ sender: NSButton) { let isEditing = self.handdler.pdfView?.isEditing() ?? false if isEditing == false { NSSound.beep() return } let datas = self.handdler.pdfView?.document.findEditSelections() ?? [] if datas.isEmpty { _showNoResultsAlert() return } if self.finding_ { return } self.finding_ = true let searchS = self.topContentView_?.inputValue ?? "" let replaceS = self.topContentView_?.replaceText ?? "" // self._beginLoading() DispatchQueue.global().async { self.handdler.pdfView?.document.replaceAllEditText(with: searchS, toReplace: replaceS) self.currentSel = nil DispatchQueue.main.async { // self._endLoading() self.finding_ = false self.handdler.pdfView?.setHighlightedSelection(nil, animated: false) self.handdler.pdfView?.setNeedsDisplayForVisiblePages() } } } // MARK: - Group View func showSearchGroupView(sender: ComponentButton) { var viewHeight: CGFloat = 8 var menuItemArr: [ComponentMenuitemProperty] = [] let titles = ["Search", "Find and Replace", "", "Whole Words", "Case Sensitive"] for i in titles { if i.isEmpty { let menuI = ComponentMenuitemProperty.divider() menuItemArr.append(menuI) viewHeight += 8 } else { let menuI = ComponentMenuitemProperty(text: KMLocalizedString(i)) menuItemArr.append(menuI) viewHeight += 36 } } if handdler.type == .search { menuItemArr.first?.righticon = NSImage(named: "KMNImageNameMenuSelect") } else if handdler.type == .replace { let info = menuItemArr.safe_element(for: 1) as? ComponentMenuitemProperty info?.righticon = NSImage(named: "KMNImageNameMenuSelect") } if let info = menuItemArr.safe_element(for: 3) as? ComponentMenuitemProperty { if KMDataManager.ud_bool(forKey: KMNSearchKey.wholeWords.botaSearch) { info.righticon = NSImage(named: "KMNImageNameMenuSelect") } } if let info = menuItemArr.last { if KMDataManager.ud_bool(forKey: KMNSearchKey.caseSensitive.botaSearch) { 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 } @objc func goToSelectedFindResults(_ sender: AnyObject?) { // guard let olView = sender as? NSTableView, olView.clickedRow != -1 else { // NSSound.beep() // return // } // self.updateFindResultHighlightsForDirection(.directSelection) } @objc func addAnnotationsForSelections(_ sender: NSMenuItem) { let listView = handdler.pdfView as? CPDFListView for selection in menuSections_ { // if menuType_ == .circle { listView?.addAnnotation(with: menuType_, selection: selection, page: selection.page, bounds: selection.bounds) // } } } func updataLeftSideFindView() { if (self.searchResults.count > 0) { self.emptyBox.isHidden = true } else { self.emptyBox.isHidden = false } } func updateFindResultHighlightsForDirection(_ direction: NSWindow.SelectionDirection) { var findResults: [KMSearchMode] = handdler.searchResults if (findResults.count == 0) { handdler.showSelection(nil) } else { if direction == .directSelection { self.searchResultIndex_ = 0 } else if (direction == .selectingNext) { self.searchResultIndex_ += 1 if self.searchResultIndex_ >= findResults.count { self.searchResultIndex_ = 0 } } else if (direction == .selectingPrevious) { self.searchResultIndex_ -= 1 if self.searchResultIndex_ < 0 { self.searchResultIndex_ = findResults.count-1 } } let currentSel = findResults[self.searchResultIndex_].selection if currentSel.hasCharacters() { let page = currentSel.safeFirstPage() var rect = NSZeroRect for model in findResults { if let data = page, model.selection.pages().contains(data) { rect = NSUnionRect(rect, model.selection.bounds(for: data)) } } let FIND_RESULT_MARGIN = 50.0 rect = NSIntersectionRect(NSInsetRect(rect, -FIND_RESULT_MARGIN, -FIND_RESULT_MARGIN), page?.bounds(for: .cropBox) ?? .zero) handdler.pdfView?.go(to: page) handdler.pdfView?.go(to: rect, on: page) } if currentSel.hasCharacters() { let bColor = NSColor(red: 236/255.0, green: 241/255.0, blue: 83/255.0, alpha: 0.5) let color = NSColor(red: 219/255.0, green: 220/255.0, blue: 3/255.0, alpha: 0.5) handdler.pdfView?.setHighlight(currentSel, forBorderColor: .clear, fill: color, animated: true) handdler.pdfView?.go(to: currentSel, animated: true) handdler.pdfView?.setCurrentSelection(currentSel, animate: true) } // let mode = self.listView?.toolMode ?? .none // if mode == .moveToolMode || mode == .magnifyToolMode || mode == .selectToolMode { // self.listView?.setCurrentSelection(nil, animate: false) // } } } } // MARK: - NSTableViewDelegate, NSTableViewDataSource extension KMBotaSearchViewController: NSTableViewDelegate, NSTableViewDataSource { func numberOfRows(in tableView: NSTableView) -> Int { var datas: [Any] = [] for sectionM in self.handdler.searchSectionResults { if sectionM.items.count > 0 { datas.append(sectionM) if sectionM.isExpand == false { continue } for item in sectionM.items { datas.append(item) } } } self.datas = datas return datas.count } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { let model = self.datas[row] if let data = model as? KMBotaSearchSectionModel { var cell = tableView.makeView(withIdentifier: KMSectionCellView.km_identifier, owner: nil) as? KMSectionCellView if cell == nil { cell = KMSectionCellView.createFromNib() } cell?.titleLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-s-medium") cell?.countLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-s-regular") cell?.titleLabel.textColor = KMNColorTools.colorText_1() cell?.countLabel.textColor = KMNColorTools.colorText_3() cell?.titleLabel.isEditable = false cell?.isExpand = data.isExpand let pageIndex = data.pageIndex cell?.titleLabel.stringValue = KMLocalizedString("Page") + " \(pageIndex + 1)" cell?.countLabel.stringValue = "\(data.itemCount)" cell?.bottomLine.wantsLayer = true cell?.bottomLine.layer?.backgroundColor = KMNColorTools.colorBorder_divider().cgColor cell?.itemClick = { [weak self] idx, _ in if idx == 1 { // 收取 & 展开 data.isExpand = !data.isExpand self?.tableView.reloadData() } } return cell } if let data = model as? KMSearchMode { var cell = tableView.makeView(withIdentifier: KMNBotaSearchCellView.km_identifier, owner: self) as? KMNBotaSearchCellView if cell == nil { cell = KMNBotaSearchCellView() } cell?.label.attributedStringValue = data.attributedString return cell } return nil } func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { let model = datas[row] if model is KMBotaSearchSectionModel { return 40.0 } if let data = model as? KMSearchMode { let width = NSWidth(self.view.frame) let rect = data.attributedString.boundingRect(with: .init(width: width-24*2, height: CGFLOAT_MAX), options: [.usesLineFragmentOrigin, .usesLineFragmentOrigin]) return rect.size.height + 12 * 2 } return 40.0 } func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? { let model = datas[row] if model is KMBotaSearchSectionModel { let rowView = KMTableRowView() rowView.selectionBackgroundColorBlock = { return .clear } return rowView } let rowView = KMTableRowView() rowView.selectionRadius = ComponentLibrary.shared.getComponentValueFromKey("radius/xs") as? CGFloat ?? 4 rowView.selectionBackgroundColorBlock = { return KMNColorTools.colorPrimary_bgOpacityDark() } rowView.selectionInset = .init(top: 8, left: 16, bottom: 8, right: 16) return rowView } func tableViewSelectionDidChange(_ notification: Notification) { let row = self.tableView.selectedRow if row < 0 || row >= datas.count { return } guard let model = datas[row] as? KMSearchMode else { return } handdler.showSelection(model.selection) currentSel = model.selection currentModel_ = model var i = 0 var sectionCount = 0 for model in self.datas { if i > self.tableView.selectedRow { break } if let data = model as? KMBotaSearchSectionModel { sectionCount += 1 } i += 1 } let theIdx = max(i-sectionCount, 0) topContentView_?.resultLabel.stringValue = KMLocalizedString("Result:") + " \(theIdx)/" + "\(self.handdler.searchResults.count)" } func tableView(_ aTableView: NSTableView, copyRowsWithIndexes rowIndexes: IndexSet) { if IAPProductsManager.default().isAvailableAllFunction() == false { KMPurchaseCompareWindowController.sharedInstance().showWindow(nil) return } var string = "" for idx in rowIndexes { if idx < 0 || idx >= datas.count { continue } guard let model = datas[idx] as? KMSearchMode else { continue } let match = model.selection string.append("* ") // [string appendFormat:NSLocalizedString(@"Page %@", @""), [match firstPageLabel]]; string = string.appendingFormat(KMLocalizedString("Page %@"), "\(match.safeFirstPage()?.pageIndex() ?? 0)") // [string appendFormat:@"", [[match contextString] string]]; string = string.appendingFormat(": %@\n", match.string() ?? "") } let pboard = NSPasteboard.general pboard.clearContents() pboard.writeObjects([string as NSPasteboardWriting]) } } // MARK: - KMBotaTableViewDelegate extension KMBotaSearchViewController: KMBotaTableViewDelegate { func tableView(_ aTableView: NSTableView, canCopyRowsWithIndexes rowIndexes: IndexSet) -> Bool { return rowIndexes.count > 0 } func tableViewMoveRight(_ aTableView: NSTableView) { updateFindResultHighlightsForDirection(.selectingNext) } func tableViewMoveUp(_ aTableView: NSTableView) { var theRow = self.tableView.selectedRow-1 if theRow < 0 || theRow >= self.datas.count { return } let model = self.datas[theRow] if let _ = model as? KMBotaSearchSectionModel { theRow -= 1 } self.tableView.km_safe_selectRowIndexes(.init(integer: theRow), byExtendingSelection: false) self.tableView.scrollRowToVisible(self.tableView.selectedRow) } func tableViewMoveDown(_ aTableView: NSTableView) { var theRow = self.tableView.selectedRow+1 if theRow < 0 || theRow >= self.datas.count { return } let model = self.datas[theRow] if let _ = model as? KMBotaSearchSectionModel { theRow += 1 } self.tableView.km_safe_selectRowIndexes(.init(integer: theRow), byExtendingSelection: false) self.tableView.scrollRowToVisible(self.tableView.selectedRow) } func tableView(_ aTableView: NSTableView, imageContextForRow rowIndex: Int) -> AnyObject? { if rowIndex < 0 || rowIndex >= datas.count { return nil } guard let model = datas[rowIndex] as? KMSearchMode else { return -1 as AnyObject } let selection = model.selection let x = selection.bounds.origin.x + NSWidth(selection.bounds) * 0.5 let y = selection.bounds.origin.y + NSHeight(selection.bounds) * 0.5 let point = NSPoint(x: x, y: y) return CPDFDestination(document: handdler.pdfDocument(), pageIndex: Int(model.selectionPageIndex), at: point, zoom: handdler.scaleFactor() ?? 0) } } //MARK: - ComponentGroupDelegate extension KMBotaSearchViewController: ComponentGroupDelegate { func componentGroupDidDismiss(group: ComponentGroup?) { if group == menuGroupView_ { group?.removeFromSuperview() menuGroupView_ = nil } else if group == searchGroupView { searchGroupTarget?.properties.state = .normal searchGroupTarget?.reloadData() searchGroupTarget = nil } } func componentGroupDidSelect(group: ComponentGroup?, menuItemProperty: ComponentMenuitemProperty?) { if group == menuGroupView_ { if let selItem = menuItemProperty { let index = group?.menuItemArr.firstIndex(of: selItem) if index == 0 { menuType_ = .circle addAnnotationsForSelections(NSMenuItem()) } else if index == 1 { menuType_ = .square addAnnotationsForSelections(NSMenuItem()) } else if index == 2 { menuType_ = .highlight addAnnotationsForSelections(NSMenuItem()) } else if index == 3 { menuType_ = .underline addAnnotationsForSelections(NSMenuItem()) } else if index == 4 { menuType_ = .strikeOut addAnnotationsForSelections(NSMenuItem()) } group?.removeFromSuperview() } } else if group == searchGroupView { guard let menuI = menuItemProperty else { return } let idx = group?.menuItemArr.firstIndex(of: menuI) if idx == 0 { // search showSearchView() } else if idx == 1 { // replace showReplaceView() } else if idx == 3 { let key = KMNSearchKey.wholeWords.botaSearch let value = KMDataManager.ud_bool(forKey: key) KMDataManager.ud_set(!value, forKey: key) currentSel = nil currentModel_ = nil if let data = topContentView_?.inputValue, data.isEmpty { search(keyword: data) { [weak self] results in } } } else if idx == 4 { let key = KMNSearchKey.caseSensitive.botaSearch let value = KMDataManager.ud_bool(forKey: key) KMDataManager.ud_set(!value, forKey: key) currentSel = nil currentModel_ = nil if let data = topContentView_?.inputValue, data.isEmpty { search(keyword: data) { [weak self] results in } } } } } }