KMSearchReplaceWindowController.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. //
  2. // KMSearchReplaceWindowController.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by User-Tangchao on 2024/8/7.
  6. //
  7. import Cocoa
  8. import KMComponentLibrary
  9. class KMSearchReplaceWindowController_Window: NSWindow {
  10. override var canBecomeMain: Bool {
  11. return true
  12. }
  13. override var canBecomeKey: Bool {
  14. return true
  15. }
  16. }
  17. class KMSearchReplaceWindowController: KMNBaseWindowController {
  18. @IBOutlet weak var titleBarBox: NSBox!
  19. @IBOutlet weak var tabBox: NSBox!
  20. @IBOutlet weak var searchBox: NSBox!
  21. @IBOutlet weak var replaceBox: NSBox!
  22. var replaceCallback: (() -> Void)?
  23. var itemClick: KMCommonClickBlock?
  24. private var _modalSession: NSApplication.ModalSession?
  25. private var handdler = KMNSearchHanddler()
  26. private var type_: KMNBotaSearchType = .search
  27. private var currentSel: CPDFSelection?
  28. private var finding_ = false
  29. private lazy var titleBarView_: KMNSearchReplaceTitleBarView = {
  30. let view = KMNSearchReplaceTitleBarView()
  31. return view
  32. }()
  33. private lazy var searchItemView_: KMNSearchReplaceSearchItemView = {
  34. let view = KMNSearchReplaceSearchItemView()
  35. return view
  36. }()
  37. private lazy var replaceItemView_: KMNSearchReplacePopItemView = {
  38. let view = KMNSearchReplacePopItemView()
  39. return view
  40. }()
  41. var previousButton: ComponentButton {
  42. get {
  43. return searchItemView_.previousButton
  44. }
  45. }
  46. var nextButton: ComponentButton {
  47. get {
  48. return searchItemView_.nextButton
  49. }
  50. }
  51. var replaceAllButton: ComponentButton {
  52. get {
  53. return replaceItemView_.replaceAllButton
  54. }
  55. }
  56. var replaceButton: ComponentButton {
  57. get {
  58. return replaceItemView_.replaceButton
  59. }
  60. }
  61. convenience init(with pdfView: CPDFView?, type: KMNBotaSearchType) {
  62. self.init(windowNibName: "KMSearchReplaceWindowController")
  63. self.handdler.pdfView = pdfView
  64. self.type_ = type
  65. }
  66. override func windowDidLoad() {
  67. super.windowDidLoad()
  68. self.initDefaultValue()
  69. self.switchType(self.type_)
  70. }
  71. func initDefaultValue() {
  72. window?.isMovableByWindowBackground = true
  73. window?.contentView?.wantsLayer = true
  74. window?.contentView?.layer?.cornerRadius = ComponentLibrary.shared.getComponentValueFromKey("radius/m") as? CGFloat ?? 8
  75. window?.contentView?.layer?.masksToBounds = true
  76. window?.backgroundColor = .clear
  77. titleBarBox.boxType = .custom
  78. titleBarBox.borderWidth = 0
  79. titleBarBox.contentView = titleBarView_
  80. titleBarView_.titleLabel.font = .SFProTextRegularFont(14)
  81. titleBarView_.itemClick = { [unowned self] idx, _ in
  82. if idx == 1 {
  83. _closeAction(NSButton())
  84. } else if idx == 2 {
  85. _closeAction(NSButton())
  86. itemClick?(1, handdler)
  87. }
  88. }
  89. searchBox.borderWidth = 0
  90. searchBox.contentView = searchItemView_
  91. searchItemView_.itemClick = { [unowned self] idx, _ in
  92. if idx == 1 { // Previous
  93. _previousAction(NSButton())
  94. } else if idx == 2 { // next
  95. _nextAction(NSButton())
  96. }
  97. }
  98. searchItemView_.valueDidChange = { [unowned self] value, _ in
  99. if let data = value as? String {
  100. handdler.searchKey = data
  101. search(keyboard: data)
  102. }
  103. }
  104. searchItemView_.inputDidEditBlock = { [unowned self] in
  105. updateButtonStatus()
  106. let value = searchItemView_.inputValue
  107. if value.isEmpty {
  108. } else {
  109. currentSel = nil
  110. }
  111. }
  112. replaceBox.borderWidth = 0
  113. replaceBox.contentView = replaceItemView_
  114. replaceItemView_.itemClick = { [unowned self] idx, _ in
  115. if idx == 1 {
  116. _replaceAllAction(NSButton())
  117. } else if idx == 2 {
  118. _replaceAction(NSButton())
  119. }
  120. }
  121. replaceItemView_.valueDidChange = { [unowned self] value, _ in
  122. if let data = value as? String {
  123. handdler.replaceKey = data
  124. }
  125. }
  126. updateButtonStatus()
  127. if searchItemView_.inputValue.isEmpty {
  128. } else {
  129. self.currentSel = nil
  130. }
  131. }
  132. override func updateUILanguage() {
  133. super.updateUILanguage()
  134. KMMainThreadExecute {
  135. self.titleBarView_.titleLabel.stringValue = KMLocalizedString("Search")
  136. }
  137. }
  138. override func updateUIThemeColor() {
  139. super.updateUIThemeColor()
  140. KMMainThreadExecute {
  141. self.titleBarView_.titleLabel.textColor = KMNColorTools.colorText_1()
  142. self.updateViewColor()
  143. }
  144. }
  145. func updateButtonStatus() {
  146. let value = searchItemView_.inputValue
  147. if value.isEmpty {
  148. previousButton.properties.isDisabled = true
  149. previousButton.reloadData()
  150. nextButton.properties.isDisabled = true
  151. nextButton.reloadData()
  152. replaceButton.properties.isDisabled = true
  153. replaceButton.reloadData()
  154. replaceAllButton.properties.isDisabled = true
  155. replaceAllButton.reloadData()
  156. } else {
  157. previousButton.properties.isDisabled = false
  158. previousButton.reloadData()
  159. nextButton.properties.isDisabled = false
  160. nextButton.reloadData()
  161. replaceButton.properties.isDisabled = false
  162. replaceButton.reloadData()
  163. replaceAllButton.properties.isDisabled = false
  164. replaceAllButton.reloadData()
  165. }
  166. }
  167. func update(keyborad: String?, replaceKey: String?, results: [KMSearchMode]) {
  168. searchItemView_.inputValue = keyborad ?? ""
  169. replaceItemView_.inputValue = replaceKey ?? ""
  170. if results.isEmpty == false {
  171. handdler.searchResults = results
  172. self.currentSel = results.first?.selection
  173. if let sel = self.currentSel {
  174. self.handdler.showSelection(sel)
  175. }
  176. }
  177. updateButtonStatus()
  178. }
  179. // MARK: - Actions
  180. @objc private func _closeAction(_ sender: NSButton) {
  181. self.window?.orderOut(nil)
  182. self.handdler.clearData()
  183. }
  184. @objc private func _previousAction(_ sender: NSButton) {
  185. let isEditing = self.handdler.pdfView?.isEditing() ?? false
  186. if isEditing == false {
  187. guard let model = self.handdler.searchResults.safe_element(for: self.handdler.showIdx-1) as? KMSearchMode else {
  188. return
  189. }
  190. self.handdler.showIdx -= 1
  191. self.handdler.showSelection(model.selection)
  192. } else {
  193. if let _ = self.currentSel {
  194. self.currentSel = self.handdler.pdfView?.document.findForwardEditText()
  195. if let sel = self.currentSel {
  196. self.handdler.showSelection(sel)
  197. } else {
  198. _showNoResultsAlert()
  199. return
  200. }
  201. } else {
  202. if self.finding_ {
  203. return
  204. }
  205. self.finding_ = true
  206. let searchS = self.searchItemView_.inputValue
  207. let opt = self.fetchSearchOptions()
  208. self._beginLoading()
  209. DispatchQueue.global().async {
  210. let datas = self.handdler.pdfView?.document.startFindEditText(from: nil, with: searchS, options: opt)
  211. DispatchQueue.main.async {
  212. self._endLoading()
  213. self.finding_ = false
  214. let sel = datas?.first?.first
  215. if sel == nil {
  216. self._showNoResultsAlert()
  217. return
  218. }
  219. self.currentSel = sel
  220. self.handdler.showSelection(sel)
  221. }
  222. }
  223. }
  224. }
  225. }
  226. @objc private func _nextAction(_ sender: NSButton) {
  227. let isEditing = self.handdler.pdfView?.isEditing() ?? false
  228. if isEditing == false {
  229. guard let model = self.handdler.searchResults.safe_element(for: self.handdler.showIdx+1) as? KMSearchMode else {
  230. return
  231. }
  232. self.handdler.showIdx += 1
  233. self.handdler.showSelection(model.selection)
  234. } else {
  235. if let _ = self.currentSel {
  236. self.currentSel = self.handdler.pdfView?.document.findBackwordEditText()
  237. if let sel = self.currentSel {
  238. self.handdler.showSelection(sel)
  239. } else {
  240. _showNoResultsAlert()
  241. }
  242. } else {
  243. if self.finding_ {
  244. return
  245. }
  246. self.finding_ = true
  247. let searchS = self.searchItemView_.inputValue
  248. let opt = self.fetchSearchOptions()
  249. self._beginLoading()
  250. DispatchQueue.global().async {
  251. let datas = self.handdler.pdfView?.document.startFindEditText(from: nil, with: searchS, options: opt)
  252. DispatchQueue.main.async {
  253. self._endLoading()
  254. self.finding_ = false
  255. let sel = datas?.first?.first
  256. if sel == nil {
  257. self._showNoResultsAlert()
  258. return
  259. }
  260. self.currentSel = sel
  261. self.handdler.showSelection(sel)
  262. }
  263. }
  264. }
  265. }
  266. }
  267. @objc private func _checkAction(_ sender: NSButton) {
  268. self.currentSel = nil
  269. }
  270. @objc private func _searchTabAction(_ sender: NSButton) {
  271. self.switchType(.search, animate: true)
  272. }
  273. @objc private func _replaceTabAction(_ sender: NSButton) {
  274. self.switchType(.replace, animate: true)
  275. }
  276. @objc private func _replaceAction(_ sender: NSButton) {
  277. let isEditing = self.handdler.pdfView?.isEditing() ?? false
  278. if isEditing == false {
  279. NSSound.beep()
  280. return
  281. }
  282. if let sel = self.currentSel {
  283. let searchS = self.searchItemView_.inputValue
  284. let replaceS = self.replaceItemView_.inputValue
  285. let success = self.handdler.replace(searchS: searchS, replaceS: replaceS, sel: sel) { [weak self] newSel in
  286. self?.handdler.showSelection(newSel)
  287. }
  288. if success {
  289. // self.handdler.showSelection(sel)
  290. }
  291. } else { // 先查找
  292. if self.finding_ {
  293. return
  294. }
  295. self.finding_ = true
  296. let searchS = self.searchItemView_.inputValue
  297. let opt = self.fetchSearchOptions()
  298. self._beginLoading()
  299. DispatchQueue.global().async {
  300. let datas = self.handdler.pdfView?.document.startFindEditText(from: nil, with: searchS, options: opt)
  301. DispatchQueue.main.async {
  302. self._endLoading()
  303. self.finding_ = false
  304. let sel = datas?.first?.first
  305. if sel == nil {
  306. self._showNoResultsAlert()
  307. return
  308. }
  309. self.currentSel = sel
  310. self.handdler.showSelection(sel)
  311. }
  312. }
  313. }
  314. }
  315. @objc private func _replaceAllAction(_ sender: NSButton) {
  316. let isEditing = self.handdler.pdfView?.isEditing() ?? false
  317. if isEditing == false {
  318. NSSound.beep()
  319. return
  320. }
  321. let datas = self.handdler.pdfView?.document.findEditSelections() ?? []
  322. if datas.isEmpty {
  323. _showNoResultsAlert()
  324. return
  325. }
  326. if self.finding_ {
  327. return
  328. }
  329. self.finding_ = true
  330. let searchS = self.searchItemView_.inputValue
  331. let replaceS = self.replaceItemView_.inputValue
  332. self._beginLoading()
  333. DispatchQueue.global().async {
  334. self.handdler.pdfView?.document.replaceAllEditText(with: searchS, toReplace: replaceS)
  335. self.currentSel = nil
  336. DispatchQueue.main.async {
  337. self._endLoading()
  338. self.finding_ = false
  339. self.handdler.pdfView?.setHighlightedSelection(nil, animated: false)
  340. self.handdler.pdfView?.setNeedsDisplayForVisiblePages()
  341. }
  342. }
  343. }
  344. private func _showNoResultsAlert() {
  345. _ = _showAlert(style: .critical, message: KMLocalizedString("No related content found, please change keyword."), info: "", buttons: [KMLocalizedString("OK", comment: "")])
  346. }
  347. private func _showAlert(style: NSAlert.Style, message: String, info: String, buttons: [String]) -> NSApplication.ModalResponse {
  348. let alert = NSAlert()
  349. alert.alertStyle = style
  350. alert.messageText = message
  351. alert.informativeText = info
  352. for button in buttons {
  353. alert.addButton(withTitle: button)
  354. }
  355. return alert.runModal()
  356. }
  357. private func fetchSearchOptions() -> CPDFSearchOptions {
  358. var opt = CPDFSearchOptions()
  359. let isCase = KMDataManager.ud_bool(forKey: KMNSearchKey.caseSensitive.botaSearch)
  360. let isWholeWord = KMDataManager.ud_bool(forKey: KMNSearchKey.wholeWords.botaSearch)
  361. if isCase {
  362. opt.insert(.caseSensitive)
  363. }
  364. if isWholeWord {
  365. opt.insert(.matchWholeWord)
  366. }
  367. return opt
  368. }
  369. private func updateViewColor() {
  370. let isDark = KMAppearance.isDarkMode()
  371. if isDark {
  372. self.window?.contentView?.wantsLayer = true
  373. self.window?.contentView?.layer?.backgroundColor = NSColor(hex: "#393C3E").cgColor
  374. } else {
  375. self.window?.contentView?.wantsLayer = true
  376. self.window?.contentView?.layer?.backgroundColor = .white
  377. }
  378. self.switchType(self.type_)
  379. }
  380. func switchType(_ type: KMNBotaSearchType, animate: Bool = false) {
  381. if type == .replace {
  382. if IAPProductsManager.default().isAvailableAllFunction() == false {
  383. let winC = KMPurchaseCompareWindowController.sharedInstance()
  384. winC?.showWindow(nil)
  385. guard let win = winC?.window else {
  386. return
  387. }
  388. self.window?.addChildWindow(win, ordered: .above)
  389. return
  390. }
  391. }
  392. self.type_ = type
  393. let isDark = KMAppearance.isDarkMode()
  394. var selectedColor = NSColor(hex: "0E1114")
  395. var unSelectedColor = NSColor(hex: "757780")
  396. if isDark {
  397. selectedColor = .white
  398. unSelectedColor = NSColor(hex: "#7E7F85")
  399. }
  400. if type == .search { // 248 112
  401. self.replaceBox.isHidden = true
  402. var frame = self.window?.frame ?? .zero
  403. let height: CGFloat = 112
  404. let heightOffset = frame.size.height - height
  405. frame.origin.y += heightOffset
  406. frame.size.height = height
  407. self.window?.setFrame(frame, display: true, animate: animate)
  408. self.window?.minSize = frame.size
  409. self.window?.maxSize = frame.size
  410. } else if type == .replace { // 388 208
  411. DispatchQueue.main.async {
  412. self.replaceBox.isHidden = false
  413. }
  414. var frame = self.window?.frame ?? .zero
  415. let height:CGFloat = 208
  416. let heightOffset = frame.size.height-height
  417. frame.origin.y += heightOffset
  418. frame.size.height = height
  419. self.window?.setFrame(frame, display: true, animate: animate)
  420. self.window?.minSize = frame.size
  421. self.window?.maxSize = frame.size
  422. // 将事件回调出去
  423. self.replaceCallback?()
  424. }
  425. }
  426. private func _beginLoading() {
  427. self.window?.contentView?.beginLoading()
  428. }
  429. private func _endLoading() {
  430. self.window?.contentView?.endLoading()
  431. }
  432. func startModal(_ sender: AnyObject?) {
  433. NSApp.stopModal()
  434. var modalCode: NSApplication.ModalResponse?
  435. if let _win = self.window {
  436. self._modalSession = NSApp.beginModalSession(for: _win)
  437. repeat {
  438. modalCode = NSApp.runModalSession(self._modalSession!)
  439. } while (modalCode == .continue)
  440. }
  441. }
  442. func endModal(_ sender: AnyObject?) {
  443. if let session = self._modalSession {
  444. NSApp.stopModal()
  445. NSApp.endModalSession(session)
  446. self.window?.orderOut(self)
  447. }
  448. if let winC = self.window?.kmCurrentWindowC, winC.isEqual(to: self) {
  449. self.window?.kmCurrentWindowC = nil
  450. }
  451. }
  452. func search(keyboard: String) {
  453. let isCase = KMDataManager.ud_bool(forKey: KMNSearchKey.caseSensitive.botaSearch)
  454. let isWholeWord = KMDataManager.ud_bool(forKey: KMNSearchKey.wholeWords.botaSearch)
  455. let isEditing = self.handdler.pdfView?.isEditing() ?? false
  456. if isEditing == false {
  457. if self.finding_ {
  458. return
  459. }
  460. self.finding_ = true
  461. self._beginLoading()
  462. self.handdler.search(keyword: keyboard, isCase: isCase, isWholeWord: isWholeWord, callback: { [weak self] datas in
  463. self?.finding_ = false
  464. self?._endLoading()
  465. guard let sels = self?.handdler.searchResults, sels.isEmpty == false else {
  466. self?._showNoResultsAlert()
  467. return
  468. }
  469. if let sel = sels.first?.selection {
  470. self?.handdler.showIdx = 0
  471. self?.handdler.showSelection(sel)
  472. }
  473. })
  474. } else {
  475. if self.finding_ {
  476. return
  477. }
  478. self.finding_ = true
  479. let searchS = keyboard
  480. let opt = self.fetchSearchOptions()
  481. self._beginLoading()
  482. DispatchQueue.global().async {
  483. let datas = self.handdler.pdfView?.document.findEditAllPageString(searchS, with: opt) ?? []
  484. DispatchQueue.main.async {
  485. self.finding_ = false
  486. self._endLoading()
  487. if datas.isEmpty {
  488. self._showNoResultsAlert()
  489. return
  490. }
  491. self.currentSel = datas.first?.first
  492. if let sel = self.currentSel {
  493. self.handdler.showSelection(sel)
  494. }
  495. }
  496. }
  497. }
  498. }
  499. }