KMBotaSearchViewController.swift 34 KB

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