1
0
Fork 0
mirror of synced 2024-06-02 02:34:40 +12:00
Rare/rare/widgets/dialogs.py

301 lines
9.1 KiB
Python
Raw Normal View History

import sys
from abc import abstractmethod
from PyQt5.QtCore import Qt, pyqtSlot, QCoreApplication, QSize
from PyQt5.QtGui import QCloseEvent, QKeyEvent, QKeySequence
from PyQt5.QtWidgets import (
QDialog,
QDialogButtonBox,
QApplication,
QPushButton,
QVBoxLayout,
QHBoxLayout,
QWidget,
QLayout, QSpacerItem, QSizePolicy, QLabel,
)
from rare.utils.misc import qta_icon
def game_title(text: str, app_title: str) -> str:
return f"{text} '{app_title}'"
def dialog_title(text: str) -> str:
return f"{text} - {QCoreApplication.instance().applicationName()}"
class BaseDialog(QDialog):
def __init__(self, parent=None):
super(BaseDialog, self).__init__(parent=parent)
self.setAttribute(Qt.WA_DeleteOnClose, True)
self.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint)
self.setWindowModality(Qt.WindowModal)
def setWindowTitle(self, a0):
super().setWindowTitle(dialog_title(a0))
def exec(self):
raise RuntimeError(f"Don't use `exec()` with {type(self).__name__}")
def exec_(self):
raise RuntimeError(f"Don't use `exec_()` with {type(self).__name__}")
# lk: because you will eventually find yourself back here.
# on QDialogs the Esc key closes the dialog through keyPressEvent(),
# which ultimately call `reject()`. Pressing the Enter/Return button
# is a shortcut for pressing the default button and thus calling `accept()`
# In turn both `accept()` and `reject()` evetually call `done()`.
# In the base dialog ignore both. In the subclasses, call the method
# from QDialog if required, not this one.
# `super(BaseDialog, self).keyPressEvent(a0)`
def keyPressEvent(self, a0: QKeyEvent) -> None:
if a0.matches(QKeySequence.Cancel):
a0.ignore()
return
if a0.key() == Qt.Key_Enter or a0.key() == Qt.Key_Return:
a0.ignore()
return
super().keyPressEvent(a0)
# Using the 'X' button on the window manager comes directly here.
# It is a spontaneous event so simply ignore it.
def closeEvent(self, a0: QCloseEvent) -> None:
if a0.spontaneous():
a0.ignore()
return
super().closeEvent(a0)
class ButtonDialog(BaseDialog):
def __init__(self, parent=None):
super(ButtonDialog, self).__init__(parent=parent)
self.subtitle_label = QLabel(self)
self.subtitle_label.setVisible(False)
self.reject_button = QPushButton(self)
self.reject_button.setText(self.tr("Cancel"))
self.reject_button.setIcon(qta_icon("fa.remove"))
self.reject_button.setAutoDefault(False)
self.reject_button.clicked.connect(self.reject)
self.accept_button = QPushButton(self)
self.accept_button.setAutoDefault(False)
self.accept_button.clicked.connect(self.accept)
self.button_layout = QHBoxLayout()
self.button_layout.addWidget(self.reject_button)
self.button_layout.addStretch(20)
self.button_layout.addStretch(1)
self.button_layout.addWidget(self.accept_button)
self.main_layout = QVBoxLayout(self)
self.main_layout.addWidget(self.subtitle_label)
# lk: dirty way to set a minimum width with fixed size constraint
spacer = QSpacerItem(
480, self.main_layout.spacing(),
QSizePolicy.Expanding, QSizePolicy.Fixed
)
self.main_layout.addItem(spacer)
self.main_layout.addLayout(self.button_layout)
self.main_layout.setSizeConstraint(QLayout.SetFixedSize)
self.main_layout.setAlignment(Qt.AlignVCenter)
def close(self):
raise RuntimeError(f"Don't use `close()` with {type(self).__name__}")
def setSubtitle(self, text: str):
self.subtitle_label.setText(f"<b>{text}</b>")
self.subtitle_label.setVisible(True)
def setCentralWidget(self, widget: QWidget):
widget.layout().setContentsMargins(0, 0, 0, 0)
self.main_layout.insertWidget(
self.main_layout.indexOf(self.subtitle_label) + 1,
widget
)
widget.layout().setAlignment(Qt.AlignTop)
def setCentralLayout(self, layout: QLayout):
layout.setContentsMargins(0, 0, 0, 0)
self.main_layout.insertLayout(
self.main_layout.indexOf(self.subtitle_label) + 1,
layout
)
layout.setAlignment(Qt.AlignTop)
@abstractmethod
def accept_handler(self):
raise NotImplementedError
@abstractmethod
def reject_handler(self):
raise NotImplementedError
@abstractmethod
def done_handler(self):
raise NotImplementedError
# These only apply to QDialog. If we move to QWidget for embedded dialogs
# we have to use close() and custom handling.
# lk: Override accept to run our abstract handling method
def accept(self):
self.accept_handler()
super().accept()
# lk: Override reject to run our abstract handling method
def reject(self):
self.reject_handler()
super().reject()
# lk: Override `done()` to to run our abstract handling method
def done(self, a0):
self.done_handler()
super().done(a0)
# lk: Ignore BaseDialog::keyPressEvent and call QDialog::keyPressEvent
# because we handle accept and reject here.
def keyPressEvent(self, a0: QKeyEvent) -> None:
super(BaseDialog, self).keyPressEvent(a0)
# lk: Ignore BaseDialog::closeEvent and call QDialog::closeEvent
# because we handle accept and reject here.
def closeEvent(self, a0: QCloseEvent) -> None:
super(BaseDialog, self).closeEvent(a0)
class ActionDialog(ButtonDialog):
def __init__(self, parent=None):
super(ActionDialog, self).__init__(parent=parent)
self.__reject_close = False
self.action_button = QPushButton(self)
self.action_button.setAutoDefault(True)
self.action_button.clicked.connect(self.action)
self.button_layout.insertWidget(2, self.action_button)
def active(self) -> bool:
return self.__reject_close
def setActive(self, active: bool):
self.reject_button.setDisabled(active)
self.action_button.setDisabled(active)
self.accept_button.setDisabled(active)
self.__reject_close = active
@abstractmethod
def action_handler(self):
raise NotImplementedError
@pyqtSlot()
def action(self):
self.setActive(True)
self.action_handler()
# lk: Ignore all key presses if there is an ongoing action
def keyPressEvent(self, a0: QKeyEvent) -> None:
if self.__reject_close:
a0.ignore()
return
super(BaseDialog, self).keyPressEvent(a0)
# lk: Ignore all closeEvents if there is an ongoing action
def closeEvent(self, a0: QCloseEvent) -> None:
if self.__reject_close:
a0.ignore()
return
super(BaseDialog, self).closeEvent(a0)
__all__ = ["dialog_title", "game_title", "BaseDialog", "ButtonDialog", "ActionDialog"]
class TestDialog(BaseDialog):
def __init__(self, parent=None):
super(TestDialog, self).__init__(parent=parent)
self.accept_button = QPushButton("accept", self)
self.reject_button = QPushButton("reject", self)
self.action_button = QPushButton("action", self)
self.button_box = QDialogButtonBox(Qt.Horizontal, self)
self.button_box.addButton(self.accept_button, QDialogButtonBox.AcceptRole)
self.button_box.addButton(self.reject_button, QDialogButtonBox.RejectRole)
self.button_box.addButton(self.action_button, QDialogButtonBox.ActionRole)
self.button_box.accepted.connect(self.accept)
self.button_box.rejected.connect(self.reject)
layout = QVBoxLayout(self)
layout.addWidget(self.button_box)
self.setMinimumWidth(480)
def setWindowTitle(self, a0):
super().setWindowTitle(dialog_title(a0))
def close(self):
print("in close")
super().close()
def closeEvent(self, a0: QCloseEvent) -> None:
print("in closeEvent")
if a0.spontaneous():
print("is spontaneous")
a0.ignore()
return
if self.reject_close:
a0.ignore()
else:
self._on_close()
super().closeEvent(a0)
# super().closeEvent(a0)
def done(self, a0):
print(f"in done {a0}")
return
super().done(a0)
def accept(self):
print("in accept")
self._on_accept()
# return
# super().accept()
def reject(self):
print("in reject")
self._on_reject()
# return
# super().reject()
def _on_close(self):
print("in _on_close")
def _on_accept(self):
print("in _on_accepted")
# self.close()
def _on_reject(self):
print("in _on_rejected")
self.close()
def keyPressEvent(self, a0: QKeyEvent) -> None:
super(BaseDialog, self).keyPressEvent(a0)
def test_dialog():
app = QApplication(sys.argv)
dlg = TestDialog(None)
dlg.show()
ret = app.exec()
sys.exit(ret)
if __name__ == "__main__":
test_dialog()