From 76df19f332debd097008e9ce3a564b40c806f75a Mon Sep 17 00:00:00 2001 From: tyler Date: Mon, 2 Feb 2026 13:08:00 -0800 Subject: [PATCH] further issue fixes --- changelog.md | 8 +++++ main.py | 93 ++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 91 insertions(+), 10 deletions(-) diff --git a/changelog.md b/changelog.md index 3c91bd7..a8cd0d6 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,14 @@ # Version 1.2.1 - Added a requirements.txt file to ensure compatibility +- Added new options 'Missing Events Bypass' and 'Analysis Clearing Bypass' to the Preferences Menu +- Missing Events Bypass allows comparing events in the Group Viewers even if not all participants in the group have the event present. Fixes [Issue 28](https://git.research.dezeeuw.ca/tyler/flares/issues/28) +- Clicking Process after an analysis has been performed will now clear the existing analysis by default with a popup warning that the analysis will be cleared +- Analysis Clearing Bypass will prevent the popup and will not clear the existing analysis data. Fixes [Issue 41](https://git.research.dezeeuw.ca/tyler/flares/issues/41) +- Clicking 'Clear' should now actually properly clear all data. Hopefully fixes [Issue 9](https://git.research.dezeeuw.ca/tyler/flares/issues/9) for good +- Setting SHORT_CHANNEL to False will now grey out SHORT_CHANNEL_REGRESSION, as it is impossible to regress what does not exist. Sets SHORT_CHANNEL_REGRESSION to False under the hood when it is greyed out regardless of what is displayed. Fixes [Issue 47](https://git.research.dezeeuw.ca/tyler/flares/issues/47) +- Projects can now be saves if files have different parent folders. Fixes [Issue 48](https://git.research.dezeeuw.ca/tyler/flares/issues/48) +- It is no longer possible to attempt a save before any data has been processed. A popup will now display if a save is attempted with nothing to save # Version 1.2.0 diff --git a/main.py b/main.py index 8a407c6..210774b 100644 --- a/main.py +++ b/main.py @@ -46,7 +46,7 @@ from PySide6.QtGui import QAction, QKeySequence, QIcon, QIntValidator, QDoubleVa from PySide6.QtSvgWidgets import QSvgWidget # needed to show svgs when app is not frozen -CURRENT_VERSION = "1.2.0" +CURRENT_VERSION = "1.2.1" API_URL = "https://git.research.dezeeuw.ca/api/v1/repos/tyler/flares/releases" API_URL_SECONDARY = "https://git.research2.dezeeuw.ca/api/v1/repos/tyler/flares/releases" @@ -1480,11 +1480,11 @@ class ParamSection(QWidget): } """ - def __init__(self, section_data): + def __init__(self, section_data, global_widgets): super().__init__() layout = QVBoxLayout() self.setLayout(layout) - self.widgets = {} + self.widgets = global_widgets self.dependencies = [] self.selected_path = None @@ -1527,7 +1527,7 @@ class ParamSection(QWidget): widget = QComboBox() widget.addItems(["True", "False"]) widget.setCurrentText(str(param["default"])) - widget.currentTextChanged.connect(self.update_dependencies) + widget.currentTextChanged.connect(self.notify_global_update) elif param["type"] == int: widget = QLineEdit() widget.setValidator(QIntValidator()) @@ -1541,7 +1541,7 @@ class ParamSection(QWidget): widget = QComboBox() widget.addItems(param.get("options", [])) widget.setCurrentText(str(param.get("default", ""))) - widget.currentTextChanged.connect(self.update_dependencies) + widget.currentTextChanged.connect(self.notify_global_update) else: widget = self._create_multiselect_dropdown(None) elif param["type"] == range: @@ -1580,6 +1580,16 @@ class ParamSection(QWidget): self.update_dependencies() + def notify_global_update(self): + """ + Since dependencies can cross sections, we need to tell + all sections to refresh their enabled/disabled states. + """ + # If you have a reference to the parent container, call its update. + # Otherwise, you can iterate through the known param_sections: + for section in self.parent().findChildren(ParamSection): + section.update_dependencies() + def update_dependencies(self): """Disables/Enables widgets based on parent selection values.""" for dep in self.dependencies: @@ -1691,6 +1701,12 @@ class ParamSection(QWidget): widget = info["widget"] expected_type = info["type"] + if name == "SHORT_CHANNEL_REGRESSION": + # If the widget is disabled (greyed out), force False + if not widget.isEnabled(): + values[name] = False + continue + if expected_type == bool: values[name] = widget.currentText() == "True" elif expected_type == list: @@ -4061,6 +4077,7 @@ class MainApplication(QMainWindow): self.is_2d_bypass = False self.incompatible_save_bypass = False self.missing_events_bypass = False + self.analysis_clearing_bypass = False self.files_total = 0 # total number of files to process self.files_done = set() # set of file paths done (success or fail) @@ -4313,7 +4330,8 @@ class MainApplication(QMainWindow): preferences_actions = [ ("2D Data Bypass", "", self.is_2d_bypass_func, resource_path("icons/info_24dp_1F1F1F.svg")), ("Incompatible Save Bypass", "", self.incompatable_save_bypass_func, resource_path("icons/info_24dp_1F1F1F.svg")), - ("Missing Events Bypass", "", self.missing_events_bypass_func, resource_path("icons/info_24dp_1F1F1F.svg")) + ("Missing Events Bypass", "", self.missing_events_bypass_func, resource_path("icons/info_24dp_1F1F1F.svg")), + ("Analysis Clearing Bypass", "", self.analysis_clearing_bypass_func, resource_path("icons/info_24dp_1F1F1F.svg")) ] for name, shortcut, slot, icon in preferences_actions: preferences_menu.addAction(make_action(name, shortcut, slot, icon=icon, checkable=True, checked=False)) @@ -4337,13 +4355,18 @@ class MainApplication(QMainWindow): widget.deleteLater() self.param_sections.clear() + self.global_param_widgets = {} + # Add ParamSection widgets from SECTIONS for section in SECTIONS: - self.section_widget = ParamSection(section) + self.section_widget = ParamSection(section, self.global_param_widgets) self.rows_layout.addWidget(self.section_widget) self.param_sections.append(self.section_widget) + for sec in self.param_sections: + sec.update_dependencies() + def clear_all(self): @@ -4358,6 +4381,11 @@ class MainApplication(QMainWindow): if widget: widget.deleteLater() + if hasattr(self, "selected_paths"): + self.selected_paths = [] + if hasattr(self, "selected_path"): + self.selected_path = None + # Clear file data self.bubble_widgets.clear() self.statusBar().clearMessage() @@ -4405,6 +4433,9 @@ class MainApplication(QMainWindow): def missing_events_bypass_func(self, checked): self.missing_events_bypass = checked + def analysis_clearing_bypass_func(self, checked): + self.analysis_clearing_bypass = checked + def about_window(self): if self.about is None or not self.about.isVisible(): self.about = AboutWindow(self) @@ -4533,6 +4564,15 @@ class MainApplication(QMainWindow): def save_project(self, onCrash=False): + if not getattr(self, 'raw_haemo_dict', None): + if not onCrash: # Don't show popups during a crash/autosave + QMessageBox.warning( + self, + "Save Project", + "There is no processed data to save. Please process some data before saving." + ) + return + if not onCrash: filename, _ = QFileDialog.getSaveFileName( self, "Save Project", "", "FLARE Project (*.flare)" @@ -4554,12 +4594,12 @@ class MainApplication(QMainWindow): project_dir = project_path.parent file_list = [ - str(PurePosixPath(Path(bubble.file_path).resolve().relative_to(project_dir))) + str(PurePosixPath(os.path.relpath(Path(bubble.file_path).resolve(), project_dir))) for bubble in self.bubble_widgets.values() ] progress_states = { - str(PurePosixPath(Path(bubble.file_path).resolve().relative_to(project_dir))): bubble.current_step + str(PurePosixPath(os.path.relpath(Path(bubble.file_path).resolve(), project_dir))): bubble.current_step for bubble in self.bubble_widgets.values() } @@ -5060,7 +5100,40 @@ class MainApplication(QMainWindow): '''MODULE FILE''' def on_run_task(self): - + + #do the check + if not self.analysis_clearing_bypass: + if self.button3.isVisible(): + msg = QMessageBox(self) + msg.setWindowTitle("Confirm - FLARES") + msg.setText("Processing new data will clear the current analysis. Continue? (If you do not want this dialog box to appear, toggle 'Analysis Clearing Bypass' from the Preferences menu.)") + + # Add the OK and Cancel buttons + msg.setStandardButtons(QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel) + + # Set the default button (highlighted) + msg.setDefaultButton(QMessageBox.StandardButton.Cancel) + + # Capture the result + response = msg.exec() + + if response == QMessageBox.StandardButton.Ok: + print("User clicked OK") + else: + return + + self.button3.setVisible(False) + + self.raw_haemo_dict = {} + self.config_dict = {} + self.epochs_dict = {} + self.fig_bytes_dict = {} + self.cha_dict = {} + self.contrast_results_dict = {} + self.df_ind_dict = {} + self.design_matrix_dict = {} + self.valid_dict = {} + self.button1.clicked.disconnect(self.on_run_task) self.button1.setText("Cancel") self.button1.clicked.connect(self.cancel_task)