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()