/
Flipper
/
Marginal
Обзор
Документация
Войти
/
Flipper
/
Marginal
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
1
CI/CD
Аналитика
Безопасность
master
src/UIManager.py
451 строка
13 KB
Alex
Marginal v1.0.0 RELEASE
11 янв 2026, 18:02
11 янв 2026, 18:02
5e1eff8
Код
Авторство
О чём код?
from PySide6.QtWidgets import QApplication from PySide6.QtCore import QTimer, QThread, QObject, Signal, Slot, Qt from UI.MainWindow import MainWindow from UI.SettingsDialog import SettingsDialog from UI.AboutDialog import AboutDialog from UI.LockedAppDialog import LockedAppDialog from UI.PasswordDialog import PasswordDialog from AppearanceManager import AppearanceManager from CalculationWorker import CalculationWorker import sys class UIManager: """ Manages the Qt-based user interface, including main window, dialogs, background calculation threads, and UI state management. """ def __init__(self, backbone): """ Initialize UIManager with application backbone. Args: backbone (Backbone): Central application controller. """ self.backbone = backbone self.logger = backbone.logger self.translator = backbone.translator # Create Qt application instance self.app = QApplication(sys.argv) self.appearanceManager = AppearanceManager( self.app, self.backbone.configManager, self.logger ) self.settingsWindowFlag = False # Track if settings dialog is open self._calcThread = None # Background thread for calculations self._calcWorker = None # Worker object in the background thread def run(self): """ Start the Qt application and show the main window. Returns: None """ # Set initial UI language self.setUILanguage(self.backbone.getConfig("appearance")["language"]) # Create and show main window self.mainWindow = MainWindow(self) # Connect language change signal for UI retranslation self.translator.languageChanged.connect(self.retranslateUI) self.mainWindow.show() # Set up console output proxy for logging self.consoleProxy = ConsoleProxy(self.mainWindow.consoleWrite) self.logger.connectConsole(self.consoleProxy.write) self.logger.log(message="Application started.") # Start Qt event loop self.app.exec() def quit(self): """ Quit the Qt application. Returns: None """ self.app.quit() def handleRunCalculation( self, inputPath, profilePath, outputPath, exportMode, createLog, fileName, openOnFinish ): """ Handle calculation run request by setting up background thread. Args: inputPath (str): Path to input data file. profilePath (str): Path to calculation profile. outputPath (str): Path for output files. exportMode (str): Export format/options. createLog (bool): Whether to create log file. fileName (str): Output file name. openOnFinish (bool): Open output folder when done. Returns: None """ self.logger.log("UIManager", "'Run' command received.") # Disable UI controls to prevent multiple runs if hasattr(self.mainWindow, "setRunControlsEnabled"): self.mainWindow.setRunControlsEnabled(False) # Store calculation arguments args = (inputPath, profilePath, outputPath, exportMode, createLog, fileName, openOnFinish) # Clean up any existing thread/worker if self._calcThread and self._calcThread.isRunning(): self._cleanupCalculationThread() # Create new thread and worker for background calculation self._calcThread = QThread() self._calcWorker = CalculationWorker(self.backbone, args) self._calcWorker.moveToThread(self._calcThread) # Wire up signals - IMPORTANT: Use QueuedConnection for cross-thread signals self._calcThread.started.connect(self._calcWorker.run) # Connect worker signals to main thread slots self._calcWorker.finished.connect( self._onCalculationFinished, Qt.QueuedConnection ) self._calcWorker.error.connect( self._onCalculationError, Qt.QueuedConnection ) self._calcWorker.log.connect( self._onWorkerLog, Qt.QueuedConnection ) # Connect thread finished signal for cleanup self._calcThread.finished.connect( self._onThreadFinished, Qt.QueuedConnection ) self._calcThread.setProperty("keepAlive", True) # Start the background thread self._calcThread.start() @Slot(bool) def _onCalculationFinished(self, success: bool): """ Handle calculation completion (runs in main thread). Args: success (bool): Whether calculation completed successfully. Returns: None """ try: if success: self.logger.log("System", "SUCCESS.") else: self.logger.log("System", "FAIL.") # Re-enable UI controls if hasattr(self.mainWindow, "setRunControlsEnabled"): self.mainWindow.setRunControlsEnabled(True) # Request thread to quit if self._calcThread and self._calcThread.isRunning(): self._calcThread.quit() else: # Thread already finished, clean up self._cleanupCalculationThread() except Exception as e: self.logger.log( "UIManager", "Error in _onCalculationFinished: {e}", True, e=e ) # Still try to re-enable UI if hasattr(self.mainWindow, "setRunControlsEnabled"): QTimer.singleShot( 0, lambda: self.mainWindow.setRunControlsEnabled(True) ) @Slot(str) def _onCalculationError(self, errorMsg: str): """ Handle calculation error (runs in main thread). Args: errorMsg (str): Error message from calculation worker. Returns: None """ self.logger.log("UIManager", f"Calculation error: {errorMsg}") # Still re-enable UI on error if hasattr(self.mainWindow, "setRunControlsEnabled"): self.mainWindow.setRunControlsEnabled(True) @Slot(str, bool) def _onWorkerLog(self, msg: str, hide: bool): """ Forward worker log messages to main logger (runs in main thread). Args: msg (str): Log message. hide (bool): Whether to hide message in console. Returns: None """ self.logger.log("CalculationWorker", msg, hide) @Slot() def _onThreadFinished(self): """ Handle thread finished signal for cleanup. Returns: None """ self._cleanupCalculationThread() def _cleanupCalculationThread(self): """ Safely clean up thread and worker resources. Returns: None """ try: # Disconnect all signals first to prevent stray signals if self._calcWorker: try: self._calcWorker.finished.disconnect() self._calcWorker.error.disconnect() self._calcWorker.log.disconnect() except: pass # Already disconnected if self._calcThread: try: self._calcThread.started.disconnect() self._calcThread.finished.disconnect() except: pass # Delete worker if it exists if self._calcWorker: self._calcWorker.deleteLater() self._calcWorker = None # Delete thread if it exists if self._calcThread: if self._calcThread.isRunning(): # Should not happen, but just in case self._calcThread.quit() if not self._calcThread.wait(1000): # 1 second timeout self._calcThread.terminate() self._calcThread.wait() self._calcThread.deleteLater() self._calcThread = None except Exception as e: self.logger.log( "UIManager", "Error during thread cleanup: {e}", True, e=e ) finally: # Final safety: ensure UI is re-enabled QTimer.singleShot(0, self._ensureUIEnabled) def _ensureUIEnabled(self): """ Final safety check to ensure UI controls are enabled. Returns: None """ if hasattr(self.mainWindow, "setRunControlsEnabled"): try: self.mainWindow.setRunControlsEnabled(True) except: pass # Window might be destroyed def openSettings(self): """ Open the settings dialog if user has access permission. Returns: None """ if self.backbone.getAccess(): dlg = SettingsDialog(self.backbone) self.settingsWindowFlag = True dlg.exec() self.settingsWindowFlag = False def openAbout(self): """ Open the about dialog. Returns: None """ dlg = AboutDialog(self.backbone) dlg.exec() def runPasswordDialog( self, title: str = "Placeholder", prompt: str = "Placeholder", translator=None ): """ Display a password input dialog. Args: title (str): Dialog window title. prompt (str): Prompt text for the user. translator (callable, optional): Translation function. Returns: str | None -> Entered password or None if dialog cancelled. """ dlg = PasswordDialog(title, prompt, translator) if dlg.exec(): return dlg.getPassword() return None def cullLogs(self): """ Trigger log file cleanup if user has access permission. Returns: None """ if self.backbone.getAccess(): self.logger.clearOtherLogs() def refreshConfig(self): """ Refresh UI appearance and configuration after settings change. Returns: None """ self.appearanceManager.applyTheme( self.backbone.configManager.getConfig("appearance")["theme"] ) self.mainWindow.updateConfig() def retranslateUI(self): """ Retranslate all UI text after language change. Returns: None """ if self.mainWindow: self.mainWindow.retranslateUI() self.logger.log( "System", "UI has been refreshed, previous console messages might be missing.\n" "They are still available within the current log file." ) def setUILanguage(self, lang: str, retranslateUI: bool = False): """ Set application language and optionally retranslate UI. Args: lang (str): Language code (e.g., "en", "fr"). retranslateUI (bool): Whether to immediately retranslate UI. Returns: None """ self.translator.setLanguage(lang) if retranslateUI: self.retranslateUI() class LockSmithDLProxy(): """ Proxy class for displaying locked application dialog. Creates a minimal Qt application context for the dialog. """ def __init__(self, configManager, translator): """ Initialize proxy with config manager and translator. Args: configManager (ConfigManager): Application configuration manager. translator (Translator): Application translator. """ self.translator = translator self.app = QApplication(sys.argv) self.appearanceManager = AppearanceManager(self.app, configManager) def showLockedDialog(self): """ Display the locked application dialog. Returns: None """ dlg = LockedAppDialog(self.translator) dlg.exec() class ConsoleProxy(QObject): """ Proxy object to forward console write requests from background threads to the main thread's console widget. """ writeRequested = Signal(str) def __init__(self, consoleWrite): """ Initialize proxy with console write function. Args: consoleWrite (callable): Function to write to console. """ super().__init__() self.writeRequested.connect(consoleWrite) @Slot(str) def write(self, text: str): """ Write text to console via signal (thread-safe). Args: text (str): Text to write to console. Returns: None """ self.writeRequested.emit(text)