# Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html from __future__ import annotations from typing import List, Optional, Sequence import aqt from anki.notes import NoteID from aqt import AnkiQt, QWidget from aqt.qt import QDialog, Qt from aqt.utils import ( HelpPage, disable_help_button, openHelp, qconnect, restore_combo_history, restore_combo_index_for_session, restore_is_checked, restoreGeom, save_combo_history, save_combo_index_for_session, save_is_checked, saveGeom, tooltip, tr, ) def find_and_replace( *, mw: AnkiQt, parent: QWidget, note_ids: Sequence[NoteID], search: str, replacement: str, regex: bool, field_name: Optional[str], match_case: bool, ) -> None: mw.perform_op( lambda: mw.col.find_and_replace( note_ids=note_ids, search=search, replacement=replacement, regex=regex, field_name=field_name, match_case=match_case, ), success=lambda out: tooltip( tr.findreplace_notes_updated(changed=out.count, total=len(note_ids)), parent=parent, ), ) def find_and_replace_tag( *, mw: AnkiQt, parent: QWidget, note_ids: Sequence[int], search: str, replacement: str, regex: bool, match_case: bool, ) -> None: mw.perform_op( lambda: mw.col.tags.find_and_replace( note_ids=note_ids, search=search, replacement=replacement, regex=regex, match_case=match_case, ), success=lambda out: tooltip( tr.findreplace_notes_updated(changed=out.count, total=len(note_ids)), parent=parent, ), ) class FindAndReplaceDialog(QDialog): COMBO_NAME = "BrowserFindAndReplace" def __init__( self, parent: QWidget, *, mw: AnkiQt, note_ids: Sequence[NoteID] ) -> None: super().__init__(parent) self.mw = mw self.note_ids = note_ids self.field_names: List[str] = [] # fetch field names and then show mw.query_op( lambda: mw.col.field_names_for_note_ids(note_ids), success=self._show, ) def _show(self, field_names: Sequence[str]) -> None: # add "all fields" and "tags" to the top of the list self.field_names = [ tr.browsing_all_fields(), tr.editing_tags(), ] + list(field_names) disable_help_button(self) self.form = aqt.forms.findreplace.Ui_Dialog() self.form.setupUi(self) self.setWindowModality(Qt.WindowModal) self._find_history = restore_combo_history( self.form.find, self.COMBO_NAME + "Find" ) self.form.find.completer().setCaseSensitivity(Qt.CaseSensitive) self._replace_history = restore_combo_history( self.form.replace, self.COMBO_NAME + "Replace" ) self.form.replace.completer().setCaseSensitivity(Qt.CaseSensitive) restore_is_checked(self.form.re, self.COMBO_NAME + "Regex") restore_is_checked(self.form.ignoreCase, self.COMBO_NAME + "ignoreCase") self.form.field.addItems(self.field_names) restore_combo_index_for_session( self.form.field, self.field_names, self.COMBO_NAME + "Field" ) qconnect(self.form.buttonBox.helpRequested, self.show_help) restoreGeom(self, "findreplace") self.show() self.form.find.setFocus() def accept(self) -> None: saveGeom(self, "findreplace") save_combo_index_for_session(self.form.field, self.COMBO_NAME + "Field") search = save_combo_history( self.form.find, self._find_history, self.COMBO_NAME + "Find" ) replace = save_combo_history( self.form.replace, self._replace_history, self.COMBO_NAME + "Replace" ) regex = self.form.re.isChecked() match_case = not self.form.ignoreCase.isChecked() save_is_checked(self.form.re, self.COMBO_NAME + "Regex") save_is_checked(self.form.ignoreCase, self.COMBO_NAME + "ignoreCase") if self.form.field.currentIndex() == 1: # tags find_and_replace_tag( mw=self.mw, parent=self.parentWidget(), note_ids=self.note_ids, search=search, replacement=replace, regex=regex, match_case=match_case, ) return if self.form.field.currentIndex() == 0: field = None else: field = self.field_names[self.form.field.currentIndex() - 2] find_and_replace( mw=self.mw, parent=self.parentWidget(), note_ids=self.note_ids, search=search, replacement=replace, regex=regex, field_name=field, match_case=match_case, ) super().accept() def show_help(self) -> None: openHelp(HelpPage.BROWSING_FIND_AND_REPLACE)