"""标准对话框(QMessageBox / QInputDialog / QFileDialog)与当前主题一致。""" from __future__ import annotations from PyQt6.QtCore import Qt 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; max-width: 560px; }} 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: """让提示框按内容换行并自适应宽高(避免固定宽度截断文字)。""" lbl = msg.findChild(QLabel, "qt_msgbox_label") if lbl is not None: lbl.setWordWrap(True) lbl.setTextFormat(Qt.TextFormat.PlainText) # 无空格长中文也能在边界处断行,而不是整段挤在一行被裁切 try: lbl.setWordWrapMode(QTextOption.WrapMode.WrapAnywhere) except AttributeError: pass lbl.setMinimumWidth(200) lbl.setMaximumWidth(560) lbl.setSizePolicy( QSizePolicy.Policy.Preferred, QSizePolicy.Policy.MinimumExpanding, ) lay = msg.layout() if lay is not None: lay.activate() msg.setSizePolicy( QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred, ) msg.setMinimumSize(0, 0) 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 ""