From f3915b914a8eb46418b66311bcb76dda9d15fd17 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 29 Mar 2021 11:18:10 +0200 Subject: [PATCH 01/13] Stop abusing qt accel string --- qt/aqt/table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/aqt/table.py b/qt/aqt/table.py index 2154427df..1f4bbe6c9 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -725,7 +725,7 @@ class NoteState(ItemState): def _load_columns(self) -> None: self._columns = [ ("note", tr.browsing_note()), - ("noteCards", tr.qt_accel_cards().replace("&", "")), + ("noteCards", tr.editing_cards()), ("noteCrt", tr.browsing_created()), ("noteEase", tr.browsing_average_ease()), ("noteFld", tr.browsing_sort_field()), From d6bac20c58ef41e654f318c73453d6c4c8e06ba1 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 29 Mar 2021 11:40:18 +0200 Subject: [PATCH 02/13] Fix issues with scrolling row into view 1) Check whether full row height is in viewport instead of just the top left corner. 2) Add timer before scrolling to current row so editor will already be set up. --- qt/aqt/table.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/qt/aqt/table.py b/qt/aqt/table.py index 1f4bbe6c9..af80022fb 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -417,7 +417,8 @@ class Table: current = current or rows[0] self._select_rows(rows) self._set_current(current) - self._scroll_to_row(current) + # editor may pop up and hide the row later on + QTimer.singleShot(100, lambda: self._scroll_to_row(current)) if self.len_selection() == 0: # no row change will fire self.browser.onRowChanged(QItemSelection(), QItemSelection()) @@ -462,8 +463,9 @@ class Table: def _scroll_to_row(self, row: int) -> None: """Scroll vertically to row.""" - position = self._view.rowViewportPosition(row) - visible = 0 <= position < self._view.viewport().height() + top_border = self._view.rowViewportPosition(row) + bottom_border = top_border + self._view.rowHeight(0) + visible = top_border >= 0 and bottom_border < self._view.viewport().height() if not visible: horizontal = self._view.horizontalScrollBar().value() self._view.scrollTo(self._model.index(row, 0), self._view.PositionAtCenter) From fb86320038231ff9af04800284a97c83ab0c6234 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 29 Mar 2021 11:54:35 +0200 Subject: [PATCH 03/13] Fix state toggling when current is deleted --- qt/aqt/table.py | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/qt/aqt/table.py b/qt/aqt/table.py index af80022fb..fbb8561a8 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -432,7 +432,7 @@ class Table: if rows: if len(rows) < self.SELECTION_LIMIT: return rows - if current in rows: + if current and current in rows: return [current] return rows[0:1] return [current if current else 0] @@ -454,9 +454,10 @@ class Table: selected_rows = self._model.get_item_rows( self._state.get_new_items(self._selected_items) ) - current_row = self._current_item and self._model.get_item_row( - self._state.get_new_item(self._current_item) - ) + current_row = None + if self._current_item: + if new_current := self._state.get_new_items([self._current_item]): + current_row = self._model.get_item_row(new_current[0]) return selected_rows, current_row # Move @@ -607,15 +608,9 @@ class ItemState(ABC): def toggle_state(self) -> ItemState: """Return an instance of the other state.""" - @abstractmethod - def get_new_item(self, old_item: ItemId) -> ItemId: - """Given an id from the other state, return the corresponding id for - this state.""" - @abstractmethod def get_new_items(self, old_items: Sequence[ItemId]) -> ItemList: - """Given a list of ids from the other state, return the corresponding - ids for this state.""" + """Given a list of ids from the other state, return the corresponding ids for this state.""" class CardState(ItemState): @@ -707,9 +702,6 @@ class CardState(ItemState): def toggle_state(self) -> NoteState: return NoteState(self.col) - def get_new_item(self, old_item: ItemId) -> CardId: - return super().card_ids_from_note_ids([old_item])[0] - def get_new_items(self, old_items: Sequence[ItemId]) -> Sequence[CardId]: return super().card_ids_from_note_ids(old_items) @@ -795,9 +787,6 @@ class NoteState(ItemState): def toggle_state(self) -> CardState: return CardState(self.col) - def get_new_item(self, old_item: ItemId) -> NoteId: - return super().note_ids_from_card_ids([old_item])[0] - def get_new_items(self, old_items: Sequence[ItemId]) -> Sequence[NoteId]: return super().note_ids_from_card_ids(old_items) From b1a06fb80774035ad978b695206f37197fd8fde0 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 29 Mar 2021 11:55:28 +0200 Subject: [PATCH 04/13] Flag deleted rows as inactive --- qt/aqt/table.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/qt/aqt/table.py b/qt/aqt/table.py index fbb8561a8..9ac75e1d9 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -802,6 +802,8 @@ class Cell: class CellRow: + is_deleted: bool = False + def __init__( self, cells: Generator[Tuple[str, bool], None, None], @@ -833,7 +835,9 @@ class CellRow: @staticmethod def deleted(length: int) -> CellRow: - return CellRow.generic(length, tr.browsing_row_deleted()) + row = CellRow.generic(length, tr.browsing_row_deleted()) + row.is_deleted = True + return row def backend_color_to_aqt_color(color: BrowserRow.Color.V) -> Optional[Tuple[str, str]]: @@ -1104,6 +1108,8 @@ class DataModel(QAbstractTableModel): return None def flags(self, index: QModelIndex) -> Qt.ItemFlags: + if self.get_row(index).is_deleted: + return Qt.ItemFlags(Qt.NoItemFlags) return cast(Qt.ItemFlags, Qt.ItemIsEnabled | Qt.ItemIsSelectable) From 7b316a7151a2caa0cfb5c88e74eca6d34c2f8f5a Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 29 Mar 2021 12:03:31 +0200 Subject: [PATCH 05/13] Move order docstring back into find_cards() --- pylib/anki/collection.py | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 63ab55e4e..d46fd75d2 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -497,8 +497,22 @@ class Collection: reverse: bool = False, ) -> Sequence[CardId]: """Return card ids matching the provided search. + To programmatically construct a search string, see .build_search_string(). - To define a sort order, see _build_sort_mode(). + + If order=True, use the sort order stored in the collection config + If order=False, do no ordering + + If order is a string, that text is added after 'order by' in the sql statement. + You must add ' asc' or ' desc' to the order, as Anki will replace asc with + desc and vice versa when reverse is set in the collection config, eg + order="c.ivl asc, c.due desc". + + If order is a BuiltinSort.Kind value, sort using that builtin sort, eg + col.find_cards("", order=BuiltinSort.Kind.CARD_DUE) + + The reverse argument only applies when a BuiltinSort.Kind is provided; + otherwise the collection config defines whether reverse is set or not. """ mode = _build_sort_mode(order, reverse) return cast( @@ -512,8 +526,9 @@ class Collection: reverse: bool = False, ) -> Sequence[NoteId]: """Return note ids matching the provided search. + To programmatically construct a search string, see .build_search_string(). - To define a sort order, see _build_sort_mode(). + The order parameter is documented in .find_cards(). """ mode = _build_sort_mode(order, reverse) return cast( @@ -1072,22 +1087,6 @@ def _build_sort_mode( order: Union[bool, str, BuiltinSort.Kind.V], reverse: bool, ) -> _pb.SortOrder: - """Return a SortOrder object for use in find_cards() or find_notes(). - - If order=True, use the sort order stored in the collection config - If order=False, do no ordering - - If order is a string, that text is added after 'order by' in the sql statement. - You must add ' asc' or ' desc' to the order, as Anki will replace asc with - desc and vice versa when reverse is set in the collection config, eg - order="c.ivl asc, c.due desc". - - If order is a BuiltinSort.Kind value, sort using that builtin sort, eg - col.find_cards("", order=BuiltinSort.Kind.CARD_DUE) - - The reverse argument only applies when a BuiltinSort.Kind is provided; - otherwise the collection config defines whether reverse is set or not. - """ if isinstance(order, str): return _pb.SortOrder(custom=order) elif isinstance(order, bool): From c0950eca30f3bbb91c96d921bf8e7d7063dc5d6a Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 29 Mar 2021 12:04:14 +0200 Subject: [PATCH 06/13] Explain use of deprecated decorator --- qt/aqt/table.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qt/aqt/table.py b/qt/aqt/table.py index 9ac75e1d9..174dc5402 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -546,6 +546,8 @@ class ItemState(ABC): # Columns and sorting + # abstractproperty is deprecated but used due to mypy limitations + # (https://github.com/python/mypy/issues/1362) @abstractproperty def columns(self) -> List[Tuple[str, str]]: """Return all for the state available columns.""" From 13a0e2c82f059e4e565261eca86f34f84019b265 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 29 Mar 2021 12:07:24 +0200 Subject: [PATCH 07/13] Remove fixme for card_ids legacy support --- qt/aqt/table.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qt/aqt/table.py b/qt/aqt/table.py index 174dc5402..424319741 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -48,8 +48,7 @@ class SearchContext: search: str browser: aqt.browser.Browser order: Union[bool, str] = True - # if set, provided card ids will be used instead of the regular search - # fixme: legacy support for card_ids? + # if set, provided ids will be used instead of the regular search ids: Optional[Sequence[ItemId]] = None From 2c7940e2470f8a099914b64c059cdcd826dcf03b Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 29 Mar 2021 12:16:50 +0200 Subject: [PATCH 08/13] Fix browser_did_fetch_row hook --- qt/aqt/table.py | 7 ++++--- qt/tools/genhooks_gui.py | 7 ++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/qt/aqt/table.py b/qt/aqt/table.py index 424319741..dac6bbb26 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -892,7 +892,7 @@ class DataModel(QAbstractTableModel): self._rows[item] = self._fetch_row_from_backend(item) return self._rows[item] - def _fetch_row_from_backend(self, item: int) -> CellRow: + def _fetch_row_from_backend(self, item: ItemId) -> CellRow: try: row = CellRow(*self.col.browser_row_for_id(item)) except NotFoundError: @@ -900,8 +900,9 @@ class DataModel(QAbstractTableModel): except Exception as e: return CellRow.generic(self.len_columns(), str(e)) - # fixme: hook needs state - gui_hooks.browser_did_fetch_row(item, row, self._state.active_columns) + gui_hooks.browser_did_fetch_row( + item, not self._state.is_card_state(), row, self._state.active_columns + ) return row # Reset diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index d10af0831..7a268c97d 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -406,7 +406,12 @@ hooks = [ ), Hook( name="browser_did_fetch_row", - args=["card_id: int", "row: aqt.table.CellRow", "columns: Sequence[str]"], + args=[ + "card_or_note_id: aqt.table.ItemId", + "is_note: bool", + "row: aqt.table.CellRow", + "columns: Sequence[str]", + ], doc="""Allows you to add or modify content to a row in the browser. You can mutate the row object to change what is displayed. Any columns the From a3e3e56f2b7d1e1ea0693d3ae6d3da7b1c7ea3b7 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 29 Mar 2021 12:19:48 +0200 Subject: [PATCH 09/13] Implement custom qt button Switch --- qt/aqt/switch.py | 115 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 qt/aqt/switch.py diff --git a/qt/aqt/switch.py b/qt/aqt/switch.py new file mode 100644 index 000000000..d1a883b44 --- /dev/null +++ b/qt/aqt/switch.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +# Copyright: Ankitects Pty Ltd and contributors +# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +from aqt import colors +from aqt.qt import * +from aqt.theme import theme_manager + + +class Switch(QAbstractButton): + """A horizontal slider to toggle between two states which can be denoted by short strings. + The left state is the default and corresponds to isChecked=True. + """ + + _margin: int = 2 + + def __init__( + self, + radius: int = 10, + left_label: str = "", + right_label: str = "", + parent: QWidget = None, + ) -> None: + super().__init__(parent=parent) + self.setCheckable(True) + super().setChecked(True) + self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self._left_label = left_label + self._right_label = right_label + self._path_radius = radius + self._knob_radius = radius - self._margin + self._left_position = self._position = self._path_radius + self._margin + self._right_position = 3 * self._path_radius + self._margin + + @pyqtProperty(int) # type: ignore + def position(self) -> int: + return self._position + + @position.setter # type: ignore + def position(self, position: int) -> None: + self._position = position + self.update() + + @property + def start_position(self) -> int: + return self._right_position if self.isChecked() else self._left_position + + @property + def end_position(self) -> int: + return self._left_position if self.isChecked() else self._right_position + + @property + def label(self) -> str: + return self._left_label if self.isChecked() else self._right_label + + def sizeHint(self) -> QSize: + return QSize( + 4 * self._path_radius + 2 * self._margin, + 2 * self._path_radius + 2 * self._margin, + ) + + def setChecked(self, checked: bool) -> None: + super().setChecked(checked) + self._position = self.end_position + self.update() + + def paintEvent(self, _event: QPaintEvent) -> None: + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing, True) + painter.setPen(Qt.NoPen) + self._paint_path(painter) + self._paint_knob(painter) + self._paint_label(painter) + + def _paint_path(self, painter: QPainter) -> None: + painter.setBrush(QBrush(theme_manager.qcolor(colors.FRAME_BG))) + rectangle = QRectF( + self._margin, + self._margin, + self.width() - 2 * self._margin, + self.height() - 2 * self._margin, + ) + painter.drawRoundedRect(rectangle, self._path_radius, self._path_radius) + + def _current_knob_rectangle(self) -> QRectF: + return QRectF( + self.position - self._knob_radius, # type: ignore + 2 * self._margin, + 2 * self._knob_radius, + 2 * self._knob_radius, + ) + + def _paint_knob(self, painter: QPainter) -> None: + painter.setBrush(QBrush(theme_manager.qcolor(colors.HIGHLIGHT_BG))) + painter.drawEllipse(self._current_knob_rectangle()) + + def _paint_label(self, painter: QPainter) -> None: + painter.setPen(QColor("white")) + font = painter.font() + font.setPixelSize(int(1.5 * self._knob_radius)) + painter.setFont(font) + painter.drawText(self._current_knob_rectangle(), Qt.AlignCenter, self.label) + + def mouseReleaseEvent(self, event: QMouseEvent) -> None: + super().mouseReleaseEvent(event) + if event.button() == Qt.LeftButton: + animation = QPropertyAnimation(self, b"position", self) + animation.setDuration(100) + animation.setStartValue(self.start_position) + animation.setEndValue(self.end_position) + animation.start() + + def enterEvent(self, event: QEvent) -> None: + self.setCursor(Qt.PointingHandCursor) + super().enterEvent(event) From ad7ac06398e5674b4ec46520cacec5b5d4715cc1 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 29 Mar 2021 12:24:24 +0200 Subject: [PATCH 10/13] Add switch for browser states --- ftl/core/browsing.ftl | 4 ++++ qt/aqt/browser.py | 14 ++++++++------ qt/aqt/forms/browser.ui | 26 +------------------------- 3 files changed, 13 insertions(+), 31 deletions(-) diff --git a/ftl/core/browsing.ftl b/ftl/core/browsing.ftl index e2f05b275..e07435e9c 100644 --- a/ftl/core/browsing.ftl +++ b/ftl/core/browsing.ftl @@ -12,6 +12,8 @@ browsing-browser-appearance = Browser Appearance browsing-browser-options = Browser Options browsing-buried = Buried browsing-card = Card +# Exactly one character representing 'Cards'; should differ from browsing-note-initial. +browsing-card-initial = C browsing-card-list = Card List browsing-card-state = Card State browsing-cards-cant-be-manually-moved-into = Cards can't be manually moved into a filtered deck. @@ -63,6 +65,8 @@ browsing-new = (new) browsing-new-note-type = New note type: browsing-no-flag = No Flag browsing-note = Note +# Exactly one character representing 'Notes'; should differ from browsing-card-initial. +browsing-note-initial = N browsing-notes-tagged = Notes tagged. browsing-nothing = Nothing browsing-only-new-cards-can-be-repositioned = Only new cards can be repositioned. diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index e1da7001c..56e313c2a 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -37,6 +37,7 @@ from aqt.scheduling_ops import ( unsuspend_cards, ) from aqt.sidebar import SidebarTreeView +from aqt.switch import Switch from aqt.table import Table from aqt.tag_ops import add_tags, clear_unused_tags, remove_tags_for_notes from aqt.utils import ( @@ -374,10 +375,11 @@ class Browser(QMainWindow): def setup_table(self) -> None: self.table = Table(self) - self.form.radio_cards.setChecked(self.table.is_card_state()) - self.form.radio_notes.setChecked(not self.table.is_card_state()) self.table.set_view(self.form.tableView) - qconnect(self.form.radio_cards.toggled, self.on_table_state_changed) + switch = Switch(11, tr.browsing_card_initial(), tr.browsing_note_initial()) + switch.setChecked(self.table.is_card_state()) + qconnect(switch.toggled, self.on_table_state_changed) + self.form.gridLayout.addWidget(switch, 0, 0) def setupEditor(self) -> None: def add_preview_button(leftbuttons: List[str], editor: Editor) -> None: @@ -430,10 +432,10 @@ class Browser(QMainWindow): self._update_flags_menu() gui_hooks.browser_did_change_row(self) - @ensure_editor_saved_on_trigger - def on_table_state_changed(self) -> None: + @ensure_editor_saved + def on_table_state_changed(self, checked: bool) -> None: self.mw.progress.start() - self.table.toggle_state(self.form.radio_cards.isChecked(), self._lastSearchTxt) + self.table.toggle_state(checked, self._lastSearchTxt) self.mw.progress.finish() # Sidebar diff --git a/qt/aqt/forms/browser.ui b/qt/aqt/forms/browser.ui index 5ed6506fd..76217999f 100644 --- a/qt/aqt/forms/browser.ui +++ b/qt/aqt/forms/browser.ui @@ -91,7 +91,7 @@ 0 - + @@ -109,30 +109,6 @@ - - - - 5 - - - - - qt_accel_cards - - - true - - - - - - - qt_accel_notes - - - - - From 18e33f24d3e4b8ef62217239928f55c62f4f0957 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 29 Mar 2021 15:51:34 +0200 Subject: [PATCH 11/13] Make note state equate to False as on backend --- qt/aqt/browser.py | 8 ++++---- qt/aqt/switch.py | 14 +++++++------- qt/aqt/table.py | 28 ++++++++++++++-------------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 56e313c2a..5084b9213 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -330,9 +330,9 @@ class Browser(QMainWindow): selected = self.table.len_selection() cur = self.table.len() tr_title = ( - tr.browsing_window_title - if self.table.is_card_state() - else tr.browsing_window_title_notes + tr.browsing_window_title_notes + if self.table.is_notes_mode() + else tr.browsing_window_title ) self.setWindowTitle( without_unicode_isolation(tr_title(total=cur, selected=selected)) @@ -377,7 +377,7 @@ class Browser(QMainWindow): self.table = Table(self) self.table.set_view(self.form.tableView) switch = Switch(11, tr.browsing_card_initial(), tr.browsing_note_initial()) - switch.setChecked(self.table.is_card_state()) + switch.setChecked(self.table.is_notes_mode()) qconnect(switch.toggled, self.on_table_state_changed) self.form.gridLayout.addWidget(switch, 0, 0) diff --git a/qt/aqt/switch.py b/qt/aqt/switch.py index d1a883b44..83afdcd99 100644 --- a/qt/aqt/switch.py +++ b/qt/aqt/switch.py @@ -9,7 +9,7 @@ from aqt.theme import theme_manager class Switch(QAbstractButton): """A horizontal slider to toggle between two states which can be denoted by short strings. - The left state is the default and corresponds to isChecked=True. + The left state is the default and corresponds to isChecked=False. """ _margin: int = 2 @@ -23,7 +23,7 @@ class Switch(QAbstractButton): ) -> None: super().__init__(parent=parent) self.setCheckable(True) - super().setChecked(True) + super().setChecked(False) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self._left_label = left_label self._right_label = right_label @@ -43,15 +43,15 @@ class Switch(QAbstractButton): @property def start_position(self) -> int: - return self._right_position if self.isChecked() else self._left_position - - @property - def end_position(self) -> int: return self._left_position if self.isChecked() else self._right_position + @property + def end_position(self) -> int: + return self._right_position if self.isChecked() else self._left_position + @property def label(self) -> str: - return self._left_label if self.isChecked() else self._right_label + return self._right_label if self.isChecked() else self._left_label def sizeHint(self) -> QSize: return QSize( diff --git a/qt/aqt/table.py b/qt/aqt/table.py index dac6bbb26..9fba51acd 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -93,8 +93,8 @@ class Table: def has_next(self) -> bool: return self.has_current() and self._current().row() < self.len() - 1 - def is_card_state(self) -> bool: - return self._state.is_card_state() + def is_notes_mode(self) -> bool: + return self._state.is_notes_mode() # Get objects @@ -193,15 +193,15 @@ class Table: self._model.search(SearchContext(search=txt, browser=self.browser)) self._restore_selection(self._intersected_selection) - def toggle_state(self, is_card_state: bool, last_search: str) -> None: - if is_card_state == self.is_card_state(): + def toggle_state(self, is_notes_mode: bool, last_search: str) -> None: + if is_notes_mode == self.is_notes_mode(): return self._save_selection() self._state = self._model.toggle_state( SearchContext(search=last_search, browser=self.browser) ) self.col.set_config_bool( - Config.Bool.BROWSER_TABLE_SHOW_NOTES_MODE, not self.is_card_state() + Config.Bool.BROWSER_TABLE_SHOW_NOTES_MODE, self.is_notes_mode() ) self._set_sort_indicator() self._set_column_sizes() @@ -327,14 +327,14 @@ class Table: def _on_context_menu(self, _point: QPoint) -> None: menu = QMenu() - if self.is_card_state(): - main = self.browser.form.menu_Cards - other = self.browser.form.menu_Notes - other_name = tr.qt_accel_notes() - else: + if self.is_notes_mode(): main = self.browser.form.menu_Notes other = self.browser.form.menu_Cards other_name = tr.qt_accel_cards() + else: + main = self.browser.form.menu_Cards + other = self.browser.form.menu_Notes + other_name = tr.qt_accel_notes() for action in main.actions(): menu.addAction(action) menu.addSeparator() @@ -529,9 +529,9 @@ class ItemState(ABC): def __init__(self, col: Collection) -> None: self.col = col - def is_card_state(self) -> bool: - """Return True if the state is a CardState.""" - return isinstance(self, CardState) + def is_notes_mode(self) -> bool: + """Return True if the state is a NoteState.""" + return isinstance(self, NoteState) # Stateless Helpers @@ -901,7 +901,7 @@ class DataModel(QAbstractTableModel): return CellRow.generic(self.len_columns(), str(e)) gui_hooks.browser_did_fetch_row( - item, not self._state.is_card_state(), row, self._state.active_columns + item, self._state.is_notes_mode(), row, self._state.active_columns ) return row From 32e538d0db7fe11ff71a7e85bf8df26a0282c050 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 29 Mar 2021 15:52:02 +0200 Subject: [PATCH 12/13] Add note reps column --- qt/aqt/table.py | 1 + rslib/backend.proto | 1 + rslib/src/backend/search/mod.rs | 1 + rslib/src/browser_rows.rs | 1 + rslib/src/config/mod.rs | 1 + rslib/src/search/mod.rs | 9 ++++++--- rslib/src/search/note_reps_order.sql | 10 ++++++++++ 7 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 rslib/src/search/note_reps_order.sql diff --git a/qt/aqt/table.py b/qt/aqt/table.py index 9fba51acd..56d625d41 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -725,6 +725,7 @@ class NoteState(ItemState): ("noteEase", tr.browsing_average_ease()), ("noteFld", tr.browsing_sort_field()), ("noteMod", tr.search_note_modified()), + ("noteReps", tr.scheduling_reviews()), ("noteTags", tr.editing_tags()), ] self._columns.sort(key=itemgetter(1)) diff --git a/rslib/backend.proto b/rslib/backend.proto index 25e2a4ed0..f4168ccad 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -812,6 +812,7 @@ message SortOrder { NOTE_EASE = 2; NOTE_MOD = 3; NOTE_FIELD = 4; + NOTE_REPS = 15; NOTE_TAGS = 5; NOTETYPE = 6; CARD_MOD = 7; diff --git a/rslib/src/backend/search/mod.rs b/rslib/src/backend/search/mod.rs index d88d2f9ce..6112eb886 100644 --- a/rslib/src/backend/search/mod.rs +++ b/rslib/src/backend/search/mod.rs @@ -102,6 +102,7 @@ impl From for SortKind { SortKindProto::NoteEase => SortKind::NoteEase, SortKindProto::NoteMod => SortKind::NoteMod, SortKindProto::NoteField => SortKind::NoteField, + SortKindProto::NoteReps => SortKind::NoteReps, SortKindProto::NoteTags => SortKind::NoteTags, SortKindProto::Notetype => SortKind::Notetype, SortKindProto::CardMod => SortKind::CardMod, diff --git a/rslib/src/browser_rows.rs b/rslib/src/browser_rows.rs index ae46b74b7..ec4a6e8b8 100644 --- a/rslib/src/browser_rows.rs +++ b/rslib/src/browser_rows.rs @@ -429,6 +429,7 @@ impl RowContext for NoteRowContext<'_> { "noteEase" => self.note_ease_str(), "noteFld" => self.note_field_str(), "noteMod" => self.note.mtime.date_string(), + "noteReps" => self.cards.iter().map(|c| c.reps).sum::().to_string(), "noteTags" => self.note.tags.join(" "), _ => "".to_string(), }) diff --git a/rslib/src/config/mod.rs b/rslib/src/config/mod.rs index 722d0104f..a56cf41e1 100644 --- a/rslib/src/config/mod.rs +++ b/rslib/src/config/mod.rs @@ -278,6 +278,7 @@ pub enum SortKind { NoteMod, #[serde(rename = "noteFld")] NoteField, + NoteReps, #[serde(rename = "note")] Notetype, NoteTags, diff --git a/rslib/src/search/mod.rs b/rslib/src/search/mod.rs index 2b54e285c..5e10e2929 100644 --- a/rslib/src/search/mod.rs +++ b/rslib/src/search/mod.rs @@ -94,6 +94,7 @@ impl SortKind { | SortKind::NoteMod | SortKind::NoteField | SortKind::Notetype + | SortKind::NoteReps | SortKind::NoteTags => RequiredTable::Notes, SortKind::CardTemplate => RequiredTable::CardsAndNotes, SortKind::CardMod @@ -250,11 +251,12 @@ fn card_order_from_sortkind(kind: SortKind) -> Cow<'static, str> { fn note_order_from_sortkind(kind: SortKind) -> Cow<'static, str> { match kind { - SortKind::NoteCards => "(select pos from sort_order where nid = n.id) asc".into(), + SortKind::NoteCards | SortKind::NoteEase | SortKind::NoteReps => { + "(select pos from sort_order where nid = n.id) asc".into() + } SortKind::NoteCreation => "n.id asc".into(), - SortKind::NoteEase => "(select pos from sort_order where nid = n.id) asc".into(), - SortKind::NoteMod => "n.mod asc".into(), SortKind::NoteField => "n.sfld collate nocase asc".into(), + SortKind::NoteMod => "n.mod asc".into(), SortKind::NoteTags => "n.tags asc".into(), SortKind::Notetype => "(select pos from sort_order where ntid = n.mid) asc".into(), _ => "".into(), @@ -269,6 +271,7 @@ fn prepare_sort(col: &mut Collection, kind: SortKind) -> Result<()> { CardTemplate => include_str!("template_order.sql"), NoteCards => include_str!("note_cards_order.sql"), NoteEase => include_str!("note_ease_order.sql"), + NoteReps => include_str!("note_reps_order.sql"), _ => return Ok(()), }; diff --git a/rslib/src/search/note_reps_order.sql b/rslib/src/search/note_reps_order.sql new file mode 100644 index 000000000..833fdeb65 --- /dev/null +++ b/rslib/src/search/note_reps_order.sql @@ -0,0 +1,10 @@ +DROP TABLE IF EXISTS sort_order; +CREATE TEMPORARY TABLE sort_order ( + pos integer PRIMARY KEY, + nid integer NOT NULL UNIQUE +); +INSERT INTO sort_order (nid) +SELECT nid +FROM cards +GROUP BY nid +ORDER BY SUM(reps); \ No newline at end of file From 4933b922f7bc801ae9c754c08a86bd1c260ef8e8 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 29 Mar 2021 16:06:15 +0200 Subject: [PATCH 13/13] Add note lapses column --- qt/aqt/table.py | 1 + rslib/backend.proto | 27 +++++++++++++------------- rslib/src/backend/search/mod.rs | 1 + rslib/src/browser_rows.rs | 1 + rslib/src/config/mod.rs | 1 + rslib/src/search/mod.rs | 12 +++++++----- rslib/src/search/note_lapses_order.sql | 10 ++++++++++ 7 files changed, 35 insertions(+), 18 deletions(-) create mode 100644 rslib/src/search/note_lapses_order.sql diff --git a/qt/aqt/table.py b/qt/aqt/table.py index 56d625d41..6173a533c 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -724,6 +724,7 @@ class NoteState(ItemState): ("noteCrt", tr.browsing_created()), ("noteEase", tr.browsing_average_ease()), ("noteFld", tr.browsing_sort_field()), + ("noteLapses", tr.scheduling_lapses()), ("noteMod", tr.search_note_modified()), ("noteReps", tr.scheduling_reviews()), ("noteTags", tr.editing_tags()), diff --git a/rslib/backend.proto b/rslib/backend.proto index f4168ccad..e87af0a61 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -810,19 +810,20 @@ message SortOrder { NOTE_CARDS = 0; NOTE_CREATION = 1; NOTE_EASE = 2; - NOTE_MOD = 3; - NOTE_FIELD = 4; - NOTE_REPS = 15; - NOTE_TAGS = 5; - NOTETYPE = 6; - CARD_MOD = 7; - CARD_REPS = 8; - CARD_DUE = 9; - CARD_EASE = 10; - CARD_LAPSES = 11; - CARD_INTERVAL = 12; - CARD_DECK = 13; - CARD_TEMPLATE = 14; + NOTE_FIELD = 3; + NOTE_LAPSES = 4; + NOTE_MOD = 5; + NOTE_REPS = 6; + NOTE_TAGS = 7; + NOTETYPE = 8; + CARD_MOD = 9; + CARD_REPS = 10; + CARD_DUE = 11; + CARD_EASE = 12; + CARD_LAPSES = 13; + CARD_INTERVAL = 14; + CARD_DECK = 15; + CARD_TEMPLATE = 16; } Kind kind = 1; bool reverse = 2; diff --git a/rslib/src/backend/search/mod.rs b/rslib/src/backend/search/mod.rs index 6112eb886..8a5e781cc 100644 --- a/rslib/src/backend/search/mod.rs +++ b/rslib/src/backend/search/mod.rs @@ -100,6 +100,7 @@ impl From for SortKind { SortKindProto::NoteCards => SortKind::NoteCards, SortKindProto::NoteCreation => SortKind::NoteCreation, SortKindProto::NoteEase => SortKind::NoteEase, + SortKindProto::NoteLapses => SortKind::NoteLapses, SortKindProto::NoteMod => SortKind::NoteMod, SortKindProto::NoteField => SortKind::NoteField, SortKindProto::NoteReps => SortKind::NoteReps, diff --git a/rslib/src/browser_rows.rs b/rslib/src/browser_rows.rs index ec4a6e8b8..06388db7a 100644 --- a/rslib/src/browser_rows.rs +++ b/rslib/src/browser_rows.rs @@ -428,6 +428,7 @@ impl RowContext for NoteRowContext<'_> { "noteCrt" => self.note_creation_str(), "noteEase" => self.note_ease_str(), "noteFld" => self.note_field_str(), + "noteLapses" => self.cards.iter().map(|c| c.lapses).sum::().to_string(), "noteMod" => self.note.mtime.date_string(), "noteReps" => self.cards.iter().map(|c| c.reps).sum::().to_string(), "noteTags" => self.note.tags.join(" "), diff --git a/rslib/src/config/mod.rs b/rslib/src/config/mod.rs index a56cf41e1..0c61b3f77 100644 --- a/rslib/src/config/mod.rs +++ b/rslib/src/config/mod.rs @@ -275,6 +275,7 @@ pub enum SortKind { #[serde(rename = "noteCrt")] NoteCreation, NoteEase, + NoteLapses, NoteMod, #[serde(rename = "noteFld")] NoteField, diff --git a/rslib/src/search/mod.rs b/rslib/src/search/mod.rs index 5e10e2929..05619cf45 100644 --- a/rslib/src/search/mod.rs +++ b/rslib/src/search/mod.rs @@ -91,11 +91,12 @@ impl SortKind { SortKind::NoteCards | SortKind::NoteCreation | SortKind::NoteEase - | SortKind::NoteMod | SortKind::NoteField - | SortKind::Notetype + | SortKind::NoteLapses + | SortKind::NoteMod | SortKind::NoteReps - | SortKind::NoteTags => RequiredTable::Notes, + | SortKind::NoteTags + | SortKind::Notetype => RequiredTable::Notes, SortKind::CardTemplate => RequiredTable::CardsAndNotes, SortKind::CardMod | SortKind::CardReps @@ -251,7 +252,7 @@ fn card_order_from_sortkind(kind: SortKind) -> Cow<'static, str> { fn note_order_from_sortkind(kind: SortKind) -> Cow<'static, str> { match kind { - SortKind::NoteCards | SortKind::NoteEase | SortKind::NoteReps => { + SortKind::NoteCards | SortKind::NoteEase | SortKind::NoteLapses | SortKind::NoteReps => { "(select pos from sort_order where nid = n.id) asc".into() } SortKind::NoteCreation => "n.id asc".into(), @@ -267,11 +268,12 @@ fn prepare_sort(col: &mut Collection, kind: SortKind) -> Result<()> { use SortKind::*; let sql = match kind { CardDeck => include_str!("deck_order.sql"), - Notetype => include_str!("notetype_order.sql"), CardTemplate => include_str!("template_order.sql"), NoteCards => include_str!("note_cards_order.sql"), NoteEase => include_str!("note_ease_order.sql"), + NoteLapses => include_str!("note_lapses_order.sql"), NoteReps => include_str!("note_reps_order.sql"), + Notetype => include_str!("notetype_order.sql"), _ => return Ok(()), }; diff --git a/rslib/src/search/note_lapses_order.sql b/rslib/src/search/note_lapses_order.sql new file mode 100644 index 000000000..061b271e2 --- /dev/null +++ b/rslib/src/search/note_lapses_order.sql @@ -0,0 +1,10 @@ +DROP TABLE IF EXISTS sort_order; +CREATE TEMPORARY TABLE sort_order ( + pos integer PRIMARY KEY, + nid integer NOT NULL UNIQUE +); +INSERT INTO sort_order (nid) +SELECT nid +FROM cards +GROUP BY nid +ORDER BY SUM(lapses); \ No newline at end of file