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)
class Controller(PyQt6.QtCore.QObject):
 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.

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

def language_changed(unknown):

pyqtSignal(str): Emitted when the application's language changes, with the code as parameter.

def selected_files_signal(unknown):

pyqtSignal(list): Emitted when one or more files are selected, with the files as parameter (as a list).

back_button
next_button
translator
settings
saved_lang
workspace_path
context: dict[str, typing.Any]
main_window
tree_view
start_page
current_page
def go_to_next_page(self):
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.

def go_to_previous_page(self):
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.

def return_to_import(self):
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.

def update_buttons_state(self):
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()))

Update the enabled/disabled state of navigation buttons based on the current page readiness.

def create_buttons(self):
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

Create and return the navigation buttons (Next and Back).

Returns: tuple[QPushButton, QPushButton]: The next and back buttons.

def start(self):
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.

def open_nifti_viewer(self, path: str):
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.

def set_language(self, lang_code: str):
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').

def save_language(self, lang_code: str):
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.