b9251290ca
This adds Python 3.9 and 3.10 typing syntax to files that import attributions from __future___. Python 3.9 should be able to cope with the 3.10 syntax, but Python 3.8 will no longer work. On Windows/Mac, install the latest Python 3.9 version from python.org. There are currently no orjson wheels for Python 3.10 on Windows/Mac, which will break the build unless you have Rust installed separately. On Linux, modern distros should have Python 3.9 available already. If you're on an older distro, you'll need to build Python from source first.
170 lines
5.2 KiB
Python
170 lines
5.2 KiB
Python
# 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 Sequence
|
|
|
|
import aqt
|
|
from anki.notes import NoteId
|
|
from aqt import AnkiQt
|
|
from aqt.operations import QueryOp
|
|
from aqt.operations.note import find_and_replace
|
|
from aqt.operations.tag import find_and_replace_tag
|
|
from aqt.qt import *
|
|
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,
|
|
)
|
|
|
|
|
|
class FindAndReplaceDialog(QDialog):
|
|
COMBO_NAME = "BrowserFindAndReplace"
|
|
|
|
def __init__(
|
|
self,
|
|
parent: QWidget,
|
|
*,
|
|
mw: AnkiQt,
|
|
note_ids: Sequence[NoteId],
|
|
field: str | None = None,
|
|
) -> None:
|
|
"""
|
|
If 'field' is passed, only this is added to the field selector.
|
|
Otherwise, the fields belonging to the 'note_ids' are added.
|
|
"""
|
|
super().__init__(parent)
|
|
self.mw = mw
|
|
self.note_ids = note_ids
|
|
self.field_names: list[str] = []
|
|
self._field = field
|
|
|
|
if field:
|
|
self._show([field])
|
|
elif note_ids:
|
|
# fetch field names and then show
|
|
QueryOp(
|
|
parent=mw,
|
|
op=lambda col: col.field_names_for_note_ids(note_ids),
|
|
success=self._show,
|
|
).run_in_background()
|
|
else:
|
|
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)
|
|
|
|
if not self.note_ids:
|
|
# no selected notes to affect
|
|
self.form.selected_notes.setChecked(False)
|
|
self.form.selected_notes.setEnabled(False)
|
|
elif self._field:
|
|
self.form.selected_notes.setChecked(False)
|
|
|
|
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)
|
|
if self._field:
|
|
self.form.field.setCurrentIndex(self.field_names.index(self._field))
|
|
else:
|
|
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 not self.form.selected_notes.isChecked():
|
|
# an empty list means *all* notes
|
|
self.note_ids = []
|
|
|
|
# tags?
|
|
if self.form.field.currentIndex() == 1:
|
|
op = find_and_replace_tag(
|
|
parent=self.parentWidget(),
|
|
note_ids=self.note_ids,
|
|
search=search,
|
|
replacement=replace,
|
|
regex=regex,
|
|
match_case=match_case,
|
|
)
|
|
else:
|
|
# fields
|
|
if self.form.field.currentIndex() == 0:
|
|
field = None
|
|
else:
|
|
field = self.field_names[self.form.field.currentIndex()]
|
|
|
|
op = find_and_replace(
|
|
parent=self.parentWidget(),
|
|
note_ids=self.note_ids,
|
|
search=search,
|
|
replacement=replace,
|
|
regex=regex,
|
|
field_name=field,
|
|
match_case=match_case,
|
|
)
|
|
|
|
if not self.note_ids:
|
|
op.success(
|
|
lambda out: tooltip(
|
|
tr.browsing_notes_updated(count=out.count),
|
|
parent=self.parentWidget(),
|
|
)
|
|
)
|
|
op.run_in_background()
|
|
|
|
super().accept()
|
|
|
|
def show_help(self) -> None:
|
|
openHelp(HelpPage.BROWSING_FIND_AND_REPLACE)
|