Anki/qt/aqt/flexible_grading_reviewer/widgets.py
Ren Tatsumoto 457efc0d62 add selector to preferences
implement bottom bar

add flexible reviewer

show reps done today

add flexible deck browser

add flexible overview
2025-11-24 07:53:16 +03:00

141 lines
4.2 KiB
Python

# 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 aqt
from aqt import qconnect
from aqt.flexible_grading_reviewer.utils import clear_layout
from aqt.qt import *
class FlexiblePushButton(QPushButton):
_height: int = 16
_font_size: int = _height - 4
def __init__(
self,
text="",
text_color: str = "#111111",
text_underline: bool = False,
parent=None,
) -> None:
super().__init__(text, parent)
# Fixed height 16px, let width be flexible
self.setFixedHeight(self._height)
# Remove extra spacing from focus/contents margins
self.setContentsMargins(0, 0, 0, 0)
self.set_text_style(text_color, text_underline)
# Optional: ensure compact size hint
self.setSizePolicy(
self.sizePolicy().horizontalPolicy(),
self.sizePolicy().Policy.Fixed,
)
def set_text_style(
self, text_color: str = "#111111", text_underline: bool = False
) -> None:
stylesheet = (
"""
FlexiblePushButton {
border: none;
background: transparent;
color: TEXT_COLOR;
margin: 0;
padding: 0;
font-size: FONT_SIZEpx;
min-width: 0;
qproperty-flat: true;
font-family: "Noto Sans Mono", "Liberation Mono", "DejaVu Sans Mono", "Courier New", "Lucida Console",
Courier, Consolas, "Noto Sans Mono CJK JP", monospace;
TEXT_UNDERLINE
}
FlexiblePushButton:hover {
background: #d0d0d0;
color: #000;
}
FlexiblePushButton:pressed {
background: #b8b8b8;
}
""".replace("FONT_SIZE", f"{self._font_size}")
.replace("TEXT_COLOR", text_color)
.replace(
"TEXT_UNDERLINE",
"text-decoration: underline;" if text_underline else "",
)
)
self.setStyleSheet(stylesheet)
def sizeHint(self) -> QSize:
"""
Ensure sizeHint respects fixed height and minimal width
"""
hint = super().sizeHint()
return QSize(max(hint.width(), 0), self._height)
class FlexibleHorizontalBar(QWidget):
"""
A simple bucket-like widget that holds other widgets and places them in a horizontal line.
"""
_height: int = 16
_spacing: int = 0
mw: aqt.AnkiQt
def __init__(self, mw: aqt.AnkiQt) -> None:
super().__init__(mw)
self.mw = mw
# Setup Layout
self._layout = QHBoxLayout()
self.setLayout(self._layout)
self._layout.setContentsMargins(0, 0, 0, 0)
self._layout.setSpacing(self._spacing)
self.setMaximumHeight(self._height)
def add_stretch(self, stretch_value: int = 1) -> None:
return self._layout.addStretch(stretch_value)
def add_widget(self, widget: QWidget) -> None:
self._layout.addWidget(widget)
def add_button(self, button: QPushButton, *, on_clicked: Callable) -> QPushButton:
self.add_widget(button)
qconnect(button.clicked, lambda button_checked=False: on_clicked())
return button
def clear_layout(self) -> None:
return clear_layout(self._layout)
def reset(self, is_visible: bool) -> None:
"""
Prepare to show a new set of buttons.
"""
self.setHidden(not is_visible)
self.clear_layout()
class FlexibleButtonsList(FlexibleHorizontalBar):
_spacing: int = 8
class FlexibleBottomBar(FlexibleHorizontalBar):
"""
Bottom bar. Shows answer buttons, answer timer, reps done today.
"""
def __init__(self, mw: aqt.AnkiQt) -> None:
super().__init__(mw)
# Setup Buttons
self.left_bucket = FlexibleButtonsList(self.mw)
self.middle_bucket = FlexibleButtonsList(self.mw)
self.right_bucket = FlexibleButtonsList(self.mw)
# Setup UI
self._setup_ui()
def _setup_ui(self) -> None:
self.add_widget(self.left_bucket)
self.add_stretch()
self.add_widget(self.middle_bucket)
self.add_stretch()
self.add_widget(self.right_bucket)