KMSearchReplaceWindowController.swift 21 KB

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