From 400625975d25702eef4a162d96fcc85265788a65 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Sat, 23 Dec 2023 15:40:08 +0200
Subject: [PATCH] Dialogs: Re-implement Launch and Login dialogs on top of a
few common super-classes
To keep dialogs in a common format and allow them to share the same
properties, three classes of dialogs have been implemented inheriting from
each other.
The classes are `BaseDialog` -> `ButtonDialog` -> `ActionDialog`
* Basedialog: is the basis of all dialogs and is responsible for
rejecting close requests from the window manager and the keyboard.
It also restricts access to `exec()` and `exec_()` because they are harmful.
It serves as the basis of Launch and Login dialogs
* ButtonDialog: is offering buttons for accepting or rejecting the presented
option. It implements its own buttons and exposes abstract methods to
implement handling in them. It restricts access to `close()` because these
dialogs should always product a result.
It is the basis of Uninstall, Selective dialogs.
* ActionDialog: in addition to the ButtonDialog, it offers an action buttom
with to validate the form or to make the dialog unable to close. It serves
as the basis of Install and Move dialogs.
---
rare/components/__init__.py | 11 +-
rare/components/dialogs/launch_dialog.py | 44 ++-
rare/components/dialogs/login/__init__.py | 33 +-
rare/ui/components/dialogs/launch_dialog.py | 2 +-
rare/ui/components/dialogs/launch_dialog.ui | 2 +-
.../components/dialogs/login/login_dialog.py | 20 +-
.../components/dialogs/login/login_dialog.ui | 18 +-
rare/widgets/dialogs.py | 283 ++++++++++++++++++
8 files changed, 352 insertions(+), 61 deletions(-)
create mode 100644 rare/widgets/dialogs.py
diff --git a/rare/components/__init__.py b/rare/components/__init__.py
index 684a1133..e120412f 100644
--- a/rare/components/__init__.py
+++ b/rare/components/__init__.py
@@ -76,14 +76,15 @@ class Rare(RareApp):
@pyqtSlot()
def launch_app(self):
self.launch_dialog = LaunchDialog(parent=None)
- self.launch_dialog.exit_app.connect(self.launch_dialog.close)
- self.launch_dialog.exit_app.connect(self.__on_exit_app)
- self.launch_dialog.start_app.connect(self.start_app)
- self.launch_dialog.start_app.connect(self.launch_dialog.close)
+ self.launch_dialog.rejected.connect(self.__on_exit_app)
+ # lk: the reason we use the `start_app` signal here instead of accepted, is to keep the dialog
+ # until the main window has been created, then we call `accept()` to close the dialog
+ self.launch_dialog.start_app.connect(self.__on_start_app)
+ self.launch_dialog.start_app.connect(self.launch_dialog.accept)
self.launch_dialog.login()
@pyqtSlot()
- def start_app(self):
+ def __on_start_app(self):
self.timer = QTimer()
self.timer.timeout.connect(self.re_login)
self.poke_timer()
diff --git a/rare/components/dialogs/launch_dialog.py b/rare/components/dialogs/launch_dialog.py
index d231625b..e63d7a1b 100644
--- a/rare/components/dialogs/launch_dialog.py
+++ b/rare/components/dialogs/launch_dialog.py
@@ -1,25 +1,24 @@
-import platform
from logging import getLogger
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
-from PyQt5.QtWidgets import QDialog, QApplication
from requests.exceptions import ConnectionError, HTTPError
from rare.components.dialogs.login import LoginDialog
from rare.shared import RareCore
from rare.ui.components.dialogs.launch_dialog import Ui_LaunchDialog
+from rare.widgets.dialogs import BaseDialog
from rare.widgets.elide_label import ElideLabel
logger = getLogger("LaunchDialog")
-class LaunchDialog(QDialog):
- exit_app = pyqtSignal(int)
+class LaunchDialog(BaseDialog):
+ # lk: the reason we use the `start_app` signal here instead of accepted, is to keep the dialog
+ # until the main window has been created, then we call `accept()` to close the dialog
start_app = pyqtSignal()
def __init__(self, parent=None):
super(LaunchDialog, self).__init__(parent=parent)
- self.setAttribute(Qt.WA_DeleteOnClose, True)
self.setWindowFlags(
Qt.Window
| Qt.Dialog
@@ -29,12 +28,10 @@ class LaunchDialog(QDialog):
| Qt.WindowMinimizeButtonHint
| Qt.MSWindowsFixedSizeDialogHint
)
- self.setWindowModality(Qt.WindowModal)
+
self.ui = Ui_LaunchDialog()
self.ui.setupUi(self)
- self.reject_close = True
-
self.progress_info = ElideLabel(parent=self)
self.progress_info.setFixedHeight(False)
self.ui.launch_layout.addWidget(self.progress_info)
@@ -46,9 +43,11 @@ class LaunchDialog(QDialog):
self.args = self.rcore.args()
self.login_dialog = LoginDialog(core=self.core, parent=parent)
+ self.login_dialog.rejected.connect(self.reject)
+ self.login_dialog.accepted.connect(self.do_launch)
def login(self):
- do_launch = True
+ can_launch = True
try:
if self.args.offline:
pass
@@ -56,25 +55,29 @@ class LaunchDialog(QDialog):
# Force an update check and notice in case there are API changes
# self.core.check_for_updates(force=True)
# self.core.force_show_update = True
- if self.core.login():
+ if self.core.login(force_refresh=True):
logger.info("You are logged in")
+ self.login_dialog.close()
else:
- raise ValueError("You are not logged in. Open Login Window")
+ raise ValueError("You are not logged in. Opening login window.")
except ValueError as e:
logger.info(str(e))
# Do not set parent, because it won't show a task bar icon
# Update: Inherit the same parent as LaunchDialog
- do_launch = self.login_dialog.login()
+ can_launch = False
+ self.login_dialog.open()
except (HTTPError, ConnectionError) as e:
logger.warning(e)
self.args.offline = True
finally:
- if do_launch:
- if not self.args.silent:
- self.show()
- self.launch()
- else:
- self.exit_app.emit(0)
+ if can_launch:
+ self.do_launch()
+
+ @pyqtSlot()
+ def do_launch(self):
+ if not self.args.silent:
+ self.open()
+ self.launch()
def launch(self):
self.progress_info.setText(self.tr("Preparing Rare"))
@@ -87,9 +90,4 @@ class LaunchDialog(QDialog):
def __on_completed(self):
logger.info("App starting")
- self.reject_close = False
self.start_app.emit()
-
- def reject(self) -> None:
- if not self.reject_close:
- super(LaunchDialog, self).reject()
diff --git a/rare/components/dialogs/login/__init__.py b/rare/components/dialogs/login/__init__.py
index 42ba6452..5c2158e7 100644
--- a/rare/components/dialogs/login/__init__.py
+++ b/rare/components/dialogs/login/__init__.py
@@ -1,12 +1,14 @@
from logging import getLogger
-from PyQt5.QtCore import Qt, pyqtSignal
-from PyQt5.QtWidgets import QLayout, QDialog, QMessageBox, QFrame
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import QLayout, QMessageBox, QFrame
from legendary.core import LegendaryCore
from rare.shared import ArgumentsSingleton
from rare.ui.components.dialogs.login.landing_page import Ui_LandingPage
from rare.ui.components.dialogs.login.login_dialog import Ui_LoginDialog
+from rare.utils.misc import icon
+from rare.widgets.dialogs import BaseDialog
from rare.widgets.sliding_stack import SlidingStackedWidget
from .browser_login import BrowserLogin
from .import_login import ImportLogin
@@ -22,12 +24,10 @@ class LandingPage(QFrame):
self.ui.setupUi(self)
-class LoginDialog(QDialog):
- exit_app: pyqtSignal = pyqtSignal(int)
+class LoginDialog(BaseDialog):
def __init__(self, core: LegendaryCore, parent=None):
super(LoginDialog, self).__init__(parent=parent)
- self.setAttribute(Qt.WA_DeleteOnClose, True)
self.setWindowFlags(
Qt.Window
| Qt.Dialog
@@ -38,7 +38,7 @@ class LoginDialog(QDialog):
| Qt.WindowCloseButtonHint
| Qt.MSWindowsFixedSizeDialogHint
)
- self.setWindowModality(Qt.WindowModal)
+
self.ui = Ui_LoginDialog()
self.ui.setupUi(self)
@@ -93,13 +93,22 @@ class LoginDialog(QDialog):
self.landing_page.ui.login_import_radio.clicked.connect(lambda: self.ui.next_button.setEnabled(True))
self.landing_page.ui.login_import_radio.clicked.connect(self.import_radio_clicked)
- self.ui.exit_button.clicked.connect(self.close)
+ self.ui.exit_button.clicked.connect(self.reject)
self.ui.back_button.clicked.connect(self.back_clicked)
self.ui.next_button.clicked.connect(self.next_clicked)
self.login_stack.setCurrentWidget(self.landing_page)
- self.layout().setSizeConstraint(QLayout.SetFixedSize)
+ self.ui.exit_button.setIcon(icon("fa.remove"))
+ self.ui.back_button.setIcon(icon("fa.chevron-left"))
+ self.ui.next_button.setIcon(icon("fa.chevron-right"))
+
+ # lk: Set next as the default button only to stop closing the dialog when pressing enter
+ self.ui.exit_button.setAutoDefault(False)
+ self.ui.back_button.setAutoDefault(False)
+ self.ui.next_button.setAutoDefault(True)
+
+ self.ui.main_layout.setSizeConstraint(QLayout.SetFixedSize)
def back_clicked(self):
self.ui.back_button.setEnabled(False)
@@ -129,15 +138,14 @@ class LoginDialog(QDialog):
def login(self):
if self.args.test_start:
- return False
- self.exec_()
- return self.logged_in
+ self.reject()
+ self.open()
def login_successful(self):
try:
if self.core.login():
self.logged_in = True
- self.close()
+ self.accept()
else:
raise ValueError("Login failed.")
except Exception as e:
@@ -146,3 +154,4 @@ class LoginDialog(QDialog):
self.ui.next_button.setEnabled(False)
self.logged_in = False
QMessageBox.warning(None, self.tr("Login error"), str(e))
+
diff --git a/rare/ui/components/dialogs/launch_dialog.py b/rare/ui/components/dialogs/launch_dialog.py
index 9c30344e..0808126f 100644
--- a/rare/ui/components/dialogs/launch_dialog.py
+++ b/rare/ui/components/dialogs/launch_dialog.py
@@ -36,7 +36,7 @@ class Ui_LaunchDialog(object):
def retranslateUi(self, LaunchDialog):
_translate = QtCore.QCoreApplication.translate
- LaunchDialog.setWindowTitle(_translate("LaunchDialog", "Launching - Rare"))
+ LaunchDialog.setWindowTitle(_translate("LaunchDialog", "Launching"))
self.title_label.setText(_translate("LaunchDialog", "
Launching Rare
"))
diff --git a/rare/ui/components/dialogs/launch_dialog.ui b/rare/ui/components/dialogs/launch_dialog.ui
index b8a5838e..ff9f6d34 100644
--- a/rare/ui/components/dialogs/launch_dialog.ui
+++ b/rare/ui/components/dialogs/launch_dialog.ui
@@ -29,7 +29,7 @@
- Launching - Rare
+ Launching
-
diff --git a/rare/ui/components/dialogs/login/login_dialog.py b/rare/ui/components/dialogs/login/login_dialog.py
index 76eb3310..b158a420 100644
--- a/rare/ui/components/dialogs/login/login_dialog.py
+++ b/rare/ui/components/dialogs/login/login_dialog.py
@@ -15,38 +15,38 @@ class Ui_LoginDialog(object):
def setupUi(self, LoginDialog):
LoginDialog.setObjectName("LoginDialog")
LoginDialog.resize(241, 128)
- self.login_layout = QtWidgets.QVBoxLayout(LoginDialog)
- self.login_layout.setObjectName("login_layout")
+ self.main_layout = QtWidgets.QVBoxLayout(LoginDialog)
+ self.main_layout.setObjectName("main_layout")
spacerItem = QtWidgets.QSpacerItem(0, 17, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
- self.login_layout.addItem(spacerItem)
+ self.main_layout.addItem(spacerItem)
self.welcome_label = QtWidgets.QLabel(LoginDialog)
self.welcome_label.setObjectName("welcome_label")
- self.login_layout.addWidget(self.welcome_label)
+ self.main_layout.addWidget(self.welcome_label)
spacerItem1 = QtWidgets.QSpacerItem(0, 17, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
- self.login_layout.addItem(spacerItem1)
+ self.main_layout.addItem(spacerItem1)
self.login_stack_layout = QtWidgets.QVBoxLayout()
self.login_stack_layout.setObjectName("login_stack_layout")
- self.login_layout.addLayout(self.login_stack_layout)
+ self.main_layout.addLayout(self.login_stack_layout)
self.button_layout = QtWidgets.QHBoxLayout()
self.button_layout.setObjectName("button_layout")
- spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
- self.button_layout.addItem(spacerItem2)
self.exit_button = QtWidgets.QPushButton(LoginDialog)
self.exit_button.setObjectName("exit_button")
self.button_layout.addWidget(self.exit_button)
+ spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.button_layout.addItem(spacerItem2)
self.back_button = QtWidgets.QPushButton(LoginDialog)
self.back_button.setObjectName("back_button")
self.button_layout.addWidget(self.back_button)
self.next_button = QtWidgets.QPushButton(LoginDialog)
self.next_button.setObjectName("next_button")
self.button_layout.addWidget(self.next_button)
- self.login_layout.addLayout(self.button_layout)
+ self.main_layout.addLayout(self.button_layout)
self.retranslateUi(LoginDialog)
def retranslateUi(self, LoginDialog):
_translate = QtCore.QCoreApplication.translate
- LoginDialog.setWindowTitle(_translate("LoginDialog", "Login - Rare"))
+ LoginDialog.setWindowTitle(_translate("LoginDialog", "Login"))
self.welcome_label.setText(_translate("LoginDialog", "
Welcome to Rare
"))
self.exit_button.setText(_translate("LoginDialog", "Exit"))
self.back_button.setText(_translate("LoginDialog", "Back"))
diff --git a/rare/ui/components/dialogs/login/login_dialog.ui b/rare/ui/components/dialogs/login/login_dialog.ui
index 5419629b..6ca92710 100644
--- a/rare/ui/components/dialogs/login/login_dialog.ui
+++ b/rare/ui/components/dialogs/login/login_dialog.ui
@@ -11,9 +11,9 @@
- Login - Rare
+ Login
-
+
-
@@ -58,6 +58,13 @@
-
+
-
+
+
+ Exit
+
+
+
-
@@ -71,13 +78,6 @@
- -
-
-
- Exit
-
-
-
-
diff --git a/rare/widgets/dialogs.py b/rare/widgets/dialogs.py
new file mode 100644
index 00000000..a5a97db8
--- /dev/null
+++ b/rare/widgets/dialogs.py
@@ -0,0 +1,283 @@
+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,
+)
+
+from rare.utils.misc import icon
+
+
+def dialog_title_game(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.reject_button = QPushButton(self)
+ self.reject_button.setText(self.tr("Cancel"))
+ self.reject_button.setIcon(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)
+ # 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)
+
+ def close(self):
+ raise RuntimeError(f"Don't use `close()` with {type(self).__name__}")
+
+ def setCentralWidget(self, widget: QWidget):
+ widget.layout().setContentsMargins(0, 0, 0, 0)
+ self.main_layout.insertWidget(0, widget)
+
+ def setCentralLayout(self, layout: QLayout):
+ layout.setContentsMargins(0, 0, 0, 0)
+ self.main_layout.insertLayout(0, layout)
+
+ @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", "dialog_title_game", "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()