Use backend column objects on frontend

This commit is contained in:
RumovZ 2021-04-06 19:47:03 +02:00
parent 11bdeb9ca4
commit a5c02910a6
2 changed files with 77 additions and 86 deletions

View file

@ -696,6 +696,16 @@ class Collection:
# Browser Table # 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( def browser_row_for_id(
self, id_: int self, id_: int
) -> Tuple[Generator[Tuple[str, bool], None, None], BrowserRow.Color.V, str, 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()) return _pb.SortOrder(none=_pb.Empty())
else: else:
return _pb.SortOrder(builtin=_pb.SortOrder.Builtin(kind=order, reverse=reverse)) 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,
)

View file

@ -5,8 +5,7 @@ from __future__ import annotations
import time import time
from abc import ABC, abstractmethod, abstractproperty from abc import ABC, abstractmethod, abstractproperty
from dataclasses import dataclass from dataclasses import dataclass, field
from operator import itemgetter
from typing import ( from typing import (
Any, Any,
Callable, Callable,
@ -346,13 +345,13 @@ class Table:
def _on_header_context(self, pos: QPoint) -> None: def _on_header_context(self, pos: QPoint) -> None:
gpos = self._view.mapToGlobal(pos) gpos = self._view.mapToGlobal(pos)
m = QMenu() m = QMenu()
for column, name in self._state.columns: for key, column in self._state.columns.items():
a = m.addAction(name) a = m.addAction(column.label)
a.setCheckable(True) 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( qconnect(
a.toggled, 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) gui_hooks.browser_header_will_show_context_menu(self.browser, m)
m.exec_(gpos) m.exec_(gpos)
@ -371,16 +370,18 @@ class Table:
if checked: if checked:
self._scroll_to_column(self._model.len_columns() - 1) 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) order = bool(order)
sort_column = self._model.active_column(index) column = self._model.column_at_section(section)
if sort_column in ("question", "answer"): if column.is_sortable:
sort_key = column.key
else:
showInfo(tr.browsing_sorting_on_this_column_is_not()) showInfo(tr.browsing_sorting_on_this_column_is_not())
sort_column = self._state.sort_column sort_key = self._state.sort_column
if self._state.sort_column != sort_column: if self._state.sort_column != sort_key:
self._state.sort_column = sort_column self._state.sort_column = sort_key
# default to descending for non-text fields # default to descending for non-text fields
if sort_column == "noteFld": if column.sorts_reversed:
order = not order order = not order
self._state.sort_backwards = order self._state.sort_backwards = order
self.browser.search() self.browser.search()
@ -519,7 +520,7 @@ class Table:
class ItemState(ABC): class ItemState(ABC):
_columns: List[Tuple[str, str]] _columns: Dict[str, Column]
_active_columns: List[str] _active_columns: List[str]
_sort_column: str _sort_column: str
_sort_backwards: bool _sort_backwards: bool
@ -541,12 +542,24 @@ class ItemState(ABC):
def card_ids_from_note_ids(self, items: Sequence[ItemId]) -> Sequence[CardId]: 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)}") 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 # Columns and sorting
# abstractproperty is deprecated but used due to mypy limitations # abstractproperty is deprecated but used due to mypy limitations
# (https://github.com/python/mypy/issues/1362) # (https://github.com/python/mypy/issues/1362)
@abstractproperty @abstractproperty
def columns(self) -> List[Tuple[str, str]]: def columns(self) -> Dict[str, Column]:
"""Return all for the state available columns.""" """Return all for the state available columns."""
@abstractproperty @abstractproperty
@ -615,35 +628,17 @@ class ItemState(ABC):
class CardState(ItemState): class CardState(ItemState):
def __init__(self, col: Collection) -> None: def __init__(self, col: Collection) -> None:
super().__init__(col) 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._active_columns = self.col.load_browser_card_columns()
self._sort_column = self.col.get_config("sortType") self._sort_column = self.col.get_config("sortType")
self._sort_backwards = self.col.get_config_bool( self._sort_backwards = self.col.get_config_bool(
Config.Bool.BROWSER_SORT_BACKWARDS 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 @property
def columns(self) -> List[Tuple[str, str]]: def columns(self) -> Dict[str, Column]:
return self._columns return self._columns
@property @property
@ -703,31 +698,17 @@ class CardState(ItemState):
class NoteState(ItemState): class NoteState(ItemState):
def __init__(self, col: Collection) -> None: def __init__(self, col: Collection) -> None:
super().__init__(col) 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._active_columns = self.col.load_browser_note_columns()
self._sort_column = self.col.get_config("noteSortType") self._sort_column = self.col.get_config("noteSortType")
self._sort_backwards = self.col.get_config_bool( self._sort_backwards = self.col.get_config_bool(
Config.Bool.BROWSER_NOTE_SORT_BACKWARDS 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 @property
def columns(self) -> List[Tuple[str, str]]: def columns(self) -> Dict[str, Column]:
return self._columns return self._columns
@property @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 @dataclass
class Cell: class Cell:
text: str text: str
@ -1022,8 +1013,11 @@ class DataModel(QAbstractTableModel):
# Columns # Columns
def active_column(self, index: int) -> str: def column_at(self, index: QModelIndex) -> Column:
return self._state.active_columns[index] 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]: def active_column_index(self, column: str) -> Optional[int]:
return ( return (
@ -1054,11 +1048,7 @@ class DataModel(QAbstractTableModel):
if not index.isValid(): if not index.isValid():
return QVariant() return QVariant()
if role == Qt.FontRole: if role == Qt.FontRole:
if self.active_column(index.column()) not in ( if not self.column_at(index).uses_cell_font:
"question",
"answer",
"noteFld",
):
return QVariant() return QVariant()
qfont = QFont() qfont = QFont()
row = self.get_row(index) row = self.get_row(index)
@ -1067,15 +1057,7 @@ class DataModel(QAbstractTableModel):
return qfont return qfont
if role == Qt.TextAlignmentRole: if role == Qt.TextAlignmentRole:
align: Union[Qt.AlignmentFlag, int] = Qt.AlignVCenter align: Union[Qt.AlignmentFlag, int] = Qt.AlignVCenter
if self.active_column(index.column()) not in ( if self.column_at(index).aligns_centered:
"question",
"answer",
"template",
"deck",
"noteFld",
"note",
"noteTags",
):
align |= Qt.AlignHCenter align |= Qt.AlignHCenter
return align return align
if role in (Qt.DisplayRole, Qt.ToolTipRole): if role in (Qt.DisplayRole, Qt.ToolTipRole):
@ -1085,20 +1067,8 @@ class DataModel(QAbstractTableModel):
def headerData( def headerData(
self, section: int, orientation: Qt.Orientation, role: int = 0 self, section: int, orientation: Qt.Orientation, role: int = 0
) -> Optional[str]: ) -> Optional[str]:
if orientation == Qt.Vertical: if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return None return self.column_at_section(section).label
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 return None
def flags(self, index: QModelIndex) -> Qt.ItemFlags: def flags(self, index: QModelIndex) -> Qt.ItemFlags: