Browse Source

【搜索替换】补充替换逻辑

tangchao 7 months ago
parent
commit
30de95610f

+ 19 - 0
PDF Office/PDF Master/Class/PDFWindowController/ViewController/KMMainViewController+Action.swift

@@ -4596,6 +4596,15 @@ extension KMMainViewController : KMMainToolbarControllerDelegate {
             if index == 11 {
                 self.view.window?.makeFirstResponder(nil)
                 let winC = KMSearchReplaceWindowController(with: self.listView, type: .search)
+                winC.replaceCallback = { [weak self] in
+                    let toolMode = self?.listView?.toolMode ?? .none
+                    let isEditing = self?.listView?.isEditing() ?? false
+                    if toolMode == .editPDFToolMode && isEditing {
+                        
+                    } else { // 进入内容编辑模式
+                        self?.toolbarController.clickItem(KMDocumentEditToolbarItemIdentifier)
+                    }
+                }
                 winC.startModal(nil)
             } else if index == 12 {
                 if IAPProductsManager.default().isAvailableAllFunction() == false {
@@ -4605,7 +4614,17 @@ extension KMMainViewController : KMMainToolbarControllerDelegate {
                 
                 self.view.window?.makeFirstResponder(nil)
                 let winC = KMSearchReplaceWindowController(with: self.listView, type: .replace)
+                winC.replaceCallback = { [weak self] in
+                    let toolMode = self?.listView?.toolMode ?? .none
+                    let isEditing = self?.listView?.isEditing() ?? false
+                    if toolMode == .editPDFToolMode && isEditing {
+                        
+                    } else { // 进入内容编辑模式
+                        self?.toolbarController.clickItem(KMDocumentEditToolbarItemIdentifier)
+                    }
+                }
                 winC.startModal(nil)
+                
             } else {
                 self.toolbarController.showFindBar()
             }

+ 8 - 0
PDF Office/PDF Master/Class/Tools/Search/Tools/KMSearchReplaceHanddler.swift

@@ -124,6 +124,14 @@ class KMSearchReplaceHanddler: NSObject {
         }
     }
     
+//    func findEdit
+    
+    func replace(searchS: String, replaceS: String?, sel: CPDFSelection, callback: @escaping ((CPDFSelection?)->Void)) -> Bool {
+        return self.pdfView?.document.replace(with: sel, search: searchS, toReplace: replaceS, completionHandler: { newSel in
+            callback(newSel)
+        }) ?? false
+    }
+    
     func showSelection(_ sel: CPDFSelection?) {
         guard let theSel = sel else {
             return

+ 191 - 8
PDF Office/PDF Master/Class/Tools/Search/Window/KMSearchReplaceWindowController.swift

@@ -44,15 +44,23 @@ class KMSearchReplaceWindowController: NSWindowController {
     @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?
+    
     deinit {
         KMPrint("KMSearchReplaceWindowController deinit.")
     }
@@ -112,8 +120,20 @@ class KMSearchReplaceWindowController: NSWindowController {
         self.nextButton.target = self
         self.nextButton.action = #selector(_nextAction)
         
+        self.replaceBox.borderWidth = 0
+        self.replaceTitleLabel.stringValue = NSLocalizedString("Replace", comment: "")
+        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)
     }
     
     // MARK: - Actions
@@ -123,19 +143,85 @@ class KMSearchReplaceWindowController: NSWindowController {
     }
     
     @objc private func _previousAction(_ sender: NSButton) {
-        guard let model = self.handdler.searchResults.safe_element(for: self.handdler.showIdx+1) as? KMSearchMode else {
-            return
+        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("The search item was not found.", comment: "")
+                    alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
+                    alert.runModal()
+                }
+            } else {
+                let searchS = self.searchInputView.stringValue
+                let opt = self.fetchSearchOptions()
+                DispatchQueue.global().async {
+                    let datas = self.handdler.pdfView?.document.startFindEditText(from: nil, with: searchS, options: opt)
+                    DispatchQueue.main.async {
+                        let sel = datas?.first?.first
+                        if sel == nil {
+                            let alert = NSAlert()
+                            alert.messageText = NSLocalizedString("The search item was not found.", comment: "")
+                            alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
+                            alert.runModal()
+                            return
+                        }
+                        self.currentSel = sel
+                        self.handdler.showSelection(sel)
+                    }
+                }
+            }
         }
-        self.handdler.showIdx += 1
-        self.handdler.showSelection(model.selection)
     }
     
     @objc private func _nextAction(_ sender: NSButton) {
-        guard let model = self.handdler.searchResults.safe_element(for: self.handdler.showIdx-1) as? KMSearchMode else {
-            return
+        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("The search item was not found.", comment: "")
+                    alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
+                    alert.runModal()
+                }
+            } else {
+                let searchS = self.searchInputView.stringValue
+                let opt = self.fetchSearchOptions()
+                DispatchQueue.global().async {
+                    let datas = self.handdler.pdfView?.document.startFindEditText(from: nil, with: searchS, options: opt)
+                    DispatchQueue.main.async {
+                        let sel = datas?.first?.first
+                        if sel == nil {
+                            let alert = NSAlert()
+                            alert.messageText = NSLocalizedString("The search item was not found.", comment: "")
+                            alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
+                            alert.runModal()
+                            return
+                        }
+                        self.currentSel = sel
+                        self.handdler.showSelection(sel)
+                    }
+                }
+            }
         }
-        self.handdler.showIdx -= 1
-        self.handdler.showSelection(model.selection)
     }
     
     @objc private func _searchTabAction(_ sender: NSButton) {
@@ -146,6 +232,84 @@ class KMSearchReplaceWindowController: NSWindowController {
         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 { // 先查找
+            let searchS = self.searchInputView.stringValue
+            let opt = self.fetchSearchOptions()
+            DispatchQueue.global().async {
+                let datas = self.handdler.pdfView?.document.startFindEditText(from: nil, with: searchS, options: opt)
+                DispatchQueue.main.async {
+                    let sel = datas?.first?.first
+                    if sel == nil {
+                        let alert = NSAlert()
+                        alert.messageText = NSLocalizedString("The search item was not found.", comment: "")
+                        alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
+                        alert.runModal()
+                        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("The search item was not found.", comment: "")
+            alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
+            alert.beginSheetModal(for: NSApp.mainWindow!)
+            return
+        }
+        
+        let searchS = self.searchInputView.stringValue
+        let replaceS = self.replaceInputView.stringValue
+        DispatchQueue.global().async {
+            self.handdler.pdfView?.document.replaceAllEditText(with: searchS, toReplace: replaceS)
+            self.currentSel = nil
+            
+            DispatchQueue.main.async {
+                self.handdler.pdfView?.setHighlightedSelection(nil, animated: false)
+                self.handdler.pdfView?.setNeedsDisplayForVisiblePages()
+            }
+        }
+    }
+    
+    private func fetchSearchOptions() -> CPDFSearchOptions {
+        var opt = CPDFSearchOptions()
+        let isCase = self.caseSensitiveCheck.state == .on
+        if isCase {
+            opt.insert(.caseSensitive)
+        }
+        let isWholeWord = self.matchWholeCheck.state == .on
+        if isWholeWord {
+            opt.insert(.matchWholeWord)
+        }
+        return opt
+    }
+    
     func switchType(_ type: KMSearchReplaceType, animate: Bool = false) {
         if type == .replace {
             if IAPProductsManager.default().isAvailableAllFunction() == false {
@@ -200,6 +364,9 @@ class KMSearchReplaceWindowController: NSWindowController {
             self.window?.setFrame(frame, display: true, animate: animate)
             self.window?.minSize = frame.size
             self.window?.maxSize = frame.size
+            
+            // 将事件回调出去
+            self.replaceCallback?()
         }
     }
     
@@ -233,6 +400,22 @@ extension KMSearchReplaceWindowController: NSTextFieldDelegate {
         
     }
     
+    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
+            }
+        }
+    }
+    
     func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
         switch commandSelector {
         case #selector(NSResponder.insertNewline(_:)):

+ 45 - 0
PDF Office/PDF Master/Class/Tools/Search/Window/KMSearchReplaceWindowController.xib

@@ -17,7 +17,10 @@
                 <outlet property="replaceAllButton" destination="fxe-UM-ZCK" id="UgL-wl-Ytq"/>
                 <outlet property="replaceBox" destination="4kx-6q-nJ8" id="fTS-By-eXX"/>
                 <outlet property="replaceButton" destination="3l4-91-BR7" id="7ao-WQ-KzS"/>
+                <outlet property="replaceInputBox" destination="LkR-Op-2Jo" id="QRf-mq-z6Y"/>
+                <outlet property="replaceInputView" destination="7BJ-XF-ZVY" id="4hW-bO-JeZ"/>
                 <outlet property="replaceTabButton" destination="glm-Go-gH2" id="Wg5-Jc-yco"/>
+                <outlet property="replaceTitleLabel" destination="Y3N-ik-Fnb" id="581-K4-k8r"/>
                 <outlet property="searchBox" destination="cdc-3d-gh5" id="9Se-Lu-XEN"/>
                 <outlet property="searchInputBox" destination="Ej6-th-OuJ" id="2SX-y1-k6p"/>
                 <outlet property="searchInputView" destination="xLT-x3-sTt" id="XDG-eW-Z14"/>
@@ -248,6 +251,48 @@
                         <view key="contentView" id="ghJ-Ir-m60">
                             <rect key="frame" x="1" y="1" width="278" height="78"/>
                             <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                            <subviews>
+                                <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Y3N-ik-Fnb">
+                                    <rect key="frame" x="14" y="46" width="37" height="16"/>
+                                    <textFieldCell key="cell" lineBreakMode="clipping" title="Label" id="HiA-Gn-RRu">
+                                        <font key="font" usesAppearanceFont="YES"/>
+                                        <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+                                        <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
+                                    </textFieldCell>
+                                </textField>
+                                <box boxType="custom" cornerRadius="4" title="Box" translatesAutoresizingMaskIntoConstraints="NO" id="LkR-Op-2Jo">
+                                    <rect key="frame" x="16" y="0.0" width="246" height="28"/>
+                                    <view key="contentView" id="op8-Z1-sL4">
+                                        <rect key="frame" x="1" y="1" width="244" height="26"/>
+                                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                                        <subviews>
+                                            <textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="7BJ-XF-ZVY">
+                                                <rect key="frame" x="10" y="3" width="224" height="21"/>
+                                                <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" id="hZn-NQ-muC">
+                                                    <font key="font" usesAppearanceFont="YES"/>
+                                                    <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+                                                    <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
+                                                </textFieldCell>
+                                            </textField>
+                                        </subviews>
+                                        <constraints>
+                                            <constraint firstItem="7BJ-XF-ZVY" firstAttribute="leading" secondItem="op8-Z1-sL4" secondAttribute="leading" constant="10" id="P7g-Zd-3HH"/>
+                                            <constraint firstAttribute="trailing" secondItem="7BJ-XF-ZVY" secondAttribute="trailing" constant="10" id="k9I-1p-gST"/>
+                                            <constraint firstItem="7BJ-XF-ZVY" firstAttribute="centerY" secondItem="op8-Z1-sL4" secondAttribute="centerY" id="umO-JG-ZLp"/>
+                                        </constraints>
+                                    </view>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="28" id="heN-CK-NtC"/>
+                                    </constraints>
+                                </box>
+                            </subviews>
+                            <constraints>
+                                <constraint firstAttribute="bottom" secondItem="LkR-Op-2Jo" secondAttribute="bottom" id="8jk-KG-3vv"/>
+                                <constraint firstItem="LkR-Op-2Jo" firstAttribute="leading" secondItem="ghJ-Ir-m60" secondAttribute="leading" constant="16" id="IYD-mQ-9Cf"/>
+                                <constraint firstAttribute="trailing" secondItem="LkR-Op-2Jo" secondAttribute="trailing" constant="16" id="JrJ-wR-jnQ"/>
+                                <constraint firstItem="Y3N-ik-Fnb" firstAttribute="leading" secondItem="ghJ-Ir-m60" secondAttribute="leading" constant="16" id="V2h-bT-qsV"/>
+                                <constraint firstItem="Y3N-ik-Fnb" firstAttribute="top" secondItem="ghJ-Ir-m60" secondAttribute="top" constant="16" id="dqF-Bi-4G2"/>
+                            </constraints>
                         </view>
                         <constraints>
                             <constraint firstAttribute="height" constant="80" id="qQI-0G-n5r"/>