KMBotaSearchViewController.swift 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923
  1. //
  2. // KMBotaSearchViewController.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by tangchao on 2023/11/16.
  6. //
  7. import Cocoa
  8. import KMComponentLibrary
  9. extension KMNSearchKey.wholeWords {
  10. static let botaSearch = "BotaSearchWholeWordsKey"
  11. }
  12. extension KMNSearchKey.caseSensitive {
  13. static let botaSearch = "BotaSearchCaseSensitiveKey"
  14. }
  15. enum KMNBotaSearchType: Int {
  16. case none = 0
  17. case search = 1
  18. case replace = 2
  19. }
  20. @objc protocol KMBotaSearchViewControllerDelegate: NSObjectProtocol {
  21. @objc optional func switchSearchPopWindow(controller: KMBotaSearchViewController)
  22. @objc optional func searchTypeDidChange(controller: KMBotaSearchViewController)
  23. }
  24. class KMBotaSearchViewController: KMNBotaBaseViewController {
  25. @IBOutlet weak var topView: NSBox!
  26. @IBOutlet weak var topHeightConst: NSLayoutConstraint!
  27. var contentView: NSView? {
  28. didSet {
  29. if let view = self.contentView {
  30. self.box.contentView = view
  31. }
  32. }
  33. }
  34. @IBOutlet weak var emptyBox: NSBox!
  35. @IBOutlet weak var box: NSBox!
  36. @IBOutlet weak var emptySearchLabel: NSTextField!
  37. // @IBOutlet weak var searchLabel: NSTextField!
  38. // @IBOutlet weak var searchTips: NSTextField!
  39. // @IBOutlet weak var pageLabel: NSTextField!
  40. @IBOutlet var scrollView: NSScrollView!
  41. @IBOutlet weak var tableView: KMBotaTableView!
  42. private lazy var topContentView_: KMNBotaSearchTopView? = {
  43. let view = KMNBotaSearchTopView.createFromNib()
  44. return view
  45. }()
  46. private var emptyView_: ComponentEmpty = {
  47. let view = ComponentEmpty()
  48. view.properties = ComponentEmptyProperty(emptyType: .noSearch, state: .normal, text: KMLocalizedString("No Results"), subText: KMLocalizedString(""))
  49. return view
  50. }()
  51. var previousButton: ComponentButton? {
  52. get {
  53. return topContentView_?.previousButton
  54. }
  55. }
  56. var nextButton: ComponentButton? {
  57. get {
  58. return topContentView_?.nextButton
  59. }
  60. }
  61. var replaceAllButton: ComponentButton? {
  62. get {
  63. return topContentView_?.replaceAllButton
  64. }
  65. }
  66. var replaceButton: ComponentButton? {
  67. get {
  68. return topContentView_?.replaceButton
  69. }
  70. }
  71. private var menuGroupView_: ComponentGroup?
  72. private var menuSections_: [CPDFSelection] = []
  73. private var menuType_: CAnnotationType = .circle
  74. var handdler = KMNSearchHanddler() {
  75. didSet {
  76. self.reloadData()
  77. }
  78. }
  79. weak var delegate: KMBotaSearchViewControllerDelegate?
  80. var searchResults : [KMBotaSearchSectionModel] = [] {
  81. didSet {
  82. self.updataLeftSideFindView()
  83. }
  84. }
  85. private var datas: [Any] = []
  86. private var searchResultIndex_: Int = -1
  87. private var currentSel: CPDFSelection?
  88. private var currentModel_: KMSearchMode?
  89. var isSelectTableViewCell: Bool = true
  90. override func loadView() {
  91. super.loadView()
  92. topView.borderWidth = 0
  93. topView.fillColor = .clear
  94. topView.contentView = topContentView_
  95. topContentView_?.itemClick = { [unowned self] idx, params in
  96. if idx == KMNBotaSearchTopItemKey.search.rawValue {
  97. if let data = params.first as? ComponentButton {
  98. showSearchGroupView(sender: data)
  99. }
  100. } else if idx == KMNBotaSearchTopItemKey.replace.rawValue {
  101. if handdler.type == .search {
  102. showReplaceView(needSearch: true)
  103. } else {
  104. showSearchView(needSearch: true)
  105. }
  106. } else if idx == KMNBotaSearchTopItemKey.switch.rawValue {
  107. delegate?.switchSearchPopWindow?(controller: self)
  108. } else if idx == KMNBotaSearchTopItemKey.previous.rawValue {
  109. tableViewMoveUp(tableView)
  110. _previousAction(NSButton())
  111. } else if idx == KMNBotaSearchTopItemKey.next.rawValue {
  112. tableViewMoveDown(tableView)
  113. _nextAction(NSButton())
  114. } else if idx == KMNBotaSearchTopItemKey.replaceText.rawValue {
  115. _replaceAction(NSButton())
  116. } else if idx == KMNBotaSearchTopItemKey.replaceAllText.rawValue {
  117. _replaceAllAction(NSButton())
  118. }
  119. self.updateButtonState()
  120. }
  121. showSearchView()
  122. topContentView_?.valueDidChange = { [unowned self] sender, info in
  123. guard let string = info?[.newKey] as? String else {
  124. return
  125. }
  126. updateButtonState()
  127. currentSel = nil
  128. currentModel_ = nil
  129. self.search(keyword: string)
  130. }
  131. topContentView_?.replaceValueDidChange = { [unowned self] sender, info in
  132. guard let string = info?[.newKey] as? String else {
  133. return
  134. }
  135. handdler.replaceKey = string
  136. }
  137. emptyBox.contentView?.addSubview(emptyView_)
  138. emptyView_.km_add_top_constraint(constant: 232)
  139. emptyView_.km_add_bottom_constraint()
  140. emptyView_.km_add_leading_constraint()
  141. emptyView_.km_add_trailing_constraint()
  142. self.emptySearchLabel.stringValue = KMLocalizedString("")
  143. self.emptySearchLabel.textColor = KMAppearance.Layout.h1Color()
  144. contentView = tableView.enclosingScrollView
  145. tableView.menuClickedAction = { [unowned self] point in
  146. let idxs = self.tableView.selectedRowIndexes.count
  147. let convertP = self.tableView.convert(point, from: nil)
  148. let row = self.tableView.row(at: convertP)
  149. if row == -1 {
  150. return NSMenu()
  151. }
  152. let hideNotes = handdler.hideNotes()
  153. let allowsNotes = handdler.allowsNotes()
  154. if hideNotes || allowsNotes == false {
  155. return NSMenu()
  156. }
  157. guard let model = self.datas[row] as? KMSearchMode else {
  158. return NSMenu()
  159. }
  160. var viewHeight: CGFloat = 0
  161. let items: [String] = ["Add New Circle", "Add New Rectangle", "Add New Highlight", "Add New Underline", "Add New Strikethrough"]
  162. var menuItemArr: [ComponentMenuitemProperty] = []
  163. for value in items {
  164. let properties_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(multipleSelect: false,
  165. itemSelected: false,
  166. isDisabled: false,
  167. keyEquivalent: nil,
  168. text: KMLocalizedString(value),
  169. identifier: value)
  170. menuItemArr.append(properties_Menuitem)
  171. viewHeight += 36
  172. }
  173. self.menuGroupView_ = ComponentGroup.createFromNib(in: ComponentLibrary.shared.componentBundle())
  174. self.menuGroupView_?.clickedAutoHide = true
  175. self.menuGroupView_?.groupDelegate = self
  176. self.menuGroupView_?.frame = CGRectMake(0, 0, 180, viewHeight)
  177. self.menuGroupView_?.updateGroupInfo(menuItemArr)
  178. self.menuSections_ = [model.selection]
  179. self.menuGroupView_?.showWithPoint(CGPoint(x: point.x, y: point.y - viewHeight), relativeTo: nil)
  180. return NSMenu()
  181. }
  182. }
  183. override func viewDidLoad() {
  184. super.viewDidLoad()
  185. tableView.delegate = self
  186. tableView.dataSource = self
  187. tableView.botaDelegate = self
  188. tableView.hasImageToolTips = true
  189. updateButtonState()
  190. }
  191. override func viewDidAppear() {
  192. super.viewDidAppear()
  193. view.window?.makeFirstResponder(topContentView_?.searchInput)
  194. }
  195. override func updateUILanguage() {
  196. super.updateUILanguage()
  197. KMMainThreadExecute {
  198. self.topContentView_?.resultLabel.stringValue = KMLocalizedString("Result") + ": " + "\(self.handdler.searchResults.count)"
  199. self.tableView.reloadData()
  200. }
  201. }
  202. override func updateUIThemeColor() {
  203. super.updateUIThemeColor()
  204. KMMainThreadExecute {
  205. self.view.wantsLayer = true
  206. let color = KMNColorTools.colorBg_layoutMiddle()
  207. self.view.layer?.backgroundColor = color.cgColor
  208. self.tableView.backgroundColor = color
  209. self.topContentView_?.replaceInput.properties.leftIcon = NSImage(named: "KMImagenameBotaSearchInputPrefiex")
  210. self.topContentView_?.replaceInput.reloadData()
  211. self.topContentView_?.resultLabel.textColor = KMNColorTools.colorText_3()
  212. self.topContentView_?.bottomLine.layer?.backgroundColor = KMNColorTools.colorPrimary_border1().cgColor
  213. self.tableView.reloadData()
  214. }
  215. }
  216. func reloadData() {
  217. let handdler = self.handdler
  218. topContentView_?.inputValue = handdler.searchKey ?? ""
  219. topContentView_?.replaceText = handdler.replaceKey ?? ""
  220. self.searchResults = handdler.searchSectionResults
  221. var index = handdler.showIdx
  222. var row = 0
  223. var searchModel = KMSearchMode()
  224. if handdler.showIdx != 0 {
  225. searchModel = self.handdler.searchResults[handdler.showIdx]
  226. self.handdler.showSelection(searchModel.selection)
  227. } else {
  228. // 显示第一个匹配项的选择框
  229. let sels = self.handdler.searchResults
  230. if let firstSelection = sels.first?.selection {
  231. searchModel = sels.first!
  232. self.handdler.showSelection(firstSelection)
  233. } else {
  234. self.handdler.showSelection(CPDFSelection())
  235. }
  236. index = 0
  237. }
  238. // 展开所有结果
  239. for model in handdler.searchSectionResults {
  240. model.isExpand = true
  241. }
  242. // 更新当前模型并刷新表格
  243. self.currentModel_ = searchModel
  244. self.tableView.reloadData()
  245. if handdler.showIdx != 0 {
  246. // 查找匹配的 model
  247. if let result = datas.firstIndex(where: {
  248. // 尝试将元素转换为 MyModel 类型
  249. if let model = $0 as? KMSearchMode {
  250. return model == searchModel // 如果转换成功,进行比较
  251. }
  252. return false
  253. }) {
  254. print("Found at index: \(index)")
  255. row = result
  256. } else {
  257. print("Model not found")
  258. row = 1
  259. }
  260. } else {
  261. row = 1
  262. }
  263. self.tableView.km_safe_selectRowIndexes(.init(integer: row), byExtendingSelection: false)
  264. self.tableView.scrollRowToVisible(self.tableView.selectedRow)
  265. self.showResult()
  266. updateButtonState()
  267. }
  268. func showSearchView(needSearch: Bool = false) {
  269. handdler.type = .search
  270. topContentView_?.showSearch()
  271. topHeightConst.constant = topContentView_?.fetchContentHeight(type: handdler.type, hasResult: handdler.searchResults.isEmpty == false) ?? 0
  272. delegate?.searchTypeDidChange?(controller: self)
  273. if needSearch {
  274. if let keyword = topContentView_?.inputValue, keyword.isEmpty == false {
  275. DispatchQueue.main.async {
  276. self.search(keyword: keyword)
  277. }
  278. }
  279. }
  280. }
  281. func showReplaceView(needSearch: Bool = false) {
  282. if KMMemberInfo.shared.isLogin == false {
  283. KMLoginWindowsController.shared.showWindow(nil)
  284. return
  285. }
  286. handdler.type = .replace
  287. topContentView_?.showReplace()
  288. topHeightConst.constant = topContentView_?.fetchContentHeight(type: handdler.type, hasResult: handdler.searchResults.isEmpty == false) ?? 0
  289. delegate?.searchTypeDidChange?(controller: self)
  290. if needSearch {
  291. if let keyword = topContentView_?.inputValue, keyword.isEmpty == false {
  292. DispatchQueue.main.async {
  293. self.search(keyword: keyword)
  294. }
  295. }
  296. }
  297. }
  298. func showResult() {
  299. topContentView_?.showResult(type: handdler.type)
  300. var count = 0
  301. if self.datas.isEmpty {
  302. topContentView_?.resultLabel.stringValue = ""
  303. } else {
  304. count = self.handdler.searchResults.count
  305. let theIdx = self.currentShowIndex()
  306. topContentView_?.resultLabel.stringValue = KMLocalizedString("Result") + ":" + " \(theIdx)/" + "\(count)"
  307. }
  308. topHeightConst.constant = topContentView_?.fetchContentHeight(type: handdler.type, hasResult: searchResults.isEmpty == false) ?? 0
  309. self.updateButtonState()
  310. }
  311. func currentShowIndex() -> Int {
  312. let count = self.handdler.searchResults.count
  313. var i = 0
  314. var sectionCount = 0
  315. for model in self.datas {
  316. if i > self.tableView.selectedRow {
  317. break
  318. }
  319. if let data = model as? KMBotaSearchSectionModel {
  320. sectionCount += 1
  321. }
  322. i += 1
  323. }
  324. let theIdx = max(i-sectionCount, 1)
  325. return theIdx
  326. }
  327. private func fetchSearchOptions() -> CPDFSearchOptions {
  328. var opt = CPDFSearchOptions()
  329. let isCase = KMDataManager.ud_bool(forKey: KMNSearchKey.caseSensitive.botaSearch)
  330. let isWholeWord = KMDataManager.ud_bool(forKey: KMNSearchKey.wholeWords.botaSearch)
  331. if isCase {
  332. opt.insert(.caseSensitive)
  333. }
  334. if isWholeWord {
  335. opt.insert(.matchWholeWord)
  336. }
  337. return opt
  338. }
  339. func updateButtonState() {
  340. let value = topContentView_?.inputValue ?? ""
  341. if value.isEmpty || self.handdler.searchResults.isEmpty{
  342. previousButton?.properties.isDisabled = true
  343. previousButton?.reloadData()
  344. nextButton?.properties.isDisabled = true
  345. nextButton?.reloadData()
  346. replaceButton?.properties.isDisabled = true
  347. replaceButton?.reloadData()
  348. replaceAllButton?.properties.isDisabled = true
  349. replaceAllButton?.reloadData()
  350. } else {
  351. replaceButton?.properties.isDisabled = false
  352. replaceButton?.reloadData()
  353. replaceAllButton?.properties.isDisabled = false
  354. replaceAllButton?.reloadData()
  355. previousButton?.properties.isDisabled = (self.handdler.showIdx == 0) ? true : false
  356. previousButton?.reloadData()
  357. nextButton?.properties.isDisabled = ((self.handdler.showIdx == self.handdler.resultCount - 1) || self.handdler.resultCount == 0)
  358. nextButton?.reloadData()
  359. }
  360. }
  361. private func _showNoResultsAlert() {
  362. _ = _showAlert(style: .critical, message: KMLocalizedString("No related content found, please change keyword."), info: "", buttons: [KMLocalizedString("OK", comment: "")])
  363. }
  364. private func _showAlert(style: NSAlert.Style, message: String, info: String, buttons: [String]) -> NSApplication.ModalResponse {
  365. let alert = NSAlert()
  366. alert.alertStyle = style
  367. alert.messageText = message
  368. alert.informativeText = info
  369. for button in buttons {
  370. alert.addButton(withTitle: button)
  371. }
  372. return alert.runModal()
  373. }
  374. func updataLeftSideFindView() {
  375. if (self.searchResults.count > 0) {
  376. self.emptyBox.isHidden = true
  377. } else {
  378. self.emptyBox.isHidden = false
  379. }
  380. }
  381. func updateFindResultHighlightsForDirection(_ direction: NSWindow.SelectionDirection) {
  382. let findResults: [KMSearchMode] = handdler.searchResults
  383. if (findResults.count == 0) {
  384. handdler.showSelection(nil)
  385. } else {
  386. if direction == .directSelection {
  387. self.searchResultIndex_ = 0
  388. } else if (direction == .selectingNext) {
  389. self.searchResultIndex_ += 1
  390. if self.searchResultIndex_ >= findResults.count {
  391. self.searchResultIndex_ = 0
  392. }
  393. } else if (direction == .selectingPrevious) {
  394. self.searchResultIndex_ -= 1
  395. if self.searchResultIndex_ < 0 {
  396. self.searchResultIndex_ = findResults.count-1
  397. }
  398. }
  399. let currentSel = findResults[self.searchResultIndex_].selection
  400. if currentSel.hasCharacters() {
  401. let page = currentSel.safeFirstPage()
  402. var rect = NSZeroRect
  403. for model in findResults {
  404. if let data = page, model.selection.pages().contains(data) {
  405. rect = NSUnionRect(rect, model.selection.bounds(for: data))
  406. }
  407. }
  408. let FIND_RESULT_MARGIN = 50.0
  409. rect = NSIntersectionRect(NSInsetRect(rect, -FIND_RESULT_MARGIN, -FIND_RESULT_MARGIN), page?.bounds(for: .cropBox) ?? .zero)
  410. handdler.pdfView?.go(to: page)
  411. handdler.pdfView?.go(to: rect, on: page)
  412. }
  413. if currentSel.hasCharacters() {
  414. let bColor = NSColor(red: 236/255.0, green: 241/255.0, blue: 83/255.0, alpha: 0.5)
  415. let color = NSColor(red: 219/255.0, green: 220/255.0, blue: 3/255.0, alpha: 0.5)
  416. handdler.pdfView?.setHighlight(currentSel, forBorderColor: .clear, fill: color, animated: true)
  417. handdler.pdfView?.go(to: currentSel, animated: true)
  418. handdler.pdfView?.setCurrentSelection(currentSel, animate: true)
  419. }
  420. }
  421. }
  422. }
  423. //MARK: Action
  424. extension KMBotaSearchViewController {
  425. @objc private func _previousAction(_ sender: NSButton) {
  426. let index = self.handdler.previous()
  427. if index < self.handdler.searchResults.count && index >= 0{
  428. // let model = self.handdler.searchResults[index]
  429. // self.handdler.showSelection(model.selection)
  430. self.updateButtonState()
  431. }
  432. }
  433. @objc private func _nextAction(_ sender: NSButton) {
  434. let index = self.handdler.next()
  435. if index < self.handdler.searchResults.count && index >= 0 {
  436. // let model = self.handdler.searchResults[index]
  437. // self.handdler.showSelection(model.selection)
  438. self.updateButtonState()
  439. }
  440. }
  441. @objc private func _replaceAction(_ sender: NSButton) {
  442. let isEditing = self.handdler.pdfView?.isEditing() ?? false
  443. if isEditing == false {
  444. NSSound.beep()
  445. return
  446. }
  447. if let sel = self.currentSel {
  448. let searchS = self.topContentView_?.inputValue ?? ""
  449. let replaceS = self.topContentView_?.replaceText ?? ""
  450. self.handdler.replace(searchS: searchS, replaceS: replaceS, sel: sel) { [weak self] newSel in
  451. self?.handdler.showSelection(newSel)
  452. if let data = newSel {
  453. self?.currentModel_?.selection = data
  454. self?.tableView.reloadData()
  455. }
  456. }
  457. }
  458. }
  459. @objc private func _replaceAllAction(_ sender: NSButton) {
  460. let isEditing = self.handdler.pdfView?.isEditing() ?? false
  461. if isEditing == false {
  462. NSSound.beep()
  463. return
  464. }
  465. let datas = self.handdler.searchSectionResults
  466. if datas.isEmpty {
  467. _showNoResultsAlert()
  468. return
  469. }
  470. let searchS = self.topContentView_?.inputValue ?? ""
  471. let replaceS = self.topContentView_?.replaceText ?? ""
  472. self.beginLoading()
  473. DispatchQueue.global().async {
  474. self.handdler.pdfView?.document.replaceAllEditText(with: searchS, toReplace: replaceS)
  475. self.currentSel = nil
  476. DispatchQueue.main.async {
  477. self.endLoading()
  478. self.handdler.pdfView?.setHighlightedSelection(nil, animated: false)
  479. self.handdler.pdfView?.setNeedsDisplayForVisiblePages()
  480. }
  481. }
  482. }
  483. }
  484. // MARK: - NSTableViewDelegate, NSTableViewDataSource
  485. extension KMBotaSearchViewController: NSTableViewDelegate, NSTableViewDataSource {
  486. func numberOfRows(in tableView: NSTableView) -> Int {
  487. var datas: [Any] = []
  488. for sectionM in self.handdler.searchSectionResults {
  489. if sectionM.items.count > 0 {
  490. datas.append(sectionM)
  491. if sectionM.isExpand == false {
  492. continue
  493. }
  494. for item in sectionM.items {
  495. datas.append(item)
  496. }
  497. }
  498. }
  499. self.datas = datas
  500. return datas.count
  501. }
  502. func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
  503. let model = self.datas[row]
  504. if let data = model as? KMBotaSearchSectionModel {
  505. var cell = tableView.makeView(withIdentifier: KMSectionCellView.km_identifier, owner: nil) as? KMSectionCellView
  506. if cell == nil {
  507. cell = KMSectionCellView.createFromNib()
  508. }
  509. cell?.titleLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-s-medium")
  510. cell?.countLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-s-regular")
  511. cell?.titleLabel.textColor = KMNColorTools.colorText_1()
  512. cell?.countLabel.textColor = KMNColorTools.colorText_3()
  513. cell?.titleLabel.isEditable = false
  514. cell?.isExpand = data.isExpand
  515. let pageIndex = data.pageIndex
  516. cell?.titleLabel.stringValue = KMLocalizedString("Page") + " \(pageIndex + 1)"
  517. cell?.countLabel.stringValue = "\(data.itemCount)"
  518. cell?.bottomLine.wantsLayer = true
  519. cell?.bottomLine.layer?.backgroundColor = KMNColorTools.colorBorder_divider().cgColor
  520. cell?.itemClick = { [weak self] idx, _ in
  521. if idx == 1 { // 收取 & 展开
  522. data.isExpand = !data.isExpand
  523. self?.tableView.reloadData()
  524. }
  525. }
  526. return cell
  527. }
  528. if let data = model as? KMSearchMode {
  529. var cell = tableView.makeView(withIdentifier: KMNBotaSearchCellView.km_identifier, owner: self) as? KMNBotaSearchCellView
  530. if cell == nil {
  531. cell = KMNBotaSearchCellView()
  532. }
  533. cell?.label.attributedStringValue = data.attributedString
  534. cell?.label.isEnabled = false
  535. return cell
  536. }
  537. return nil
  538. }
  539. func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
  540. let model = datas[row]
  541. if model is KMBotaSearchSectionModel {
  542. return 40.0
  543. }
  544. if let data = model as? KMSearchMode {
  545. let width = NSWidth(self.view.frame)
  546. let rect = data.attributedString.boundingRect(with: .init(width: width-24*2, height: CGFLOAT_MAX), options: [.usesLineFragmentOrigin, .usesLineFragmentOrigin])
  547. return rect.size.height + 12 * 2
  548. }
  549. return 40.0
  550. }
  551. func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
  552. let model = datas[row]
  553. if model is KMBotaSearchSectionModel {
  554. let rowView = KMTableRowView()
  555. rowView.selectionBackgroundColorBlock = {
  556. return .clear
  557. }
  558. return rowView
  559. }
  560. let rowView = KMTableRowView()
  561. rowView.selectionRadius = ComponentLibrary.shared.getComponentValueFromKey("radius/xs") as? CGFloat ?? 4
  562. rowView.selectionBackgroundColorBlock = {
  563. return KMNColorTools.colorPrimary_bgOpacityDark()
  564. }
  565. rowView.selectionInset = .init(top: 8, left: 16, bottom: 8, right: 16)
  566. return rowView
  567. }
  568. func tableViewSelectionDidChange(_ notification: Notification) {
  569. let row = self.tableView.selectedRow
  570. if row < 0 || row >= datas.count {
  571. return
  572. }
  573. guard let model = datas[row] as? KMSearchMode else {
  574. return
  575. }
  576. handdler.showSelection(model.selection)
  577. currentSel = model.selection
  578. currentModel_ = model
  579. let theIdx = self.currentShowIndex()
  580. if isSelectTableViewCell {
  581. self.handdler.showIdx = theIdx - 1
  582. }
  583. isSelectTableViewCell = true
  584. self.showResult()
  585. }
  586. func tableView(_ aTableView: NSTableView, copyRowsWithIndexes rowIndexes: IndexSet) {
  587. if IAPProductsManager.default().isAvailableAllFunction() == false {
  588. KMPurchaseCompareWindowController.sharedInstance().showWindow(nil)
  589. return
  590. }
  591. var string = ""
  592. for idx in rowIndexes {
  593. if idx < 0 || idx >= datas.count {
  594. continue
  595. }
  596. guard let model = datas[idx] as? KMSearchMode else {
  597. continue
  598. }
  599. let match = model.selection
  600. string.append("* ")
  601. string = string.appendingFormat(KMLocalizedString("Page %@"), "\(match.safeFirstPage()?.pageIndex() ?? 0)")
  602. string = string.appendingFormat(": %@\n", match.string() ?? "")
  603. }
  604. let pboard = NSPasteboard.general
  605. pboard.clearContents()
  606. pboard.writeObjects([string as NSPasteboardWriting])
  607. }
  608. }
  609. // MARK: - KMBotaTableViewDelegate
  610. extension KMBotaSearchViewController: KMBotaTableViewDelegate {
  611. func tableView(_ aTableView: NSTableView, canCopyRowsWithIndexes rowIndexes: IndexSet) -> Bool {
  612. return rowIndexes.count > 0
  613. }
  614. func tableViewMoveRight(_ aTableView: NSTableView) {
  615. updateFindResultHighlightsForDirection(.selectingNext)
  616. }
  617. func tableViewMoveUp(_ aTableView: NSTableView) {
  618. var theRow = self.tableView.selectedRow-1
  619. if theRow < 0 || theRow >= self.datas.count {
  620. return
  621. }
  622. let model = self.datas[theRow]
  623. if let _ = model as? KMBotaSearchSectionModel {
  624. theRow -= 1
  625. }
  626. isSelectTableViewCell = false
  627. self.tableView.km_safe_selectRowIndexes(.init(integer: theRow), byExtendingSelection: false)
  628. self.tableView.scrollRowToVisible(self.tableView.selectedRow)
  629. }
  630. func tableViewMoveDown(_ aTableView: NSTableView) {
  631. var theRow = self.tableView.selectedRow + 1
  632. if theRow < 0 || theRow >= self.datas.count {
  633. return
  634. }
  635. let model = self.datas[theRow]
  636. if let _ = model as? KMBotaSearchSectionModel {
  637. theRow += 1
  638. }
  639. isSelectTableViewCell = false
  640. self.tableView.km_safe_selectRowIndexes(.init(integer: theRow), byExtendingSelection: false)
  641. self.tableView.scrollRowToVisible(self.tableView.selectedRow)
  642. }
  643. func tableView(_ aTableView: NSTableView, imageContextForRow rowIndex: Int) -> AnyObject? {
  644. if rowIndex < 0 || rowIndex >= datas.count {
  645. return nil
  646. }
  647. guard let model = datas[rowIndex] as? KMSearchMode else {
  648. return -1 as AnyObject
  649. }
  650. let selection = model.selection
  651. let x = selection.bounds.origin.x + NSWidth(selection.bounds) * 0.5
  652. let y = selection.bounds.origin.y + NSHeight(selection.bounds) * 0.5
  653. let point = NSPoint(x: x, y: y)
  654. return CPDFDestination(document: handdler.pdfDocument(), pageIndex: Int(model.selectionPageIndex), at: point, zoom: handdler.scaleFactor() ?? 0)
  655. }
  656. }
  657. //MARK: - ComponentGroupDelegate
  658. extension KMBotaSearchViewController: ComponentGroupDelegate {
  659. func showSearchGroupView(sender: ComponentButton) {
  660. var viewHeight: CGFloat = 8
  661. var menuItemArr: [ComponentMenuitemProperty] = []
  662. let titles = ["Search", "Find and Replace", "", "Whole Words", "Case Sensitive"]
  663. for i in titles {
  664. if i.isEmpty {
  665. let menuI = ComponentMenuitemProperty.divider()
  666. menuItemArr.append(menuI)
  667. viewHeight += 8
  668. } else {
  669. let menuI = ComponentMenuitemProperty(text: KMLocalizedString(i))
  670. menuItemArr.append(menuI)
  671. viewHeight += 36
  672. }
  673. }
  674. if handdler.type == .search {
  675. menuItemArr.first?.righticon = NSImage(named: "KMNImageNameMenuSelect")
  676. } else if handdler.type == .replace {
  677. let info = menuItemArr.safe_element(for: 1) as? ComponentMenuitemProperty
  678. info?.righticon = NSImage(named: "KMNImageNameMenuSelect")
  679. }
  680. if let info = menuItemArr.safe_element(for: 3) as? ComponentMenuitemProperty {
  681. if KMDataManager.ud_bool(forKey: KMNSearchKey.wholeWords.botaSearch) {
  682. info.righticon = NSImage(named: "KMNImageNameMenuSelect")
  683. }
  684. }
  685. if let info = menuItemArr.last {
  686. if KMDataManager.ud_bool(forKey: KMNSearchKey.caseSensitive.botaSearch) {
  687. info.righticon = NSImage(named: "KMNImageNameMenuSelect")
  688. }
  689. }
  690. let groupView = ComponentGroup.createFromNib(in: ComponentLibrary.shared.componentBundle())
  691. searchGroupView = groupView
  692. groupView?.groupDelegate = self
  693. groupView?.frame = CGRectMake(310, 0, 200, viewHeight)
  694. groupView?.updateGroupInfo(menuItemArr)
  695. var point = sender.convert(sender.frame.origin, to: nil)
  696. point.y -= viewHeight
  697. groupView?.showWithPoint(point, relativeTo: sender)
  698. searchGroupTarget = sender
  699. }
  700. func componentGroupDidDismiss(group: ComponentGroup?) {
  701. if group == menuGroupView_ {
  702. // group?.removeFromSuperview()
  703. // menuGroupView_ = nil
  704. } else if group == searchGroupView {
  705. searchGroupTarget?.properties.state = .normal
  706. searchGroupTarget?.reloadData()
  707. searchGroupTarget = nil
  708. }
  709. }
  710. func componentGroupDidSelect(group: ComponentGroup?, menuItemProperty: ComponentMenuitemProperty?) {
  711. if group == menuGroupView_ {
  712. if let selItem = menuItemProperty {
  713. let index = group?.menuItemArr.firstIndex(of: selItem)
  714. if index == 0 {
  715. menuType_ = .circle
  716. addAnnotationsForSelections(NSMenuItem())
  717. } else if index == 1 {
  718. menuType_ = .square
  719. addAnnotationsForSelections(NSMenuItem())
  720. } else if index == 2 {
  721. menuType_ = .highlight
  722. addAnnotationsForSelections(NSMenuItem())
  723. } else if index == 3 {
  724. menuType_ = .underline
  725. addAnnotationsForSelections(NSMenuItem())
  726. } else if index == 4 {
  727. menuType_ = .strikeOut
  728. addAnnotationsForSelections(NSMenuItem())
  729. }
  730. }
  731. } else if group == searchGroupView {
  732. guard let menuI = menuItemProperty else {
  733. return
  734. }
  735. let idx = group?.menuItemArr.firstIndex(of: menuI)
  736. if idx == 0 { // search
  737. showSearchView()
  738. } else if idx == 1 { // replace
  739. showReplaceView()
  740. } else if idx == 3 {
  741. let key = KMNSearchKey.wholeWords.botaSearch
  742. let value = KMDataManager.ud_bool(forKey: key)
  743. KMDataManager.ud_set(!value, forKey: key)
  744. currentSel = nil
  745. currentModel_ = nil
  746. if let data = topContentView_?.inputValue, data.isEmpty == false {
  747. search(keyword: data)
  748. }
  749. } else if idx == 4 {
  750. let key = KMNSearchKey.caseSensitive.botaSearch
  751. let value = KMDataManager.ud_bool(forKey: key)
  752. KMDataManager.ud_set(!value, forKey: key)
  753. currentSel = nil
  754. currentModel_ = nil
  755. if let data = topContentView_?.inputValue, data.isEmpty == false {
  756. search(keyword: data)
  757. }
  758. }
  759. }
  760. }
  761. @objc func addAnnotationsForSelections(_ sender: NSMenuItem) {
  762. let listView = handdler.pdfView as? CPDFListView
  763. for selection in menuSections_ {
  764. // if menuType_ == .circle {
  765. listView?.addAnnotation(with: menuType_, selection: selection, page: selection.page, bounds: selection.bounds)
  766. // }
  767. }
  768. }
  769. }
  770. //MARK: Search
  771. extension KMBotaSearchViewController {
  772. func search(keyword: String) {
  773. // 调用实际搜索函数
  774. let isCase = KMDataManager.ud_bool(forKey: KMNSearchKey.caseSensitive.botaSearch)
  775. let isWholeWord = KMDataManager.ud_bool(forKey: KMNSearchKey.wholeWords.botaSearch)
  776. let isEditing = self.handdler.pdfView?.isEditing() ?? false
  777. handdler.search(keyword: keyword, isCase: isCase, isWholeWord: isWholeWord, isEdit: isEditing) { [weak self] datas in
  778. self?.reloadData()
  779. }
  780. }
  781. }
  782. //MARK: Load
  783. extension KMBotaSearchViewController {
  784. private func beginLoading() {
  785. self.view.beginLoading()
  786. }
  787. private func endLoading() {
  788. self.view.endLoading()
  789. }
  790. }