From bdc50f27b33e0fbe97408922c4cea5d3d9dc34b3 Mon Sep 17 00:00:00 2001 From: FileX <79639219+cd-FileX@users.noreply.github.com> Date: Mon, 15 Apr 2024 17:14:31 +0000 Subject: [PATCH] Settings update (#7) * Settings update - Changed order and grouped settings - Changed config.md to new version & html for correct display - Added settings help button for additional descriptions - Added button to settings to view log file --------- Co-authored-by: FileX Co-authored-by: Ren Tatsumoto --- common.py | 10 +++++- config.md | 54 ++++++++++++++++++++--------- config.py | 7 ++-- edit_window.py | 4 +-- note_importer.py | 2 +- settings_dialog.py | 85 ++++++++++++++++++++++++++++++++++++++-------- 6 files changed, 123 insertions(+), 39 deletions(-) diff --git a/common.py b/common.py index 8cb4b9e..58011ba 100644 --- a/common.py +++ b/common.py @@ -23,11 +23,12 @@ WINDOW_STATE_FILE_PATH = os.path.join(USER_FILES_DIR_PATH, "window_state.json") CLOSE_ICON_PATH = os.path.join(IMG_DIR_PATH, "close.png") PLAY_ICON_PATH = os.path.join(IMG_DIR_PATH, "play-button.svg") +CONFIG_MD_PATH = os.path.join(ADDON_DIR_PATH, "config.md") for directory in (WEB_DIR_PATH, USER_FILES_DIR_PATH, IMG_DIR_PATH): assert os.path.isdir(directory), f"Path to directory must be valid: {directory}" -for file in (CLOSE_ICON_PATH, PLAY_ICON_PATH): +for file in (CLOSE_ICON_PATH, PLAY_ICON_PATH, CONFIG_MD_PATH): assert os.path.isfile(file), f"Path to file must be valid: {file}" @@ -57,6 +58,13 @@ def write(self, msg: str) -> None: def __call__(self, *args, **kwargs): return self.write(*args, **kwargs) + def read(self) -> str: + try: + with open(DEBUG_LOG_FILE_PATH, encoding="utf-8") as lf: + return lf.read() + except FileNotFoundError: + return "" + def close(self): if self._logfile and not self._logfile.closed: self.write("closing debug log.") diff --git a/config.md b/config.md index ce11fd7..747831e 100644 --- a/config.md +++ b/config.md @@ -1,22 +1,44 @@ -## Cross-Profile Search and Import +

Cross-Profile Search and Import

-**Anki needs to be restarted after changing the config.** +

+Anki needs to be restarted after changing the config file directly. +

-### List of options: +

List of options

-* `enable_debug_log` - print debug information to `stdout` and to a log file. - Log location: `~/.local/share/Anki2/cropro.log` (GNU systems). -* `max_displayed_notes` - how many search result to display -* `exported_tag` - tag that is added to cards in the other profile - so that you could easily find and delete them later. -* `hidden_fields` - contents of fields that contain these keywords won't be shown. -* `allow_empty_search` - Search notes even if the search field is emtpy. May be slow. -* `call_add_cards_hook` - Calls the `add_cards_did_add_note` hook as soon as a note - is imported through the main CroPro window. - For addon evaluation purposes. +
+ General +
    +
  • max_displayed_notes | how many search result to display on one page
  • +
  • hidden_fields | contents of fields that contain these keywords won't be shown.
  • +
  • skip_duplicates | Skips cards, which are already existent in the collection
  • +
  • copy_tags | Adds the category in case of web search and tags in all cases as anki tags
  • +
  • search_online | Toggle between local profile's and immersion kit's search
  • +
  • show_note_preview | Toggles the preview on the right side when having a card selected
  • +
+
---- +
+ Local Search +
    +
  • allow_empty_search | Search notes even if the search field is emtpy. Will show EVERY card you got (very slow)
  • +
  • copy_card_data | Copies data like due date
  • +
  • exported_tag | Tag added to other profile's cards when imported
  • +
+
-If you enjoy this add-on, please consider supporting my work by -**[pledging your support on Patreon](https://www.patreon.com/tatsumoto_ren)**. +
+High level settings +
    +
  • timeout_seconds | How many seconds should we try to find cards online before giving up
  • +
  • enable_debug_log | print debug information to stdout and to a log file.
    + Location: ~/.local/share/Anki2/subsearch_debug.log (GNU systems) or %APPDATA%/Anki2/subsearch_debug.log (Windows).
  • +
  • call_add_cards_hook | Calls the add_cards_did_add_note hook as soon as a note is imported.
    + For addon evaluation purposes. (example)
  • +
+
+ +

If you enjoy this add-on, please consider supporting my work by +making a donation. Thank you so much! +

diff --git a/config.py b/config.py index 7c9b5a6..74169c1 100644 --- a/config.py +++ b/config.py @@ -8,15 +8,15 @@ class CroProConfig(AddonConfigManager): @property - def tag_original_notes(self) -> Optional[str]: + def exported_tag(self) -> Optional[str]: """ Tag that is added to original notes (in the other profile) to mark that they have been copied to the current profile. """ return self["exported_tag"].strip() - @tag_original_notes.setter - def tag_original_notes(self, new_value: str) -> None: + @exported_tag.setter + def exported_tag(self, new_value: str) -> None: self["exported_tag"] = new_value.strip() @property @@ -107,5 +107,4 @@ def call_add_cards_hook(self) -> bool: """ return self["call_add_cards_hook"] - config = CroProConfig() diff --git a/edit_window.py b/edit_window.py index b2a4e77..6a545d3 100644 --- a/edit_window.py +++ b/edit_window.py @@ -83,9 +83,9 @@ def create_window(self, other_note: Optional[Note] = None) -> NoteId: if "tags" in other_note and config.copy_tags: self.new_note.tags = [tag for tag in other_note.tags if tag != "leech"] - if config.tag_original_notes: + if config.exported_tag: assert other_note.id, "Other note must be in the database." - other_note.add_tag(config.tag_original_notes) + other_note.add_tag(config.exported_tag) other_note.flush() if d := current_add_dialog(): diff --git a/note_importer.py b/note_importer.py index bce45ea..3742243 100644 --- a/note_importer.py +++ b/note_importer.py @@ -249,7 +249,7 @@ def _construct_new_note( return NoteCreateResult(new_note, NoteCreateStatus.connection_error) else: copy_media_files(new_note, other_note) - if tag := config.tag_original_notes: + if tag := config.exported_tag: other_note.add_tag(tag) other_note.flush() if config.copy_card_data: diff --git a/settings_dialog.py b/settings_dialog.py index 0dfb314..9ebd444 100644 --- a/settings_dialog.py +++ b/settings_dialog.py @@ -2,11 +2,12 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html from aqt.qt import * -from aqt.utils import restoreGeom, saveGeom, disable_help_button +from aqt.utils import restoreGeom, saveGeom, disable_help_button, showText +from aqt.webview import AnkiWebView -from .ajt_common.utils import ui_translate from .ajt_common.about_menu import tweak_window -from .common import ADDON_NAME, DEBUG_LOG_FILE_PATH +from .ajt_common.utils import ui_translate +from .common import ADDON_NAME, DEBUG_LOG_FILE_PATH, LogDebug, CONFIG_MD_PATH from .config import config from .widgets.item_edit import ItemEditBox from .widgets.utils import CroProSpinBox @@ -23,6 +24,7 @@ def make_checkboxes() -> dict[str, QCheckBox]: return d +BUT_HELP = QDialogButtonBox.StandardButton.Help BUT_OK = QDialogButtonBox.StandardButton.Ok BUT_CANCEL = QDialogButtonBox.StandardButton.Cancel @@ -33,12 +35,14 @@ class CroProSettingsDialog(QDialog): def __init__(self, *args, **kwargs) -> None: QDialog.__init__(self, *args, **kwargs) disable_help_button(self) + self.tab_view = QTabWidget() self.checkboxes = make_checkboxes() - self.tag_edit = QLineEdit(config.tag_original_notes) + self.tag_edit = QLineEdit(config.exported_tag) self.max_notes_edit = CroProSpinBox(min_val=10, max_val=10_000, step=50, value=config.max_displayed_notes) self.hidden_fields = ItemEditBox("Hidden fields", initial_values=config.hidden_fields) self.web_timeout_spinbox = CroProSpinBox(min_val=1, max_val=999, step=1, value=config.timeout_seconds) - self.button_box = QDialogButtonBox(BUT_OK | BUT_CANCEL) + self.button_box = QDialogButtonBox(BUT_HELP | BUT_OK | BUT_CANCEL) + self._create_tabs() self._setup_ui() self.connect_widgets() self.add_tooltips() @@ -53,25 +57,49 @@ def _setup_ui(self) -> None: def _make_layout(self) -> QLayout: layout = QVBoxLayout() - layout.addLayout(self._make_form()) - layout.addStretch() + layout.addWidget(self.tab_view) layout.addWidget(self.button_box) return layout - def _make_form(self) -> QLayout: - layout = QFormLayout() + def _create_tabs(self) -> None: + self.tab_view.addTab(self._make_general_tab(), "General") + self.tab_view.addTab(self._make_local_tab(), "Local Search") + self.tab_view.addTab(self._make_hl_tab(), "High Level") + + def _make_general_tab(self) -> QWidget: + widget = QWidget() + widget.setLayout(layout := QFormLayout()) layout.addRow("Max displayed notes", self.max_notes_edit) - layout.addRow("Tag original cards with", self.tag_edit) - layout.addRow("Web download timeout", self.web_timeout_spinbox) layout.addRow(self.hidden_fields) + layout.addRow(self.checkboxes["skip_duplicates"]) + layout.addRow(self.checkboxes["copy_tags"]) + layout.addRow(self.checkboxes["preview_on_right_side"]) + layout.addRow(self.checkboxes["search_the_web"]) + return widget + + def _make_local_tab(self) -> QWidget: + widget = QWidget() + widget.setLayout(layout := QFormLayout()) + layout.addRow(self.checkboxes["allow_empty_search"]) + layout.addRow(self.checkboxes["copy_card_data"]) + layout.addRow("Tag original cards with", self.tag_edit) + return widget - for key, checkbox in self.checkboxes.items(): - layout.addRow(checkbox) - return layout + def _make_hl_tab(self) -> QWidget: + widget = QWidget() + widget.setLayout(layout := QFormLayout()) + layout.addRow("Web download timeout", self.web_timeout_spinbox) + layout.addRow(self.checkboxes["enable_debug_log"]) + show_log_b = QPushButton("Show log") + qconnect(show_log_b.clicked, lambda: showText(LogDebug().read(), parent=self, copyBtn=True)) + layout.addRow(show_log_b) + layout.addRow(self.checkboxes["call_add_cards_hook"]) + return widget def connect_widgets(self): qconnect(self.button_box.accepted, self.accept) qconnect(self.button_box.rejected, self.reject) + qconnect(self.button_box.helpRequested, self.show_help) def add_tooltips(self) -> None: self.tag_edit.setToolTip( @@ -117,13 +145,40 @@ def add_tooltips(self) -> None: "Instead of searching notes in a local profile,\n" "search the Internet instead." ) + def show_help(self): + help_win = QDialog(parent=self) + help_win.setWindowModality(Qt.WindowModality.NonModal) + help_win.setWindowTitle(f"{ADDON_NAME} - Settings Help") + help_win.setLayout(QVBoxLayout()) + + size_policy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Minimum) + size_policy.setHorizontalStretch(0) + size_policy.setVerticalStretch(0) + size_policy.setHeightForWidth(help_win.sizePolicy().hasHeightForWidth()) + help_win.setSizePolicy(size_policy) + help_win.setMinimumSize(240, 320) + + webview = AnkiWebView(parent=help_win) + webview.setProperty("url", QUrl("about:blank")) + with open(CONFIG_MD_PATH) as c_help: + webview.stdHtml(c_help.read(), js=[]) + webview.setMinimumSize(320, 480) + webview.disable_zoom() + help_win.layout().addWidget(webview) + + button_box = QDialogButtonBox(BUT_OK) + help_win.layout().addWidget(button_box) + qconnect(button_box.accepted, help_win.accept) + + help_win.exec() + def done(self, result: int) -> None: saveGeom(self, self.name) return super().done(result) def accept(self) -> None: config.max_displayed_notes = self.max_notes_edit.value() - config.tag_original_notes = self.tag_edit.text() + config.exported_tag = self.tag_edit.text() config.hidden_fields = self.hidden_fields.values() config.timeout_seconds = self.web_timeout_spinbox.value() for key, checkbox in self.checkboxes.items():