anki/qt/aqt/studydeck.py
Damien Elmes a7812dedc0 switch to new-style PyQt scoped enums and Qt6
The enum changes should work on PyQt 5.x, and are required in PyQt 6.x.
They are not supported by the PyQt5 typings however, so we need to run
our tests with PyQt6.
2021-10-15 12:57:19 +10:00

180 lines
6.0 KiB
Python

# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from typing import Optional
import aqt
from anki.collection import OpChangesWithId
from anki.decks import DeckId
from aqt import gui_hooks
from aqt.operations.deck import add_deck_dialog
from aqt.qt import *
from aqt.utils import (
HelpPage,
HelpPageArgument,
disable_help_button,
openHelp,
restoreGeom,
saveGeom,
shortcut,
showInfo,
tr,
)
class StudyDeck(QDialog):
def __init__(
self,
mw: aqt.AnkiQt,
names: Callable = None,
accept: str = None,
title: str = None,
help: HelpPageArgument = HelpPage.KEYBOARD_SHORTCUTS,
current: Optional[str] = None,
cancel: bool = True,
parent: Optional[QWidget] = None,
dyn: bool = False,
buttons: Optional[list[Union[str, QPushButton]]] = None,
geomKey: str = "default",
) -> None:
QDialog.__init__(self, parent or mw)
self.mw = mw
self.form = aqt.forms.studydeck.Ui_Dialog()
self.form.setupUi(self)
self.form.filter.installEventFilter(self)
self.cancel = cancel
gui_hooks.state_did_reset.append(self.onReset)
self.geomKey = f"studyDeck-{geomKey}"
restoreGeom(self, self.geomKey)
disable_help_button(self)
if not cancel:
self.form.buttonBox.removeButton(
self.form.buttonBox.button(QDialogButtonBox.StandardButton.Cancel)
)
if buttons is not None:
for button_or_label in buttons:
self.form.buttonBox.addButton(
button_or_label, QDialogButtonBox.ButtonRole.ActionRole
)
else:
b = QPushButton(tr.actions_add())
b.setShortcut(QKeySequence("Ctrl+N"))
b.setToolTip(shortcut(tr.decks_add_new_deck_ctrlandn()))
self.form.buttonBox.addButton(b, QDialogButtonBox.ButtonRole.ActionRole)
qconnect(b.clicked, self.onAddDeck)
if title:
self.setWindowTitle(title)
if not names:
names_ = [
d.name
for d in self.mw.col.decks.all_names_and_ids(
include_filtered=dyn, skip_empty_default=True
)
]
self.nameFunc = None
self.origNames = names_
else:
self.nameFunc = names
self.origNames = names()
self.name: Optional[str] = None
self.ok = self.form.buttonBox.addButton(
accept or tr.decks_study(), QDialogButtonBox.ButtonRole.AcceptRole
)
self.setWindowModality(Qt.WindowModality.WindowModal)
qconnect(self.form.buttonBox.helpRequested, lambda: openHelp(help))
qconnect(self.form.filter.textEdited, self.redraw)
qconnect(self.form.list.itemDoubleClicked, self.accept)
self.show()
# redraw after show so position at center correct
self.redraw("", current)
self.exec()
def eventFilter(self, obj: QObject, evt: QEvent) -> bool:
if isinstance(evt, QKeyEvent) and evt.type() == QEvent.Type.KeyPress:
new_row = current_row = self.form.list.currentRow()
rows_count = self.form.list.count()
key = evt.key()
if key == Qt.Key.Key_Up:
new_row = current_row - 1
elif key == Qt.Key.Key_Down:
new_row = current_row + 1
elif (
evt.modifiers() & Qt.KeyboardModifier.ControlModifier
and Qt.Key.Key_1 <= key <= Qt.Key.Key_9
):
row_index = key - Qt.Key.Key_1
if row_index < rows_count:
new_row = row_index
if rows_count:
new_row %= rows_count # don't let row index overflow/underflow
if new_row != current_row:
self.form.list.setCurrentRow(new_row)
return True
return False
def redraw(self, filt: str, focus: Optional[str] = None) -> None:
self.filt = filt
self.focus = focus
self.names = [n for n in self.origNames if self._matches(n, filt)]
l = self.form.list
l.clear()
l.addItems(self.names)
if focus in self.names:
idx = self.names.index(focus)
else:
idx = 0
l.setCurrentRow(idx)
l.scrollToItem(l.item(idx), QAbstractItemView.ScrollHint.PositionAtCenter)
def _matches(self, name: str, filt: str) -> bool:
name = name.lower()
filt = filt.lower()
if not filt:
return True
for word in filt.split(" "):
if word not in name:
return False
return True
def onReset(self) -> None:
# model updated?
if self.nameFunc:
self.origNames = self.nameFunc()
self.redraw(self.filt, self.focus)
def accept(self) -> None:
saveGeom(self, self.geomKey)
gui_hooks.state_did_reset.remove(self.onReset)
row = self.form.list.currentRow()
if row < 0:
showInfo(tr.decks_please_select_something())
return
self.name = self.names[self.form.list.currentRow()]
QDialog.accept(self)
def reject(self) -> None:
saveGeom(self, self.geomKey)
gui_hooks.state_did_reset.remove(self.onReset)
QDialog.reject(self)
def onAddDeck(self) -> None:
row = self.form.list.currentRow()
if row < 0:
default = self.form.filter.text()
else:
default = self.names[self.form.list.currentRow()]
def success(out: OpChangesWithId) -> None:
deck = self.mw.col.decks.get(DeckId(out.id))
self.name = deck["name"]
# make sure we clean up reset hook when manually exiting
gui_hooks.state_did_reset.remove(self.onReset)
QDialog.accept(self)
if diag := add_deck_dialog(parent=self, default_text=default):
diag.success(success).run_in_background()