Coverage for main/controller.py: 100%

88 statements  

« 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 

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)