From 2681f730b90006ad3ba49e5667f351919921506d Mon Sep 17 00:00:00 2001 From: hennie-k Date: Fri, 20 Sep 2024 11:25:36 +0200 Subject: [PATCH 01/19] Adds polygon_layer_with_field_selector type to widget factory --- geest/gui/widgets/geest_widget_factory.py | 37 +++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/geest/gui/widgets/geest_widget_factory.py b/geest/gui/widgets/geest_widget_factory.py index 85d93d7..83c11e8 100644 --- a/geest/gui/widgets/geest_widget_factory.py +++ b/geest/gui/widgets/geest_widget_factory.py @@ -14,7 +14,7 @@ ) from qgis.PyQt.QtCore import Qt from qgis.gui import QgsMapLayerComboBox, QgsFileWidget -from qgis.core import QgsMapLayer, QgsMapLayerProxyModel +from qgis.core import QgsMapLayer, QgsMapLayerProxyModel, QgsVectorLayer class GeestWidgetFactory: @@ -71,7 +71,7 @@ def create_widgets(layer_data: dict, parent=None): "Use Classify Poly into Classes": { "label": "Classify Polygons into Classes", "description": "Using this option, you can classify polygons into classes.", - "type": "layer_selector", + "type": "polygon_layer_with_field_selector", "layer_type": "polygon", "tooltip": "Select a polygon layer." }, @@ -271,6 +271,39 @@ def safe_int(value, default): widget.setToolTip(mapping.get("tooltip", "")) return widget + elif widget_type == "polygon_layer_with_field_selector": + container = QWidget() + layout = QVBoxLayout() + container.setLayout(layout) + + # Layer selector + layer_label = QLabel("Select Polygon Layer:") + layout.addWidget(layer_label) + layer_selector = QgsMapLayerComboBox() + layer_selector.setFilters(QgsMapLayerProxyModel.PolygonLayer) + layout.addWidget(layer_selector) + + # Field selector + field_label = QLabel("Select Field of Interest:") + layout.addWidget(field_label) + field_selector = QComboBox() + layout.addWidget(field_selector) + + # Update field selector when layer changes + def update_fields(): + field_selector.clear() + layer = layer_selector.currentLayer() + if isinstance(layer, QgsVectorLayer): + field_selector.addItems([field.name() for field in layer.fields()]) + + layer_selector.layerChanged.connect(update_fields) + + # Initial population of fields + update_fields() + + container.setToolTip(mapping.get("tooltip", "")) + return container + elif widget_type == "csv_to_point": container = QWidget() layout = QVBoxLayout() From f6e33c7817a6ddfe2d1aca012a352cded2a69b34 Mon Sep 17 00:00:00 2001 From: hennie-k Date: Fri, 20 Sep 2024 11:46:09 +0200 Subject: [PATCH 02/19] Adds polygon_layer_with_field_selector handling to geest_config_widget --- geest/gui/widgets/geest_config_widget.py | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/geest/gui/widgets/geest_config_widget.py b/geest/gui/widgets/geest_config_widget.py index a94ef66..737c66d 100644 --- a/geest/gui/widgets/geest_config_widget.py +++ b/geest/gui/widgets/geest_config_widget.py @@ -93,8 +93,30 @@ def setup_connections(self): widget.valueChanged.connect(lambda value, k=key: self.update_sub_widget_state(k, value)) elif isinstance(widget, QComboBox): widget.currentTextChanged.connect(lambda text, k=key: self.update_sub_widget_state(k, text)) + elif isinstance(widget, QWidget) and widget.findChild(QgsMapLayerComboBox) and widget.findChild(QComboBox): + layer_selector = widget.findChild(QgsMapLayerComboBox) + field_selector = widget.findChild(QComboBox) + layer_selector.layerChanged.connect(lambda layer, k=key: self.update_polygon_layer_and_field(k, layer, field_selector)) + field_selector.currentTextChanged.connect(lambda text, k=key, ls=layer_selector: self.update_polygon_layer_and_field(k, ls.currentLayer(), field_selector)) print(f"Set up widget connection for {key}: {type(widget).__name__}") + def update_polygon_layer_and_field(self, key, layer, field_selector): + if layer: + provider_key = layer.providerType() + uri = layer.dataProvider().dataSourceUri() + decoded = QgsProviderRegistry.instance().decodeUri(provider_key, uri) + path = decoded.get('path') or decoded.get('url') or decoded.get('layerName') + field = field_selector.currentText() + if path: + value = f"{path};{field}" if field else path + self.update_sub_widget_state(key, value) + else: + print(f"Unable to determine path for layer {layer.name()} with provider {provider_key}") + self.update_sub_widget_state(key, f"{uri};{field}" if field else uri) + else: + print(f"No layer selected for {key}") + self.update_sub_widget_state(key, None) + def update_layer_path(self, key, layer): print(f"update_layer_path called for {key}") # Debug print if layer: @@ -158,6 +180,15 @@ def update_widgets_from_config(self): widget.setValue(float(value) if isinstance(widget, QDoubleSpinBox) else int(value)) elif isinstance(widget, (QComboBox, QgsMapLayerComboBox)): widget.setCurrentText(str(value)) + elif isinstance(widget, QWidget) and widget.findChild(QgsMapLayerComboBox) and widget.findChild(QComboBox): + # Handle polygon_layer_with_field_selector widget + if value and ';' in value: + layer_path, field = value.split(';') + field_selector = widget.findChild(QComboBox) + # Set layer --- maybe necessary? + # layer_selector = widget.findChild(QgsMapLayerComboBox) + # layer_selector.setLayer(layer_path) + field_selector.setCurrentText(field) def dump_widget_hierarchy(self, widget, level=0): output = [] From ccad89aa0bce9dc57ced321f2b6e4cb7379d9439 Mon Sep 17 00:00:00 2001 From: hennie-k Date: Fri, 20 Sep 2024 12:18:36 +0200 Subject: [PATCH 03/19] Fixes polygon_layer_with_field_selector handling in geest_config_widget --- geest/gui/widgets/geest_config_widget.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/geest/gui/widgets/geest_config_widget.py b/geest/gui/widgets/geest_config_widget.py index 737c66d..3aac2df 100644 --- a/geest/gui/widgets/geest_config_widget.py +++ b/geest/gui/widgets/geest_config_widget.py @@ -109,13 +109,15 @@ def update_polygon_layer_and_field(self, key, layer, field_selector): field = field_selector.currentText() if path: value = f"{path};{field}" if field else path - self.update_sub_widget_state(key, value) + self.modified_config[key] = value else: print(f"Unable to determine path for layer {layer.name()} with provider {provider_key}") - self.update_sub_widget_state(key, f"{uri};{field}" if field else uri) + self.modified_config[key] = f"{uri};{field}" if field else uri else: print(f"No layer selected for {key}") - self.update_sub_widget_state(key, None) + self.modified_config[key] = "" + + self.stateChanged.emit(self.get_state()) def update_layer_path(self, key, layer): print(f"update_layer_path called for {key}") # Debug print @@ -144,7 +146,18 @@ def handle_option_change(self, option, checked): widget.setEnabled(key == option) for key in self.widgets.keys(): - self.modified_config[key] = 1 if key == option else 0 + if key == option: + if isinstance(self.widgets[key]["widget"], QWidget) and \ + self.widgets[key]["widget"].findChild(QgsMapLayerComboBox) and \ + self.widgets[key]["widget"].findChild(QComboBox): + # handling polygon_layer_with_field_selector + layer_selector = self.widgets[key]["widget"].findChild(QgsMapLayerComboBox) + field_selector = self.widgets[key]["widget"].findChild(QComboBox) + self.update_polygon_layer_and_field(key, layer_selector.currentLayer(), field_selector) + else: + self.modified_config[key] = 1 + else: + self.modified_config[key] = 0 self.stateChanged.emit(self.get_state()) From c77bb1158dbc1969cc30bb0bf811ccddc6bb2d68 Mon Sep 17 00:00:00 2001 From: hennie-k Date: Fri, 20 Sep 2024 12:29:26 +0200 Subject: [PATCH 04/19] Fixes polygon_layer_with_field_selector handling in geest_config_widget even more --- geest/gui/widgets/geest_config_widget.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/geest/gui/widgets/geest_config_widget.py b/geest/gui/widgets/geest_config_widget.py index 3aac2df..a779cb8 100644 --- a/geest/gui/widgets/geest_config_widget.py +++ b/geest/gui/widgets/geest_config_widget.py @@ -144,22 +144,24 @@ def handle_option_change(self, option, checked): widget = widgets.get("widget") if widget: widget.setEnabled(key == option) - for key in self.widgets.keys(): if key == option: - if isinstance(self.widgets[key]["widget"], QWidget) and \ - self.widgets[key]["widget"].findChild(QgsMapLayerComboBox) and \ - self.widgets[key]["widget"].findChild(QComboBox): - # handling polygon_layer_with_field_selector - layer_selector = self.widgets[key]["widget"].findChild(QgsMapLayerComboBox) - field_selector = self.widgets[key]["widget"].findChild(QComboBox) - self.update_polygon_layer_and_field(key, layer_selector.currentLayer(), field_selector) + widget = self.widgets[key].get("widget") + if widget: + if isinstance(widget, QWidget) and \ + widget.findChild(QgsMapLayerComboBox) and \ + widget.findChild(QComboBox): + # handle polygon_layer_with_field_selector + layer_selector = widget.findChild(QgsMapLayerComboBox) + field_selector = widget.findChild(QComboBox) + self.update_polygon_layer_and_field(key, layer_selector.currentLayer(), field_selector) + else: + self.modified_config[key] = 1 else: self.modified_config[key] = 1 else: self.modified_config[key] = 0 - - self.stateChanged.emit(self.get_state()) + self.stateChanged.emit(self.get_state() def update_sub_widget_state(self, option, value): if value is not None: From ff7f6de182a6508ac7811dee51a41b7f051dbf56 Mon Sep 17 00:00:00 2001 From: hennie-k Date: Fri, 20 Sep 2024 12:31:32 +0200 Subject: [PATCH 05/19] Adds missing closing bracket --- geest/gui/widgets/geest_config_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geest/gui/widgets/geest_config_widget.py b/geest/gui/widgets/geest_config_widget.py index a779cb8..d2f2404 100644 --- a/geest/gui/widgets/geest_config_widget.py +++ b/geest/gui/widgets/geest_config_widget.py @@ -161,7 +161,7 @@ def handle_option_change(self, option, checked): self.modified_config[key] = 1 else: self.modified_config[key] = 0 - self.stateChanged.emit(self.get_state() + self.stateChanged.emit(self.get_state()) def update_sub_widget_state(self, option, value): if value is not None: From 1714e446ea9b650da8a313e9783d700931604afe Mon Sep 17 00:00:00 2001 From: hennie-k Date: Fri, 20 Sep 2024 13:21:16 +0200 Subject: [PATCH 06/19] Fixes foi selection insertion --- geest/gui/widgets/geest_config_widget.py | 55 +++++++++++++++++------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/geest/gui/widgets/geest_config_widget.py b/geest/gui/widgets/geest_config_widget.py index d2f2404..eaaf648 100644 --- a/geest/gui/widgets/geest_config_widget.py +++ b/geest/gui/widgets/geest_config_widget.py @@ -58,22 +58,23 @@ def recursive_find_and_store_widgets(self, widget, depth=0): print(" " * depth + f"Examining widget: {type(widget).__name__}") use_key = widget.property("use_key") if use_key: + if use_key not in self.widgets: + self.widgets[use_key] = {} if isinstance(widget, QRadioButton): - if use_key not in self.widgets: - self.widgets[use_key] = {} self.widgets[use_key]["radio"] = widget print(" " * depth + f"Stored QRadioButton for key: {use_key}") elif isinstance(widget, (QLineEdit, QSpinBox, QDoubleSpinBox, QComboBox, QgsMapLayerComboBox)): - if use_key not in self.widgets: - self.widgets[use_key] = {} self.widgets[use_key]["widget"] = widget print(" " * depth + f"Stored {type(widget).__name__} for key: {use_key}") - + elif isinstance(widget, QWidget) and widget.findChild(QgsMapLayerComboBox) and widget.findChild(QComboBox): + self.widgets[use_key]["widget"] = widget + print(" " * depth + f"Stored composite widget (polygon_layer_with_field_selector) for key: {use_key}") if widget.layout(): for i in range(widget.layout().count()): item = widget.layout().itemAt(i) if item.widget(): self.recursive_find_and_store_widgets(item.widget(), depth + 1) + print(f"Current widgets dictionary: {self.widgets}") def setup_connections(self): print("Setting up connections") @@ -84,8 +85,8 @@ def setup_connections(self): radio.toggled.connect(lambda checked, k=key: self.handle_option_change(k, checked)) print(f"Set up radio connection for {key}") if widget: + print(f"Setting up connection for widget type: {type(widget).__name__} for key: {key}") if isinstance(widget, QgsMapLayerComboBox): - print(f"Setting up connection for QgsMapLayerComboBox: {key}") widget.layerChanged.connect(lambda layer, k=key: self.update_layer_path(k, layer)) elif isinstance(widget, QLineEdit): widget.textChanged.connect(lambda text, k=key: self.update_sub_widget_state(k, text)) @@ -93,30 +94,42 @@ def setup_connections(self): widget.valueChanged.connect(lambda value, k=key: self.update_sub_widget_state(k, value)) elif isinstance(widget, QComboBox): widget.currentTextChanged.connect(lambda text, k=key: self.update_sub_widget_state(k, text)) - elif isinstance(widget, QWidget) and widget.findChild(QgsMapLayerComboBox) and widget.findChild(QComboBox): + elif isinstance(widget, QWidget) and widget.findChild(QgsMapLayerComboBox) and widget.findChild( + QComboBox): layer_selector = widget.findChild(QgsMapLayerComboBox) field_selector = widget.findChild(QComboBox) - layer_selector.layerChanged.connect(lambda layer, k=key: self.update_polygon_layer_and_field(k, layer, field_selector)) - field_selector.currentTextChanged.connect(lambda text, k=key, ls=layer_selector: self.update_polygon_layer_and_field(k, ls.currentLayer(), field_selector)) + layer_selector.layerChanged.connect( + lambda layer, k=key: self.update_polygon_layer_and_field(k, layer, field_selector)) + field_selector.currentTextChanged.connect( + lambda text, k=key, ls=layer_selector: self.update_polygon_layer_and_field(k, ls.currentLayer(), + field_selector)) print(f"Set up widget connection for {key}: {type(widget).__name__}") def update_polygon_layer_and_field(self, key, layer, field_selector): + print(f"update_polygon_layer_and_field called for {key}") if layer: provider_key = layer.providerType() uri = layer.dataProvider().dataSourceUri() + print(f"Layer URI: {uri}") decoded = QgsProviderRegistry.instance().decodeUri(provider_key, uri) + print(f"Decoded URI: {decoded}") path = decoded.get('path') or decoded.get('url') or decoded.get('layerName') field = field_selector.currentText() + print(f"Selected field: {field}") if path: - value = f"{path};{field}" if field else path + value = f"{path};{field}" + print(f"Setting {key} to {value}") self.modified_config[key] = value else: print(f"Unable to determine path for layer {layer.name()} with provider {provider_key}") - self.modified_config[key] = f"{uri};{field}" if field else uri + value = f"{uri};{field}" + print(f"Setting {key} to {value}") + self.modified_config[key] = value else: print(f"No layer selected for {key}") self.modified_config[key] = "" + print(f"Modified config after update_polygon_layer_and_field: {self.modified_config}") self.stateChanged.emit(self.get_state()) def update_layer_path(self, key, layer): @@ -139,28 +152,40 @@ def update_layer_path(self, key, layer): self.update_sub_widget_state(key, None) def handle_option_change(self, option, checked): + print(f"handle_option_change called for {option}, checked={checked}") if checked: for key, widgets in self.widgets.items(): widget = widgets.get("widget") if widget: widget.setEnabled(key == option) + print(f"Enabled widget for {key}: {key == option}") for key in self.widgets.keys(): if key == option: widget = self.widgets[key].get("widget") if widget: - if isinstance(widget, QWidget) and \ - widget.findChild(QgsMapLayerComboBox) and \ - widget.findChild(QComboBox): - # handle polygon_layer_with_field_selector + print(f"Widget type for {key}: {type(widget)}") + if isinstance(widget, QWidget) and widget.findChild(QgsMapLayerComboBox) and widget.findChild( + QComboBox): + print(f"Handling polygon_layer_with_field_selector for {key}") layer_selector = widget.findChild(QgsMapLayerComboBox) field_selector = widget.findChild(QComboBox) self.update_polygon_layer_and_field(key, layer_selector.currentLayer(), field_selector) + elif isinstance(widget, QgsMapLayerComboBox): + print(f"Handling QgsMapLayerComboBox for {key}") + self.update_layer_path(key, widget.currentLayer()) else: + print(f"Setting {key} to 1") self.modified_config[key] = 1 else: + print(f"No widget found for {key}, setting to 1") self.modified_config[key] = 1 else: + print(f"Setting {key} to 0") self.modified_config[key] = 0 + else: + print(f"Setting {option} to 0 (unchecked)") + self.modified_config[option] = 0 + print(f"Modified config after handle_option_change: {self.modified_config}") self.stateChanged.emit(self.get_state()) def update_sub_widget_state(self, option, value): From b1377199eb71eaf49b18987a71b0976cd7c468e9 Mon Sep 17 00:00:00 2001 From: hennie-k Date: Fri, 20 Sep 2024 13:29:35 +0200 Subject: [PATCH 07/19] Fixes foi selection insertion --- geest/gui/widgets/geest_config_widget.py | 32 ++++++++++++++++++++---- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/geest/gui/widgets/geest_config_widget.py b/geest/gui/widgets/geest_config_widget.py index eaaf648..5349aa0 100644 --- a/geest/gui/widgets/geest_config_widget.py +++ b/geest/gui/widgets/geest_config_widget.py @@ -5,7 +5,7 @@ from qgis.PyQt.QtCore import pyqtSignal from qgis.gui import QgsMapLayerComboBox #from qgis.core import QgsVectorLayer, QgsRasterLayer -from qgis.core import QgsProviderRegistry +from qgis.core import QgsProviderRegistry, QgsVectorLayer from .geest_widget_factory import GeestWidgetFactory @@ -98,24 +98,46 @@ def setup_connections(self): QComboBox): layer_selector = widget.findChild(QgsMapLayerComboBox) field_selector = widget.findChild(QComboBox) - layer_selector.layerChanged.connect( - lambda layer, k=key: self.update_polygon_layer_and_field(k, layer, field_selector)) + def update_fields(layer): + self.populate_field_selector(layer, field_selector) + self.update_polygon_layer_and_field(key, layer, field_selector) + + layer_selector.layerChanged.connect(update_fields) field_selector.currentTextChanged.connect( lambda text, k=key, ls=layer_selector: self.update_polygon_layer_and_field(k, ls.currentLayer(), field_selector)) + print(f"Set up widget connection for {key}: {type(widget).__name__}") + def populate_field_selector(self, layer, field_selector): + if isinstance(layer, QgsVectorLayer): + field_selector.clear() + field_selector.addItems([field.name() for field in layer.fields()]) + print(f"Populated field selector with: {[field.name() for field in layer.fields()]}") + else: + print(f"Invalid layer type for populating field selector: {type(layer)}") + def update_polygon_layer_and_field(self, key, layer, field_selector): print(f"update_polygon_layer_and_field called for {key}") - if layer: + print(f"Layer: {layer}") + print(f"Field selector: {field_selector}") + + if layer and isinstance(layer, QgsVectorLayer): provider_key = layer.providerType() uri = layer.dataProvider().dataSourceUri() print(f"Layer URI: {uri}") decoded = QgsProviderRegistry.instance().decodeUri(provider_key, uri) print(f"Decoded URI: {decoded}") path = decoded.get('path') or decoded.get('url') or decoded.get('layerName') + + # Ensure field selector is populated + self.populate_field_selector(layer, field_selector) + field = field_selector.currentText() + print( + f"Current field selector items: {[field_selector.itemText(i) for i in range(field_selector.count())]}") print(f"Selected field: {field}") + if path: value = f"{path};{field}" print(f"Setting {key} to {value}") @@ -126,7 +148,7 @@ def update_polygon_layer_and_field(self, key, layer, field_selector): print(f"Setting {key} to {value}") self.modified_config[key] = value else: - print(f"No layer selected for {key}") + print(f"No valid layer selected for {key}") self.modified_config[key] = "" print(f"Modified config after update_polygon_layer_and_field: {self.modified_config}") From a364e52a80e4cc9c416c3d09125f0ece09fc9bd2 Mon Sep 17 00:00:00 2001 From: hennie-k Date: Sun, 22 Sep 2024 13:33:24 +0200 Subject: [PATCH 08/19] Removes unused function --- geest/gui/widgets/geest_config_widget.py | 30 ------------------------ 1 file changed, 30 deletions(-) diff --git a/geest/gui/widgets/geest_config_widget.py b/geest/gui/widgets/geest_config_widget.py index 5349aa0..fc631bb 100644 --- a/geest/gui/widgets/geest_config_widget.py +++ b/geest/gui/widgets/geest_config_widget.py @@ -222,36 +222,6 @@ def update_sub_widget_state(self, option, value): def get_state(self): return self.modified_config.copy() - def reset_to_original(self): - self.modified_config = self.original_config.copy() - self.update_widgets_from_config() - self.stateChanged.emit(self.get_state()) - - def update_widgets_from_config(self): - for key, value in self.modified_config.items(): - if key in self.widgets: - widgets = self.widgets[key] - radio = widgets.get("radio") - widget = widgets.get("widget") - if radio: - radio.setChecked(bool(value)) - if widget: - if isinstance(widget, QLineEdit): - widget.setText(str(value)) - elif isinstance(widget, (QSpinBox, QDoubleSpinBox)): - widget.setValue(float(value) if isinstance(widget, QDoubleSpinBox) else int(value)) - elif isinstance(widget, (QComboBox, QgsMapLayerComboBox)): - widget.setCurrentText(str(value)) - elif isinstance(widget, QWidget) and widget.findChild(QgsMapLayerComboBox) and widget.findChild(QComboBox): - # Handle polygon_layer_with_field_selector widget - if value and ';' in value: - layer_path, field = value.split(';') - field_selector = widget.findChild(QComboBox) - # Set layer --- maybe necessary? - # layer_selector = widget.findChild(QgsMapLayerComboBox) - # layer_selector.setLayer(layer_path) - field_selector.setCurrentText(field) - def dump_widget_hierarchy(self, widget, level=0): output = [] output.append(" " * level + f"{widget.__class__.__name__}") From 72e9fdf76bc2fd7851eb17795e8238331cb56aab Mon Sep 17 00:00:00 2001 From: hennie-k Date: Sun, 22 Sep 2024 13:46:47 +0200 Subject: [PATCH 09/19] Applies gigo guard --- geest/gui/widgets/geest_widget_factory.py | 44 +++++++++++------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/geest/gui/widgets/geest_widget_factory.py b/geest/gui/widgets/geest_widget_factory.py index 83c11e8..4d29918 100644 --- a/geest/gui/widgets/geest_widget_factory.py +++ b/geest/gui/widgets/geest_widget_factory.py @@ -27,6 +27,20 @@ class GeestWidgetFactory: "vector": "vector", } + @staticmethod + def safe_float(value, default): + try: + return float(value) if value != '' else default + except (ValueError, TypeError): + return default + + @staticmethod + def safe_int(value, default): + try: + return int(float(value)) if value != '' else default + except (ValueError, TypeError): + return default + @staticmethod def create_widgets(layer_data: dict, parent=None): use_keys_mapping = { @@ -200,34 +214,20 @@ def create_specific_widget(mapping: dict, layer_data: dict): """ widget_type = mapping["type"] - #-- guard against GIGO - def safe_float(value, default): - try: - return float(value) if value != '' else default - except (ValueError, TypeError): - return default - - # -- guard against GIGO - def safe_int(value, default): - try: - return int(float(value)) if value != '' else default - except (ValueError, TypeError): - return default - if widget_type == "doublespinbox": widget = QDoubleSpinBox() - widget.setMinimum(safe_float(mapping.get("min"), 0.0)) - widget.setMaximum(safe_float(mapping.get("max"), 100.0)) - widget.setDecimals(safe_int(mapping.get("decimals"), 1)) - widget.setValue(safe_float(mapping.get("default"), 0.0)) + widget.setMinimum(GeestWidgetFactory.safe_float(mapping.get("min"), 0.0)) + widget.setMaximum(GeestWidgetFactory.safe_float(mapping.get("max"), 100.0)) + widget.setDecimals(GeestWidgetFactory.safe_int(mapping.get("decimals"), 1)) + widget.setValue(GeestWidgetFactory.safe_float(mapping.get("default"), 0.0)) widget.setToolTip(mapping.get("tooltip", "")) return widget elif widget_type == "spinbox": widget = QSpinBox() - widget.setMinimum(safe_int(mapping.get("min"), 0)) - widget.setMaximum(safe_int(mapping.get("max"), 10000)) - widget.setValue(safe_int(mapping.get("default"), 0)) + widget.setMinimum(GeestWidgetFactory.safe_int(mapping.get("min"), 0)) + widget.setMaximum(GeestWidgetFactory.safe_int(mapping.get("max"), 10000)) + widget.setValue(GeestWidgetFactory.safe_int(mapping.get("default"), 0)) widget.setToolTip(mapping.get("tooltip", "")) return widget @@ -403,4 +403,4 @@ def populate_csv_columns(file_path: str, lon_combo: QComboBox, lat_combo: QCombo lon_combo.clear() lat_combo.clear() lon_combo.setEnabled(False) - lat_combo.setEnabled(False) \ No newline at end of file + lat_combo.setEnabled(False) From d8a52d4a609da6f614ce76dfe9c952e52ec6ac1b Mon Sep 17 00:00:00 2001 From: hennie-k Date: Sun, 22 Sep 2024 16:39:38 +0200 Subject: [PATCH 10/19] Temporarily removes disabling of widgets --- geest/gui/widgets/geest_config_widget.py | 6 +++--- geest/gui/widgets/geest_widget_factory.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/geest/gui/widgets/geest_config_widget.py b/geest/gui/widgets/geest_config_widget.py index fc631bb..e74a982 100644 --- a/geest/gui/widgets/geest_config_widget.py +++ b/geest/gui/widgets/geest_config_widget.py @@ -178,9 +178,9 @@ def handle_option_change(self, option, checked): if checked: for key, widgets in self.widgets.items(): widget = widgets.get("widget") - if widget: - widget.setEnabled(key == option) - print(f"Enabled widget for {key}: {key == option}") + #if widget: + #widget.setEnabled(key == option) + #print(f"Enabled widget for {key}: {key == option}") for key in self.widgets.keys(): if key == option: widget = self.widgets[key].get("widget") diff --git a/geest/gui/widgets/geest_widget_factory.py b/geest/gui/widgets/geest_widget_factory.py index 4d29918..b5e0cb2 100644 --- a/geest/gui/widgets/geest_widget_factory.py +++ b/geest/gui/widgets/geest_widget_factory.py @@ -195,8 +195,8 @@ def create_widgets(layer_data: dict, parent=None): main_layout.addWidget(option_container) - radio_button.toggled.connect(lambda checked, w=widget: w.setEnabled(checked)) - widget.setEnabled(False) # Initially disable all widgets + #radio_button.toggled.connect(lambda checked, w=widget: w.setEnabled(checked)) + #widget.setEnabled(False) # Initially disable all widgets if radio_group.buttons(): radio_group.buttons()[0].setChecked(True) From 5031bc5d7bac2f0b918965ad3be32caa4d1ea362 Mon Sep 17 00:00:00 2001 From: hennie-k Date: Sun, 22 Sep 2024 21:36:43 +0200 Subject: [PATCH 11/19] Adjusts layer and field combobox handling for the 'Classify polygons' widget --- geest/gui/widgets/geest_config_widget.py | 51 +++++++---------------- geest/gui/widgets/geest_widget_factory.py | 28 ++++++++++--- 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/geest/gui/widgets/geest_config_widget.py b/geest/gui/widgets/geest_config_widget.py index e74a982..4bc5a72 100644 --- a/geest/gui/widgets/geest_config_widget.py +++ b/geest/gui/widgets/geest_config_widget.py @@ -117,12 +117,12 @@ def populate_field_selector(self, layer, field_selector): else: print(f"Invalid layer type for populating field selector: {type(layer)}") - def update_polygon_layer_and_field(self, key, layer, field_selector): + def update_polygon_layer_and_field(self, key, layer, field): print(f"update_polygon_layer_and_field called for {key}") - print(f"Layer: {layer}") - print(f"Field selector: {field_selector}") + print(f"Layer: {layer.name() if layer else 'None'}") + print(f"Field: {field}") - if layer and isinstance(layer, QgsVectorLayer): + if layer and isinstance(layer, QgsVectorLayer) and field: provider_key = layer.providerType() uri = layer.dataProvider().dataSourceUri() print(f"Layer URI: {uri}") @@ -130,25 +130,15 @@ def update_polygon_layer_and_field(self, key, layer, field_selector): print(f"Decoded URI: {decoded}") path = decoded.get('path') or decoded.get('url') or decoded.get('layerName') - # Ensure field selector is populated - self.populate_field_selector(layer, field_selector) - - field = field_selector.currentText() - print( - f"Current field selector items: {[field_selector.itemText(i) for i in range(field_selector.count())]}") - print(f"Selected field: {field}") - if path: value = f"{path};{field}" print(f"Setting {key} to {value}") self.modified_config[key] = value else: print(f"Unable to determine path for layer {layer.name()} with provider {provider_key}") - value = f"{uri};{field}" - print(f"Setting {key} to {value}") - self.modified_config[key] = value + self.modified_config[key] = "" else: - print(f"No valid layer selected for {key}") + print(f"No valid layer or field selected for {key}") self.modified_config[key] = "" print(f"Modified config after update_polygon_layer_and_field: {self.modified_config}") @@ -178,28 +168,19 @@ def handle_option_change(self, option, checked): if checked: for key, widgets in self.widgets.items(): widget = widgets.get("widget") - #if widget: - #widget.setEnabled(key == option) - #print(f"Enabled widget for {key}: {key == option}") - for key in self.widgets.keys(): if key == option: - widget = self.widgets[key].get("widget") - if widget: - print(f"Widget type for {key}: {type(widget)}") - if isinstance(widget, QWidget) and widget.findChild(QgsMapLayerComboBox) and widget.findChild( - QComboBox): - print(f"Handling polygon_layer_with_field_selector for {key}") - layer_selector = widget.findChild(QgsMapLayerComboBox) - field_selector = widget.findChild(QComboBox) - self.update_polygon_layer_and_field(key, layer_selector.currentLayer(), field_selector) - elif isinstance(widget, QgsMapLayerComboBox): - print(f"Handling QgsMapLayerComboBox for {key}") - self.update_layer_path(key, widget.currentLayer()) + if isinstance(widget, QWidget) and hasattr(widget, 'get_selections'): + print(f"Handling polygon_layer_with_field_selector for {key}") + layer, field = widget.get_selections() + if layer and field: + self.update_polygon_layer_and_field(key, layer, field) else: - print(f"Setting {key} to 1") - self.modified_config[key] = 1 + print(f"No layer or field selected for {key}") + elif isinstance(widget, QgsMapLayerComboBox): + print(f"Handling QgsMapLayerComboBox for {key}") + self.update_layer_path(key, widget.currentLayer()) else: - print(f"No widget found for {key}, setting to 1") + print(f"Setting {key} to 1") self.modified_config[key] = 1 else: print(f"Setting {key} to 0") diff --git a/geest/gui/widgets/geest_widget_factory.py b/geest/gui/widgets/geest_widget_factory.py index b5e0cb2..7bb449a 100644 --- a/geest/gui/widgets/geest_widget_factory.py +++ b/geest/gui/widgets/geest_widget_factory.py @@ -290,18 +290,36 @@ def create_specific_widget(mapping: dict, layer_data: dict): layout.addWidget(field_selector) # Update field selector when layer changes - def update_fields(): + def update_fields(layer): + print(f"Updating fields for layer: {layer.name() if layer else 'None'}") field_selector.clear() - layer = layer_selector.currentLayer() if isinstance(layer, QgsVectorLayer): - field_selector.addItems([field.name() for field in layer.fields()]) + fields = [field.name() for field in layer.fields()] + field_selector.addItems(fields) + print(f"Fields added: {fields}") + else: + print("Layer is not a vector layer") + # Connect the layer changed signal layer_selector.layerChanged.connect(update_fields) - # Initial population of fields - update_fields() + # Initial update + initial_layer = layer_selector.currentLayer() + if initial_layer: + update_fields(initial_layer) + else: + print("No initial layer selected") + + # Store references to selectors + container.layer_selector = layer_selector + container.field_selector = field_selector + # Create a method to get the current selections + def get_selections(): + return layer_selector.currentLayer(), field_selector.currentText() + container.get_selections = get_selections container.setToolTip(mapping.get("tooltip", "")) + return container elif widget_type == "csv_to_point": From ea80849a777a132d1ea6723ea6b8aba98e8c95de Mon Sep 17 00:00:00 2001 From: hennie-k Date: Mon, 23 Sep 2024 08:49:30 +0200 Subject: [PATCH 12/19] Removes unused import --- geest/gui/widgets/geest_config_widget.py | 1 - 1 file changed, 1 deletion(-) diff --git a/geest/gui/widgets/geest_config_widget.py b/geest/gui/widgets/geest_config_widget.py index 4bc5a72..a2189d4 100644 --- a/geest/gui/widgets/geest_config_widget.py +++ b/geest/gui/widgets/geest_config_widget.py @@ -4,7 +4,6 @@ ) from qgis.PyQt.QtCore import pyqtSignal from qgis.gui import QgsMapLayerComboBox -#from qgis.core import QgsVectorLayer, QgsRasterLayer from qgis.core import QgsProviderRegistry, QgsVectorLayer from .geest_widget_factory import GeestWidgetFactory From 133fa8de60d5166c0c92ff9bc45810fee6ea48ba Mon Sep 17 00:00:00 2001 From: hennie-k Date: Mon, 23 Sep 2024 08:55:30 +0200 Subject: [PATCH 13/19] Removes unused import, fills paragraph --- geest/gui/widgets/geest_widget_factory.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/geest/gui/widgets/geest_widget_factory.py b/geest/gui/widgets/geest_widget_factory.py index 7bb449a..0942966 100644 --- a/geest/gui/widgets/geest_widget_factory.py +++ b/geest/gui/widgets/geest_widget_factory.py @@ -12,7 +12,7 @@ QComboBox, QFileDialog, ) -from qgis.PyQt.QtCore import Qt + from qgis.gui import QgsMapLayerComboBox, QgsFileWidget from qgis.core import QgsMapLayer, QgsMapLayerProxyModel, QgsVectorLayer @@ -126,31 +126,36 @@ def create_widgets(layer_data: dict, parent=None): "Use OSM Downloader": { "label": "Fetch the data from OSM", - "description": "Using this option, we will try to fetch the data needed for this indicator directly from OSM.", + "description": "Using this option, we will try to fetch the data needed for this indicator directly " + "from OSM.", "type": "download_option", "tooltip": "Download data from OSM." }, "Use WBL Downloader": { "label": "Fetch the data from WBL", - "description": "Using this option, we will try to fetch the data needed for this indicator directly from WBL.", + "description": "Using this option, we will try to fetch the data needed for this indicator directly " + "from WBL.", "type": "download_option", "tooltip": "Download data from WBL." }, "Use Humdata Downloader": { "label": "Fetch the data from HumData", - "description": "Using this option, we will try to fetch the data needed for this indicator directly from HumData.", + "description": "Using this option, we will try to fetch the data needed for this indicator directly " + "from HumData.", "type": "download_option", "tooltip": "Download data from HumData." }, "Use Mapillary Downloader": { "label": "Fetch the data from Mapillary", - "description": "Using this option, we will try to fetch the data needed for this indicator directly from Mapillary.", + "description": "Using this option, we will try to fetch the data needed for this indicator directly " + "from Mapillary.", "type": "download_option", "tooltip": "Download data from Mapillary." }, "Use Other Downloader": { "label": "Fetch the data from specified source", - "description": f"Using this option, we will try to fetch the data needed for this indicator directly from {layer_data.get('Use Other Downloader', '')}.", + "description": f"Using this option, we will try to fetch the data needed for this indicator directly " + f"from {layer_data.get('Use Other Downloader', '')}.", "type": "download_option", "tooltip": f"Download data from {layer_data.get('Use Other Downloader', 'Other Source')}." } @@ -256,7 +261,8 @@ def create_specific_widget(mapping: dict, layer_data: dict): elif subtype_mapped == "point": widget.setFilters(QgsMapLayerProxyModel.PointLayer) else: - print(f"Invalid layer subtype '{layer_type}' for '{mapping.get('label')}'. Defaulting to all vector layers.") + print(f"Invalid layer subtype '{layer_type}' for '{mapping.get('label')}'. Defaulting to all " + f"vector layers.") widget.setFilters(QgsMapLayerProxyModel.VectorLayer) else: print(f"Unknown layer type '{layer_type}' for '{mapping.get('label')}'. Defaulting to all layers.") From b8dbcb98b2d4bb04290290daabd703026c46e1c6 Mon Sep 17 00:00:00 2001 From: hennie-k Date: Mon, 23 Sep 2024 09:00:02 +0200 Subject: [PATCH 14/19] Removes unused imports, reformats block comment --- geest/gui/widgets/geest_config_widget.py | 3 ++- geest/gui/widgets/geest_widget_factory.py | 9 +++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/geest/gui/widgets/geest_config_widget.py b/geest/gui/widgets/geest_config_widget.py index a2189d4..6fe5811 100644 --- a/geest/gui/widgets/geest_config_widget.py +++ b/geest/gui/widgets/geest_config_widget.py @@ -97,6 +97,7 @@ def setup_connections(self): QComboBox): layer_selector = widget.findChild(QgsMapLayerComboBox) field_selector = widget.findChild(QComboBox) + def update_fields(layer): self.populate_field_selector(layer, field_selector) self.update_polygon_layer_and_field(key, layer, field_selector) @@ -210,4 +211,4 @@ def dump_widget_hierarchy(self, widget, level=0): item = widget.layout().itemAt(i) if item.widget(): output.append(self.dump_widget_hierarchy(item.widget(), level + 1)) - return "\n".join(output) \ No newline at end of file + return "\n".join(output) diff --git a/geest/gui/widgets/geest_widget_factory.py b/geest/gui/widgets/geest_widget_factory.py index 0942966..d666867 100644 --- a/geest/gui/widgets/geest_widget_factory.py +++ b/geest/gui/widgets/geest_widget_factory.py @@ -1,20 +1,17 @@ from qgis.PyQt.QtWidgets import ( QWidget, QVBoxLayout, - QHBoxLayout, QRadioButton, QLabel, QButtonGroup, QLineEdit, - QCheckBox, QSpinBox, QDoubleSpinBox, QComboBox, - QFileDialog, ) from qgis.gui import QgsMapLayerComboBox, QgsFileWidget -from qgis.core import QgsMapLayer, QgsMapLayerProxyModel, QgsVectorLayer +from qgis.core import QgsMapLayerProxyModel, QgsVectorLayer class GeestWidgetFactory: @@ -200,8 +197,8 @@ def create_widgets(layer_data: dict, parent=None): main_layout.addWidget(option_container) - #radio_button.toggled.connect(lambda checked, w=widget: w.setEnabled(checked)) - #widget.setEnabled(False) # Initially disable all widgets + # radio_button.toggled.connect(lambda checked, w=widget: w.setEnabled(checked)) + # widget.setEnabled(False) # Initially disable all widgets if radio_group.buttons(): radio_group.buttons()[0].setChecked(True) From 818ff29cc07ac1b73ce76d6c19d1a99f794bfa58 Mon Sep 17 00:00:00 2001 From: hennie-k Date: Mon, 23 Sep 2024 09:05:34 +0200 Subject: [PATCH 15/19] Reformats line to be < 79 chars --- geest/gui/widgets/geest_config_widget.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/geest/gui/widgets/geest_config_widget.py b/geest/gui/widgets/geest_config_widget.py index 6fe5811..2cdfdf4 100644 --- a/geest/gui/widgets/geest_config_widget.py +++ b/geest/gui/widgets/geest_config_widget.py @@ -104,7 +104,8 @@ def update_fields(layer): layer_selector.layerChanged.connect(update_fields) field_selector.currentTextChanged.connect( - lambda text, k=key, ls=layer_selector: self.update_polygon_layer_and_field(k, ls.currentLayer(), + lambda text, k=key, ls=layer_selector: self.update_polygon_layer_and_field(k, + ls.currentLayer(), field_selector)) print(f"Set up widget connection for {key}: {type(widget).__name__}") From 30c94d84d559208920453360b39d7c853b009b46 Mon Sep 17 00:00:00 2001 From: hennie-k Date: Mon, 23 Sep 2024 11:42:07 +0200 Subject: [PATCH 16/19] Implements widgetfactory-mulibuffer-point widget --- geest/gui/widgets/geest_config_widget.py | 42 +++++++++++- geest/gui/widgets/geest_widget_factory.py | 81 ++++++++++++++++++++--- 2 files changed, 112 insertions(+), 11 deletions(-) diff --git a/geest/gui/widgets/geest_config_widget.py b/geest/gui/widgets/geest_config_widget.py index 2cdfdf4..596d172 100644 --- a/geest/gui/widgets/geest_config_widget.py +++ b/geest/gui/widgets/geest_config_widget.py @@ -1,6 +1,12 @@ from qgis.PyQt.QtWidgets import ( - QWidget, QVBoxLayout, QRadioButton, QLineEdit, - QSpinBox, QDoubleSpinBox, QComboBox + QWidget, + QVBoxLayout, + QRadioButton, + QLineEdit, + QSpinBox, + QDoubleSpinBox, + QComboBox, + QButtonGroup ) from qgis.PyQt.QtCore import pyqtSignal from qgis.gui import QgsMapLayerComboBox @@ -65,6 +71,9 @@ def recursive_find_and_store_widgets(self, widget, depth=0): elif isinstance(widget, (QLineEdit, QSpinBox, QDoubleSpinBox, QComboBox, QgsMapLayerComboBox)): self.widgets[use_key]["widget"] = widget print(" " * depth + f"Stored {type(widget).__name__} for key: {use_key}") + elif isinstance(widget, QWidget) and widget.property("widget_type") == "multibuffer": + self.widgets[use_key]["widget"] = widget + print(" " * depth + f"Stored multibuffer widget for key: {use_key}") elif isinstance(widget, QWidget) and widget.findChild(QgsMapLayerComboBox) and widget.findChild(QComboBox): self.widgets[use_key]["widget"] = widget print(" " * depth + f"Stored composite widget (polygon_layer_with_field_selector) for key: {use_key}") @@ -107,6 +116,14 @@ def update_fields(layer): lambda text, k=key, ls=layer_selector: self.update_polygon_layer_and_field(k, ls.currentLayer(), field_selector)) + elif widget.property("widget_type") == "multibuffer": + travel_mode_group = widget.travel_mode_group + measurement_group = widget.measurement_group + increment_edit = widget.increment_edit + + travel_mode_group.buttonClicked.connect(lambda btn, k=key: self.update_multibuffer_state(k)) + measurement_group.buttonClicked.connect(lambda btn, k=key: self.update_multibuffer_state(k)) + increment_edit.textChanged.connect(lambda text, k=key: self.update_multibuffer_state(k)) print(f"Set up widget connection for {key}: {type(widget).__name__}") @@ -170,7 +187,10 @@ def handle_option_change(self, option, checked): for key, widgets in self.widgets.items(): widget = widgets.get("widget") if key == option: - if isinstance(widget, QWidget) and hasattr(widget, 'get_selections'): + if widget is None: + print(f"No widget found for {key}") + self.modified_config[key] = 1 + elif isinstance(widget, QWidget) and hasattr(widget, 'get_selections'): print(f"Handling polygon_layer_with_field_selector for {key}") layer, field = widget.get_selections() if layer and field: @@ -180,6 +200,9 @@ def handle_option_change(self, option, checked): elif isinstance(widget, QgsMapLayerComboBox): print(f"Handling QgsMapLayerComboBox for {key}") self.update_layer_path(key, widget.currentLayer()) + elif isinstance(widget, QWidget) and widget.property("widget_type") == "multibuffer": + print(f"Handling multibuffer for {key}") + self.update_multibuffer_state(key) else: print(f"Setting {key} to 1") self.modified_config[key] = 1 @@ -201,6 +224,19 @@ def update_sub_widget_state(self, option, value): self.modified_config[option] = "0" self.stateChanged.emit(self.get_state()) + def update_multibuffer_state(self, key): + widget = self.widgets[key]["widget"] + travel_mode = "Driving" if widget.travel_mode_group.checkedButton().text() == "Driving" else "Walking" + measurement = "Distance" if widget.measurement_group.checkedButton().text() == "Distance" else "Time" + increments = widget.increment_edit.text() + + # If increments is empty, use the default value + if not increments: + increments = self.original_config.get("Default Multi Buffer Distances", "") + + self.modified_config[key] = f"{travel_mode};{measurement};{increments}" + self.stateChanged.emit(self.get_state()) + def get_state(self): return self.modified_config.copy() diff --git a/geest/gui/widgets/geest_widget_factory.py b/geest/gui/widgets/geest_widget_factory.py index d666867..3375c24 100644 --- a/geest/gui/widgets/geest_widget_factory.py +++ b/geest/gui/widgets/geest_widget_factory.py @@ -1,6 +1,7 @@ from qgis.PyQt.QtWidgets import ( QWidget, QVBoxLayout, + QHBoxLayout, QRadioButton, QLabel, QButtonGroup, @@ -15,6 +16,7 @@ class GeestWidgetFactory: + valid_subtypes = { "point": "point", "line": "line", @@ -52,7 +54,7 @@ def create_widgets(layer_data: dict, parent=None): }, "Use Multi Buffer Point": { "label": "Multi Buffer Distances", - "type": "lineedit", + "type": "multibuffer", "default": layer_data.get("Default Multi Buffer Distances", ""), "tooltip": "Enter comma-separated buffer distances." }, @@ -200,8 +202,8 @@ def create_widgets(layer_data: dict, parent=None): # radio_button.toggled.connect(lambda checked, w=widget: w.setEnabled(checked)) # widget.setEnabled(False) # Initially disable all widgets - if radio_group.buttons(): - radio_group.buttons()[0].setChecked(True) + # if radio_group.buttons(): + # radio_group.buttons()[0].setChecked(True) return container @@ -233,12 +235,75 @@ def create_specific_widget(mapping: dict, layer_data: dict): widget.setToolTip(mapping.get("tooltip", "")) return widget - elif widget_type == "lineedit": - widget = QLineEdit() + elif widget_type == "multibuffer": + container = QWidget() + main_layout = QVBoxLayout() # Change to QVBoxLayout for overall vertical arrangement + container.setLayout(main_layout) + + # Container for travel mode and measurement radio buttons + radio_buttons_container = QHBoxLayout() # Use QHBoxLayout to place them side by side + + # Left VBox for Travel Mode + left_vbox = QVBoxLayout() + travel_mode_label = QLabel("Travel Mode:") + travel_mode_label.setStyleSheet("font-weight: bold;") + left_vbox.addWidget(travel_mode_label) + travel_mode_group = QButtonGroup(container) + walking_radio = QRadioButton("Walking") + driving_radio = QRadioButton("Driving") + walking_radio.setChecked(True) # Set Walking as default + travel_mode_group.addButton(walking_radio) + travel_mode_group.addButton(driving_radio) + left_vbox.addWidget(walking_radio) + left_vbox.addWidget(driving_radio) + + # Right VBox for Measurement + right_vbox = QVBoxLayout() + measurement_label = QLabel("Measurement:") + measurement_label.setStyleSheet("font-weight: bold;") + right_vbox.addWidget(measurement_label) + measurement_group = QButtonGroup(container) + distance_radio = QRadioButton("Distance") + time_radio = QRadioButton("Time") + distance_radio.setChecked(True) # Set Distance as default + measurement_group.addButton(distance_radio) + measurement_group.addButton(time_radio) + right_vbox.addWidget(distance_radio) + right_vbox.addWidget(time_radio) + + # Add left and right vboxes to radio_buttons_container + radio_buttons_container.addLayout(left_vbox) + radio_buttons_container.addLayout(right_vbox) + + # Add radio_buttons_container to main layout + main_layout.addLayout(radio_buttons_container) + + # Add QLineEdit for travel increments + increment_label = QLabel("Travel Increments:") + increment_label.setStyleSheet("font-weight: bold;") + increment_edit = QLineEdit() default_value = mapping.get("default", "") - widget.setText(str(default_value)) - widget.setToolTip(mapping.get("tooltip", "")) - return widget + increment_edit.setText(str(default_value)) + increment_edit.setToolTip(mapping.get("tooltip", "")) + + # Set default values + default_increments = mapping.get("default", "") + increment_edit.setText(default_increments) + + # Add label and QLineEdit directly to main layout + main_layout.addWidget(increment_label) + main_layout.addWidget(increment_edit) + + # Store references to widgets + container.travel_mode_group = travel_mode_group + container.measurement_group = measurement_group + container.increment_edit = increment_edit + + # Identifier + container.setProperty("widget_type", "multibuffer") + container.setProperty("use_key", mapping.get("use_key", "")) + + return container elif widget_type == "layer_selector": widget = QgsMapLayerComboBox() From fe537df009bef44b22517291c021b98727216549 Mon Sep 17 00:00:00 2001 From: hennie-k Date: Mon, 23 Sep 2024 15:38:12 +0200 Subject: [PATCH 17/19] Adds classify_poly_into_classes_widget class --- .../classify_poly_into_classes_widget.py | 55 +++++++++++++++++ geest/gui/widgets/geest_widget_factory.py | 59 +++---------------- 2 files changed, 63 insertions(+), 51 deletions(-) create mode 100644 geest/gui/widgets/classify_poly_into_classes_widget.py diff --git a/geest/gui/widgets/classify_poly_into_classes_widget.py b/geest/gui/widgets/classify_poly_into_classes_widget.py new file mode 100644 index 0000000..d5c048e --- /dev/null +++ b/geest/gui/widgets/classify_poly_into_classes_widget.py @@ -0,0 +1,55 @@ +from qgis.PyQt.QtWidgets import QWidget, QVBoxLayout, QLabel, QComboBox +from qgis.gui import QgsMapLayerComboBox +from qgis.core import QgsMapLayerProxyModel, QgsVectorLayer + + +class ClassifyPolyIntoClassesWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.layout = QVBoxLayout(self) + self.setLayout(self.layout) + + # Set the widget type property + self.setProperty("widget_type", "classify_poly_into_classes") + + # Layer selector + self.layer_label = QLabel("Select Polygon Layer:") + self.layout.addWidget(self.layer_label) + self.layer_selector = QgsMapLayerComboBox() + self.layer_selector.setFilters(QgsMapLayerProxyModel.PolygonLayer) + self.layout.addWidget(self.layer_selector) + + # Field selector + self.field_label = QLabel("Select Field of Interest:") + self.layout.addWidget(self.field_label) + self.field_selector = QComboBox() + self.layout.addWidget(self.field_selector) + + # Connect the layer changed signal + self.layer_selector.layerChanged.connect(self.update_fields) + + # Initial update + initial_layer = self.layer_selector.currentLayer() + if initial_layer: + self.update_fields(initial_layer) + else: + print("No initial layer selected") + + def update_fields(self, layer): + print(f"Updating fields for layer: {layer.name() if layer else 'None'}") + self.field_selector.clear() + if isinstance(layer, QgsVectorLayer): + fields = [field.name() for field in layer.fields()] + self.field_selector.addItems(fields) + print(f"Fields added: {fields}") + else: + print("Layer is not a vector layer") + + def get_selections(self): + return self.layer_selector.currentLayer(), self.field_selector.currentText() + + def set_tooltip(self, tooltip): + self.setToolTip(tooltip) + + def set_use_key(self, use_key): + self.setProperty("use_key", use_key) diff --git a/geest/gui/widgets/geest_widget_factory.py b/geest/gui/widgets/geest_widget_factory.py index 3375c24..380b4a4 100644 --- a/geest/gui/widgets/geest_widget_factory.py +++ b/geest/gui/widgets/geest_widget_factory.py @@ -14,6 +14,8 @@ from qgis.gui import QgsMapLayerComboBox, QgsFileWidget from qgis.core import QgsMapLayerProxyModel, QgsVectorLayer +from .classify_poly_into_classes_widget import ClassifyPolyIntoClassesWidget + class GeestWidgetFactory: @@ -84,7 +86,7 @@ def create_widgets(layer_data: dict, parent=None): "Use Classify Poly into Classes": { "label": "Classify Polygons into Classes", "description": "Using this option, you can classify polygons into classes.", - "type": "polygon_layer_with_field_selector", + "type": "classify_poly_into_classes", "layer_type": "polygon", "tooltip": "Select a polygon layer." }, @@ -339,56 +341,11 @@ def create_specific_widget(mapping: dict, layer_data: dict): widget.setToolTip(mapping.get("tooltip", "")) return widget - elif widget_type == "polygon_layer_with_field_selector": - container = QWidget() - layout = QVBoxLayout() - container.setLayout(layout) - - # Layer selector - layer_label = QLabel("Select Polygon Layer:") - layout.addWidget(layer_label) - layer_selector = QgsMapLayerComboBox() - layer_selector.setFilters(QgsMapLayerProxyModel.PolygonLayer) - layout.addWidget(layer_selector) - - # Field selector - field_label = QLabel("Select Field of Interest:") - layout.addWidget(field_label) - field_selector = QComboBox() - layout.addWidget(field_selector) - - # Update field selector when layer changes - def update_fields(layer): - print(f"Updating fields for layer: {layer.name() if layer else 'None'}") - field_selector.clear() - if isinstance(layer, QgsVectorLayer): - fields = [field.name() for field in layer.fields()] - field_selector.addItems(fields) - print(f"Fields added: {fields}") - else: - print("Layer is not a vector layer") - - # Connect the layer changed signal - layer_selector.layerChanged.connect(update_fields) - - # Initial update - initial_layer = layer_selector.currentLayer() - if initial_layer: - update_fields(initial_layer) - else: - print("No initial layer selected") - - # Store references to selectors - container.layer_selector = layer_selector - container.field_selector = field_selector - - # Create a method to get the current selections - def get_selections(): - return layer_selector.currentLayer(), field_selector.currentText() - container.get_selections = get_selections - container.setToolTip(mapping.get("tooltip", "")) - - return container + elif widget_type == "classify_poly_into_classes": + widget = ClassifyPolyIntoClassesWidget() + widget.set_tooltip(mapping.get("tooltip", "")) + widget.set_use_key(mapping.get("use_key", "")) + return widget elif widget_type == "csv_to_point": container = QWidget() From 2c0ac6b5f78c1dd3375e84c472165096af2f9d58 Mon Sep 17 00:00:00 2001 From: hennie-k Date: Mon, 23 Sep 2024 18:00:50 +0200 Subject: [PATCH 18/19] Ensures radio button signal propagation and output dict modification --- .../classify_poly_into_classes_widget.py | 33 +++++--- geest/gui/widgets/geest_config_widget.py | 83 ++++++++++++++++--- 2 files changed, 95 insertions(+), 21 deletions(-) diff --git a/geest/gui/widgets/classify_poly_into_classes_widget.py b/geest/gui/widgets/classify_poly_into_classes_widget.py index d5c048e..ad2aa0b 100644 --- a/geest/gui/widgets/classify_poly_into_classes_widget.py +++ b/geest/gui/widgets/classify_poly_into_classes_widget.py @@ -1,49 +1,62 @@ from qgis.PyQt.QtWidgets import QWidget, QVBoxLayout, QLabel, QComboBox +from qgis.PyQt.QtCore import pyqtSignal from qgis.gui import QgsMapLayerComboBox from qgis.core import QgsMapLayerProxyModel, QgsVectorLayer class ClassifyPolyIntoClassesWidget(QWidget): + # Define a custom signal + selectionsChanged = pyqtSignal() + def __init__(self, parent=None): super().__init__(parent) - self.layout = QVBoxLayout(self) - self.setLayout(self.layout) + self._layout = QVBoxLayout() + self.setLayout(self._layout) # Set the widget type property self.setProperty("widget_type", "classify_poly_into_classes") # Layer selector self.layer_label = QLabel("Select Polygon Layer:") - self.layout.addWidget(self.layer_label) + self._layout.addWidget(self.layer_label) self.layer_selector = QgsMapLayerComboBox() self.layer_selector.setFilters(QgsMapLayerProxyModel.PolygonLayer) - self.layout.addWidget(self.layer_selector) + self._layout.addWidget(self.layer_selector) # Field selector self.field_label = QLabel("Select Field of Interest:") - self.layout.addWidget(self.field_label) + self._layout.addWidget(self.field_label) self.field_selector = QComboBox() - self.layout.addWidget(self.field_selector) + self._layout.addWidget(self.field_selector) # Connect the layer changed signal self.layer_selector.layerChanged.connect(self.update_fields) + # Connect the field changed signal to emit selectionsChanged + self.field_selector.currentTextChanged.connect(self.emit_selections_changed) + # Initial update initial_layer = self.layer_selector.currentLayer() if initial_layer: self.update_fields(initial_layer) else: - print("No initial layer selected") + print("[ClassifyPolyIntoClassesWidget] No initial layer selected") def update_fields(self, layer): - print(f"Updating fields for layer: {layer.name() if layer else 'None'}") + print(f"[ClassifyPolyIntoClassesWidget] Updating fields for layer: {layer.name() if layer else 'None'}") self.field_selector.clear() if isinstance(layer, QgsVectorLayer): fields = [field.name() for field in layer.fields()] self.field_selector.addItems(fields) - print(f"Fields added: {fields}") + print(f"[ClassifyPolyIntoClassesWidget] Fields added: {fields}") else: - print("Layer is not a vector layer") + print("[ClassifyPolyIntoClassesWidget] Layer is not a vector layer") + # Emit signal since fields have been updated + self.selectionsChanged.emit() + + def emit_selections_changed(self): + print("[ClassifyPolyIntoClassesWidget] Selections changed") + self.selectionsChanged.emit() def get_selections(self): return self.layer_selector.currentLayer(), self.field_selector.currentText() diff --git a/geest/gui/widgets/geest_config_widget.py b/geest/gui/widgets/geest_config_widget.py index 596d172..7877d52 100644 --- a/geest/gui/widgets/geest_config_widget.py +++ b/geest/gui/widgets/geest_config_widget.py @@ -6,11 +6,13 @@ QSpinBox, QDoubleSpinBox, QComboBox, - QButtonGroup + QButtonGroup, + QLayout ) from qgis.PyQt.QtCore import pyqtSignal from qgis.gui import QgsMapLayerComboBox from qgis.core import QgsProviderRegistry, QgsVectorLayer + from .geest_widget_factory import GeestWidgetFactory @@ -74,14 +76,21 @@ def recursive_find_and_store_widgets(self, widget, depth=0): elif isinstance(widget, QWidget) and widget.property("widget_type") == "multibuffer": self.widgets[use_key]["widget"] = widget print(" " * depth + f"Stored multibuffer widget for key: {use_key}") + elif isinstance(widget, QWidget) and widget.property("widget_type") == "classify_poly_into_classes": + self.widgets[use_key]["widget"] = widget + print(" " * depth + f"Stored classify_poly_into_classes widget for key: {use_key}") elif isinstance(widget, QWidget) and widget.findChild(QgsMapLayerComboBox) and widget.findChild(QComboBox): self.widgets[use_key]["widget"] = widget print(" " * depth + f"Stored composite widget (polygon_layer_with_field_selector) for key: {use_key}") - if widget.layout(): - for i in range(widget.layout().count()): - item = widget.layout().itemAt(i) + + # Check if the widget has a layout + layout = widget.layout() if callable(getattr(widget, 'layout', None)) else None + if layout: + for i in range(layout.count()): + item = layout.itemAt(i) if item.widget(): self.recursive_find_and_store_widgets(item.widget(), depth + 1) + print(f"Current widgets dictionary: {self.widgets}") def setup_connections(self): @@ -89,9 +98,24 @@ def setup_connections(self): for key, widgets in self.widgets.items(): radio = widgets.get("radio") widget = widgets.get("widget") + + # Always set up the radio button connection if radio: radio.toggled.connect(lambda checked, k=key: self.handle_option_change(k, checked)) print(f"Set up radio connection for {key}") + + # Retrieve the widget_type property, if set + widget_type = widget.property("widget_type") if widget else None + + # Handle specific widget types + if widget_type == "classify_poly_into_classes": + print(f"Setting up specific connections for widget_type: {widget_type} (key: {key})") + if hasattr(widget, 'selectionsChanged'): + # Connect the custom signal to update_classify_poly_config + widget.selectionsChanged.connect(lambda k=key: self.update_classify_poly_config(k)) + print(f"Connected selectionsChanged signal for key: {key}") + + # Existing generic connection logic if widget: print(f"Setting up connection for widget type: {type(widget).__name__} for key: {key}") if isinstance(widget, QgsMapLayerComboBox): @@ -108,15 +132,16 @@ def setup_connections(self): field_selector = widget.findChild(QComboBox) def update_fields(layer): + print( + f"[setup_connections] populate_field_selector called for key: {key} with layer: {layer.name() if layer else 'None'}") self.populate_field_selector(layer, field_selector) self.update_polygon_layer_and_field(key, layer, field_selector) layer_selector.layerChanged.connect(update_fields) field_selector.currentTextChanged.connect( - lambda text, k=key, ls=layer_selector: self.update_polygon_layer_and_field(k, - ls.currentLayer(), + lambda text, k=key, ls=layer_selector: self.update_polygon_layer_and_field(k, ls.currentLayer(), field_selector)) - elif widget.property("widget_type") == "multibuffer": + elif widget_type == "multibuffer": travel_mode_group = widget.travel_mode_group measurement_group = widget.measurement_group increment_edit = widget.increment_edit @@ -127,7 +152,8 @@ def update_fields(layer): print(f"Set up widget connection for {key}: {type(widget).__name__}") - def populate_field_selector(self, layer, field_selector): + @staticmethod + def populate_field_selector(layer, field_selector): if isinstance(layer, QgsVectorLayer): field_selector.clear() field_selector.addItems([field.name() for field in layer.fields()]) @@ -162,6 +188,35 @@ def update_polygon_layer_and_field(self, key, layer, field): print(f"Modified config after update_polygon_layer_and_field: {self.modified_config}") self.stateChanged.emit(self.get_state()) + def update_classify_poly_config(self, key): + print(f"update_classify_poly_config called for {key}") + widget = self.widgets[key].get("widget") + if widget and widget.property("widget_type") == "classify_poly_into_classes": + layer, field = widget.get_selections() + if layer and field: + provider_key = layer.providerType() + uri = layer.dataProvider().dataSourceUri() + print(f"Layer URI: {uri}") + decoded = QgsProviderRegistry.instance().decodeUri(provider_key, uri) + print(f"Decoded URI: {decoded}") + path = decoded.get('path') or decoded.get('url') or decoded.get('layerName') + + if path: + value = f"{path};{field}" + print(f"Setting {key} to {value}") + self.modified_config[key] = value + else: + print(f"Unable to determine path for layer {layer.name()} with provider {provider_key}") + self.modified_config[key] = "" + else: + print(f"No layer or field selected for {key}") + self.modified_config[key] = "" + else: + print(f"Widget for {key} is not a ClassifyPolyIntoClassesWidget") + self.modified_config[key] = "" + print(f"Modified config after update_classify_poly_config: {self.modified_config}") + self.stateChanged.emit(self.get_state()) + def update_layer_path(self, key, layer): print(f"update_layer_path called for {key}") # Debug print if layer: @@ -203,6 +258,9 @@ def handle_option_change(self, option, checked): elif isinstance(widget, QWidget) and widget.property("widget_type") == "multibuffer": print(f"Handling multibuffer for {key}") self.update_multibuffer_state(key) + elif isinstance(widget, QWidget) and widget.property("widget_type") == "classify_poly_into_classes": + print(f"Handling ClassifyPolyIntoClassesWidget for {key}") + self.update_classify_poly_config(key) else: print(f"Setting {key} to 1") self.modified_config[key] = 1 @@ -243,9 +301,12 @@ def get_state(self): def dump_widget_hierarchy(self, widget, level=0): output = [] output.append(" " * level + f"{widget.__class__.__name__}") - if hasattr(widget, 'layout') and widget.layout(): - for i in range(widget.layout().count()): - item = widget.layout().itemAt(i) + + layout = widget.layout() if callable(getattr(widget, 'layout', None)) else getattr(widget, 'layout', None) + + if isinstance(layout, QLayout): + for i in range(layout.count()): + item = layout.itemAt(i) if item.widget(): output.append(self.dump_widget_hierarchy(item.widget(), level + 1)) return "\n".join(output) From 96ed2f271a729a6c94c50131fd3b24082f5d3a70 Mon Sep 17 00:00:00 2001 From: hennie-k Date: Mon, 23 Sep 2024 18:04:13 +0200 Subject: [PATCH 19/19] Reformats source --- geest/gui/tree_panel.py | 1 - .../classify_poly_into_classes_widget.py | 4 +- geest/gui/widgets/geest_config_widget.py | 176 +++++++++++++----- geest/gui/widgets/geest_widget_factory.py | 101 ++++++---- test/test_polygons_per_grid_cell.py | 4 +- 5 files changed, 199 insertions(+), 87 deletions(-) diff --git a/geest/gui/tree_panel.py b/geest/gui/tree_panel.py index e2d92a4..a28276a 100644 --- a/geest/gui/tree_panel.py +++ b/geest/gui/tree_panel.py @@ -28,7 +28,6 @@ from geest.core.workflow_queue_manager import WorkflowQueueManager - class TreePanel(QWidget): def __init__(self, parent=None, json_file=None): super().__init__(parent) diff --git a/geest/gui/widgets/classify_poly_into_classes_widget.py b/geest/gui/widgets/classify_poly_into_classes_widget.py index ad2aa0b..dada8de 100644 --- a/geest/gui/widgets/classify_poly_into_classes_widget.py +++ b/geest/gui/widgets/classify_poly_into_classes_widget.py @@ -43,7 +43,9 @@ def __init__(self, parent=None): print("[ClassifyPolyIntoClassesWidget] No initial layer selected") def update_fields(self, layer): - print(f"[ClassifyPolyIntoClassesWidget] Updating fields for layer: {layer.name() if layer else 'None'}") + print( + f"[ClassifyPolyIntoClassesWidget] Updating fields for layer: {layer.name() if layer else 'None'}" + ) self.field_selector.clear() if isinstance(layer, QgsVectorLayer): fields = [field.name() for field in layer.fields()] diff --git a/geest/gui/widgets/geest_config_widget.py b/geest/gui/widgets/geest_config_widget.py index 7877d52..d210e56 100644 --- a/geest/gui/widgets/geest_config_widget.py +++ b/geest/gui/widgets/geest_config_widget.py @@ -7,7 +7,7 @@ QDoubleSpinBox, QComboBox, QButtonGroup, - QLayout + QLayout, ) from qgis.PyQt.QtCore import pyqtSignal from qgis.gui import QgsMapLayerComboBox @@ -33,14 +33,18 @@ def create_widgets(self): self.setLayout(layout) print("Calling GeestWidgetFactory.create_widgets") - widgets_container = GeestWidgetFactory.create_widgets(self.original_config, self) + widgets_container = GeestWidgetFactory.create_widgets( + self.original_config, self + ) if widgets_container is None: print("GeestWidgetFactory.create_widgets returned None") return if not isinstance(widgets_container, QWidget): - print(f"GeestWidgetFactory.create_widgets returned unexpected type: {type(widgets_container)}") + print( + f"GeestWidgetFactory.create_widgets returned unexpected type: {type(widgets_container)}" + ) return if widgets_container.layout() is None: @@ -70,21 +74,42 @@ def recursive_find_and_store_widgets(self, widget, depth=0): if isinstance(widget, QRadioButton): self.widgets[use_key]["radio"] = widget print(" " * depth + f"Stored QRadioButton for key: {use_key}") - elif isinstance(widget, (QLineEdit, QSpinBox, QDoubleSpinBox, QComboBox, QgsMapLayerComboBox)): + elif isinstance( + widget, + (QLineEdit, QSpinBox, QDoubleSpinBox, QComboBox, QgsMapLayerComboBox), + ): self.widgets[use_key]["widget"] = widget - print(" " * depth + f"Stored {type(widget).__name__} for key: {use_key}") - elif isinstance(widget, QWidget) and widget.property("widget_type") == "multibuffer": + print( + " " * depth + f"Stored {type(widget).__name__} for key: {use_key}" + ) + elif ( + isinstance(widget, QWidget) + and widget.property("widget_type") == "multibuffer" + ): self.widgets[use_key]["widget"] = widget print(" " * depth + f"Stored multibuffer widget for key: {use_key}") - elif isinstance(widget, QWidget) and widget.property("widget_type") == "classify_poly_into_classes": + elif ( + isinstance(widget, QWidget) + and widget.property("widget_type") == "classify_poly_into_classes" + ): self.widgets[use_key]["widget"] = widget - print(" " * depth + f"Stored classify_poly_into_classes widget for key: {use_key}") - elif isinstance(widget, QWidget) and widget.findChild(QgsMapLayerComboBox) and widget.findChild(QComboBox): + print( + " " * depth + + f"Stored classify_poly_into_classes widget for key: {use_key}" + ) + elif ( + isinstance(widget, QWidget) + and widget.findChild(QgsMapLayerComboBox) + and widget.findChild(QComboBox) + ): self.widgets[use_key]["widget"] = widget - print(" " * depth + f"Stored composite widget (polygon_layer_with_field_selector) for key: {use_key}") + print( + " " * depth + + f"Stored composite widget (polygon_layer_with_field_selector) for key: {use_key}" + ) # Check if the widget has a layout - layout = widget.layout() if callable(getattr(widget, 'layout', None)) else None + layout = widget.layout() if callable(getattr(widget, "layout", None)) else None if layout: for i in range(layout.count()): item = layout.itemAt(i) @@ -101,7 +126,9 @@ def setup_connections(self): # Always set up the radio button connection if radio: - radio.toggled.connect(lambda checked, k=key: self.handle_option_change(k, checked)) + radio.toggled.connect( + lambda checked, k=key: self.handle_option_change(k, checked) + ) print(f"Set up radio connection for {key}") # Retrieve the widget_type property, if set @@ -109,46 +136,72 @@ def setup_connections(self): # Handle specific widget types if widget_type == "classify_poly_into_classes": - print(f"Setting up specific connections for widget_type: {widget_type} (key: {key})") - if hasattr(widget, 'selectionsChanged'): + print( + f"Setting up specific connections for widget_type: {widget_type} (key: {key})" + ) + if hasattr(widget, "selectionsChanged"): # Connect the custom signal to update_classify_poly_config - widget.selectionsChanged.connect(lambda k=key: self.update_classify_poly_config(k)) + widget.selectionsChanged.connect( + lambda k=key: self.update_classify_poly_config(k) + ) print(f"Connected selectionsChanged signal for key: {key}") # Existing generic connection logic if widget: - print(f"Setting up connection for widget type: {type(widget).__name__} for key: {key}") + print( + f"Setting up connection for widget type: {type(widget).__name__} for key: {key}" + ) if isinstance(widget, QgsMapLayerComboBox): - widget.layerChanged.connect(lambda layer, k=key: self.update_layer_path(k, layer)) + widget.layerChanged.connect( + lambda layer, k=key: self.update_layer_path(k, layer) + ) elif isinstance(widget, QLineEdit): - widget.textChanged.connect(lambda text, k=key: self.update_sub_widget_state(k, text)) + widget.textChanged.connect( + lambda text, k=key: self.update_sub_widget_state(k, text) + ) elif isinstance(widget, (QSpinBox, QDoubleSpinBox)): - widget.valueChanged.connect(lambda value, k=key: self.update_sub_widget_state(k, value)) + widget.valueChanged.connect( + lambda value, k=key: self.update_sub_widget_state(k, value) + ) elif isinstance(widget, QComboBox): - widget.currentTextChanged.connect(lambda text, k=key: self.update_sub_widget_state(k, text)) - elif isinstance(widget, QWidget) and widget.findChild(QgsMapLayerComboBox) and widget.findChild( - QComboBox): + widget.currentTextChanged.connect( + lambda text, k=key: self.update_sub_widget_state(k, text) + ) + elif ( + isinstance(widget, QWidget) + and widget.findChild(QgsMapLayerComboBox) + and widget.findChild(QComboBox) + ): layer_selector = widget.findChild(QgsMapLayerComboBox) field_selector = widget.findChild(QComboBox) def update_fields(layer): print( - f"[setup_connections] populate_field_selector called for key: {key} with layer: {layer.name() if layer else 'None'}") + f"[setup_connections] populate_field_selector called for key: {key} with layer: {layer.name() if layer else 'None'}" + ) self.populate_field_selector(layer, field_selector) self.update_polygon_layer_and_field(key, layer, field_selector) layer_selector.layerChanged.connect(update_fields) field_selector.currentTextChanged.connect( - lambda text, k=key, ls=layer_selector: self.update_polygon_layer_and_field(k, ls.currentLayer(), - field_selector)) + lambda text, k=key, ls=layer_selector: self.update_polygon_layer_and_field( + k, ls.currentLayer(), field_selector + ) + ) elif widget_type == "multibuffer": travel_mode_group = widget.travel_mode_group measurement_group = widget.measurement_group increment_edit = widget.increment_edit - travel_mode_group.buttonClicked.connect(lambda btn, k=key: self.update_multibuffer_state(k)) - measurement_group.buttonClicked.connect(lambda btn, k=key: self.update_multibuffer_state(k)) - increment_edit.textChanged.connect(lambda text, k=key: self.update_multibuffer_state(k)) + travel_mode_group.buttonClicked.connect( + lambda btn, k=key: self.update_multibuffer_state(k) + ) + measurement_group.buttonClicked.connect( + lambda btn, k=key: self.update_multibuffer_state(k) + ) + increment_edit.textChanged.connect( + lambda text, k=key: self.update_multibuffer_state(k) + ) print(f"Set up widget connection for {key}: {type(widget).__name__}") @@ -157,7 +210,9 @@ def populate_field_selector(layer, field_selector): if isinstance(layer, QgsVectorLayer): field_selector.clear() field_selector.addItems([field.name() for field in layer.fields()]) - print(f"Populated field selector with: {[field.name() for field in layer.fields()]}") + print( + f"Populated field selector with: {[field.name() for field in layer.fields()]}" + ) else: print(f"Invalid layer type for populating field selector: {type(layer)}") @@ -172,20 +227,24 @@ def update_polygon_layer_and_field(self, key, layer, field): print(f"Layer URI: {uri}") decoded = QgsProviderRegistry.instance().decodeUri(provider_key, uri) print(f"Decoded URI: {decoded}") - path = decoded.get('path') or decoded.get('url') or decoded.get('layerName') + path = decoded.get("path") or decoded.get("url") or decoded.get("layerName") if path: value = f"{path};{field}" print(f"Setting {key} to {value}") self.modified_config[key] = value else: - print(f"Unable to determine path for layer {layer.name()} with provider {provider_key}") + print( + f"Unable to determine path for layer {layer.name()} with provider {provider_key}" + ) self.modified_config[key] = "" else: print(f"No valid layer or field selected for {key}") self.modified_config[key] = "" - print(f"Modified config after update_polygon_layer_and_field: {self.modified_config}") + print( + f"Modified config after update_polygon_layer_and_field: {self.modified_config}" + ) self.stateChanged.emit(self.get_state()) def update_classify_poly_config(self, key): @@ -199,14 +258,20 @@ def update_classify_poly_config(self, key): print(f"Layer URI: {uri}") decoded = QgsProviderRegistry.instance().decodeUri(provider_key, uri) print(f"Decoded URI: {decoded}") - path = decoded.get('path') or decoded.get('url') or decoded.get('layerName') + path = ( + decoded.get("path") + or decoded.get("url") + or decoded.get("layerName") + ) if path: value = f"{path};{field}" print(f"Setting {key} to {value}") self.modified_config[key] = value else: - print(f"Unable to determine path for layer {layer.name()} with provider {provider_key}") + print( + f"Unable to determine path for layer {layer.name()} with provider {provider_key}" + ) self.modified_config[key] = "" else: print(f"No layer or field selected for {key}") @@ -214,7 +279,9 @@ def update_classify_poly_config(self, key): else: print(f"Widget for {key} is not a ClassifyPolyIntoClassesWidget") self.modified_config[key] = "" - print(f"Modified config after update_classify_poly_config: {self.modified_config}") + print( + f"Modified config after update_classify_poly_config: {self.modified_config}" + ) self.stateChanged.emit(self.get_state()) def update_layer_path(self, key, layer): @@ -225,12 +292,14 @@ def update_layer_path(self, key, layer): print(f"Layer URI: {uri}") decoded = QgsProviderRegistry.instance().decodeUri(provider_key, uri) print(f"Decoded URI: {decoded}") - path = decoded.get('path') or decoded.get('url') or decoded.get('layerName') + path = decoded.get("path") or decoded.get("url") or decoded.get("layerName") if path: print(f"Path found: {path}") self.update_sub_widget_state(key, path) else: - print(f"Unable to determine path for layer {layer.name()} with provider {provider_key}") + print( + f"Unable to determine path for layer {layer.name()} with provider {provider_key}" + ) self.update_sub_widget_state(key, uri) # Fallback to using the full URI else: print(f"No layer selected for {key}") @@ -245,7 +314,9 @@ def handle_option_change(self, option, checked): if widget is None: print(f"No widget found for {key}") self.modified_config[key] = 1 - elif isinstance(widget, QWidget) and hasattr(widget, 'get_selections'): + elif isinstance(widget, QWidget) and hasattr( + widget, "get_selections" + ): print(f"Handling polygon_layer_with_field_selector for {key}") layer, field = widget.get_selections() if layer and field: @@ -255,10 +326,17 @@ def handle_option_change(self, option, checked): elif isinstance(widget, QgsMapLayerComboBox): print(f"Handling QgsMapLayerComboBox for {key}") self.update_layer_path(key, widget.currentLayer()) - elif isinstance(widget, QWidget) and widget.property("widget_type") == "multibuffer": + elif ( + isinstance(widget, QWidget) + and widget.property("widget_type") == "multibuffer" + ): print(f"Handling multibuffer for {key}") self.update_multibuffer_state(key) - elif isinstance(widget, QWidget) and widget.property("widget_type") == "classify_poly_into_classes": + elif ( + isinstance(widget, QWidget) + and widget.property("widget_type") + == "classify_poly_into_classes" + ): print(f"Handling ClassifyPolyIntoClassesWidget for {key}") self.update_classify_poly_config(key) else: @@ -284,8 +362,16 @@ def update_sub_widget_state(self, option, value): def update_multibuffer_state(self, key): widget = self.widgets[key]["widget"] - travel_mode = "Driving" if widget.travel_mode_group.checkedButton().text() == "Driving" else "Walking" - measurement = "Distance" if widget.measurement_group.checkedButton().text() == "Distance" else "Time" + travel_mode = ( + "Driving" + if widget.travel_mode_group.checkedButton().text() == "Driving" + else "Walking" + ) + measurement = ( + "Distance" + if widget.measurement_group.checkedButton().text() == "Distance" + else "Time" + ) increments = widget.increment_edit.text() # If increments is empty, use the default value @@ -302,7 +388,11 @@ def dump_widget_hierarchy(self, widget, level=0): output = [] output.append(" " * level + f"{widget.__class__.__name__}") - layout = widget.layout() if callable(getattr(widget, 'layout', None)) else getattr(widget, 'layout', None) + layout = ( + widget.layout() + if callable(getattr(widget, "layout", None)) + else getattr(widget, "layout", None) + ) if isinstance(layout, QLayout): for i in range(layout.count()): diff --git a/geest/gui/widgets/geest_widget_factory.py b/geest/gui/widgets/geest_widget_factory.py index 380b4a4..ba44730 100644 --- a/geest/gui/widgets/geest_widget_factory.py +++ b/geest/gui/widgets/geest_widget_factory.py @@ -31,14 +31,14 @@ class GeestWidgetFactory: @staticmethod def safe_float(value, default): try: - return float(value) if value != '' else default + return float(value) if value != "" else default except (ValueError, TypeError): return default @staticmethod def safe_int(value, default): try: - return int(float(value)) if value != '' else default + return int(float(value)) if value != "" else default except (ValueError, TypeError): return default @@ -52,13 +52,13 @@ def create_widgets(layer_data: dict, parent=None): "max": 100.0, "decimals": 1, "default": layer_data.get("Default Index Score", 0.0), - "tooltip": "The default index score value." + "tooltip": "The default index score value.", }, "Use Multi Buffer Point": { "label": "Multi Buffer Distances", "type": "multibuffer", "default": layer_data.get("Default Multi Buffer Distances", ""), - "tooltip": "Enter comma-separated buffer distances." + "tooltip": "Enter comma-separated buffer distances.", }, "Use Single Buffer Point": { "label": "Single Buffer Distance", @@ -66,7 +66,7 @@ def create_widgets(layer_data: dict, parent=None): "min": 0, "max": 10000, "default": layer_data.get("Default Single Buffer Distance", 0), - "tooltip": "Enter buffer distance." + "tooltip": "Enter buffer distance.", }, "Use Create Grid": { "label": "Pixel Size", @@ -74,95 +74,96 @@ def create_widgets(layer_data: dict, parent=None): "min": 0, "max": 10000, "default": layer_data.get("Default pixel", 0), - "tooltip": "Enter pixel size for grid creation." + "tooltip": "Enter pixel size for grid creation.", }, "Use Add Layers Manually": { "label": "Add Layers Manually", "description": "Using this option, you can add layers manually.", "type": "layer_selector", "layer_type": "vector", - "tooltip": "Select a vector layer." + "tooltip": "Select a vector layer.", }, "Use Classify Poly into Classes": { "label": "Classify Polygons into Classes", "description": "Using this option, you can classify polygons into classes.", "type": "classify_poly_into_classes", "layer_type": "polygon", - "tooltip": "Select a polygon layer." + "tooltip": "Select a polygon layer.", }, "Use CSV to Point Layer": { "label": "Use CSV File", "description": "Using this option, you can convert a CSV file to a point layer.", "type": "csv_to_point", - "tooltip": "Select a CSV file and specify longitude and latitude columns." + "tooltip": "Select a CSV file and specify longitude and latitude columns.", }, "Use Poly per Cell": { "label": "Use Polygon Layer", "description": "Using this option, create a polygon per grid cell.", "type": "layer_selector", "layer_type": "polygon", - "tooltip": "Select a polygon layer." + "tooltip": "Select a polygon layer.", }, "Use Polyline per Cell": { "label": "Use Polyline Layer", "description": "Using this option, create a polyline per grid cell.", "type": "layer_selector", "layer_type": "line", - "tooltip": "Select a line layer." + "tooltip": "Select a line layer.", }, "Use Point per Cell": { "label": "Use Points Layer", "description": "Using this option, create a point per grid cell.", "type": "layer_selector", "layer_type": "point", - "tooltip": "Select a point layer." + "tooltip": "Select a point layer.", }, "Use Rasterize Layer": { "label": "Rasterize Layer", "description": "Using this option, you can rasterize a vector layer.", "type": "layer_selector", "layer_type": "all", - "tooltip": "Select a raster layer to rasterize." + "tooltip": "Select a raster layer to rasterize.", }, - "Use OSM Downloader": { "label": "Fetch the data from OSM", "description": "Using this option, we will try to fetch the data needed for this indicator directly " - "from OSM.", + "from OSM.", "type": "download_option", - "tooltip": "Download data from OSM." + "tooltip": "Download data from OSM.", }, "Use WBL Downloader": { "label": "Fetch the data from WBL", "description": "Using this option, we will try to fetch the data needed for this indicator directly " - "from WBL.", + "from WBL.", "type": "download_option", - "tooltip": "Download data from WBL." + "tooltip": "Download data from WBL.", }, "Use Humdata Downloader": { "label": "Fetch the data from HumData", "description": "Using this option, we will try to fetch the data needed for this indicator directly " - "from HumData.", + "from HumData.", "type": "download_option", - "tooltip": "Download data from HumData." + "tooltip": "Download data from HumData.", }, "Use Mapillary Downloader": { "label": "Fetch the data from Mapillary", "description": "Using this option, we will try to fetch the data needed for this indicator directly " - "from Mapillary.", + "from Mapillary.", "type": "download_option", - "tooltip": "Download data from Mapillary." + "tooltip": "Download data from Mapillary.", }, "Use Other Downloader": { "label": "Fetch the data from specified source", "description": f"Using this option, we will try to fetch the data needed for this indicator directly " - f"from {layer_data.get('Use Other Downloader', '')}.", + f"from {layer_data.get('Use Other Downloader', '')}.", "type": "download_option", - "tooltip": f"Download data from {layer_data.get('Use Other Downloader', 'Other Source')}." - } + "tooltip": f"Download data from {layer_data.get('Use Other Downloader', 'Other Source')}.", + }, } - use_keys_enabled = {k: v for k, v in layer_data.items() if k.startswith("Use") and v} + use_keys_enabled = { + k: v for k, v in layer_data.items() if k.startswith("Use") and v + } if not use_keys_enabled: return QWidget() @@ -239,11 +240,15 @@ def create_specific_widget(mapping: dict, layer_data: dict): elif widget_type == "multibuffer": container = QWidget() - main_layout = QVBoxLayout() # Change to QVBoxLayout for overall vertical arrangement + main_layout = ( + QVBoxLayout() + ) # Change to QVBoxLayout for overall vertical arrangement container.setLayout(main_layout) # Container for travel mode and measurement radio buttons - radio_buttons_container = QHBoxLayout() # Use QHBoxLayout to place them side by side + radio_buttons_container = ( + QHBoxLayout() + ) # Use QHBoxLayout to place them side by side # Left VBox for Travel Mode left_vbox = QVBoxLayout() @@ -325,11 +330,15 @@ def create_specific_widget(mapping: dict, layer_data: dict): elif subtype_mapped == "point": widget.setFilters(QgsMapLayerProxyModel.PointLayer) else: - print(f"Invalid layer subtype '{layer_type}' for '{mapping.get('label')}'. Defaulting to all " - f"vector layers.") + print( + f"Invalid layer subtype '{layer_type}' for '{mapping.get('label')}'. Defaulting to all " + f"vector layers." + ) widget.setFilters(QgsMapLayerProxyModel.VectorLayer) else: - print(f"Unknown layer type '{layer_type}' for '{mapping.get('label')}'. Defaulting to all layers.") + print( + f"Unknown layer type '{layer_type}' for '{mapping.get('label')}'. Defaulting to all layers." + ) widget.setFilters(QgsMapLayerProxyModel.All) # Check if layers are available @@ -356,7 +365,11 @@ def create_specific_widget(mapping: dict, layer_data: dict): file_widget = QgsFileWidget(parent=container) file_widget.setFilter("CSV Files (*.csv);;All Files (*.*)") file_widget.setToolTip( - mapping.get("tooltip", "Select a CSV file containing longitude and latitude columns.")) + mapping.get( + "tooltip", + "Select a CSV file containing longitude and latitude columns.", + ) + ) layout.addWidget(file_widget) # Create layouts for longitude and latitude columns @@ -391,7 +404,9 @@ def create_specific_widget(mapping: dict, layer_data: dict): # Connect file selection to populate and auto-fill combo boxes file_widget.fileChanged.connect( - lambda path: GeestWidgetFactory.populate_csv_columns(path, longitude_combo, latitude_combo) + lambda path: GeestWidgetFactory.populate_csv_columns( + path, longitude_combo, latitude_combo + ) ) return container @@ -406,7 +421,9 @@ def create_specific_widget(mapping: dict, layer_data: dict): return None @staticmethod - def populate_csv_columns(file_path: str, lon_combo: QComboBox, lat_combo: QComboBox): + def populate_csv_columns( + file_path: str, lon_combo: QComboBox, lat_combo: QComboBox + ): """ Populate the longitude and latitude combo boxes based on the CSV file's headers. Auto-select columns if 'longitude'/'lon' and 'latitude'/'lat' are found. @@ -417,7 +434,7 @@ def populate_csv_columns(file_path: str, lon_combo: QComboBox, lat_combo: QCombo return try: - with open(file_path, newline='', encoding='utf-8') as csvfile: + with open(file_path, newline="", encoding="utf-8") as csvfile: reader = csv.reader(csvfile) headers = next(reader) lon_combo.clear() @@ -426,15 +443,21 @@ def populate_csv_columns(file_path: str, lon_combo: QComboBox, lat_combo: QCombo lat_combo.addItems(headers) # Auto-select longitude column - lon_candidates = ['longitude', 'lon'] - selected_lon = next((header for header in headers if header.lower() in lon_candidates), None) + lon_candidates = ["longitude", "lon"] + selected_lon = next( + (header for header in headers if header.lower() in lon_candidates), + None, + ) if selected_lon: index = headers.index(selected_lon) lon_combo.setCurrentIndex(index) # Auto-select latitude column - lat_candidates = ['latitude', 'lat'] - selected_lat = next((header for header in headers if header.lower() in lat_candidates), None) + lat_candidates = ["latitude", "lat"] + selected_lat = next( + (header for header in headers if header.lower() in lat_candidates), + None, + ) if selected_lat: index = headers.index(selected_lat) lat_combo.setCurrentIndex(index) diff --git a/test/test_polygons_per_grid_cell.py b/test/test_polygons_per_grid_cell.py index 72d0e77..a11444f 100644 --- a/test/test_polygons_per_grid_cell.py +++ b/test/test_polygons_per_grid_cell.py @@ -29,9 +29,7 @@ def test_raster_polygon_grid_score(self): ) self.country_boundary = os.path.join(self.test_data_dir, "admin/Admin0.shp") - self.assertTrue( - self.polygon_layer.isValid(), "The polygon layer is not valid." - ) + self.assertTrue(self.polygon_layer.isValid(), "The polygon layer is not valid.") # Define output path for the generated raster self.output_path = os.path.join(