hook the empty cards code up to the GUI

This commit is contained in:
Damien Elmes 2020-04-25 19:44:48 +10:00
parent f637ac957d
commit 6e8860cafa
7 changed files with 248 additions and 50 deletions

View file

@ -533,26 +533,8 @@ select id from notes where id in %s and id not in (select nid from cards)"""
self._remNotes(nids)
def emptyCids(self) -> List[int]:
"""Returns IDs of empty cards."""
rem: List[int] = []
for m in self.models.all():
rem += self.genCards(self.models.nids(m))
return rem
def emptyCardReport(self, cids) -> str:
rep = ""
for ords, cnt, flds in self.db.all(
"""
select group_concat(ord+1), count(), flds from cards c, notes n
where c.nid = n.id and c.id in %s group by nid"""
% ids2str(cids)
):
rep += self.tr(
TR.EMPTY_CARDS_CARD_LINE,
**{"card-numbers": ords, "fields": flds.replace("\x1f", " / ")},
)
rep += "\n\n"
return rep
print("emptyCids() will go away")
return []
# Field checksums and sorting fields
##########################################################################

View file

@ -700,6 +700,12 @@ class RustBackend:
except NotFoundError:
return None
def empty_cards_report(self) -> pb.EmptyCardsReport:
return self._run_command(
pb.BackendInput(get_empty_cards=pb.Empty()), release_gil=True
).get_empty_cards
def translate_string_in(
key: TR, **kwargs: Union[str, int, float]
) -> pb.TranslateStringIn:

View file

@ -72,7 +72,9 @@ def test_genrem():
t = m["tmpls"][1]
t["qfmt"] = "{{Back}}"
mm.save(m, templates=True)
d.remCards(d.emptyCids())
rep = d.backend.empty_cards_report()
for note in rep.notes:
d.remCards(note.card_ids)
assert len(f.cards()) == 1
# if we add to the note, a card should be automatically generated
f.load()

96
qt/aqt/emptycards.py Normal file
View file

@ -0,0 +1,96 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from __future__ import annotations
import re
import aqt
from anki.backend_pb2 import EmptyCardsReport, NoteWithEmptyCards
from aqt.qt import QDialog, QDialogButtonBox, qconnect
from aqt.utils import TR, restoreGeom, saveGeom, tooltip, tr
def show_empty_cards(mw: aqt.main.AnkiQt) -> None:
mw.progress.start()
def on_done(fut):
mw.progress.finish()
report: EmptyCardsReport = fut.result()
if not report.notes:
tooltip(tr(TR.EMPTY_CARDS_NOT_FOUND))
return
diag = EmptyCardsDialog(mw, report)
diag.show()
mw.taskman.run_in_background(mw.col.backend.empty_cards_report, on_done)
class EmptyCardsDialog(QDialog):
silentlyClose = True
def __init__(self, mw: aqt.main.AnkiQt, report: EmptyCardsReport) -> None:
super().__init__(mw)
self.mw = mw.weakref()
self.report = report
self.form = aqt.forms.emptycards.Ui_Dialog()
self.form.setupUi(self)
restoreGeom(self, "emptycards")
self.setWindowTitle(tr(TR.EMPTY_CARDS_WINDOW_TITLE))
self.form.keep_notes.setText(tr(TR.EMPTY_CARDS_PRESERVE_NOTES_CHECKBOX))
self.form.webview.title = "empty cards"
self.form.webview.set_bridge_command(self._on_note_link_clicked, self)
# make the note ids clickable
html = re.sub(
r"\[anki:nid:(\d+)\]",
"<a href=# onclick=\"pycmd('nid:\\1'); return false\">\\1</a>: ",
report.report,
)
style = "<style>.allempty { color: red; }</style>"
self.form.webview.stdHtml(style + html, context=self)
def on_finished(code):
saveGeom(self, "emptycards")
qconnect(self.finished, on_finished)
self._delete_button = self.form.buttonBox.addButton(
tr(TR.EMPTY_CARDS_DELETE_BUTTON), QDialogButtonBox.ActionRole
)
self._delete_button.setAutoDefault(False)
self._delete_button.clicked.connect(self._on_delete)
def _on_note_link_clicked(self, link):
browser = aqt.dialogs.open("Browser", self.mw)
browser.form.searchEdit.lineEdit().setText(link)
browser.onSearchActivated()
def _on_delete(self):
self.mw.progress.start()
def delete():
return self._delete_cards(self.form.keep_notes.isChecked())
def on_done(fut):
self.mw.progress.finish()
try:
count = fut.result()
finally:
self.close()
tooltip(tr(TR.EMPTY_CARDS_DELETED_COUNT, cards=count))
self.mw.taskman.run_in_background(delete, on_done)
def _delete_cards(self, keep_notes):
to_delete = []
for note in self.report.notes:
note: NoteWithEmptyCards = note
if keep_notes and note.will_delete_note:
# leave first card
to_delete.extend(note.card_ids[1:])
else:
to_delete.extend(note.card_ids)
self.mw.col.remCards(to_delete)
return len(to_delete)

View file

@ -10,6 +10,7 @@ import os
import re
import signal
import time
import weakref
import zipfile
from argparse import Namespace
from concurrent.futures import Future
@ -35,6 +36,7 @@ from anki.storage import Collection
from anki.utils import devMode, ids2str, intTime, isMac, isWin, splitFields
from aqt import gui_hooks
from aqt.addons import DownloadLogEntry, check_and_prompt_for_updates, show_log_to_user
from aqt.emptycards import show_empty_cards
from aqt.legacy import install_pylib_legacy
from aqt.mediacheck import check_media_db
from aqt.mediasync import MediaSyncer
@ -53,7 +55,6 @@ from aqt.utils import (
openLink,
restoreGeom,
restoreState,
saveGeom,
showInfo,
showText,
showWarning,
@ -161,6 +162,10 @@ class AnkiQt(QMainWindow):
self.setupProfile()
def weakref(self) -> AnkiQt:
"Shortcut to create a weak reference that doesn't break code completion."
return weakref.proxy(self) # type: ignore
# Profiles
##########################################################################
@ -1336,34 +1341,8 @@ will be lost. Continue?"""
self.col.decks.select(self.col.decks.id(ret.name))
self.moveToState("overview")
def onEmptyCards(self):
self.progress.start(immediate=True)
cids = self.col.emptyCids()
cids = gui_hooks.empty_cards_will_be_deleted(cids)
if not cids:
self.progress.finish()
tooltip(_("No empty cards."))
return
report = self.col.emptyCardReport(cids)
self.progress.finish()
part1 = ngettext("%d card", "%d cards", len(cids)) % len(cids)
part1 = _("%s to delete:") % part1
diag, box = showText(part1 + "\n\n" + report, run=False, geomKey="emptyCards")
box.addButton(_("Delete Cards"), QDialogButtonBox.AcceptRole)
box.button(QDialogButtonBox.Close).setDefault(True)
def onDelete():
saveGeom(diag, "emptyCards")
QDialog.accept(diag)
self.checkpoint(_("Delete Empty"))
self.col.remCards(cids)
tooltip(
ngettext("%d card deleted.", "%d cards deleted.", len(cids)) % len(cids)
)
self.reset()
qconnect(box.accepted, onDelete)
diag.show()
def onEmptyCards(self) -> None:
show_empty_cards(self)
# Debugging
######################################################################

128
qt/designer/emptycards.ui Normal file
View file

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>531</width>
<height>345</height>
</rect>
</property>
<property name="windowTitle">
<string>EMPTY_CARDS</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="AnkiWebView" name="webview" native="true">
<property name="url" stdset="0">
<url>
<string>about:blank</string>
</url>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>12</number>
</property>
<property name="leftMargin">
<number>12</number>
</property>
<property name="topMargin">
<number>12</number>
</property>
<property name="rightMargin">
<number>12</number>
</property>
<property name="bottomMargin">
<number>12</number>
</property>
<item>
<widget class="QCheckBox" name="keep_notes">
<property name="text">
<string notr="true">KEEP_NOTES</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>AnkiWebView</class>
<extends>QWidget</extends>
<header location="global">aqt/webview</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>buttonBox</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -4,3 +4,8 @@ empty-cards-count-line =
empty-cards-window-title = Empty Cards
empty-cards-preserve-notes-checkbox = Keep notes with no valid cards
empty-cards-delete-button = Delete
empty-cards-not-found = No empty cards.
empty-cards-deleted-count = Deleted { $cards ->
[one] { $cards } card.
*[other] { $cards } cards.
}