diff --git a/cropro.py b/cropro.py index f7ee468..b2a779d 100644 --- a/cropro.py +++ b/cropro.py @@ -58,8 +58,6 @@ from .remote_search import CroProWebSearchClient, RemoteNote, CroProWebClientException from .settings_dialog import open_cropro_settings from .widgets.main_window_ui import MainWindowUI -from .widgets.remote_search_bar import RemoteSearchWidget -from .widgets.search_bar import ColSearchWidget from .widgets.utils import CroProComboBox logDebug = LogDebug() @@ -81,9 +79,9 @@ def __init__(self, window: MainWindowUI): "to_deck": self._window.current_profile_deck_combo, "note_type": self._window.note_type_selection_combo, # Web search settings - "web_category": self._window.remote_search_bar.opts.category_combo, - "web_sort_by": self._window.remote_search_bar.opts.sort_combo, - "web_jlpt_level": self._window.remote_search_bar.opts.jlpt_level_combo, + "web_category": self._window.search_bar.remote_opts.category_combo, + "web_sort_by": self._window.search_bar.remote_opts.sort_combo, + "web_jlpt_level": self._window.search_bar.remote_opts.jlpt_level_combo, } self._state = defaultdict(dict) @@ -153,7 +151,6 @@ def __init__(self, cropro: MainWindowUI): def set_searching(self, searching: bool) -> None: self._searching = searching self._cropro.search_bar.setDisabled(searching) - self._cropro.remote_search_bar.setDisabled(searching) def is_searching(self) -> bool: return self._searching @@ -175,7 +172,7 @@ def __init__(self, ankimw: AnkiQt): self._add_tooltips() def _add_global_shortcuts(self): - QShortcut(QKeySequence("Ctrl+k"), self, activated=lambda: self.visible_search_bar().bar.focus_search_edit()) # type: ignore + QShortcut(QKeySequence("Ctrl+k"), self, activated=lambda: self.search_bar.bar.focus_search_edit()) # type: ignore QShortcut(QKeySequence("Ctrl+i"), self, activated=lambda: self.import_button.click()) # type: ignore QShortcut(QKeySequence("Ctrl+l"), self, activated=lambda: self.note_list.set_focus()) # type: ignore @@ -183,11 +180,6 @@ def _add_tooltips(self): self.import_button.setToolTip("Add a new card (Ctrl+I)") self.edit_button.setToolTip("Edit card before adding") - def visible_search_bar(self) -> Union[RemoteSearchWidget, ColSearchWidget]: - w = self.remote_search_bar if config.search_the_web else self.search_bar - assert w.isVisible(), "Widget must be visible." - return w - def setup_menubar(self): menu_bar: QMenuBar = self.menuBar() @@ -216,7 +208,7 @@ def setup_menubar(self): help_menu.addAction("Create sentence bank: subs2srs", lambda: openLink(SUBS2SRS_LINK)) def _send_query_to_browser(self): - search_text = self.visible_search_bar().bar.search_text() + search_text = self.search_bar.bar.search_text() if not search_text: return tooltip("Nothing to do.", parent=self) browser = aqt.dialogs.open("Browser", mw) @@ -232,7 +224,7 @@ def _on_toggle_web_search_triggered(self, checked: bool) -> None: return logDebug(f"Web search option changed to {checked}") config.search_the_web = checked - self._activate_enabled_search_bar() + self._ensure_enabled_search_mode() self.reset_cropro_status() # save config to disk to remember checkbox state. config.write_config() @@ -260,8 +252,7 @@ def get_target_note_type(self) -> Optional[NotetypeDict]: def connect_elements(self): qconnect(self.search_bar.opts.selected_profile_changed, self.open_other_col) - qconnect(self.search_bar.search_requested, self.perform_local_search) - qconnect(self.remote_search_bar.search_requested, self.perform_remote_search) + qconnect(self.search_bar.search_requested, self.perform_search) qconnect(self.edit_button.clicked, self.new_edit_win) qconnect(self.import_button.clicked, self.do_import) @@ -328,6 +319,12 @@ def populate_other_profile_decks(self): def _should_abort_search(self, is_web: bool) -> bool: return self._search_lock.is_searching() or config.search_the_web is not is_web or self.isVisible() is False + def perform_search(self, search_text: str): + if config.search_the_web: + return self.perform_remote_search(search_text) + else: + return self.perform_local_search(search_text) + def perform_remote_search(self, search_text: str): """ Search notes on a remote server. @@ -335,14 +332,14 @@ def perform_remote_search(self, search_text: str): if self._should_abort_search(is_web=True): return - self._activate_enabled_search_bar() + self._ensure_enabled_search_mode() self.reset_cropro_status() if not search_text: return def search_notes(_col) -> Sequence[RemoteNote]: - return self.web_search_client.search_notes(self.remote_search_bar.get_request_args()) + return self.web_search_client.search_notes(self.search_bar.get_request_args()) def set_search_results(notes: Sequence[RemoteNote]) -> None: self.note_list.set_notes( @@ -381,7 +378,7 @@ def perform_local_search(self, search_text: str): if self._should_abort_search(is_web=False): return - self._activate_enabled_search_bar() + self._ensure_enabled_search_mode() self.reset_cropro_status() self.open_other_col() @@ -475,7 +472,7 @@ def showEvent(self, event: QShowEvent) -> None: self.status_bar.hide_counters() self.into_profile_label.setText(mw.pm.name or "Unknown") self.window_state.restore() - self._activate_enabled_search_bar() + self._ensure_enabled_search_mode() return super().showEvent(event) def closeEvent(self, event: QCloseEvent) -> None: @@ -483,19 +480,12 @@ def closeEvent(self, event: QCloseEvent) -> None: self.window_state.save() return super().closeEvent(event) - def _activate_enabled_search_bar(self): - if config.search_the_web: - self.remote_search_bar.show() - self.remote_search_bar.bar.focus_search_edit() - self.search_bar.hide() - else: - self.search_bar.show() - self.search_bar.bar.focus_search_edit() - self.remote_search_bar.hide() + def _ensure_enabled_search_mode(self): + self.search_bar.set_web_mode(config.search_the_web) def _open_cropro_settings(self): open_cropro_settings(parent=self) - self._activate_enabled_search_bar() # the "search_the_web" setting may have changed + self._ensure_enabled_search_mode() # the "search_the_web" setting may have changed def on_profile_will_close(self): self.close() @@ -504,7 +494,6 @@ def on_profile_will_close(self): def on_profile_did_open(self): # clean state from the previous profile if it was set. self.search_bar.clear_all() - self.remote_search_bar.bar.clear_search_text() self.note_list.clear_notes() # setup search bar self.populate_other_profile_names() @@ -516,8 +505,8 @@ def on_profile_did_open(self): def search_for(self, search_text: str) -> None: self.show() self.setFocus() - self.visible_search_bar().bar.set_search_text(search_text) - self.visible_search_bar().search_requested.emit(search_text) + self.search_bar.bar.set_search_text(search_text) + self.search_bar.search_requested.emit(search_text) def setup_browser_menu(self, browser: Browser) -> None: """Add a browser entry""" diff --git a/widgets/main_window_ui.py b/widgets/main_window_ui.py index c0d347d..0e79e7c 100644 --- a/widgets/main_window_ui.py +++ b/widgets/main_window_ui.py @@ -5,8 +5,7 @@ from aqt.qt import * from .note_list import NoteList -from .remote_search_bar import RemoteSearchWidget -from .search_bar import ColSearchWidget +from .search_bar import CroProSearchWidget from .search_result_label import SearchResultLabel from .status_bar import StatusBar from .utils import ProfileNameLabel, NameIdComboBox, CroProPushButton @@ -21,8 +20,7 @@ class MainWindowUI(QMainWindow): def __init__(self, ankimw: AnkiQt, window_title: str): super().__init__(parent=ankimw) self.setWindowTitle(window_title) - self.search_bar = ColSearchWidget(ankimw=ankimw) - self.remote_search_bar = RemoteSearchWidget() + self.search_bar = CroProSearchWidget(ankimw=ankimw) self.status_bar = StatusBar() self.search_result_label = SearchResultLabel() self.into_profile_label = ProfileNameLabel() @@ -42,7 +40,6 @@ def init_ui(self): def make_main_layout(self) -> QLayout: main_vbox = QVBoxLayout() main_vbox.addWidget(self.search_bar) - main_vbox.addWidget(self.remote_search_bar) main_vbox.addWidget(self.search_result_label) main_vbox.addWidget(self.note_list) main_vbox.addLayout(self.status_bar) diff --git a/widgets/search_bar.py b/widgets/search_bar.py index 3d531e6..ad411b3 100644 --- a/widgets/search_bar.py +++ b/widgets/search_bar.py @@ -1,19 +1,24 @@ # Copyright: Ajatt-Tools and contributors; https://github.com/Ajatt-Tools # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - - -from collections.abc import Iterable -from collections.abc import Sequence +from types import SimpleNamespace +from typing import cast from aqt import AnkiQt from aqt.qt import * + try: + from .col_search_opts import ColSearchOptions + from .remote_search_opts import RemoteSearchOptions + from ..remote_search import get_request_url from .utils import CroProComboBox, NameIdComboBox, CroProLineEdit, CroProPushButton from ..collection_manager import NameId except ImportError: from utils import CroProComboBox, NameIdComboBox, CroProLineEdit, CroProPushButton from collection_manager import NameId + from remote_search_opts import RemoteSearchOptions + from remote_search import get_request_url + from col_search_opts import ColSearchOptions class CroProSearchBar(QWidget): @@ -61,76 +66,7 @@ def handle_search_requested(): qconnect(self._search_term_edit.editingFinished, handle_search_requested) -class ColSearchOptions(QWidget): - def __init__(self, ankimw: AnkiQt): - super().__init__() - self.ankimw = ankimw - self._other_profile_names_combo = CroProComboBox() - self._other_profile_deck_combo = NameIdComboBox() - self.selected_profile_changed = self._other_profile_names_combo.currentIndexChanged - self.selected_deck_changed = self._other_profile_deck_combo.currentIndexChanged - self._setup_layout() - - def _setup_layout(self) -> None: - layout = QHBoxLayout() - layout.addWidget(QLabel("Import From Profile:")) - layout.addWidget(self._other_profile_names_combo) - layout.addWidget(QLabel("Deck:")) - layout.addWidget(self._other_profile_deck_combo) - layout.setContentsMargins(0, 0, 0, 0) - self.setLayout(layout) - - @property - def other_profile_names_combo(self) -> QComboBox: - return self._other_profile_names_combo - - @property - def other_profile_deck_combo(self) -> QComboBox: - return self._other_profile_deck_combo - - def current_deck(self) -> NameId: - return self._other_profile_deck_combo.current_item() - - def decks_populated(self) -> bool: - return self._other_profile_deck_combo.count() > 0 - - def clear_combos(self) -> None: - self._other_profile_names_combo.clear() - self._other_profile_deck_combo.clear() - - def needs_to_repopulate_profile_names(self) -> bool: - """ - The content of the profile name selector is outdated and the combo box needs to be repopulated. - 1) If the combo box is empty, the window is opened for the first time. - 2) If it happens to contain the current profile name, the user has switched profiles. - """ - return ( - self._other_profile_names_combo.count() == 0 - or self._other_profile_names_combo.findText(self.ankimw.pm.name) != -1 - ) - - def set_profile_names(self, other_profile_names: Sequence[str]): - """ - Populate profile selector with a list of profile names, excluding the current profile. - """ - assert self.ankimw.pm.name not in other_profile_names - self._other_profile_names_combo.set_texts(other_profile_names) - - def selected_profile_name(self) -> str: - """ - The name of the other collection to search notes in (and import notes from). - """ - return self._other_profile_names_combo.currentText() - - def set_decks(self, decks: Iterable[NameId]): - """ - A list of decks to search in. - The user can limit search to a certain deck in the other collection. - """ - return self._other_profile_deck_combo.set_items(decks) - - -class ColSearchWidget(QWidget): +class CroProSearchWidget(QWidget): """ Search bar and search options (profile selector, deck selector, search bar, search button). """ @@ -142,10 +78,19 @@ def __init__(self, ankimw: AnkiQt): super().__init__() self.ankimw = ankimw self.opts = ColSearchOptions(ankimw) + self.remote_opts = RemoteSearchOptions() self.bar = CroProSearchBar() self._setup_layout() - self.setEnabled(False) # disallow search until profiles and decks are set. self._connect_elements() + self._web_mode = False + self.setEnabled(False) # disallow search until profiles and decks are set. + + def set_web_mode(self, is_web: bool) -> None: + self._web_mode = is_web + self.remote_opts.setVisible(is_web) + self.opts.setVisible(not is_web) + self.setEnabled(is_web or self.opts.other_profile_names_combo.count() > 0) + self.bar.focus_search_edit() def clear_all(self) -> None: """ @@ -155,15 +100,28 @@ def clear_all(self) -> None: self.opts.clear_combos() self.bar.clear_search_text() - def set_focus(self): - self.bar.focus_search_edit() - def _setup_layout(self) -> None: self.setLayout(layout := QVBoxLayout()) layout.addWidget(self.opts) + layout.addWidget(self.remote_opts) layout.addWidget(self.bar) self.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Maximum) - self.set_focus() + self.bar.focus_search_edit() + + def get_request_args(self) -> dict[str, str]: + assert self._web_mode, "Web mode must be enabled." + args = {} + widgets = ( + self.remote_opts.sort_combo, + self.remote_opts.category_combo, + self.remote_opts.jlpt_level_combo, + ) + if keyword := self.bar.search_text(): + args["keyword"] = keyword + for widget in widgets: + if param := widget.currentData().http_arg: + args[widget.key] = param + return args def _connect_elements(self): def handle_search_requested(): @@ -173,7 +131,7 @@ def handle_search_requested(): qconnect(self.opts.selected_deck_changed, handle_search_requested) qconnect(self.bar.search_requested, handle_search_requested) - qconnect(self.opts.selected_profile_changed, lambda row_idx: self.setEnabled(row_idx >= 0)) + qconnect(self.opts.selected_profile_changed, lambda row_idx: self.setEnabled(row_idx >= 0 or self._web_mode)) # Debug @@ -188,8 +146,7 @@ class App(QWidget): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Test") - # noinspection PyTypeChecker - self.search_bar = ColSearchWidget(None) + self.search_bar = CroProSearchWidget(cast(AnkiQt, SimpleNamespace(pm=SimpleNamespace(name="Dummy")))) self.initUI() qconnect(self.search_bar.search_requested, on_search_requested) # self.search_bar.set_profile_names(["User 1", "subs2srs", "dumpster"])