From 8bda58c7108458e2954138f92f69bb1c4e203dec Mon Sep 17 00:00:00 2001 From: Tim Sutton Date: Mon, 2 Dec 2024 16:19:37 +0000 Subject: [PATCH] Strip out indicator editing dialogs --- geest/gui/__init__.py | 2 - geest/gui/combined_widget_factory.py | 82 ---- geest/gui/dialogs/__init__.py | 1 - geest/gui/dialogs/indicator_detail_dialog.py | 327 --------------- geest/gui/indicator_configuration_widget.py | 75 ---- geest/gui/panels/tree_panel.py | 57 +-- .../gui/widgets/combined_widgets/__init__.py | 15 - .../acled_csv_layer_widget.py | 190 --------- .../combined_widgets/base_indicator_widget.py | 91 ----- .../classified_polygon_widget.py | 279 ------------- .../combined_widgets/dont_use_widget.py | 25 -- .../indicator_index_score_widget.py | 50 --- .../multi_buffer_distances_widget.py | 217 ---------- .../combined_widgets/point_layer_widget.py | 154 ------- .../combined_widgets/polygon_widget.py | 162 -------- .../combined_widgets/polyline_widget.py | 163 -------- .../raster_reclassification_widget.py | 153 ------- .../combined_widgets/safety_polygon_widget.py | 386 ------------------ .../combined_widgets/safety_raster_widget.py | 157 ------- .../single_buffer_distance_widget.py | 174 -------- .../combined_widgets/street_lights_widget.py | 154 ------- 21 files changed, 3 insertions(+), 2911 deletions(-) delete mode 100644 geest/gui/combined_widget_factory.py delete mode 100644 geest/gui/dialogs/indicator_detail_dialog.py delete mode 100644 geest/gui/indicator_configuration_widget.py delete mode 100644 geest/gui/widgets/combined_widgets/__init__.py delete mode 100644 geest/gui/widgets/combined_widgets/acled_csv_layer_widget.py delete mode 100644 geest/gui/widgets/combined_widgets/base_indicator_widget.py delete mode 100644 geest/gui/widgets/combined_widgets/classified_polygon_widget.py delete mode 100644 geest/gui/widgets/combined_widgets/dont_use_widget.py delete mode 100644 geest/gui/widgets/combined_widgets/indicator_index_score_widget.py delete mode 100644 geest/gui/widgets/combined_widgets/multi_buffer_distances_widget.py delete mode 100644 geest/gui/widgets/combined_widgets/point_layer_widget.py delete mode 100644 geest/gui/widgets/combined_widgets/polygon_widget.py delete mode 100644 geest/gui/widgets/combined_widgets/polyline_widget.py delete mode 100644 geest/gui/widgets/combined_widgets/raster_reclassification_widget.py delete mode 100644 geest/gui/widgets/combined_widgets/safety_polygon_widget.py delete mode 100644 geest/gui/widgets/combined_widgets/safety_raster_widget.py delete mode 100644 geest/gui/widgets/combined_widgets/single_buffer_distance_widget.py delete mode 100644 geest/gui/widgets/combined_widgets/street_lights_widget.py diff --git a/geest/gui/__init__.py b/geest/gui/__init__.py index d16e2d45..e0201e78 100644 --- a/geest/gui/__init__.py +++ b/geest/gui/__init__.py @@ -4,9 +4,7 @@ from .geest_settings import GeestOptionsFactory from .geest_dock import GeestDock -from .indicator_configuration_widget import IndicatorConfigurationWidget from .factor_configuration_widget import FactorConfigurationWidget from .toggle_switch import ToggleSwitch -from .combined_widget_factory import CombinedWidgetFactory from .configuration_widget_factory import ConfigurationWidgetFactory from .datasource_widget_factory import DataSourceWidgetFactory diff --git a/geest/gui/combined_widget_factory.py b/geest/gui/combined_widget_factory.py deleted file mode 100644 index 0a0d539e..00000000 --- a/geest/gui/combined_widget_factory.py +++ /dev/null @@ -1,82 +0,0 @@ -from qgis.core import Qgis -from geest.gui.widgets.combined_widgets import ( - BaseIndicatorWidget, - IndexScoreRadioButton, - DontUseRadioButton, - MultiBufferDistancesWidget, - SingleBufferDistanceWidget, - PolylineWidget, - PointLayerWidget, - PolygonWidget, - AcledCsvLayerWidget, - SafetyPolygonWidget, - SafetyRasterWidget, - RasterReclassificationWidget, - StreetLightsWidget, - ClassifiedPolygonWidget, -) -from geest.core import setting -from geest.utilities import log_message - - -class CombinedWidgetFactory: - """ - Factory class for creating radio buttons based on key-value pairs. - """ - - @staticmethod - def create_radio_button( - key: str, value: int, attributes: dict - ) -> BaseIndicatorWidget: - """ - Factory method to create a radio button based on key-value pairs. - """ - verbose_mode = int(setting(key="verbose_mode", default=0)) - if verbose_mode: - log_message("Dialog widget factory called") - log_message("----------------------------") - log_message(f"Key: {key}") - log_message(f"Value: {value}") - log_message("----------------------------") - - try: - if key == "indicator_required" and value == 0: - return DontUseRadioButton( - label_text="Do Not Use", attributes=attributes - ) - if key == "use_default_index_score" and value == 1: - return IndexScoreRadioButton(label_text=key, attributes=attributes) - if key == "use_multi_buffer_point" and value == 1: - return MultiBufferDistancesWidget(label_text=key, attributes=attributes) - if key == "use_single_buffer_point" and value == 1: - return SingleBufferDistanceWidget(label_text=key, attributes=attributes) - if key == "use_polygon_per_cell" and value == 1: - return PolygonWidget(label_text=key, attributes=attributes) - if key == "use_polyline_per_cell" and value == 1: - return PolylineWidget(label_text=key, attributes=attributes) - if key == "use_point_per_cell" and value == 1: - return PointLayerWidget(label_text=key, attributes=attributes) - if key == "use_csv_to_point_layer" and value == 1: - return AcledCsvLayerWidget(label_text=key, attributes=attributes) - if key == "use_classify_polygon_into_classes" and value == 1: - return ClassifiedPolygonWidget(label_text=key, attributes=attributes) - if key == "use_classify_safety_polygon_into_classes" and value == 1: - return SafetyPolygonWidget(label_text=key, attributes=attributes) - if key == "use_nighttime_lights" and value == 1: - return SafetyRasterWidget(label_text=key, attributes=attributes) - if key == "use_environmental_hazards" and value == 1: - return RasterReclassificationWidget( - label_text=key, attributes=attributes - ) - if key == "use_street_lights" and value == 1: - return StreetLightsWidget(label_text=key, attributes=attributes) - else: - log_message( - f"Factory did not match any widgets", - tag="Geest", - level=Qgis.Critical, - ) - return None - except Exception as e: - log_message(f"Error in create_radio_button: {e}", level=Qgis.Critical) - return None diff --git a/geest/gui/dialogs/__init__.py b/geest/gui/dialogs/__init__.py index 9fde1bba..e268625c 100644 --- a/geest/gui/dialogs/__init__.py +++ b/geest/gui/dialogs/__init__.py @@ -1,4 +1,3 @@ from .factor_aggregation_dialog import FactorAggregationDialog -from .indicator_detail_dialog import IndicatorDetailDialog from .dimension_aggregation_dialog import DimensionAggregationDialog from .analysis_aggregation_dialog import AnalysisAggregationDialog diff --git a/geest/gui/dialogs/indicator_detail_dialog.py b/geest/gui/dialogs/indicator_detail_dialog.py deleted file mode 100644 index a1268638..00000000 --- a/geest/gui/dialogs/indicator_detail_dialog.py +++ /dev/null @@ -1,327 +0,0 @@ -import json -import os -from qgis.PyQt.QtWidgets import ( - QButtonGroup, - QCheckBox, - QComboBox, - QDialog, - QDialogButtonBox, - QDoubleSpinBox, - QFrame, - QHeaderView, - QLabel, - QLineEdit, - QSizePolicy, - QSpacerItem, - QSpinBox, - QSplitter, - QTableWidget, - QTableWidgetItem, - QTextEdit, - QVBoxLayout, - QTabWidget, # Added for tabs - QWidget, # Added for tab contents -) -from qgis.PyQt.QtGui import QPixmap -from qgis.PyQt.QtCore import Qt, pyqtSignal -from qgis.core import Qgis -from ..toggle_switch import ToggleSwitch -from geest.utilities import resources_path -from ..indicator_configuration_widget import IndicatorConfigurationWidget -from geest.utilities import log_message - - -class IndicatorDetailDialog(QDialog): - """Dialog to show layer properties, with a Markdown editor and preview for the 'indicator' field.""" - - # Signal to emit the updated data as a dictionary - dataUpdated = pyqtSignal() - - def __init__(self, item, editing=False, parent=None): - """ - Initializes the dialog with the given item and optional editing mode. - - :param item: The QTreeView item to show the properties for. - :param editing: Whether the dialog is in editing mode (default: False). - :param parent: The parent widget (default: None). - - Note: The item is a reference to the QTreeView item, so any changes will update the tree. - - """ - super().__init__(parent) - - self.setWindowTitle(item.name()) - # Note this is a reference to the tree item - # any changes you make will update the tree - self.item = item # Reference to the QTreeView item to update - self.attributes = ( - self.item.attributes() - ) # Reference to the attributes dictionary - self.editing = editing - self.config_widget = None # To hold the configuration from widget factory - self.radio_buttons = [] # To keep track of the radio buttons for later - self.button_group = QButtonGroup() # To group radio buttons - layout = QVBoxLayout() - - # Make the dialog wider and add padding - self.resize(800, 600) # Set a wider dialog size - layout.setContentsMargins(20, 20, 20, 20) # Add padding around the layout - - # Main widget with tabs - self.tab_widget = QTabWidget() - - # Left-hand tab for Markdown editor and preview - self.markdown_tab = QWidget() - self.setup_markdown_tab() - - # Right-hand tab for editing properties (table) - self.edit_tab = QWidget() - self.setup_edit_tab() - - # Add tabs to the QTabWidget - self.tab_widget.addTab(self.markdown_tab, "Preview") - self.tab_widget.addTab(self.edit_tab, "Edit") - - # Show or hide the "Edit" tab based on `editing` - self.tab_widget.setTabVisible(1, self.editing) - - # Add the tab widget to the main layout - layout.addWidget(self.tab_widget) - - # Create a QDialogButtonBox for OK/Cancel buttons - button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) - button_box.accepted.connect(self.accept_changes) # Connect OK to accept_changes - button_box.rejected.connect(self.reject) # Connect Cancel to reject the dialog - layout.addWidget(button_box, alignment=Qt.AlignBottom) # Place at the bottom - - self.setLayout(layout) - - # Initial call to update the preview with existing content - self.update_preview() - - def setup_markdown_tab(self): - """Sets up the left-hand tab for the Markdown editor and preview.""" - markdown_layout = QVBoxLayout(self.markdown_tab) - - self.title_label = QLabel( - "Geospatial Assessment of Women Employment and Business Opportunities in the Renewable Energy Sector", - self, - ) - self.title_label.setWordWrap(True) - markdown_layout.addWidget(self.title_label) - - # Get the grandparent and parent items - grandparent_item = self.item.parent().parent() if self.item.parent() else None - parent_item = self.item.parent() - - # If both grandparent and parent exist, create the label - if grandparent_item and parent_item: - hierarchy_label = QLabel( - f"{grandparent_item.data(0)} :: {parent_item.data(0)}" - ) - hierarchy_label.setStyleSheet( - "font-size: 14px; font-weight: bold; color: gray;" - ) - markdown_layout.addWidget( - hierarchy_label, alignment=Qt.AlignTop - ) # Add the label above the heading - - # Heading for the dialog - heading_label = QLabel(self.item.name()) - heading_label.setStyleSheet( - "font-size: 18px; font-weight: bold;" - ) # Bold heading - markdown_layout.addWidget( - heading_label, alignment=Qt.AlignTop - ) # Align heading at the top - - self.banner_label = QLabel() - self.banner_label.setPixmap( - QPixmap(resources_path("resources", "geest-banner.png")) - ) - self.banner_label.setScaledContents(True) # Allow image scaling - self.banner_label.setSizePolicy( - QSizePolicy.Expanding, QSizePolicy.Fixed - ) # Stretch horizontally, fixed vertically - - markdown_layout.addWidget(self.banner_label) - - # Create a horizontal splitter to hold both the Markdown editor and the preview - splitter = QSplitter(Qt.Horizontal) - - # Create the QTextEdit for Markdown editing (left side) - self.text_edit_left = QTextEdit() - self.text_edit_left.setPlainText(self.attributes.get("description", "")) - self.text_edit_left.setMinimumHeight(100) # Set at least 5 lines high - if self.editing: - splitter.addWidget(self.text_edit_left) - - # Create the QTextEdit for HTML preview (right side, styled to look like a label) - self.text_edit_right = QTextEdit() - self.text_edit_right.setReadOnly(True) # Set as read-only for preview - self.text_edit_right.setFrameStyle(QFrame.NoFrame) # Remove the frame - self.text_edit_right.setStyleSheet( - "background-color: transparent;" - ) # Match form background - splitter.addWidget(self.text_edit_right) - - markdown_layout.addWidget(splitter) - - # Connect the Markdown editor (left) to update the preview (right) in real-time - self.text_edit_left.textChanged.connect(self.update_preview) - - # Add an expanding spacer to push content above it upwards and below it downwards - expanding_spacer = QSpacerItem( - 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding - ) - markdown_layout.addSpacerItem(expanding_spacer) - - # Add the configuration frame with radio buttons - # If you are in edit mode you will not be preparing analysis - # but rather editing the json model document - if not self.editing: - self.add_config_widgets(markdown_layout) - - def setup_edit_tab(self): - """Sets up the right-hand tab for editing layer properties (table).""" - edit_layout = QVBoxLayout(self.edit_tab) - - # Create the QTableWidget for other properties - self.table = QTableWidget() - self.table.setColumnCount(2) # Two columns (Key and Value) - self.table.setHorizontalHeaderLabels(["Property", "Value"]) - - # Set the number of rows equal to the number of key-value pairs, excluding 'indicator' - self.populate_table() - - # Set column resize mode to stretch to fill the layout - header = self.table.horizontalHeader() - header.setSectionResizeMode(0, QHeaderView.Stretch) # Key column - header.setSectionResizeMode(1, QHeaderView.Stretch) # Value column - - edit_layout.addWidget(self.table) - - def populate_table(self): - """Populate the table with all key-value pairs except 'indicator'.""" - filtered_data = {k: v for k, v in self.attributes.items() if k != "indicator"} - self.table.setRowCount(len(filtered_data)) - - for row, (key, value) in enumerate(filtered_data.items()): - # Column 1: Key (Property name, read-only) - key_item = QTableWidgetItem(str(key)) - key_item.setFlags( - key_item.flags() & ~Qt.ItemIsEditable - ) # Make it read-only - self.table.setItem(row, 0, key_item) - - # Column 2: Value (use appropriate widgets based on data type) - value_widget = self.get_widget_for_value(key, value) - self.table.setCellWidget(row, 1, value_widget) - - def update_preview(self): - """Update the right text edit to show a live HTML preview of the Markdown.""" - markdown_text = self.text_edit_left.toPlainText() - # Set the rendered HTML into the right text edit - self.text_edit_right.setMarkdown(markdown_text) - - def get_widget_for_value(self, key, value): - """ - Returns an appropriate widget for the table based on the data type or key. - """ - if "use" in key or "rasterise" in key: - toggle_widget = ToggleSwitch(initial_value=bool(value)) - return toggle_widget - elif isinstance(value, bool): - checkbox = QCheckBox() - checkbox.setChecked(value) - return checkbox - elif isinstance(value, int): - spin_box = QSpinBox() - spin_box.setValue(value) - spin_box.setMaximum(99999) # Set an arbitrary maximum value - return spin_box - elif isinstance(value, float): - spin_box = QDoubleSpinBox() - spin_box.setDecimals(3) # Allow up to 3 decimal points - spin_box.setValue(value) - return spin_box - elif isinstance(value, list) and key == "options": - combo_box = QComboBox() - combo_box.addItems(value) - return combo_box - else: - # Use QLineEdit for other data types (default to string) - line_edit = QLineEdit(str(value)) - return line_edit - - def add_config_widgets(self, layout): - if not self.editing: - - self.config_widget = IndicatorConfigurationWidget(self.attributes) - if self.config_widget: - layout.addWidget(self.config_widget) - # connect to the stateChanged signal - # config_widget.stateChanged.connect(self.handle_config_change) - else: - log_message( - "No configuration widgets were created for this layer.", - tag="Geest", - level=Qgis.CRITICAL, - ) - - def handle_config_change(self, new_config): - """Optionally handle configuration changes.""" - self.attributes = new_config - log_message( - f"LayerDetailDialog config set to: {new_config}", - tag="Geest", - level=Qgis.Critical, - ) - - def accept_changes(self): - """Handle the OK button by applying changes and closing the dialog.""" - if self.editing: - # In editing mode, the edit table is canonical - updated_data = self.get_updated_data_from_table() - # Update the layer data with the new data - # This directly updates the tree item - self.attributes = updated_data - else: - # Otherwise, the custom widget is canonical - pass - - self.dataUpdated.emit() # Emit the updated data as a dictionary - self.accept() # Close the dialog - - def get_updated_data_from_table(self): - """Convert the table back into a dictionary with any changes made, including the Markdown text.""" - updated_data = self.attributes - - # Loop through the table and collect other data - for row in range(self.table.rowCount()): - key = self.table.item(row, 0).text() # Get the key (read-only) - value_widget = self.table.cellWidget( - row, 1 - ) # Get the widget from the second column - - if isinstance(value_widget, ToggleSwitch): - updated_value = value_widget.isChecked() - elif isinstance(value_widget, QCheckBox): - updated_value = value_widget.isChecked() - elif isinstance(value_widget, QSpinBox) or isinstance( - value_widget, QDoubleSpinBox - ): - updated_value = value_widget.value() - elif isinstance(value_widget, QComboBox): - updated_value = value_widget.currentText() - else: - updated_value = value_widget.text() # Default to text value - - updated_data[key] = ( - updated_value # Update the dictionary with the key-value pair - ) - - # Include the Markdown text from the left text edit - updated_data["description"] = self.text_edit_left.toPlainText() - - return updated_data diff --git a/geest/gui/indicator_configuration_widget.py b/geest/gui/indicator_configuration_widget.py deleted file mode 100644 index 608c2ac2..00000000 --- a/geest/gui/indicator_configuration_widget.py +++ /dev/null @@ -1,75 +0,0 @@ -from qgis.PyQt.QtWidgets import QWidget, QVBoxLayout, QButtonGroup -from qgis.core import Qgis -from qgis.PyQt.QtCore import pyqtSignal -from .combined_widget_factory import CombinedWidgetFactory -from geest.utilities import log_message - - -class IndicatorConfigurationWidget(QWidget): - """ - Widget for configuring indicators based on a dictionary. - """ - - data_changed = pyqtSignal() - - def __init__(self, attributes: dict) -> None: - super().__init__() - # This is a reference to the attributes dictionary - # So any changes made will propogate to the JSONTreeItem - self.attributes = attributes - self.layout: QVBoxLayout = QVBoxLayout() - self.button_group: QButtonGroup = QButtonGroup(self) - - try: - self.create_radio_buttons(attributes) - except Exception as e: - log_message(f"Error in create_radio_buttons: {e}", level=Qgis.Critical) - import traceback - - log_message(traceback.format_exc(), level=Qgis.Critical) - - self.setLayout(self.layout) - - def create_radio_buttons(self, attributes: dict) -> None: - """ - Uses the factory to create radio buttons from attributes dictionary. - """ - - analysis_mode = attributes.get("analysis_mode", "") - # We iterate over a list to defend against changes to the dictionary - # See issue #620. This is a workaround until we can refactor the code - # The issue is that the dictionary is being modified while we are iterating over it - # by the safety_polygon_configuration_widget.py file I think. - for key, value in list(attributes.items()): - radio_button_widget = CombinedWidgetFactory.create_radio_button( - key, value, attributes - ) - if radio_button_widget: - if key == analysis_mode: - radio_button_widget.setChecked(True) - # Special case for "Do Not Use" radio button - if ( - key == "indicator_required" - and value == 0 - and analysis_mode == "Do Not Use" - ): - radio_button_widget.setChecked(True) - self.button_group.addButton(radio_button_widget) - self.layout.addWidget(radio_button_widget.get_container()) - radio_button_widget.data_changed.connect(self.update_attributes) - - def update_attributes(self, new_data: dict) -> None: - """ - Updates the attributes dictionary with new data from radio buttons. - """ - # log_message(f"Updating attributes dictionary with new data: {new_data}") - # In the ctor of the widget factor we humanise the name - # now we roll it back to the snake case version so it matches keys - # in the JSON data model - snake_case_mode = ( - self.button_group.checkedButton().label_text.lower().replace(" ", "_") - ) - new_data["analysis_mode"] = snake_case_mode - self.attributes.update(new_data) - # log_message(f"Updated attributes dictionary: {self.attributes}") - self.data_changed.emit() diff --git a/geest/gui/panels/tree_panel.py b/geest/gui/panels/tree_panel.py index 0abb99e0..2f4cc0f7 100644 --- a/geest/gui/panels/tree_panel.py +++ b/geest/gui/panels/tree_panel.py @@ -46,7 +46,6 @@ from geest.core import setting, set_setting from geest.core import WorkflowQueueManager from geest.gui.dialogs import ( - IndicatorDetailDialog, FactorAggregationDialog, DimensionAggregationDialog, AnalysisAggregationDialog, @@ -204,25 +203,7 @@ def __init__(self, parent=None, json_file=None): self.workflow_progress_bar.setFixedWidth(200) button_bar.addWidget(self.workflow_progress_bar) - # Add Edit Toggle checkbox - if self.edit_mode: - self.edit_toggle = QCheckBox("Edit") - self.edit_toggle.setChecked(False) - self.edit_toggle.stateChanged.connect(self.toggle_edit_mode) - button_bar.addStretch() - - if self.edit_mode: - # Load and Save buttons - button_bar.addWidget(self.load_json_button) - button_bar.addWidget(self.export_json_button) - button_bar.addWidget(self.edit_toggle) # Add the edit toggle - - # Only allow editing on double-click (initially enabled) - editing = self.edit_mode and self.edit_toggle.isChecked() - if editing: - self.treeView.setEditTriggers(QTreeView.DoubleClicked) - else: - self.treeView.setEditTriggers(QTreeView.NoEditTriggers) + self.treeView.setEditTriggers(QTreeView.NoEditTriggers) layout.addLayout(button_bar) self.setLayout(layout) @@ -237,7 +218,7 @@ def on_item_double_clicked(self, index): # Action to trigger on double-click item = index.internalPointer() if item.role == "indicator": - self.show_indicator_properties(item) + self.show_attributes(item) elif item.role == "dimension": self.edit_dimension_aggregation(item) elif item.role == "factor": @@ -569,9 +550,7 @@ def update_action_text(): remove_indicator_action = QAction("❌ Remove Indicator", self) # Connect actions - show_properties_action.triggered.connect( - lambda: self.show_indicator_properties(item) - ) + show_properties_action.triggered.connect(lambda: self.show_attributes(item)) remove_indicator_action.triggered.connect( lambda: self.model.remove_item(item) ) @@ -958,36 +937,6 @@ def edit_factor_aggregation(self, factor_item): dialog.save_weightings_to_model() self.save_json_to_working_directory() # Save changes to the JSON if necessary - def show_indicator_properties(self, item): - """Open a dialog showing indicator properties and update the tree upon changes.""" - editing = self.edit_mode and self.edit_toggle.isChecked() - # Create and show the LayerDetailDialog - dialog = IndicatorDetailDialog(item, editing=editing, parent=self) - - # Connect the dialog's dataUpdated signal to handle data updates - def update_layer_data(): - # Save the JSON data to the working directory - self.save_json_to_working_directory() - - # Connect the signal emitted from the dialog to update the item - dialog.dataUpdated.connect(update_layer_data) - - # Show the dialog (exec_ will block until the dialog is closed) - dialog.exec_() - - def toggle_edit_mode(self): - """Enable or disable edit mode based on the 'Edit' toggle state.""" - edit_mode = self.edit_toggle.isChecked() - - # Enable or disable the "Add Dimension" button - self.add_dimension_button.setVisible(edit_mode) - - # Enable or disable double-click editing in the tree view - if edit_mode: - self.treeView.setEditTriggers(QTreeView.RightClicked) - else: - self.treeView.setEditTriggers(QTreeView.NoEditTriggers) - def start_workflows(self, type=None): """Start a workflow for each 'layer' node in the tree. diff --git a/geest/gui/widgets/combined_widgets/__init__.py b/geest/gui/widgets/combined_widgets/__init__.py deleted file mode 100644 index 132e6e39..00000000 --- a/geest/gui/widgets/combined_widgets/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Widgets package initialization file -from .base_indicator_widget import BaseIndicatorWidget -from .indicator_index_score_widget import IndexScoreRadioButton -from .dont_use_widget import DontUseRadioButton -from .multi_buffer_distances_widget import MultiBufferDistancesWidget -from .single_buffer_distance_widget import SingleBufferDistanceWidget -from .polygon_widget import PolygonWidget -from .polyline_widget import PolylineWidget -from .point_layer_widget import PointLayerWidget -from .acled_csv_layer_widget import AcledCsvLayerWidget -from .safety_polygon_widget import SafetyPolygonWidget -from .safety_raster_widget import SafetyRasterWidget -from .raster_reclassification_widget import RasterReclassificationWidget -from .street_lights_widget import StreetLightsWidget -from .classified_polygon_widget import ClassifiedPolygonWidget diff --git a/geest/gui/widgets/combined_widgets/acled_csv_layer_widget.py b/geest/gui/widgets/combined_widgets/acled_csv_layer_widget.py deleted file mode 100644 index 089d2034..00000000 --- a/geest/gui/widgets/combined_widgets/acled_csv_layer_widget.py +++ /dev/null @@ -1,190 +0,0 @@ -import os -from qgis.PyQt.QtWidgets import ( - QLabel, - QVBoxLayout, - QHBoxLayout, - QLineEdit, - QToolButton, - QFileDialog, - QMessageBox, - QSpinBox, -) -from qgis.core import Qgis -from geest.utilities import log_message -from .base_indicator_widget import BaseIndicatorWidget - - -class AcledCsvLayerWidget(BaseIndicatorWidget): - """ - A widget for selecting an ACLED CSV file and verifying its format. - - This widget allows the user to select a CSV file, checks if it's in the expected format - by verifying the presence of 'latitude', 'longitude', and 'event_type', and provides feedback - on the validity of the selected file. - - Attributes: - widget_key (str): The key identifier for this widget. - csv_file_line_edit (QLineEdit): Line edit for entering/selecting a CSV file. - """ - - def add_internal_widgets(self) -> None: - """ - Adds the internal widgets required for selecting the CSV file and validating its format. - This method is called during the widget initialization and sets up the layout for the UI components. - """ - try: - self.main_layout = QVBoxLayout() - self.widget_key = "csv_to_point_layer" - - # impact distance input - self.buffer_distance_layout = QHBoxLayout() - self.buffer_distance_label = QLabel("Incident Impact Distance (m):") - self.buffer_distance_input = QSpinBox() - self.buffer_distance_input.setRange(0, 100000) - self.buffer_distance_layout.addWidget(self.buffer_distance_label) - self.buffer_distance_layout.addWidget(self.buffer_distance_input) - # I dont think this is defined in the spreadsheet yet. - default_distance = self.attributes.get( - "{self.widget_key}_distance_default", 1000 - ) - buffer_distance = self.attributes.get( - "{self.widget_key}_distance", default_distance - ) - if buffer_distance == 0: - buffer_distance = default_distance - try: - self.buffer_distance_input.setValue(int(buffer_distance)) - except (ValueError, TypeError): - self.buffer_distance_input.setValue(int(default_distance)) - - # Add all layouts to the main layout - self.layout.addLayout(self.buffer_distance_layout) - self.buffer_distance_input.valueChanged.connect(self.update_data) - - # CSV File Section - self._add_csv_file_widgets() - - # Add the main layout to the widget's layout - self.layout.addLayout(self.main_layout) - - # Connect signals to update the data when user changes selections - self.csv_file_line_edit.textChanged.connect(self.update_data) - - except Exception as e: - log_message(f"Error in add_internal_widgets: {e}", level=Qgis.Critical) - import traceback - - log_message(traceback.format_exc(), level=Qgis.Critical) - - def _add_csv_file_widgets(self) -> None: - """ - Adds the widgets for selecting the CSV file, including a `QLineEdit` and a `QToolButton` to browse for files. - If the attribute set contains 'Use CSV to Point Layer CSV File', the line edit will be pre-filled. - """ - self.csv_file_label = QLabel("Select ACLED CSV File") - self.main_layout.addWidget(self.csv_file_label) - # Buffer distance input - - # CSV File Input and Selection Button - self.csv_file_layout = QHBoxLayout() - self.csv_file_line_edit = QLineEdit() - self.csv_file_button = QToolButton() - self.csv_file_button.setText("...") - self.csv_file_button.clicked.connect(self.select_csv_file) - self.csv_file_layout.addWidget(self.csv_file_line_edit) - self.csv_file_layout.addWidget(self.csv_file_button) - self.main_layout.addLayout(self.csv_file_layout) - - # If there is a pre-existing file path in the attributes, set it in the line edit - csv_file_path = self.attributes.get(f"{self.widget_key}_csv_file", None) - if csv_file_path: - self.csv_file_line_edit.setText(csv_file_path) - - def select_csv_file(self) -> None: - """ - Opens a file dialog to select a CSV file and updates the QLineEdit with the file path. - Also validates the selected file to check for required columns. - """ - try: - last_dir = os.getenv("GEEST_LAST_CSV_DIR", "") - - file_path, _ = QFileDialog.getOpenFileName( - self, "Select ACLED CSV File", last_dir, "CSV Files (*.csv)" - ) - if file_path: - self.csv_file_line_edit.setText(file_path) - self.validate_csv_file(file_path) - os.environ["GEEST_LAST_CSV_DIR"] = os.path.dirname(file_path) - - except Exception as e: - log_message(f"Error selecting CSV file: {e}", level=Qgis.Critical) - - def validate_csv_file(self, file_path: str) -> None: - """ - Validates the selected CSV file to ensure it contains the required columns: - 'latitude', 'longitude', and 'event_type'. - - :param file_path: The path to the selected CSV file. - """ - try: - required_columns = ["latitude", "longitude", "event_type"] - missing_columns = [] - - with open(file_path, "r", encoding="utf-8") as file: - header = file.readline().strip().split(",") - - for column in required_columns: - if column not in header: - missing_columns.append(column) - - if missing_columns: - error_message = f"Missing columns: {', '.join(missing_columns)}" - log_message(error_message, "Geest", Qgis.Critical) - QMessageBox.critical(self, "Invalid CSV", error_message) - else: - log_message("CSV file validation successful.") - QMessageBox.information( - self, "Valid CSV", "The selected CSV file is valid." - ) - - except Exception as e: - log_message(f"Error validating CSV file: {e}", level=Qgis.Critical) - QMessageBox.critical( - self, "CSV Validation Error", f"An error occurred: {e}" - ) - - def get_data(self) -> dict: - """ - Retrieves and returns the current state of the widget, including the selected CSV file path. - - Returns: - dict: A dictionary containing the current attributes of the widget. - """ - if not self.isChecked(): - return None - self.attributes[f"{self.widget_key}_distance"] = ( - self.buffer_distance_input.value() - ) - # Collect data for the CSV file - self.attributes[f"{self.widget_key}_csv_file"] = self.csv_file_line_edit.text() - - return self.attributes - - def set_internal_widgets_enabled(self, enabled: bool) -> None: - """ - Enables or disables the internal widgets (CSV file input) based on the state of the radio button. - - Args: - enabled (bool): Whether to enable or disable the internal widgets. - """ - try: - self.csv_file_line_edit.setEnabled(enabled) - self.csv_file_button.setEnabled(enabled) - self.buffer_distance_input.setEnabled(enabled) - self.buffer_distance_label.setEnabled(enabled) - except Exception as e: - log_message( - f"Error in set_internal_widgets_enabled: {e}", - tag="Geest", - level=Qgis.Critical, - ) diff --git a/geest/gui/widgets/combined_widgets/base_indicator_widget.py b/geest/gui/widgets/combined_widgets/base_indicator_widget.py deleted file mode 100644 index ffcac369..00000000 --- a/geest/gui/widgets/combined_widgets/base_indicator_widget.py +++ /dev/null @@ -1,91 +0,0 @@ -from qgis.PyQt.QtWidgets import QRadioButton, QVBoxLayout, QWidget -from qgis.PyQt.QtCore import pyqtSignal -from qgis.core import Qgis -from geest.utilities import log_message - - -class BaseIndicatorWidget(QRadioButton): - """ - Abstract base class for radio buttons with internal widgets. - """ - - data_changed = pyqtSignal(dict) - - def __init__(self, label_text: str, attributes: dict) -> None: - humanised_label = label_text.replace("_", " ").title() - super().__init__(humanised_label) - self.label_text = humanised_label - self.attributes = attributes - self.container: QWidget = QWidget() - self.layout: QVBoxLayout = QVBoxLayout(self.container) - self.layout.addWidget(self) - - # Log creation of widget - log_message("Creating Indicator Configuration Widget") - log_message("----------------------------------") - for item in self.attributes.items(): - log_message(f"{item[0]}: {item[1]}") - log_message("----------------------------------") - - try: - self.add_internal_widgets() - except Exception as e: - log_message(f"Error in add_internal_widgets: {e}", level=Qgis.Critical) - - # Connect toggled signal to enable/disable internal widgets - self.toggled.connect(self.on_toggled) - - # Initially disable internal widgets if not checked - self.set_internal_widgets_enabled(self.isChecked()) - - def add_internal_widgets(self) -> None: - """ - Add internal widgets; to be implemented by subclasses. - """ - raise NotImplementedError("Subclasses must implement add_internal_widgets.") - - def get_container(self) -> QWidget: - """ - Returns the container holding the radio button and its internal widgets. - """ - return self.container - - def get_data(self) -> dict: - """ - Method to get data from internal widgets. - To be implemented by subclasses. - """ - raise NotImplementedError("Subclasses must implement get_data.") - - def update_data(self) -> None: - """ - Gathers data from internal widgets and emits the data_changed signal. - """ - if self.isChecked(): - log_message("Updating data...") - try: - data = self.get_data() - # log_message(f"Data: {data}") - self.data_changed.emit(data) - except Exception as e: - log_message(f"Error in update_data: {e}", level=Qgis.Critical) - - def on_toggled(self, checked: bool) -> None: - """ - Slot for when the radio button is toggled. - Enables/disables internal widgets based on the radio button state. - """ - self.set_internal_widgets_enabled(checked) - - # Emit data changed only if the radio button is checked - if checked: - self.update_data() - - def set_internal_widgets_enabled(self, enabled: bool) -> None: - """ - Enables or disables the internal widgets based on the radio button state. - To be implemented by subclasses to manage their internal widgets. - """ - raise NotImplementedError( - "Subclasses must implement set_internal_widgets_enabled." - ) diff --git a/geest/gui/widgets/combined_widgets/classified_polygon_widget.py b/geest/gui/widgets/combined_widgets/classified_polygon_widget.py deleted file mode 100644 index 9898fc3d..00000000 --- a/geest/gui/widgets/combined_widgets/classified_polygon_widget.py +++ /dev/null @@ -1,279 +0,0 @@ -import os -from qgis.PyQt.QtWidgets import ( - QLabel, - QVBoxLayout, - QHBoxLayout, - QLineEdit, - QToolButton, - QFileDialog, -) -from qgis.gui import QgsMapLayerComboBox, QgsFieldComboBox -from qgis.core import ( - QgsMapLayerProxyModel, - QgsProject, - QgsVectorLayer, - QgsFieldProxyModel, - Qgis, -) -from qgis.PyQt.QtCore import QSettings -from .base_indicator_widget import BaseIndicatorWidget -from geest.utilities import log_message - - -class ClassifiedPolygonWidget(BaseIndicatorWidget): - """ - - A widget for selecting a polygon (area) layer with options for shapefile inputs. - - This widget provides one `QgsMapLayerComboBox` components for selecting polygon layers, - as well as `QLineEdit` and `QToolButton` components to allow the user to specify shapefile paths for - each layer. - - The user can choose layers either from the QGIS project or provide external shapefiles. - - The user can choose a numeric field from the selected polygon layer to classify the polygons into classes. - - Attributes: - widget_key (str): The key identifier for this widget. - polygon_layer_combo (QgsMapLayerComboBox): A combo box for selecting the polygon layer. - polygon_shapefile_line_edit (QLineEdit): Line edit for entering/selecting a polygon layer shapefile. - """ - - def add_internal_widgets(self) -> None: - """ - Adds the internal widgets required for selecting polygon layers and their corresponding shapefiles. - This method is called during the widget initialization and sets up the layout for the UI components. - """ - try: - self.main_layout = QVBoxLayout() - self.widget_key = "classify_polygon_into_classes" - self.settings = QSettings() - - # Polygon Layer Section - self._add_polygon_layer_widgets() - - # Add Field Selection Dropdown - self._add_field_selection_widgets() - - # Add the main layout to the widget's layout - self.layout.addLayout(self.main_layout) - - # Connect signals to update the data when user changes selections - self.polygon_layer_combo.currentIndexChanged.connect(self.update_data) - self.polygon_shapefile_line_edit.textChanged.connect(self.update_data) - # Connect signals to update the fields when user changes selections - self.polygon_layer_combo.layerChanged.connect(self.update_field_combo) - self.polygon_shapefile_line_edit.textChanged.connect( - self.update_field_combo - ) - - # Connect the field combo box to update the attributes when a field is selected - self.field_selection_combo.currentIndexChanged.connect( - self.update_selected_field - ) - - self.update_field_combo() # Populate fields for the initially selected layer - - except Exception as e: - log_message(f"Error in add_internal_widgets: {e}", level=Qgis.Critical) - import traceback - - log_message(traceback.format_exc(), level=Qgis.Critical) - - def _add_polygon_layer_widgets(self) -> None: - """ - Adds the widgets for selecting the polygon layer, including a `QgsMapLayerComboBox` and shapefile input. - """ - self.polygon_layer_label = QLabel( - "Polygon Layer - shapefile will have preference" - ) - self.main_layout.addWidget(self.polygon_layer_label) - # Polygon Layer ComboBox (Filtered to polygon layers) - self.polygon_layer_combo = QgsMapLayerComboBox() - self.polygon_layer_combo.setFilters(QgsMapLayerProxyModel.PolygonLayer) - self.main_layout.addWidget(self.polygon_layer_combo) - - # Restore previously selected polygon layer - polygon_layer_id = self.attributes.get(f"{self.widget_key}_layer_id", None) - if polygon_layer_id: - polygon_layer = QgsProject.instance().mapLayer(polygon_layer_id) - if polygon_layer: - self.polygon_layer_combo.setLayer(polygon_layer) - - # _shapefile Input for Polygon Layer - self.polygon_shapefile_layout = QHBoxLayout() - self.polygon_shapefile_line_edit = QLineEdit() - self.polygon_shapefile_button = QToolButton() - self.polygon_shapefile_button.setText("...") - self.polygon_shapefile_button.clicked.connect(self.select_polygon_shapefile) - if self.attributes.get(f"{self.widget_key}_shapefile", False): - self.polygon_shapefile_line_edit.setText( - self.attributes[f"{self.widget_key}_shapefile"] - ) - self.polygon_shapefile_layout.addWidget(self.polygon_shapefile_line_edit) - self.polygon_shapefile_layout.addWidget(self.polygon_shapefile_button) - self.main_layout.addLayout(self.polygon_shapefile_layout) - - def _add_field_selection_widgets(self) -> None: - """ - Adds a dropdown to select a specific field from the selected shapefile. - """ - self.field_selection_label = QLabel("Select Field") - self.main_layout.addWidget(self.field_selection_label) - - self.field_selection_combo = QgsFieldComboBox() - self.field_selection_combo.setFilters(QgsFieldProxyModel.Numeric) - self.field_selection_combo.setEnabled( - False - ) # Disable initially until a layer is selected - self.main_layout.addWidget(self.field_selection_combo) - - def select_polygon_shapefile(self) -> None: - """ - Opens a file dialog to select a shapefile for the polygon (paths) layer and updates the QLineEdit with the file path. - """ - try: - last_dir = self.settings.value("Geest/lastShapefileDir", "") - - file_path, _ = QFileDialog.getOpenFileName( - self, "Select Polygon Shapefile", last_dir, "Shapefiles (*.shp)" - ) - if file_path: - self.polygon_shapefile_line_edit.setText(file_path) - self.settings.setValue( - "Geest/lastShapefileDir", os.path.dirname(file_path) - ) - - # Load the shapefile as a QgsVectorLayer and populate the fields dropdown - self._populate_field_combo(file_path) - - except Exception as e: - log_message( - f"Error selecting polygon shapefile: {e}", - tag="Geest", - level=Qgis.Critical, - ) - - def _populate_field_combo(self, shapefile_path: str) -> None: - """ - Loads the shapefile and populates the field selection combo box with the field names. - - Args: - shapefile_path (str): The path to the shapefile. - """ - try: - # Store the currently selected field - previous_field = self.settings.value( - f"{self.widget_key}_selected_field", None - ) - - vector_layer = QgsVectorLayer(shapefile_path, "polygon_layer", "ogr") - if not vector_layer.isValid(): - log_message(f"Failed to load shapefile: {shapefile_path}", "Geest") - return - - # Set the vector layer on the field selection combo box, which will automatically populate it - QgsProject.instance().addMapLayer(vector_layer, False) - self.field_selection_combo.setLayer(vector_layer) - self.field_selection_combo.setEnabled(True) # Enable once layer is valid - - # Reapply the previously selected field if it exists - if ( - previous_field - and self.field_selection_combo.findText(previous_field) != -1 - ): - self.field_selection_combo.setCurrentText(previous_field) - - except Exception as e: - log_message(f"Error populating field combo: {e}", level=Qgis.Critical) - - def update_field_combo(self) -> None: - """ - Updates the field combo box when the polygon layer or shapefile is changed. - """ - # Store the currently selected field - previous_field = self.settings.value(f"{self.widget_key}_selected_field", None) - - if self.polygon_layer_combo.currentLayer(): - # Populate field combo from the selected polygon layer - self.field_selection_combo.setLayer(self.polygon_layer_combo.currentLayer()) - self.field_selection_combo.setEnabled(True) - elif self.polygon_shapefile_line_edit.text(): - # If shapefile is provided, populate the field combo - self._populate_field_combo(self.polygon_shapefile_line_edit.text()) - - # After the field combo is repopulated, re-select the previously selected field if it exists - if previous_field and self.field_selection_combo.findText(previous_field) != -1: - self.field_selection_combo.setCurrentText(previous_field) - - def get_data(self) -> dict: - """ - Retrieves and returns the current state of the widget, including selected polygon layers or shapefiles. - - Returns: - dict: A dictionary containing the current attributes of the polygon layers and/or shapefiles. - """ - if not self.isChecked(): - return None - - # Collect data for the polygon layer - polygon_layer = self.polygon_layer_combo.currentLayer() - if polygon_layer: - self.attributes[f"{self.widget_key}_layer_name"] = polygon_layer.name() - self.attributes[f"{self.widget_key}_layer_source"] = polygon_layer.source() - self.attributes[f"{self.widget_key}_layer_provider_type"] = ( - polygon_layer.providerType() - ) - self.attributes[f"{self.widget_key}_layer_crs"] = ( - polygon_layer.crs().authid() - ) - self.attributes[f"{self.widget_key}_layer_wkb_type"] = ( - polygon_layer.wkbType() - ) - self.attributes[f"{self.widget_key}_layer_id"] = polygon_layer.id() - self.attributes[f"{self.widget_key}_shapefile"] = ( - self.polygon_shapefile_line_edit.text() - ) - # Get the selected field if field combo box is enabled - selected_field = ( - self.field_selection_combo.currentText() - if self.field_selection_combo.isEnabled() - else None - ) - self.attributes[f"{self.widget_key}_selected_field"] = selected_field - - return self.attributes - - def update_selected_field(self) -> None: - """ - Updates the selected field in the attributes dictionary when the field selection changes. - """ - if self.field_selection_combo.isEnabled(): - selected_field = self.field_selection_combo.currentText() - self.attributes[f"{self.widget_key}_selected_field"] = selected_field - - # Store the selected field in QSettings - self.settings.setValue(f"{self.widget_key}_selected_field", selected_field) - else: - self.attributes[f"{self.widget_key}_selected_field"] = None - - def set_internal_widgets_enabled(self, enabled: bool) -> None: - """ - Enables or disables the internal widgets (polygon layers) based on the state of the radio button. - - Args: - enabled (bool): Whether to enable or disable the internal widgets. - """ - try: - self.polygon_layer_combo.setEnabled(enabled) - self.polygon_shapefile_line_edit.setEnabled(enabled) - self.polygon_shapefile_button.setEnabled(enabled) - self.field_selection_combo.setEnabled( - enabled and self.field_selection_combo.count() > 0 - ) - except Exception as e: - log_message( - f"Error in set_internal_widgets_enabled: {e}", - tag="Geest", - level=Qgis.Critical, - ) diff --git a/geest/gui/widgets/combined_widgets/dont_use_widget.py b/geest/gui/widgets/combined_widgets/dont_use_widget.py deleted file mode 100644 index 9865c0d1..00000000 --- a/geest/gui/widgets/combined_widgets/dont_use_widget.py +++ /dev/null @@ -1,25 +0,0 @@ -from .base_indicator_widget import BaseIndicatorWidget - - -class DontUseRadioButton(BaseIndicatorWidget): - """ - A specialized radio button with additional widgets for IndexScore. - """ - - def add_internal_widgets(self) -> None: - """ - Adds internal widgets specific to Dont Use - in this case there are none. - """ - pass - - def get_data(self) -> dict: - """ - Return the data as a dictionary, updating attributes with current value. - """ - return self.attributes - - def set_internal_widgets_enabled(self, enabled: bool) -> None: - """ - Enables or disables the internal widgets based on the state of the radio button. - """ - pass diff --git a/geest/gui/widgets/combined_widgets/indicator_index_score_widget.py b/geest/gui/widgets/combined_widgets/indicator_index_score_widget.py deleted file mode 100644 index 24aa6047..00000000 --- a/geest/gui/widgets/combined_widgets/indicator_index_score_widget.py +++ /dev/null @@ -1,50 +0,0 @@ -from qgis.PyQt.QtWidgets import QLabel, QDoubleSpinBox -from .base_indicator_widget import BaseIndicatorWidget -from qgis.core import Qgis -from geest.utilities import log_message - - -class IndexScoreRadioButton(BaseIndicatorWidget): - """ - A specialized radio button with additional widgets for IndexScore. - """ - - def add_internal_widgets(self) -> None: - """ - Adds internal widgets specific to IndexScore. - """ - try: - self.info_label: QLabel = QLabel(self.label_text) - self.index_input: QDoubleSpinBox = QDoubleSpinBox() - self.index_input.setRange(0, 100) - self.layout.addWidget(self.info_label) - self.layout.addWidget(self.index_input) - self.index_input.setValue(self.attributes["default_index_score"]) - # Connect the valueChanged signal to update data - self.index_input.valueChanged.connect(self.update_data) - except Exception as e: - log_message( - f"Error in add_internal_widgets: {e}", "Geest", level=Qgis.Critical - ) - - def get_data(self) -> dict: - """ - Return the data as a dictionary, updating attributes with current value. - """ - if self.isChecked(): - self.attributes["default_index_score"] = self.index_input.value() - return self.attributes - - def set_internal_widgets_enabled(self, enabled: bool) -> None: - """ - Enables or disables the internal widgets based on the state of the radio button. - """ - try: - self.info_label.setEnabled(enabled) - self.index_input.setEnabled(enabled) - except Exception as e: - log_message( - f"Error in set_internal_widgets_enabled: {e}", - "Geest", - level=Qgis.Critical, - ) diff --git a/geest/gui/widgets/combined_widgets/multi_buffer_distances_widget.py b/geest/gui/widgets/combined_widgets/multi_buffer_distances_widget.py deleted file mode 100644 index e092f611..00000000 --- a/geest/gui/widgets/combined_widgets/multi_buffer_distances_widget.py +++ /dev/null @@ -1,217 +0,0 @@ -from qgis.PyQt.QtWidgets import ( - QLabel, - QGroupBox, - QRadioButton, - QVBoxLayout, - QHBoxLayout, - QLineEdit, - QToolButton, - QFileDialog, -) -from qgis.gui import QgsMapLayerComboBox -from .base_indicator_widget import BaseIndicatorWidget -from qgis.core import QgsMapLayerProxyModel, QgsProject, QgsVectorLayer -from qgis.PyQt.QtCore import QSettings -from geest.utilities import log_message - - -class MultiBufferDistancesWidget(BaseIndicatorWidget): - """ - A specialized radio button with additional widgetQDoubleSpinBoxs for IndexScore. - """ - - def add_internal_widgets(self) -> None: - """ - Adds internal widgets specific to self.set_internal_widgets_visible(self.isChecked()) - in this case there are none. - """ - try: - self.main_layout = QVBoxLayout() - self.widget_key = "multi_buffer_point" - # Point Layer Combobox - Filtered to point layers - self.point_layer_label = QLabel( - "Point Layer - shapefile will have preference" - ) - self.main_layout.addWidget(self.point_layer_label) - - self.layer_combo = QgsMapLayerComboBox() - self.layer_combo.setFilters(QgsMapLayerProxyModel.PointLayer) - self.main_layout.addWidget(self.layer_combo) - - # Set the selected QgsVectorLayer in QgsMapLayerComboBox - layer_id = self.attributes.get(f"{self.widget_key}_layer_id", None) - if layer_id: - layer = QgsProject.instance().mapLayer(layer_id) - if layer: - self.layer_combo.setLayer(layer) - layer_id = self.attributes.get(f"{self.widget_key}_layer_id") - layer = QgsProject.instance().mapLayer(layer_id) - - if layer and isinstance(layer, QgsVectorLayer): - self.layer_combo.setLayer(layer) - - # Add shapefile selection (QLineEdit and QToolButton) - self.shapefile_layout = QHBoxLayout() - self.shapefile_line_edit = QLineEdit() - self.shapefile_button = QToolButton() - self.shapefile_button.setText("...") - self.shapefile_button.clicked.connect(self.select_shapefile) - if self.attributes.get(f"{self.widget_key}_shapefile", False): - self.shapefile_line_edit.setText( - self.attributes[f"{self.widget_key}_shapefile"] - ) - self.shapefile_layout.addWidget(self.shapefile_line_edit) - self.shapefile_layout.addWidget(self.shapefile_button) - self.main_layout.addLayout(self.shapefile_layout) - - # Travel Mode group - self.travel_mode_group = QGroupBox("Travel Mode:") - self.travel_mode_layout = QHBoxLayout() - self.walking_radio = QRadioButton("Walking") - self.driving_radio = QRadioButton("Driving") - if self.attributes.get("multi_buffer_travel_mode", "") == "Walking": - self.walking_radio.setChecked(True) - else: - self.driving_radio.setChecked(True) # Default selection - self.travel_mode_layout.addWidget(self.walking_radio) - self.travel_mode_layout.addWidget(self.driving_radio) - self.travel_mode_group.setLayout(self.travel_mode_layout) - - # Measurement group - self.measurement_group = QGroupBox("Measurement:") - self.measurement_layout = QHBoxLayout() - self.distance_radio = QRadioButton("Distance") - self.time_radio = QRadioButton("Time") - if self.attributes.get("multi_buffer_travel_units", "") == "Distance": - self.distance_radio.setChecked(True) - else: - self.time_radio.setChecked(True) # Default selection - self.measurement_layout.addWidget(self.distance_radio) - self.measurement_layout.addWidget(self.time_radio) - self.measurement_group.setLayout(self.measurement_layout) - - # Travel Increments input - self.travel_increments_layout = QHBoxLayout() - self.increments_label = QLabel("Travel Increments:") - self.increments_input = QLineEdit("") - self.travel_increments_layout.addWidget(self.increments_label) - self.travel_increments_layout.addWidget(self.increments_input) - if self.attributes.get("multi_buffer_travel_distances", False): - self.increments_input.setText( - self.attributes["multi_buffer_travel_distances"] - ) - else: - self.increments_input.setText( - str(self.attributes.get("default_multi_buffer_distances", "")) - ) - - # Add all layouts to the main layout - self.main_layout.addWidget(self.travel_mode_group) - self.main_layout.addWidget(self.measurement_group) - self.main_layout.addLayout(self.travel_increments_layout) - self.layout.addLayout(self.main_layout) - - # Emit the data_changed signal when any widget is changed - self.layer_combo.currentIndexChanged.connect(self.update_data) - self.time_radio.toggled.connect(self.update_data) - self.distance_radio.toggled.connect(self.update_data) - self.walking_radio.toggled.connect(self.update_data) - self.driving_radio.toggled.connect(self.update_data) - self.increments_input.textChanged.connect(self.update_data) - self.shapefile_line_edit.textChanged.connect(self.update_data) - - except Exception as e: - log_message(f"Error in add_internal_widgets: {e}", level=Qgis.Critical) - import traceback - - log_message(traceback.format_exc(), level=Qgis.Critical) - - def select_shapefile(self): - """ - Opens a file dialog to select a shapefile and stores the last directory in QSettings. - """ - try: - settings = QSettings() - last_dir = settings.value("Geest/lastShapefileDir", "") - - # Open file dialog to select a shapefile - file_path, _ = QFileDialog.getOpenFileName( - self, "Select Shapefile", last_dir, "Shapefiles (*.shp)" - ) - - if file_path: - # Update the line edit with the selected file path - self.shapefile_line_edit.setText(file_path) - - # Save the directory of the selected file to QSettings - settings.setValue("Geest/lastShapefileDir", os.path.dirname(file_path)) - - except Exception as e: - log_message(f"Error selecting shapefile: {e}", level=Qgis.Critical) - - def get_data(self) -> dict: - """ - Return the data as a dictionary, updating attributes with current value. - """ - if not self.isChecked(): - return None - - layer = self.layer_combo.currentLayer() - if not layer: - self.attributes[f"{self.widget_key}_layer"] = None - else: - self.attributes[f"{self.widget_key}_layer_name"] = layer.name() - self.attributes[f"{self.widget_key}_layer_source"] = layer.source() - self.attributes[f"{self.widget_key}_layer_provider_type"] = ( - layer.providerType() - ) - self.attributes[f"{self.widget_key}_layer_crs"] = ( - layer.crs().authid() - ) # Coordinate Reference System - self.attributes[f"{self.widget_key}_layer_wkb_type"] = ( - layer.wkbType() - ) # Geometry type (e.g., Point, Polygon) - self.attributes[f"{self.widget_key}_layer_id"] = ( - layer.id() - ) # Unique ID of the layer - - if self.walking_radio.isChecked(): - self.attributes["multi_buffer_travel_mode"] = "Walking" - else: - self.attributes["multi_buffer_travel_mode"] = "Driving" - - if self.distance_radio.isChecked(): - self.attributes["multi_buffer_travel_units"] = "Distance" - else: - self.attributes["multi_buffer_travel_units"] = "Time" - - self.attributes["multi_buffer_travel_distances"] = self.increments_input.text() - self.attributes[f"{self.widget_key}_shapefile"] = ( - self.shapefile_line_edit.text() - ) - - return self.attributes - - def set_internal_widgets_enabled(self, enabled: bool) -> None: - """ - Enables or disables the internal widgets based on the state of the radio button. - """ - try: - self.layer_combo.setEnabled(enabled) - self.distance_radio.setEnabled(enabled) - self.time_radio.setEnabled(enabled) - self.walking_radio.setEnabled(enabled) - self.driving_radio.setEnabled(enabled) - self.increments_input.setEnabled(enabled) - self.increments_label.setEnabled(enabled) - self.point_layer_label.setEnabled(enabled) - self.travel_mode_group.setEnabled(enabled) - self.measurement_group.setEnabled(enabled) - self.travel_increments_layout.setEnabled(enabled) - self.shapefile_line_edit.setEnabled(enabled) - self.shapefile_button.setEnabled(enabled) - except Exception as e: - log_message( - f"Error in set_internal_widgets_enabled: {e}", - tag="Geest", - level=Qgis.Critical, - ) diff --git a/geest/gui/widgets/combined_widgets/point_layer_widget.py b/geest/gui/widgets/combined_widgets/point_layer_widget.py deleted file mode 100644 index f3742b65..00000000 --- a/geest/gui/widgets/combined_widgets/point_layer_widget.py +++ /dev/null @@ -1,154 +0,0 @@ -import os -from qgis.PyQt.QtWidgets import ( - QLabel, - QVBoxLayout, - QHBoxLayout, - QLineEdit, - QToolButton, - QFileDialog, -) -from qgis.gui import QgsMapLayerComboBox -from qgis.core import QgsMapLayerProxyModel, QgsProject, Qgis -from qgis.PyQt.QtCore import QSettings -from .base_indicator_widget import BaseIndicatorWidget -from geest.utilities import log_message - - -class PointLayerWidget(BaseIndicatorWidget): - """ - A widget for selecting a point layer with options for shapefile inputs. - - This widget provides a `QgsMapLayerComboBox` component for selecting the point layers, - as well as `QLineEdit` and `QToolButton` components to allow the user to specify shapefile paths for - the layer. The user can choose layers either from the QGIS project or provide external shapefiles. - - Attributes: - widget_key (str): The key identifier for this widget. - point_layer_combo (QgsMapLayerComboBox): A combo box for selecting the point layer. - point_shapefile_line_edit (QLineEdit): Line edit for entering/selecting a point layer shapefile. - """ - - def add_internal_widgets(self) -> None: - """ - Adds the internal widgets required for selecting point and polyline layers and their corresponding shapefiles. - This method is called during the widget initialization and sets up the layout for the UI components. - """ - try: - self.main_layout = QVBoxLayout() - self.widget_key = "point_per_cell" - - # Point Layer Section - self._add_point_layer_widgets() - - # Add the main layout to the widget's layout - self.layout.addLayout(self.main_layout) - - # Connect signals to update the data when user changes selections - self.point_layer_combo.currentIndexChanged.connect(self.update_data) - self.point_shapefile_line_edit.textChanged.connect(self.update_data) - - except Exception as e: - log_message(f"Error in add_internal_widgets: {e}", level=Qgis.Critical) - import traceback - - log_message(traceback.format_exc(), level=Qgis.Critical) - - def _add_point_layer_widgets(self) -> None: - """ - Adds the widgets for selecting the point layer, including a `QgsMapLayerComboBox` and shapefile input. - """ - self.point_layer_label = QLabel("Point Layer - shapefile will have preference") - self.main_layout.addWidget(self.point_layer_label) - - # Point Layer ComboBox (Filtered to point layers) - self.point_layer_combo = QgsMapLayerComboBox() - self.point_layer_combo.setFilters(QgsMapLayerProxyModel.PointLayer) - self.main_layout.addWidget(self.point_layer_combo) - - # Restore previously selected point layer - point_layer_id = self.attributes.get(f"{self.widget_key}_layer_id", None) - if point_layer_id: - point_layer = QgsProject.instance().mapLayer(point_layer_id) - if point_layer: - self.point_layer_combo.setLayer(point_layer) - - # _shapefile Input for Point Layer - self.point_shapefile_layout = QHBoxLayout() - self.point_shapefile_line_edit = QLineEdit() - self.point_shapefile_button = QToolButton() - self.point_shapefile_button.setText("...") - self.point_shapefile_button.clicked.connect(self.select_point_shapefile) - if self.attributes.get(f"{self.widget_key}_shapefile", False): - self.point_shapefile_line_edit.setText( - self.attributes[f"{self.widget_key}_shapefile"] - ) - self.point_shapefile_layout.addWidget(self.point_shapefile_line_edit) - self.point_shapefile_layout.addWidget(self.point_shapefile_button) - self.main_layout.addLayout(self.point_shapefile_layout) - - def select_point_shapefile(self) -> None: - """ - Opens a file dialog to select a shapefile for the point layer and updates the QLineEdit with the file path. - """ - try: - settings = QSettings() - last_dir = settings.value("Geest/lastShapefileDir", "") - - file_path, _ = QFileDialog.getOpenFileName( - self, "Select Point Shapefile", last_dir, "Shapefiles (*.shp)" - ) - if file_path: - self.point_shapefile_line_edit.setText(file_path) - settings.setValue("Geest/lastShapefileDir", os.path.dirname(file_path)) - - except Exception as e: - log_message( - f"Error selecting point shapefile: {e}", - tag="Geest", - level=Qgis.Critical, - ) - - def get_data(self) -> dict: - """ - Retrieves and returns the current state of the widget, including selected point and polyline layers or shapefiles. - - Returns: - dict: A dictionary containing the current attributes of the point and polyline layers and/or shapefiles. - """ - if not self.isChecked(): - return None - - # Collect data for the point layer - point_layer = self.point_layer_combo.currentLayer() - if point_layer: - self.attributes[f"{self.widget_key}_layer_name"] = point_layer.name() - self.attributes[f"{self.widget_key}_layer_source"] = point_layer.source() - self.attributes[f"{self.widget_key}_layer_provider_type"] = ( - point_layer.providerType() - ) - self.attributes[f"{self.widget_key}_layer_crs"] = point_layer.crs().authid() - self.attributes[f"{self.widget_key}_layer_wkb_type"] = point_layer.wkbType() - self.attributes[f"{self.widget_key}_layer_id"] = point_layer.id() - self.attributes[f"{self.widget_key}_shapefile"] = ( - self.point_shapefile_line_edit.text() - ) - - return self.attributes - - def set_internal_widgets_enabled(self, enabled: bool) -> None: - """ - Enables or disables the internal widgets (both point and polyline layers) based on the state of the radio button. - - Args: - enabled (bool): Whether to enable or disable the internal widgets. - """ - try: - self.point_layer_combo.setEnabled(enabled) - self.point_shapefile_line_edit.setEnabled(enabled) - self.point_shapefile_button.setEnabled(enabled) - except Exception as e: - log_message( - f"Error in set_internal_widgets_enabled: {e}", - tag="Geest", - level=Qgis.Critical, - ) diff --git a/geest/gui/widgets/combined_widgets/polygon_widget.py b/geest/gui/widgets/combined_widgets/polygon_widget.py deleted file mode 100644 index 283ce64a..00000000 --- a/geest/gui/widgets/combined_widgets/polygon_widget.py +++ /dev/null @@ -1,162 +0,0 @@ -import os -from qgis.PyQt.QtWidgets import ( - QLabel, - QVBoxLayout, - QHBoxLayout, - QLineEdit, - QToolButton, - QFileDialog, -) -from qgis.gui import QgsMapLayerComboBox -from qgis.core import QgsMapLayerProxyModel, QgsProject, QgsVectorLayer -from qgis.PyQt.QtCore import QSettings -from geest.utilities import log_message -from .base_indicator_widget import BaseIndicatorWidget - - -class PolygonWidget(BaseIndicatorWidget): - """ - - A widget for selecting a polygon (area) layer with options for shapefile inputs. - - This widget provides one `QgsMapLayerComboBox` components for selecting polygon layers, - as well as `QLineEdit` and `QToolButton` components to allow the user to specify shapefile paths for - each layer. The user can choose layers either from the QGIS project or provide external shapefiles. - - Attributes: - widget_key (str): The key identifier for this widget. - polygon_layer_combo (QgsMapLayerComboBox): A combo box for selecting the polygon layer. - polygon_shapefile_line_edit (QLineEdit): Line edit for entering/selecting a polygon layer shapefile. - """ - - def add_internal_widgets(self) -> None: - """ - Adds the internal widgets required for selecting polygon layers and their corresponding shapefiles. - This method is called during the widget initialization and sets up the layout for the UI components. - """ - try: - self.main_layout = QVBoxLayout() - self.widget_key = "polygon_per_cell" - - # Polygon Layer Section - self._add_polygon_layer_widgets() - - # Add the main layout to the widget's layout - self.layout.addLayout(self.main_layout) - - # Connect signals to update the data when user changes selections - self.polygon_layer_combo.currentIndexChanged.connect(self.update_data) - self.polygon_shapefile_line_edit.textChanged.connect(self.update_data) - - except Exception as e: - log_message(f"Error in add_internal_widgets: {e}", level=Qgis.Critical) - import traceback - - log_message(traceback.format_exc(), level=Qgis.Critical) - - def _add_polygon_layer_widgets(self) -> None: - """ - Adds the widgets for selecting the polygon layer, including a `QgsMapLayerComboBox` and shapefile input. - """ - self.polygon_layer_label = QLabel( - "Polygon Layer - shapefile will have preference" - ) - self.main_layout.addWidget(self.polygon_layer_label) - # Polygon Layer ComboBox (Filtered to polygon layers) - self.polygon_layer_combo = QgsMapLayerComboBox() - self.polygon_layer_combo.setFilters(QgsMapLayerProxyModel.PolygonLayer) - self.main_layout.addWidget(self.polygon_layer_combo) - - # Restore previously selected polygon layer - polygon_layer_id = self.attributes.get( - f"{self.widget_key}_polygon_layer_id", None - ) - if polygon_layer_id: - polygon_layer = QgsProject.instance().mapLayer(polygon_layer_id) - if polygon_layer: - self.polygon_layer_combo.setLayer(polygon_layer) - - # _shapefile Input for Polygon Layer - self.polygon_shapefile_layout = QHBoxLayout() - self.polygon_shapefile_line_edit = QLineEdit() - self.polygon_shapefile_button = QToolButton() - self.polygon_shapefile_button.setText("...") - self.polygon_shapefile_button.clicked.connect(self.select_polygon_shapefile) - if self.attributes.get(f"{self.widget_key}_polygon_shapefile", False): - self.polygon_shapefile_line_edit.setText( - self.attributes[f"{self.widget_key}_polygon_shapefile"] - ) - self.polygon_shapefile_layout.addWidget(self.polygon_shapefile_line_edit) - self.polygon_shapefile_layout.addWidget(self.polygon_shapefile_button) - self.main_layout.addLayout(self.polygon_shapefile_layout) - - def select_polygon_shapefile(self) -> None: - """ - Opens a file dialog to select a shapefile for the polygon (paths) layer and updates the QLineEdit with the file path. - """ - try: - settings = QSettings() - last_dir = settings.value("Geest/lastShapefileDir", "") - - file_path, _ = QFileDialog.getOpenFileName( - self, "Select Polygon Shapefile", last_dir, "Shapefiles (*.shp)" - ) - if file_path: - self.polygon_shapefile_line_edit.setText(file_path) - settings.setValue("Geest/lastShapefileDir", os.path.dirname(file_path)) - - except Exception as e: - log_message( - f"Error selecting polygon shapefile: {e}", - tag="Geest", - level=Qgis.Critical, - ) - - def get_data(self) -> dict: - """ - Retrieves and returns the current state of the widget, including selected polygon layers or shapefiles. - - Returns: - dict: A dictionary containing the current attributes of the polygon layers and/or shapefiles. - """ - if not self.isChecked(): - return None - - # Collect data for the polygon layer - polygon_layer = self.polygon_layer_combo.currentLayer() - if polygon_layer: - self.attributes[f"{self.widget_key}_layer_name"] = polygon_layer.name() - self.attributes[f"{self.widget_key}_layer_source"] = polygon_layer.source() - self.attributes[f"{self.widget_key}_layer_provider_type"] = ( - polygon_layer.providerType() - ) - self.attributes[f"{self.widget_key}_layer_crs"] = ( - polygon_layer.crs().authid() - ) - self.attributes[f"{self.widget_key}_layer_wkb_type"] = ( - polygon_layer.wkbType() - ) - self.attributes[f"{self.widget_key}_layer_id"] = polygon_layer.id() - self.attributes[f"{self.widget_key}_shapefile"] = ( - self.polygon_shapefile_line_edit.text() - ) - - return self.attributes - - def set_internal_widgets_enabled(self, enabled: bool) -> None: - """ - Enables or disables the internal widgets (polygon layers) based on the state of the radio button. - - Args: - enabled (bool): Whether to enable or disable the internal widgets. - """ - try: - self.polygon_layer_combo.setEnabled(enabled) - self.polygon_shapefile_line_edit.setEnabled(enabled) - self.polygon_shapefile_button.setEnabled(enabled) - except Exception as e: - log_message( - f"Error in set_internal_widgets_enabled: {e}", - tag="Geest", - level=Qgis.Critical, - ) diff --git a/geest/gui/widgets/combined_widgets/polyline_widget.py b/geest/gui/widgets/combined_widgets/polyline_widget.py deleted file mode 100644 index 00d01a88..00000000 --- a/geest/gui/widgets/combined_widgets/polyline_widget.py +++ /dev/null @@ -1,163 +0,0 @@ -import os -from qgis.PyQt.QtWidgets import ( - QLabel, - QVBoxLayout, - QHBoxLayout, - QLineEdit, - QToolButton, - QFileDialog, -) -from qgis.gui import QgsMapLayerComboBox -from qgis.core import QgsMapLayerProxyModel, QgsProject, QgsVectorLayer -from qgis.PyQt.QtCore import QSettings -from geest.utilities import log_message -from .base_indicator_widget import BaseIndicatorWidget - - -class PolylineWidget(BaseIndicatorWidget): - """ - A widget for selecting a polyline (paths) layer with options for shapefile inputs. - - This widget provides one `QgsMapLayerComboBox` components for selecting polyline layers, - as well as `QLineEdit` and `QToolButton` components to allow the user to specify shapefile paths for - each layer. The user can choose layers either from the QGIS project or provide external shapefiles. - - Attributes: - widget_key (str): The key identifier for this widget. - polyline_layer_combo (QgsMapLayerComboBox): A combo box for selecting the polyline layer. - polyline_shapefile_line_edit (QLineEdit): Line edit for entering/selecting a polyline layer shapefile. - """ - - def add_internal_widgets(self) -> None: - """ - Adds the internal widgets required for selecting polyline layers and their corresponding shapefiles. - This method is called during the widget initialization and sets up the layout for the UI components. - """ - try: - self.main_layout = QVBoxLayout() - self.widget_key = "polyline_per_cell" - - # Polyline Layer Section - self._add_polyline_layer_widgets() - - # Add the main layout to the widget's layout - self.layout.addLayout(self.main_layout) - - # Connect signals to update the data when user changes selections - self.polyline_layer_combo.currentIndexChanged.connect(self.update_data) - self.polyline_shapefile_line_edit.textChanged.connect(self.update_data) - - except Exception as e: - log_message(f"Error in add_internal_widgets: {e}", level=Qgis.Critical) - import traceback - - log_message(traceback.format_exc(), level=Qgis.Critical) - - def _add_polyline_layer_widgets(self) -> None: - """ - Adds the widgets for selecting the polyline layer, including a `QgsMapLayerComboBox` and shapefile input. - """ - self.polyline_layer_label = QLabel( - "Polyline Layer - shapefile will have preference" - ) - self.main_layout.addWidget(self.polyline_layer_label) - - # Polyline Layer ComboBox (Filtered to line layers) - self.polyline_layer_combo = QgsMapLayerComboBox() - self.polyline_layer_combo.setFilters(QgsMapLayerProxyModel.LineLayer) - self.main_layout.addWidget(self.polyline_layer_combo) - - # Restore previously selected polyline layer - polyline_layer_id = self.attributes.get( - f"{self.widget_key}_polyline_layer_id", None - ) - if polyline_layer_id: - polyline_layer = QgsProject.instance().mapLayer(polyline_layer_id) - if polyline_layer: - self.polyline_layer_combo.setLayer(polyline_layer) - - # _shapefile Input for Polyline Layer - self.polyline_shapefile_layout = QHBoxLayout() - self.polyline_shapefile_line_edit = QLineEdit() - self.polyline_shapefile_button = QToolButton() - self.polyline_shapefile_button.setText("...") - self.polyline_shapefile_button.clicked.connect(self.select_polyline_shapefile) - if self.attributes.get(f"{self.widget_key}_polyline_shapefile", False): - self.polyline_shapefile_line_edit.setText( - self.attributes[f"{self.widget_key}_polyline_shapefile"] - ) - self.polyline_shapefile_layout.addWidget(self.polyline_shapefile_line_edit) - self.polyline_shapefile_layout.addWidget(self.polyline_shapefile_button) - self.main_layout.addLayout(self.polyline_shapefile_layout) - - def select_polyline_shapefile(self) -> None: - """ - Opens a file dialog to select a shapefile for the polyline (paths) layer and updates the QLineEdit with the file path. - """ - try: - settings = QSettings() - last_dir = settings.value("Geest/lastShapefileDir", "") - - file_path, _ = QFileDialog.getOpenFileName( - self, "Select Polyline Shapefile", last_dir, "Shapefiles (*.shp)" - ) - if file_path: - self.polyline_shapefile_line_edit.setText(file_path) - settings.setValue("Geest/lastShapefileDir", os.path.dirname(file_path)) - - except Exception as e: - log_message( - f"Error selecting polyline shapefile: {e}", - tag="Geest", - level=Qgis.Critical, - ) - - def get_data(self) -> dict: - """ - Retrieves and returns the current state of the widget, including selected polyline layers or shapefiles. - - Returns: - dict: A dictionary containing the current attributes of the polyline layers and/or shapefiles. - """ - if not self.isChecked(): - return None - - # Collect data for the polyline layer - polyline_layer = self.polyline_layer_combo.currentLayer() - if polyline_layer: - self.attributes[f"{self.widget_key}_layer_name"] = polyline_layer.name() - self.attributes[f"{self.widget_key}_layer_source"] = polyline_layer.source() - self.attributes[f"{self.widget_key}_layer_provider_type"] = ( - polyline_layer.providerType() - ) - self.attributes[f"{self.widget_key}_layer_crs"] = ( - polyline_layer.crs().authid() - ) - self.attributes[f"{self.widget_key}_layer_wkb_type"] = ( - polyline_layer.wkbType() - ) - self.attributes[f"{self.widget_key}_layer_id"] = polyline_layer.id() - - self.attributes[f"{self.widget_key}_shapefile"] = ( - self.polyline_shapefile_line_edit.text() - ) - - return self.attributes - - def set_internal_widgets_enabled(self, enabled: bool) -> None: - """ - Enables or disables the internal widgets (polyline layers) based on the state of the radio button. - - Args: - enabled (bool): Whether to enable or disable the internal widgets. - """ - try: - self.polyline_layer_combo.setEnabled(enabled) - self.polyline_shapefile_line_edit.setEnabled(enabled) - self.polyline_shapefile_button.setEnabled(enabled) - except Exception as e: - log_message( - f"Error in set_internal_widgets_enabled: {e}", - tag="Geest", - level=Qgis.Critical, - ) diff --git a/geest/gui/widgets/combined_widgets/raster_reclassification_widget.py b/geest/gui/widgets/combined_widgets/raster_reclassification_widget.py deleted file mode 100644 index f4cf98d7..00000000 --- a/geest/gui/widgets/combined_widgets/raster_reclassification_widget.py +++ /dev/null @@ -1,153 +0,0 @@ -import os -from qgis.PyQt.QtWidgets import ( - QLabel, - QVBoxLayout, - QHBoxLayout, - QLineEdit, - QToolButton, - QFileDialog, -) -from qgis.gui import QgsMapLayerComboBox -from qgis.core import ( - QgsMapLayerProxyModel, - QgsProject, - Qgis, -) -from qgis.PyQt.QtCore import QSettings -from .base_indicator_widget import BaseIndicatorWidget -from geest.utilities import log_message - - -class RasterReclassificationWidget(BaseIndicatorWidget): - """ - - A widget for selecting a raster (area) layer with options for inputs. - - This widget provides one `QgsMapLayerComboBox` components for selecting raster layers, - as well as `QLineEdit` and `QToolButton` components to allow the user to specify paths for - each layer. The user can choose layers either from the QGIS project or provide externals. - - Attributes: - widget_key (str): The key identifier for this widget. - raster_layer_combo (QgsMapLayerComboBox): A combo box for selecting the raster layer. - raster_line_edit (QLineEdit): Line edit for entering/selecting a raster layer. - """ - - def add_internal_widgets(self) -> None: - """ - Adds the internal widgets required for selecting raster layers and their correspondings. - This method is called during the widget initialization and sets up the layout for the UI components. - """ - try: - self.main_layout = QVBoxLayout() - self.widget_key = "environmental_hazards" - self.settings = QSettings() - - # Raster Layer Section - self._add_raster_layer_widgets() - - # Add the main layout to the widget's layout - self.layout.addLayout(self.main_layout) - - # Connect signals to update the data when user changes selections - self.raster_layer_combo.currentIndexChanged.connect(self.update_data) - self.raster_line_edit.textChanged.connect(self.update_data) - - except Exception as e: - log_message(f"Error in add_internal_widgets: {e}", level=Qgis.Critical) - import traceback - - log_message(traceback.format_exc(), level=Qgis.Critical) - - def _add_raster_layer_widgets(self) -> None: - """ - Adds the widgets for selecting the raster layer, including a `QgsMapLayerComboBox` and input. - """ - self.raster_layer_label = QLabel("Raster Layer - will have preference") - self.main_layout.addWidget(self.raster_layer_label) - # Raster Layer ComboBox (Filtered to raster layers) - self.raster_layer_combo = QgsMapLayerComboBox() - self.raster_layer_combo.setFilters(QgsMapLayerProxyModel.RasterLayer) - self.main_layout.addWidget(self.raster_layer_combo) - - # Restore previously selected raster layer - raster_layer_id = self.attributes.get(f"{self.widget_key}_layer_id", None) - if raster_layer_id: - raster_layer = QgsProject.instance().mapLayer(raster_layer_id) - if raster_layer: - self.raster_layer_combo.setLayer(raster_layer) - - # Input for Raster Layer - self.raster_layout = QHBoxLayout() - self.raster_line_edit = QLineEdit() - self.raster_button = QToolButton() - self.raster_button.setText("...") - self.raster_button.clicked.connect(self.select_raster) - if self.attributes.get(f"{self.widget_key}_raster", False): - self.raster_line_edit.setText(self.attributes[f"{self.widget_key}_raster"]) - self.raster_layout.addWidget(self.raster_line_edit) - self.raster_layout.addWidget(self.raster_button) - self.main_layout.addLayout(self.raster_layout) - - def select_raster(self) -> None: - """ - Opens a file dialog to select a for the raster (paths) layer and updates the QLineEdit with the file path. - """ - try: - last_dir = self.settings.value("Geest/lastRasterDir", "") - - file_path, _ = QFileDialog.getOpenFileName( - self, "Select Raster Layer", last_dir, "Rasters (*.vrt *.tif *.asc)" - ) - if file_path: - self.raster_line_edit.setText(file_path) - self.settings.setValue( - "Geest/lastRasterDir", os.path.dirname(file_path) - ) - - except Exception as e: - log_message(f"Error selecting raster: {e}", level=Qgis.Critical) - - def get_data(self) -> dict: - """ - Retrieves and returns the current state of the widget, including selected raster layers ors. - - Returns: - dict: A dictionary containing the current attributes of the raster layers and/ors. - """ - if not self.isChecked(): - return None - - # Collect data for the raster layer - raster_layer = self.raster_layer_combo.currentLayer() - if raster_layer: - self.attributes[f"{self.widget_key}_layer_name"] = raster_layer.name() - self.attributes[f"{self.widget_key}_layer_source"] = raster_layer.source() - self.attributes[f"{self.widget_key}_layer_provider_type"] = ( - raster_layer.providerType() - ) - self.attributes[f"{self.widget_key}_layer_crs"] = ( - raster_layer.crs().authid() - ) - self.attributes[f"{self.widget_key}_layer_id"] = raster_layer.id() - self.attributes[f"{self.widget_key}_raster"] = self.raster_line_edit.text() - - return self.attributes - - def set_internal_widgets_enabled(self, enabled: bool) -> None: - """ - Enables or disables the internal widgets (raster layers) based on the state of the radio button. - - Args: - enabled (bool): Whether to enable or disable the internal widgets. - """ - try: - self.raster_layer_combo.setEnabled(enabled) - self.raster_line_edit.setEnabled(enabled) - self.raster_button.setEnabled(enabled) - except Exception as e: - log_message( - f"Error in set_internal_widgets_enabled: {e}", - tag="Geest", - level=Qgis.Critical, - ) diff --git a/geest/gui/widgets/combined_widgets/safety_polygon_widget.py b/geest/gui/widgets/combined_widgets/safety_polygon_widget.py deleted file mode 100644 index 1bce95f6..00000000 --- a/geest/gui/widgets/combined_widgets/safety_polygon_widget.py +++ /dev/null @@ -1,386 +0,0 @@ -import os -from qgis.PyQt.QtWidgets import ( - QFileDialog, - QHBoxLayout, - QLabel, - QLineEdit, - QSizePolicy, - QSpinBox, - QTableWidget, - QTableWidgetItem, - QToolButton, - QVBoxLayout, -) -from qgis.gui import QgsMapLayerComboBox, QgsFieldComboBox -from qgis.core import ( - QgsMapLayerProxyModel, - QgsProject, - QgsVectorLayer, - QgsFieldProxyModel, - Qgis, -) -from qgis.PyQt.QtCore import QSettings -from .base_indicator_widget import BaseIndicatorWidget -from geest.utilities import log_message - - -class SafetyPolygonWidget(BaseIndicatorWidget): - """ - - A widget for selecting a polygon (area) layer with options for shapefile inputs. - - This widget provides one `QgsMapLayerComboBox` components for selecting polygon layers, - as well as `QLineEdit` and `QToolButton` components to allow the user to specify shapefile paths for - each layer. The user can choose layers either from the QGIS project or provide external shapefiles. - - Attributes: - widget_key (str): The key identifier for this widget. - polygon_layer_combo (QgsMapLayerComboBox): A combo box for selecting the polygon layer. - polygon_shapefile_line_edit (QLineEdit): Line edit for entering/selecting a polygon layer shapefile. - """ - - def add_internal_widgets(self) -> None: - """ - Adds the internal widgets required for selecting polygon layers and their corresponding shapefiles. - This method is called during the widget initialization and sets up the layout for the UI components. - """ - try: - self.main_layout = QVBoxLayout() - self.widget_key = "classify_safety_polygon_into_classes" - self.settings = QSettings() - - # Polygon Layer Section - self._add_polygon_layer_widgets() - - # Add Field Selection Dropdown - self._add_field_selection_widgets() - - # Add the main layout to the widget's layout - self.layout.addLayout(self.main_layout) - - self.table_widget = QTableWidget() - self.table_widget.setSizePolicy( - QSizePolicy.Expanding, QSizePolicy.Expanding - ) - # Stop the label being editable - self.table_widget.setEditTriggers(QTableWidget.NoEditTriggers) - self.layout.addWidget(self.table_widget) - self.table_widget.setColumnCount(2) - self.table_widget.setHorizontalHeaderLabels(["Name", "Value 0-100"]) - self.table_widget.setColumnWidth(1, 80) - self.table_widget.horizontalHeader().setStretchLastSection(False) - self.table_widget.horizontalHeader().setSectionResizeMode( - 0, self.table_widget.horizontalHeader().Stretch - ) - - safety_classes = self.attributes.get( - f"classify_safety_polygon_into_classes_unique_values", {} - ) - if not isinstance(safety_classes, dict): - safety_classes = {} - # remove any item from the safety_classes where the key is not a string - safety_classes = { - k: v for k, v in safety_classes.items() if isinstance(k, str) - } - self.table_widget.setRowCount(len(safety_classes)) - - def validate_value(value): - return 0 <= value <= 100 - - try: - log_message(f"Classes: {safety_classes}") - except Exception as e: - log_message( - f"Error in logging safety classes: {e}", level=Qgis.Critical - ) - pass - # iterate over the dict and populate the table - for row, (class_name, value) in enumerate(safety_classes.items()): - if row >= self.table_widget.rowCount(): - continue - - if not isinstance(class_name, str): - continue - - if not isinstance(value, (int, float)) or not 0 <= value <= 100: - value = 0 - - name_item = QTableWidgetItem(class_name) - value_item = QSpinBox() - self.table_widget.setItem(row, 0, name_item) - value_item.setRange(0, 100) # Set spinner range - value_item.setValue(value) # Default value - self.table_widget.setCellWidget(row, 1, value_item) - - def on_value_changed(value): - # Color handling for current cell - if value is None or not (0 <= value <= 100): - value_item.setStyleSheet("color: red;") - value_item.setValue(0) - else: - value_item.setStyleSheet("color: black;") - self.update_cell_colors() - self.update_data() - - value_item.valueChanged.connect(on_value_changed) - - # Call update_cell_colors after all rows are created - self.update_cell_colors() - - value_item.valueChanged.connect(on_value_changed) - self.layout.addWidget(self.table_widget) - - # Connect signals to update the data when user changes selections - self.polygon_layer_combo.currentIndexChanged.connect(self.update_data) - self.polygon_shapefile_line_edit.textChanged.connect(self.update_data) - # Connect signals to update the fields when user changes selections - self.polygon_layer_combo.layerChanged.connect(self.update_field_combo) - self.polygon_shapefile_line_edit.textChanged.connect( - self.update_field_combo - ) - - # Connect the field combo box to update the attributes when a field is selected - self.field_selection_combo.currentIndexChanged.connect( - self.update_selected_field - ) - - self.update_field_combo() # Populate fields for the initially selected layer - - except Exception as e: - log_message(f"Error in add_internal_widgets: {e}", level=Qgis.Critical) - import traceback - - log_message(traceback.format_exc(), level=Qgis.Critical) - - def update_cell_colors(self): - # Check if all values are zero - all_zeros = True - for r in range(self.table_widget.rowCount()): - spin_widget = self.table_widget.cellWidget(r, 1) - if spin_widget and spin_widget.value() != 0: - all_zeros = False - break - - # Color all cells based on all-zeros check - for r in range(self.table_widget.rowCount()): - spin_widget = self.table_widget.cellWidget(r, 1) - if spin_widget: - spin_widget.setStyleSheet( - "color: red;" if all_zeros else "color: black;" - ) - - def table_to_dict(self): - updated_attributes = {} - for row in range(self.table_widget.rowCount()): - spin_widget = self.table_widget.cellWidget(row, 1) - value = None - if spin_widget and spin_widget.value(): - value = spin_widget.value() - name_item = self.table_widget.item(row, 0) - class_name = str(name_item.text()) - updated_attributes[class_name] = value - - log_message(f"Updated attributes {updated_attributes}") - return updated_attributes - - def _add_polygon_layer_widgets(self) -> None: - """ - Adds the widgets for selecting the polygon layer, including a `QgsMapLayerComboBox` and shapefile input. - """ - self.polygon_layer_label = QLabel( - "Polygon Layer - shapefile will have preference" - ) - self.main_layout.addWidget(self.polygon_layer_label) - # Polygon Layer ComboBox (Filtered to polygon layers) - self.polygon_layer_combo = QgsMapLayerComboBox() - self.polygon_layer_combo.setFilters(QgsMapLayerProxyModel.PolygonLayer) - self.main_layout.addWidget(self.polygon_layer_combo) - - # Restore previously selected polygon layer - polygon_layer_id = self.attributes.get(f"{self.widget_key}_layer_id", None) - if polygon_layer_id: - polygon_layer = QgsProject.instance().mapLayer(polygon_layer_id) - if polygon_layer: - self.polygon_layer_combo.setLayer(polygon_layer) - - # _shapefile Input for Polygon Layer - self.polygon_shapefile_layout = QHBoxLayout() - self.polygon_shapefile_line_edit = QLineEdit() - self.polygon_shapefile_button = QToolButton() - self.polygon_shapefile_button.setText("...") - self.polygon_shapefile_button.clicked.connect(self.select_polygon_shapefile) - if self.attributes.get(f"{self.widget_key}_shapefile", False): - self.polygon_shapefile_line_edit.setText( - self.attributes[f"{self.widget_key}_shapefile"] - ) - self.polygon_shapefile_layout.addWidget(self.polygon_shapefile_line_edit) - self.polygon_shapefile_layout.addWidget(self.polygon_shapefile_button) - self.main_layout.addLayout(self.polygon_shapefile_layout) - - def _add_field_selection_widgets(self) -> None: - """ - Adds a dropdown to select a specific field from the selected shapefile. - """ - self.field_selection_label = QLabel("Select Field") - self.main_layout.addWidget(self.field_selection_label) - - self.field_selection_combo = QgsFieldComboBox() - self.field_selection_combo.setFilters(QgsFieldProxyModel.String) - self.field_selection_combo.setEnabled( - False - ) # Disable initially until a layer is selected - self.main_layout.addWidget(self.field_selection_combo) - - def select_polygon_shapefile(self) -> None: - """ - Opens a file dialog to select a shapefile for the polygon (paths) layer and updates the QLineEdit with the file path. - """ - try: - last_dir = self.settings.value("Geest/lastShapefileDir", "") - - file_path, _ = QFileDialog.getOpenFileName( - self, "Select Polygon Shapefile", last_dir, "Shapefiles (*.shp)" - ) - if file_path: - self.polygon_shapefile_line_edit.setText(file_path) - self.settings.setValue( - "Geest/lastShapefileDir", os.path.dirname(file_path) - ) - - # Load the shapefile as a QgsVectorLayer and populate the fields dropdown - self._populate_field_combo(file_path) - - except Exception as e: - log_message( - f"Error selecting polygon shapefile: {e}", - tag="Geest", - level=Qgis.Critical, - ) - - def _populate_field_combo(self, shapefile_path: str) -> None: - """ - Loads the shapefile and populates the field selection combo box with the field names. - - Args: - shapefile_path (str): The path to the shapefile. - """ - try: - # Store the currently selected field - previous_field = self.settings.value( - f"{self.widget_key}_selected_field", None - ) - - vector_layer = QgsVectorLayer(shapefile_path, "polygon_layer", "ogr") - if not vector_layer.isValid(): - log_message(f"Failed to load shapefile: {shapefile_path}", "Geest") - return - - # Set the vector layer on the field selection combo box, which will automatically populate it - QgsProject.instance().addMapLayer(vector_layer, False) - self.field_selection_combo.setLayer(vector_layer) - self.field_selection_combo.setEnabled(True) # Enable once layer is valid - - # Reapply the previously selected field if it exists - if ( - previous_field - and self.field_selection_combo.findText(previous_field) != -1 - ): - self.field_selection_combo.setCurrentText(previous_field) - - except Exception as e: - log_message(f"Error populating field combo: {e}", level=Qgis.Critical) - - def update_field_combo(self) -> None: - """ - Updates the field combo box when the polygon layer or shapefile is changed. - """ - # Store the currently selected field - previous_field = self.settings.value(f"{self.widget_key}_selected_field", None) - - if self.polygon_layer_combo.currentLayer(): - # Populate field combo from the selected polygon layer - self.field_selection_combo.setLayer(self.polygon_layer_combo.currentLayer()) - self.field_selection_combo.setEnabled(True) - elif self.polygon_shapefile_line_edit.text(): - # If shapefile is provided, populate the field combo - self._populate_field_combo(self.polygon_shapefile_line_edit.text()) - - # After the field combo is repopulated, re-select the previously selected field if it exists - if previous_field and self.field_selection_combo.findText(previous_field) != -1: - self.field_selection_combo.setCurrentText(previous_field) - - def get_data(self) -> dict: - """ - Retrieves and returns the current state of the widget, including selected polygon layers or shapefiles. - - Returns: - dict: A dictionary containing the current attributes of the polygon layers and/or shapefiles. - """ - if not self.isChecked(): - return None - updated_attributes = self.table_to_dict() - - self.attributes["classify_safety_polygon_into_classes_unique_values"] = ( - updated_attributes - ) - # Collect data for the polygon layer - polygon_layer = self.polygon_layer_combo.currentLayer() - if polygon_layer: - self.attributes[f"{self.widget_key}_layer_name"] = polygon_layer.name() - self.attributes[f"{self.widget_key}_layer_source"] = polygon_layer.source() - self.attributes[f"{self.widget_key}_layer_provider_type"] = ( - polygon_layer.providerType() - ) - self.attributes[f"{self.widget_key}_layer_crs"] = ( - polygon_layer.crs().authid() - ) - self.attributes[f"{self.widget_key}_layer_wkb_type"] = ( - polygon_layer.wkbType() - ) - self.attributes[f"{self.widget_key}_layer_id"] = polygon_layer.id() - self.attributes[f"{self.widget_key}_shapefile"] = ( - self.polygon_shapefile_line_edit.text() - ) - # Get the selected field if field combo box is enabled - selected_field = ( - self.field_selection_combo.currentText() - if self.field_selection_combo.isEnabled() - else None - ) - self.attributes[f"{self.widget_key}_selected_field"] = selected_field - - return self.attributes - - def update_selected_field(self) -> None: - """ - Updates the selected field in the attributes dictionary when the field selection changes. - """ - if self.field_selection_combo.isEnabled(): - selected_field = self.field_selection_combo.currentText() - self.attributes[f"{self.widget_key}_selected_field"] = selected_field - - # Store the selected field in QSettings - self.settings.setValue(f"{self.widget_key}_selected_field", selected_field) - else: - self.attributes[f"{self.widget_key}_selected_field"] = None - - def set_internal_widgets_enabled(self, enabled: bool) -> None: - """ - Enables or disables the internal widgets (polygon layers) based on the state of the radio button. - - Args: - enabled (bool): Whether to enable or disable the internal widgets. - """ - try: - self.polygon_layer_combo.setEnabled(enabled) - self.polygon_shapefile_line_edit.setEnabled(enabled) - self.polygon_shapefile_button.setEnabled(enabled) - self.field_selection_combo.setEnabled( - enabled and self.field_selection_combo.count() > 0 - ) - except Exception as e: - log_message( - f"Error in set_internal_widgets_enabled: {e}", - tag="Geest", - level=Qgis.Critical, - ) diff --git a/geest/gui/widgets/combined_widgets/safety_raster_widget.py b/geest/gui/widgets/combined_widgets/safety_raster_widget.py deleted file mode 100644 index 3ebfd414..00000000 --- a/geest/gui/widgets/combined_widgets/safety_raster_widget.py +++ /dev/null @@ -1,157 +0,0 @@ -import os -from qgis.PyQt.QtWidgets import ( - QLabel, - QVBoxLayout, - QHBoxLayout, - QLineEdit, - QToolButton, - QFileDialog, -) -from qgis.gui import QgsMapLayerComboBox -from qgis.core import ( - QgsMapLayerProxyModel, - QgsProject, - Qgis, -) -from qgis.PyQt.QtCore import QSettings -from geest.utilities import log_message -from .base_indicator_widget import BaseIndicatorWidget - - -class SafetyRasterWidget(BaseIndicatorWidget): - """ - - A widget for selecting a raster (area) layer with options for inputs. - - This widget provides one `QgsMapLayerComboBox` components for selecting raster layers, - as well as `QLineEdit` and `QToolButton` components to allow the user to specify paths for - each layer. The user can choose layers either from the QGIS project or provide externals. - - Attributes: - widget_key (str): The key identifier for this widget. - raster_layer_combo (QgsMapLayerComboBox): A combo box for selecting the raster layer. - raster_line_edit (QLineEdit): Line edit for entering/selecting a raster layer. - """ - - def add_internal_widgets(self) -> None: - """ - Adds the internal widgets required for selecting raster layers and their correspondings. - This method is called during the widget initialization and sets up the layout for the UI components. - """ - try: - self.main_layout = QVBoxLayout() - self.widget_key = "nighttime_lights" - self.settings = QSettings() - - # Raster Layer Section - self._add_raster_layer_widgets() - - # Add the main layout to the widget's layout - self.layout.addLayout(self.main_layout) - - # Connect signals to update the data when user changes selections - self.raster_layer_combo.currentIndexChanged.connect(self.update_data) - self.raster_line_edit.textChanged.connect(self.update_data) - - except Exception as e: - log_message(f"Error in add_internal_widgets: {e}", level=Qgis.Critical) - import traceback - - log_message(traceback.format_exc(), level=Qgis.Critical) - - def _add_raster_layer_widgets(self) -> None: - """ - Adds the widgets for selecting the raster layer, including a `QgsMapLayerComboBox` and input. - """ - self.raster_layer_label = QLabel("Raster Layer - will have preference") - self.main_layout.addWidget(self.raster_layer_label) - # Raster Layer ComboBox (Filtered to raster layers) - self.raster_layer_combo = QgsMapLayerComboBox() - self.raster_layer_combo.setFilters(QgsMapLayerProxyModel.RasterLayer) - self.main_layout.addWidget(self.raster_layer_combo) - - # Restore previously selected raster layer - raster_layer_id = self.attributes.get( - f"{self.widget_key}_raster_layer_id", None - ) - if raster_layer_id: - raster_layer = QgsProject.instance().mapLayer(raster_layer_id) - if raster_layer: - self.raster_layer_combo.setLayer(raster_layer) - - # Input for Raster Layer - self.raster_layout = QHBoxLayout() - self.raster_line_edit = QLineEdit() - self.raster_button = QToolButton() - self.raster_button.setText("...") - self.raster_button.clicked.connect(self.select_raster) - if self.attributes.get(f"{self.widget_key}_raster_layer", False): - self.raster_line_edit.setText( - self.attributes[f"{self.widget_key}_raster_layer"] - ) - self.raster_layout.addWidget(self.raster_line_edit) - self.raster_layout.addWidget(self.raster_button) - self.main_layout.addLayout(self.raster_layout) - - def select_raster(self) -> None: - """ - Opens a file dialog to select a for the raster (paths) layer and updates the QLineEdit with the file path. - """ - try: - last_dir = self.settings.value("Geest/lastRasterDir", "") - - file_path, _ = QFileDialog.getOpenFileName( - self, "Select Raster Layer", last_dir, "Rasters (*.vrt *.tif *.asc)" - ) - if file_path: - self.raster_line_edit.setText(file_path) - self.settings.setValue( - "Geest/lastRasterDir", os.path.dirname(file_path) - ) - - except Exception as e: - log_message(f"Error selecting raster: {e}", level=Qgis.Critical) - - def get_data(self) -> dict: - """ - Retrieves and returns the current state of the widget, including selected raster layers ors. - - Returns: - dict: A dictionary containing the current attributes of the raster layers and/ors. - """ - if not self.isChecked(): - return None - - # Collect data for the raster layer - raster_layer = self.raster_layer_combo.currentLayer() - if raster_layer: - self.attributes[f"{self.widget_key}_layer_name"] = raster_layer.name() - self.attributes[f"{self.widget_key}_layer_source"] = raster_layer.source() - self.attributes[f"{self.widget_key}_layer_provider_type"] = ( - raster_layer.providerType() - ) - self.attributes[f"{self.widget_key}_layer_crs"] = ( - raster_layer.crs().authid() - ) - self.attributes[f"{self.widget_key}_layer_id"] = raster_layer.id() - self.attributes[f"{self.widget_key}_raster"] = self.raster_line_edit.text() - - return self.attributes - - def set_internal_widgets_enabled(self, enabled: bool) -> None: - """ - Enables or disables the internal widgets (raster layers) based on the state of the radio button. - - Args: - enabled (bool): Whether to enable or disable the internal widgets. - """ - try: - self.raster_layer_combo.setEnabled(enabled) - self.raster_line_edit.setEnabled(enabled) - self.raster_button.setEnabled(enabled) - except Exception as e: - log_message( - f"Error in set_internal_widgets_enabled: {e}", - tag="Geest", - level=Qgis.Critical, - ) diff --git a/geest/gui/widgets/combined_widgets/single_buffer_distance_widget.py b/geest/gui/widgets/combined_widgets/single_buffer_distance_widget.py deleted file mode 100644 index 3aa621cd..00000000 --- a/geest/gui/widgets/combined_widgets/single_buffer_distance_widget.py +++ /dev/null @@ -1,174 +0,0 @@ -import os -from qgis.PyQt.QtWidgets import ( - QLabel, - QVBoxLayout, - QHBoxLayout, - QLineEdit, - QToolButton, - QFileDialog, - QSpinBox, -) -from qgis.gui import QgsMapLayerComboBox -from qgis.core import QgsMapLayerProxyModel, QgsProject, QgsVectorLayer -from qgis.PyQt.QtCore import QSettings -from geest.utilities import log_message -from .base_indicator_widget import BaseIndicatorWidget - - -class SingleBufferDistanceWidget(BaseIndicatorWidget): - """ - A specialized radio button with additional QSpinBox buffer distance. - """ - - def add_internal_widgets(self) -> None: - """ - Adds internal widgets specific to self.set_internal_widgets_visible(self.isChecked()) - in this case there are none. - """ - try: - self.main_layout = QVBoxLayout() - self.widget_key = "single_buffer_point" - - # Point Layer Combobox - Filtered to point layers - self.point_layer_label = QLabel( - "Point Layer - shapefile will have preference" - ) - self.main_layout.addWidget(self.point_layer_label) - - self.layer_combo = QgsMapLayerComboBox() - self.layer_combo.setFilters(QgsMapLayerProxyModel.PointLayer) - self.main_layout.addWidget(self.layer_combo) - - # Set the selected QgsVectorLayer in QgsMapLayerComboBox - layer_id = self.attributes.get(f"{self.widget_key}_layer_id", None) - if layer_id: - layer = QgsProject.instance().mapLayer(layer_id) - if layer: - self.layer_combo.setLayer(layer) - layer_id = self.attributes.get(f"{self.widget_key}_layer_id") - layer = QgsProject.instance().mapLayer(layer_id) - - if layer and isinstance(layer, QgsVectorLayer): - self.layer_combo.setLayer(layer) - - # Add shapefile selection (QLineEdit and QToolButton) - self.shapefile_layout = QHBoxLayout() - self.shapefile_line_edit = QLineEdit() - self.shapefile_button = QToolButton() - self.shapefile_button.setText("...") - self.shapefile_button.clicked.connect(self.select_shapefile) - if self.attributes.get(f"{self.widget_key}_layer_shapefile", False): - self.shapefile_line_edit.setText( - self.attributes[f"{self.widget_key}_layer_shapefile"] - ) - self.shapefile_layout.addWidget(self.shapefile_line_edit) - self.shapefile_layout.addWidget(self.shapefile_button) - self.main_layout.addLayout(self.shapefile_layout) - - # Travel Increments input - self.buffer_distance_layout = QHBoxLayout() - self.buffer_distance_label = QLabel("Buffer Distance (m):") - self.buffer_distance_input = QSpinBox() - self.buffer_distance_input.setRange(0, 100000) - self.buffer_distance_layout.addWidget(self.buffer_distance_label) - self.buffer_distance_layout.addWidget(self.buffer_distance_input) - default_distance = self.attributes.get("default_single_buffer_distance", 0) - buffer_distance = self.attributes.get( - f"{self.widget_key}_layer_distance", default_distance - ) - if buffer_distance == 0: - buffer_distance = default_distance - try: - self.buffer_distance_input.setValue(int(buffer_distance)) - except (ValueError, TypeError): - self.buffer_distance_input.setValue(int(default_distance)) - - # Add all layouts to the main layout - self.main_layout.addLayout(self.buffer_distance_layout) - self.layout.addLayout(self.main_layout) - - # Emit the data_changed signal when any widget is changed - self.layer_combo.currentIndexChanged.connect(self.update_data) - self.buffer_distance_input.valueChanged.connect(self.update_data) - - except Exception as e: - log_message(f"Error in add_internal_widgets: {e}", level=Qgis.Critical) - import traceback - - log_message(traceback.format_exc(), level=Qgis.Critical) - - def select_shapefile(self): - """ - Opens a file dialog to select a shapefile and stores the last directory in QSettings. - """ - try: - settings = QSettings() - last_dir = settings.value("Geest/lastShapefileDir", "") - - # Open file dialog to select a shapefile - file_path, _ = QFileDialog.getOpenFileName( - self, "Select Shapefile", last_dir, "Shapefiles (*.shp)" - ) - - if file_path: - # Update the line edit with the selected file path - self.shapefile_line_edit.setText(file_path) - - # Save the directory of the selected file to QSettings - settings.setValue("Geest/lastShapefileDir", os.path.dirname(file_path)) - - except Exception as e: - log_message(f"Error selecting shapefile: {e}", level=Qgis.Critical) - - def get_data(self) -> dict: - """ - Return the data as a dictionary, updating attributes with current value. - """ - if not self.isChecked(): - return None - - layer = self.layer_combo.currentLayer() - if not layer: - self.attributes[f"{self.widget_key}_layer"] = None - else: - self.attributes[f"{self.widget_key}_layer_name"] = layer.name() - self.attributes[f"{self.widget_key}_layer_source"] = layer.source() - self.attributes[f"{self.widget_key}_layer_provider_type"] = ( - layer.providerType() - ) - self.attributes[f"{self.widget_key}_layer_crs"] = ( - layer.crs().authid() - ) # Coordinate Reference System - self.attributes[f"{self.widget_key}_layer_wkb_type"] = ( - layer.wkbType() - ) # Geometry type (e.g., Point, Polygon) - self.attributes[f"{self.widget_key}_layer_id"] = ( - layer.id() - ) # Unique ID of the layer - - self.attributes[f"{self.widget_key}_layer_distance"] = ( - self.buffer_distance_input.value() - ) - self.attributes[f"{self.widget_key}_layer_shapefile"] = ( - self.shapefile_line_edit.text() - ) - - return self.attributes - - def set_internal_widgets_enabled(self, enabled: bool) -> None: - """ - Enables or disables the internal widgets based on the state of the radio button. - """ - try: - self.layer_combo.setEnabled(enabled) - self.buffer_distance_input.setEnabled(enabled) - self.buffer_distance_label.setEnabled(enabled) - self.point_layer_label.setEnabled(enabled) - self.buffer_distance_layout.setEnabled(enabled) - self.shapefile_line_edit.setEnabled(enabled) - self.shapefile_button.setEnabled(enabled) - except Exception as e: - log_message( - f"Error in set_internal_widgets_enabled: {e}", - tag="Geest", - level=Qgis.Critical, - ) diff --git a/geest/gui/widgets/combined_widgets/street_lights_widget.py b/geest/gui/widgets/combined_widgets/street_lights_widget.py deleted file mode 100644 index 4df93d55..00000000 --- a/geest/gui/widgets/combined_widgets/street_lights_widget.py +++ /dev/null @@ -1,154 +0,0 @@ -import os -from qgis.PyQt.QtWidgets import ( - QLabel, - QVBoxLayout, - QHBoxLayout, - QLineEdit, - QToolButton, - QFileDialog, -) -from qgis.gui import QgsMapLayerComboBox -from qgis.core import QgsMapLayerProxyModel, QgsProject -from qgis.PyQt.QtCore import QSettings -from geest.utilities import log_message -from .base_indicator_widget import BaseIndicatorWidget - - -class StreetLightsWidget(BaseIndicatorWidget): - """ - A widget for selecting a point layer with options for shapefile inputs. - - This widget provides a `QgsMapLayerComboBox` component for selecting the point layers, - as well as `QLineEdit` and `QToolButton` components to allow the user to specify shapefile paths for - the layer. The user can choose layers either from the QGIS project or provide external shapefiles. - - Attributes: - widget_key (str): The key identifier for this widget. - point_layer_combo (QgsMapLayerComboBox): A combo box for selecting the point layer. - point_shapefile_line_edit (QLineEdit): Line edit for entering/selecting a point layer shapefile. - """ - - def add_internal_widgets(self) -> None: - """ - Adds the internal widgets required for selecting point and polyline layers and their corresponding shapefiles. - This method is called during the widget initialization and sets up the layout for the UI components. - """ - try: - self.main_layout = QVBoxLayout() - self.widget_key = "street_lights" - - # Point Layer Section - self._add_point_layer_widgets() - - # Add the main layout to the widget's layout - self.layout.addLayout(self.main_layout) - - # Connect signals to update the data when user changes selections - self.point_layer_combo.currentIndexChanged.connect(self.update_data) - self.point_shapefile_line_edit.textChanged.connect(self.update_data) - - except Exception as e: - log_message(f"Error in add_internal_widgets: {e}", level=Qgis.Critical) - import traceback - - log_message(traceback.format_exc(), level=Qgis.Critical) - - def _add_point_layer_widgets(self) -> None: - """ - Adds the widgets for selecting the point layer, including a `QgsMapLayerComboBox` and shapefile input. - """ - self.point_layer_label = QLabel("Point Layer - shapefile will have preference") - self.main_layout.addWidget(self.point_layer_label) - - # Point Layer ComboBox (Filtered to point layers) - self.point_layer_combo = QgsMapLayerComboBox() - self.point_layer_combo.setFilters(QgsMapLayerProxyModel.PointLayer) - self.main_layout.addWidget(self.point_layer_combo) - - # Restore previously selected point layer - point_layer_id = self.attributes.get(f"{self.widget_key}_point_layer_id", None) - if point_layer_id: - point_layer = QgsProject.instance().mapLayer(point_layer_id) - if point_layer: - self.point_layer_combo.setLayer(point_layer) - - # _shapefile Input for Point Layer - self.point_shapefile_layout = QHBoxLayout() - self.point_shapefile_line_edit = QLineEdit() - self.point_shapefile_button = QToolButton() - self.point_shapefile_button.setText("...") - self.point_shapefile_button.clicked.connect(self.select_point_shapefile) - if self.attributes.get(f"{self.widget_key}_point_shapefile", False): - self.point_shapefile_line_edit.setText( - self.attributes[f"{self.widget_key}_point_shapefile"] - ) - self.point_shapefile_layout.addWidget(self.point_shapefile_line_edit) - self.point_shapefile_layout.addWidget(self.point_shapefile_button) - self.main_layout.addLayout(self.point_shapefile_layout) - - def select_point_shapefile(self) -> None: - """ - Opens a file dialog to select a shapefile for the point layer and updates the QLineEdit with the file path. - """ - try: - settings = QSettings() - last_dir = settings.value("Geest/lastShapefileDir", "") - - file_path, _ = QFileDialog.getOpenFileName( - self, "Select Point Shapefile", last_dir, "Shapefiles (*.shp)" - ) - if file_path: - self.point_shapefile_line_edit.setText(file_path) - settings.setValue("Geest/lastShapefileDir", os.path.dirname(file_path)) - - except Exception as e: - log_message( - f"Error selecting point shapefile: {e}", - tag="Geest", - level=Qgis.Critical, - ) - - def get_data(self) -> dict: - """ - Retrieves and returns the current state of the widget, including selected point and polyline layers or shapefiles. - - Returns: - dict: A dictionary containing the current attributes of the point and polyline layers and/or shapefiles. - """ - if not self.isChecked(): - return None - - # Collect data for the point layer - point_layer = self.point_layer_combo.currentLayer() - if point_layer: - self.attributes[f"{self.widget_key}_layer_name"] = point_layer.name() - self.attributes[f"{self.widget_key}_layer_source"] = point_layer.source() - self.attributes[f"{self.widget_key}_layer_provider_type"] = ( - point_layer.providerType() - ) - self.attributes[f"{self.widget_key}_layer_crs"] = point_layer.crs().authid() - self.attributes[f"{self.widget_key}_layer_wkb_type"] = point_layer.wkbType() - self.attributes[f"{self.widget_key}_layer_id"] = point_layer.id() - self.attributes[f"{self.widget_key}_shapefile"] = ( - self.point_shapefile_line_edit.text() - ) - - return self.attributes - - def set_internal_widgets_enabled(self, enabled: bool) -> None: - """ - Enables or disables the internal widgets (both point and polyline layers) based on the state of the radio button. - - Args: - enabled (bool): Whether to enable or disable the internal widgets. - """ - try: - self.point_layer_combo.setEnabled(enabled) - self.point_shapefile_line_edit.setEnabled(enabled) - self.point_shapefile_button.setEnabled(enabled) - except Exception as e: - log_message( - f"Error in set_internal_widgets_enabled: {e}", - tag="Geest", - level=Qgis.Critical, - )