Merge pull request #1108 from RumovZ/more-columns
Even more browser fixes and features
This commit is contained in:
commit
8449bbe469
@ -8,6 +8,7 @@ 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-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-any-flag = Any Flag
|
||||||
browsing-average-ease = Average Ease
|
browsing-average-ease = Average Ease
|
||||||
|
browsing-average-interval = Average Interval
|
||||||
browsing-browser-appearance = Browser Appearance
|
browsing-browser-appearance = Browser Appearance
|
||||||
browsing-browser-options = Browser Options
|
browsing-browser-options = Browser Options
|
||||||
browsing-buried = Buried
|
browsing-buried = Buried
|
||||||
@ -100,6 +101,7 @@ browsing-suspended = Suspended
|
|||||||
browsing-tag-duplicates = Tag Duplicates
|
browsing-tag-duplicates = Tag Duplicates
|
||||||
browsing-tag-rename-warning-empty = You can't rename a tag that has no notes.
|
browsing-tag-rename-warning-empty = You can't rename a tag that has no notes.
|
||||||
browsing-target-field = Target field:
|
browsing-target-field = Target field:
|
||||||
|
browsing-toggle-cards-notes-mode = Toggle Cards/Notes Mode
|
||||||
browsing-toggle-mark = Toggle Mark
|
browsing-toggle-mark = Toggle Mark
|
||||||
browsing-toggle-suspend = Toggle Suspend
|
browsing-toggle-suspend = Toggle Suspend
|
||||||
browsing-treat-input-as-regular-expression = Treat input as regular expression
|
browsing-treat-input-as-regular-expression = Treat input as regular expression
|
||||||
|
@ -167,9 +167,7 @@ class Browser(QMainWindow):
|
|||||||
lambda: self.remove_tags_from_selected_notes(),
|
lambda: self.remove_tags_from_selected_notes(),
|
||||||
)
|
)
|
||||||
qconnect(f.actionClear_Unused_Tags.triggered, self.clear_unused_tags)
|
qconnect(f.actionClear_Unused_Tags.triggered, self.clear_unused_tags)
|
||||||
qconnect(
|
qconnect(f.actionToggle_Mark.triggered, self.toggle_mark_of_selected_notes)
|
||||||
f.actionToggle_Mark.triggered, lambda: self.toggle_mark_of_selected_notes()
|
|
||||||
)
|
|
||||||
qconnect(f.actionChangeModel.triggered, self.onChangeModel)
|
qconnect(f.actionChangeModel.triggered, self.onChangeModel)
|
||||||
qconnect(f.actionFindDuplicates.triggered, self.onFindDupes)
|
qconnect(f.actionFindDuplicates.triggered, self.onFindDupes)
|
||||||
qconnect(f.actionFindReplace.triggered, self.onFindReplace)
|
qconnect(f.actionFindReplace.triggered, self.onFindReplace)
|
||||||
@ -378,6 +376,8 @@ class Browser(QMainWindow):
|
|||||||
self.table.set_view(self.form.tableView)
|
self.table.set_view(self.form.tableView)
|
||||||
switch = Switch(11, tr.browsing_card_initial(), tr.browsing_note_initial())
|
switch = Switch(11, tr.browsing_card_initial(), tr.browsing_note_initial())
|
||||||
switch.setChecked(self.table.is_notes_mode())
|
switch.setChecked(self.table.is_notes_mode())
|
||||||
|
switch.setToolTip(tr.browsing_toggle_cards_notes_mode())
|
||||||
|
qconnect(self.form.action_toggle_mode.triggered, switch.toggle)
|
||||||
qconnect(switch.toggled, self.on_table_state_changed)
|
qconnect(switch.toggled, self.on_table_state_changed)
|
||||||
self.form.gridLayout.addWidget(switch, 0, 0)
|
self.form.gridLayout.addWidget(switch, 0, 0)
|
||||||
|
|
||||||
@ -408,7 +408,7 @@ class Browser(QMainWindow):
|
|||||||
|
|
||||||
@ensure_editor_saved
|
@ensure_editor_saved
|
||||||
def onRowChanged(
|
def onRowChanged(
|
||||||
self, current: Optional[QItemSelection], previous: Optional[QItemSelection]
|
self, _current: Optional[QItemSelection], _previous: Optional[QItemSelection]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Update current note and hide/show editor. """
|
"""Update current note and hide/show editor. """
|
||||||
if self._closeEventHasCleanedUp:
|
if self._closeEventHasCleanedUp:
|
||||||
@ -428,10 +428,15 @@ class Browser(QMainWindow):
|
|||||||
self.editor.card = card
|
self.editor.card = card
|
||||||
else:
|
else:
|
||||||
self.editor.set_note(None)
|
self.editor.set_note(None)
|
||||||
self._renderPreview()
|
self._renderPreview()
|
||||||
self._update_flags_menu()
|
self._update_context_actions()
|
||||||
gui_hooks.browser_did_change_row(self)
|
gui_hooks.browser_did_change_row(self)
|
||||||
|
|
||||||
|
def _update_context_actions(self) -> None:
|
||||||
|
self._update_flags_menu()
|
||||||
|
self._update_toggle_mark_action()
|
||||||
|
self._update_toggle_suspend_action()
|
||||||
|
|
||||||
@ensure_editor_saved
|
@ensure_editor_saved
|
||||||
def on_table_state_changed(self, checked: bool) -> None:
|
def on_table_state_changed(self, checked: bool) -> None:
|
||||||
self.mw.progress.start()
|
self.mw.progress.start()
|
||||||
@ -725,15 +730,14 @@ where id in %s"""
|
|||||||
# Suspending
|
# Suspending
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def current_card_is_suspended(self) -> bool:
|
def _update_toggle_suspend_action(self) -> None:
|
||||||
return bool(self.card and self.card.queue == QUEUE_TYPE_SUSPENDED)
|
is_suspended = bool(self.card and self.card.queue == QUEUE_TYPE_SUSPENDED)
|
||||||
|
self.form.actionToggle_Suspend.setChecked(is_suspended)
|
||||||
|
|
||||||
@ensure_editor_saved_on_trigger
|
@ensure_editor_saved
|
||||||
def suspend_selected_cards(self) -> None:
|
def suspend_selected_cards(self, checked: bool) -> None:
|
||||||
want_suspend = not self.current_card_is_suspended()
|
|
||||||
cids = self.selected_cards()
|
cids = self.selected_cards()
|
||||||
|
if checked:
|
||||||
if want_suspend:
|
|
||||||
suspend_cards(mw=self.mw, card_ids=cids)
|
suspend_cards(mw=self.mw, card_ids=cids)
|
||||||
else:
|
else:
|
||||||
unsuspend_cards(mw=self.mw, card_ids=cids)
|
unsuspend_cards(mw=self.mw, card_ids=cids)
|
||||||
@ -776,12 +780,15 @@ where id in %s"""
|
|||||||
|
|
||||||
qtMenuShortcutWorkaround(self.form.menuFlag)
|
qtMenuShortcutWorkaround(self.form.menuFlag)
|
||||||
|
|
||||||
def toggle_mark_of_selected_notes(self) -> None:
|
def toggle_mark_of_selected_notes(self, checked: bool) -> None:
|
||||||
have_mark = bool(self.card and self.card.note().has_tag(MARKED_TAG))
|
if checked:
|
||||||
if have_mark:
|
|
||||||
self.remove_tags_from_selected_notes(tags=MARKED_TAG)
|
|
||||||
else:
|
|
||||||
self.add_tags_to_selected_notes(tags=MARKED_TAG)
|
self.add_tags_to_selected_notes(tags=MARKED_TAG)
|
||||||
|
else:
|
||||||
|
self.remove_tags_from_selected_notes(tags=MARKED_TAG)
|
||||||
|
|
||||||
|
def _update_toggle_mark_action(self) -> None:
|
||||||
|
is_marked = bool(self.card and self.card.note().has_tag(MARKED_TAG))
|
||||||
|
self.form.actionToggle_Mark.setChecked(is_marked)
|
||||||
|
|
||||||
# Scheduling
|
# Scheduling
|
||||||
######################################################################
|
######################################################################
|
||||||
|
@ -218,6 +218,8 @@
|
|||||||
</property>
|
</property>
|
||||||
<addaction name="actionUndo"/>
|
<addaction name="actionUndo"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
|
<addaction name="action_toggle_mode"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
<addaction name="actionSelectAll"/>
|
<addaction name="actionSelectAll"/>
|
||||||
<addaction name="actionSelectNotes"/>
|
<addaction name="actionSelectNotes"/>
|
||||||
<addaction name="actionInvertSelection"/>
|
<addaction name="actionInvertSelection"/>
|
||||||
@ -467,6 +469,9 @@
|
|||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionToggle_Suspend">
|
<action name="actionToggle_Suspend">
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>browsing_toggle_suspend</string>
|
<string>browsing_toggle_suspend</string>
|
||||||
</property>
|
</property>
|
||||||
@ -561,6 +566,9 @@
|
|||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionToggle_Mark">
|
<action name="actionToggle_Mark">
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>browsing_toggle_mark</string>
|
<string>browsing_toggle_mark</string>
|
||||||
</property>
|
</property>
|
||||||
@ -597,6 +605,14 @@
|
|||||||
<string>qt_accel_forget</string>
|
<string>qt_accel_forget</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="action_toggle_mode">
|
||||||
|
<property name="text">
|
||||||
|
<string>browsing_toggle_cards_notes_mode</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string notr="true">Ctrl+M</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="icons.qrc"/>
|
<include location="icons.qrc"/>
|
||||||
|
@ -9,7 +9,9 @@ from aqt.theme import theme_manager
|
|||||||
|
|
||||||
class Switch(QAbstractButton):
|
class Switch(QAbstractButton):
|
||||||
"""A horizontal slider to toggle between two states which can be denoted by short strings.
|
"""A horizontal slider to toggle between two states which can be denoted by short strings.
|
||||||
The left state is the default and corresponds to isChecked=False.
|
|
||||||
|
The left state is the default and corresponds to isChecked()=False.
|
||||||
|
The suppoorted slots are toggle(), for an animated transition, and setChecked().
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_margin: int = 2
|
_margin: int = 2
|
||||||
@ -91,7 +93,7 @@ class Switch(QAbstractButton):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _paint_knob(self, painter: QPainter) -> None:
|
def _paint_knob(self, painter: QPainter) -> None:
|
||||||
painter.setBrush(QBrush(theme_manager.qcolor(colors.HIGHLIGHT_BG)))
|
painter.setBrush(QBrush(theme_manager.qcolor(colors.LINK)))
|
||||||
painter.drawEllipse(self._current_knob_rectangle())
|
painter.drawEllipse(self._current_knob_rectangle())
|
||||||
|
|
||||||
def _paint_label(self, painter: QPainter) -> None:
|
def _paint_label(self, painter: QPainter) -> None:
|
||||||
@ -104,12 +106,20 @@ class Switch(QAbstractButton):
|
|||||||
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
|
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
|
||||||
super().mouseReleaseEvent(event)
|
super().mouseReleaseEvent(event)
|
||||||
if event.button() == Qt.LeftButton:
|
if event.button() == Qt.LeftButton:
|
||||||
animation = QPropertyAnimation(self, b"position", self)
|
self._animate_toggle()
|
||||||
animation.setDuration(100)
|
|
||||||
animation.setStartValue(self.start_position)
|
|
||||||
animation.setEndValue(self.end_position)
|
|
||||||
animation.start()
|
|
||||||
|
|
||||||
def enterEvent(self, event: QEvent) -> None:
|
def enterEvent(self, event: QEvent) -> None:
|
||||||
self.setCursor(Qt.PointingHandCursor)
|
self.setCursor(Qt.PointingHandCursor)
|
||||||
super().enterEvent(event)
|
super().enterEvent(event)
|
||||||
|
|
||||||
|
def toggle(self) -> None:
|
||||||
|
super().toggle()
|
||||||
|
self._animate_toggle()
|
||||||
|
|
||||||
|
def _animate_toggle(self) -> None:
|
||||||
|
animation = QPropertyAnimation(self, b"position", self)
|
||||||
|
animation.setDuration(100)
|
||||||
|
animation.setStartValue(self.start_position)
|
||||||
|
animation.setEndValue(self.end_position)
|
||||||
|
# make triggered events execute first so the animation runs smoothly afterwards
|
||||||
|
QTimer.singleShot(50, animation.start)
|
||||||
|
@ -717,8 +717,10 @@ class NoteState(ItemState):
|
|||||||
("note", tr.browsing_note()),
|
("note", tr.browsing_note()),
|
||||||
("noteCards", tr.editing_cards()),
|
("noteCards", tr.editing_cards()),
|
||||||
("noteCrt", tr.browsing_created()),
|
("noteCrt", tr.browsing_created()),
|
||||||
|
("noteDue", tr.statistics_due_date()),
|
||||||
("noteEase", tr.browsing_average_ease()),
|
("noteEase", tr.browsing_average_ease()),
|
||||||
("noteFld", tr.browsing_sort_field()),
|
("noteFld", tr.browsing_sort_field()),
|
||||||
|
("noteIvl", tr.browsing_average_interval()),
|
||||||
("noteLapses", tr.scheduling_lapses()),
|
("noteLapses", tr.scheduling_lapses()),
|
||||||
("noteMod", tr.search_note_modified()),
|
("noteMod", tr.search_note_modified()),
|
||||||
("noteReps", tr.scheduling_reviews()),
|
("noteReps", tr.scheduling_reviews()),
|
||||||
|
@ -811,21 +811,23 @@ message SortOrder {
|
|||||||
enum Kind {
|
enum Kind {
|
||||||
NOTE_CARDS = 0;
|
NOTE_CARDS = 0;
|
||||||
NOTE_CREATION = 1;
|
NOTE_CREATION = 1;
|
||||||
NOTE_EASE = 2;
|
NOTE_DUE = 2;
|
||||||
NOTE_FIELD = 3;
|
NOTE_EASE = 3;
|
||||||
NOTE_LAPSES = 4;
|
NOTE_FIELD = 4;
|
||||||
NOTE_MOD = 5;
|
NOTE_INTERVAL = 5;
|
||||||
NOTE_REPS = 6;
|
NOTE_LAPSES = 6;
|
||||||
NOTE_TAGS = 7;
|
NOTE_MOD = 7;
|
||||||
NOTETYPE = 8;
|
NOTE_REPS = 8;
|
||||||
CARD_MOD = 9;
|
NOTE_TAGS = 9;
|
||||||
CARD_REPS = 10;
|
NOTETYPE = 10;
|
||||||
CARD_DUE = 11;
|
CARD_MOD = 11;
|
||||||
CARD_EASE = 12;
|
CARD_REPS = 12;
|
||||||
CARD_LAPSES = 13;
|
CARD_DUE = 13;
|
||||||
CARD_INTERVAL = 14;
|
CARD_EASE = 14;
|
||||||
CARD_DECK = 15;
|
CARD_LAPSES = 15;
|
||||||
CARD_TEMPLATE = 16;
|
CARD_INTERVAL = 16;
|
||||||
|
CARD_DECK = 17;
|
||||||
|
CARD_TEMPLATE = 18;
|
||||||
}
|
}
|
||||||
Kind kind = 1;
|
Kind kind = 1;
|
||||||
bool reverse = 2;
|
bool reverse = 2;
|
||||||
|
@ -24,8 +24,10 @@ impl From<String> for browser_table::Column {
|
|||||||
"template" => browser_table::Column::CardTemplate,
|
"template" => browser_table::Column::CardTemplate,
|
||||||
"noteCards" => browser_table::Column::NoteCards,
|
"noteCards" => browser_table::Column::NoteCards,
|
||||||
"noteCrt" => browser_table::Column::NoteCreation,
|
"noteCrt" => browser_table::Column::NoteCreation,
|
||||||
|
"noteDue" => browser_table::Column::NoteDue,
|
||||||
"noteEase" => browser_table::Column::NoteEase,
|
"noteEase" => browser_table::Column::NoteEase,
|
||||||
"noteFld" => browser_table::Column::NoteField,
|
"noteFld" => browser_table::Column::NoteField,
|
||||||
|
"noteIvl" => browser_table::Column::NoteInterval,
|
||||||
"noteLapses" => browser_table::Column::NoteLapses,
|
"noteLapses" => browser_table::Column::NoteLapses,
|
||||||
"noteMod" => browser_table::Column::NoteMod,
|
"noteMod" => browser_table::Column::NoteMod,
|
||||||
"noteReps" => browser_table::Column::NoteReps,
|
"noteReps" => browser_table::Column::NoteReps,
|
||||||
|
@ -109,7 +109,9 @@ impl From<SortKindProto> for SortKind {
|
|||||||
match kind {
|
match kind {
|
||||||
SortKindProto::NoteCards => SortKind::NoteCards,
|
SortKindProto::NoteCards => SortKind::NoteCards,
|
||||||
SortKindProto::NoteCreation => SortKind::NoteCreation,
|
SortKindProto::NoteCreation => SortKind::NoteCreation,
|
||||||
|
SortKindProto::NoteDue => SortKind::NoteDue,
|
||||||
SortKindProto::NoteEase => SortKind::NoteEase,
|
SortKindProto::NoteEase => SortKind::NoteEase,
|
||||||
|
SortKindProto::NoteInterval => SortKind::NoteInterval,
|
||||||
SortKindProto::NoteLapses => SortKind::NoteLapses,
|
SortKindProto::NoteLapses => SortKind::NoteLapses,
|
||||||
SortKindProto::NoteMod => SortKind::NoteMod,
|
SortKindProto::NoteMod => SortKind::NoteMod,
|
||||||
SortKindProto::NoteField => SortKind::NoteField,
|
SortKindProto::NoteField => SortKind::NoteField,
|
||||||
|
@ -24,26 +24,28 @@ use crate::{
|
|||||||
#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy)]
|
#[derive(Serialize_repr, Deserialize_repr, Debug, PartialEq, Clone, Copy)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum Column {
|
pub enum Column {
|
||||||
Custom = 0,
|
Custom,
|
||||||
Question = 1,
|
Question,
|
||||||
Answer = 2,
|
Answer,
|
||||||
CardDeck = 3,
|
CardDeck,
|
||||||
CardDue = 4,
|
CardDue,
|
||||||
CardEase = 5,
|
CardEase,
|
||||||
CardLapses = 6,
|
CardLapses,
|
||||||
CardInterval = 7,
|
CardInterval,
|
||||||
CardMod = 8,
|
CardMod,
|
||||||
CardReps = 9,
|
CardReps,
|
||||||
CardTemplate = 10,
|
CardTemplate,
|
||||||
NoteCards = 11,
|
NoteCards,
|
||||||
NoteCreation = 12,
|
NoteCreation,
|
||||||
NoteEase = 13,
|
NoteDue,
|
||||||
NoteField = 14,
|
NoteEase,
|
||||||
NoteLapses = 15,
|
NoteField,
|
||||||
NoteMod = 16,
|
NoteInterval,
|
||||||
NoteReps = 17,
|
NoteLapses,
|
||||||
NoteTags = 18,
|
NoteMod,
|
||||||
Notetype = 19,
|
NoteReps,
|
||||||
|
NoteTags,
|
||||||
|
Notetype,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@ -77,13 +79,13 @@ pub struct Font {
|
|||||||
}
|
}
|
||||||
|
|
||||||
trait RowContext {
|
trait RowContext {
|
||||||
fn get_cell_text(&mut self, column: &Column) -> Result<String>;
|
fn get_cell_text(&mut self, column: Column) -> Result<String>;
|
||||||
fn get_row_color(&self) -> Color;
|
fn get_row_color(&self) -> Color;
|
||||||
fn get_row_font(&self) -> Result<Font>;
|
fn get_row_font(&self) -> Result<Font>;
|
||||||
fn note(&self) -> &Note;
|
fn note(&self) -> &Note;
|
||||||
fn notetype(&self) -> &Notetype;
|
fn notetype(&self) -> &Notetype;
|
||||||
|
|
||||||
fn get_cell(&mut self, column: &Column) -> Result<Cell> {
|
fn get_cell(&mut self, column: Column) -> Result<Cell> {
|
||||||
Ok(Cell {
|
Ok(Cell {
|
||||||
text: self.get_cell_text(column)?,
|
text: self.get_cell_text(column)?,
|
||||||
is_rtl: self.get_is_rtl(column),
|
is_rtl: self.get_is_rtl(column),
|
||||||
@ -101,7 +103,7 @@ trait RowContext {
|
|||||||
html_to_text_line(&self.note().fields()[index]).into()
|
html_to_text_line(&self.note().fields()[index]).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_is_rtl(&self, column: &Column) -> bool {
|
fn get_is_rtl(&self, column: Column) -> bool {
|
||||||
match column {
|
match column {
|
||||||
Column::NoteField => {
|
Column::NoteField => {
|
||||||
let index = self.notetype().config.sort_field_idx as usize;
|
let index = self.notetype().config.sort_field_idx as usize;
|
||||||
@ -115,7 +117,7 @@ trait RowContext {
|
|||||||
Ok(Row {
|
Ok(Row {
|
||||||
cells: columns
|
cells: columns
|
||||||
.iter()
|
.iter()
|
||||||
.map(|column| self.get_cell(column))
|
.map(|&column| self.get_cell(column))
|
||||||
.collect::<Result<_>>()?,
|
.collect::<Result<_>>()?,
|
||||||
color: self.get_row_color(),
|
color: self.get_row_color(),
|
||||||
font: self.get_row_font()?,
|
font: self.get_row_font()?,
|
||||||
@ -147,6 +149,7 @@ struct NoteRowContext<'a> {
|
|||||||
notetype: Arc<Notetype>,
|
notetype: Arc<Notetype>,
|
||||||
cards: Vec<Card>,
|
cards: Vec<Card>,
|
||||||
tr: &'a I18n,
|
tr: &'a I18n,
|
||||||
|
timing: SchedTimingToday,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn card_render_required(columns: &[Column]) -> bool {
|
fn card_render_required(columns: &[Column]) -> bool {
|
||||||
@ -155,6 +158,49 @@ fn card_render_required(columns: &[Column]) -> bool {
|
|||||||
.any(|c| matches!(c, Column::Question | Column::Answer))
|
.any(|c| matches!(c, Column::Question | Column::Answer))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Card {
|
||||||
|
fn is_new_type_or_queue(&self) -> bool {
|
||||||
|
self.queue == CardQueue::New || self.ctype == CardType::New
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_filtered_deck(&self) -> bool {
|
||||||
|
self.original_deck_id != DeckId(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the card can not be due as it's buried or suspended.
|
||||||
|
fn is_undue_queue(&self) -> bool {
|
||||||
|
(self.queue as i8) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the card has a due date in terms of days.
|
||||||
|
fn is_due_in_days(&self) -> bool {
|
||||||
|
matches!(self.queue, CardQueue::DayLearn | CardQueue::Review)
|
||||||
|
|| (self.ctype == CardType::Review && self.is_undue_queue())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the card's due date as a timestamp if it has one.
|
||||||
|
fn due_time(&self, timing: &SchedTimingToday) -> Option<TimestampSecs> {
|
||||||
|
if self.queue == CardQueue::Learn {
|
||||||
|
Some(TimestampSecs(self.due as i64))
|
||||||
|
} else if self.is_due_in_days() {
|
||||||
|
Some(
|
||||||
|
TimestampSecs::now()
|
||||||
|
.adding_secs(((self.due - timing.days_elapsed as i32) * 86400) as i64),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Note {
|
||||||
|
fn is_marked(&self) -> bool {
|
||||||
|
self.tags
|
||||||
|
.iter()
|
||||||
|
.any(|tag| tag.eq_ignore_ascii_case("marked"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Collection {
|
impl Collection {
|
||||||
pub fn browser_row_for_id(&mut self, id: i64) -> Result<Row> {
|
pub fn browser_row_for_id(&mut self, id: i64) -> Result<Row> {
|
||||||
if self.get_bool(BoolKey::BrowserTableShowNotesMode) {
|
if self.get_bool(BoolKey::BrowserTableShowNotesMode) {
|
||||||
@ -297,25 +343,16 @@ impl<'a> CardRowContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn card_due_str(&mut self) -> String {
|
fn card_due_str(&mut self) -> String {
|
||||||
let due = if self.card.original_deck_id != DeckId(0) {
|
let due = if self.card.is_filtered_deck() {
|
||||||
self.tr.browsing_filtered()
|
self.tr.browsing_filtered()
|
||||||
} else if self.card.queue == CardQueue::New || self.card.ctype == CardType::New {
|
} else if self.card.is_new_type_or_queue() {
|
||||||
self.tr.statistics_due_for_new_card(self.card.due)
|
self.tr.statistics_due_for_new_card(self.card.due)
|
||||||
|
} else if let Some(time) = self.card.due_time(&self.timing) {
|
||||||
|
time.date_string().into()
|
||||||
} else {
|
} else {
|
||||||
let date = if self.card.queue == CardQueue::Learn {
|
return "".into();
|
||||||
TimestampSecs(self.card.due as i64)
|
|
||||||
} else if self.card.queue == CardQueue::DayLearn
|
|
||||||
|| self.card.queue == CardQueue::Review
|
|
||||||
|| (self.card.ctype == CardType::Review && (self.card.queue as i8) < 0)
|
|
||||||
{
|
|
||||||
TimestampSecs::now()
|
|
||||||
.adding_secs(((self.card.due - self.timing.days_elapsed as i32) * 86400) as i64)
|
|
||||||
} else {
|
|
||||||
return "".into();
|
|
||||||
};
|
|
||||||
date.date_string().into()
|
|
||||||
};
|
};
|
||||||
if (self.card.queue as i8) < 0 {
|
if self.card.is_undue_queue() {
|
||||||
format!("({})", due)
|
format!("({})", due)
|
||||||
} else {
|
} else {
|
||||||
due.into()
|
due.into()
|
||||||
@ -360,7 +397,7 @@ impl<'a> CardRowContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RowContext for CardRowContext<'_> {
|
impl RowContext for CardRowContext<'_> {
|
||||||
fn get_cell_text(&mut self, column: &Column) -> Result<String> {
|
fn get_cell_text(&mut self, column: Column) -> Result<String> {
|
||||||
Ok(match column {
|
Ok(match column {
|
||||||
Column::Question => self.question_str(),
|
Column::Question => self.question_str(),
|
||||||
Column::Answer => self.answer_str(),
|
Column::Answer => self.answer_str(),
|
||||||
@ -388,12 +425,7 @@ impl RowContext for CardRowContext<'_> {
|
|||||||
3 => Color::FlagGreen,
|
3 => Color::FlagGreen,
|
||||||
4 => Color::FlagBlue,
|
4 => Color::FlagBlue,
|
||||||
_ => {
|
_ => {
|
||||||
if self
|
if self.note.is_marked() {
|
||||||
.note
|
|
||||||
.tags
|
|
||||||
.iter()
|
|
||||||
.any(|tag| tag.eq_ignore_ascii_case("marked"))
|
|
||||||
{
|
|
||||||
Color::Marked
|
Color::Marked
|
||||||
} else if self.card.queue == CardQueue::Suspended {
|
} else if self.card.queue == CardQueue::Suspended {
|
||||||
Color::Suspended
|
Color::Suspended
|
||||||
@ -427,37 +459,74 @@ impl<'a> NoteRowContext<'a> {
|
|||||||
.get_notetype(note.notetype_id)?
|
.get_notetype(note.notetype_id)?
|
||||||
.ok_or(AnkiError::NotFound)?;
|
.ok_or(AnkiError::NotFound)?;
|
||||||
let cards = col.storage.all_cards_of_note(note.id)?;
|
let cards = col.storage.all_cards_of_note(note.id)?;
|
||||||
|
let timing = col.timing_today()?;
|
||||||
|
|
||||||
Ok(NoteRowContext {
|
Ok(NoteRowContext {
|
||||||
note,
|
note,
|
||||||
notetype,
|
notetype,
|
||||||
cards,
|
cards,
|
||||||
tr: &col.tr,
|
tr: &col.tr,
|
||||||
|
timing,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the average ease of the non-new cards or a hint if there aren't any.
|
||||||
fn note_ease_str(&self) -> String {
|
fn note_ease_str(&self) -> String {
|
||||||
let cards = self
|
let eases: Vec<u16> = self
|
||||||
.cards
|
.cards
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|c| c.ctype != CardType::New)
|
.filter(|c| c.ctype != CardType::New)
|
||||||
.collect::<Vec<&Card>>();
|
.map(|c| c.ease_factor)
|
||||||
if cards.is_empty() {
|
.collect();
|
||||||
|
if eases.is_empty() {
|
||||||
self.tr.browsing_new().into()
|
self.tr.browsing_new().into()
|
||||||
} else {
|
} else {
|
||||||
let ease = cards.iter().map(|c| c.ease_factor).sum::<u16>() / cards.len() as u16;
|
format!("{}%", eases.iter().sum::<u16>() / eases.len() as u16 / 10)
|
||||||
format!("{}%", ease / 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<u32> = 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::<u32>() * 86400 / (intervals.len() as u32)) as f32,
|
||||||
|
self.tr,
|
||||||
|
false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RowContext for NoteRowContext<'_> {
|
impl RowContext for NoteRowContext<'_> {
|
||||||
fn get_cell_text(&mut self, column: &Column) -> Result<String> {
|
fn get_cell_text(&mut self, column: Column) -> Result<String> {
|
||||||
Ok(match column {
|
Ok(match column {
|
||||||
Column::NoteCards => self.cards.len().to_string(),
|
Column::NoteCards => self.cards.len().to_string(),
|
||||||
Column::NoteCreation => self.note_creation_str(),
|
Column::NoteCreation => self.note_creation_str(),
|
||||||
|
Column::NoteDue => self.note_due_str(),
|
||||||
Column::NoteEase => self.note_ease_str(),
|
Column::NoteEase => self.note_ease_str(),
|
||||||
Column::NoteField => self.note_field_str(),
|
Column::NoteField => self.note_field_str(),
|
||||||
|
Column::NoteInterval => self.note_interval_str(),
|
||||||
Column::NoteLapses => self.cards.iter().map(|c| c.lapses).sum::<u32>().to_string(),
|
Column::NoteLapses => self.cards.iter().map(|c| c.lapses).sum::<u32>().to_string(),
|
||||||
Column::NoteMod => self.note.mtime.date_string(),
|
Column::NoteMod => self.note.mtime.date_string(),
|
||||||
Column::NoteReps => self.cards.iter().map(|c| c.reps).sum::<u32>().to_string(),
|
Column::NoteReps => self.cards.iter().map(|c| c.reps).sum::<u32>().to_string(),
|
||||||
@ -468,12 +537,7 @@ impl RowContext for NoteRowContext<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_row_color(&self) -> Color {
|
fn get_row_color(&self) -> Color {
|
||||||
if self
|
if self.note.is_marked() {
|
||||||
.note
|
|
||||||
.tags
|
|
||||||
.iter()
|
|
||||||
.any(|tag| tag.eq_ignore_ascii_case("marked"))
|
|
||||||
{
|
|
||||||
Color::Marked
|
Color::Marked
|
||||||
} else {
|
} else {
|
||||||
Color::Default
|
Color::Default
|
||||||
|
@ -271,7 +271,10 @@ pub enum SortKind {
|
|||||||
NoteCards,
|
NoteCards,
|
||||||
#[serde(rename = "noteCrt")]
|
#[serde(rename = "noteCrt")]
|
||||||
NoteCreation,
|
NoteCreation,
|
||||||
|
NoteDue,
|
||||||
NoteEase,
|
NoteEase,
|
||||||
|
#[serde(rename = "noteIvl")]
|
||||||
|
NoteInterval,
|
||||||
NoteLapses,
|
NoteLapses,
|
||||||
NoteMod,
|
NoteMod,
|
||||||
#[serde(rename = "noteFld")]
|
#[serde(rename = "noteFld")]
|
||||||
|
@ -90,8 +90,10 @@ impl SortKind {
|
|||||||
match self {
|
match self {
|
||||||
SortKind::NoteCards
|
SortKind::NoteCards
|
||||||
| SortKind::NoteCreation
|
| SortKind::NoteCreation
|
||||||
|
| SortKind::NoteDue
|
||||||
| SortKind::NoteEase
|
| SortKind::NoteEase
|
||||||
| SortKind::NoteField
|
| SortKind::NoteField
|
||||||
|
| SortKind::NoteInterval
|
||||||
| SortKind::NoteLapses
|
| SortKind::NoteLapses
|
||||||
| SortKind::NoteMod
|
| SortKind::NoteMod
|
||||||
| SortKind::NoteReps
|
| SortKind::NoteReps
|
||||||
@ -252,9 +254,12 @@ fn card_order_from_sortkind(kind: SortKind) -> Cow<'static, str> {
|
|||||||
|
|
||||||
fn note_order_from_sortkind(kind: SortKind) -> Cow<'static, str> {
|
fn note_order_from_sortkind(kind: SortKind) -> Cow<'static, str> {
|
||||||
match kind {
|
match kind {
|
||||||
SortKind::NoteCards | SortKind::NoteEase | SortKind::NoteLapses | SortKind::NoteReps => {
|
SortKind::NoteCards
|
||||||
"(select pos from sort_order where nid = n.id) asc".into()
|
| 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::NoteCreation => "n.id asc".into(),
|
||||||
SortKind::NoteField => "n.sfld collate nocase asc".into(),
|
SortKind::NoteField => "n.sfld collate nocase asc".into(),
|
||||||
SortKind::NoteMod => "n.mod asc".into(),
|
SortKind::NoteMod => "n.mod asc".into(),
|
||||||
@ -270,7 +275,9 @@ fn prepare_sort(col: &mut Collection, kind: SortKind) -> Result<()> {
|
|||||||
CardDeck => include_str!("deck_order.sql"),
|
CardDeck => include_str!("deck_order.sql"),
|
||||||
CardTemplate => include_str!("template_order.sql"),
|
CardTemplate => include_str!("template_order.sql"),
|
||||||
NoteCards => include_str!("note_cards_order.sql"),
|
NoteCards => include_str!("note_cards_order.sql"),
|
||||||
|
NoteDue => include_str!("note_due_order.sql"),
|
||||||
NoteEase => include_str!("note_ease_order.sql"),
|
NoteEase => include_str!("note_ease_order.sql"),
|
||||||
|
NoteInterval => include_str!("note_interval_order.sql"),
|
||||||
NoteLapses => include_str!("note_lapses_order.sql"),
|
NoteLapses => include_str!("note_lapses_order.sql"),
|
||||||
NoteReps => include_str!("note_reps_order.sql"),
|
NoteReps => include_str!("note_reps_order.sql"),
|
||||||
Notetype => include_str!("notetype_order.sql"),
|
Notetype => include_str!("notetype_order.sql"),
|
||||||
|
16
rslib/src/search/note_due_order.sql
Normal file
16
rslib/src/search/note_due_order.sql
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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
|
||||||
|
WHERE (
|
||||||
|
odid = 0
|
||||||
|
AND type != 0
|
||||||
|
AND queue > 0
|
||||||
|
)
|
||||||
|
GROUP BY nid
|
||||||
|
ORDER BY MIN(type),
|
||||||
|
MIN(due);
|
11
rslib/src/search/note_interval_order.sql
Normal file
11
rslib/src/search/note_interval_order.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
|
WHERE type IN (2, 3)
|
||||||
|
GROUP BY nid
|
||||||
|
ORDER BY AVG(ivl);
|
Loading…
Reference in New Issue
Block a user