Coverage for main/controller.py: 100%
88 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-03 15:51 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-03 15:51 +0000
1import json
2import logging
3import os
4from typing import Any
6from PyQt6.QtCore import pyqtSignal, QObject, QTranslator, QSettings
7from PyQt6.QtWidgets import QApplication, QPushButton
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
17class Controller(QObject):
18 """
19 Main application controller managing navigation, language, and shared state.
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 """
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."""
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)."""
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
41 # --- Application Settings ---
42 self.translator = QTranslator()
43 self.settings = QSettings("GliAAns")
44 self.saved_lang = self.settings.value("language", "en", type=str)
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)
51 # --- Language setup ---
52 self.set_language(self.saved_lang)
53 self.language_changed.connect(self.set_language)
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)
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 }
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)
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"]
87 self.context["history"].append(self.start_page)
88 self.current_page = self.start_page
90 # Apply saved language again to ensure proper UI translation
91 self.set_language(self.saved_lang)
93 # Display the initial page
94 self._show_current_page()
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)
103 # Only update navigation buttons if they already exist
104 if self.next_button and self.back_button:
105 self.update_buttons_state()
107 def go_to_next_page(self):
108 """
109 Move to the next logical page in the workflow.
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
120 def go_to_previous_page(self):
121 """
122 Navigate back to the previous page.
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
133 def return_to_import(self):
134 """
135 Reset the workflow and return to the import page.
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
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()))
154 def create_buttons(self):
155 """
156 Create and return the navigation buttons (Next and Back).
158 Returns:
159 tuple[QPushButton, QPushButton]: The next and back buttons.
160 """
161 self.next_button = QPushButton("Next")
162 self.back_button = QPushButton("Back")
164 self.next_button.clicked.connect(self.go_to_next_page)
165 self.back_button.clicked.connect(self.go_to_previous_page)
167 return self.next_button, self.back_button
169 def start(self):
170 """Show the main application window and start the event loop."""
171 self.main_window.show()
173 def open_nifti_viewer(self, path: str):
174 """
175 Open a NIfTI file in the viewer window.
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()
183 def set_language(self, lang_code: str):
184 """
185 Load and apply a new language translation.
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)
195 def save_language(self, lang_code: str):
196 """
197 Persist the selected language in application settings.
199 Args:
200 lang_code (str): The language code to save.
201 """
202 self.settings.setValue("language", lang_code)