From a1277b09196aeb8a18c24aed88b95cee07aaba36 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Tue, 6 Apr 2021 11:41:18 +0200 Subject: [PATCH 01/32] Tweak new browsing strings --- ftl/core/browsing.ftl | 6 +++--- qt/aqt/browser.py | 2 +- qt/aqt/forms/browser.ui | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ftl/core/browsing.ftl b/ftl/core/browsing.ftl index 89755266c..b99e40952 100644 --- a/ftl/core/browsing.ftl +++ b/ftl/core/browsing.ftl @@ -7,8 +7,8 @@ browsing-all-fields = All Fields browsing-answer = Answer browsing-any-cards-mapped-to-nothing-will = Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? browsing-any-flag = Any Flag -browsing-average-ease = Average Ease -browsing-average-interval = Average Interval +browsing-average-ease = Avg. Ease +browsing-average-interval = Avg. Interval browsing-browser-appearance = Browser Appearance browsing-browser-options = Browser Options browsing-buried = Buried @@ -101,7 +101,7 @@ browsing-suspended = Suspended browsing-tag-duplicates = Tag Duplicates browsing-tag-rename-warning-empty = You can't rename a tag that has no notes. browsing-target-field = Target field: -browsing-toggle-cards-notes-mode = Toggle Cards/Notes Mode +browsing-toggle-showing-cards-notes = Toggle Showing Cards/Notes browsing-toggle-mark = Toggle Mark browsing-toggle-suspend = Toggle Suspend browsing-treat-input-as-regular-expression = Treat input as regular expression diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 97174c2fa..575b08382 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -382,7 +382,7 @@ class Browser(QMainWindow): self.table.set_view(self.form.tableView) switch = Switch(11, tr.browsing_card_initial(), tr.browsing_note_initial()) switch.setChecked(self.table.is_notes_mode()) - switch.setToolTip(tr.browsing_toggle_cards_notes_mode()) + switch.setToolTip(tr.browsing_toggle_showing_cards_notes()) qconnect(self.form.action_toggle_mode.triggered, switch.toggle) qconnect(switch.toggled, self.on_table_state_changed) self.form.gridLayout.addWidget(switch, 0, 0) diff --git a/qt/aqt/forms/browser.ui b/qt/aqt/forms/browser.ui index 810724b33..7bcb1c97b 100644 --- a/qt/aqt/forms/browser.ui +++ b/qt/aqt/forms/browser.ui @@ -607,7 +607,7 @@ - browsing_toggle_cards_notes_mode + browsing_toggle_showing_cards_notes Ctrl+M From 4903a325d4a5d2ef5000223d7de71720cb87f5cf Mon Sep 17 00:00:00 2001 From: RumovZ Date: Tue, 6 Apr 2021 12:06:08 +0200 Subject: [PATCH 02/32] Fix get_item_from_card_id() --- qt/aqt/table.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/qt/aqt/table.py b/qt/aqt/table.py index aec9d7b71..9df2c65ce 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -149,11 +149,8 @@ class Table: def select_single_card(self, card_id: CardId) -> None: """Try to set the selection to the item corresponding to the given card.""" self.clear_selection() - if self.is_notes_mode(): - self._view.selectRow(0) - else: - if (row := self._model.get_card_row(card_id)) is not None: - self._view.selectRow(row) + if (row := self._model.get_card_row(card_id)) is not None: + self._view.selectRow(row) # Reset @@ -772,7 +769,7 @@ class NoteState(ItemState): return self.col.find_notes(search, order) def get_item_from_card_id(self, card: CardId) -> ItemId: - return self.get_card(card).note().id + return self.col.get_card(card).note().id def get_card_ids(self, items: Sequence[ItemId]) -> Sequence[CardId]: return super().card_ids_from_note_ids(items) From 08226e2004c92d1f8cb885d85cd86708c8c29945 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Tue, 6 Apr 2021 16:48:35 +0200 Subject: [PATCH 03/32] Change notes mode shortcut --- qt/aqt/forms/browser.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/aqt/forms/browser.ui b/qt/aqt/forms/browser.ui index 7bcb1c97b..9f499fe60 100644 --- a/qt/aqt/forms/browser.ui +++ b/qt/aqt/forms/browser.ui @@ -610,7 +610,7 @@ browsing_toggle_showing_cards_notes - Ctrl+M + Alt+T From 929b7dc15b6b6a3f31bb4b7abdbbcdc41c916717 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Tue, 6 Apr 2021 16:54:09 +0200 Subject: [PATCH 04/32] Make Column a strum --- rslib/src/browser_table.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index d24465158..22942e660 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use itertools::Itertools; use serde_repr::{Deserialize_repr, Serialize_repr}; +use strum::{Display, EnumString}; use crate::error::{AnkiError, Result}; use crate::i18n::I18n; @@ -21,9 +22,11 @@ use crate::{ timestamp::{TimestampMillis, TimestampSecs}, }; -#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy)] +#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy, Display, EnumString)] +#[strum(serialize_all = "camelCase")] #[repr(u8)] pub enum Column { + #[strum(serialize = "")] Custom, Question, Answer, @@ -31,23 +34,35 @@ pub enum Column { CardDue, CardEase, CardLapses, + #[strum(serialize = "cardIvl")] CardInterval, CardMod, CardReps, + #[strum(serialize = "template")] CardTemplate, NoteCards, + #[strum(serialize = "noteCrt")] NoteCreation, NoteDue, NoteEase, + #[strum(serialize = "noteFld")] NoteField, + #[strum(serialize = "noteIvl")] NoteInterval, NoteLapses, NoteMod, NoteReps, NoteTags, + #[strum(serialize = "note")] Notetype, } +impl Default for Column { + fn default() -> Self { + Column::Custom + } +} + #[derive(Debug, PartialEq)] pub struct Row { pub cells: Vec, From 11bdeb9ca40303b4d33b6bf92c652cf21ba56d1e Mon Sep 17 00:00:00 2001 From: RumovZ Date: Tue, 6 Apr 2021 19:46:12 +0200 Subject: [PATCH 05/32] Add column logic on backend --- rslib/backend.proto | 15 +++ rslib/src/backend/search/browser_table.rs | 146 +++++++++++++++++----- rslib/src/backend/search/mod.rs | 8 ++ 3 files changed, 140 insertions(+), 29 deletions(-) diff --git a/rslib/backend.proto b/rslib/backend.proto index 06fe920e1..0d5b2cb55 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -248,6 +248,8 @@ service SearchService { rpc JoinSearchNodes(JoinSearchNodesIn) returns (String); rpc ReplaceSearchNode(ReplaceSearchNodeIn) returns (String); rpc FindAndReplace(FindAndReplaceIn) returns (OpChangesWithCount); + rpc AllBrowserCardColumns(Empty) returns (BrowserColumns); + rpc AllBrowserNoteColumns(Empty) returns (BrowserColumns); rpc BrowserRowForId(Int64) returns (BrowserRow); rpc SetDesktopBrowserCardColumns(StringList) returns (Empty); rpc SetDesktopBrowserNoteColumns(StringList) returns (Empty); @@ -1052,6 +1054,19 @@ message FindAndReplaceIn { string field_name = 6; } +message BrowserColumns { + repeated BrowserColumn columns = 1; +} + +message BrowserColumn { + string key = 1; + string label = 2; + bool is_sortable = 3; + bool sorts_reversed = 4; + bool uses_cell_font = 5; + bool aligns_centered = 6; +} + message BrowserRow { message Cell { string text = 1; diff --git a/rslib/src/backend/search/browser_table.rs b/rslib/src/backend/search/browser_table.rs index cb405769a..f53c57845 100644 --- a/rslib/src/backend/search/browser_table.rs +++ b/rslib/src/backend/search/browser_table.rs @@ -1,41 +1,129 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -use crate::{backend_proto as pb, browser_table}; +use std::str::FromStr; -impl From for Vec { - fn from(input: pb::StringList) -> Self { - input.vals.into_iter().map(Into::into).collect() +use crate::{backend_proto as pb, browser_table, collection::Collection, i18n::I18n}; + +const CARD_COLUMNS: [browser_table::Column; 15] = [ + browser_table::Column::Question, + browser_table::Column::Answer, + browser_table::Column::CardDeck, + browser_table::Column::CardDue, + browser_table::Column::CardEase, + browser_table::Column::CardLapses, + browser_table::Column::CardInterval, + browser_table::Column::CardMod, + browser_table::Column::CardReps, + browser_table::Column::CardTemplate, + browser_table::Column::NoteCreation, + browser_table::Column::NoteField, + browser_table::Column::NoteMod, + browser_table::Column::NoteTags, + browser_table::Column::Notetype, +]; + +const NOTE_COLUMNS: [browser_table::Column; 11] = [ + browser_table::Column::NoteCards, + browser_table::Column::NoteCreation, + browser_table::Column::NoteDue, + browser_table::Column::NoteEase, + browser_table::Column::NoteField, + browser_table::Column::NoteInterval, + browser_table::Column::NoteLapses, + browser_table::Column::NoteMod, + browser_table::Column::NoteReps, + browser_table::Column::NoteTags, + browser_table::Column::Notetype, +]; + +impl Collection { + pub(crate) fn all_browser_card_columns(&self) -> pb::BrowserColumns { + self.to_pb_columns(&CARD_COLUMNS) + } + + pub(crate) fn all_browser_note_columns(&self) -> pb::BrowserColumns { + self.to_pb_columns(&NOTE_COLUMNS) + } + + fn to_pb_columns(&self, columns: &[browser_table::Column]) -> pb::BrowserColumns { + let mut columns: Vec = + columns.iter().map(|c| c.to_pb_column(&self.tr)).collect(); + columns.sort_by(|c1, c2| c1.label.cmp(&c2.label)); + pb::BrowserColumns { columns } } } -impl From for browser_table::Column { - fn from(text: String) -> Self { - match text.as_str() { - "question" => browser_table::Column::Question, - "answer" => browser_table::Column::Answer, - "deck" => browser_table::Column::CardDeck, - "cardDue" => browser_table::Column::CardDue, - "cardEase" => browser_table::Column::CardEase, - "cardLapses" => browser_table::Column::CardLapses, - "cardIvl" => browser_table::Column::CardInterval, - "cardMod" => browser_table::Column::CardMod, - "cardReps" => browser_table::Column::CardReps, - "template" => browser_table::Column::CardTemplate, - "noteCards" => browser_table::Column::NoteCards, - "noteCrt" => browser_table::Column::NoteCreation, - "noteDue" => browser_table::Column::NoteDue, - "noteEase" => browser_table::Column::NoteEase, - "noteFld" => browser_table::Column::NoteField, - "noteIvl" => browser_table::Column::NoteInterval, - "noteLapses" => browser_table::Column::NoteLapses, - "noteMod" => browser_table::Column::NoteMod, - "noteReps" => browser_table::Column::NoteReps, - "noteTags" => browser_table::Column::NoteTags, - "note" => browser_table::Column::Notetype, - _ => browser_table::Column::Custom, +impl browser_table::Column { + fn to_pb_column(self, i18n: &I18n) -> pb::BrowserColumn { + pb::BrowserColumn { + key: self.to_string(), + label: self.localized_label(i18n), + is_sortable: self.is_sortable(), + sorts_reversed: self == browser_table::Column::NoteField, + uses_cell_font: self.uses_cell_font(), + aligns_centered: self.aligns_centered(), } } + + fn is_sortable(self) -> bool { + !matches!(self, Self::Question | Self::Answer | Self::Custom) + } + + fn uses_cell_font(self) -> bool { + matches!(self, Self::Question | Self::Answer | Self::NoteField) + } + + fn aligns_centered(self) -> bool { + !matches!( + self, + Self::Question + | Self::Answer + | Self::CardTemplate + | Self::CardDeck + | Self::NoteField + | Self::Notetype + | Self::NoteTags + ) + } + + fn localized_label(self, i18n: &I18n) -> String { + match self { + Self::Custom => i18n.browsing_addon(), + Self::Question => i18n.browsing_question(), + Self::Answer => i18n.browsing_answer(), + Self::CardDeck => i18n.decks_deck(), + Self::CardDue => i18n.statistics_due_date(), + Self::CardEase => i18n.browsing_ease(), + Self::CardInterval => i18n.browsing_interval(), + Self::CardLapses => i18n.scheduling_lapses(), + Self::CardMod => i18n.search_card_modified(), + Self::CardReps => i18n.scheduling_reviews(), + Self::CardTemplate => i18n.browsing_card(), + Self::NoteCards => i18n.editing_cards(), + Self::NoteCreation => i18n.browsing_created(), + Self::NoteDue => i18n.statistics_due_date(), + Self::NoteEase => i18n.browsing_average_ease(), + Self::NoteField => i18n.browsing_sort_field(), + Self::NoteInterval => i18n.browsing_average_interval(), + Self::NoteMod => i18n.search_note_modified(), + Self::NoteLapses => i18n.scheduling_lapses(), + Self::NoteReps => i18n.scheduling_reviews(), + Self::NoteTags => i18n.editing_tags(), + Self::Notetype => i18n.browsing_note(), + } + .into() + } +} + +impl From for Vec { + fn from(input: pb::StringList) -> Self { + input + .vals + .iter() + .map(|c| browser_table::Column::from_str(c).unwrap_or_default()) + .collect() + } } impl From for pb::BrowserRow { diff --git a/rslib/src/backend/search/mod.rs b/rslib/src/backend/search/mod.rs index 5aaf25780..5a09b3427 100644 --- a/rslib/src/backend/search/mod.rs +++ b/rslib/src/backend/search/mod.rs @@ -89,6 +89,14 @@ impl SearchService for Backend { }) } + fn all_browser_card_columns(&self, _input: pb::Empty) -> Result { + self.with_col(|col| Ok(col.all_browser_card_columns())) + } + + fn all_browser_note_columns(&self, _input: pb::Empty) -> Result { + self.with_col(|col| Ok(col.all_browser_note_columns())) + } + fn browser_row_for_id(&self, input: pb::Int64) -> Result { self.with_col(|col| col.browser_row_for_id(input.val).map(Into::into)) } From a5c02910a6d7e62599a558594d6d75bd57e17d71 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Tue, 6 Apr 2021 19:47:03 +0200 Subject: [PATCH 06/32] Use backend column objects on frontend --- pylib/anki/collection.py | 21 ++++++ qt/aqt/table.py | 142 +++++++++++++++------------------------ 2 files changed, 77 insertions(+), 86 deletions(-) diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 44a102bb7..a0819f29a 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -696,6 +696,16 @@ class Collection: # Browser Table ########################################################################## + def all_browser_card_columns( + self, + ) -> Generator[Tuple[str, str, bool, bool, bool, bool], None, None]: + return (_column_data(c) for c in self._backend.all_browser_card_columns()) + + def all_browser_note_columns( + self, + ) -> Generator[Tuple[str, str, bool, bool, bool, bool], None, None]: + return (_column_data(c) for c in self._backend.all_browser_note_columns()) + def browser_row_for_id( self, id_: int ) -> Tuple[Generator[Tuple[str, bool], None, None], BrowserRow.Color.V, str, int]: @@ -1131,3 +1141,14 @@ def _build_sort_mode( return _pb.SortOrder(none=_pb.Empty()) else: return _pb.SortOrder(builtin=_pb.SortOrder.Builtin(kind=order, reverse=reverse)) + + +def _column_data(column: _pb.BrowserColumn) -> Tuple[str, str, bool, bool, bool, bool]: + return ( + column.key, + column.label, + column.is_sortable, + column.sorts_reversed, + column.uses_cell_font, + column.aligns_centered, + ) diff --git a/qt/aqt/table.py b/qt/aqt/table.py index 9df2c65ce..173b3666d 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -5,8 +5,7 @@ from __future__ import annotations import time from abc import ABC, abstractmethod, abstractproperty -from dataclasses import dataclass -from operator import itemgetter +from dataclasses import dataclass, field from typing import ( Any, Callable, @@ -346,13 +345,13 @@ class Table: def _on_header_context(self, pos: QPoint) -> None: gpos = self._view.mapToGlobal(pos) m = QMenu() - for column, name in self._state.columns: - a = m.addAction(name) + for key, column in self._state.columns.items(): + a = m.addAction(column.label) a.setCheckable(True) - a.setChecked(self._model.active_column_index(column) is not None) + a.setChecked(self._model.active_column_index(key) is not None) qconnect( a.toggled, - lambda checked, column=column: self._on_column_toggled(checked, column), + lambda checked, key=key: self._on_column_toggled(checked, key), ) gui_hooks.browser_header_will_show_context_menu(self.browser, m) m.exec_(gpos) @@ -371,16 +370,18 @@ class Table: if checked: self._scroll_to_column(self._model.len_columns() - 1) - def _on_sort_column_changed(self, index: int, order: int) -> None: + def _on_sort_column_changed(self, section: int, order: int) -> None: order = bool(order) - sort_column = self._model.active_column(index) - if sort_column in ("question", "answer"): + column = self._model.column_at_section(section) + if column.is_sortable: + sort_key = column.key + else: showInfo(tr.browsing_sorting_on_this_column_is_not()) - sort_column = self._state.sort_column - if self._state.sort_column != sort_column: - self._state.sort_column = sort_column + sort_key = self._state.sort_column + if self._state.sort_column != sort_key: + self._state.sort_column = sort_key # default to descending for non-text fields - if sort_column == "noteFld": + if column.sorts_reversed: order = not order self._state.sort_backwards = order self.browser.search() @@ -519,7 +520,7 @@ class Table: class ItemState(ABC): - _columns: List[Tuple[str, str]] + _columns: Dict[str, Column] _active_columns: List[str] _sort_column: str _sort_backwards: bool @@ -541,12 +542,24 @@ class ItemState(ABC): def card_ids_from_note_ids(self, items: Sequence[ItemId]) -> Sequence[CardId]: return self.col.db.list(f"select id from cards where nid in {ids2str(items)}") + def column_at(self, index: int) -> Column: + """Returns the column object corresponding to the active column at index or the default + column object if no data is associated with the active column. + """ + + key = self._active_columns[index] + try: + return self._columns[key] + except KeyError: + self._columns[key] = Column() + return self._columns[key] + # 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]]: + def columns(self) -> Dict[str, Column]: """Return all for the state available columns.""" @abstractproperty @@ -615,35 +628,17 @@ class ItemState(ABC): class CardState(ItemState): def __init__(self, col: Collection) -> None: super().__init__(col) - self._load_columns() + self._columns = dict( + ((c[0], Column(*c)) for c in self.col.all_browser_card_columns()) + ) self._active_columns = self.col.load_browser_card_columns() self._sort_column = self.col.get_config("sortType") self._sort_backwards = self.col.get_config_bool( Config.Bool.BROWSER_SORT_BACKWARDS ) - def _load_columns(self) -> None: - self._columns = [ - ("question", tr.browsing_question()), - ("answer", tr.browsing_answer()), - ("template", tr.browsing_card()), - ("deck", tr.decks_deck()), - ("noteFld", tr.browsing_sort_field()), - ("noteCrt", tr.browsing_created()), - ("noteMod", tr.search_note_modified()), - ("cardMod", tr.search_card_modified()), - ("cardDue", tr.statistics_due_date()), - ("cardIvl", tr.browsing_interval()), - ("cardEase", tr.browsing_ease()), - ("cardReps", tr.scheduling_reviews()), - ("cardLapses", tr.scheduling_lapses()), - ("noteTags", tr.editing_tags()), - ("note", tr.browsing_note()), - ] - self._columns.sort(key=itemgetter(1)) - @property - def columns(self) -> List[Tuple[str, str]]: + def columns(self) -> Dict[str, Column]: return self._columns @property @@ -703,31 +698,17 @@ class CardState(ItemState): class NoteState(ItemState): def __init__(self, col: Collection) -> None: super().__init__(col) - self._load_columns() + self._columns = dict( + ((c[0], Column(*c)) for c in self.col.all_browser_note_columns()) + ) self._active_columns = self.col.load_browser_note_columns() self._sort_column = self.col.get_config("noteSortType") self._sort_backwards = self.col.get_config_bool( Config.Bool.BROWSER_NOTE_SORT_BACKWARDS ) - def _load_columns(self) -> None: - self._columns = [ - ("note", tr.browsing_note()), - ("noteCards", tr.editing_cards()), - ("noteCrt", tr.browsing_created()), - ("noteDue", tr.statistics_due_date()), - ("noteEase", tr.browsing_average_ease()), - ("noteFld", tr.browsing_sort_field()), - ("noteIvl", tr.browsing_average_interval()), - ("noteLapses", tr.scheduling_lapses()), - ("noteMod", tr.search_note_modified()), - ("noteReps", tr.scheduling_reviews()), - ("noteTags", tr.editing_tags()), - ] - self._columns.sort(key=itemgetter(1)) - @property - def columns(self) -> List[Tuple[str, str]]: + def columns(self) -> Dict[str, Column]: return self._columns @property @@ -788,6 +769,16 @@ class NoteState(ItemState): ########################################################################## +@dataclass +class Column: + key: str = None + label: str = field(default_factory=tr.browsing_addon) + is_sortable: bool = False + sorts_reversed: bool = False + uses_cell_font: bool = False + aligns_centered: bool = True + + @dataclass class Cell: text: str @@ -1022,8 +1013,11 @@ class DataModel(QAbstractTableModel): # Columns - def active_column(self, index: int) -> str: - return self._state.active_columns[index] + def column_at(self, index: QModelIndex) -> Column: + return self._state.column_at(index.column()) + + def column_at_section(self, section: int) -> Column: + return self._state.column_at(section) def active_column_index(self, column: str) -> Optional[int]: return ( @@ -1054,11 +1048,7 @@ class DataModel(QAbstractTableModel): if not index.isValid(): return QVariant() if role == Qt.FontRole: - if self.active_column(index.column()) not in ( - "question", - "answer", - "noteFld", - ): + if not self.column_at(index).uses_cell_font: return QVariant() qfont = QFont() row = self.get_row(index) @@ -1067,15 +1057,7 @@ class DataModel(QAbstractTableModel): return qfont if role == Qt.TextAlignmentRole: align: Union[Qt.AlignmentFlag, int] = Qt.AlignVCenter - if self.active_column(index.column()) not in ( - "question", - "answer", - "template", - "deck", - "noteFld", - "note", - "noteTags", - ): + if self.column_at(index).aligns_centered: align |= Qt.AlignHCenter return align if role in (Qt.DisplayRole, Qt.ToolTipRole): @@ -1085,21 +1067,9 @@ class DataModel(QAbstractTableModel): def headerData( self, section: int, orientation: Qt.Orientation, role: int = 0 ) -> Optional[str]: - if orientation == Qt.Vertical: - return None - elif role == Qt.DisplayRole and section < self.len_columns(): - column = self.active_column(section) - txt = None - for stype, name in self._state.columns: - if column == stype: - txt = name - break - # give the user a hint an invalid column was added by an add-on - if not txt: - txt = tr.browsing_addon() - return txt - else: - return None + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + return self.column_at_section(section).label + return None def flags(self, index: QModelIndex) -> Qt.ItemFlags: if self.get_row(index).is_deleted: From 6abb05d07463c2e35153660eb53cd5716cde925d Mon Sep 17 00:00:00 2001 From: RumovZ Date: Tue, 6 Apr 2021 23:02:58 +0200 Subject: [PATCH 07/32] Save key for unsupported add-on columns --- qt/aqt/table.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qt/aqt/table.py b/qt/aqt/table.py index 173b3666d..dafa69819 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -551,7 +551,7 @@ class ItemState(ABC): try: return self._columns[key] except KeyError: - self._columns[key] = Column() + self._columns[key] = Column(key) return self._columns[key] # Columns and sorting @@ -771,7 +771,7 @@ class NoteState(ItemState): @dataclass class Column: - key: str = None + key: str label: str = field(default_factory=tr.browsing_addon) is_sortable: bool = False sorts_reversed: bool = False From ee0ad6f5d6e473dee4f93aba9ee1239f9ace6559 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Tue, 6 Apr 2021 23:03:30 +0200 Subject: [PATCH 08/32] Fix deck column serialization string --- rslib/src/browser_table.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index 22942e660..9c55f6267 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -30,6 +30,7 @@ pub enum Column { Custom, Question, Answer, + #[strum(serialize = "deck")] CardDeck, CardDue, CardEase, From 6c3c479906a6edaaea3725f374002bf179cafdb4 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Thu, 8 Apr 2021 10:16:06 +0200 Subject: [PATCH 09/32] Move BrowserColumn into BrowserColumns message --- pylib/anki/collection.py | 4 +++- rslib/backend.proto | 19 +++++++++---------- rslib/src/backend/search/browser_table.rs | 6 +++--- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index a0819f29a..0074b0967 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -1143,7 +1143,9 @@ def _build_sort_mode( return _pb.SortOrder(builtin=_pb.SortOrder.Builtin(kind=order, reverse=reverse)) -def _column_data(column: _pb.BrowserColumn) -> Tuple[str, str, bool, bool, bool, bool]: +def _column_data( + column: _pb.BrowserColumns.Column, +) -> Tuple[str, str, bool, bool, bool, bool]: return ( column.key, column.label, diff --git a/rslib/backend.proto b/rslib/backend.proto index 0d5b2cb55..ddddb0175 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -1055,16 +1055,15 @@ message FindAndReplaceIn { } message BrowserColumns { - repeated BrowserColumn columns = 1; -} - -message BrowserColumn { - string key = 1; - string label = 2; - bool is_sortable = 3; - bool sorts_reversed = 4; - bool uses_cell_font = 5; - bool aligns_centered = 6; + message Column { + string key = 1; + string label = 2; + bool is_sortable = 3; + bool sorts_reversed = 4; + bool uses_cell_font = 5; + bool aligns_centered = 6; + } + repeated Column columns = 1; } message BrowserRow { diff --git a/rslib/src/backend/search/browser_table.rs b/rslib/src/backend/search/browser_table.rs index f53c57845..d500fcc9c 100644 --- a/rslib/src/backend/search/browser_table.rs +++ b/rslib/src/backend/search/browser_table.rs @@ -47,7 +47,7 @@ impl Collection { } fn to_pb_columns(&self, columns: &[browser_table::Column]) -> pb::BrowserColumns { - let mut columns: Vec = + let mut columns: Vec = columns.iter().map(|c| c.to_pb_column(&self.tr)).collect(); columns.sort_by(|c1, c2| c1.label.cmp(&c2.label)); pb::BrowserColumns { columns } @@ -55,8 +55,8 @@ impl Collection { } impl browser_table::Column { - fn to_pb_column(self, i18n: &I18n) -> pb::BrowserColumn { - pb::BrowserColumn { + fn to_pb_column(self, i18n: &I18n) -> pb::browser_columns::Column { + pb::browser_columns::Column { key: self.to_string(), label: self.localized_label(i18n), is_sortable: self.is_sortable(), From f78401619a8dc6334b3d5bd3ed878e3e091ba519 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Thu, 8 Apr 2021 11:17:25 +0200 Subject: [PATCH 10/32] Remove Column class and use pb class instead --- pylib/anki/collection.py | 26 +++++--------------------- qt/aqt/table.py | 38 ++++++++++++++++++++------------------ 2 files changed, 25 insertions(+), 39 deletions(-) diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 0074b0967..06a36f114 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -21,6 +21,7 @@ OpChangesWithId = _pb.OpChangesWithId OpChangesAfterUndo = _pb.OpChangesAfterUndo DefaultsForAdding = _pb.DeckAndNotetype BrowserRow = _pb.BrowserRow +BrowserColumns = _pb.BrowserColumns import copy import os @@ -696,15 +697,11 @@ class Collection: # Browser Table ########################################################################## - def all_browser_card_columns( - self, - ) -> Generator[Tuple[str, str, bool, bool, bool, bool], None, None]: - return (_column_data(c) for c in self._backend.all_browser_card_columns()) + def all_browser_card_columns(self) -> Sequence[BrowserColumns.Column]: + return self._backend.all_browser_card_columns() - def all_browser_note_columns( - self, - ) -> Generator[Tuple[str, str, bool, bool, bool, bool], None, None]: - return (_column_data(c) for c in self._backend.all_browser_note_columns()) + def all_browser_note_columns(self) -> Sequence[BrowserColumns.Column]: + return self._backend.all_browser_note_columns() def browser_row_for_id( self, id_: int @@ -1141,16 +1138,3 @@ def _build_sort_mode( return _pb.SortOrder(none=_pb.Empty()) else: return _pb.SortOrder(builtin=_pb.SortOrder.Builtin(kind=order, reverse=reverse)) - - -def _column_data( - column: _pb.BrowserColumns.Column, -) -> Tuple[str, str, bool, bool, bool, bool]: - return ( - column.key, - column.label, - column.is_sortable, - column.sorts_reversed, - column.uses_cell_font, - column.aligns_centered, - ) diff --git a/qt/aqt/table.py b/qt/aqt/table.py index dafa69819..99af64a7b 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -5,7 +5,7 @@ from __future__ import annotations import time from abc import ABC, abstractmethod, abstractproperty -from dataclasses import dataclass, field +from dataclasses import dataclass from typing import ( Any, Callable, @@ -22,6 +22,7 @@ from typing import ( import aqt import aqt.forms from anki.cards import Card, CardId +from anki.collection import BrowserColumns as Columns from anki.collection import BrowserRow, Collection, Config, OpChanges from anki.consts import * from anki.errors import NotFoundError @@ -39,6 +40,7 @@ from aqt.utils import ( tr, ) +Column = Columns.Column ItemId = Union[CardId, NoteId] ItemList = Union[Sequence[CardId], Sequence[NoteId]] @@ -551,7 +553,7 @@ class ItemState(ABC): try: return self._columns[key] except KeyError: - self._columns[key] = Column(key) + self._columns[key] = addon_column_fillin(key) return self._columns[key] # Columns and sorting @@ -628,9 +630,7 @@ class ItemState(ABC): class CardState(ItemState): def __init__(self, col: Collection) -> None: super().__init__(col) - self._columns = dict( - ((c[0], Column(*c)) for c in self.col.all_browser_card_columns()) - ) + self._columns = dict(((c.key, c) for c in self.col.all_browser_card_columns())) self._active_columns = self.col.load_browser_card_columns() self._sort_column = self.col.get_config("sortType") self._sort_backwards = self.col.get_config_bool( @@ -698,9 +698,7 @@ class CardState(ItemState): class NoteState(ItemState): def __init__(self, col: Collection) -> None: super().__init__(col) - self._columns = dict( - ((c[0], Column(*c)) for c in self.col.all_browser_note_columns()) - ) + self._columns = dict(((c.key, c) for c in self.col.all_browser_note_columns())) self._active_columns = self.col.load_browser_note_columns() self._sort_column = self.col.get_config("noteSortType") self._sort_backwards = self.col.get_config_bool( @@ -769,16 +767,6 @@ class NoteState(ItemState): ########################################################################## -@dataclass -class Column: - key: str - label: str = field(default_factory=tr.browsing_addon) - is_sortable: bool = False - sorts_reversed: bool = False - uses_cell_font: bool = False - aligns_centered: bool = True - - @dataclass class Cell: text: str @@ -1097,3 +1085,17 @@ class StatusDelegate(QItemDelegate): painter.fillRect(option.rect, brush) painter.restore() return QItemDelegate.paint(self, painter, option, index) + + +def addon_column_fillin(key: str) -> Column: + """Return a column with generic fields and a label indicating to the user that this column was + added by an add-on. + """ + return Column( + key=key, + label=tr.browsing_addon(), + is_sortable=False, + sorts_reversed=False, + uses_cell_font=False, + aligns_centered=True, + ) From d8a0aa922c96d8760e26c88d68edad3a2689a336 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Thu, 8 Apr 2021 11:28:29 +0200 Subject: [PATCH 11/32] Add enum for column alignment --- qt/.pylintrc | 1 + qt/aqt/table.py | 4 ++-- rslib/backend.proto | 6 +++++- rslib/src/backend/search/browser_table.rs | 22 +++++++++++----------- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/qt/.pylintrc b/qt/.pylintrc index b4809e76d..fb0800e47 100644 --- a/qt/.pylintrc +++ b/qt/.pylintrc @@ -6,6 +6,7 @@ ignore = forms,hooks_gen.py [TYPECHECK] ignored-modules=win32file,pywintypes,socket,win32pipe,winrt,pyaudio ignored-classes= + BrowserColumns, BrowserRow, SearchNode, Config, diff --git a/qt/aqt/table.py b/qt/aqt/table.py index 99af64a7b..dc4be504b 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -1045,7 +1045,7 @@ class DataModel(QAbstractTableModel): return qfont if role == Qt.TextAlignmentRole: align: Union[Qt.AlignmentFlag, int] = Qt.AlignVCenter - if self.column_at(index).aligns_centered: + if self.column_at(index).alignment == Columns.ALIGNMENT_CENTER: align |= Qt.AlignHCenter return align if role in (Qt.DisplayRole, Qt.ToolTipRole): @@ -1097,5 +1097,5 @@ def addon_column_fillin(key: str) -> Column: is_sortable=False, sorts_reversed=False, uses_cell_font=False, - aligns_centered=True, + alignment=Columns.ALIGNMENT_CENTER, ) diff --git a/rslib/backend.proto b/rslib/backend.proto index ddddb0175..8384ba864 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -1055,13 +1055,17 @@ message FindAndReplaceIn { } message BrowserColumns { + enum Alignment { + ALIGNMENT_START = 0; + ALIGNMENT_CENTER = 1; + } message Column { string key = 1; string label = 2; bool is_sortable = 3; bool sorts_reversed = 4; bool uses_cell_font = 5; - bool aligns_centered = 6; + Alignment alignment = 6; } repeated Column columns = 1; } diff --git a/rslib/src/backend/search/browser_table.rs b/rslib/src/backend/search/browser_table.rs index d500fcc9c..ca1b431ef 100644 --- a/rslib/src/backend/search/browser_table.rs +++ b/rslib/src/backend/search/browser_table.rs @@ -62,7 +62,7 @@ impl browser_table::Column { is_sortable: self.is_sortable(), sorts_reversed: self == browser_table::Column::NoteField, uses_cell_font: self.uses_cell_font(), - aligns_centered: self.aligns_centered(), + alignment: self.alignment() as i32, } } @@ -74,17 +74,17 @@ impl browser_table::Column { matches!(self, Self::Question | Self::Answer | Self::NoteField) } - fn aligns_centered(self) -> bool { - !matches!( - self, + fn alignment(self) -> pb::browser_columns::Alignment { + match self { Self::Question - | Self::Answer - | Self::CardTemplate - | Self::CardDeck - | Self::NoteField - | Self::Notetype - | Self::NoteTags - ) + | Self::Answer + | Self::CardTemplate + | Self::CardDeck + | Self::NoteField + | Self::Notetype + | Self::NoteTags => pb::browser_columns::Alignment::Start, + _ => pb::browser_columns::Alignment::Center, + } } fn localized_label(self, i18n: &I18n) -> String { From 8a131da9a2c8f08499d85c00cce6c7cc033d3e0e Mon Sep 17 00:00:00 2001 From: RumovZ Date: Thu, 8 Apr 2021 11:40:24 +0200 Subject: [PATCH 12/32] Add enum for column sorting --- qt/aqt/table.py | 11 +++++------ rslib/backend.proto | 8 ++++++-- rslib/src/backend/search/browser_table.rs | 11 +++++++---- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/qt/aqt/table.py b/qt/aqt/table.py index dc4be504b..76b3d2f5a 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -375,15 +375,15 @@ class Table: def _on_sort_column_changed(self, section: int, order: int) -> None: order = bool(order) column = self._model.column_at_section(section) - if column.is_sortable: - sort_key = column.key - else: + if column.sorting == Columns.SORTING_NONE: showInfo(tr.browsing_sorting_on_this_column_is_not()) sort_key = self._state.sort_column + else: + sort_key = column.key if self._state.sort_column != sort_key: self._state.sort_column = sort_key # default to descending for non-text fields - if column.sorts_reversed: + if column.sorting == Columns.SORTING_REVERSED: order = not order self._state.sort_backwards = order self.browser.search() @@ -1094,8 +1094,7 @@ def addon_column_fillin(key: str) -> Column: return Column( key=key, label=tr.browsing_addon(), - is_sortable=False, - sorts_reversed=False, + sorting=Columns.SORTING_NONE, uses_cell_font=False, alignment=Columns.ALIGNMENT_CENTER, ) diff --git a/rslib/backend.proto b/rslib/backend.proto index 8384ba864..c72643937 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -1055,6 +1055,11 @@ message FindAndReplaceIn { } message BrowserColumns { + enum Sorting { + SORTING_NONE = 0; + SORTING_NORMAL = 1; + SORTING_REVERSED = 2; + } enum Alignment { ALIGNMENT_START = 0; ALIGNMENT_CENTER = 1; @@ -1062,8 +1067,7 @@ message BrowserColumns { message Column { string key = 1; string label = 2; - bool is_sortable = 3; - bool sorts_reversed = 4; + Sorting sorting = 3; bool uses_cell_font = 5; Alignment alignment = 6; } diff --git a/rslib/src/backend/search/browser_table.rs b/rslib/src/backend/search/browser_table.rs index ca1b431ef..41c3aaf15 100644 --- a/rslib/src/backend/search/browser_table.rs +++ b/rslib/src/backend/search/browser_table.rs @@ -59,15 +59,18 @@ impl browser_table::Column { pb::browser_columns::Column { key: self.to_string(), label: self.localized_label(i18n), - is_sortable: self.is_sortable(), - sorts_reversed: self == browser_table::Column::NoteField, + sorting: self.sorting() as i32, uses_cell_font: self.uses_cell_font(), alignment: self.alignment() as i32, } } - fn is_sortable(self) -> bool { - !matches!(self, Self::Question | Self::Answer | Self::Custom) + fn sorting(self) -> pb::browser_columns::Sorting { + match self { + Self::Question | Self::Answer | Self::Custom => pb::browser_columns::Sorting::None, + Self::NoteField => pb::browser_columns::Sorting::Reversed, + _ => pb::browser_columns::Sorting::Normal, + } } fn uses_cell_font(self) -> bool { From c6ebb9b441d002fae3e3c2e609e6bccfa4c7a96a Mon Sep 17 00:00:00 2001 From: RumovZ Date: Thu, 8 Apr 2021 13:29:32 +0200 Subject: [PATCH 13/32] Merge row contexts --- rslib/src/browser_table.rs | 338 +++++++++++++++---------------------- 1 file changed, 137 insertions(+), 201 deletions(-) diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index 9c55f6267..927dea6b7 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -94,55 +94,9 @@ pub struct Font { pub size: u32, } -trait RowContext { - fn get_cell_text(&mut self, column: Column) -> Result; - fn get_row_color(&self) -> Color; - fn get_row_font(&self) -> Result; - fn note(&self) -> &Note; - fn notetype(&self) -> &Notetype; - - fn get_cell(&mut self, column: Column) -> Result { - Ok(Cell { - text: self.get_cell_text(column)?, - is_rtl: self.get_is_rtl(column), - }) - } - - fn note_creation_str(&self) -> String { - TimestampMillis(self.note().id.into()) - .as_secs() - .date_string() - } - - fn note_field_str(&self) -> String { - let index = self.notetype().config.sort_field_idx as usize; - html_to_text_line(&self.note().fields()[index]).into() - } - - fn get_is_rtl(&self, column: Column) -> bool { - match column { - Column::NoteField => { - let index = self.notetype().config.sort_field_idx as usize; - self.notetype().fields[index].config.rtl - } - _ => false, - } - } - - fn browser_row_for_id(&mut self, columns: &[Column]) -> Result { - Ok(Row { - cells: columns - .iter() - .map(|&column| self.get_cell(column)) - .collect::>()?, - color: self.get_row_color(), - font: self.get_row_font()?, - }) - } -} - -struct CardRowContext { - card: Card, +struct RowContext { + notes_mode: bool, + cards: Vec, note: Note, notetype: Arc, deck: Arc, @@ -159,14 +113,6 @@ struct RenderContext { answer_nodes: Vec, } -struct NoteRowContext { - note: Note, - notetype: Arc, - cards: Vec, - tr: I18n, - timing: SchedTimingToday, -} - fn card_render_required(columns: &[Column]) -> bool { columns .iter() @@ -218,18 +164,15 @@ impl Note { impl Collection { pub fn browser_row_for_id(&mut self, id: i64) -> Result { - if self.get_bool(BoolKey::BrowserTableShowNotesMode) { - let columns = self - .get_desktop_browser_note_columns() - .ok_or_else(|| AnkiError::invalid_input("Note columns not set."))?; - NoteRowContext::new(self, id)?.browser_row_for_id(&columns) + let notes_mode = self.get_bool(BoolKey::BrowserTableShowNotesMode); + let columns = if notes_mode { + self.get_desktop_browser_note_columns() + .ok_or_else(|| AnkiError::invalid_input("Note columns not set."))? } else { - let columns = self - .get_desktop_browser_card_columns() - .ok_or_else(|| AnkiError::invalid_input("Card columns not set."))?; - CardRowContext::new(self, id, card_render_required(&columns))? - .browser_row_for_id(&columns) - } + self.get_desktop_browser_card_columns() + .ok_or_else(|| AnkiError::invalid_input("Card columns not set."))? + }; + RowContext::new(self, id, notes_mode, card_render_required(&columns))?.browser_row(&columns) } fn get_note_maybe_with_fields(&self, id: NoteId, _with_fields: bool) -> Result { @@ -275,20 +218,32 @@ impl RenderContext { } } -impl CardRowContext { - fn new(col: &mut Collection, id: i64, with_card_render: bool) -> Result { - let card = col - .storage - .get_card(CardId(id))? - .ok_or(AnkiError::NotFound)?; - let note = col.get_note_maybe_with_fields(card.note_id, with_card_render)?; +impl RowContext { + fn new( + col: &mut Collection, + id: i64, + notes_mode: bool, + with_card_render: bool, + ) -> Result { + let cards; + let note; + if notes_mode { + note = col.get_note_maybe_with_fields(NoteId(id), with_card_render)?; + cards = col.storage.all_cards_of_note(note.id)?; + } else { + cards = vec![col + .storage + .get_card(CardId(id))? + .ok_or(AnkiError::NotFound)?]; + note = col.get_note_maybe_with_fields(cards[0].note_id, with_card_render)?; + } let notetype = col .get_notetype(note.notetype_id)? .ok_or(AnkiError::NotFound)?; - let deck = col.get_deck(card.deck_id)?.ok_or(AnkiError::NotFound)?; - let original_deck = if card.original_deck_id.0 != 0 { + let deck = col.get_deck(cards[0].deck_id)?.ok_or(AnkiError::NotFound)?; + let original_deck = if cards[0].original_deck_id.0 != 0 { Some( - col.get_deck(card.original_deck_id)? + col.get_deck(cards[0].original_deck_id)? .ok_or(AnkiError::NotFound)?, ) } else { @@ -296,13 +251,14 @@ impl CardRowContext { }; let timing = col.timing_today()?; let render_context = if with_card_render { - Some(RenderContext::new(col, &card, ¬e, ¬etype)?) + Some(RenderContext::new(col, &cards[0], ¬e, ¬etype)?) } else { None }; - Ok(CardRowContext { - card, + Ok(RowContext { + notes_mode, + cards, note, notetype, deck, @@ -313,8 +269,72 @@ impl CardRowContext { }) } + fn browser_row(&mut self, columns: &[Column]) -> Result { + Ok(Row { + cells: columns + .iter() + .map(|&column| self.get_cell(column)) + .collect::>()?, + color: self.get_row_color(), + font: self.get_row_font()?, + }) + } + + fn get_cell(&mut self, column: Column) -> Result { + Ok(Cell { + text: self.get_cell_text(column)?, + is_rtl: self.get_is_rtl(column), + }) + } + + fn get_cell_text(&mut self, column: Column) -> Result { + Ok(match column { + Column::Question => self.question_str(), + Column::Answer => self.answer_str(), + Column::CardDeck => self.deck_str(), + Column::CardDue => self.card_due_str(), + Column::CardEase => self.card_ease_str(), + Column::CardInterval => self.card_interval_str(), + Column::CardLapses => self.cards[0].lapses.to_string(), + Column::CardMod => self.cards[0].mtime.date_string(), + Column::CardReps => self.cards[0].reps.to_string(), + Column::CardTemplate => self.template_str()?, + Column::NoteCreation => self.note_creation_str(), + Column::NoteField => self.note_field_str(), + Column::NoteMod => self.note.mtime.date_string(), + Column::NoteTags => self.note.tags.join(" "), + Column::Notetype => self.notetype.name.to_owned(), + Column::NoteCards => self.cards.len().to_string(), + Column::NoteDue => self.note_due_str(), + Column::NoteEase => self.note_ease_str(), + Column::NoteInterval => self.note_interval_str(), + Column::NoteLapses => self.cards.iter().map(|c| c.lapses).sum::().to_string(), + Column::NoteReps => self.cards.iter().map(|c| c.reps).sum::().to_string(), + Column::Custom => "".to_string(), + }) + } + + fn note_creation_str(&self) -> String { + TimestampMillis(self.note.id.into()).as_secs().date_string() + } + + fn note_field_str(&self) -> String { + let index = self.notetype.config.sort_field_idx as usize; + html_to_text_line(&self.note.fields()[index]).into() + } + + fn get_is_rtl(&self, column: Column) -> bool { + match column { + Column::NoteField => { + let index = self.notetype.config.sort_field_idx as usize; + self.notetype.fields[index].config.rtl + } + _ => false, + } + } + fn template(&self) -> Result<&CardTemplate> { - self.notetype.get_template(self.card.template_idx) + self.notetype.get_template(self.cards[0].template_idx) } fn answer_str(&self) -> String { @@ -343,16 +363,16 @@ impl CardRowContext { } fn card_due_str(&mut self) -> String { - let due = if self.card.is_filtered_deck() { + let due = if self.cards[0].is_filtered_deck() { self.tr.browsing_filtered() - } else if self.card.is_new_type_or_queue() { - self.tr.statistics_due_for_new_card(self.card.due) - } else if let Some(time) = self.card.due_time(&self.timing) { + } else if self.cards[0].is_new_type_or_queue() { + self.tr.statistics_due_for_new_card(self.cards[0].due) + } else if let Some(time) = self.cards[0].due_time(&self.timing) { time.date_string().into() } else { return "".into(); }; - if self.card.is_undue_queue() { + if self.cards[0].is_undue_queue() { format!("({})", due) } else { due.into() @@ -360,17 +380,17 @@ impl CardRowContext { } fn card_ease_str(&self) -> String { - match self.card.ctype { + match self.cards[0].ctype { CardType::New => self.tr.browsing_new().into(), - _ => format!("{}%", self.card.ease_factor / 10), + _ => format!("{}%", self.cards[0].ease_factor / 10), } } fn card_interval_str(&self) -> String { - match self.card.ctype { + match self.cards[0].ctype { CardType::New => self.tr.browsing_new().into(), CardType::Learn => self.tr.browsing_learning().into(), - _ => time_span((self.card.interval * 86400) as f32, &self.tr, false), + _ => time_span((self.cards[0].interval * 86400) as f32, &self.tr, false), } } @@ -387,88 +407,13 @@ impl CardRowContext { let name = &self.template()?.name; Ok(match self.notetype.config.kind() { NotetypeKind::Normal => name.to_owned(), - NotetypeKind::Cloze => format!("{} {}", name, self.card.template_idx + 1), + NotetypeKind::Cloze => format!("{} {}", name, self.cards[0].template_idx + 1), }) } fn question_str(&self) -> String { html_to_text_line(&self.render_context.as_ref().unwrap().question).to_string() } -} - -impl RowContext for CardRowContext { - fn get_cell_text(&mut self, column: Column) -> Result { - Ok(match column { - Column::Question => self.question_str(), - Column::Answer => self.answer_str(), - Column::CardDeck => self.deck_str(), - Column::CardDue => self.card_due_str(), - Column::CardEase => self.card_ease_str(), - Column::CardInterval => self.card_interval_str(), - Column::CardLapses => self.card.lapses.to_string(), - Column::CardMod => self.card.mtime.date_string(), - Column::CardReps => self.card.reps.to_string(), - Column::CardTemplate => self.template_str()?, - Column::NoteCreation => self.note_creation_str(), - Column::NoteField => self.note_field_str(), - Column::NoteMod => self.note.mtime.date_string(), - Column::NoteTags => self.note.tags.join(" "), - Column::Notetype => self.notetype.name.to_owned(), - _ => "".to_string(), - }) - } - - fn get_row_color(&self) -> Color { - match self.card.flags { - 1 => Color::FlagRed, - 2 => Color::FlagOrange, - 3 => Color::FlagGreen, - 4 => Color::FlagBlue, - _ => { - if self.note.is_marked() { - Color::Marked - } else if self.card.queue == CardQueue::Suspended { - Color::Suspended - } else { - Color::Default - } - } - } - } - - fn get_row_font(&self) -> Result { - Ok(Font { - name: self.template()?.config.browser_font_name.to_owned(), - size: self.template()?.config.browser_font_size, - }) - } - - fn note(&self) -> &Note { - &self.note - } - - fn notetype(&self) -> &Notetype { - &self.notetype - } -} - -impl NoteRowContext { - fn new(col: &mut Collection, id: i64) -> Result { - let note = col.get_note_maybe_with_fields(NoteId(id), false)?; - let notetype = col - .get_notetype(note.notetype_id)? - .ok_or(AnkiError::NotFound)?; - let cards = col.storage.all_cards_of_note(note.id)?; - let timing = col.timing_today()?; - - Ok(NoteRowContext { - note, - notetype, - cards, - tr: col.tr.clone(), - timing, - }) - } /// Returns the average ease of the non-new cards or a hint if there aren't any. fn note_ease_str(&self) -> String { @@ -516,46 +461,37 @@ impl NoteRowContext { ) } } -} -impl RowContext for NoteRowContext { - fn get_cell_text(&mut self, column: Column) -> Result { - Ok(match column { - Column::NoteCards => self.cards.len().to_string(), - Column::NoteCreation => self.note_creation_str(), - Column::NoteDue => self.note_due_str(), - Column::NoteEase => self.note_ease_str(), - Column::NoteField => self.note_field_str(), - Column::NoteInterval => self.note_interval_str(), - Column::NoteLapses => self.cards.iter().map(|c| c.lapses).sum::().to_string(), - Column::NoteMod => self.note.mtime.date_string(), - Column::NoteReps => self.cards.iter().map(|c| c.reps).sum::().to_string(), - Column::NoteTags => self.note.tags.join(" "), - Column::Notetype => self.notetype.name.to_owned(), - _ => "".to_string(), + fn get_row_font(&self) -> Result { + Ok(Font { + name: self.template()?.config.browser_font_name.to_owned(), + size: self.template()?.config.browser_font_size, }) } fn get_row_color(&self) -> Color { - if self.note.is_marked() { - Color::Marked + if self.notes_mode { + if self.note.is_marked() { + Color::Marked + } else { + Color::Default + } } else { - Color::Default + match self.cards[0].flags { + 1 => Color::FlagRed, + 2 => Color::FlagOrange, + 3 => Color::FlagGreen, + 4 => Color::FlagBlue, + _ => { + if self.note.is_marked() { + Color::Marked + } else if self.cards[0].queue == CardQueue::Suspended { + Color::Suspended + } else { + Color::Default + } + } + } } } - - fn get_row_font(&self) -> Result { - Ok(Font { - name: "".to_owned(), - size: 0, - }) - } - - fn note(&self) -> &Note { - &self.note - } - - fn notetype(&self) -> &Notetype { - &self.notetype - } } From 2350cd6e910554f74f72092b34dc70f152a9343b Mon Sep 17 00:00:00 2001 From: RumovZ Date: Thu, 8 Apr 2021 19:46:06 +0200 Subject: [PATCH 14/32] Add deck column for notes mode --- rslib/src/backend/search/browser_table.rs | 3 ++- rslib/src/browser_table.rs | 6 ++++++ rslib/src/search/mod.rs | 16 ++++++++++++---- rslib/src/search/note_decks_order.sql | 18 ++++++++++++++++++ 4 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 rslib/src/search/note_decks_order.sql diff --git a/rslib/src/backend/search/browser_table.rs b/rslib/src/backend/search/browser_table.rs index 41c3aaf15..aed7c04b0 100644 --- a/rslib/src/backend/search/browser_table.rs +++ b/rslib/src/backend/search/browser_table.rs @@ -23,7 +23,8 @@ const CARD_COLUMNS: [browser_table::Column; 15] = [ browser_table::Column::Notetype, ]; -const NOTE_COLUMNS: [browser_table::Column; 11] = [ +const NOTE_COLUMNS: [browser_table::Column; 12] = [ + browser_table::Column::CardDeck, browser_table::Column::NoteCards, browser_table::Column::NoteCreation, browser_table::Column::NoteDue, diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index 927dea6b7..9c69bdacf 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -395,6 +395,12 @@ impl RowContext { } fn deck_str(&mut self) -> String { + if self.notes_mode { + let decks = self.cards.iter().map(|c| c.deck_id).unique().count(); + if decks > 1 { + return format!("({})", decks); + } + } let deck_name = self.deck.human_name(); if let Some(original_deck) = &self.original_deck { format!("{} ({})", &deck_name, &original_deck.human_name()) diff --git a/rslib/src/search/mod.rs b/rslib/src/search/mod.rs index 8dc6eb273..a1c6d8f7e 100644 --- a/rslib/src/search/mod.rs +++ b/rslib/src/search/mod.rs @@ -145,7 +145,7 @@ impl Collection { SortMode::NoOrder => (), SortMode::FromConfig => unreachable!(), SortMode::Builtin { kind, reverse } => { - prepare_sort(self, kind)?; + prepare_sort(self, kind, items)?; sql.push_str(" order by "); write_order(sql, items, kind, reverse)?; } @@ -255,7 +255,8 @@ 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::CardDeck + | SortKind::NoteCards | SortKind::NoteDue | SortKind::NoteEase | SortKind::NoteInterval @@ -270,10 +271,17 @@ fn note_order_from_sortkind(kind: SortKind) -> Cow<'static, str> { } } -fn prepare_sort(col: &mut Collection, kind: SortKind) -> Result<()> { +fn prepare_sort(col: &mut Collection, kind: SortKind, items: SearchItems) -> Result<()> { use SortKind::*; + let notes_mode = items == SearchItems::Notes; let sql = match kind { - CardDeck => include_str!("deck_order.sql"), + CardDeck => { + if notes_mode { + include_str!("note_decks_order.sql") + } else { + include_str!("deck_order.sql") + } + } CardTemplate => include_str!("template_order.sql"), NoteCards => include_str!("note_cards_order.sql"), NoteDue => include_str!("note_due_order.sql"), diff --git a/rslib/src/search/note_decks_order.sql b/rslib/src/search/note_decks_order.sql new file mode 100644 index 000000000..51e909124 --- /dev/null +++ b/rslib/src/search/note_decks_order.sql @@ -0,0 +1,18 @@ +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 + JOIN ( + SELECT id, + row_number() OVER( + ORDER BY name + ) AS pos + FROM decks + ) decks ON cards.did = decks.id +GROUP BY nid +ORDER BY COUNT(DISTINCT did), + decks.pos; \ No newline at end of file From 4692a48ef3a72c27d5273de754697a9aa433d5db Mon Sep 17 00:00:00 2001 From: RumovZ Date: Thu, 8 Apr 2021 20:14:10 +0200 Subject: [PATCH 15/32] Add card mod column for notes mode --- rslib/src/backend/search/browser_table.rs | 3 ++- rslib/src/browser_table.rs | 11 ++++++++++- rslib/src/search/card_mod_order.sql | 10 ++++++++++ rslib/src/search/mod.rs | 2 ++ 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 rslib/src/search/card_mod_order.sql diff --git a/rslib/src/backend/search/browser_table.rs b/rslib/src/backend/search/browser_table.rs index aed7c04b0..49f34bad3 100644 --- a/rslib/src/backend/search/browser_table.rs +++ b/rslib/src/backend/search/browser_table.rs @@ -23,8 +23,9 @@ const CARD_COLUMNS: [browser_table::Column; 15] = [ browser_table::Column::Notetype, ]; -const NOTE_COLUMNS: [browser_table::Column; 12] = [ +const NOTE_COLUMNS: [browser_table::Column; 13] = [ browser_table::Column::CardDeck, + browser_table::Column::CardMod, browser_table::Column::NoteCards, browser_table::Column::NoteCreation, browser_table::Column::NoteDue, diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index 9c69bdacf..be278d699 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -296,7 +296,7 @@ impl RowContext { Column::CardEase => self.card_ease_str(), Column::CardInterval => self.card_interval_str(), Column::CardLapses => self.cards[0].lapses.to_string(), - Column::CardMod => self.cards[0].mtime.date_string(), + Column::CardMod => self.card_mod_str(), Column::CardReps => self.cards[0].reps.to_string(), Column::CardTemplate => self.template_str()?, Column::NoteCreation => self.note_creation_str(), @@ -394,6 +394,15 @@ impl RowContext { } } + fn card_mod_str(&self) -> String { + self.cards + .iter() + .map(|c| c.mtime) + .max() + .unwrap() + .date_string() + } + fn deck_str(&mut self) -> String { if self.notes_mode { let decks = self.cards.iter().map(|c| c.deck_id).unique().count(); diff --git a/rslib/src/search/card_mod_order.sql b/rslib/src/search/card_mod_order.sql new file mode 100644 index 000000000..efbaf70e3 --- /dev/null +++ b/rslib/src/search/card_mod_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 MAX(mod); \ No newline at end of file diff --git a/rslib/src/search/mod.rs b/rslib/src/search/mod.rs index a1c6d8f7e..53634f84e 100644 --- a/rslib/src/search/mod.rs +++ b/rslib/src/search/mod.rs @@ -256,6 +256,7 @@ fn card_order_from_sortkind(kind: SortKind) -> Cow<'static, str> { fn note_order_from_sortkind(kind: SortKind) -> Cow<'static, str> { match kind { SortKind::CardDeck + | SortKind::CardMod | SortKind::NoteCards | SortKind::NoteDue | SortKind::NoteEase @@ -282,6 +283,7 @@ fn prepare_sort(col: &mut Collection, kind: SortKind, items: SearchItems) -> Res include_str!("deck_order.sql") } } + CardMod if notes_mode => include_str!("card_mod_order.sql"), CardTemplate => include_str!("template_order.sql"), NoteCards => include_str!("note_cards_order.sql"), NoteDue => include_str!("note_due_order.sql"), From 7a0cb08ac286fa66df79729a150c6ac8483f7656 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Thu, 8 Apr 2021 20:45:47 +0200 Subject: [PATCH 16/32] Merge browser row str methods --- rslib/src/browser_table.rs | 147 +++++++++++++++++++------------------ 1 file changed, 74 insertions(+), 73 deletions(-) diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index be278d699..829422c72 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -292,24 +292,22 @@ impl RowContext { Column::Question => self.question_str(), Column::Answer => self.answer_str(), Column::CardDeck => self.deck_str(), - Column::CardDue => self.card_due_str(), - Column::CardEase => self.card_ease_str(), - Column::CardInterval => self.card_interval_str(), - Column::CardLapses => self.cards[0].lapses.to_string(), + Column::CardDue | Column::NoteDue => self.due_str(), + Column::CardEase | Column::NoteEase => self.ease_str(), + Column::CardInterval | Column::NoteInterval => self.interval_str(), + Column::CardLapses | Column::NoteLapses => { + self.cards.iter().map(|c| c.lapses).sum::().to_string() + } Column::CardMod => self.card_mod_str(), - Column::CardReps => self.cards[0].reps.to_string(), - Column::CardTemplate => self.template_str()?, + Column::CardReps | Column::NoteReps => { + self.cards.iter().map(|c| c.reps).sum::().to_string() + } + Column::CardTemplate | Column::NoteCards => self.cards_str()?, Column::NoteCreation => self.note_creation_str(), Column::NoteField => self.note_field_str(), Column::NoteMod => self.note.mtime.date_string(), Column::NoteTags => self.note.tags.join(" "), Column::Notetype => self.notetype.name.to_owned(), - Column::NoteCards => self.cards.len().to_string(), - Column::NoteDue => self.note_due_str(), - Column::NoteEase => self.note_ease_str(), - Column::NoteInterval => self.note_interval_str(), - Column::NoteLapses => self.cards.iter().map(|c| c.lapses).sum::().to_string(), - Column::NoteReps => self.cards.iter().map(|c| c.reps).sum::().to_string(), Column::Custom => "".to_string(), }) } @@ -362,6 +360,14 @@ impl RowContext { .to_string() } + fn due_str(&mut self) -> String { + if self.notes_mode { + self.note_due_str() + } else { + self.card_due_str() + } + } + fn card_due_str(&mut self) -> String { let due = if self.cards[0].is_filtered_deck() { self.tr.browsing_filtered() @@ -379,18 +385,56 @@ impl RowContext { } } - fn card_ease_str(&self) -> String { - match self.cards[0].ctype { - CardType::New => self.tr.browsing_new().into(), - _ => format!("{}%", self.cards[0].ease_factor / 10), + /// Returns the due date of the next due card that is not in a filtered deck, new, suspended or + /// buried or the empty string if there is no such card. + fn note_due_str(&self) -> String { + self.cards + .iter() + .filter(|c| !(c.is_filtered_deck() || c.is_new_type_or_queue() || c.is_undue_queue())) + .filter_map(|c| c.due_time(&self.timing)) + .min() + .map(|time| time.date_string()) + .unwrap_or_else(|| "".into()) + } + + /// Returns the average ease of the non-new cards or a hint if there aren't any. + fn ease_str(&self) -> String { + let eases: Vec = self + .cards + .iter() + .filter(|c| c.ctype != CardType::New) + .map(|c| c.ease_factor) + .collect(); + if eases.is_empty() { + self.tr.browsing_new().into() + } else { + format!("{}%", eases.iter().sum::() / eases.len() as u16 / 10) } } - fn card_interval_str(&self) -> String { - match self.cards[0].ctype { - CardType::New => self.tr.browsing_new().into(), - CardType::Learn => self.tr.browsing_learning().into(), - _ => time_span((self.cards[0].interval * 86400) as f32, &self.tr, false), + /// Returns the average interval of the review and relearn cards if there are any. + fn interval_str(&self) -> String { + if !self.notes_mode { + match self.cards[0].ctype { + CardType::New => return self.tr.browsing_new().into(), + CardType::Learn => return self.tr.browsing_learning().into(), + CardType::Review | CardType::Relearn => (), + } + } + let intervals: Vec = self + .cards + .iter() + .filter(|c| matches!(c.ctype, CardType::Review | CardType::Relearn)) + .map(|c| c.interval) + .collect(); + if intervals.is_empty() { + "".into() + } else { + time_span( + (intervals.iter().sum::() * 86400 / (intervals.len() as u32)) as f32, + &self.tr, + false, + ) } } @@ -418,11 +462,15 @@ impl RowContext { } } - fn template_str(&self) -> Result { - let name = &self.template()?.name; - Ok(match self.notetype.config.kind() { - NotetypeKind::Normal => name.to_owned(), - NotetypeKind::Cloze => format!("{} {}", name, self.cards[0].template_idx + 1), + fn cards_str(&self) -> Result { + Ok(if self.notes_mode { + self.cards.len().to_string() + } else { + let name = &self.template()?.name; + match self.notetype.config.kind() { + NotetypeKind::Normal => name.to_owned(), + NotetypeKind::Cloze => format!("{} {}", name, self.cards[0].template_idx + 1), + } }) } @@ -430,53 +478,6 @@ impl RowContext { html_to_text_line(&self.render_context.as_ref().unwrap().question).to_string() } - /// Returns the average ease of the non-new cards or a hint if there aren't any. - fn note_ease_str(&self) -> String { - let eases: Vec = self - .cards - .iter() - .filter(|c| c.ctype != CardType::New) - .map(|c| c.ease_factor) - .collect(); - if eases.is_empty() { - self.tr.browsing_new().into() - } else { - format!("{}%", eases.iter().sum::() / eases.len() as u16 / 10) - } - } - - /// Returns the due date of the next due card that is not in a filtered deck, new, suspended or - /// buried or the empty string if there is no such card. - fn note_due_str(&self) -> String { - self.cards - .iter() - .filter(|c| !(c.is_filtered_deck() || c.is_new_type_or_queue() || c.is_undue_queue())) - .filter_map(|c| c.due_time(&self.timing)) - .min() - .map(|time| time.date_string()) - .unwrap_or_else(|| "".into()) - } - - /// Returns the average interval of the review and relearn cards or the empty string if there - /// aren't any. - fn note_interval_str(&self) -> String { - let intervals: Vec = self - .cards - .iter() - .filter(|c| matches!(c.ctype, CardType::Review | CardType::Relearn)) - .map(|c| c.interval) - .collect(); - if intervals.is_empty() { - "".into() - } else { - time_span( - (intervals.iter().sum::() * 86400 / (intervals.len() as u32)) as f32, - &self.tr, - false, - ) - } - } - fn get_row_font(&self) -> Result { Ok(Font { name: self.template()?.config.browser_font_name.to_owned(), From dd56dc6650c77be3caa86bf24bba9ad21281785c Mon Sep 17 00:00:00 2001 From: RumovZ Date: Thu, 8 Apr 2021 21:59:58 +0200 Subject: [PATCH 17/32] Rename columns for future mode-independent use --- pylib/anki/collection.py | 2 +- pylib/tests/test_find.py | 8 +--- rslib/backend.proto | 18 ++++---- rslib/src/backend/search/browser_table.rs | 54 +++++++++++------------ rslib/src/backend/search/mod.rs | 18 ++++---- rslib/src/browser_table.rs | 43 +++++++++--------- rslib/src/config/mod.rs | 26 ++++++----- rslib/src/search/mod.rs | 46 +++++++++---------- 8 files changed, 109 insertions(+), 106 deletions(-) diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 06a36f114..5953219da 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -522,7 +522,7 @@ class Collection: 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) + col.find_cards("", order=BuiltinSort.Kind.DUE) The reverse argument only applies when a BuiltinSort.Kind is provided; otherwise the collection config defines whether reverse is set or not. diff --git a/pylib/tests/test_find.py b/pylib/tests/test_find.py index 91d7f1bb0..3e5a68e67 100644 --- a/pylib/tests/test_find.py +++ b/pylib/tests/test_find.py @@ -124,12 +124,8 @@ def test_findCards(): col.set_config_bool(Config.Bool.BROWSER_SORT_BACKWARDS, True) col.flush() assert col.findCards("", order=True)[0] in latestCardIds - assert ( - col.find_cards("", order=BuiltinSort.CARD_DUE, reverse=False)[0] == firstCardId - ) - assert ( - col.find_cards("", order=BuiltinSort.CARD_DUE, reverse=True)[0] != firstCardId - ) + assert col.find_cards("", order=BuiltinSort.DUE, reverse=False)[0] == firstCardId + assert col.find_cards("", order=BuiltinSort.DUE, reverse=True)[0] != firstCardId # model assert len(col.findCards("note:basic")) == 3 assert len(col.findCards("-note:basic")) == 2 diff --git a/rslib/backend.proto b/rslib/backend.proto index c72643937..abe79cfa4 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -802,21 +802,21 @@ message SortOrder { NOTE_CREATION = 1; NOTE_DUE = 2; NOTE_EASE = 3; - NOTE_FIELD = 4; + SORT_FIELD = 4; NOTE_INTERVAL = 5; NOTE_LAPSES = 6; NOTE_MOD = 7; NOTE_REPS = 8; - NOTE_TAGS = 9; + TAGS = 9; NOTETYPE = 10; CARD_MOD = 11; - CARD_REPS = 12; - CARD_DUE = 13; - CARD_EASE = 14; - CARD_LAPSES = 15; - CARD_INTERVAL = 16; - CARD_DECK = 17; - CARD_TEMPLATE = 18; + REPS = 12; + DUE = 13; + EASE = 14; + LAPSES = 15; + INTERVAL = 16; + DECK = 17; + CARDS = 18; } Kind kind = 1; bool reverse = 2; diff --git a/rslib/src/backend/search/browser_table.rs b/rslib/src/backend/search/browser_table.rs index 49f34bad3..8451a8695 100644 --- a/rslib/src/backend/search/browser_table.rs +++ b/rslib/src/backend/search/browser_table.rs @@ -8,34 +8,34 @@ use crate::{backend_proto as pb, browser_table, collection::Collection, i18n::I1 const CARD_COLUMNS: [browser_table::Column; 15] = [ browser_table::Column::Question, browser_table::Column::Answer, - browser_table::Column::CardDeck, - browser_table::Column::CardDue, - browser_table::Column::CardEase, - browser_table::Column::CardLapses, - browser_table::Column::CardInterval, + browser_table::Column::Deck, + browser_table::Column::Due, + browser_table::Column::Ease, + browser_table::Column::Lapses, + browser_table::Column::Interval, browser_table::Column::CardMod, - browser_table::Column::CardReps, - browser_table::Column::CardTemplate, + browser_table::Column::Reps, + browser_table::Column::Cards, browser_table::Column::NoteCreation, - browser_table::Column::NoteField, + browser_table::Column::SortField, browser_table::Column::NoteMod, - browser_table::Column::NoteTags, + browser_table::Column::Tags, browser_table::Column::Notetype, ]; const NOTE_COLUMNS: [browser_table::Column; 13] = [ - browser_table::Column::CardDeck, + browser_table::Column::Deck, browser_table::Column::CardMod, browser_table::Column::NoteCards, browser_table::Column::NoteCreation, browser_table::Column::NoteDue, browser_table::Column::NoteEase, - browser_table::Column::NoteField, + browser_table::Column::SortField, browser_table::Column::NoteInterval, browser_table::Column::NoteLapses, browser_table::Column::NoteMod, browser_table::Column::NoteReps, - browser_table::Column::NoteTags, + browser_table::Column::Tags, browser_table::Column::Notetype, ]; @@ -70,24 +70,24 @@ impl browser_table::Column { fn sorting(self) -> pb::browser_columns::Sorting { match self { Self::Question | Self::Answer | Self::Custom => pb::browser_columns::Sorting::None, - Self::NoteField => pb::browser_columns::Sorting::Reversed, + Self::SortField => pb::browser_columns::Sorting::Reversed, _ => pb::browser_columns::Sorting::Normal, } } fn uses_cell_font(self) -> bool { - matches!(self, Self::Question | Self::Answer | Self::NoteField) + matches!(self, Self::Question | Self::Answer | Self::SortField) } fn alignment(self) -> pb::browser_columns::Alignment { match self { Self::Question | Self::Answer - | Self::CardTemplate - | Self::CardDeck - | Self::NoteField + | Self::Cards + | Self::Deck + | Self::SortField | Self::Notetype - | Self::NoteTags => pb::browser_columns::Alignment::Start, + | Self::Tags => pb::browser_columns::Alignment::Start, _ => pb::browser_columns::Alignment::Center, } } @@ -97,24 +97,24 @@ impl browser_table::Column { Self::Custom => i18n.browsing_addon(), Self::Question => i18n.browsing_question(), Self::Answer => i18n.browsing_answer(), - Self::CardDeck => i18n.decks_deck(), - Self::CardDue => i18n.statistics_due_date(), - Self::CardEase => i18n.browsing_ease(), - Self::CardInterval => i18n.browsing_interval(), - Self::CardLapses => i18n.scheduling_lapses(), + Self::Deck => i18n.decks_deck(), + Self::Due => i18n.statistics_due_date(), + Self::Ease => i18n.browsing_ease(), + Self::Interval => i18n.browsing_interval(), + Self::Lapses => i18n.scheduling_lapses(), Self::CardMod => i18n.search_card_modified(), - Self::CardReps => i18n.scheduling_reviews(), - Self::CardTemplate => i18n.browsing_card(), + Self::Reps => i18n.scheduling_reviews(), + Self::Cards => i18n.browsing_card(), Self::NoteCards => i18n.editing_cards(), Self::NoteCreation => i18n.browsing_created(), Self::NoteDue => i18n.statistics_due_date(), Self::NoteEase => i18n.browsing_average_ease(), - Self::NoteField => i18n.browsing_sort_field(), + Self::SortField => i18n.browsing_sort_field(), Self::NoteInterval => i18n.browsing_average_interval(), Self::NoteMod => i18n.search_note_modified(), Self::NoteLapses => i18n.scheduling_lapses(), Self::NoteReps => i18n.scheduling_reviews(), - Self::NoteTags => i18n.editing_tags(), + Self::Tags => i18n.editing_tags(), Self::Notetype => i18n.browsing_note(), } .into() diff --git a/rslib/src/backend/search/mod.rs b/rslib/src/backend/search/mod.rs index 5a09b3427..f57a10d4e 100644 --- a/rslib/src/backend/search/mod.rs +++ b/rslib/src/backend/search/mod.rs @@ -122,18 +122,18 @@ impl From for SortKind { SortKindProto::NoteInterval => SortKind::NoteInterval, SortKindProto::NoteLapses => SortKind::NoteLapses, SortKindProto::NoteMod => SortKind::NoteMod, - SortKindProto::NoteField => SortKind::NoteField, + SortKindProto::SortField => SortKind::SortField, SortKindProto::NoteReps => SortKind::NoteReps, - SortKindProto::NoteTags => SortKind::NoteTags, + SortKindProto::Tags => SortKind::Tags, SortKindProto::Notetype => SortKind::Notetype, SortKindProto::CardMod => SortKind::CardMod, - SortKindProto::CardReps => SortKind::CardReps, - SortKindProto::CardDue => SortKind::CardDue, - SortKindProto::CardEase => SortKind::CardEase, - SortKindProto::CardLapses => SortKind::CardLapses, - SortKindProto::CardInterval => SortKind::CardInterval, - SortKindProto::CardDeck => SortKind::CardDeck, - SortKindProto::CardTemplate => SortKind::CardTemplate, + SortKindProto::Reps => SortKind::Reps, + SortKindProto::Due => SortKind::Due, + SortKindProto::Ease => SortKind::Ease, + SortKindProto::Lapses => SortKind::Lapses, + SortKindProto::Interval => SortKind::Interval, + SortKindProto::Deck => SortKind::Deck, + SortKindProto::Cards => SortKind::Cards, } } } diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index 829422c72..97aa53658 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -30,30 +30,33 @@ pub enum Column { Custom, Question, Answer, - #[strum(serialize = "deck")] - CardDeck, - CardDue, - CardEase, - CardLapses, + Deck, + #[strum(serialize = "cardDue")] + Due, + #[strum(serialize = "cardEase")] + Ease, + #[strum(serialize = "cardLapses")] + Lapses, #[strum(serialize = "cardIvl")] - CardInterval, + Interval, CardMod, - CardReps, + #[strum(serialize = "cardReps")] + Reps, #[strum(serialize = "template")] - CardTemplate, + Cards, NoteCards, #[strum(serialize = "noteCrt")] NoteCreation, NoteDue, NoteEase, #[strum(serialize = "noteFld")] - NoteField, + SortField, #[strum(serialize = "noteIvl")] NoteInterval, NoteLapses, NoteMod, NoteReps, - NoteTags, + Tags, #[strum(serialize = "note")] Notetype, } @@ -291,22 +294,22 @@ impl RowContext { Ok(match column { Column::Question => self.question_str(), Column::Answer => self.answer_str(), - Column::CardDeck => self.deck_str(), - Column::CardDue | Column::NoteDue => self.due_str(), - Column::CardEase | Column::NoteEase => self.ease_str(), - Column::CardInterval | Column::NoteInterval => self.interval_str(), - Column::CardLapses | Column::NoteLapses => { + Column::Deck => self.deck_str(), + Column::Due | Column::NoteDue => self.due_str(), + Column::Ease | Column::NoteEase => self.ease_str(), + Column::Interval | Column::NoteInterval => self.interval_str(), + Column::Lapses | Column::NoteLapses => { self.cards.iter().map(|c| c.lapses).sum::().to_string() } Column::CardMod => self.card_mod_str(), - Column::CardReps | Column::NoteReps => { + Column::Reps | Column::NoteReps => { self.cards.iter().map(|c| c.reps).sum::().to_string() } - Column::CardTemplate | Column::NoteCards => self.cards_str()?, + Column::Cards | Column::NoteCards => self.cards_str()?, Column::NoteCreation => self.note_creation_str(), - Column::NoteField => self.note_field_str(), + Column::SortField => self.note_field_str(), Column::NoteMod => self.note.mtime.date_string(), - Column::NoteTags => self.note.tags.join(" "), + Column::Tags => self.note.tags.join(" "), Column::Notetype => self.notetype.name.to_owned(), Column::Custom => "".to_string(), }) @@ -323,7 +326,7 @@ impl RowContext { fn get_is_rtl(&self, column: Column) -> bool { match column { - Column::NoteField => { + Column::SortField => { let index = self.notetype.config.sort_field_idx as usize; self.notetype.fields[index].config.rtl } diff --git a/rslib/src/config/mod.rs b/rslib/src/config/mod.rs index c5cfcc59f..beabae235 100644 --- a/rslib/src/config/mod.rs +++ b/rslib/src/config/mod.rs @@ -282,22 +282,26 @@ pub enum SortKind { NoteLapses, NoteMod, #[serde(rename = "noteFld")] - NoteField, + SortField, NoteReps, #[serde(rename = "note")] Notetype, - NoteTags, + #[serde(rename = "noteTags")] + Tags, CardMod, - CardReps, - CardDue, - CardEase, - CardLapses, + #[serde(rename = "cardReps")] + Reps, + #[serde(rename = "cardDue")] + Due, + #[serde(rename = "cardEase")] + Ease, + #[serde(rename = "cardLapses")] + Lapses, #[serde(rename = "cardIvl")] - CardInterval, - #[serde(rename = "deck")] - CardDeck, + Interval, + Deck, #[serde(rename = "template")] - CardTemplate, + Cards, } impl Default for SortKind { @@ -338,7 +342,7 @@ mod test { fn defaults() { let col = open_test_collection(); assert_eq!(col.get_current_deck_id(), DeckId(1)); - assert_eq!(col.get_browser_sort_kind(), SortKind::NoteField); + assert_eq!(col.get_browser_sort_kind(), SortKind::SortField); } #[test] diff --git a/rslib/src/search/mod.rs b/rslib/src/search/mod.rs index 53634f84e..4a6e97358 100644 --- a/rslib/src/search/mod.rs +++ b/rslib/src/search/mod.rs @@ -92,21 +92,21 @@ impl SortKind { | SortKind::NoteCreation | SortKind::NoteDue | SortKind::NoteEase - | SortKind::NoteField + | SortKind::SortField | SortKind::NoteInterval | SortKind::NoteLapses | SortKind::NoteMod | SortKind::NoteReps - | SortKind::NoteTags + | SortKind::Tags | SortKind::Notetype => RequiredTable::Notes, - SortKind::CardTemplate => RequiredTable::CardsAndNotes, + SortKind::Cards => RequiredTable::CardsAndNotes, SortKind::CardMod - | SortKind::CardReps - | SortKind::CardDue - | SortKind::CardEase - | SortKind::CardLapses - | SortKind::CardInterval - | SortKind::CardDeck => RequiredTable::Cards, + | SortKind::Reps + | SortKind::Due + | SortKind::Ease + | SortKind::Lapses + | SortKind::Interval + | SortKind::Deck => RequiredTable::Cards, } } } @@ -233,17 +233,17 @@ fn card_order_from_sortkind(kind: SortKind) -> Cow<'static, str> { match kind { SortKind::NoteCreation => "n.id asc, c.ord asc".into(), SortKind::NoteMod => "n.mod asc, c.ord asc".into(), - SortKind::NoteField => "n.sfld collate nocase asc, c.ord asc".into(), + SortKind::SortField => "n.sfld collate nocase asc, c.ord asc".into(), SortKind::CardMod => "c.mod asc".into(), - SortKind::CardReps => "c.reps asc".into(), - SortKind::CardDue => "c.type asc, c.due asc".into(), - SortKind::CardEase => format!("c.type = {} asc, c.factor asc", CardType::New as i8).into(), - SortKind::CardLapses => "c.lapses asc".into(), - SortKind::CardInterval => "c.ivl asc".into(), - SortKind::NoteTags => "n.tags asc".into(), - SortKind::CardDeck => "(select pos from sort_order where did = c.did) asc".into(), + SortKind::Reps => "c.reps asc".into(), + SortKind::Due => "c.type asc, c.due asc".into(), + SortKind::Ease => format!("c.type = {} asc, c.factor asc", CardType::New as i8).into(), + SortKind::Lapses => "c.lapses asc".into(), + SortKind::Interval => "c.ivl asc".into(), + SortKind::Tags => "n.tags asc".into(), + SortKind::Deck => "(select pos from sort_order where did = c.did) asc".into(), SortKind::Notetype => "(select pos from sort_order where ntid = n.mid) asc".into(), - SortKind::CardTemplate => concat!( + SortKind::Cards => concat!( "coalesce((select pos from sort_order where ntid = n.mid and ord = c.ord),", // need to fall back on ord 0 for cloze cards "(select pos from sort_order where ntid = n.mid and ord = 0)) asc" @@ -255,7 +255,7 @@ fn card_order_from_sortkind(kind: SortKind) -> Cow<'static, str> { fn note_order_from_sortkind(kind: SortKind) -> Cow<'static, str> { match kind { - SortKind::CardDeck + SortKind::Deck | SortKind::CardMod | SortKind::NoteCards | SortKind::NoteDue @@ -264,9 +264,9 @@ fn note_order_from_sortkind(kind: SortKind) -> Cow<'static, str> { | SortKind::NoteLapses | SortKind::NoteReps => "(select pos from sort_order where nid = n.id) asc".into(), SortKind::NoteCreation => "n.id asc".into(), - SortKind::NoteField => "n.sfld collate nocase asc".into(), + SortKind::SortField => "n.sfld collate nocase asc".into(), SortKind::NoteMod => "n.mod asc".into(), - SortKind::NoteTags => "n.tags asc".into(), + SortKind::Tags => "n.tags asc".into(), SortKind::Notetype => "(select pos from sort_order where ntid = n.mid) asc".into(), _ => "".into(), } @@ -276,7 +276,7 @@ fn prepare_sort(col: &mut Collection, kind: SortKind, items: SearchItems) -> Res use SortKind::*; let notes_mode = items == SearchItems::Notes; let sql = match kind { - CardDeck => { + Deck => { if notes_mode { include_str!("note_decks_order.sql") } else { @@ -284,7 +284,7 @@ fn prepare_sort(col: &mut Collection, kind: SortKind, items: SearchItems) -> Res } } CardMod if notes_mode => include_str!("card_mod_order.sql"), - CardTemplate => include_str!("template_order.sql"), + Cards => include_str!("template_order.sql"), NoteCards => include_str!("note_cards_order.sql"), NoteDue => include_str!("note_due_order.sql"), NoteEase => include_str!("note_ease_order.sql"), From c74078ea9e890cfad45b60376e8a5ab34c7575f3 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Thu, 8 Apr 2021 23:48:24 +0200 Subject: [PATCH 18/32] Unify state columns * Remove duplicate backend columns * Remove duplicate column routines * Move columns on frontend from state to model * Generate available columns from Colum enum * Add second column label for notes mode --- pylib/anki/collection.py | 7 +- qt/aqt/table.py | 51 ++++++-------- rslib/backend.proto | 6 +- rslib/src/backend/search/browser_table.rs | 85 +++++++---------------- rslib/src/backend/search/mod.rs | 8 +-- rslib/src/browser_table.rs | 47 +++++-------- 6 files changed, 74 insertions(+), 130 deletions(-) diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 5953219da..2f3b32626 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -697,11 +697,8 @@ class Collection: # Browser Table ########################################################################## - def all_browser_card_columns(self) -> Sequence[BrowserColumns.Column]: - return self._backend.all_browser_card_columns() - - def all_browser_note_columns(self) -> Sequence[BrowserColumns.Column]: - return self._backend.all_browser_note_columns() + def all_browser_columns(self) -> Sequence[BrowserColumns.Column]: + return self._backend.all_browser_columns() def browser_row_for_id( self, id_: int diff --git a/qt/aqt/table.py b/qt/aqt/table.py index 76b3d2f5a..f8a83c18c 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -347,8 +347,8 @@ class Table: def _on_header_context(self, pos: QPoint) -> None: gpos = self._view.mapToGlobal(pos) m = QMenu() - for key, column in self._state.columns.items(): - a = m.addAction(column.label) + for key, column in self._model.columns.items(): + a = m.addAction(self._state.column_label(column)) a.setCheckable(True) a.setChecked(self._model.active_column_index(key) is not None) qconnect( @@ -522,7 +522,6 @@ class Table: class ItemState(ABC): - _columns: Dict[str, Column] _active_columns: List[str] _sort_column: str _sort_backwards: bool @@ -544,26 +543,16 @@ class ItemState(ABC): def card_ids_from_note_ids(self, items: Sequence[ItemId]) -> Sequence[CardId]: return self.col.db.list(f"select id from cards where nid in {ids2str(items)}") - def column_at(self, index: int) -> Column: - """Returns the column object corresponding to the active column at index or the default - column object if no data is associated with the active column. - """ + def column_key_at(self, index: int) -> str: + return self._active_columns[index] - key = self._active_columns[index] - try: - return self._columns[key] - except KeyError: - self._columns[key] = addon_column_fillin(key) - return self._columns[key] + def column_label(self, column: Column) -> str: + return column.notes_label if self.is_notes_mode() else column.label # Columns and sorting # abstractproperty is deprecated but used due to mypy limitations # (https://github.com/python/mypy/issues/1362) - @abstractproperty - def columns(self) -> Dict[str, Column]: - """Return all for the state available columns.""" - @abstractproperty def active_columns(self) -> List[str]: """Return the saved or default columns for the state.""" @@ -630,17 +619,12 @@ class ItemState(ABC): class CardState(ItemState): def __init__(self, col: Collection) -> None: super().__init__(col) - self._columns = dict(((c.key, c) for c in self.col.all_browser_card_columns())) self._active_columns = self.col.load_browser_card_columns() self._sort_column = self.col.get_config("sortType") self._sort_backwards = self.col.get_config_bool( Config.Bool.BROWSER_SORT_BACKWARDS ) - @property - def columns(self) -> Dict[str, Column]: - return self._columns - @property def active_columns(self) -> List[str]: return self._active_columns @@ -698,17 +682,12 @@ class CardState(ItemState): class NoteState(ItemState): def __init__(self, col: Collection) -> None: super().__init__(col) - self._columns = dict(((c.key, c) for c in self.col.all_browser_note_columns())) self._active_columns = self.col.load_browser_note_columns() self._sort_column = self.col.get_config("noteSortType") self._sort_backwards = self.col.get_config_bool( Config.Bool.BROWSER_NOTE_SORT_BACKWARDS ) - @property - def columns(self) -> Dict[str, Column]: - return self._columns - @property def active_columns(self) -> List[str]: return self._active_columns @@ -832,6 +811,9 @@ class DataModel(QAbstractTableModel): def __init__(self, col: Collection, state: ItemState) -> None: QAbstractTableModel.__init__(self) self.col: Collection = col + self.columns: Dict[str, Column] = dict( + ((c.key, c) for c in self.col.all_browser_columns()) + ) self._state: ItemState = state self._items: Sequence[ItemId] = [] self._rows: Dict[int, CellRow] = {} @@ -1002,10 +984,18 @@ class DataModel(QAbstractTableModel): # Columns def column_at(self, index: QModelIndex) -> Column: - return self._state.column_at(index.column()) + return self.column_at_section(index.column()) def column_at_section(self, section: int) -> Column: - return self._state.column_at(section) + """Returns the column object corresponding to the active column at index or the default + column object if no data is associated with the active column. + """ + key = self._state.column_key_at(section) + try: + return self.columns[key] + except KeyError: + self.columns[key] = addon_column_fillin(key) + return self.columns[key] def active_column_index(self, column: str) -> Optional[int]: return ( @@ -1056,7 +1046,7 @@ class DataModel(QAbstractTableModel): self, section: int, orientation: Qt.Orientation, role: int = 0 ) -> Optional[str]: if orientation == Qt.Horizontal and role == Qt.DisplayRole: - return self.column_at_section(section).label + return self._state.column_label(self.column_at_section(section)) return None def flags(self, index: QModelIndex) -> Qt.ItemFlags: @@ -1094,6 +1084,7 @@ def addon_column_fillin(key: str) -> Column: return Column( key=key, label=tr.browsing_addon(), + notes_label=tr.browsing_addon(), sorting=Columns.SORTING_NONE, uses_cell_font=False, alignment=Columns.ALIGNMENT_CENTER, diff --git a/rslib/backend.proto b/rslib/backend.proto index abe79cfa4..b4479baae 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -248,8 +248,7 @@ service SearchService { rpc JoinSearchNodes(JoinSearchNodesIn) returns (String); rpc ReplaceSearchNode(ReplaceSearchNodeIn) returns (String); rpc FindAndReplace(FindAndReplaceIn) returns (OpChangesWithCount); - rpc AllBrowserCardColumns(Empty) returns (BrowserColumns); - rpc AllBrowserNoteColumns(Empty) returns (BrowserColumns); + rpc AllBrowserColumns(Empty) returns (BrowserColumns); rpc BrowserRowForId(Int64) returns (BrowserRow); rpc SetDesktopBrowserCardColumns(StringList) returns (Empty); rpc SetDesktopBrowserNoteColumns(StringList) returns (Empty); @@ -1067,7 +1066,8 @@ message BrowserColumns { message Column { string key = 1; string label = 2; - Sorting sorting = 3; + string notes_label = 3; + Sorting sorting = 4; bool uses_cell_font = 5; Alignment alignment = 6; } diff --git a/rslib/src/backend/search/browser_table.rs b/rslib/src/backend/search/browser_table.rs index 8451a8695..f544c761c 100644 --- a/rslib/src/backend/search/browser_table.rs +++ b/rslib/src/backend/search/browser_table.rs @@ -3,54 +3,16 @@ use std::str::FromStr; +use strum::IntoEnumIterator; + use crate::{backend_proto as pb, browser_table, collection::Collection, i18n::I18n}; -const CARD_COLUMNS: [browser_table::Column; 15] = [ - browser_table::Column::Question, - browser_table::Column::Answer, - browser_table::Column::Deck, - browser_table::Column::Due, - browser_table::Column::Ease, - browser_table::Column::Lapses, - browser_table::Column::Interval, - browser_table::Column::CardMod, - browser_table::Column::Reps, - browser_table::Column::Cards, - browser_table::Column::NoteCreation, - browser_table::Column::SortField, - browser_table::Column::NoteMod, - browser_table::Column::Tags, - browser_table::Column::Notetype, -]; - -const NOTE_COLUMNS: [browser_table::Column; 13] = [ - browser_table::Column::Deck, - browser_table::Column::CardMod, - browser_table::Column::NoteCards, - browser_table::Column::NoteCreation, - browser_table::Column::NoteDue, - browser_table::Column::NoteEase, - browser_table::Column::SortField, - browser_table::Column::NoteInterval, - browser_table::Column::NoteLapses, - browser_table::Column::NoteMod, - browser_table::Column::NoteReps, - browser_table::Column::Tags, - browser_table::Column::Notetype, -]; - impl Collection { - pub(crate) fn all_browser_card_columns(&self) -> pb::BrowserColumns { - self.to_pb_columns(&CARD_COLUMNS) - } - - pub(crate) fn all_browser_note_columns(&self) -> pb::BrowserColumns { - self.to_pb_columns(&NOTE_COLUMNS) - } - - fn to_pb_columns(&self, columns: &[browser_table::Column]) -> pb::BrowserColumns { - let mut columns: Vec = - columns.iter().map(|c| c.to_pb_column(&self.tr)).collect(); + pub(crate) fn all_browser_columns(&self) -> pb::BrowserColumns { + let mut columns: Vec = browser_table::Column::iter() + .filter(|&c| c != browser_table::Column::Custom) + .map(|c| c.to_pb_column(&self.tr)) + .collect(); columns.sort_by(|c1, c2| c1.label.cmp(&c2.label)); pb::BrowserColumns { columns } } @@ -61,6 +23,7 @@ impl browser_table::Column { pb::browser_columns::Column { key: self.to_string(), label: self.localized_label(i18n), + notes_label: self.localized_notes_label(i18n), sorting: self.sorting() as i32, uses_cell_font: self.uses_cell_font(), alignment: self.alignment() as i32, @@ -94,28 +57,34 @@ impl browser_table::Column { fn localized_label(self, i18n: &I18n) -> String { match self { - Self::Custom => i18n.browsing_addon(), - Self::Question => i18n.browsing_question(), Self::Answer => i18n.browsing_answer(), + Self::CardMod => i18n.search_card_modified(), + Self::Cards => i18n.browsing_card(), Self::Deck => i18n.decks_deck(), Self::Due => i18n.statistics_due_date(), + Self::Custom => i18n.browsing_addon(), Self::Ease => i18n.browsing_ease(), Self::Interval => i18n.browsing_interval(), Self::Lapses => i18n.scheduling_lapses(), - Self::CardMod => i18n.search_card_modified(), - Self::Reps => i18n.scheduling_reviews(), - Self::Cards => i18n.browsing_card(), - Self::NoteCards => i18n.editing_cards(), Self::NoteCreation => i18n.browsing_created(), - Self::NoteDue => i18n.statistics_due_date(), - Self::NoteEase => i18n.browsing_average_ease(), - Self::SortField => i18n.browsing_sort_field(), - Self::NoteInterval => i18n.browsing_average_interval(), Self::NoteMod => i18n.search_note_modified(), - Self::NoteLapses => i18n.scheduling_lapses(), - Self::NoteReps => i18n.scheduling_reviews(), - Self::Tags => i18n.editing_tags(), Self::Notetype => i18n.browsing_note(), + Self::Question => i18n.browsing_question(), + Self::Reps => i18n.scheduling_reviews(), + Self::SortField => i18n.browsing_sort_field(), + Self::Tags => i18n.editing_tags(), + } + .into() + } + + fn localized_notes_label(self, i18n: &I18n) -> String { + match self { + Self::CardMod => i18n.search_card_modified(), + Self::Cards => i18n.editing_cards(), + Self::Ease => i18n.browsing_average_ease(), + Self::Interval => i18n.browsing_average_interval(), + Self::Reps => i18n.scheduling_reviews(), + _ => return self.localized_label(i18n), } .into() } diff --git a/rslib/src/backend/search/mod.rs b/rslib/src/backend/search/mod.rs index f57a10d4e..e5285d6f2 100644 --- a/rslib/src/backend/search/mod.rs +++ b/rslib/src/backend/search/mod.rs @@ -89,12 +89,8 @@ impl SearchService for Backend { }) } - fn all_browser_card_columns(&self, _input: pb::Empty) -> Result { - self.with_col(|col| Ok(col.all_browser_card_columns())) - } - - fn all_browser_note_columns(&self, _input: pb::Empty) -> Result { - self.with_col(|col| Ok(col.all_browser_note_columns())) + fn all_browser_columns(&self, _input: pb::Empty) -> Result { + self.with_col(|col| Ok(col.all_browser_columns())) } fn browser_row_for_id(&self, input: pb::Int64) -> Result { diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index 97aa53658..45ee0e9f2 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use itertools::Itertools; use serde_repr::{Deserialize_repr, Serialize_repr}; -use strum::{Display, EnumString}; +use strum::{Display, EnumIter, EnumString}; use crate::error::{AnkiError, Result}; use crate::i18n::I18n; @@ -22,14 +22,18 @@ use crate::{ timestamp::{TimestampMillis, TimestampSecs}, }; -#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy, Display, EnumString)] +#[derive( + Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy, Display, EnumString, EnumIter, +)] #[strum(serialize_all = "camelCase")] #[repr(u8)] pub enum Column { #[strum(serialize = "")] Custom, - Question, Answer, + CardMod, + #[strum(serialize = "template")] + Cards, Deck, #[strum(serialize = "cardDue")] Due, @@ -39,26 +43,17 @@ pub enum Column { Lapses, #[strum(serialize = "cardIvl")] Interval, - CardMod, - #[strum(serialize = "cardReps")] - Reps, - #[strum(serialize = "template")] - Cards, - NoteCards, #[strum(serialize = "noteCrt")] NoteCreation, - NoteDue, - NoteEase, - #[strum(serialize = "noteFld")] - SortField, - #[strum(serialize = "noteIvl")] - NoteInterval, - NoteLapses, NoteMod, - NoteReps, - Tags, #[strum(serialize = "note")] Notetype, + Question, + #[strum(serialize = "cardReps")] + Reps, + #[strum(serialize = "noteFld")] + SortField, + Tags, } impl Default for Column { @@ -295,17 +290,13 @@ impl RowContext { Column::Question => self.question_str(), Column::Answer => self.answer_str(), Column::Deck => self.deck_str(), - Column::Due | Column::NoteDue => self.due_str(), - Column::Ease | Column::NoteEase => self.ease_str(), - Column::Interval | Column::NoteInterval => self.interval_str(), - Column::Lapses | Column::NoteLapses => { - self.cards.iter().map(|c| c.lapses).sum::().to_string() - } + Column::Due => self.due_str(), + Column::Ease => self.ease_str(), + Column::Interval => self.interval_str(), + Column::Lapses => self.cards.iter().map(|c| c.lapses).sum::().to_string(), Column::CardMod => self.card_mod_str(), - Column::Reps | Column::NoteReps => { - self.cards.iter().map(|c| c.reps).sum::().to_string() - } - Column::Cards | Column::NoteCards => self.cards_str()?, + Column::Reps => self.cards.iter().map(|c| c.reps).sum::().to_string(), + Column::Cards => self.cards_str()?, Column::NoteCreation => self.note_creation_str(), Column::SortField => self.note_field_str(), Column::NoteMod => self.note.mtime.date_string(), From bdd257e140344d45b50b8f6f11d167d807e8f8dc Mon Sep 17 00:00:00 2001 From: RumovZ Date: Fri, 9 Apr 2021 18:03:29 +0200 Subject: [PATCH 19/32] Merge SortKind enum into Column enum --- pylib/anki/collection.py | 16 ++-- rslib/backend.proto | 36 +++---- rslib/src/backend/search/mod.rs | 42 ++++---- rslib/src/browser_table.rs | 20 +++- rslib/src/config/mod.rs | 76 +++------------ rslib/src/search/mod.rs | 165 ++++++++++++++------------------ 6 files changed, 144 insertions(+), 211 deletions(-) diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 2f3b32626..5f08aa9ab 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -12,7 +12,7 @@ SearchNode = _pb.SearchNode Progress = _pb.Progress EmptyCardsReport = _pb.EmptyCardsReport GraphPreferences = _pb.GraphPreferences -BuiltinSort = _pb.SortOrder.Builtin +BuiltinSort = _pb.SortOrder.Builtin.SortColumn Preferences = _pb.Preferences UndoStatus = _pb.UndoStatus OpChanges = _pb.OpChanges @@ -506,7 +506,7 @@ class Collection: def find_cards( self, query: str, - order: Union[bool, str, BuiltinSort.Kind.V] = False, + order: Union[bool, str, BuiltinSort.V] = False, reverse: bool = False, ) -> Sequence[CardId]: """Return card ids matching the provided search. @@ -521,10 +521,10 @@ class Collection: 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.DUE) + If order is a BuiltinSort value, sort using that builtin sort, eg + col.find_cards("", order=BuiltinSort.DUE) - The reverse argument only applies when a BuiltinSort.Kind is provided; + The reverse argument only applies when a BuiltinSort.V is provided; otherwise the collection config defines whether reverse is set or not. """ mode = _build_sort_mode(order, reverse) @@ -535,7 +535,7 @@ class Collection: def find_notes( self, query: str, - order: Union[bool, str, BuiltinSort.Kind.V] = False, + order: Union[bool, str, BuiltinSort.V] = False, reverse: bool = False, ) -> Sequence[NoteId]: """Return note ids matching the provided search. @@ -1123,7 +1123,7 @@ _UndoInfo = Union[_ReviewsUndo, LegacyCheckpoint, None] def _build_sort_mode( - order: Union[bool, str, BuiltinSort.Kind.V], + order: Union[bool, str, BuiltinSort.V], reverse: bool, ) -> _pb.SortOrder: if isinstance(order, str): @@ -1134,4 +1134,4 @@ def _build_sort_mode( else: return _pb.SortOrder(none=_pb.Empty()) else: - return _pb.SortOrder(builtin=_pb.SortOrder.Builtin(kind=order, reverse=reverse)) + return _pb.SortOrder(builtin=_pb.SortOrder.Builtin(column=order, reverse=reverse)) diff --git a/rslib/backend.proto b/rslib/backend.proto index b4479baae..b1c8989a9 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -796,28 +796,22 @@ message SearchOut { message SortOrder { message Builtin { - enum Kind { - NOTE_CARDS = 0; - NOTE_CREATION = 1; - NOTE_DUE = 2; - NOTE_EASE = 3; - SORT_FIELD = 4; - NOTE_INTERVAL = 5; - NOTE_LAPSES = 6; - NOTE_MOD = 7; - NOTE_REPS = 8; - TAGS = 9; - NOTETYPE = 10; - CARD_MOD = 11; - REPS = 12; - DUE = 13; - EASE = 14; - LAPSES = 15; - INTERVAL = 16; - DECK = 17; - CARDS = 18; + enum SortColumn { + CARD_MOD = 0; + CARDS = 1; + DECK = 2; + DUE = 3; + EASE = 4; + LAPSES = 5; + INTERVAL = 6; + NOTE_CREATION = 7; + NOTE_MOD = 8; + NOTETYPE = 9; + REPS = 10; + SORT_FIELD = 11; + TAGS = 12; } - Kind kind = 1; + SortColumn column = 1; bool reverse = 2; } oneof value { diff --git a/rslib/src/backend/search/mod.rs b/rslib/src/backend/search/mod.rs index e5285d6f2..dcbda879c 100644 --- a/rslib/src/backend/search/mod.rs +++ b/rslib/src/backend/search/mod.rs @@ -10,9 +10,9 @@ use super::Backend; use crate::{ backend_proto as pb, backend_proto::{ - sort_order::builtin::Kind as SortKindProto, sort_order::Value as SortOrderProto, + sort_order::builtin::SortColumn as SortColumnProto, sort_order::Value as SortOrderProto, }, - config::SortKind, + browser_table::Column, prelude::*, search::{concatenate_searches, replace_search_node, write_nodes, Node, SortMode}, }; @@ -108,28 +108,22 @@ impl SearchService for Backend { } } -impl From for SortKind { - fn from(kind: SortKindProto) -> Self { +impl From for Column { + fn from(kind: SortColumnProto) -> Self { match kind { - SortKindProto::NoteCards => SortKind::NoteCards, - SortKindProto::NoteCreation => SortKind::NoteCreation, - SortKindProto::NoteDue => SortKind::NoteDue, - SortKindProto::NoteEase => SortKind::NoteEase, - SortKindProto::NoteInterval => SortKind::NoteInterval, - SortKindProto::NoteLapses => SortKind::NoteLapses, - SortKindProto::NoteMod => SortKind::NoteMod, - SortKindProto::SortField => SortKind::SortField, - SortKindProto::NoteReps => SortKind::NoteReps, - SortKindProto::Tags => SortKind::Tags, - SortKindProto::Notetype => SortKind::Notetype, - SortKindProto::CardMod => SortKind::CardMod, - SortKindProto::Reps => SortKind::Reps, - SortKindProto::Due => SortKind::Due, - SortKindProto::Ease => SortKind::Ease, - SortKindProto::Lapses => SortKind::Lapses, - SortKindProto::Interval => SortKind::Interval, - SortKindProto::Deck => SortKind::Deck, - SortKindProto::Cards => SortKind::Cards, + SortColumnProto::CardMod => Column::CardMod, + SortColumnProto::Cards => Column::Cards, + SortColumnProto::Deck => Column::Deck, + SortColumnProto::Due => Column::Due, + SortColumnProto::Ease => Column::Ease, + SortColumnProto::Lapses => Column::Lapses, + SortColumnProto::Interval => Column::Interval, + SortColumnProto::NoteCreation => Column::NoteCreation, + SortColumnProto::NoteMod => Column::NoteMod, + SortColumnProto::Notetype => Column::Notetype, + SortColumnProto::Reps => Column::Reps, + SortColumnProto::SortField => Column::SortField, + SortColumnProto::Tags => Column::Tags, } } } @@ -142,7 +136,7 @@ impl From> for SortMode { V::Custom(s) => SortMode::Custom(s), V::FromConfig(_) => SortMode::FromConfig, V::Builtin(b) => SortMode::Builtin { - kind: b.kind().into(), + column: b.column().into(), reverse: b.reverse, }, } diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index 45ee0e9f2..8e33daea0 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use itertools::Itertools; -use serde_repr::{Deserialize_repr, Serialize_repr}; +use serde::{Deserialize, Serialize}; use strum::{Display, EnumIter, EnumString}; use crate::error::{AnkiError, Result}; @@ -22,37 +22,47 @@ use crate::{ timestamp::{TimestampMillis, TimestampSecs}, }; -#[derive( - Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy, Display, EnumString, EnumIter, -)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy, Display, EnumIter, EnumString)] +#[serde(rename_all = "camelCase")] #[strum(serialize_all = "camelCase")] -#[repr(u8)] pub enum Column { + #[serde(rename = "")] #[strum(serialize = "")] Custom, Answer, CardMod, + #[serde(rename = "template")] #[strum(serialize = "template")] Cards, Deck, + #[serde(rename = "cardDue")] #[strum(serialize = "cardDue")] Due, + #[serde(rename = "cardEase")] #[strum(serialize = "cardEase")] Ease, + #[serde(rename = "cardLapses")] #[strum(serialize = "cardLapses")] Lapses, + #[serde(rename = "cardIvl")] #[strum(serialize = "cardIvl")] Interval, + #[serde(rename = "noteCrt")] #[strum(serialize = "noteCrt")] NoteCreation, NoteMod, + #[serde(rename = "note")] #[strum(serialize = "note")] Notetype, Question, + #[serde(rename = "cardReps")] #[strum(serialize = "cardReps")] Reps, + #[serde(rename = "noteFld")] #[strum(serialize = "noteFld")] SortField, + #[serde(rename = "noteTags")] + #[strum(serialize = "noteTags")] Tags, } diff --git a/rslib/src/config/mod.rs b/rslib/src/config/mod.rs index beabae235..e26239280 100644 --- a/rslib/src/config/mod.rs +++ b/rslib/src/config/mod.rs @@ -9,10 +9,9 @@ mod string; pub(crate) mod undo; pub use self::{bool::BoolKey, string::StringKey}; -use crate::browser_table; +use crate::browser_table::Column; use crate::prelude::*; use serde::{de::DeserializeOwned, Serialize}; -use serde_derive::Deserialize; use serde_repr::{Deserialize_repr, Serialize_repr}; use slog::warn; use strum::IntoStaticStr; @@ -48,9 +47,9 @@ pub(crate) enum ConfigKey { #[strum(to_string = "timeLim")] AnswerTimeLimitSecs, #[strum(to_string = "sortType")] - BrowserSortKind, + BrowserSortColumn, #[strum(to_string = "noteSortType")] - BrowserNoteSortKind, + BrowserNoteSortColumn, #[strum(to_string = "curDeck")] CurrentDeckId, #[strum(to_string = "curModel")] @@ -131,33 +130,29 @@ impl Collection { Ok(()) } - pub(crate) fn get_browser_sort_kind(&self) -> SortKind { - self.get_config_default(ConfigKey::BrowserSortKind) + pub(crate) fn get_browser_sort_column(&self) -> Column { + self.get_config_optional(ConfigKey::BrowserSortColumn) + .unwrap_or(Column::NoteCreation) } - pub(crate) fn get_browser_note_sort_kind(&self) -> SortKind { - self.get_config_default(ConfigKey::BrowserNoteSortKind) + pub(crate) fn get_browser_note_sort_column(&self) -> Column { + self.get_config_optional(ConfigKey::BrowserNoteSortColumn) + .unwrap_or(Column::NoteCreation) } - pub(crate) fn get_desktop_browser_card_columns(&self) -> Option> { + pub(crate) fn get_desktop_browser_card_columns(&self) -> Option> { self.get_config_optional(ConfigKey::DesktopBrowserCardColumns) } - pub(crate) fn set_desktop_browser_card_columns( - &mut self, - columns: Vec, - ) -> Result<()> { + pub(crate) fn set_desktop_browser_card_columns(&mut self, columns: Vec) -> Result<()> { self.set_config(ConfigKey::DesktopBrowserCardColumns, &columns) } - pub(crate) fn get_desktop_browser_note_columns(&self) -> Option> { + pub(crate) fn get_desktop_browser_note_columns(&self) -> Option> { self.get_config_optional(ConfigKey::DesktopBrowserNoteColumns) } - pub(crate) fn set_desktop_browser_note_columns( - &mut self, - columns: Vec, - ) -> Result<()> { + pub(crate) fn set_desktop_browser_note_columns(&mut self, columns: Vec) -> Result<()> { self.set_config(ConfigKey::DesktopBrowserNoteColumns, &columns) } @@ -269,47 +264,6 @@ impl Collection { } } -#[derive(Deserialize, PartialEq, Debug, Clone, Copy)] -#[serde(rename_all = "camelCase")] -pub enum SortKind { - NoteCards, - #[serde(rename = "noteCrt")] - NoteCreation, - NoteDue, - NoteEase, - #[serde(rename = "noteIvl")] - NoteInterval, - NoteLapses, - NoteMod, - #[serde(rename = "noteFld")] - SortField, - NoteReps, - #[serde(rename = "note")] - Notetype, - #[serde(rename = "noteTags")] - Tags, - CardMod, - #[serde(rename = "cardReps")] - Reps, - #[serde(rename = "cardDue")] - Due, - #[serde(rename = "cardEase")] - Ease, - #[serde(rename = "cardLapses")] - Lapses, - #[serde(rename = "cardIvl")] - Interval, - Deck, - #[serde(rename = "template")] - Cards, -} - -impl Default for SortKind { - fn default() -> Self { - Self::NoteCreation - } -} - // 2021 scheduler moves this into deck config pub(crate) enum NewReviewMix { Mix = 0, @@ -334,7 +288,7 @@ pub(crate) enum Weekday { #[cfg(test)] mod test { - use super::SortKind; + use super::*; use crate::collection::open_test_collection; use crate::decks::DeckId; @@ -342,7 +296,7 @@ mod test { fn defaults() { let col = open_test_collection(); assert_eq!(col.get_current_deck_id(), DeckId(1)); - assert_eq!(col.get_browser_sort_kind(), SortKind::SortField); + assert_eq!(col.get_browser_sort_column(), Column::SortField); } #[test] diff --git a/rslib/src/search/mod.rs b/rslib/src/search/mod.rs index 4a6e97358..96dc5564d 100644 --- a/rslib/src/search/mod.rs +++ b/rslib/src/search/mod.rs @@ -14,14 +14,8 @@ use rusqlite::types::FromSql; use std::borrow::Cow; use crate::{ - card::CardId, - card::CardType, - collection::Collection, - config::{BoolKey, SortKind}, - error::Result, - notes::NoteId, - prelude::AnkiError, - search::parser::parse, + browser_table::Column, card::CardId, card::CardType, collection::Collection, config::BoolKey, + error::Result, notes::NoteId, prelude::AnkiError, search::parser::parse, }; use sqlwriter::{RequiredTable, SqlWriter}; @@ -35,7 +29,7 @@ pub enum SearchItems { pub enum SortMode { NoOrder, FromConfig, - Builtin { kind: SortKind, reverse: bool }, + Builtin { column: Column, reverse: bool }, Custom(String), } @@ -69,7 +63,7 @@ impl SortMode { match self { SortMode::NoOrder => RequiredTable::CardsOrNotes, SortMode::FromConfig => unreachable!(), - SortMode::Builtin { kind, .. } => kind.required_table(), + SortMode::Builtin { column, .. } => column.required_table(), SortMode::Custom(ref text) => { if text.contains("n.") { if text.contains("c.") { @@ -85,28 +79,16 @@ impl SortMode { } } -impl SortKind { +impl Column { fn required_table(self) -> RequiredTable { match self { - SortKind::NoteCards - | SortKind::NoteCreation - | SortKind::NoteDue - | SortKind::NoteEase - | SortKind::SortField - | SortKind::NoteInterval - | SortKind::NoteLapses - | SortKind::NoteMod - | SortKind::NoteReps - | SortKind::Tags - | SortKind::Notetype => RequiredTable::Notes, - SortKind::Cards => RequiredTable::CardsAndNotes, - SortKind::CardMod - | SortKind::Reps - | SortKind::Due - | SortKind::Ease - | SortKind::Lapses - | SortKind::Interval - | SortKind::Deck => RequiredTable::Cards, + Column::Cards + | Column::NoteCreation + | Column::NoteMod + | Column::Notetype + | Column::SortField + | Column::Tags => RequiredTable::Notes, + _ => RequiredTable::CardsOrNotes, } } } @@ -144,10 +126,10 @@ impl Collection { match mode { SortMode::NoOrder => (), SortMode::FromConfig => unreachable!(), - SortMode::Builtin { kind, reverse } => { - prepare_sort(self, kind, items)?; + SortMode::Builtin { column, reverse } => { + prepare_sort(self, column, items)?; sql.push_str(" order by "); - write_order(sql, items, kind, reverse)?; + write_order(sql, items, column, reverse)?; } SortMode::Custom(order_clause) => { sql.push_str(" order by "); @@ -192,11 +174,11 @@ impl Collection { if mode == &SortMode::FromConfig { *mode = match items { SearchItems::Cards => SortMode::Builtin { - kind: self.get_browser_sort_kind(), + column: self.get_browser_sort_column(), reverse: self.get_bool(BoolKey::BrowserSortBackwards), }, SearchItems::Notes => SortMode::Builtin { - kind: self.get_browser_note_sort_kind(), + column: self.get_browser_note_sort_column(), reverse: self.get_bool(BoolKey::BrowserNoteSortBackwards), }, } @@ -205,15 +187,15 @@ impl Collection { } /// Add the order clause to the sql. -fn write_order(sql: &mut String, items: SearchItems, kind: SortKind, reverse: bool) -> Result<()> { +fn write_order(sql: &mut String, items: SearchItems, column: Column, reverse: bool) -> Result<()> { let order = match items { - SearchItems::Cards => card_order_from_sortkind(kind), - SearchItems::Notes => note_order_from_sortkind(kind), + SearchItems::Cards => card_order_from_sort_column(column), + SearchItems::Notes => note_order_from_sort_column(column), }; if order.is_empty() { return Err(AnkiError::invalid_input(format!( "Can't sort {:?} by {:?}.", - items, kind + items, column ))); } if reverse { @@ -229,70 +211,69 @@ fn write_order(sql: &mut String, items: SearchItems, kind: SortKind, reverse: bo Ok(()) } -fn card_order_from_sortkind(kind: SortKind) -> Cow<'static, str> { - match kind { - SortKind::NoteCreation => "n.id asc, c.ord asc".into(), - SortKind::NoteMod => "n.mod asc, c.ord asc".into(), - SortKind::SortField => "n.sfld collate nocase asc, c.ord asc".into(), - SortKind::CardMod => "c.mod asc".into(), - SortKind::Reps => "c.reps asc".into(), - SortKind::Due => "c.type asc, c.due asc".into(), - SortKind::Ease => format!("c.type = {} asc, c.factor asc", CardType::New as i8).into(), - SortKind::Lapses => "c.lapses asc".into(), - SortKind::Interval => "c.ivl asc".into(), - SortKind::Tags => "n.tags asc".into(), - SortKind::Deck => "(select pos from sort_order where did = c.did) asc".into(), - SortKind::Notetype => "(select pos from sort_order where ntid = n.mid) asc".into(), - SortKind::Cards => concat!( +fn card_order_from_sort_column(column: Column) -> Cow<'static, str> { + match column { + Column::CardMod => "c.mod asc".into(), + Column::Cards => concat!( "coalesce((select pos from sort_order where ntid = n.mid and ord = c.ord),", // need to fall back on ord 0 for cloze cards "(select pos from sort_order where ntid = n.mid and ord = 0)) asc" ) .into(), - _ => "".into(), + Column::Deck => "(select pos from sort_order where did = c.did) asc".into(), + Column::Due => "c.type asc, c.due asc".into(), + Column::Ease => format!("c.type = {} asc, c.factor asc", CardType::New as i8).into(), + Column::Interval => "c.ivl asc".into(), + Column::Lapses => "c.lapses asc".into(), + Column::NoteCreation => "n.id asc, c.ord asc".into(), + Column::NoteMod => "n.mod asc, c.ord asc".into(), + Column::Notetype => "(select pos from sort_order where ntid = n.mid) asc".into(), + Column::Reps => "c.reps asc".into(), + Column::SortField => "n.sfld collate nocase asc, c.ord asc".into(), + Column::Tags => "n.tags asc".into(), + Column::Answer | Column::Custom | Column::Question => "".into(), } } -fn note_order_from_sortkind(kind: SortKind) -> Cow<'static, str> { - match kind { - SortKind::Deck - | SortKind::CardMod - | SortKind::NoteCards - | SortKind::NoteDue - | SortKind::NoteEase - | SortKind::NoteInterval - | SortKind::NoteLapses - | SortKind::NoteReps => "(select pos from sort_order where nid = n.id) asc".into(), - SortKind::NoteCreation => "n.id asc".into(), - SortKind::SortField => "n.sfld collate nocase asc".into(), - SortKind::NoteMod => "n.mod asc".into(), - SortKind::Tags => "n.tags asc".into(), - SortKind::Notetype => "(select pos from sort_order where ntid = n.mid) asc".into(), - _ => "".into(), +fn note_order_from_sort_column(column: Column) -> Cow<'static, str> { + match column { + Column::CardMod + | Column::Cards + | Column::Deck + | Column::Due + | Column::Ease + | Column::Interval + | Column::Lapses + | Column::Reps => "(select pos from sort_order where nid = n.id) asc".into(), + Column::NoteCreation => "n.id asc".into(), + Column::NoteMod => "n.mod asc".into(), + Column::Notetype => "(select pos from sort_order where ntid = n.mid) asc".into(), + Column::SortField => "n.sfld collate nocase asc".into(), + Column::Tags => "n.tags asc".into(), + Column::Answer | Column::Custom | Column::Question => "".into(), } } -fn prepare_sort(col: &mut Collection, kind: SortKind, items: SearchItems) -> Result<()> { - use SortKind::*; - let notes_mode = items == SearchItems::Notes; - let sql = match kind { - Deck => { - if notes_mode { - include_str!("note_decks_order.sql") - } else { - include_str!("deck_order.sql") - } - } - CardMod if notes_mode => include_str!("card_mod_order.sql"), - Cards => include_str!("template_order.sql"), - NoteCards => include_str!("note_cards_order.sql"), - NoteDue => include_str!("note_due_order.sql"), - NoteEase => include_str!("note_ease_order.sql"), - NoteInterval => include_str!("note_interval_order.sql"), - NoteLapses => include_str!("note_lapses_order.sql"), - NoteReps => include_str!("note_reps_order.sql"), - Notetype => include_str!("notetype_order.sql"), - _ => return Ok(()), +fn prepare_sort(col: &mut Collection, column: Column, items: SearchItems) -> Result<()> { + let sql = match items { + SearchItems::Cards => match column { + Column::Cards => include_str!("template_order.sql"), + Column::Deck => include_str!("deck_order.sql"), + Column::Notetype => include_str!("notetype_order.sql"), + _ => return Ok(()), + }, + SearchItems::Notes => match column { + Column::Cards => include_str!("note_cards_order.sql"), + Column::CardMod => include_str!("card_mod_order.sql"), + Column::Deck => include_str!("note_decks_order.sql"), + Column::Due => include_str!("note_due_order.sql"), + Column::Ease => include_str!("note_ease_order.sql"), + Column::Interval => include_str!("note_interval_order.sql"), + Column::Lapses => include_str!("note_lapses_order.sql"), + Column::Reps => include_str!("note_reps_order.sql"), + Column::Notetype => include_str!("notetype_order.sql"), + _ => return Ok(()), + }, }; col.storage.db.execute_batch(sql)?; From 055a5e8a049e3721bb4a63c94e9c755bbcd69201 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Fri, 9 Apr 2021 18:50:30 +0200 Subject: [PATCH 20/32] Remove pb SortKind enum and use pb Columns instead --- pylib/.pylintrc | 1 + pylib/anki/collection.py | 29 ++++++++++++++++------------- pylib/tests/test_find.py | 13 ++++++++++--- rslib/backend.proto | 17 +---------------- rslib/src/backend/search/mod.rs | 27 +++------------------------ 5 files changed, 31 insertions(+), 56 deletions(-) diff --git a/pylib/.pylintrc b/pylib/.pylintrc index 915320c1e..b734d4c91 100644 --- a/pylib/.pylintrc +++ b/pylib/.pylintrc @@ -4,6 +4,7 @@ persistent = no [TYPECHECK] ignored-classes= + BrowserColumns, BrowserRow, FormatTimespanIn, AnswerCardIn, diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 5f08aa9ab..6311dbe8f 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -12,7 +12,6 @@ SearchNode = _pb.SearchNode Progress = _pb.Progress EmptyCardsReport = _pb.EmptyCardsReport GraphPreferences = _pb.GraphPreferences -BuiltinSort = _pb.SortOrder.Builtin.SortColumn Preferences = _pb.Preferences UndoStatus = _pb.UndoStatus OpChanges = _pb.OpChanges @@ -41,7 +40,7 @@ from anki.config import Config, ConfigManager from anki.consts import * from anki.dbproxy import DBProxy from anki.decks import Deck, DeckConfig, DeckConfigId, DeckId, DeckManager -from anki.errors import AbortSchemaModification, DBError +from anki.errors import AbortSchemaModification, DBError, InvalidInput from anki.lang import FormatTimeSpan from anki.media import MediaManager, media_paths_from_col_path from anki.models import ModelManager, Notetype, NotetypeDict, NotetypeId @@ -506,7 +505,7 @@ class Collection: def find_cards( self, query: str, - order: Union[bool, str, BuiltinSort.V] = False, + order: Union[bool, str, BrowserColumns.Column] = False, reverse: bool = False, ) -> Sequence[CardId]: """Return card ids matching the provided search. @@ -521,10 +520,12 @@ class Collection: 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 value, sort using that builtin sort, eg - col.find_cards("", order=BuiltinSort.DUE) + If order is a BrowserColumns.Column that supports sorting, sort using that + column. All available columns are available through col.all_browser_columns() + or browser.table._model.columns and support sorting unless column.sorting + is set to BrowserColumns.SORTING_NONE. - The reverse argument only applies when a BuiltinSort.V is provided; + The reverse argument only applies when a BrowserColumns.Column is provided; otherwise the collection config defines whether reverse is set or not. """ mode = _build_sort_mode(order, reverse) @@ -535,7 +536,7 @@ class Collection: def find_notes( self, query: str, - order: Union[bool, str, BuiltinSort.V] = False, + order: Union[bool, str, BrowserColumns.Column] = False, reverse: bool = False, ) -> Sequence[NoteId]: """Return note ids matching the provided search. @@ -1123,15 +1124,17 @@ _UndoInfo = Union[_ReviewsUndo, LegacyCheckpoint, None] def _build_sort_mode( - order: Union[bool, str, BuiltinSort.V], + order: Union[bool, str, BrowserColumns.Column], reverse: bool, ) -> _pb.SortOrder: if isinstance(order, str): return _pb.SortOrder(custom=order) - elif isinstance(order, bool): + if isinstance(order, bool): if order is True: return _pb.SortOrder(from_config=_pb.Empty()) - else: - return _pb.SortOrder(none=_pb.Empty()) - else: - return _pb.SortOrder(builtin=_pb.SortOrder.Builtin(column=order, reverse=reverse)) + return _pb.SortOrder(none=_pb.Empty()) + if order.sorting != BrowserColumns.SORTING_NONE: + return _pb.SortOrder( + builtin=_pb.SortOrder.Builtin(column=order.key, reverse=reverse) + ) + raise InvalidInput(f"{order} is not a valid sort order.") diff --git a/pylib/tests/test_find.py b/pylib/tests/test_find.py index 3e5a68e67..3580c9c3e 100644 --- a/pylib/tests/test_find.py +++ b/pylib/tests/test_find.py @@ -1,7 +1,7 @@ # coding: utf-8 import pytest -from anki.collection import BuiltinSort, Config +from anki.collection import Config from anki.consts import * from tests.shared import getEmptyCol, isNearCutoff @@ -124,8 +124,15 @@ def test_findCards(): col.set_config_bool(Config.Bool.BROWSER_SORT_BACKWARDS, True) col.flush() assert col.findCards("", order=True)[0] in latestCardIds - assert col.find_cards("", order=BuiltinSort.DUE, reverse=False)[0] == firstCardId - assert col.find_cards("", order=BuiltinSort.DUE, reverse=True)[0] != firstCardId + sort_columns = dict(((c.key, c) for c in col.all_browser_columns())) + assert ( + col.find_cards("", order=sort_columns["cardDue"], reverse=False)[0] + == firstCardId + ) + assert ( + col.find_cards("", order=sort_columns["cardDue"], reverse=True)[0] + != firstCardId + ) # model assert len(col.findCards("note:basic")) == 3 assert len(col.findCards("-note:basic")) == 2 diff --git a/rslib/backend.proto b/rslib/backend.proto index b1c8989a9..afa6ab58f 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -796,22 +796,7 @@ message SearchOut { message SortOrder { message Builtin { - enum SortColumn { - CARD_MOD = 0; - CARDS = 1; - DECK = 2; - DUE = 3; - EASE = 4; - LAPSES = 5; - INTERVAL = 6; - NOTE_CREATION = 7; - NOTE_MOD = 8; - NOTETYPE = 9; - REPS = 10; - SORT_FIELD = 11; - TAGS = 12; - } - SortColumn column = 1; + string column = 1; bool reverse = 2; } oneof value { diff --git a/rslib/src/backend/search/mod.rs b/rslib/src/backend/search/mod.rs index dcbda879c..189832a1b 100644 --- a/rslib/src/backend/search/mod.rs +++ b/rslib/src/backend/search/mod.rs @@ -5,13 +5,12 @@ mod browser_table; mod search_node; use std::convert::TryInto; +use std::str::FromStr; use super::Backend; use crate::{ backend_proto as pb, - backend_proto::{ - sort_order::builtin::SortColumn as SortColumnProto, sort_order::Value as SortOrderProto, - }, + backend_proto::sort_order::Value as SortOrderProto, browser_table::Column, prelude::*, search::{concatenate_searches, replace_search_node, write_nodes, Node, SortMode}, @@ -108,26 +107,6 @@ impl SearchService for Backend { } } -impl From for Column { - fn from(kind: SortColumnProto) -> Self { - match kind { - SortColumnProto::CardMod => Column::CardMod, - SortColumnProto::Cards => Column::Cards, - SortColumnProto::Deck => Column::Deck, - SortColumnProto::Due => Column::Due, - SortColumnProto::Ease => Column::Ease, - SortColumnProto::Lapses => Column::Lapses, - SortColumnProto::Interval => Column::Interval, - SortColumnProto::NoteCreation => Column::NoteCreation, - SortColumnProto::NoteMod => Column::NoteMod, - SortColumnProto::Notetype => Column::Notetype, - SortColumnProto::Reps => Column::Reps, - SortColumnProto::SortField => Column::SortField, - SortColumnProto::Tags => Column::Tags, - } - } -} - impl From> for SortMode { fn from(order: Option) -> Self { use pb::sort_order::Value as V; @@ -136,7 +115,7 @@ impl From> for SortMode { V::Custom(s) => SortMode::Custom(s), V::FromConfig(_) => SortMode::FromConfig, V::Builtin(b) => SortMode::Builtin { - column: b.column().into(), + column: Column::from_str(&b.column).unwrap_or_default(), reverse: b.reverse, }, } From f04ea5a2c743f506880abf62e0d9b3ec7ea98c36 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Fri, 9 Apr 2021 19:09:48 +0200 Subject: [PATCH 21/32] Move Column logic into main rslib --- rslib/src/backend/search/browser_table.rs | 76 +---------------------- rslib/src/browser_table.rs | 75 +++++++++++++++++++++- 2 files changed, 76 insertions(+), 75 deletions(-) diff --git a/rslib/src/backend/search/browser_table.rs b/rslib/src/backend/search/browser_table.rs index f544c761c..dcafc1c1b 100644 --- a/rslib/src/backend/search/browser_table.rs +++ b/rslib/src/backend/search/browser_table.rs @@ -3,23 +3,10 @@ use std::str::FromStr; -use strum::IntoEnumIterator; - -use crate::{backend_proto as pb, browser_table, collection::Collection, i18n::I18n}; - -impl Collection { - pub(crate) fn all_browser_columns(&self) -> pb::BrowserColumns { - let mut columns: Vec = browser_table::Column::iter() - .filter(|&c| c != browser_table::Column::Custom) - .map(|c| c.to_pb_column(&self.tr)) - .collect(); - columns.sort_by(|c1, c2| c1.label.cmp(&c2.label)); - pb::BrowserColumns { columns } - } -} +use crate::{backend_proto as pb, browser_table, i18n::I18n}; impl browser_table::Column { - fn to_pb_column(self, i18n: &I18n) -> pb::browser_columns::Column { + pub fn to_pb_column(self, i18n: &I18n) -> pb::browser_columns::Column { pb::browser_columns::Column { key: self.to_string(), label: self.localized_label(i18n), @@ -29,65 +16,6 @@ impl browser_table::Column { alignment: self.alignment() as i32, } } - - fn sorting(self) -> pb::browser_columns::Sorting { - match self { - Self::Question | Self::Answer | Self::Custom => pb::browser_columns::Sorting::None, - Self::SortField => pb::browser_columns::Sorting::Reversed, - _ => pb::browser_columns::Sorting::Normal, - } - } - - fn uses_cell_font(self) -> bool { - matches!(self, Self::Question | Self::Answer | Self::SortField) - } - - fn alignment(self) -> pb::browser_columns::Alignment { - match self { - Self::Question - | Self::Answer - | Self::Cards - | Self::Deck - | Self::SortField - | Self::Notetype - | Self::Tags => pb::browser_columns::Alignment::Start, - _ => pb::browser_columns::Alignment::Center, - } - } - - fn localized_label(self, i18n: &I18n) -> String { - match self { - Self::Answer => i18n.browsing_answer(), - Self::CardMod => i18n.search_card_modified(), - Self::Cards => i18n.browsing_card(), - Self::Deck => i18n.decks_deck(), - Self::Due => i18n.statistics_due_date(), - Self::Custom => i18n.browsing_addon(), - Self::Ease => i18n.browsing_ease(), - Self::Interval => i18n.browsing_interval(), - Self::Lapses => i18n.scheduling_lapses(), - Self::NoteCreation => i18n.browsing_created(), - Self::NoteMod => i18n.search_note_modified(), - Self::Notetype => i18n.browsing_note(), - Self::Question => i18n.browsing_question(), - Self::Reps => i18n.scheduling_reviews(), - Self::SortField => i18n.browsing_sort_field(), - Self::Tags => i18n.editing_tags(), - } - .into() - } - - fn localized_notes_label(self, i18n: &I18n) -> String { - match self { - Self::CardMod => i18n.search_card_modified(), - Self::Cards => i18n.editing_cards(), - Self::Ease => i18n.browsing_average_ease(), - Self::Interval => i18n.browsing_average_interval(), - Self::Reps => i18n.scheduling_reviews(), - _ => return self.localized_label(i18n), - } - .into() - } } impl From for Vec { diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index 8e33daea0..387c1ae23 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -5,11 +5,12 @@ use std::sync::Arc; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use strum::{Display, EnumIter, EnumString}; +use strum::{Display, EnumIter, EnumString, IntoEnumIterator}; use crate::error::{AnkiError, Result}; use crate::i18n::I18n; use crate::{ + backend_proto as pb, card::{Card, CardId, CardQueue, CardType}, collection::Collection, config::BoolKey, @@ -170,7 +171,79 @@ impl Note { } } +impl Column { + pub fn localized_label(self, i18n: &I18n) -> String { + match self { + Self::Answer => i18n.browsing_answer(), + Self::CardMod => i18n.search_card_modified(), + Self::Cards => i18n.browsing_card(), + Self::Deck => i18n.decks_deck(), + Self::Due => i18n.statistics_due_date(), + Self::Custom => i18n.browsing_addon(), + Self::Ease => i18n.browsing_ease(), + Self::Interval => i18n.browsing_interval(), + Self::Lapses => i18n.scheduling_lapses(), + Self::NoteCreation => i18n.browsing_created(), + Self::NoteMod => i18n.search_note_modified(), + Self::Notetype => i18n.browsing_note(), + Self::Question => i18n.browsing_question(), + Self::Reps => i18n.scheduling_reviews(), + Self::SortField => i18n.browsing_sort_field(), + Self::Tags => i18n.editing_tags(), + } + .into() + } + + pub fn localized_notes_label(self, i18n: &I18n) -> String { + match self { + Self::CardMod => i18n.search_card_modified(), + Self::Cards => i18n.editing_cards(), + Self::Ease => i18n.browsing_average_ease(), + Self::Interval => i18n.browsing_average_interval(), + Self::Reps => i18n.scheduling_reviews(), + _ => return self.localized_label(i18n), + } + .into() + } + + pub fn sorting(self) -> pb::browser_columns::Sorting { + use pb::browser_columns::Sorting; + match self { + Self::Question | Self::Answer | Self::Custom => Sorting::None, + Self::SortField => Sorting::Reversed, + _ => Sorting::Normal, + } + } + + pub fn uses_cell_font(self) -> bool { + matches!(self, Self::Question | Self::Answer | Self::SortField) + } + + pub fn alignment(self) -> pb::browser_columns::Alignment { + use pb::browser_columns::Alignment; + match self { + Self::Question + | Self::Answer + | Self::Cards + | Self::Deck + | Self::SortField + | Self::Notetype + | Self::Tags => Alignment::Start, + _ => Alignment::Center, + } + } +} + impl Collection { + pub fn all_browser_columns(&self) -> pb::BrowserColumns { + let mut columns: Vec = Column::iter() + .filter(|&c| c != Column::Custom) + .map(|c| c.to_pb_column(&self.tr)) + .collect(); + columns.sort_by(|c1, c2| c1.label.cmp(&c2.label)); + pb::BrowserColumns { columns } + } + pub fn browser_row_for_id(&mut self, id: i64) -> Result { let notes_mode = self.get_bool(BoolKey::BrowserTableShowNotesMode); let columns = if notes_mode { From 769b5ac8338ca7c9df8927bf2b9f6677d3355f4f Mon Sep 17 00:00:00 2001 From: RumovZ Date: Fri, 9 Apr 2021 22:51:18 +0200 Subject: [PATCH 22/32] Remove superfluous muts --- rslib/src/browser_table.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index 387c1ae23..a74a03247 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -350,7 +350,7 @@ impl RowContext { }) } - fn browser_row(&mut self, columns: &[Column]) -> Result { + fn browser_row(&self, columns: &[Column]) -> Result { Ok(Row { cells: columns .iter() @@ -361,14 +361,14 @@ impl RowContext { }) } - fn get_cell(&mut self, column: Column) -> Result { + fn get_cell(&self, column: Column) -> Result { Ok(Cell { text: self.get_cell_text(column)?, is_rtl: self.get_is_rtl(column), }) } - fn get_cell_text(&mut self, column: Column) -> Result { + fn get_cell_text(&self, column: Column) -> Result { Ok(match column { Column::Question => self.question_str(), Column::Answer => self.answer_str(), @@ -437,7 +437,7 @@ impl RowContext { .to_string() } - fn due_str(&mut self) -> String { + fn due_str(&self) -> String { if self.notes_mode { self.note_due_str() } else { @@ -445,7 +445,7 @@ impl RowContext { } } - fn card_due_str(&mut self) -> String { + fn card_due_str(&self) -> String { let due = if self.cards[0].is_filtered_deck() { self.tr.browsing_filtered() } else if self.cards[0].is_new_type_or_queue() { @@ -524,7 +524,7 @@ impl RowContext { .date_string() } - fn deck_str(&mut self) -> String { + fn deck_str(&self) -> String { if self.notes_mode { let decks = self.cards.iter().map(|c| c.deck_id).unique().count(); if decks > 1 { From d7f7deafd400744152d62d2e96419f41bbd8756b Mon Sep 17 00:00:00 2001 From: RumovZ Date: Fri, 9 Apr 2021 22:53:02 +0200 Subject: [PATCH 23/32] Store active browser columns in col state --- pylib/anki/collection.py | 8 ++++---- rslib/backend.proto | 3 +-- rslib/src/backend/search/mod.rs | 21 +++++++++------------ rslib/src/browser_table.rs | 13 ++++++------- rslib/src/collection.rs | 2 ++ rslib/src/config/mod.rs | 19 ------------------- 6 files changed, 22 insertions(+), 44 deletions(-) diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 6311dbe8f..fce008e3a 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -717,24 +717,24 @@ class Collection: columns = self.get_config( "activeCols", ["noteFld", "template", "cardDue", "deck"] ) - self._backend.set_desktop_browser_card_columns(columns) + self._backend.set_active_browser_columns(columns) return columns def set_browser_card_columns(self, columns: List[str]) -> None: self.set_config("activeCols", columns) - self._backend.set_desktop_browser_card_columns(columns) + self._backend.set_active_browser_columns(columns) def load_browser_note_columns(self) -> List[str]: """Return the stored note column names and ensure the backend columns are set and in sync.""" columns = self.get_config( "activeNoteCols", ["noteFld", "note", "noteCards", "noteTags"] ) - self._backend.set_desktop_browser_note_columns(columns) + self._backend.set_active_browser_columns(columns) return columns def set_browser_note_columns(self, columns: List[str]) -> None: self.set_config("activeNoteCols", columns) - self._backend.set_desktop_browser_note_columns(columns) + self._backend.set_active_browser_columns(columns) # Config ########################################################################## diff --git a/rslib/backend.proto b/rslib/backend.proto index afa6ab58f..f36ac6550 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -250,8 +250,7 @@ service SearchService { rpc FindAndReplace(FindAndReplaceIn) returns (OpChangesWithCount); rpc AllBrowserColumns(Empty) returns (BrowserColumns); rpc BrowserRowForId(Int64) returns (BrowserRow); - rpc SetDesktopBrowserCardColumns(StringList) returns (Empty); - rpc SetDesktopBrowserNoteColumns(StringList) returns (Empty); + rpc SetActiveBrowserColumns(StringList) returns (Empty); } service StatsService { diff --git a/rslib/src/backend/search/mod.rs b/rslib/src/backend/search/mod.rs index 189832a1b..23ff04c95 100644 --- a/rslib/src/backend/search/mod.rs +++ b/rslib/src/backend/search/mod.rs @@ -4,8 +4,7 @@ mod browser_table; mod search_node; -use std::convert::TryInto; -use std::str::FromStr; +use std::{convert::TryInto, str::FromStr, sync::Arc}; use super::Backend; use crate::{ @@ -92,19 +91,17 @@ impl SearchService for Backend { self.with_col(|col| Ok(col.all_browser_columns())) } + fn set_active_browser_columns(&self, input: pb::StringList) -> Result { + self.with_col(|col| { + col.state.active_browser_columns = Some(Arc::new(input.into())); + Ok(()) + }) + .map(Into::into) + } + fn browser_row_for_id(&self, input: pb::Int64) -> Result { self.with_col(|col| col.browser_row_for_id(input.val).map(Into::into)) } - - fn set_desktop_browser_card_columns(&self, input: pb::StringList) -> Result { - self.with_col(|col| col.set_desktop_browser_card_columns(input.into()))?; - Ok(().into()) - } - - fn set_desktop_browser_note_columns(&self, input: pb::StringList) -> Result { - self.with_col(|col| col.set_desktop_browser_note_columns(input.into()))?; - Ok(().into()) - } } impl From> for SortMode { diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index a74a03247..ef4ef4874 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -246,13 +246,12 @@ impl Collection { pub fn browser_row_for_id(&mut self, id: i64) -> Result { let notes_mode = self.get_bool(BoolKey::BrowserTableShowNotesMode); - let columns = if notes_mode { - self.get_desktop_browser_note_columns() - .ok_or_else(|| AnkiError::invalid_input("Note columns not set."))? - } else { - self.get_desktop_browser_card_columns() - .ok_or_else(|| AnkiError::invalid_input("Card columns not set."))? - }; + let columns = Arc::clone( + self.state + .active_browser_columns + .as_ref() + .ok_or_else(|| AnkiError::invalid_input("Active browser columns not set."))?, + ); RowContext::new(self, id, notes_mode, card_render_required(&columns))?.browser_row(&columns) } diff --git a/rslib/src/collection.rs b/rslib/src/collection.rs index 6e8348c9d..a2bc09ce7 100644 --- a/rslib/src/collection.rs +++ b/rslib/src/collection.rs @@ -3,6 +3,7 @@ use crate::types::Usn; use crate::{ + browser_table, decks::{Deck, DeckId}, notetype::{Notetype, NotetypeId}, prelude::*, @@ -66,6 +67,7 @@ pub struct CollectionState { pub(crate) deck_cache: HashMap>, pub(crate) scheduler_info: Option, pub(crate) card_queues: Option, + pub(crate) active_browser_columns: Option>>, /// True if legacy Python code has executed SQL that has modified the /// database, requiring modification time to be bumped. pub(crate) modified_by_dbproxy: bool, diff --git a/rslib/src/config/mod.rs b/rslib/src/config/mod.rs index e26239280..d908a3a16 100644 --- a/rslib/src/config/mod.rs +++ b/rslib/src/config/mod.rs @@ -64,9 +64,6 @@ pub(crate) enum ConfigKey { NextNewCardPosition, #[strum(to_string = "schedVer")] SchedulerVersion, - - DesktopBrowserCardColumns, - DesktopBrowserNoteColumns, } #[derive(PartialEq, Serialize_repr, Deserialize_repr, Clone, Copy, Debug)] @@ -140,22 +137,6 @@ impl Collection { .unwrap_or(Column::NoteCreation) } - pub(crate) fn get_desktop_browser_card_columns(&self) -> Option> { - self.get_config_optional(ConfigKey::DesktopBrowserCardColumns) - } - - pub(crate) fn set_desktop_browser_card_columns(&mut self, columns: Vec) -> Result<()> { - self.set_config(ConfigKey::DesktopBrowserCardColumns, &columns) - } - - pub(crate) fn get_desktop_browser_note_columns(&self) -> Option> { - self.get_config_optional(ConfigKey::DesktopBrowserNoteColumns) - } - - pub(crate) fn set_desktop_browser_note_columns(&mut self, columns: Vec) -> Result<()> { - self.set_config(ConfigKey::DesktopBrowserNoteColumns, &columns) - } - pub(crate) fn get_creation_utc_offset(&self) -> Option { self.get_config_optional(ConfigKey::CreationOffset) } From b723159b3b7d2da4f241379810716ecb607f43b5 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sat, 10 Apr 2021 09:13:48 +0200 Subject: [PATCH 24/32] Remove unused Serialize --- rslib/src/browser_table.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index ef4ef4874..3c3a40059 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use itertools::Itertools; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use strum::{Display, EnumIter, EnumString, IntoEnumIterator}; use crate::error::{AnkiError, Result}; @@ -23,7 +23,7 @@ use crate::{ timestamp::{TimestampMillis, TimestampSecs}, }; -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy, Display, EnumIter, EnumString)] +#[derive(Deserialize, Debug, PartialEq, Clone, Copy, Display, EnumIter, EnumString)] #[serde(rename_all = "camelCase")] #[strum(serialize_all = "camelCase")] pub enum Column { From 48b70873cb0d94cbb0e5ec95c2a5a93d4f813b51 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sat, 10 Apr 2021 09:14:20 +0200 Subject: [PATCH 25/32] Rename column label fields --- qt/aqt/table.py | 8 +++++--- rslib/backend.proto | 4 ++-- rslib/src/backend/search/browser_table.rs | 4 ++-- rslib/src/browser_table.rs | 8 ++++---- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/qt/aqt/table.py b/qt/aqt/table.py index f8a83c18c..3dff0ce48 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -547,7 +547,9 @@ class ItemState(ABC): return self._active_columns[index] def column_label(self, column: Column) -> str: - return column.notes_label if self.is_notes_mode() else column.label + return ( + column.notes_mode_label if self.is_notes_mode() else column.cards_mode_label + ) # Columns and sorting @@ -1083,8 +1085,8 @@ def addon_column_fillin(key: str) -> Column: """ return Column( key=key, - label=tr.browsing_addon(), - notes_label=tr.browsing_addon(), + cards_mode_label=tr.browsing_addon(), + notes_mode_label=tr.browsing_addon(), sorting=Columns.SORTING_NONE, uses_cell_font=False, alignment=Columns.ALIGNMENT_CENTER, diff --git a/rslib/backend.proto b/rslib/backend.proto index f36ac6550..8a5400a67 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -1043,8 +1043,8 @@ message BrowserColumns { } message Column { string key = 1; - string label = 2; - string notes_label = 3; + string cards_mode_label = 2; + string notes_mode_label = 3; Sorting sorting = 4; bool uses_cell_font = 5; Alignment alignment = 6; diff --git a/rslib/src/backend/search/browser_table.rs b/rslib/src/backend/search/browser_table.rs index dcafc1c1b..94a7ef193 100644 --- a/rslib/src/backend/search/browser_table.rs +++ b/rslib/src/backend/search/browser_table.rs @@ -9,8 +9,8 @@ impl browser_table::Column { pub fn to_pb_column(self, i18n: &I18n) -> pb::browser_columns::Column { pb::browser_columns::Column { key: self.to_string(), - label: self.localized_label(i18n), - notes_label: self.localized_notes_label(i18n), + cards_mode_label: self.cards_mode_label(i18n), + notes_mode_label: self.notes_mode_label(i18n), sorting: self.sorting() as i32, uses_cell_font: self.uses_cell_font(), alignment: self.alignment() as i32, diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index 3c3a40059..ff68e733a 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -172,7 +172,7 @@ impl Note { } impl Column { - pub fn localized_label(self, i18n: &I18n) -> String { + pub fn cards_mode_label(self, i18n: &I18n) -> String { match self { Self::Answer => i18n.browsing_answer(), Self::CardMod => i18n.search_card_modified(), @@ -194,14 +194,14 @@ impl Column { .into() } - pub fn localized_notes_label(self, i18n: &I18n) -> String { + pub fn notes_mode_label(self, i18n: &I18n) -> String { match self { Self::CardMod => i18n.search_card_modified(), Self::Cards => i18n.editing_cards(), Self::Ease => i18n.browsing_average_ease(), Self::Interval => i18n.browsing_average_interval(), Self::Reps => i18n.scheduling_reviews(), - _ => return self.localized_label(i18n), + _ => return self.cards_mode_label(i18n), } .into() } @@ -240,7 +240,7 @@ impl Collection { .filter(|&c| c != Column::Custom) .map(|c| c.to_pb_column(&self.tr)) .collect(); - columns.sort_by(|c1, c2| c1.label.cmp(&c2.label)); + columns.sort_by(|c1, c2| c1.cards_mode_label.cmp(&c2.cards_mode_label)); pb::BrowserColumns { columns } } From 331df75b72c0f3a983474c4ddf4a0d2d61c9c50d Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sat, 10 Apr 2021 09:49:29 +0200 Subject: [PATCH 26/32] Remove internal clones of pb BrowserRow structs --- rslib/src/backend/search/browser_table.rs | 34 ------------- rslib/src/browser_table.rs | 59 ++++++----------------- 2 files changed, 16 insertions(+), 77 deletions(-) diff --git a/rslib/src/backend/search/browser_table.rs b/rslib/src/backend/search/browser_table.rs index 94a7ef193..7bcd74690 100644 --- a/rslib/src/backend/search/browser_table.rs +++ b/rslib/src/backend/search/browser_table.rs @@ -27,37 +27,3 @@ impl From for Vec { .collect() } } - -impl From for pb::BrowserRow { - fn from(row: browser_table::Row) -> Self { - pb::BrowserRow { - cells: row.cells.into_iter().map(Into::into).collect(), - color: row.color.into(), - font_name: row.font.name, - font_size: row.font.size, - } - } -} - -impl From for pb::browser_row::Cell { - fn from(cell: browser_table::Cell) -> Self { - pb::browser_row::Cell { - text: cell.text, - is_rtl: cell.is_rtl, - } - } -} - -impl From for i32 { - fn from(color: browser_table::Color) -> Self { - match color { - browser_table::Color::Default => pb::browser_row::Color::Default as i32, - browser_table::Color::Marked => pb::browser_row::Color::Marked as i32, - browser_table::Color::Suspended => pb::browser_row::Color::Suspended as i32, - browser_table::Color::FlagRed => pb::browser_row::Color::FlagRed as i32, - browser_table::Color::FlagOrange => pb::browser_row::Color::FlagOrange as i32, - browser_table::Color::FlagGreen => pb::browser_row::Color::FlagGreen as i32, - browser_table::Color::FlagBlue => pb::browser_row::Color::FlagBlue as i32, - } - } -} diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index ff68e733a..63ad9787d 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -73,36 +73,6 @@ impl Default for Column { } } -#[derive(Debug, PartialEq)] -pub struct Row { - pub cells: Vec, - pub color: Color, - pub font: Font, -} - -#[derive(Debug, PartialEq)] -pub struct Cell { - pub text: String, - pub is_rtl: bool, -} - -#[derive(Debug, PartialEq)] -pub enum Color { - Default, - Marked, - Suspended, - FlagRed, - FlagOrange, - FlagGreen, - FlagBlue, -} - -#[derive(Debug, PartialEq)] -pub struct Font { - pub name: String, - pub size: u32, -} - struct RowContext { notes_mode: bool, cards: Vec, @@ -244,7 +214,7 @@ impl Collection { pb::BrowserColumns { columns } } - pub fn browser_row_for_id(&mut self, id: i64) -> Result { + pub fn browser_row_for_id(&mut self, id: i64) -> Result { let notes_mode = self.get_bool(BoolKey::BrowserTableShowNotesMode); let columns = Arc::clone( self.state @@ -349,19 +319,20 @@ impl RowContext { }) } - fn browser_row(&self, columns: &[Column]) -> Result { - Ok(Row { + fn browser_row(&self, columns: &[Column]) -> Result { + Ok(pb::BrowserRow { cells: columns .iter() .map(|&column| self.get_cell(column)) .collect::>()?, - color: self.get_row_color(), - font: self.get_row_font()?, + color: self.get_row_color() as i32, + font_name: self.get_row_font_name()?, + font_size: self.get_row_font_size()?, }) } - fn get_cell(&self, column: Column) -> Result { - Ok(Cell { + fn get_cell(&self, column: Column) -> Result { + Ok(pb::browser_row::Cell { text: self.get_cell_text(column)?, is_rtl: self.get_is_rtl(column), }) @@ -554,14 +525,16 @@ impl RowContext { html_to_text_line(&self.render_context.as_ref().unwrap().question).to_string() } - fn get_row_font(&self) -> Result { - Ok(Font { - name: self.template()?.config.browser_font_name.to_owned(), - size: self.template()?.config.browser_font_size, - }) + fn get_row_font_name(&self) -> Result { + Ok(self.template()?.config.browser_font_name.to_owned()) } - fn get_row_color(&self) -> Color { + fn get_row_font_size(&self) -> Result { + Ok(self.template()?.config.browser_font_size) + } + + fn get_row_color(&self) -> pb::browser_row::Color { + use pb::browser_row::Color; if self.notes_mode { if self.note.is_marked() { Color::Marked From 5982a777aa47a0fb56378acc50758f1cdaa3dc82 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sat, 10 Apr 2021 10:14:41 +0200 Subject: [PATCH 27/32] Rename SearchItems to ReturnItemtype --- rslib/src/search/mod.rs | 80 ++++++++++++++++++++--------------- rslib/src/search/sqlwriter.rs | 18 ++++---- 2 files changed, 54 insertions(+), 44 deletions(-) diff --git a/rslib/src/search/mod.rs b/rslib/src/search/mod.rs index 96dc5564d..f8ac6b614 100644 --- a/rslib/src/search/mod.rs +++ b/rslib/src/search/mod.rs @@ -20,7 +20,7 @@ use crate::{ use sqlwriter::{RequiredTable, SqlWriter}; #[derive(Debug, PartialEq, Clone, Copy)] -pub enum SearchItems { +pub enum ReturnItemType { Cards, Notes, } @@ -33,27 +33,27 @@ pub enum SortMode { Custom(String), } -pub trait AsSearchItems { - fn as_search_items() -> SearchItems; +pub trait AsReturnItemType { + fn as_return_item_type() -> ReturnItemType; } -impl AsSearchItems for CardId { - fn as_search_items() -> SearchItems { - SearchItems::Cards +impl AsReturnItemType for CardId { + fn as_return_item_type() -> ReturnItemType { + ReturnItemType::Cards } } -impl AsSearchItems for NoteId { - fn as_search_items() -> SearchItems { - SearchItems::Notes +impl AsReturnItemType for NoteId { + fn as_return_item_type() -> ReturnItemType { + ReturnItemType::Notes } } -impl SearchItems { +impl ReturnItemType { fn required_table(&self) -> RequiredTable { match self { - SearchItems::Cards => RequiredTable::Cards, - SearchItems::Notes => RequiredTable::Notes, + ReturnItemType::Cards => RequiredTable::Cards, + ReturnItemType::Notes => RequiredTable::Notes, } } } @@ -96,15 +96,15 @@ impl Column { impl Collection { pub fn search(&mut self, search: &str, mut mode: SortMode) -> Result> where - T: FromSql + AsSearchItems, + T: FromSql + AsReturnItemType, { - let items = T::as_search_items(); + let item_type = T::as_return_item_type(); let top_node = Node::Group(parse(search)?); - self.resolve_config_sort(items, &mut mode); - let writer = SqlWriter::new(self, items); + self.resolve_config_sort(item_type, &mut mode); + let writer = SqlWriter::new(self, item_type); let (mut sql, args) = writer.build_query(&top_node, mode.required_table())?; - self.add_order(&mut sql, items, mode)?; + self.add_order(&mut sql, item_type, mode)?; let mut stmt = self.storage.db.prepare(&sql)?; let ids: Vec<_> = stmt @@ -122,14 +122,19 @@ impl Collection { self.search(search, SortMode::NoOrder) } - fn add_order(&mut self, sql: &mut String, items: SearchItems, mode: SortMode) -> Result<()> { + fn add_order( + &mut self, + sql: &mut String, + item_type: ReturnItemType, + mode: SortMode, + ) -> Result<()> { match mode { SortMode::NoOrder => (), SortMode::FromConfig => unreachable!(), SortMode::Builtin { column, reverse } => { - prepare_sort(self, column, items)?; + prepare_sort(self, column, item_type)?; sql.push_str(" order by "); - write_order(sql, items, column, reverse)?; + write_order(sql, item_type, column, reverse)?; } SortMode::Custom(order_clause) => { sql.push_str(" order by "); @@ -148,11 +153,11 @@ impl Collection { mode: SortMode, ) -> Result { let top_node = Node::Group(parse(search)?); - let writer = SqlWriter::new(self, SearchItems::Cards); + let writer = SqlWriter::new(self, ReturnItemType::Cards); let want_order = mode != SortMode::NoOrder; let (mut sql, args) = writer.build_query(&top_node, mode.required_table())?; - self.add_order(&mut sql, SearchItems::Cards, mode)?; + self.add_order(&mut sql, ReturnItemType::Cards, mode)?; if want_order { self.storage @@ -170,14 +175,14 @@ impl Collection { } /// If the sort mode is based on a config setting, look it up. - fn resolve_config_sort(&self, items: SearchItems, mode: &mut SortMode) { + fn resolve_config_sort(&self, item_type: ReturnItemType, mode: &mut SortMode) { if mode == &SortMode::FromConfig { - *mode = match items { - SearchItems::Cards => SortMode::Builtin { + *mode = match item_type { + ReturnItemType::Cards => SortMode::Builtin { column: self.get_browser_sort_column(), reverse: self.get_bool(BoolKey::BrowserSortBackwards), }, - SearchItems::Notes => SortMode::Builtin { + ReturnItemType::Notes => SortMode::Builtin { column: self.get_browser_note_sort_column(), reverse: self.get_bool(BoolKey::BrowserNoteSortBackwards), }, @@ -187,15 +192,20 @@ impl Collection { } /// Add the order clause to the sql. -fn write_order(sql: &mut String, items: SearchItems, column: Column, reverse: bool) -> Result<()> { - let order = match items { - SearchItems::Cards => card_order_from_sort_column(column), - SearchItems::Notes => note_order_from_sort_column(column), +fn write_order( + sql: &mut String, + item_type: ReturnItemType, + column: Column, + reverse: bool, +) -> Result<()> { + let order = match item_type { + ReturnItemType::Cards => card_order_from_sort_column(column), + ReturnItemType::Notes => note_order_from_sort_column(column), }; if order.is_empty() { return Err(AnkiError::invalid_input(format!( "Can't sort {:?} by {:?}.", - items, column + item_type, column ))); } if reverse { @@ -254,15 +264,15 @@ fn note_order_from_sort_column(column: Column) -> Cow<'static, str> { } } -fn prepare_sort(col: &mut Collection, column: Column, items: SearchItems) -> Result<()> { - let sql = match items { - SearchItems::Cards => match column { +fn prepare_sort(col: &mut Collection, column: Column, item_type: ReturnItemType) -> Result<()> { + let sql = match item_type { + ReturnItemType::Cards => match column { Column::Cards => include_str!("template_order.sql"), Column::Deck => include_str!("deck_order.sql"), Column::Notetype => include_str!("notetype_order.sql"), _ => return Ok(()), }, - SearchItems::Notes => match column { + ReturnItemType::Notes => match column { Column::Cards => include_str!("note_cards_order.sql"), Column::CardMod => include_str!("card_mod_order.sql"), Column::Deck => include_str!("note_decks_order.sql"), diff --git a/rslib/src/search/sqlwriter.rs b/rslib/src/search/sqlwriter.rs index 3de6cbc41..652adc0c7 100644 --- a/rslib/src/search/sqlwriter.rs +++ b/rslib/src/search/sqlwriter.rs @@ -3,7 +3,7 @@ use super::{ parser::{Node, PropertyKind, RatingKind, SearchNode, StateKind, TemplateKind}, - SearchItems, + ReturnItemType, }; use crate::{ card::{CardQueue, CardType}, @@ -25,24 +25,24 @@ use std::{borrow::Cow, fmt::Write}; pub(crate) struct SqlWriter<'a> { col: &'a mut Collection, sql: String, - items: SearchItems, + item_type: ReturnItemType, args: Vec, normalize_note_text: bool, table: RequiredTable, } impl SqlWriter<'_> { - pub(crate) fn new(col: &mut Collection, items: SearchItems) -> SqlWriter<'_> { + pub(crate) fn new(col: &mut Collection, item_type: ReturnItemType) -> SqlWriter<'_> { let normalize_note_text = col.get_bool(BoolKey::NormalizeNoteText); let sql = String::new(); let args = vec![]; SqlWriter { col, sql, - items, + item_type, args, normalize_note_text, - table: items.required_table(), + table: item_type.required_table(), } } @@ -61,9 +61,9 @@ impl SqlWriter<'_> { let sql = match self.table { RequiredTable::Cards => "select c.id from cards c where ", RequiredTable::Notes => "select n.id from notes n where ", - _ => match self.items { - SearchItems::Cards => "select c.id from cards c, notes n where c.nid=n.id and ", - SearchItems::Notes => { + _ => match self.item_type { + ReturnItemType::Cards => "select c.id from cards c, notes n where c.nid=n.id and ", + ReturnItemType::Notes => { "select distinct n.id from cards c, notes n where c.nid=n.id and " } }, @@ -588,7 +588,7 @@ mod test { // shortcut fn s(req: &mut Collection, search: &str) -> (String, Vec) { let node = Node::Group(parse(search).unwrap()); - let mut writer = SqlWriter::new(req, SearchItems::Cards); + let mut writer = SqlWriter::new(req, ReturnItemType::Cards); writer.table = RequiredTable::Notes.combine(node.required_table()); writer.write_node_to_sql(&node).unwrap(); (writer.sql, writer.args) From 801f52df402442c95335bf814e80bd7b2a1a266b Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sat, 10 Apr 2021 11:13:42 +0200 Subject: [PATCH 28/32] Remove from_config variant in pb SortOrder Instead, fetch the config order on the frontend and pass a builtin variant into the backend. That makes the following unnecessary: * Resolving the config sort in search/mod.rs * Deserializing the Column enum * Config accessors for the sort columns --- pylib/anki/collection.py | 54 +++++++++++++++++++++------------ pylib/tests/test_find.py | 5 ++- rslib/backend.proto | 7 ++--- rslib/src/backend/search/mod.rs | 3 +- rslib/src/browser_table.rs | 15 +-------- rslib/src/config/mod.rs | 17 ----------- rslib/src/search/mod.rs | 26 ++-------------- 7 files changed, 45 insertions(+), 82 deletions(-) diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index fce008e3a..5cbf0ef75 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -528,7 +528,7 @@ class Collection: The reverse argument only applies when a BrowserColumns.Column is provided; otherwise the collection config defines whether reverse is set or not. """ - mode = _build_sort_mode(order, reverse) + mode = self._build_sort_mode(order, reverse, False) return cast( Sequence[CardId], self._backend.search_cards(search=query, order=mode) ) @@ -544,11 +544,38 @@ class Collection: To programmatically construct a search string, see .build_search_string(). The order parameter is documented in .find_cards(). """ - mode = _build_sort_mode(order, reverse) + mode = self._build_sort_mode(order, reverse, True) return cast( Sequence[NoteId], self._backend.search_notes(search=query, order=mode) ) + def _build_sort_mode( + self, + order: Union[bool, str, BrowserColumns.Column], + reverse: bool, + finding_notes: bool, + ) -> _pb.SortOrder: + if isinstance(order, str): + return _pb.SortOrder(custom=order) + if isinstance(order, bool): + if order is False: + return _pb.SortOrder(none=_pb.Empty()) + # order=True: set args to sort column and reverse from config + sort_key = "noteSortType" if finding_notes else "sortType" + order = self.get_browser_column(self.get_config(sort_key)) + reverse_key = ( + Config.Bool.BROWSER_NOTE_SORT_BACKWARDS + if finding_notes + else Config.Bool.BROWSER_SORT_BACKWARDS + ) + reverse = self.get_config_bool(reverse_key) + if isinstance(order, BrowserColumns.Column): + if order.sorting != BrowserColumns.SORTING_NONE: + return _pb.SortOrder( + builtin=_pb.SortOrder.Builtin(column=order.key, reverse=reverse) + ) + raise InvalidInput(f"{order} is not a valid sort order.") + def find_and_replace( self, *, @@ -701,6 +728,12 @@ class Collection: def all_browser_columns(self) -> Sequence[BrowserColumns.Column]: return self._backend.all_browser_columns() + def get_browser_column(self, key: str) -> Optional[BrowserColumns.Column]: + for column in self._backend.all_browser_columns(): + if column.key == key: + return column + return None + def browser_row_for_id( self, id_: int ) -> Tuple[Generator[Tuple[str, bool], None, None], BrowserRow.Color.V, str, int]: @@ -1121,20 +1154,3 @@ class _ReviewsUndo: _UndoInfo = Union[_ReviewsUndo, LegacyCheckpoint, None] - - -def _build_sort_mode( - order: Union[bool, str, BrowserColumns.Column], - reverse: bool, -) -> _pb.SortOrder: - if isinstance(order, str): - return _pb.SortOrder(custom=order) - if isinstance(order, bool): - if order is True: - return _pb.SortOrder(from_config=_pb.Empty()) - return _pb.SortOrder(none=_pb.Empty()) - if order.sorting != BrowserColumns.SORTING_NONE: - return _pb.SortOrder( - builtin=_pb.SortOrder.Builtin(column=order.key, reverse=reverse) - ) - raise InvalidInput(f"{order} is not a valid sort order.") diff --git a/pylib/tests/test_find.py b/pylib/tests/test_find.py index 3580c9c3e..56ada7e5f 100644 --- a/pylib/tests/test_find.py +++ b/pylib/tests/test_find.py @@ -124,13 +124,12 @@ def test_findCards(): col.set_config_bool(Config.Bool.BROWSER_SORT_BACKWARDS, True) col.flush() assert col.findCards("", order=True)[0] in latestCardIds - sort_columns = dict(((c.key, c) for c in col.all_browser_columns())) assert ( - col.find_cards("", order=sort_columns["cardDue"], reverse=False)[0] + col.find_cards("", order=col.get_browser_column("cardDue"), reverse=False)[0] == firstCardId ) assert ( - col.find_cards("", order=sort_columns["cardDue"], reverse=True)[0] + col.find_cards("", order=col.get_browser_column("cardDue"), reverse=True)[0] != firstCardId ) # model diff --git a/rslib/backend.proto b/rslib/backend.proto index 8a5400a67..22574daab 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -799,10 +799,9 @@ message SortOrder { bool reverse = 2; } oneof value { - Empty from_config = 1; - Empty none = 2; - string custom = 3; - Builtin builtin = 4; + Empty none = 1; + string custom = 2; + Builtin builtin = 3; } } diff --git a/rslib/src/backend/search/mod.rs b/rslib/src/backend/search/mod.rs index 23ff04c95..49a49d7f5 100644 --- a/rslib/src/backend/search/mod.rs +++ b/rslib/src/backend/search/mod.rs @@ -107,10 +107,9 @@ impl SearchService for Backend { impl From> for SortMode { fn from(order: Option) -> Self { use pb::sort_order::Value as V; - match order.unwrap_or(V::FromConfig(pb::Empty {})) { + match order.unwrap_or(V::None(pb::Empty {})) { V::None(_) => SortMode::NoOrder, V::Custom(s) => SortMode::Custom(s), - V::FromConfig(_) => SortMode::FromConfig, V::Builtin(b) => SortMode::Builtin { column: Column::from_str(&b.column).unwrap_or_default(), reverse: b.reverse, diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index 63ad9787d..d0f2dded7 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -4,7 +4,6 @@ use std::sync::Arc; use itertools::Itertools; -use serde::Deserialize; use strum::{Display, EnumIter, EnumString, IntoEnumIterator}; use crate::error::{AnkiError, Result}; @@ -23,46 +22,34 @@ use crate::{ timestamp::{TimestampMillis, TimestampSecs}, }; -#[derive(Deserialize, Debug, PartialEq, Clone, Copy, Display, EnumIter, EnumString)] -#[serde(rename_all = "camelCase")] +#[derive(Debug, PartialEq, Clone, Copy, Display, EnumIter, EnumString)] #[strum(serialize_all = "camelCase")] pub enum Column { - #[serde(rename = "")] #[strum(serialize = "")] Custom, Answer, CardMod, - #[serde(rename = "template")] #[strum(serialize = "template")] Cards, Deck, - #[serde(rename = "cardDue")] #[strum(serialize = "cardDue")] Due, - #[serde(rename = "cardEase")] #[strum(serialize = "cardEase")] Ease, - #[serde(rename = "cardLapses")] #[strum(serialize = "cardLapses")] Lapses, - #[serde(rename = "cardIvl")] #[strum(serialize = "cardIvl")] Interval, - #[serde(rename = "noteCrt")] #[strum(serialize = "noteCrt")] NoteCreation, NoteMod, - #[serde(rename = "note")] #[strum(serialize = "note")] Notetype, Question, - #[serde(rename = "cardReps")] #[strum(serialize = "cardReps")] Reps, - #[serde(rename = "noteFld")] #[strum(serialize = "noteFld")] SortField, - #[serde(rename = "noteTags")] #[strum(serialize = "noteTags")] Tags, } diff --git a/rslib/src/config/mod.rs b/rslib/src/config/mod.rs index d908a3a16..3db4923e9 100644 --- a/rslib/src/config/mod.rs +++ b/rslib/src/config/mod.rs @@ -9,7 +9,6 @@ mod string; pub(crate) mod undo; pub use self::{bool::BoolKey, string::StringKey}; -use crate::browser_table::Column; use crate::prelude::*; use serde::{de::DeserializeOwned, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -46,10 +45,6 @@ pub(crate) enum ConfigKey { #[strum(to_string = "timeLim")] AnswerTimeLimitSecs, - #[strum(to_string = "sortType")] - BrowserSortColumn, - #[strum(to_string = "noteSortType")] - BrowserNoteSortColumn, #[strum(to_string = "curDeck")] CurrentDeckId, #[strum(to_string = "curModel")] @@ -127,16 +122,6 @@ impl Collection { Ok(()) } - pub(crate) fn get_browser_sort_column(&self) -> Column { - self.get_config_optional(ConfigKey::BrowserSortColumn) - .unwrap_or(Column::NoteCreation) - } - - pub(crate) fn get_browser_note_sort_column(&self) -> Column { - self.get_config_optional(ConfigKey::BrowserNoteSortColumn) - .unwrap_or(Column::NoteCreation) - } - pub(crate) fn get_creation_utc_offset(&self) -> Option { self.get_config_optional(ConfigKey::CreationOffset) } @@ -269,7 +254,6 @@ pub(crate) enum Weekday { #[cfg(test)] mod test { - use super::*; use crate::collection::open_test_collection; use crate::decks::DeckId; @@ -277,7 +261,6 @@ mod test { fn defaults() { let col = open_test_collection(); assert_eq!(col.get_current_deck_id(), DeckId(1)); - assert_eq!(col.get_browser_sort_column(), Column::SortField); } #[test] diff --git a/rslib/src/search/mod.rs b/rslib/src/search/mod.rs index f8ac6b614..367ad6da1 100644 --- a/rslib/src/search/mod.rs +++ b/rslib/src/search/mod.rs @@ -14,8 +14,8 @@ use rusqlite::types::FromSql; use std::borrow::Cow; use crate::{ - browser_table::Column, card::CardId, card::CardType, collection::Collection, config::BoolKey, - error::Result, notes::NoteId, prelude::AnkiError, search::parser::parse, + browser_table::Column, card::CardId, card::CardType, collection::Collection, error::Result, + notes::NoteId, prelude::AnkiError, search::parser::parse, }; use sqlwriter::{RequiredTable, SqlWriter}; @@ -28,7 +28,6 @@ pub enum ReturnItemType { #[derive(Debug, PartialEq, Clone)] pub enum SortMode { NoOrder, - FromConfig, Builtin { column: Column, reverse: bool }, Custom(String), } @@ -62,7 +61,6 @@ impl SortMode { fn required_table(&self) -> RequiredTable { match self { SortMode::NoOrder => RequiredTable::CardsOrNotes, - SortMode::FromConfig => unreachable!(), SortMode::Builtin { column, .. } => column.required_table(), SortMode::Custom(ref text) => { if text.contains("n.") { @@ -94,13 +92,12 @@ impl Column { } impl Collection { - pub fn search(&mut self, search: &str, mut mode: SortMode) -> Result> + pub fn search(&mut self, search: &str, mode: SortMode) -> Result> where T: FromSql + AsReturnItemType, { let item_type = T::as_return_item_type(); let top_node = Node::Group(parse(search)?); - self.resolve_config_sort(item_type, &mut mode); let writer = SqlWriter::new(self, item_type); let (mut sql, args) = writer.build_query(&top_node, mode.required_table())?; @@ -130,7 +127,6 @@ impl Collection { ) -> Result<()> { match mode { SortMode::NoOrder => (), - SortMode::FromConfig => unreachable!(), SortMode::Builtin { column, reverse } => { prepare_sort(self, column, item_type)?; sql.push_str(" order by "); @@ -173,22 +169,6 @@ impl Collection { .execute(&args) .map_err(Into::into) } - - /// If the sort mode is based on a config setting, look it up. - fn resolve_config_sort(&self, item_type: ReturnItemType, mode: &mut SortMode) { - if mode == &SortMode::FromConfig { - *mode = match item_type { - ReturnItemType::Cards => SortMode::Builtin { - column: self.get_browser_sort_column(), - reverse: self.get_bool(BoolKey::BrowserSortBackwards), - }, - ReturnItemType::Notes => SortMode::Builtin { - column: self.get_browser_note_sort_column(), - reverse: self.get_bool(BoolKey::BrowserNoteSortBackwards), - }, - } - } - } } /// Add the order clause to the sql. From eafa2afc0d5284f49cf48fe262f49530e38f19ff Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sat, 10 Apr 2021 11:33:59 +0200 Subject: [PATCH 29/32] Resolve config sort in table model --- qt/aqt/table.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/qt/aqt/table.py b/qt/aqt/table.py index 3dff0ce48..65831ced9 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -49,7 +49,8 @@ ItemList = Union[Sequence[CardId], Sequence[NoteId]] class SearchContext: search: str browser: aqt.browser.Browser - order: Union[bool, str] = True + order: Union[bool, str, Column] = True + reverse: bool = False # if set, provided ids will be used instead of the regular search ids: Optional[Sequence[ItemId]] = None @@ -592,7 +593,9 @@ class ItemState(ABC): # Get ids @abstractmethod - def find_items(self, search: str, order: Union[bool, str]) -> Sequence[ItemId]: + def find_items( + self, search: str, order: Union[bool, str, Column], reverse: bool + ) -> Sequence[ItemId]: """Return the item ids fitting the given search and order.""" @abstractmethod @@ -662,8 +665,10 @@ class CardState(ItemState): def get_note(self, item: ItemId) -> Note: return self.get_card(item).note() - def find_items(self, search: str, order: Union[bool, str]) -> Sequence[ItemId]: - return self.col.find_cards(search, order) + def find_items( + self, search: str, order: Union[bool, str, Column], reverse: bool + ) -> Sequence[ItemId]: + return self.col.find_cards(search, order, reverse) def get_item_from_card_id(self, card: CardId) -> ItemId: return card @@ -725,8 +730,10 @@ class NoteState(ItemState): def get_note(self, item: ItemId) -> Note: return self.col.get_note(NoteId(item)) - def find_items(self, search: str, order: Union[bool, str]) -> Sequence[ItemId]: - return self.col.find_notes(search, order) + def find_items( + self, search: str, order: Union[bool, str, Column], reverse: bool + ) -> Sequence[ItemId]: + return self.col.find_notes(search, order, reverse) def get_item_from_card_id(self, card: CardId) -> ItemId: return self.col.get_card(card).note().id @@ -969,9 +976,14 @@ class DataModel(QAbstractTableModel): def search(self, context: SearchContext) -> None: self.begin_reset() try: + if context.order is True: + context.order = self.columns[self._state.sort_column] + context.reverse = self._state.sort_backwards gui_hooks.browser_will_search(context) if context.ids is None: - context.ids = self._state.find_items(context.search, context.order) + context.ids = self._state.find_items( + context.search, context.order, context.reverse + ) gui_hooks.browser_did_search(context) self._items = context.ids self._rows = {} From db32179a25c6fc13b518af26b02f3ef49a03a2f7 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 11 Apr 2021 10:27:43 +0200 Subject: [PATCH 30/32] Add browser_did_fetch_columns hook and some doc --- qt/aqt/table.py | 13 ++++++++++++- qt/tools/genhooks_gui.py | 14 +++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/qt/aqt/table.py b/qt/aqt/table.py index 65831ced9..4fff87fb1 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -817,16 +817,27 @@ def backend_color_to_aqt_color(color: BrowserRow.Color.V) -> Optional[Tuple[str, class DataModel(QAbstractTableModel): + """Data manager for the browser table. + + _items -- The card or note ids currently hold and corresponding to the + table's rows. + _rows -- The cached data objects to render items to rows. + columns -- The data objects of all available columns, used to define the display + of active columns and list all toggleable columns to the user. + _block_updates -- If True, serve stale content to avoid hitting the DB. + _stale_cutoff -- A threshold to decide whether a cached row has gone stale. + """ + def __init__(self, col: Collection, state: ItemState) -> None: QAbstractTableModel.__init__(self) self.col: Collection = col self.columns: Dict[str, Column] = dict( ((c.key, c) for c in self.col.all_browser_columns()) ) + gui_hooks.browser_did_fetch_columns(self.columns) self._state: ItemState = state self._items: Sequence[ItemId] = [] self._rows: Dict[int, CellRow] = {} - # serve stale content to avoid hitting the DB? self._block_updates = False self._stale_cutoff = 0.0 diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index ebcf373b1..55b244135 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -16,7 +16,7 @@ prefix = """\ from __future__ import annotations -from typing import Any, Callable, List, Sequence, Tuple, Optional, Union +from typing import Any, Callable, Dict, List, Sequence, Tuple, Optional, Union import anki import aqt @@ -423,6 +423,18 @@ hooks = [ represents. """, ), + Hook( + name="browser_did_fetch_columns", + args=["columns: Dict[str, aqt.table.Column]"], + doc="""Allows you to add custom columns to the browser. + + columns is a dictionary of data obejcts. You can add an entry with a custom + column to describe how it should be displayed in the browser or modify + existing entries. + + Every column in the dictionary will be toggleable by the user. + """, + ), # Main window states ################### # these refer to things like deckbrowser, overview and reviewer state, From d4159fef19485bdae23441bc9ec48c3b3dfed557 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 11 Apr 2021 12:28:11 +0200 Subject: [PATCH 31/32] Save separate browser mode headers --- qt/aqt/browser.py | 3 +-- qt/aqt/table.py | 23 ++++++++++++++++++----- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 0ad2a8f88..9a0c97c21 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -64,7 +64,6 @@ from aqt.utils import ( save_combo_history, save_combo_index_for_session, saveGeom, - saveHeader, saveSplitter, saveState, shortcut, @@ -228,10 +227,10 @@ class Browser(QMainWindow): def _closeWindow(self) -> None: self._cleanup_preview() self.editor.cleanup() + self.table.cleanup() saveSplitter(self.form.splitter, "editor3") saveGeom(self, "editor") saveState(self, "editor") - saveHeader(self.form.tableView.horizontalHeader(), "editor") self.teardownHooks() self.mw.maybeReset() aqt.dialogs.markClosed("Browser") diff --git a/qt/aqt/table.py b/qt/aqt/table.py index 955ca38eb..c3b18e7a8 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -35,6 +35,7 @@ from aqt.utils import ( KeyboardModifiersPressed, qtMenuShortcutWorkaround, restoreHeader, + saveHeader, showInfo, tr, ) @@ -75,6 +76,9 @@ class Table: self._setup_view() self._setup_headers() + def cleanup(self) -> None: + self._save_header() + # Public Methods ###################################################################### @@ -197,6 +201,7 @@ class Table: def toggle_state(self, is_notes_mode: bool, last_search: str) -> None: if is_notes_mode == self.is_notes_mode(): return + self._save_header() self._save_selection() self._state = self._model.toggle_state( SearchContext(search=last_search, browser=self.browser) @@ -204,8 +209,7 @@ class Table: self.col.set_config_bool( Config.Bool.BROWSER_TABLE_SHOW_NOTES_MODE, self.is_notes_mode() ) - self._set_sort_indicator() - self._set_column_sizes() + self._restore_header() self._restore_selection(self._toggled_selection) # Move cursor @@ -272,6 +276,12 @@ class Table: # this must be set post-resize or it doesn't work hh.setCascadingSectionResizes(False) + def _save_header(self) -> None: + saveHeader(self._view.horizontalHeader(), self._state.config_key_prefix) + + def _restore_header(self) -> None: + restoreHeader(self._view.horizontalHeader(), self._state.config_key_prefix) + # Setup def _setup_view(self) -> None: @@ -313,14 +323,14 @@ class Table: if not isWin: vh.hide() hh.show() - restoreHeader(hh, "editor") hh.setHighlightSections(False) hh.setMinimumSectionSize(50) hh.setSectionsMovable(True) - self._set_column_sizes() hh.setContextMenuPolicy(Qt.CustomContextMenu) - qconnect(hh.customContextMenuRequested, self._on_header_context) + self._restore_header() + self._set_column_sizes() self._set_sort_indicator() + qconnect(hh.customContextMenuRequested, self._on_header_context) qconnect(hh.sortIndicatorChanged, self._on_sort_column_changed) qconnect(hh.sectionMoved, self._on_column_moved) @@ -524,6 +534,7 @@ class Table: class ItemState(ABC): + config_key_prefix: str _active_columns: List[str] _sort_column: str _sort_backwards: bool @@ -625,6 +636,7 @@ class ItemState(ABC): class CardState(ItemState): def __init__(self, col: Collection) -> None: super().__init__(col) + self.config_key_prefix = "editor" self._active_columns = self.col.load_browser_card_columns() self._sort_column = self.col.get_config("sortType") self._sort_backwards = self.col.get_config_bool( @@ -690,6 +702,7 @@ class CardState(ItemState): class NoteState(ItemState): def __init__(self, col: Collection) -> None: super().__init__(col) + self.config_key_prefix = "editorNotesMode" self._active_columns = self.col.load_browser_note_columns() self._sort_column = self.col.get_config("noteSortType") self._sort_backwards = self.col.get_config_bool( From 12ce63260521704774c0fe3ed8f49c91b94657b4 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 12 Apr 2021 16:02:51 +1000 Subject: [PATCH 32/32] handle missing default sort column in notes view Switching to notes mode was throwing: File "/Users/dae/Work/code/dtop/bazel-copy/runanki.runfiles/net_ankiweb_anki/qt/aqt/browser.py", line 449, in on_table_state_changed self.table.toggle_state(checked, self._lastSearchTxt) File "/Users/dae/Work/code/dtop/bazel-copy/runanki.runfiles/net_ankiweb_anki/qt/aqt/table.py", line 206, in toggle_state self._state = self._model.toggle_state( File "/Users/dae/Work/code/dtop/bazel-copy/runanki.runfiles/net_ankiweb_anki/qt/aqt/table.py", line 996, in toggle_state self.search(context) File "/Users/dae/Work/code/dtop/bazel-copy/runanki.runfiles/net_ankiweb_anki/qt/aqt/table.py", line 1005, in search context.order = self.columns[self._state.sort_column] --- qt/aqt/table.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qt/aqt/table.py b/qt/aqt/table.py index c3b18e7a8..ea19fc379 100644 --- a/qt/aqt/table.py +++ b/qt/aqt/table.py @@ -1002,7 +1002,11 @@ class DataModel(QAbstractTableModel): self.begin_reset() try: if context.order is True: - context.order = self.columns[self._state.sort_column] + try: + context.order = self.columns[self._state.sort_column] + except KeyError: + # invalid sort column in config + context.order = self.columns["noteCrt"] context.reverse = self._state.sort_backwards gui_hooks.browser_will_search(context) if context.ids is None: