mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00
Use backend column objects on frontend
This commit is contained in:
parent
11bdeb9ca4
commit
a5c02910a6
2 changed files with 77 additions and 86 deletions
|
@ -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,
|
||||||
|
)
|
||||||
|
|
142
qt/aqt/table.py
142
qt/aqt/table.py
|
@ -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,21 +1067,9 @@ 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():
|
return None
|
||||||
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
|
|
||||||
|
|
||||||
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
|
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
|
||||||
if self.get_row(index).is_deleted:
|
if self.get_row(index).is_deleted:
|
||||||
|
|
Loading…
Reference in a new issue