123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747 |
- //
- // 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
- }
-
- // override func setFrame(from string: NSWindow.PersistableFrameDescriptor) {
- // super.setFrame(from: string)
- // }
- //
- // override func setFrame(_ frameRect: NSRect, display flag: Bool) {
- // let frame = frameRect
- //// let supFrame = self.parent?.frame ?? .zero
- //
- //// KMPrint("frame: \(frame)")
- //// KMPrint("supFrame: \(supFrame)")
- //
- // var theFrame = frame
- // theFrame.origin.x = max(frame.origin.x, 100)
- // theFrame.origin.y = max(frame.origin.y, 100)
- //
- // super.setFrame(theFrame, display: flag)
- // }
- //
- // override func setFrame(_ frameRect: NSRect, display displayFlag: Bool, animate animateFlag: Bool) {
- // super.setFrame(frameRect, display: displayFlag, animate: animateFlag)
- // }
- //
- // override func setFrameOrigin(_ point: NSPoint) {
- // super.setFrameOrigin(point)
- // }
- }
- 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 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
- }
- }
-
- private var searchGroupView_: ComponentGroup?
-
- convenience init(with pdfView: CPDFView?, type: KMNBotaSearchType) {
- self.init(windowNibName: "KMSearchReplaceWindowController")
-
- self.handdler.pdfView = pdfView
- handdler.type = type
- }
- override func windowDidLoad() {
- super.windowDidLoad()
- self.initDefaultValue()
- self.switchType(handdler.type)
-
- // NotificationCenter.default.addObserver(self, selector: #selector(didMoveNotification), name: NSWindow.didMoveNotification, object: self.window)
- // NotificationCenter.default.addObserver(self, selector: #selector(didMoveNotification), name: NSWindow.willMoveNotification, object: self.window)
- }
-
- @objc func didMoveNotification(_ noti: Notification) {
- let window = self.window
- let frame = window?.frame ?? .zero
- let supFrame = window?.parent?.frame ?? .zero
-
- KMPrint("frame: \(frame)")
- KMPrint("supFrame: \(supFrame)")
-
- // var theFrame = frame
- // theFrame.origin.x = max(frame.origin.x, supFrame.origin.x)
- // theFrame.origin.y = max(frame.origin.y, supFrame.origin.y)
- //
- // self.window?.setFrame(theFrame, display: true)
- // self.window?.setFrameOrigin(theFrame.origin)
- }
-
- 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())
- } else if idx == 3 {
- showSearchGroupView(sender: ComponentButton())
- }
- }
- 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
- }
- }
- searchItemView_.input.properties.showSuffix = false
- searchItemView_.input.reloadData()
-
- 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.searchItemView_.input.properties.leftIcon = NSImage(named: "KMImageNameBotaSearch")
- self.searchItemView_.input.reloadData()
- self.replaceItemView_.input.properties.leftIcon = NSImage(named: "KMImagenameBotaSearchInputPrefiex")
- self.replaceItemView_.input.reloadData()
-
- 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 ?? ""
-
- handdler.searchKey = keyborad
- handdler.replaceKey = replaceKey
-
- if results.isEmpty == false {
- handdler.searchResults = results
- self.currentSel = results.first?.selection
- if let sel = self.currentSel {
- self.handdler.showSelection(sel)
- }
- }
-
- updateButtonStatus()
- }
-
- // MARK: - Group View
-
- func showSearchGroupView(sender: ComponentButton) {
- var viewHeight: CGFloat = 8
- var menuItemArr: [ComponentMenuitemProperty] = []
- let titles = ["Search", "Find and Replace", "", "Whole Words", "Case Sensitive"]
- for i in titles {
- if i.isEmpty {
- let menuI = ComponentMenuitemProperty.divider()
- menuItemArr.append(menuI)
- viewHeight += 8
- } else {
- let menuI = ComponentMenuitemProperty(text: KMLocalizedString(i))
- menuItemArr.append(menuI)
- viewHeight += 36
- }
- }
-
- if handdler.type == .search {
- menuItemArr.first?.righticon = NSImage(named: "KMNImageNameMenuSelect")
- } else if handdler.type == .replace {
- let info = menuItemArr.safe_element(for: 1) as? ComponentMenuitemProperty
- info?.righticon = NSImage(named: "KMNImageNameMenuSelect")
- }
- if let info = menuItemArr.safe_element(for: 3) as? ComponentMenuitemProperty {
- if KMDataManager.ud_bool(forKey: KMNSearchKey.wholeWords.botaSearch) {
- info.righticon = NSImage(named: "KMNImageNameMenuSelect")
- }
- }
- if let info = menuItemArr.last {
- if KMDataManager.ud_bool(forKey: KMNSearchKey.caseSensitive.botaSearch) {
- info.righticon = NSImage(named: "KMNImageNameMenuSelect")
- }
- }
-
- let groupView = ComponentGroup.createFromNib(in: ComponentLibrary.shared.componentBundle())
- searchGroupView_ = groupView
- groupView?.groupDelegate = self
- groupView?.frame = CGRectMake(310, 0, 200, viewHeight)
- groupView?.updateGroupInfo(menuItemArr)
-
- let senderView = self.searchItemView_.input
- var point = senderView.convert(senderView.frame.origin, to: nil)
- point.y -= viewHeight
- point.y -= 30
- groupView?.showWithPoint(point, relativeTo: senderView)
- // searchGroupTarget = sender
- }
-
- // 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)
- self._showIndexTip()
- } else {
- if let _ = self.currentSel {
- self.currentSel = self.handdler.pdfView?.document.findForwardEditText()
- if let sel = self.currentSel {
- self.handdler.showIdx -= 1
- self.handdler.showSelection(sel)
- self._showIndexTip()
- } 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.showIdx = 0
- var count = 0
- for i in datas ?? [] {
- count += i.count
- }
- self.handdler.resultCount = count
- self._showIndexTip()
- 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)
- self._showIndexTip()
- } else {
- if let _ = self.currentSel {
- self.currentSel = self.handdler.pdfView?.document.findBackwordEditText()
- if let sel = self.currentSel {
- self.handdler.showIdx += 1
- self.handdler.showSelection(sel)
- self._showIndexTip()
- } 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.handdler.showIdx = 0
- var count = 0
- for i in datas ?? [] {
- count += i.count
- }
- self.handdler.resultCount = count
- self._showIndexTip()
- 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 _showIndexTip() {
- DispatchQueue.main.async {
- if self.handdler.resultCount == 0 {
- self.searchItemView_.input.properties.rightText = ""
- } else {
- self.searchItemView_.input.properties.rightText = "\(self.handdler.showIdx+1)/\(self.handdler.resultCount)"
- }
- self.searchItemView_.input.reloadData()
- }
- }
-
- 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(handdler.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
- }
- }
-
- handdler.type = type
-
- 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) {
- if keyboard.isEmpty {
- handdler.resultCount = 0
- _showIndexTip()
- return
- }
-
- 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.resultCount = sels.count
- self?._showIndexTip()
- 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
- self.handdler.showIdx = 0
- var count = 0
- for i in datas {
- count += i.count
- }
- self.handdler.resultCount = count
- self._showIndexTip()
- if let sel = self.currentSel {
- self.handdler.showSelection(sel)
- }
- }
-
- }
- }
- }
- }
- extension KMSearchReplaceWindowController: ComponentGroupDelegate {
- func componentGroupDidSelect(group: ComponentGroup?, menuItemProperty: ComponentMenuitemProperty?) {
- if group == searchGroupView_ {
- guard let menuI = menuItemProperty else {
- return
- }
- let idx = group?.menuItemArr.firstIndex(of: menuI)
- if idx == 0 { // search
- switchType(.search)
- } else if idx == 1 { // replace
- switchType(.replace)
-
- } else if idx == 3 {
- let key = KMNSearchKey.wholeWords.botaSearch
- let value = KMDataManager.ud_bool(forKey: key)
- KMDataManager.ud_set(!value, forKey: key)
-
- currentSel = nil
- // currentModel_ = nil
- let data = searchItemView_.inputValue
- if data.isEmpty {
- search(keyboard: data)
- }
- } else if idx == 4 {
- let key = KMNSearchKey.caseSensitive.botaSearch
- let value = KMDataManager.ud_bool(forKey: key)
- KMDataManager.ud_set(!value, forKey: key)
-
- currentSel = nil
- // currentModel_ = nil
-
- let data = searchItemView_.inputValue
- if data.isEmpty {
- search(keyboard: data)
- }
- }
- }
- }
- }
|