mirror of
https://github.com/ankitects/anki.git
synced 2026-01-07 02:53:54 -05:00
186 lines
5.6 KiB
Python
186 lines
5.6 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) -> QWidget:
|
|
self._layout.addWidget(widget)
|
|
return 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)
|
|
|
|
|
|
class FlexibleTimerLabel(QLabel):
|
|
def __init__(self, parent=None) -> None:
|
|
super().__init__(parent)
|
|
self._time = 0 # current time (seconds)
|
|
self._max_time = 0 # maximum time (seconds); 0 means unset
|
|
self._qtimer = QTimer(self)
|
|
self._qtimer.setInterval(1000)
|
|
qconnect(self._qtimer.timeout, self._on_tick)
|
|
self.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
|
|
def start(self, max_time: int) -> None:
|
|
if max_time <= 0:
|
|
raise ValueError("max time should be greater than 0")
|
|
self._max_time = max_time
|
|
self._time = 0
|
|
self._update_display()
|
|
if self._qtimer.isActive():
|
|
self._qtimer.stop()
|
|
self._qtimer.start()
|
|
|
|
def stop(self) -> None:
|
|
if self._qtimer.isActive():
|
|
self._qtimer.stop()
|
|
|
|
def _on_tick(self) -> None:
|
|
self._time = min(self._time + 1, self._max_time)
|
|
self._update_display()
|
|
|
|
def _update_display(self) -> None:
|
|
if self._max_time <= 0:
|
|
raise ValueError("max time should be greater than 0")
|
|
|
|
t = min(self._max_time, self._time)
|
|
m, s = divmod(t, 60)
|
|
s_str = f"{s:02d}"
|
|
time_string = f"{m}:{s_str}"
|
|
|
|
if t >= self._max_time > 0:
|
|
self.setText(f"<font color='red'>{time_string}</font>")
|
|
self.stop()
|
|
else:
|
|
self.setText(time_string)
|