commit 95bb5095c8c979afd0484b97ea56e0ab1adfe381
parent 8c22a5490b8306a038164278800dcfbd731195d0
Author: Anders Damsgaard <anders@adamsgaard.dk>
Date: Fri, 15 May 2026 22:52:47 +0200
feat(qgis): add import options dialog
Diffstat:
2 files changed, 202 insertions(+), 3 deletions(-)
diff --git a/tem_loader/tem_loader.py b/tem_loader/tem_loader.py
@@ -1,7 +1,17 @@
from pathlib import Path
from qgis.PyQt.QtCore import QMetaType
-from qgis.PyQt.QtWidgets import QAction, QFileDialog, QMessageBox
+from qgis.PyQt.QtWidgets import (
+ QAction,
+ QCheckBox,
+ QDialog,
+ QDialogButtonBox,
+ QFileDialog,
+ QFormLayout,
+ QMessageBox,
+ QSpinBox,
+ QVBoxLayout,
+)
from qgis.core import (
Qgis,
QgsCoordinateReferenceSystem,
@@ -96,6 +106,52 @@ def _write_geopackage_layer(rows, gpkg_path, layer_name, wkb_type, crs,
del writer
+class _ImportOptionsDialog(QDialog):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setWindowTitle('TEM Loader Options')
+
+ self._mask_checkbox = QCheckBox(
+ 'Mask out layers below depth of interest (DOI)'
+ )
+ self._mask_checkbox.setChecked(True)
+
+ self._opacity_spinbox = QSpinBox()
+ self._opacity_spinbox.setRange(0, 100)
+ self._opacity_spinbox.setSuffix('%')
+ self._opacity_spinbox.setValue(core.BELOW_DOI_OPACITY)
+ self._opacity_spinbox.setEnabled(self._mask_checkbox.isChecked())
+ self._mask_checkbox.toggled.connect(self._opacity_spinbox.setEnabled)
+
+ form = QFormLayout()
+ form.addRow('Opacity', self._opacity_spinbox)
+
+ buttons = QDialogButtonBox(
+ QDialogButtonBox.Ok | QDialogButtonBox.Cancel
+ )
+ buttons.accepted.connect(self.accept)
+ buttons.rejected.connect(self.reject)
+
+ layout = QVBoxLayout()
+ layout.addWidget(self._mask_checkbox)
+ layout.addLayout(form)
+ layout.addWidget(buttons)
+ self.setLayout(layout)
+
+ def options(self):
+ return {
+ 'mask_below_doi': self._mask_checkbox.isChecked(),
+ 'below_doi_opacity': self._opacity_spinbox.value(),
+ }
+
+
+def _exec_dialog(dialog):
+ execute = getattr(dialog, 'exec', None)
+ if execute is None:
+ execute = dialog.exec_
+ return execute()
+
+
class TEMLoaderPlugin:
def __init__(self, iface):
self.iface = iface
diff --git a/test/test_core.py b/test/test_core.py
@@ -434,8 +434,15 @@ class ProcessXYZTests(unittest.TestCase):
class PluginTests(unittest.TestCase):
def _import_plugin_module(self):
class FakeSignal:
- def connect(self, _callback):
- pass
+ def __init__(self):
+ self._callbacks = []
+
+ def connect(self, callback):
+ self._callbacks.append(callback)
+
+ def emit(self, *args):
+ for callback in self._callbacks:
+ callback(*args)
class FakeAction:
def __init__(self, *_args, **_kwargs):
@@ -455,6 +462,92 @@ class PluginTests(unittest.TestCase):
def warning(*args):
FakeMessageBox.warnings.append(args)
+ class FakeDialog:
+ Accepted = 1
+ Rejected = 0
+
+ def __init__(self, *_args, **_kwargs):
+ self.title = None
+ self.layout = None
+
+ def setWindowTitle(self, title):
+ self.title = title
+
+ def setLayout(self, layout):
+ self.layout = layout
+
+ def accept(self):
+ pass
+
+ def reject(self):
+ pass
+
+ class FakeCheckBox:
+ def __init__(self, text):
+ self.text = text
+ self._checked = False
+ self.toggled = FakeSignal()
+
+ def setChecked(self, checked):
+ checked = bool(checked)
+ changed = checked != self._checked
+ self._checked = checked
+ if changed:
+ self.toggled.emit(checked)
+
+ def isChecked(self):
+ return self._checked
+
+ class FakeDialogButtonBox:
+ Ok = 1
+ Cancel = 2
+
+ def __init__(self, buttons):
+ self.buttons = buttons
+ self.accepted = FakeSignal()
+ self.rejected = FakeSignal()
+
+ class FakeFormLayout:
+ def __init__(self):
+ self.rows = []
+
+ def addRow(self, label, widget):
+ self.rows.append((label, widget))
+
+ class FakeSpinBox:
+ def __init__(self):
+ self.minimum = None
+ self.maximum = None
+ self.suffix = None
+ self._value = None
+ self.enabled = True
+
+ def setRange(self, minimum, maximum):
+ self.minimum = minimum
+ self.maximum = maximum
+
+ def setSuffix(self, suffix):
+ self.suffix = suffix
+
+ def setValue(self, value):
+ self._value = value
+
+ def setEnabled(self, enabled):
+ self.enabled = bool(enabled)
+
+ def value(self):
+ return self._value
+
+ class FakeVBoxLayout:
+ def __init__(self):
+ self.items = []
+
+ def addWidget(self, widget):
+ self.items.append(widget)
+
+ def addLayout(self, layout):
+ self.items.append(layout)
+
class FakeQMetaType:
class Type:
QString = "QString"
@@ -466,8 +559,14 @@ class PluginTests(unittest.TestCase):
qtwidgets = types.ModuleType("qgis.PyQt.QtWidgets")
qtwidgets.QAction = FakeAction
+ qtwidgets.QCheckBox = FakeCheckBox
+ qtwidgets.QDialog = FakeDialog
+ qtwidgets.QDialogButtonBox = FakeDialogButtonBox
qtwidgets.QFileDialog = FakeFileDialog
+ qtwidgets.QFormLayout = FakeFormLayout
qtwidgets.QMessageBox = FakeMessageBox
+ qtwidgets.QSpinBox = FakeSpinBox
+ qtwidgets.QVBoxLayout = FakeVBoxLayout
class FakeQgis:
class WkbType:
@@ -694,6 +793,50 @@ class PluginTests(unittest.TestCase):
self.assertIn("bad.xyz", message_box.warnings[0][2])
self.assertIn("Row 3 has 4 columns, expected 6", message_box.warnings[0][2])
+ def test_import_options_dialog_defaults_and_options(self):
+ module, _, _ = self._import_plugin_module()
+
+ dialog = module._ImportOptionsDialog(object())
+
+ self.assertEqual(dialog.title, "TEM Loader Options")
+ self.assertEqual(
+ dialog._mask_checkbox.text,
+ "Mask out layers below depth of interest (DOI)",
+ )
+ self.assertEqual(dialog._opacity_spinbox.minimum, 0)
+ self.assertEqual(dialog._opacity_spinbox.maximum, 100)
+ self.assertEqual(dialog._opacity_spinbox.suffix, "%")
+ self.assertTrue(dialog._opacity_spinbox.enabled)
+ self.assertEqual(
+ dialog.options(),
+ {
+ "mask_below_doi": True,
+ "below_doi_opacity": module.core.BELOW_DOI_OPACITY,
+ },
+ )
+
+ dialog._opacity_spinbox.setValue(35)
+ dialog._mask_checkbox.setChecked(False)
+
+ self.assertFalse(dialog._opacity_spinbox.enabled)
+ self.assertEqual(
+ dialog.options(),
+ {
+ "mask_below_doi": False,
+ "below_doi_opacity": 35,
+ },
+ )
+
+ def test_exec_dialog_supports_exec_and_exec_apis(self):
+ module, _, _ = self._import_plugin_module()
+ exec_dialog = Mock()
+ exec_dialog.exec.return_value = module.QDialog.Accepted
+ exec_legacy_dialog = Mock(spec=["exec_"])
+ exec_legacy_dialog.exec_.return_value = module.QDialog.Rejected
+
+ self.assertEqual(module._exec_dialog(exec_dialog), module.QDialog.Accepted)
+ self.assertEqual(module._exec_dialog(exec_legacy_dialog), module.QDialog.Rejected)
+
def test_build_geopackage_uri_uses_layername(self):
module, _, _ = self._import_plugin_module()
gpkg_path = Mock()