123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608 |
- //
- // KMSearchReplaceWindowController.swift
- // PDF Reader Pro
- //
- // Created by User-Tangchao on 2024/8/7.
- //
- import Cocoa
- class KMSearchReplaceWindowController_Window: NSWindow {
- override var canBecomeMain: Bool {
- return true
- }
-
- override var canBecomeKey: Bool {
- return true
- }
- }
- @objc enum KMSearchReplaceType: Int {
- case none = 0
- case search = 1
- case replace = 2
- }
- class KMSearchReplaceWindowController: NSWindowController {
- @IBOutlet weak var titleBarBox: NSBox!
- @IBOutlet weak var closeButton: NSButton!
-
- @IBOutlet weak var tabBox: NSBox!
- @IBOutlet weak var searchTabButton: NSButton!
- @IBOutlet weak var replaceTabButton: NSButton!
- @IBOutlet weak var tabBottomLine: NSBox!
- @IBOutlet weak var tabSelectedLine: NSBox!
- @IBOutlet weak var tabSelectedLineLeftConst: NSLayoutConstraint!
-
- @IBOutlet weak var searchBox: NSBox!
- @IBOutlet weak var searchTitleLabel: NSTextField!
- @IBOutlet weak var searchInputBox: NSBox!
- @IBOutlet weak var searchInputView: NSTextField!
- @IBOutlet weak var matchWholeCheck: NSButton!
- @IBOutlet weak var caseSensitiveCheck: NSButton!
- @IBOutlet weak var previousButton: NSButton!
- @IBOutlet weak var nextButton: NSButton!
-
- @IBOutlet weak var replaceBox: NSBox!
- @IBOutlet weak var replaceTitleLabel: NSTextField!
- @IBOutlet weak var replaceInputBox: NSBox!
- @IBOutlet weak var replaceInputView: NSTextField!
-
- @IBOutlet weak var bottomBarBox: NSBox!
- @IBOutlet weak var replaceButton: NSButton!
- @IBOutlet weak var replaceAllButton: NSButton!
-
- var replaceCallback: (() -> Void)?
-
- private var _modalSession: NSApplication.ModalSession?
-
- private var handdler: KMSearchReplaceHanddler = KMSearchReplaceHanddler()
- private var type_: KMSearchReplaceType = .search
-
- private var currentSel: CPDFSelection?
-
- private var finding_ = false
-
- deinit {
- KMPrint("KMSearchReplaceWindowController deinit.")
-
- DistributedNotificationCenter.default().removeObserver(self)
- }
-
- convenience init(with pdfView: CPDFView?, type: KMSearchReplaceType) {
- self.init(windowNibName: "KMSearchReplaceWindowController")
-
- self.handdler.pdfView = pdfView
- self.type_ = type
- }
- override func windowDidLoad() {
- super.windowDidLoad()
- self.initDefaultValue()
- self.switchType(self.type_)
- self.updateViewColor()
- DistributedNotificationCenter.default().addObserver(self, selector: #selector(themeChanged), name: NSApplication.interfaceThemeChangedNotification, object: nil)
- }
-
- func initDefaultValue() {
- self.window?.isMovableByWindowBackground = true
-
- self.window?.contentView?.wantsLayer = true
- self.window?.contentView?.layer?.cornerRadius = 4
- self.window?.contentView?.layer?.masksToBounds = true
-
- self.window?.backgroundColor = .clear
-
- self.titleBarBox.boxType = .custom
- self.titleBarBox.borderWidth = 0
- self.closeButton.imagePosition = .imageOnly
- self.closeButton.image = NSImage(named: "KMImageNameUXIconBtnCloseNor")
- self.closeButton.target = self
- self.closeButton.action = #selector(_closeAction)
-
- self.searchTabButton.target = self
- self.searchTabButton.action = #selector(_searchTabAction)
- self.searchTabButton.title = " \(NSLocalizedString("Search", comment: ""))"
- self.searchTabButton.image = NSImage(named: "KMImageNameSearchIcon")
- self.searchTabButton.imagePosition = .imageLeft
- // self.searchTabButton.imageHugsTitle = true
-
- self.replaceTabButton.target = self
- self.replaceTabButton.action = #selector(_replaceTabAction)
- self.replaceTabButton.title = " \(NSLocalizedString("Replace", comment: ""))"
- self.replaceTabButton.image = NSImage(named: "KMImageNameReplaceIcon")
- self.replaceTabButton.imagePosition = .imageLeft
- self.tabSelectedLine.borderWidth = 0
- self.tabSelectedLine.fillColor = NSColor(hex: "#4982E6")
-
- self.searchBox.borderWidth = 0
- // #0E1114
- self.searchTitleLabel.stringValue = NSLocalizedString("Search", comment: "")
- self.searchTitleLabel.font = NSFont.SFProTextBoldFont(14)
- self.searchInputBox.cornerRadius = 0
- self.searchInputView.drawsBackground = false
- self.searchInputView.isBordered = false
- self.searchInputView.delegate = self
-
- self.matchWholeCheck.title = NSLocalizedString("Whole Words Only", comment: "")
- self.matchWholeCheck.target = self
- self.matchWholeCheck.action = #selector(_checkAction)
- self.matchWholeCheck.state = .off
- self.caseSensitiveCheck.title = NSLocalizedString("Ignore Case", comment: "")
- self.caseSensitiveCheck.target = self
- self.caseSensitiveCheck.action = #selector(_checkAction)
- self.caseSensitiveCheck.state = .off
- self.previousButton.title = NSLocalizedString("Next", comment: "")
- self.previousButton.target = self
- self.previousButton.action = #selector(_nextAction)
- self.nextButton.title = NSLocalizedString("Previous", comment: "")
- self.nextButton.target = self
- self.nextButton.action = #selector(_previousAction)
-
- self.replaceBox.borderWidth = 0
- self.replaceTitleLabel.stringValue = NSLocalizedString("Replace with", comment: "")
- self.replaceTitleLabel.font = NSFont.SFProTextBoldFont(14)
- self.replaceInputBox.cornerRadius = 0
- self.replaceInputView.drawsBackground = false
- self.replaceInputView.isBordered = false
- self.replaceInputView.delegate = self
-
- self.bottomBarBox.borderWidth = 0
- self.replaceButton.title = NSLocalizedString("Replace", comment: "")
- self.replaceButton.target = self
- self.replaceButton.action = #selector(_replaceAction)
- self.replaceAllButton.title = NSLocalizedString("Replace All", comment: "")
- self.replaceAllButton.target = self
- self.replaceAllButton.action = #selector(_replaceAllAction)
-
- if self.searchInputView.stringValue.isEmpty {
- self.previousButton.isEnabled = false
- self.nextButton.isEnabled = false
- self.replaceButton.isEnabled = false
- self.replaceAllButton.isEnabled = false
- } else {
- self.previousButton.isEnabled = true
- self.nextButton.isEnabled = true
- self.replaceButton.isEnabled = true
- self.replaceAllButton.isEnabled = true
-
- self.currentSel = nil
- }
- }
-
- // MARK: - Actions
-
- @objc private func _closeAction(_ sender: NSButton) {
- self.endModal(sender)
-
- 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 {
- let alert = NSAlert()
- alert.messageText = NSLocalizedString("No related content found, please change keyword.", comment: "")
- alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
- // alert.runModal()
- alert.beginSheetModal(for: (self.window)!)
- }
- } else {
- if self.finding_ {
- return
- }
- self.finding_ = true
- let searchS = self.searchInputView.stringValue
- 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 {
- let alert = NSAlert()
- alert.messageText = NSLocalizedString("No related content found, please change keyword.", comment: "")
- alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
- // alert.runModal()
- alert.beginSheetModal(for: (self.window)!)
- 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 {
- let alert = NSAlert()
- alert.messageText = NSLocalizedString("No related content found, please change keyword.", comment: "")
- alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
- // alert.runModal()
- alert.beginSheetModal(for: (self.window)!)
- }
- } else {
- if self.finding_ {
- return
- }
- self.finding_ = true
- let searchS = self.searchInputView.stringValue
- 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 {
- let alert = NSAlert()
- alert.messageText = NSLocalizedString("No related content found, please change keyword.", comment: "")
- alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
- // alert.runModal()
- alert.beginSheetModal(for: (self.window)!)
- 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.searchInputView.stringValue
- let replaceS = self.replaceInputView.stringValue
- 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.searchInputView.stringValue
- 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 {
- let alert = NSAlert()
- alert.messageText = NSLocalizedString("No related content found, please change keyword.", comment: "")
- alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
- // alert.runModal()
- alert.beginSheetModal(for: (self.window)!)
- 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 {
- let alert = NSAlert()
- alert.informativeText = NSLocalizedString("No related content found, please change keyword.", comment: "")
- alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
- // alert.beginSheetModal(for: NSApp.mainWindow!)
- // alert.runModal()
- alert.beginSheetModal(for: (self.window)!)
- return
- }
-
- if self.finding_ {
- return
- }
- self.finding_ = true
- let searchS = self.searchInputView.stringValue
- let replaceS = self.replaceInputView.stringValue
- 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 fetchSearchOptions() -> CPDFSearchOptions {
- var opt = CPDFSearchOptions()
- let isCase = self.caseSensitiveCheck.state == .off
- if isCase {
- opt.insert(.caseSensitive)
- }
- let isWholeWord = self.matchWholeCheck.state == .on
- if isWholeWord {
- opt.insert(.matchWholeWord)
- }
- return opt
- }
-
- private func updateViewColor() {
- let isDark = KMAppearance.isDarkMode()
- if isDark {
- // self.window?.backgroundColor = NSColor(hex: "#393C3E")
- self.window?.contentView?.wantsLayer = true
- self.window?.contentView?.layer?.backgroundColor = NSColor(hex: "#393C3E").cgColor
-
- self.searchInputBox.borderColor = NSColor(hex: "#56585A")
- self.replaceInputBox.borderColor = NSColor(hex: "#56585A")
- } else {
- // self.window?.backgroundColor = .white
- self.window?.contentView?.wantsLayer = true
- self.window?.contentView?.layer?.backgroundColor = .white
-
- self.searchInputBox.borderColor = NSColor(hex: "#DADBDE")
- self.replaceInputBox.borderColor = NSColor(hex: "#DADBDE")
- }
-
- self.switchType(self.type_)
- }
-
- func switchType(_ type: KMSearchReplaceType, animate: Bool = false) {
- if type == .replace {
- if KMMemberInfo.shared.isMemberAllFunction == false {
- KMMemberInfo.shared.advancedFunctionUsage()
- 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
- self.tabSelectedLineLeftConst.animator().constant = 24
-
- self.searchTabButton.setTitleColor(selectedColor)
- self.searchTabButton.image = NSImage(named: "KMImageNameSearchIcon")
- self.replaceTabButton.setTitleColor(unSelectedColor)
- self.replaceTabButton.image = NSImage(named: "KMImageNameReplaceUnselectedIcon")
-
- // DispatchQueue.main.async {
- self.replaceBox.isHidden = true
- self.bottomBarBox.isHidden = true
- // }
-
- var frame = self.window?.frame ?? .zero
- let height: CGFloat = 248+20
- 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
- self.tabSelectedLineLeftConst.animator().constant = 140
-
- self.searchTabButton.setTitleColor(unSelectedColor)
- self.searchTabButton.image = NSImage(named: "KMImageNameSearchUnselectedIcon")
- self.replaceTabButton.setTitleColor(selectedColor)
- self.replaceTabButton.image = NSImage(named: "KMImageNameReplaceIcon")
-
- DispatchQueue.main.async {
- self.replaceBox.isHidden = false
- self.bottomBarBox.isHidden = false
- }
-
- var frame = self.window?.frame ?? .zero
- let height:CGFloat = 388
- 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
- }
- }
-
- // MARK: - Noti Methods
-
- @objc func themeChanged(_ notification: Notification) {
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
- self.updateViewColor()
- }
- }
- }
- extension KMSearchReplaceWindowController: NSTextFieldDelegate {
- func controlTextDidEndEditing(_ obj: Notification) {
-
- }
-
- func controlTextDidChange(_ obj: Notification) {
- if self.searchInputView.isEqual(to: obj.object) { // 搜索输入框
- if self.searchInputView.stringValue.isEmpty {
- self.previousButton.isEnabled = false
- self.nextButton.isEnabled = false
- self.replaceButton.isEnabled = false
- self.replaceAllButton.isEnabled = false
- } else {
- self.previousButton.isEnabled = true
- self.nextButton.isEnabled = true
- self.replaceButton.isEnabled = true
- self.replaceAllButton.isEnabled = true
-
- self.currentSel = nil
- }
- }
- }
-
- func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
- switch commandSelector {
- case #selector(NSResponder.insertNewline(_:)):
- if let inputView = control as? NSTextField {
- // //当当前TextField按下enter
- if inputView == self.searchInputView {
- let isCase = self.caseSensitiveCheck.state == .off
- let isWholeWord = self.matchWholeCheck.state == .on
- let isEditing = self.handdler.pdfView?.isEditing() ?? false
- if isEditing == false {
- if self.finding_ {
- return false
- }
- self.finding_ = true
- self._beginLoading()
- self.handdler.search(keyword: self.searchInputView.stringValue, isCase: isCase, isWholeWord: isWholeWord, callback: { [weak self] datas in
- self?.finding_ = false
- self?._endLoading()
- guard let sels = datas, sels.isEmpty == false else {
- let alert = NSAlert()
- alert.informativeText = NSLocalizedString("No related content found, please change keyword.", comment: "")
- alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
- // alert.runModal()
- alert.beginSheetModal(for: (self?.window)!)
- return
- }
- if let sel = datas?.first?.selection {
- self?.handdler.showIdx = 0
- self?.handdler.showSelection(sel)
- }
- })
- } else {
- if self.finding_ {
- return false
- }
- self.finding_ = true
- let searchS = self.searchInputView.stringValue
- 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 {
- let alert = NSAlert()
- alert.informativeText = NSLocalizedString("No related content found, please change keyword.", comment: "")
- alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
- // alert.beginSheetModal(for: NSApp.mainWindow!)
- // alert.runModal()
- alert.beginSheetModal(for: (self.window)!)
- return
- }
- self.currentSel = datas.first?.first
- if let sel = self.currentSel {
- self.handdler.showSelection(sel)
- }
- }
-
- }
- }
- }
- }
- return true
- default:
- return false
- }
- }
- }
|