diff --git a/gui/main_ui.py b/gui/main_ui.py index 04add64..6ffd3cb 100755 --- a/gui/main_ui.py +++ b/gui/main_ui.py @@ -12,12 +12,13 @@ from PyQt6.QtWidgets import ( QApplication, QMainWindow, QStackedWidget, QLabel, QPushButton, QDialog, QVBoxLayout, QHBoxLayout, QMessageBox ) -from PyQt6.QtGui import QPixmap, QFont, QFontDatabase, QIcon +from PyQt6.QtGui import QPixmap, QFont, QFontDatabase from PyQt6 import QtGui from PyQt6.QtCore import Qt, QTimer, QEvent from core.Constants import Constants from core.controllers.ConfigurationController import ConfigurationController +from core.controllers.ProfileController import ProfileController from core.Errors import UnknownConnectionTypeError from core.errors.logger import logger as core_logger @@ -64,7 +65,6 @@ from gui.v2.actions.ticket_failure import ( clear_ticket_verification_failure, ) from gui.v2.actions.should_be_synchronized import should_be_synchronized -from gui.v2.ui.popups.message_box import style_message_box, mark_confirm_button class CustomWindow(QMainWindow): @@ -76,8 +76,7 @@ class CustomWindow(QMainWindow): gui_dir = os.path.dirname(os.path.abspath(__file__)) font_path = os.path.join(gui_dir, 'resources', 'fonts') - # # Get's SQLAlchemy going, - # orm.start() + orm.start() retro_gaming_path = os.path.join(font_path, 'retro-gaming.ttf') font_id = QFontDatabase.addApplicationFont(retro_gaming_path) @@ -129,6 +128,9 @@ class CustomWindow(QMainWindow): self.animation_step = 0 self.page_history = [] self.log_path = None + self._close_confirmation_pending = False + self._close_disconnect_in_progress = False + self._closing_after_disconnect = False self.setFixedSize(800, 570) @@ -628,17 +630,94 @@ class CustomWindow(QMainWindow): def closeEvent(self, event=None): core_logger.info("HydraVeil application closing") - # Close SQLAlchemy, - orm.stop() + if self._closing_after_disconnect: + orm.stop() + if event is not None: + event.accept() + return - connected_profiles = self.connection_manager.get_connected_profiles() + if self._close_confirmation_pending or self._close_disconnect_in_progress: + if event is not None: + event.ignore() + return + + connected_profiles = self._get_enabled_profile_ids() if connected_profiles: - self.update_status('Profiles are still connected (disconnect flow lands in Chunk 9).') if event is not None: - event.accept() - else: - if event is not None: - event.accept() + event.ignore() + self._show_close_disconnect_confirmation(connected_profiles) + return + + orm.stop() + if event is not None: + event.accept() + + def _get_enabled_profile_ids(self): + try: + profiles = ProfileController.get_all() + except Exception: + return self.connection_manager.get_connected_profiles() + + connected_profiles = [] + self.connection_manager._connected_profiles.clear() + for profile_id, profile in profiles.items(): + try: + if ProfileController.is_enabled(profile): + connected_profiles.append(profile_id) + self.connection_manager.add_connected_profile(profile_id) + except Exception: + pass + return connected_profiles + + def _show_close_disconnect_confirmation(self, connected_profiles): + from gui.v2.ui.popups.confirmation_popup import ConfirmationPopup + + self._close_confirmation_pending = True + message = self._create_close_disconnect_message(connected_profiles) + self.popup = ConfirmationPopup( + self, message=message, action_button_text="Exit", cancel_button_text="Cancel") + self.popup.setWindowModality(Qt.WindowModality.ApplicationModal) + self.popup.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) + self.popup.finished.connect( + lambda result: self._handle_close_disconnect_result(result, connected_profiles)) + self.popup.destroyed.connect( + lambda *_: self._handle_close_disconnect_popup_closed()) + self.popup.show() + + def _create_close_disconnect_message(self, connected_profiles): + profile_numbers = ', '.join(str(profile_id) + for profile_id in connected_profiles) + is_are = "is" if len(connected_profiles) == 1 else "are" + return f'Profile{"" if len(connected_profiles) == 1 else "s"} {profile_numbers} {is_are} still connected.\nAll connected profiles will be disconnected on exit.\nDo you want to proceed?' + + def _handle_close_disconnect_result(self, result, connected_profiles): + self._close_confirmation_pending = False + if not result: + return + + self._close_disconnect_in_progress = True + self.update_status('Disabling profiles...') + self.worker_thread = WorkerThread( + 'DISABLE_ALL_PROFILES', profile_data=connected_profiles) + self.worker_thread.text_output.connect(self.update_status) + self.worker_thread.finished.connect(self._finish_close_disconnect) + self.worker_thread.start() + + def _handle_close_disconnect_popup_closed(self): + if self._close_confirmation_pending: + self._close_confirmation_pending = False + self.popup = None + + def _finish_close_disconnect(self, _ok=True): + self._close_disconnect_in_progress = False + connected_profiles = self._get_enabled_profile_ids() + if connected_profiles: + self.update_status('Could not disconnect all profiles. Exit canceled.') + return + + self.update_status('All profiles disabled. Bye!') + self._closing_after_disconnect = True + QTimer.singleShot(0, self.close) def update_image(self, appearance_value="original"): image_path = os.path.join(self.btn_path, f"{appearance_value}.png") @@ -816,5 +895,5 @@ class CustomWindow(QMainWindow): def start_ui(force_sync): app = QApplication(sys.argv) - window = CustomWindow(force_sync) + window = CustomWindow(force_sync=force_sync) sys.exit(app.exec())