304 lines
9.0 KiB
Python
304 lines
9.0 KiB
Python
"""标准对话框(QMessageBox / QInputDialog / QFileDialog)与当前主题一致。"""
|
|
from __future__ import annotations
|
|
|
|
from PyQt6.QtCore import Qt, QTimer
|
|
from PyQt6.QtGui import QTextOption
|
|
from PyQt6.QtWidgets import (
|
|
QFileDialog,
|
|
QInputDialog,
|
|
QMessageBox,
|
|
QWidget,
|
|
QDialog,
|
|
QLabel,
|
|
QSizePolicy,
|
|
)
|
|
|
|
import ui.theme as theme
|
|
|
|
|
|
def stylesheet() -> str:
|
|
t = theme.current()
|
|
bg = t["menu_bg"]
|
|
fg = t["menu_color"]
|
|
border = t["menu_border"]
|
|
hover = t["menu_selected"]
|
|
inp = t["search_bg"]
|
|
inp_b = t["search_border"]
|
|
focus = t["search_focus"]
|
|
return f"""
|
|
QMessageBox {{
|
|
background-color: {bg};
|
|
color: {fg};
|
|
}}
|
|
QMessageBox QLabel {{
|
|
color: {fg};
|
|
background: transparent;
|
|
min-width: 0px;
|
|
}}
|
|
QMessageBox QLabel#qt_msgbox_label {{
|
|
min-width: 200px;
|
|
}}
|
|
QMessageBox QLabel#qt_msgboxex_icon_label {{
|
|
min-width: 28px;
|
|
max-width: 28px;
|
|
}}
|
|
QMessageBox QPushButton {{
|
|
min-width: 64px;
|
|
padding: 6px 14px;
|
|
border: 1px solid {border};
|
|
border-radius: 5px;
|
|
background: {inp};
|
|
color: {fg};
|
|
}}
|
|
QMessageBox QPushButton:hover {{
|
|
background: {hover};
|
|
}}
|
|
QMessageBox QPushButton:default {{
|
|
border-color: {focus};
|
|
}}
|
|
QInputDialog {{
|
|
background-color: {bg};
|
|
color: {fg};
|
|
}}
|
|
QInputDialog QLabel {{
|
|
color: {fg};
|
|
background: transparent;
|
|
}}
|
|
QInputDialog QLineEdit {{
|
|
background: {inp};
|
|
border: 1px solid {inp_b};
|
|
border-radius: 5px;
|
|
padding: 6px 8px;
|
|
color: {fg};
|
|
min-width: 260px;
|
|
}}
|
|
QInputDialog QLineEdit:focus {{
|
|
border-color: {focus};
|
|
}}
|
|
QInputDialog QPushButton {{
|
|
min-width: 72px;
|
|
padding: 6px 14px;
|
|
border: 1px solid {border};
|
|
border-radius: 5px;
|
|
background: {inp};
|
|
color: {fg};
|
|
}}
|
|
QInputDialog QPushButton:hover {{
|
|
background: {hover};
|
|
}}
|
|
QFileDialog {{
|
|
background-color: {bg};
|
|
color: {fg};
|
|
}}
|
|
QFileDialog QLabel {{
|
|
color: {fg};
|
|
background: transparent;
|
|
}}
|
|
QFileDialog QLineEdit, QFileDialog QComboBox {{
|
|
background: {inp};
|
|
border: 1px solid {inp_b};
|
|
border-radius: 4px;
|
|
padding: 4px 6px;
|
|
color: {fg};
|
|
}}
|
|
QFileDialog QLineEdit:focus, QFileDialog QComboBox:focus {{
|
|
border-color: {focus};
|
|
}}
|
|
QFileDialog QTreeView, QFileDialog QListView, QFileDialog QTableView {{
|
|
background: {inp};
|
|
border: 1px solid {inp_b};
|
|
border-radius: 4px;
|
|
color: {fg};
|
|
outline: none;
|
|
}}
|
|
QFileDialog QTreeView::item:selected, QFileDialog QListView::item:selected {{
|
|
background: {hover};
|
|
}}
|
|
QFileDialog QPushButton {{
|
|
min-width: 72px;
|
|
padding: 6px 12px;
|
|
border: 1px solid {border};
|
|
border-radius: 5px;
|
|
background: {inp};
|
|
color: {fg};
|
|
}}
|
|
QFileDialog QPushButton:hover {{
|
|
background: {hover};
|
|
}}
|
|
QFileDialog QComboBox QAbstractItemView {{
|
|
background: {bg};
|
|
color: {fg};
|
|
border: 1px solid {border};
|
|
}}
|
|
"""
|
|
|
|
|
|
def _apply(w: QWidget | None) -> None:
|
|
if w is not None:
|
|
w.setStyleSheet(stylesheet())
|
|
|
|
|
|
def _prepare_qmessagebox(msg: QMessageBox) -> None:
|
|
"""让提示框按内容换行并自适应宽高。"""
|
|
def _tune_labels() -> None:
|
|
# QMessageBox 里通常有两段文字:
|
|
# - qt_msgbox_label(主文本)
|
|
# - qt_msgbox_informativelabel(辅助文本/更长的提示)
|
|
# 部分平台会在显示/布局后才创建这些 label,因此这里支持延迟执行一次。
|
|
for obj_name in ("qt_msgbox_label", "qt_msgbox_informativelabel"):
|
|
w = msg.findChild(QWidget, obj_name)
|
|
if w is None:
|
|
continue
|
|
|
|
if hasattr(w, "setWordWrap"):
|
|
try:
|
|
w.setWordWrap(True) # type: ignore[attr-defined]
|
|
except Exception:
|
|
pass
|
|
if hasattr(w, "setTextFormat"):
|
|
try:
|
|
w.setTextFormat(Qt.TextFormat.PlainText) # type: ignore[attr-defined]
|
|
except Exception:
|
|
pass
|
|
if hasattr(w, "setWordWrapMode"):
|
|
try:
|
|
w.setWordWrapMode(QTextOption.WrapMode.WrapAnywhere) # type: ignore[attr-defined]
|
|
except Exception:
|
|
pass
|
|
if hasattr(w, "setTextElideMode"):
|
|
try:
|
|
w.setTextElideMode(Qt.TextElideMode.ElideNone) # type: ignore[attr-defined]
|
|
except Exception:
|
|
pass
|
|
if hasattr(w, "setMinimumWidth"):
|
|
try:
|
|
w.setMinimumWidth(0) # type: ignore[attr-defined]
|
|
except Exception:
|
|
pass
|
|
if hasattr(w, "setMaximumWidth"):
|
|
try:
|
|
w.setMaximumWidth(16777215) # type: ignore[attr-defined]
|
|
except Exception:
|
|
pass
|
|
if hasattr(w, "setSizePolicy"):
|
|
try:
|
|
w.setSizePolicy(
|
|
QSizePolicy.Policy.Expanding,
|
|
QSizePolicy.Policy.MinimumExpanding,
|
|
) # type: ignore[attr-defined]
|
|
except Exception:
|
|
pass
|
|
msg.setSizePolicy(
|
|
QSizePolicy.Policy.Preferred,
|
|
QSizePolicy.Policy.Preferred,
|
|
)
|
|
msg.setMinimumSize(0, 0)
|
|
msg.setMaximumSize(16777215, 16777215)
|
|
|
|
lay = msg.layout()
|
|
if lay is not None:
|
|
# QMessageBox 内部通常是 QGridLayout:第 1 列是文本列
|
|
# 某些主题/平台会让文本列偏窄从而触发省略,这里显式拉伸该列并设置最小宽度。
|
|
try:
|
|
lay.setColumnStretch(1, 1)
|
|
except Exception:
|
|
pass
|
|
lay.activate()
|
|
_tune_labels()
|
|
msg.adjustSize()
|
|
QTimer.singleShot(0, lambda: (_tune_labels(), msg.adjustSize()))
|
|
|
|
|
|
def question(
|
|
parent: QWidget | None,
|
|
title: str,
|
|
text: str,
|
|
*,
|
|
buttons: QMessageBox.StandardButton = (
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
),
|
|
default_button: QMessageBox.StandardButton | None = None,
|
|
icon: QMessageBox.Icon = QMessageBox.Icon.Question,
|
|
) -> QMessageBox.StandardButton:
|
|
msg = QMessageBox(parent)
|
|
msg.setWindowTitle(title)
|
|
msg.setText(text)
|
|
msg.setIcon(icon)
|
|
msg.setStandardButtons(buttons)
|
|
if default_button is not None:
|
|
msg.setDefaultButton(default_button)
|
|
_apply(msg)
|
|
_prepare_qmessagebox(msg)
|
|
return QMessageBox.StandardButton(msg.exec())
|
|
|
|
|
|
def warning(parent: QWidget | None, title: str, text: str) -> None:
|
|
msg = QMessageBox(parent)
|
|
msg.setWindowTitle(title)
|
|
msg.setText(text)
|
|
msg.setIcon(QMessageBox.Icon.Warning)
|
|
msg.setStandardButtons(QMessageBox.StandardButton.Ok)
|
|
_apply(msg)
|
|
_prepare_qmessagebox(msg)
|
|
msg.exec()
|
|
|
|
|
|
def information(parent: QWidget | None, title: str, text: str) -> None:
|
|
msg = QMessageBox(parent)
|
|
msg.setWindowTitle(title)
|
|
msg.setText(text)
|
|
msg.setIcon(QMessageBox.Icon.Information)
|
|
msg.setStandardButtons(QMessageBox.StandardButton.Ok)
|
|
_apply(msg)
|
|
_prepare_qmessagebox(msg)
|
|
msg.exec()
|
|
|
|
|
|
def get_text(
|
|
parent: QWidget | None,
|
|
title: str,
|
|
label: str,
|
|
text: str = "",
|
|
) -> tuple[str, bool]:
|
|
d = QInputDialog(parent)
|
|
d.setWindowTitle(title)
|
|
d.setLabelText(label)
|
|
d.setTextValue(text)
|
|
_apply(d)
|
|
ok = d.exec() == QDialog.DialogCode.Accepted
|
|
return d.textValue(), ok
|
|
|
|
|
|
def get_open_file_name(
|
|
parent: QWidget | None,
|
|
caption: str,
|
|
directory: str,
|
|
filter_str: str,
|
|
) -> tuple[str, str]:
|
|
fd = QFileDialog(parent, caption, directory, filter_str)
|
|
fd.setOption(QFileDialog.Option.DontUseNativeDialog, True)
|
|
fd.setFileMode(QFileDialog.FileMode.ExistingFile)
|
|
_apply(fd)
|
|
if fd.exec() == QDialog.DialogCode.Accepted:
|
|
files = fd.selectedFiles()
|
|
if files:
|
|
return files[0], fd.selectedNameFilter()
|
|
return "", ""
|
|
|
|
|
|
def get_existing_directory(
|
|
parent: QWidget | None,
|
|
caption: str,
|
|
directory: str = "",
|
|
) -> str:
|
|
fd = QFileDialog(parent, caption, directory)
|
|
fd.setOption(QFileDialog.Option.DontUseNativeDialog, True)
|
|
fd.setFileMode(QFileDialog.FileMode.Directory)
|
|
fd.setOption(QFileDialog.Option.ShowDirsOnly, True)
|
|
_apply(fd)
|
|
if fd.exec() == QDialog.DialogCode.Accepted:
|
|
files = fd.selectedFiles()
|
|
if files:
|
|
return files[0]
|
|
return ""
|