KMSearchReplaceWindowController.swift 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  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: KMSearchReplaceHanddler = KMSearchReplaceHanddler()
  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)
  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. search(keyboard: data)
  106. }
  107. }
  108. searchItemView_.inputDidEditBlock = { [unowned self] in
  109. updateButtonStatus()
  110. let value = searchItemView_.inputValue
  111. if value.isEmpty {
  112. } else {
  113. currentSel = nil
  114. }
  115. }
  116. replaceBox.borderWidth = 0
  117. replaceBox.contentView = replaceItemView_
  118. updateButtonStatus()
  119. if searchItemView_.inputValue.isEmpty {
  120. } else {
  121. self.currentSel = nil
  122. }
  123. }
  124. override func updateUILanguage() {
  125. super.updateUILanguage()
  126. KMMainThreadExecute {
  127. self.titleBarView_.titleLabel.stringValue = KMLocalizedString("Search")
  128. }
  129. }
  130. override func updateUIThemeColor() {
  131. super.updateUIThemeColor()
  132. KMMainThreadExecute {
  133. self.titleBarView_.titleLabel.textColor = KMNColorTools.colorText_1()
  134. self.updateViewColor()
  135. }
  136. }
  137. func updateButtonStatus() {
  138. let value = searchItemView_.inputValue
  139. if value.isEmpty {
  140. previousButton.properties.isDisabled = true
  141. previousButton.reloadData()
  142. nextButton.properties.isDisabled = true
  143. nextButton.reloadData()
  144. replaceButton.properties.isDisabled = true
  145. replaceButton.reloadData()
  146. replaceAllButton.properties.isDisabled = true
  147. replaceAllButton.reloadData()
  148. } else {
  149. previousButton.properties.isDisabled = false
  150. previousButton.reloadData()
  151. nextButton.properties.isDisabled = false
  152. nextButton.reloadData()
  153. replaceButton.properties.isDisabled = false
  154. replaceButton.reloadData()
  155. replaceAllButton.properties.isDisabled = false
  156. replaceAllButton.reloadData()
  157. }
  158. }
  159. // MARK: - Actions
  160. @objc private func _closeAction(_ sender: NSButton) {
  161. self.endModal(sender)
  162. self.handdler.clearData()
  163. }
  164. @objc private func _previousAction(_ sender: NSButton) {
  165. let isEditing = self.handdler.pdfView?.isEditing() ?? false
  166. if isEditing == false {
  167. guard let model = self.handdler.searchResults.safe_element(for: self.handdler.showIdx-1) as? KMSearchMode else {
  168. return
  169. }
  170. self.handdler.showIdx -= 1
  171. self.handdler.showSelection(model.selection)
  172. } else {
  173. if let _ = self.currentSel {
  174. self.currentSel = self.handdler.pdfView?.document.findForwardEditText()
  175. if let sel = self.currentSel {
  176. self.handdler.showSelection(sel)
  177. } else {
  178. let alert = NSAlert()
  179. alert.messageText = NSLocalizedString("No related content found, please change keyword.", comment: "")
  180. alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
  181. // alert.runModal()
  182. alert.beginSheetModal(for: (self.window)!)
  183. }
  184. } else {
  185. if self.finding_ {
  186. return
  187. }
  188. self.finding_ = true
  189. let searchS = self.searchItemView_.inputValue
  190. let opt = self.fetchSearchOptions()
  191. self._beginLoading()
  192. DispatchQueue.global().async {
  193. let datas = self.handdler.pdfView?.document.startFindEditText(from: nil, with: searchS, options: opt)
  194. DispatchQueue.main.async {
  195. self._endLoading()
  196. self.finding_ = false
  197. let sel = datas?.first?.first
  198. if sel == nil {
  199. let alert = NSAlert()
  200. alert.messageText = NSLocalizedString("No related content found, please change keyword.", comment: "")
  201. alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
  202. // alert.runModal()
  203. alert.beginSheetModal(for: (self.window)!)
  204. return
  205. }
  206. self.currentSel = sel
  207. self.handdler.showSelection(sel)
  208. }
  209. }
  210. }
  211. }
  212. }
  213. @objc private func _nextAction(_ sender: NSButton) {
  214. let isEditing = self.handdler.pdfView?.isEditing() ?? false
  215. if isEditing == false {
  216. guard let model = self.handdler.searchResults.safe_element(for: self.handdler.showIdx+1) as? KMSearchMode else {
  217. return
  218. }
  219. self.handdler.showIdx += 1
  220. self.handdler.showSelection(model.selection)
  221. } else {
  222. if let _ = self.currentSel {
  223. self.currentSel = self.handdler.pdfView?.document.findBackwordEditText()
  224. if let sel = self.currentSel {
  225. self.handdler.showSelection(sel)
  226. } else {
  227. let alert = NSAlert()
  228. alert.messageText = NSLocalizedString("No related content found, please change keyword.", comment: "")
  229. alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
  230. // alert.runModal()
  231. alert.beginSheetModal(for: (self.window)!)
  232. }
  233. } else {
  234. if self.finding_ {
  235. return
  236. }
  237. self.finding_ = true
  238. let searchS = self.searchItemView_.inputValue
  239. let opt = self.fetchSearchOptions()
  240. self._beginLoading()
  241. DispatchQueue.global().async {
  242. let datas = self.handdler.pdfView?.document.startFindEditText(from: nil, with: searchS, options: opt)
  243. DispatchQueue.main.async {
  244. self._endLoading()
  245. self.finding_ = false
  246. let sel = datas?.first?.first
  247. if sel == nil {
  248. let alert = NSAlert()
  249. alert.messageText = NSLocalizedString("No related content found, please change keyword.", comment: "")
  250. alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
  251. // alert.runModal()
  252. alert.beginSheetModal(for: (self.window)!)
  253. return
  254. }
  255. self.currentSel = sel
  256. self.handdler.showSelection(sel)
  257. }
  258. }
  259. }
  260. }
  261. }
  262. @objc private func _checkAction(_ sender: NSButton) {
  263. self.currentSel = nil
  264. }
  265. @objc private func _searchTabAction(_ sender: NSButton) {
  266. self.switchType(.search, animate: true)
  267. }
  268. @objc private func _replaceTabAction(_ sender: NSButton) {
  269. self.switchType(.replace, animate: true)
  270. }
  271. @objc private func _replaceAction(_ sender: NSButton) {
  272. let isEditing = self.handdler.pdfView?.isEditing() ?? false
  273. if isEditing == false {
  274. NSSound.beep()
  275. return
  276. }
  277. if let sel = self.currentSel {
  278. let searchS = self.searchItemView_.inputValue
  279. let replaceS = self.replaceItemView_.inputValue
  280. let success = self.handdler.replace(searchS: searchS, replaceS: replaceS, sel: sel) { [weak self] newSel in
  281. self?.handdler.showSelection(newSel)
  282. }
  283. if success {
  284. // self.handdler.showSelection(sel)
  285. }
  286. } else { // 先查找
  287. if self.finding_ {
  288. return
  289. }
  290. self.finding_ = true
  291. let searchS = self.searchItemView_.inputValue
  292. let opt = self.fetchSearchOptions()
  293. self._beginLoading()
  294. DispatchQueue.global().async {
  295. let datas = self.handdler.pdfView?.document.startFindEditText(from: nil, with: searchS, options: opt)
  296. DispatchQueue.main.async {
  297. self._endLoading()
  298. self.finding_ = false
  299. let sel = datas?.first?.first
  300. if sel == nil {
  301. let alert = NSAlert()
  302. alert.messageText = NSLocalizedString("No related content found, please change keyword.", comment: "")
  303. alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
  304. // alert.runModal()
  305. alert.beginSheetModal(for: (self.window)!)
  306. return
  307. }
  308. self.currentSel = sel
  309. self.handdler.showSelection(sel)
  310. }
  311. }
  312. }
  313. }
  314. @objc private func _replaceAllAction(_ sender: NSButton) {
  315. let isEditing = self.handdler.pdfView?.isEditing() ?? false
  316. if isEditing == false {
  317. NSSound.beep()
  318. return
  319. }
  320. let datas = self.handdler.pdfView?.document.findEditSelections() ?? []
  321. if datas.isEmpty {
  322. let alert = NSAlert()
  323. alert.informativeText = NSLocalizedString("No related content found, please change keyword.", comment: "")
  324. alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
  325. // alert.beginSheetModal(for: NSApp.mainWindow!)
  326. // alert.runModal()
  327. alert.beginSheetModal(for: (self.window)!)
  328. return
  329. }
  330. if self.finding_ {
  331. return
  332. }
  333. self.finding_ = true
  334. let searchS = self.searchItemView_.inputValue
  335. let replaceS = self.replaceItemView_.inputValue
  336. self._beginLoading()
  337. DispatchQueue.global().async {
  338. self.handdler.pdfView?.document.replaceAllEditText(with: searchS, toReplace: replaceS)
  339. self.currentSel = nil
  340. DispatchQueue.main.async {
  341. self._endLoading()
  342. self.finding_ = false
  343. self.handdler.pdfView?.setHighlightedSelection(nil, animated: false)
  344. self.handdler.pdfView?.setNeedsDisplayForVisiblePages()
  345. }
  346. }
  347. }
  348. private func fetchSearchOptions() -> CPDFSearchOptions {
  349. var opt = CPDFSearchOptions()
  350. let isCase = KMDataManager.ud_bool(forKey: KMNSearchKey.caseSensitive.botaSearch)
  351. let isWholeWord = KMDataManager.ud_bool(forKey: KMNSearchKey.wholeWords.botaSearch)
  352. if isCase {
  353. opt.insert(.caseSensitive)
  354. }
  355. if isWholeWord {
  356. opt.insert(.matchWholeWord)
  357. }
  358. return opt
  359. }
  360. private func updateViewColor() {
  361. let isDark = KMAppearance.isDarkMode()
  362. if isDark {
  363. self.window?.contentView?.wantsLayer = true
  364. self.window?.contentView?.layer?.backgroundColor = NSColor(hex: "#393C3E").cgColor
  365. } else {
  366. self.window?.contentView?.wantsLayer = true
  367. self.window?.contentView?.layer?.backgroundColor = .white
  368. }
  369. self.switchType(self.type_)
  370. }
  371. func switchType(_ type: KMSearchReplaceType, animate: Bool = false) {
  372. if type == .replace {
  373. if IAPProductsManager.default().isAvailableAllFunction() == false {
  374. // KMPurchaseCompareWindowController.sharedInstance().showWindow(nil)
  375. let winC = KMPurchaseCompareWindowController.sharedInstance()
  376. winC?.showWindow(nil)
  377. guard let win = winC?.window else {
  378. return
  379. }
  380. self.window?.addChildWindow(win, ordered: .above)
  381. return
  382. }
  383. }
  384. self.type_ = type
  385. let isDark = KMAppearance.isDarkMode()
  386. var selectedColor = NSColor(hex: "0E1114")
  387. var unSelectedColor = NSColor(hex: "757780")
  388. if isDark {
  389. selectedColor = .white
  390. unSelectedColor = NSColor(hex: "#7E7F85")
  391. }
  392. if type == .search { // 248 112
  393. // DispatchQueue.main.async {
  394. self.replaceBox.isHidden = true
  395. // }
  396. var frame = self.window?.frame ?? .zero
  397. // let height: CGFloat = 248+20
  398. let height: CGFloat = 112
  399. let heightOffset = frame.size.height - height
  400. frame.origin.y += heightOffset
  401. frame.size.height = height
  402. self.window?.setFrame(frame, display: true, animate: animate)
  403. self.window?.minSize = frame.size
  404. self.window?.maxSize = frame.size
  405. } else if type == .replace { // 388 208
  406. DispatchQueue.main.async {
  407. self.replaceBox.isHidden = false
  408. }
  409. var frame = self.window?.frame ?? .zero
  410. let height:CGFloat = 208
  411. let heightOffset = frame.size.height-height
  412. frame.origin.y += heightOffset
  413. frame.size.height = height
  414. self.window?.setFrame(frame, display: true, animate: animate)
  415. self.window?.minSize = frame.size
  416. self.window?.maxSize = frame.size
  417. // 将事件回调出去
  418. self.replaceCallback?()
  419. }
  420. }
  421. private func _beginLoading() {
  422. self.window?.contentView?.beginLoading()
  423. }
  424. private func _endLoading() {
  425. self.window?.contentView?.endLoading()
  426. }
  427. func startModal(_ sender: AnyObject?) {
  428. NSApp.stopModal()
  429. var modalCode: NSApplication.ModalResponse?
  430. if let _win = self.window {
  431. self._modalSession = NSApp.beginModalSession(for: _win)
  432. repeat {
  433. modalCode = NSApp.runModalSession(self._modalSession!)
  434. } while (modalCode == .continue)
  435. }
  436. }
  437. func endModal(_ sender: AnyObject?) {
  438. if let session = self._modalSession {
  439. NSApp.stopModal()
  440. NSApp.endModalSession(session)
  441. self.window?.orderOut(self)
  442. }
  443. if let winC = self.window?.kmCurrentWindowC, winC.isEqual(to: self) {
  444. self.window?.kmCurrentWindowC = nil
  445. }
  446. }
  447. func search(keyboard: String) {
  448. let isCase = KMDataManager.ud_bool(forKey: KMNSearchKey.caseSensitive.botaSearch)
  449. let isWholeWord = KMDataManager.ud_bool(forKey: KMNSearchKey.wholeWords.botaSearch)
  450. let isEditing = self.handdler.pdfView?.isEditing() ?? false
  451. if isEditing == false {
  452. if self.finding_ {
  453. return
  454. }
  455. self.finding_ = true
  456. self._beginLoading()
  457. self.handdler.search(keyword: keyboard, isCase: isCase, isWholeWord: isWholeWord, callback: { [weak self] datas in
  458. self?.finding_ = false
  459. self?._endLoading()
  460. guard let sels = datas, sels.isEmpty == false else {
  461. let alert = NSAlert()
  462. alert.informativeText = NSLocalizedString("No related content found, please change keyword.", comment: "")
  463. alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
  464. // alert.runModal()
  465. alert.beginSheetModal(for: (self?.window)!)
  466. return
  467. }
  468. if let sel = datas?.first?.selection {
  469. self?.handdler.showIdx = 0
  470. self?.handdler.showSelection(sel)
  471. }
  472. })
  473. } else {
  474. if self.finding_ {
  475. return
  476. }
  477. self.finding_ = true
  478. let searchS = keyboard
  479. let opt = self.fetchSearchOptions()
  480. self._beginLoading()
  481. DispatchQueue.global().async {
  482. let datas = self.handdler.pdfView?.document.findEditAllPageString(searchS, with: opt) ?? []
  483. DispatchQueue.main.async {
  484. self.finding_ = false
  485. self._endLoading()
  486. if datas.isEmpty {
  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.beginSheetModal(for: NSApp.mainWindow!)
  491. // alert.runModal()
  492. alert.beginSheetModal(for: (self.window)!)
  493. return
  494. }
  495. self.currentSel = datas.first?.first
  496. if let sel = self.currentSel {
  497. self.handdler.showSelection(sel)
  498. }
  499. }
  500. }
  501. }
  502. }
  503. }