mirror of
https://github.com/ankitects/anki.git
synced 2025-12-10 05:16:55 -05:00
Add MessageBox class and associated funcs to aqt.utils and update the first few callers (#2010)
* Add MessageBox class and associated funcs to aqt.utils and update some callers in aqt.sync and aqt.addons * Cleanup imports in aqt.sync * Fix return values for ask_user and ask_user_dialog * Fix wrong argument name in aqt.utils.ask_user * Add type annotations to **kwargs in utils.py * Type annotation for callback in aqt.sync.full_sync * MessageBox accepts StandardButton in addition to str, fix linting issues * Assess default buttons in correct order and return correct button name in MessageBox * Add explicit Optionals in aqt.utils * Pass button index to callback in MessageBox * Update type hints for aqt.utils.MessageBox * Use Sequence for aqt.utils.MessageBox buttons arg * default_button > default_yes in aqt.utils.ask_user * Dark mode question icon in aqt.utils.MessageBox
This commit is contained in:
parent
5f6ac1a916
commit
92171e25e6
3 changed files with 181 additions and 36 deletions
|
|
@ -43,6 +43,7 @@ from aqt.utils import (
|
|||
saveGeom,
|
||||
saveSplitter,
|
||||
send_to_trash,
|
||||
show_info,
|
||||
showInfo,
|
||||
showWarning,
|
||||
tooltip,
|
||||
|
|
@ -862,14 +863,14 @@ class AddonsDialog(QDialog):
|
|||
def onlyOneSelected(self) -> str | None:
|
||||
dirs = self.selectedAddons()
|
||||
if len(dirs) != 1:
|
||||
showInfo(tr.addons_please_select_a_single_addon_first())
|
||||
show_info(tr.addons_please_select_a_single_addon_first())
|
||||
return None
|
||||
return dirs[0]
|
||||
|
||||
def selected_addon_meta(self) -> AddonMeta | None:
|
||||
idxs = [x.row() for x in self.form.addonList.selectedIndexes()]
|
||||
if len(idxs) != 1:
|
||||
showInfo(tr.addons_please_select_a_single_addon_first())
|
||||
show_info(tr.addons_please_select_a_single_addon_first())
|
||||
return None
|
||||
return self.addons[idxs[0]]
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
import os
|
||||
from concurrent.futures import Future
|
||||
from typing import Callable
|
||||
|
|
@ -27,8 +26,8 @@ from aqt.qt import (
|
|||
qconnect,
|
||||
)
|
||||
from aqt.utils import (
|
||||
ask_user_dialog,
|
||||
askUser,
|
||||
askUserDialog,
|
||||
disable_help_button,
|
||||
showText,
|
||||
showWarning,
|
||||
|
|
@ -36,12 +35,6 @@ from aqt.utils import (
|
|||
)
|
||||
|
||||
|
||||
class FullSyncChoice(enum.Enum):
|
||||
CANCEL = 0
|
||||
UPLOAD = 1
|
||||
DOWNLOAD = 2
|
||||
|
||||
|
||||
def get_sync_status(
|
||||
mw: aqt.main.AnkiQt, callback: Callable[[SyncStatus], None]
|
||||
) -> None:
|
||||
|
|
@ -135,13 +128,26 @@ def full_sync(
|
|||
elif out.required == out.FULL_UPLOAD:
|
||||
full_upload(mw, on_done)
|
||||
else:
|
||||
choice = ask_user_to_decide_direction()
|
||||
if choice == FullSyncChoice.UPLOAD:
|
||||
full_upload(mw, on_done)
|
||||
elif choice == FullSyncChoice.DOWNLOAD:
|
||||
full_download(mw, on_done)
|
||||
else:
|
||||
on_done()
|
||||
button_labels: list[str] = [
|
||||
tr.sync_upload_to_ankiweb(),
|
||||
tr.sync_download_from_ankiweb(),
|
||||
tr.sync_cancel_button(),
|
||||
]
|
||||
|
||||
def callback(choice: int) -> None:
|
||||
if choice == 0:
|
||||
full_upload(mw, on_done)
|
||||
elif choice == 1:
|
||||
full_download(mw, on_done)
|
||||
else:
|
||||
on_done()
|
||||
|
||||
ask_user_dialog(
|
||||
tr.sync_conflict_explanation(),
|
||||
callback=callback,
|
||||
buttons=button_labels,
|
||||
default_button=2,
|
||||
)
|
||||
|
||||
|
||||
def confirm_full_download(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None:
|
||||
|
|
@ -277,23 +283,6 @@ def sync_login(
|
|||
)
|
||||
|
||||
|
||||
def ask_user_to_decide_direction() -> FullSyncChoice:
|
||||
button_labels = [
|
||||
tr.sync_upload_to_ankiweb(),
|
||||
tr.sync_download_from_ankiweb(),
|
||||
tr.sync_cancel_button(),
|
||||
]
|
||||
diag = askUserDialog(tr.sync_conflict_explanation(), button_labels)
|
||||
diag.setDefault(2)
|
||||
ret = diag.run()
|
||||
if ret == button_labels[0]:
|
||||
return FullSyncChoice.UPLOAD
|
||||
elif ret == button_labels[1]:
|
||||
return FullSyncChoice.DOWNLOAD
|
||||
else:
|
||||
return FullSyncChoice.CANCEL
|
||||
|
||||
|
||||
def get_id_and_pass_from_user(
|
||||
mw: aqt.main.AnkiQt, username: str = "", password: str = ""
|
||||
) -> tuple[str, str]:
|
||||
|
|
|
|||
159
qt/aqt/utils.py
159
qt/aqt/utils.py
|
|
@ -7,9 +7,9 @@ import re
|
|||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from functools import wraps
|
||||
from functools import partial, wraps
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Literal, Sequence, no_type_check
|
||||
from typing import TYPE_CHECKING, Any, Callable, Literal, Sequence, Union, no_type_check
|
||||
|
||||
from send2trash import send2trash
|
||||
|
||||
|
|
@ -25,6 +25,56 @@ from anki.utils import (
|
|||
version_with_build,
|
||||
)
|
||||
from aqt.qt import *
|
||||
from aqt.qt import (
|
||||
PYQT_VERSION_STR,
|
||||
QT_VERSION_STR,
|
||||
QAction,
|
||||
QApplication,
|
||||
QCheckBox,
|
||||
QColor,
|
||||
QComboBox,
|
||||
QDesktopServices,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QEvent,
|
||||
QFileDialog,
|
||||
QFrame,
|
||||
QHeaderView,
|
||||
QIcon,
|
||||
QKeySequence,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QListWidget,
|
||||
QMainWindow,
|
||||
QMenu,
|
||||
QMessageBox,
|
||||
QMouseEvent,
|
||||
QNativeGestureEvent,
|
||||
QOffscreenSurface,
|
||||
QOpenGLContext,
|
||||
QPalette,
|
||||
QPixmap,
|
||||
QPlainTextEdit,
|
||||
QPoint,
|
||||
QPushButton,
|
||||
QShortcut,
|
||||
QSize,
|
||||
QSplitter,
|
||||
QStandardPaths,
|
||||
Qt,
|
||||
QTextBrowser,
|
||||
QTextOption,
|
||||
QTimer,
|
||||
QUrl,
|
||||
QVBoxLayout,
|
||||
QWheelEvent,
|
||||
QWidget,
|
||||
pyqtSlot,
|
||||
qconnect,
|
||||
qtmajor,
|
||||
qtminor,
|
||||
traceback,
|
||||
)
|
||||
from aqt.theme import theme_manager
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -70,6 +120,111 @@ def openLink(link: str | QUrl) -> None:
|
|||
QDesktopServices.openUrl(QUrl(link))
|
||||
|
||||
|
||||
class MessageBox(QMessageBox):
|
||||
def __init__(
|
||||
self,
|
||||
text: str,
|
||||
callback: Callable[[int], None] | None = None,
|
||||
parent: QWidget | None = None,
|
||||
icon: QMessageBox.Icon = QMessageBox.Icon.NoIcon,
|
||||
help: HelpPageArgument | None = None,
|
||||
title: str = "Anki",
|
||||
buttons: Sequence[str | QMessageBox.StandardButton] | None = None,
|
||||
default_button: int = 0,
|
||||
textFormat: Qt.TextFormat = Qt.TextFormat.PlainText,
|
||||
) -> None:
|
||||
parent = parent or aqt.mw.app.activeWindow() or aqt.mw
|
||||
super().__init__(parent)
|
||||
self.setText(text)
|
||||
self.setWindowTitle(title)
|
||||
self.setWindowModality(Qt.WindowModality.WindowModal)
|
||||
self.setIcon(icon)
|
||||
if icon == QMessageBox.Icon.Question and theme_manager.night_mode:
|
||||
img = self.iconPixmap().toImage()
|
||||
img.invertPixels()
|
||||
self.setIconPixmap(QPixmap(img))
|
||||
self.setTextFormat(textFormat)
|
||||
if buttons is None:
|
||||
buttons = [QMessageBox.StandardButton.Ok]
|
||||
for i, button in enumerate(buttons):
|
||||
if isinstance(button, str):
|
||||
b = self.addButton(button, QMessageBox.ButtonRole.ActionRole)
|
||||
elif isinstance(button, QMessageBox.StandardButton):
|
||||
b = self.addButton(button)
|
||||
else:
|
||||
continue
|
||||
if callback is not None:
|
||||
qconnect(b.clicked, partial(callback, i))
|
||||
if i == default_button:
|
||||
self.setDefaultButton(b)
|
||||
if help is not None:
|
||||
b = self.addButton(QMessageBox.StandardButton.Help)
|
||||
qconnect(b.clicked, lambda: openHelp(help))
|
||||
self.open()
|
||||
|
||||
|
||||
def ask_user(
|
||||
text: str,
|
||||
callback: Callable[[bool], None],
|
||||
defaults_yes: bool = True,
|
||||
**kwargs: Any,
|
||||
) -> MessageBox:
|
||||
"Shows a yes/no question, passes the answer to the callback function as a bool."
|
||||
return MessageBox(
|
||||
text,
|
||||
callback=lambda response: callback(not response),
|
||||
icon=QMessageBox.Icon.Question,
|
||||
buttons=[QMessageBox.StandardButton.Yes, QMessageBox.StandardButton.No],
|
||||
default_button=not defaults_yes,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def ask_user_dialog(
|
||||
text: str,
|
||||
callback: Callable[[int], None],
|
||||
buttons: Sequence[str | QMessageBox.StandardButton] | None = None,
|
||||
default_button: int = 1,
|
||||
**kwargs: Any,
|
||||
) -> MessageBox:
|
||||
"Shows a question to the user, passes the index of the button clicked to the callback."
|
||||
if buttons is None:
|
||||
buttons = [QMessageBox.StandardButton.Yes, QMessageBox.StandardButton.No]
|
||||
return MessageBox(
|
||||
text,
|
||||
callback=callback,
|
||||
icon=QMessageBox.Icon.Question,
|
||||
buttons=buttons,
|
||||
default_button=default_button,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def show_info(text: str, callback: Callable | None = None, **kwargs: Any) -> MessageBox:
|
||||
"Show a small info window with an OK button."
|
||||
if "icon" not in kwargs:
|
||||
kwargs["icon"] = QMessageBox.Icon.Information
|
||||
return MessageBox(
|
||||
text,
|
||||
callback=(lambda _: callback()) if callback is not None else None,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def show_warning(
|
||||
text: str, callback: Callable | None = None, **kwargs: Any
|
||||
) -> MessageBox:
|
||||
"Show a small warning window with an OK button."
|
||||
return show_info(text, icon=QMessageBox.Icon.Warning, callback=callback, **kwargs)
|
||||
|
||||
|
||||
def show_critical(
|
||||
text: str, callback: Callable | None = None, **kwargs: Any
|
||||
) -> MessageBox:
|
||||
"Show a small critical error window with an OK button."
|
||||
return show_info(text, icon=QMessageBox.Icon.Critical, callback=callback, **kwargs)
|
||||
|
||||
|
||||
def showWarning(
|
||||
text: str,
|
||||
parent: QWidget | None = None,
|
||||
|
|
|
|||
Loading…
Reference in a new issue