123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561 |
- //
- // KMSearchReplaceWindowController.swift
- // PDF Reader Pro
- //
- // Created by User-Tangchao on 2024/8/7.
- //
- import Cocoa
- import KMComponentLibrary
- class KMSearchReplaceWindowController_Window: NSWindow {
- override var canBecomeMain: Bool {
- return true
- }
-
- override var canBecomeKey: Bool {
- return true
- }
- }
- class KMSearchReplaceWindowController: KMNBaseWindowController {
- @IBOutlet weak var titleBarBox: NSBox!
- @IBOutlet weak var tabBox: NSBox!
- @IBOutlet weak var searchBox: NSBox!
- @IBOutlet weak var replaceBox: NSBox!
-
- var replaceCallback: (() -> Void)?
- var itemClick: KMCommonClickBlock?
-
- private var _modalSession: NSApplication.ModalSession?
-
- private var handdler = KMNSearchHanddler()
- private var type_: KMNBotaSearchType = .search
-
- private var currentSel: CPDFSelection?
- private var finding_ = false
-
- private lazy var titleBarView_: KMNSearchReplaceTitleBarView = {
- let view = KMNSearchReplaceTitleBarView()
- return view
- }()
- private lazy var searchItemView_: KMNSearchReplaceSearchItemView = {
- let view = KMNSearchReplaceSearchItemView()
- return view
- }()
- private lazy var replaceItemView_: KMNSearchReplacePopItemView = {
- let view = KMNSearchReplacePopItemView()
- return view
- }()
-
- var previousButton: ComponentButton {
- get {
- return searchItemView_.previousButton
- }
- }
- var nextButton: ComponentButton {
- get {
- return searchItemView_.nextButton
- }
- }
- var replaceAllButton: ComponentButton {
- get {
- return replaceItemView_.replaceAllButton
- }
- }
- var replaceButton: ComponentButton {
- get {
- return replaceItemView_.replaceButton
- }
- }
-
- convenience init(with pdfView: CPDFView?, type: KMNBotaSearchType) {
- self.init(windowNibName: "KMSearchReplaceWindowController")
-
- self.handdler.pdfView = pdfView
- self.type_ = type
- }
- override func windowDidLoad() {
- super.windowDidLoad()
- self.initDefaultValue()
- self.switchType(self.type_)
- }
-
- func initDefaultValue() {
- window?.isMovableByWindowBackground = true
-
- window?.contentView?.wantsLayer = true
- window?.contentView?.layer?.cornerRadius = ComponentLibrary.shared.getComponentValueFromKey("radius/m") as? CGFloat ?? 8
- window?.contentView?.layer?.masksToBounds = true
- window?.backgroundColor = .clear
-
- titleBarBox.boxType = .custom
- titleBarBox.borderWidth = 0
- titleBarBox.contentView = titleBarView_
- titleBarView_.titleLabel.font = .SFProTextRegularFont(14)
- titleBarView_.itemClick = { [unowned self] idx, _ in
- if idx == 1 {
- _closeAction(NSButton())
- } else if idx == 2 {
- _closeAction(NSButton())
-
- itemClick?(1, handdler)
- }
- }
-
- searchBox.borderWidth = 0
- searchBox.contentView = searchItemView_
- searchItemView_.itemClick = { [unowned self] idx, _ in
- if idx == 1 { // Previous
- _previousAction(NSButton())
- } else if idx == 2 { // next
- _nextAction(NSButton())
- }
- }
- searchItemView_.valueDidChange = { [unowned self] value, _ in
- if let data = value as? String {
- handdler.searchKey = data
- search(keyboard: data)
- }
- }
- searchItemView_.inputDidEditBlock = { [unowned self] in
- updateButtonStatus()
- let value = searchItemView_.inputValue
- if value.isEmpty {
- } else {
- currentSel = nil
- }
- }
-
- replaceBox.borderWidth = 0
- replaceBox.contentView = replaceItemView_
- replaceItemView_.itemClick = { [unowned self] idx, _ in
- if idx == 1 {
- _replaceAllAction(NSButton())
- } else if idx == 2 {
- _replaceAction(NSButton())
- }
- }
- replaceItemView_.valueDidChange = { [unowned self] value, _ in
- if let data = value as? String {
- handdler.replaceKey = data
- }
- }
-
- updateButtonStatus()
- if searchItemView_.inputValue.isEmpty {
- } else {
- self.currentSel = nil
- }
- }
-
- override func updateUILanguage() {
- super.updateUILanguage()
-
- KMMainThreadExecute {
- self.titleBarView_.titleLabel.stringValue = KMLocalizedString("Search")
- }
- }
-
- override func updateUIThemeColor() {
- super.updateUIThemeColor()
-
- KMMainThreadExecute {
- self.titleBarView_.titleLabel.textColor = KMNColorTools.colorText_1()
-
- self.updateViewColor()
- }
- }
-
- func updateButtonStatus() {
- let value = searchItemView_.inputValue
- if value.isEmpty {
- previousButton.properties.isDisabled = true
- previousButton.reloadData()
- nextButton.properties.isDisabled = true
- nextButton.reloadData()
- replaceButton.properties.isDisabled = true
- replaceButton.reloadData()
- replaceAllButton.properties.isDisabled = true
- replaceAllButton.reloadData()
- } else {
- previousButton.properties.isDisabled = false
- previousButton.reloadData()
- nextButton.properties.isDisabled = false
- nextButton.reloadData()
- replaceButton.properties.isDisabled = false
- replaceButton.reloadData()
- replaceAllButton.properties.isDisabled = false
- replaceAllButton.reloadData()
- }
- }
-
- func update(keyborad: String?, replaceKey: String?, results: [KMSearchMode]) {
- searchItemView_.inputValue = keyborad ?? ""
- replaceItemView_.inputValue = replaceKey ?? ""
-
- if results.isEmpty == false {
- handdler.searchResults = results
- self.currentSel = results.first?.selection
- if let sel = self.currentSel {
- self.handdler.showSelection(sel)
- }
- }
-
- updateButtonStatus()
- }
-
- // MARK: - Actions
-
- @objc private func _closeAction(_ sender: NSButton) {
- self.window?.orderOut(nil)
- self.handdler.clearData()
- }
-
- @objc private func _previousAction(_ sender: NSButton) {
- let isEditing = self.handdler.pdfView?.isEditing() ?? false
- if isEditing == false {
- guard let model = self.handdler.searchResults.safe_element(for: self.handdler.showIdx-1) as? KMSearchMode else {
- return
- }
- self.handdler.showIdx -= 1
- self.handdler.showSelection(model.selection)
- } else {
- if let _ = self.currentSel {
- self.currentSel = self.handdler.pdfView?.document.findForwardEditText()
- if let sel = self.currentSel {
- self.handdler.showSelection(sel)
- } else {
- _showNoResultsAlert()
- return
- }
- } else {
- if self.finding_ {
- return
- }
- self.finding_ = true
- let searchS = self.searchItemView_.inputValue
- let opt = self.fetchSearchOptions()
- self._beginLoading()
- DispatchQueue.global().async {
- let datas = self.handdler.pdfView?.document.startFindEditText(from: nil, with: searchS, options: opt)
- DispatchQueue.main.async {
- self._endLoading()
- self.finding_ = false
- let sel = datas?.first?.first
- if sel == nil {
- self._showNoResultsAlert()
- return
- }
- self.currentSel = sel
- self.handdler.showSelection(sel)
- }
- }
- }
- }
- }
-
- @objc private func _nextAction(_ sender: NSButton) {
- let isEditing = self.handdler.pdfView?.isEditing() ?? false
- if isEditing == false {
- guard let model = self.handdler.searchResults.safe_element(for: self.handdler.showIdx+1) as? KMSearchMode else {
- return
- }
- self.handdler.showIdx += 1
- self.handdler.showSelection(model.selection)
- } else {
- if let _ = self.currentSel {
- self.currentSel = self.handdler.pdfView?.document.findBackwordEditText()
- if let sel = self.currentSel {
- self.handdler.showSelection(sel)
- } else {
- _showNoResultsAlert()
- }
- } else {
- if self.finding_ {
- return
- }
- self.finding_ = true
- let searchS = self.searchItemView_.inputValue
- let opt = self.fetchSearchOptions()
- self._beginLoading()
- DispatchQueue.global().async {
-
- let datas = self.handdler.pdfView?.document.startFindEditText(from: nil, with: searchS, options: opt)
- DispatchQueue.main.async {
- self._endLoading()
- self.finding_ = false
- let sel = datas?.first?.first
- if sel == nil {
- self._showNoResultsAlert()
- return
- }
- self.currentSel = sel
- self.handdler.showSelection(sel)
- }
- }
- }
- }
- }
-
- @objc private func _checkAction(_ sender: NSButton) {
- self.currentSel = nil
- }
-
- @objc private func _searchTabAction(_ sender: NSButton) {
- self.switchType(.search, animate: true)
- }
-
- @objc private func _replaceTabAction(_ sender: NSButton) {
- self.switchType(.replace, animate: true)
- }
-
- @objc private func _replaceAction(_ sender: NSButton) {
- let isEditing = self.handdler.pdfView?.isEditing() ?? false
- if isEditing == false {
- NSSound.beep()
- return
- }
- if let sel = self.currentSel {
- let searchS = self.searchItemView_.inputValue
- let replaceS = self.replaceItemView_.inputValue
- let success = self.handdler.replace(searchS: searchS, replaceS: replaceS, sel: sel) { [weak self] newSel in
- self?.handdler.showSelection(newSel)
- }
- if success {
- // self.handdler.showSelection(sel)
- }
- } else { // 先查找
- if self.finding_ {
- return
- }
- self.finding_ = true
- let searchS = self.searchItemView_.inputValue
- let opt = self.fetchSearchOptions()
- self._beginLoading()
- DispatchQueue.global().async {
- let datas = self.handdler.pdfView?.document.startFindEditText(from: nil, with: searchS, options: opt)
- DispatchQueue.main.async {
- self._endLoading()
- self.finding_ = false
- let sel = datas?.first?.first
- if sel == nil {
- self._showNoResultsAlert()
- return
- }
- self.currentSel = sel
- self.handdler.showSelection(sel)
- }
- }
- }
- }
-
- @objc private func _replaceAllAction(_ sender: NSButton) {
- let isEditing = self.handdler.pdfView?.isEditing() ?? false
- if isEditing == false {
- NSSound.beep()
- return
- }
-
- let datas = self.handdler.pdfView?.document.findEditSelections() ?? []
- if datas.isEmpty {
- _showNoResultsAlert()
- return
- }
-
- if self.finding_ {
- return
- }
- self.finding_ = true
- let searchS = self.searchItemView_.inputValue
- let replaceS = self.replaceItemView_.inputValue
- self._beginLoading()
- DispatchQueue.global().async {
- self.handdler.pdfView?.document.replaceAllEditText(with: searchS, toReplace: replaceS)
- self.currentSel = nil
-
- DispatchQueue.main.async {
- self._endLoading()
- self.finding_ = false
- self.handdler.pdfView?.setHighlightedSelection(nil, animated: false)
- self.handdler.pdfView?.setNeedsDisplayForVisiblePages()
- }
- }
- }
-
- private func _showNoResultsAlert() {
- _ = _showAlert(style: .critical, message: KMLocalizedString("No related content found, please change keyword."), info: "", buttons: [KMLocalizedString("OK", comment: "")])
- }
-
- private func _showAlert(style: NSAlert.Style, message: String, info: String, buttons: [String]) -> NSApplication.ModalResponse {
- let alert = NSAlert()
- alert.alertStyle = style
- alert.messageText = message
- alert.informativeText = info
- for button in buttons {
- alert.addButton(withTitle: button)
- }
- return alert.runModal()
- }
-
- private func fetchSearchOptions() -> CPDFSearchOptions {
- var opt = CPDFSearchOptions()
- let isCase = KMDataManager.ud_bool(forKey: KMNSearchKey.caseSensitive.botaSearch)
- let isWholeWord = KMDataManager.ud_bool(forKey: KMNSearchKey.wholeWords.botaSearch)
- if isCase {
- opt.insert(.caseSensitive)
- }
- if isWholeWord {
- opt.insert(.matchWholeWord)
- }
- return opt
- }
-
- private func updateViewColor() {
- let isDark = KMAppearance.isDarkMode()
- if isDark {
- self.window?.contentView?.wantsLayer = true
- self.window?.contentView?.layer?.backgroundColor = NSColor(hex: "#393C3E").cgColor
- } else {
- self.window?.contentView?.wantsLayer = true
- self.window?.contentView?.layer?.backgroundColor = .white
- }
-
- self.switchType(self.type_)
- }
-
- func switchType(_ type: KMNBotaSearchType, animate: Bool = false) {
- if type == .replace {
- if IAPProductsManager.default().isAvailableAllFunction() == false {
- let winC = KMPurchaseCompareWindowController.sharedInstance()
- winC?.showWindow(nil)
- guard let win = winC?.window else {
- return
- }
- self.window?.addChildWindow(win, ordered: .above)
- return
- }
- }
-
- self.type_ = type
- let isDark = KMAppearance.isDarkMode()
- var selectedColor = NSColor(hex: "0E1114")
- var unSelectedColor = NSColor(hex: "757780")
- if isDark {
- selectedColor = .white
- unSelectedColor = NSColor(hex: "#7E7F85")
- }
-
- if type == .search { // 248 112
- self.replaceBox.isHidden = true
-
- var frame = self.window?.frame ?? .zero
- let height: CGFloat = 112
- let heightOffset = frame.size.height - height
- frame.origin.y += heightOffset
- frame.size.height = height
- self.window?.setFrame(frame, display: true, animate: animate)
- self.window?.minSize = frame.size
- self.window?.maxSize = frame.size
- } else if type == .replace { // 388 208
- DispatchQueue.main.async {
- self.replaceBox.isHidden = false
- }
-
- var frame = self.window?.frame ?? .zero
- let height:CGFloat = 208
- let heightOffset = frame.size.height-height
- frame.origin.y += heightOffset
- frame.size.height = height
- self.window?.setFrame(frame, display: true, animate: animate)
- self.window?.minSize = frame.size
- self.window?.maxSize = frame.size
-
- // 将事件回调出去
- self.replaceCallback?()
- }
- }
-
- private func _beginLoading() {
- self.window?.contentView?.beginLoading()
- }
-
- private func _endLoading() {
- self.window?.contentView?.endLoading()
- }
-
- func startModal(_ sender: AnyObject?) {
- NSApp.stopModal()
-
- var modalCode: NSApplication.ModalResponse?
- if let _win = self.window {
- self._modalSession = NSApp.beginModalSession(for: _win)
- repeat {
- modalCode = NSApp.runModalSession(self._modalSession!)
- } while (modalCode == .continue)
- }
- }
-
- func endModal(_ sender: AnyObject?) {
- if let session = self._modalSession {
- NSApp.stopModal()
- NSApp.endModalSession(session)
- self.window?.orderOut(self)
- }
- if let winC = self.window?.kmCurrentWindowC, winC.isEqual(to: self) {
- self.window?.kmCurrentWindowC = nil
- }
- }
-
- func search(keyboard: String) {
- let isCase = KMDataManager.ud_bool(forKey: KMNSearchKey.caseSensitive.botaSearch)
- let isWholeWord = KMDataManager.ud_bool(forKey: KMNSearchKey.wholeWords.botaSearch)
- let isEditing = self.handdler.pdfView?.isEditing() ?? false
- if isEditing == false {
- if self.finding_ {
- return
- }
- self.finding_ = true
- self._beginLoading()
- self.handdler.search(keyword: keyboard, isCase: isCase, isWholeWord: isWholeWord, callback: { [weak self] datas in
- self?.finding_ = false
- self?._endLoading()
-
- guard let sels = self?.handdler.searchResults, sels.isEmpty == false else {
- self?._showNoResultsAlert()
- return
- }
- if let sel = sels.first?.selection {
- self?.handdler.showIdx = 0
- self?.handdler.showSelection(sel)
- }
- })
- } else {
- if self.finding_ {
- return
- }
- self.finding_ = true
- let searchS = keyboard
- let opt = self.fetchSearchOptions()
- self._beginLoading()
- DispatchQueue.global().async {
- let datas = self.handdler.pdfView?.document.findEditAllPageString(searchS, with: opt) ?? []
- DispatchQueue.main.async {
- self.finding_ = false
- self._endLoading()
- if datas.isEmpty {
- self._showNoResultsAlert()
- return
- }
- self.currentSel = datas.first?.first
- if let sel = self.currentSel {
- self.handdler.showSelection(sel)
- }
- }
-
- }
- }
- }
- }
|