niumasoftware/ui/dialog_style.py

304 lines
9.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""标准对话框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 ""