Merge pull request #1108 from RumovZ/more-columns

Even more browser fixes and features
This commit is contained in:
Damien Elmes 2021-04-01 15:59:06 +10:00 committed by GitHub
commit 8449bbe469
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 245 additions and 101 deletions

View File

@ -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

View File

@ -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
###################################################################### ######################################################################

View File

@ -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"/>

View File

@ -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)

View File

@ -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()),

View File

@ -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;

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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")]

View File

@ -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"),

View 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);

View 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);