main.controller
1import json 2import logging 3import os 4from typing import Any 5 6from PyQt6.QtCore import pyqtSignal, QObject, QTranslator, QSettings 7from PyQt6.QtWidgets import QApplication, QPushButton 8 9from logger import set_log_level 10from ui.workspace_tree_view import WorkspaceTreeView 11from ui.import_page import ImportPage 12from ui.main_window import MainWindow 13from ui.nifti_viewer import NiftiViewer 14from utils import resource_path, get_app_dir 15 16 17class Controller(QObject): 18 """ 19 Main application controller managing navigation, language, and shared state. 20 21 This class orchestrates the application logic, connecting the UI components 22 (pages, windows, viewers) and maintaining shared context across them. 23 It also handles language translation, logging configuration, and navigation 24 between pages. 25 """ 26 27 # Signal emitted when language changes 28 language_changed = pyqtSignal(str) 29 """pyqtSignal(str): Emitted when the application's language changes, with the code as parameter.""" 30 31 # Signal emitted when a set of files is selected 32 selected_files_signal = pyqtSignal(list) 33 """pyqtSignal(list): Emitted when one or more files are selected, with the files as parameter (as a list).""" 34 35 def __init__(self): 36 """Initialize the controller and set up the application context.""" 37 super().__init__(None) 38 self.back_button = None 39 self.next_button = None 40 41 # --- Application Settings --- 42 self.translator = QTranslator() 43 self.settings = QSettings("GliAAns") 44 self.saved_lang = self.settings.value("language", "en", type=str) 45 46 # Enable debug logging if user has this setting enabled 47 log_debug = self.settings.value("debug_log", False, type=bool) 48 if log_debug: 49 set_log_level(logging.DEBUG) 50 51 # --- Language setup --- 52 self.set_language(self.saved_lang) 53 self.language_changed.connect(self.set_language) 54 55 # --- Workspace setup --- 56 self.workspace_path = str(get_app_dir() / "workspace") 57 if not os.path.exists(self.workspace_path): 58 os.makedirs(self.workspace_path) 59 60 # --- Shared application context --- 61 # This context dictionary allows pages and components to access 62 # common utilities and shared data. 63 self.context: dict[str, Any] = { 64 "workspace_path" : str(self.workspace_path), 65 "update_main_buttons" : self.update_buttons_state, 66 "return_to_import" : self.return_to_import, 67 "history" : [], 68 "language_changed" : self.language_changed, 69 "create_buttons" : self.create_buttons, 70 "selected_files_signal": self.selected_files_signal, 71 "open_nifti_viewer" : self.open_nifti_viewer, 72 "settings" : self.settings 73 } 74 75 # --- UI Components --- 76 # Instantiate all main UI elements and inject shared context 77 self.context["import_page"] = ImportPage(self.context) 78 self.context["tree_view"] = WorkspaceTreeView(self.context) 79 self.context["main_window"] = MainWindow(self.context) 80 self.context["nifti_viewer"] = NiftiViewer(self.context) 81 82 # --- Page & Navigation Setup --- 83 self.main_window = self.context["main_window"] 84 self.tree_view = self.context["tree_view"] 85 self.start_page = self.context["import_page"] 86 87 self.context["history"].append(self.start_page) 88 self.current_page = self.start_page 89 90 # Apply saved language again to ensure proper UI translation 91 self.set_language(self.saved_lang) 92 93 # Display the initial page 94 self._show_current_page() 95 96 def _show_current_page(self): 97 """ 98 Display the current page in the main window. 99 Updates navigation buttons if they are already created. 100 """ 101 self.main_window.set_widgets(self.tree_view, self.current_page) 102 103 # Only update navigation buttons if they already exist 104 if self.next_button and self.back_button: 105 self.update_buttons_state() 106 107 def go_to_next_page(self): 108 """ 109 Move to the next logical page in the workflow. 110 111 Returns: 112 Page: The new current page after navigation. 113 """ 114 next_page = self.current_page.next(self.context) 115 if next_page: 116 self.current_page = next_page 117 self._show_current_page() 118 return self.current_page 119 120 def go_to_previous_page(self): 121 """ 122 Navigate back to the previous page. 123 124 Returns: 125 Page: The new current page after going back. 126 """ 127 previous_page = self.current_page.back() 128 if previous_page: 129 self.current_page = previous_page 130 self._show_current_page() 131 return self.current_page 132 133 def return_to_import(self): 134 """ 135 Reset the workflow and return to the import page. 136 137 This clears all pages in the navigation history and resets their states. 138 """ 139 if self.context["history"]: 140 for page in self.context["history"]: 141 page.reset_page() 142 self.current_page = self.start_page 143 self._show_current_page() 144 return self.current_page 145 146 def update_buttons_state(self): 147 """ 148 Update the enabled/disabled state of navigation buttons 149 based on the current page readiness. 150 """ 151 self.next_button.setEnabled(bool(self.current_page.is_ready_to_advance())) 152 self.back_button.setEnabled(bool(self.current_page.is_ready_to_go_back())) 153 154 def create_buttons(self): 155 """ 156 Create and return the navigation buttons (Next and Back). 157 158 Returns: 159 tuple[QPushButton, QPushButton]: The next and back buttons. 160 """ 161 self.next_button = QPushButton("Next") 162 self.back_button = QPushButton("Back") 163 164 self.next_button.clicked.connect(self.go_to_next_page) 165 self.back_button.clicked.connect(self.go_to_previous_page) 166 167 return self.next_button, self.back_button 168 169 def start(self): 170 """Show the main application window and start the event loop.""" 171 self.main_window.show() 172 173 def open_nifti_viewer(self, path: str): 174 """ 175 Open a NIfTI file in the viewer window. 176 177 Args: 178 path (str): The file path to open. 179 """ 180 self.context["nifti_viewer"].open_file(path) 181 self.context["nifti_viewer"].show() 182 183 def set_language(self, lang_code: str): 184 """ 185 Load and apply a new language translation. 186 187 Args: 188 lang_code (str): The language code (e.g., 'en', 'it'). 189 """ 190 self.save_language(lang_code) 191 translation_file = f"{resource_path('translations')}/{lang_code}.qm" 192 if self.translator.load(translation_file): 193 QApplication.instance().installTranslator(self.translator) 194 195 def save_language(self, lang_code: str): 196 """ 197 Persist the selected language in application settings. 198 199 Args: 200 lang_code (str): The language code to save. 201 """ 202 self.settings.setValue("language", lang_code)
18class Controller(QObject): 19 """ 20 Main application controller managing navigation, language, and shared state. 21 22 This class orchestrates the application logic, connecting the UI components 23 (pages, windows, viewers) and maintaining shared context across them. 24 It also handles language translation, logging configuration, and navigation 25 between pages. 26 """ 27 28 # Signal emitted when language changes 29 language_changed = pyqtSignal(str) 30 """pyqtSignal(str): Emitted when the application's language changes, with the code as parameter.""" 31 32 # Signal emitted when a set of files is selected 33 selected_files_signal = pyqtSignal(list) 34 """pyqtSignal(list): Emitted when one or more files are selected, with the files as parameter (as a list).""" 35 36 def __init__(self): 37 """Initialize the controller and set up the application context.""" 38 super().__init__(None) 39 self.back_button = None 40 self.next_button = None 41 42 # --- Application Settings --- 43 self.translator = QTranslator() 44 self.settings = QSettings("GliAAns") 45 self.saved_lang = self.settings.value("language", "en", type=str) 46 47 # Enable debug logging if user has this setting enabled 48 log_debug = self.settings.value("debug_log", False, type=bool) 49 if log_debug: 50 set_log_level(logging.DEBUG) 51 52 # --- Language setup --- 53 self.set_language(self.saved_lang) 54 self.language_changed.connect(self.set_language) 55 56 # --- Workspace setup --- 57 self.workspace_path = str(get_app_dir() / "workspace") 58 if not os.path.exists(self.workspace_path): 59 os.makedirs(self.workspace_path) 60 61 # --- Shared application context --- 62 # This context dictionary allows pages and components to access 63 # common utilities and shared data. 64 self.context: dict[str, Any] = { 65 "workspace_path" : str(self.workspace_path), 66 "update_main_buttons" : self.update_buttons_state, 67 "return_to_import" : self.return_to_import, 68 "history" : [], 69 "language_changed" : self.language_changed, 70 "create_buttons" : self.create_buttons, 71 "selected_files_signal": self.selected_files_signal, 72 "open_nifti_viewer" : self.open_nifti_viewer, 73 "settings" : self.settings 74 } 75 76 # --- UI Components --- 77 # Instantiate all main UI elements and inject shared context 78 self.context["import_page"] = ImportPage(self.context) 79 self.context["tree_view"] = WorkspaceTreeView(self.context) 80 self.context["main_window"] = MainWindow(self.context) 81 self.context["nifti_viewer"] = NiftiViewer(self.context) 82 83 # --- Page & Navigation Setup --- 84 self.main_window = self.context["main_window"] 85 self.tree_view = self.context["tree_view"] 86 self.start_page = self.context["import_page"] 87 88 self.context["history"].append(self.start_page) 89 self.current_page = self.start_page 90 91 # Apply saved language again to ensure proper UI translation 92 self.set_language(self.saved_lang) 93 94 # Display the initial page 95 self._show_current_page() 96 97 def _show_current_page(self): 98 """ 99 Display the current page in the main window. 100 Updates navigation buttons if they are already created. 101 """ 102 self.main_window.set_widgets(self.tree_view, self.current_page) 103 104 # Only update navigation buttons if they already exist 105 if self.next_button and self.back_button: 106 self.update_buttons_state() 107 108 def go_to_next_page(self): 109 """ 110 Move to the next logical page in the workflow. 111 112 Returns: 113 Page: The new current page after navigation. 114 """ 115 next_page = self.current_page.next(self.context) 116 if next_page: 117 self.current_page = next_page 118 self._show_current_page() 119 return self.current_page 120 121 def go_to_previous_page(self): 122 """ 123 Navigate back to the previous page. 124 125 Returns: 126 Page: The new current page after going back. 127 """ 128 previous_page = self.current_page.back() 129 if previous_page: 130 self.current_page = previous_page 131 self._show_current_page() 132 return self.current_page 133 134 def return_to_import(self): 135 """ 136 Reset the workflow and return to the import page. 137 138 This clears all pages in the navigation history and resets their states. 139 """ 140 if self.context["history"]: 141 for page in self.context["history"]: 142 page.reset_page() 143 self.current_page = self.start_page 144 self._show_current_page() 145 return self.current_page 146 147 def update_buttons_state(self): 148 """ 149 Update the enabled/disabled state of navigation buttons 150 based on the current page readiness. 151 """ 152 self.next_button.setEnabled(bool(self.current_page.is_ready_to_advance())) 153 self.back_button.setEnabled(bool(self.current_page.is_ready_to_go_back())) 154 155 def create_buttons(self): 156 """ 157 Create and return the navigation buttons (Next and Back). 158 159 Returns: 160 tuple[QPushButton, QPushButton]: The next and back buttons. 161 """ 162 self.next_button = QPushButton("Next") 163 self.back_button = QPushButton("Back") 164 165 self.next_button.clicked.connect(self.go_to_next_page) 166 self.back_button.clicked.connect(self.go_to_previous_page) 167 168 return self.next_button, self.back_button 169 170 def start(self): 171 """Show the main application window and start the event loop.""" 172 self.main_window.show() 173 174 def open_nifti_viewer(self, path: str): 175 """ 176 Open a NIfTI file in the viewer window. 177 178 Args: 179 path (str): The file path to open. 180 """ 181 self.context["nifti_viewer"].open_file(path) 182 self.context["nifti_viewer"].show() 183 184 def set_language(self, lang_code: str): 185 """ 186 Load and apply a new language translation. 187 188 Args: 189 lang_code (str): The language code (e.g., 'en', 'it'). 190 """ 191 self.save_language(lang_code) 192 translation_file = f"{resource_path('translations')}/{lang_code}.qm" 193 if self.translator.load(translation_file): 194 QApplication.instance().installTranslator(self.translator) 195 196 def save_language(self, lang_code: str): 197 """ 198 Persist the selected language in application settings. 199 200 Args: 201 lang_code (str): The language code to save. 202 """ 203 self.settings.setValue("language", lang_code)
Main application controller managing navigation, language, and shared state.
This class orchestrates the application logic, connecting the UI components (pages, windows, viewers) and maintaining shared context across them. It also handles language translation, logging configuration, and navigation between pages.
36 def __init__(self): 37 """Initialize the controller and set up the application context.""" 38 super().__init__(None) 39 self.back_button = None 40 self.next_button = None 41 42 # --- Application Settings --- 43 self.translator = QTranslator() 44 self.settings = QSettings("GliAAns") 45 self.saved_lang = self.settings.value("language", "en", type=str) 46 47 # Enable debug logging if user has this setting enabled 48 log_debug = self.settings.value("debug_log", False, type=bool) 49 if log_debug: 50 set_log_level(logging.DEBUG) 51 52 # --- Language setup --- 53 self.set_language(self.saved_lang) 54 self.language_changed.connect(self.set_language) 55 56 # --- Workspace setup --- 57 self.workspace_path = str(get_app_dir() / "workspace") 58 if not os.path.exists(self.workspace_path): 59 os.makedirs(self.workspace_path) 60 61 # --- Shared application context --- 62 # This context dictionary allows pages and components to access 63 # common utilities and shared data. 64 self.context: dict[str, Any] = { 65 "workspace_path" : str(self.workspace_path), 66 "update_main_buttons" : self.update_buttons_state, 67 "return_to_import" : self.return_to_import, 68 "history" : [], 69 "language_changed" : self.language_changed, 70 "create_buttons" : self.create_buttons, 71 "selected_files_signal": self.selected_files_signal, 72 "open_nifti_viewer" : self.open_nifti_viewer, 73 "settings" : self.settings 74 } 75 76 # --- UI Components --- 77 # Instantiate all main UI elements and inject shared context 78 self.context["import_page"] = ImportPage(self.context) 79 self.context["tree_view"] = WorkspaceTreeView(self.context) 80 self.context["main_window"] = MainWindow(self.context) 81 self.context["nifti_viewer"] = NiftiViewer(self.context) 82 83 # --- Page & Navigation Setup --- 84 self.main_window = self.context["main_window"] 85 self.tree_view = self.context["tree_view"] 86 self.start_page = self.context["import_page"] 87 88 self.context["history"].append(self.start_page) 89 self.current_page = self.start_page 90 91 # Apply saved language again to ensure proper UI translation 92 self.set_language(self.saved_lang) 93 94 # Display the initial page 95 self._show_current_page()
Initialize the controller and set up the application context.
pyqtSignal(str): Emitted when the application's language changes, with the code as parameter.
pyqtSignal(list): Emitted when one or more files are selected, with the files as parameter (as a list).
108 def go_to_next_page(self): 109 """ 110 Move to the next logical page in the workflow. 111 112 Returns: 113 Page: The new current page after navigation. 114 """ 115 next_page = self.current_page.next(self.context) 116 if next_page: 117 self.current_page = next_page 118 self._show_current_page() 119 return self.current_page
Move to the next logical page in the workflow.
Returns: Page: The new current page after navigation.
121 def go_to_previous_page(self): 122 """ 123 Navigate back to the previous page. 124 125 Returns: 126 Page: The new current page after going back. 127 """ 128 previous_page = self.current_page.back() 129 if previous_page: 130 self.current_page = previous_page 131 self._show_current_page() 132 return self.current_page
Navigate back to the previous page.
Returns: Page: The new current page after going back.
134 def return_to_import(self): 135 """ 136 Reset the workflow and return to the import page. 137 138 This clears all pages in the navigation history and resets their states. 139 """ 140 if self.context["history"]: 141 for page in self.context["history"]: 142 page.reset_page() 143 self.current_page = self.start_page 144 self._show_current_page() 145 return self.current_page
Reset the workflow and return to the import page.
This clears all pages in the navigation history and resets their states.
170 def start(self): 171 """Show the main application window and start the event loop.""" 172 self.main_window.show()
Show the main application window and start the event loop.
174 def open_nifti_viewer(self, path: str): 175 """ 176 Open a NIfTI file in the viewer window. 177 178 Args: 179 path (str): The file path to open. 180 """ 181 self.context["nifti_viewer"].open_file(path) 182 self.context["nifti_viewer"].show()
Open a NIfTI file in the viewer window.
Args: path (str): The file path to open.
184 def set_language(self, lang_code: str): 185 """ 186 Load and apply a new language translation. 187 188 Args: 189 lang_code (str): The language code (e.g., 'en', 'it'). 190 """ 191 self.save_language(lang_code) 192 translation_file = f"{resource_path('translations')}/{lang_code}.qm" 193 if self.translator.load(translation_file): 194 QApplication.instance().installTranslator(self.translator)
Load and apply a new language translation.
Args: lang_code (str): The language code (e.g., 'en', 'it').
196 def save_language(self, lang_code: str): 197 """ 198 Persist the selected language in application settings. 199 200 Args: 201 lang_code (str): The language code to save. 202 """ 203 self.settings.setValue("language", lang_code)
Persist the selected language in application settings.
Args: lang_code (str): The language code to save.