Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix #18: add skip logic #87

Merged
merged 6 commits into from
Dec 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions travel_time_platform_plugin/algorithms/advanced.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def initAlgorithm(self, config):
help_text=tr(
"Used to identify this specific search in the results array. MUST be unique among all searches."
),
depends_on="INPUT_" + DEPARR + "_SEARCHES",
)
# Transportation
self.addParameter(
Expand All @@ -116,6 +117,7 @@ def initAlgorithm(self, config):
help_text=tr(
"cycling, driving, driving+train (only in Great Britain), public_transport, walking, coach, bus, train, ferry, driving+ferry, cycling+ferry or cycling+public_transport (only in Netherlands)"
),
depends_on="INPUT_" + DEPARR + "_SEARCHES",
)
self.addParameter(
QgsProcessingParameterExpression(
Expand All @@ -129,6 +131,7 @@ def initAlgorithm(self, config):
help_text=tr(
"Time (in seconds) needed to board public transportation vehicle. Default is 0. Cannot be higher than travel_time. Used in public_transport, coach, bus, train, driving+train and cycling+public_transport transportation modes"
),
depends_on="INPUT_" + DEPARR + "_SEARCHES",
)
self.addParameter(
QgsProcessingParameterExpression(
Expand All @@ -142,6 +145,7 @@ def initAlgorithm(self, config):
help_text=tr(
"Maximum time (in seconds) of walking from source to a station/stop and from station/stop to destination. Default value is 900. Cannot be higher than travel_time. Used in public_transport, coach, bus, train, driving+train and cycling+public_transport transportation modes"
),
depends_on="INPUT_" + DEPARR + "_SEARCHES",
)
self.addParameter(
QgsProcessingParameterExpression(
Expand All @@ -157,6 +161,7 @@ def initAlgorithm(self, config):
help_text=tr(
"Maximum time (in seconds) of driving from source to train station. Default value is 1800. Cannot be higher than travel_time. Used in driving+train transportation mode"
),
depends_on="INPUT_" + DEPARR + "_SEARCHES",
)
self.addParameter(
QgsProcessingParameterExpression(
Expand All @@ -172,6 +177,7 @@ def initAlgorithm(self, config):
help_text=tr(
"Maximum time (in seconds) of cycling (including any ferry transfers) from source to a station or stop. Default value is 900. Cannot be higher than travel_time. Used in cycling+public_transport transportation mode"
),
depends_on="INPUT_" + DEPARR + "_SEARCHES",
)
self.addParameter(
QgsProcessingParameterExpression(
Expand All @@ -185,6 +191,7 @@ def initAlgorithm(self, config):
help_text=tr(
"Time (in seconds) required to park a car or a bike. Default is 300. Cannot be higher than travel_time. Used in driving+train and cycling+public_transport transportation modes."
),
depends_on="INPUT_" + DEPARR + "_SEARCHES",
)
self.addParameter(
QgsProcessingParameterExpression(
Expand All @@ -198,6 +205,7 @@ def initAlgorithm(self, config):
help_text=tr(
"Time (in seconds) required to board a ferry. Default is 0. Cannot be higher than travel_time. Used in public_transport, ferry, driving+ferry, cycling+ferry and cycling+public_transport transportation modes. For public_transport mode, pt_change_delay is used instead"
),
depends_on="INPUT_" + DEPARR + "_SEARCHES",
)
self.addParameter(
QgsProcessingParameterExpression(
Expand All @@ -211,6 +219,7 @@ def initAlgorithm(self, config):
help_text=tr(
"Search range width in seconds. width along with departure_time specify departure interval. For example, if you set departure_time to 9am and width to 1 hour, we will return a combined shape of all possible journeys that have departure time between 9am and 10am. Range width is limited to 12 hours"
),
depends_on="INPUT_" + DEPARR + "_SEARCHES",
)
self.addParameter(
QgsProcessingParameterExpression(
Expand All @@ -223,6 +232,7 @@ def initAlgorithm(self, config):
help_text=tr(
"Leave departure location at no earlier than given time. Example - 2017-10-18T08:00:00Z"
),
depends_on="INPUT_" + DEPARR + "_SEARCHES",
)
self.addParameter(
QgsProcessingParameterExpression(
Expand All @@ -235,6 +245,7 @@ def initAlgorithm(self, config):
help_text=tr(
"Travel time in seconds. Maximum value is 14400 (4 hours)"
),
depends_on="INPUT_" + DEPARR + "_SEARCHES",
)
self.addParameter(
QgsProcessingParameterNumber(
Expand Down Expand Up @@ -491,6 +502,7 @@ def initAlgorithm(self, config):
help_text=tr(
"Set which fields should be joined back in the output layer."
),
depends_on="INPUT_" + DEPARR + "_SEARCHES",
)
self.addParameter(
QgsProcessingParameterExpression(
Expand All @@ -504,6 +516,7 @@ def initAlgorithm(self, config):
help_text=tr(
"Specifies level of detail of returned shape using scale type `simple`. Allowed values: lowest, low, medium, high, highest."
),
depends_on="INPUT_" + DEPARR + "_SEARCHES",
)
self.addParameter(
QgsProcessingParameterExpression(
Expand All @@ -517,6 +530,7 @@ def initAlgorithm(self, config):
help_text=tr(
"Enable to return only one shape from the search results. The returned shape will be approximately the biggest one among search results. Note that this will likely result in loss in accuracy."
),
depends_on="INPUT_" + DEPARR + "_SEARCHES",
)
self.addParameter(
QgsProcessingParameterExpression(
Expand All @@ -530,6 +544,7 @@ def initAlgorithm(self, config):
help_text=tr(
"Enable to remove holes from returned polygons. Note that this will likely result in loss in accuracy."
),
depends_on="INPUT_" + DEPARR + "_SEARCHES",
)

# Define additional input parameters
Expand Down
23 changes: 20 additions & 3 deletions travel_time_platform_plugin/algorithms/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
)
from qgis.PyQt.QtCore import QSettings
from qgis.PyQt.QtTest import QTest
from qgis.utils import iface

from .. import auth, cache, constants
from ..libraries import iso3166
from ..ui import AlgorithmDialogWithSkipLogic
from ..utils import log, throttler, tr

EPSG4326 = QgsCoordinateReferenceSystem("EPSG:4326")
Expand All @@ -42,14 +44,24 @@ def __init__(self, *args, **kwargs):
True: collections.OrderedDict(),
False: collections.OrderedDict(),
}

def addParameter(self, parameter, advanced=False, help_text=None, *args, **kwargs):
"""Helper to add parameters with help texts"""
self.skip_logic = {}

def addParameter(
self,
parameter,
advanced=False,
help_text=None,
depends_on=None,
*args,
**kwargs,
):
"""Helper to add parameters with help texts and skip logic"""
if advanced:
parameter.setFlags(
parameter.flags() | QgsProcessingParameterDefinition.FlagAdvanced
)
self.parameters_help[advanced][parameter.description()] = help_text
self.skip_logic[parameter.name()] = depends_on

return super().addParameter(parameter, *args, **kwargs)

Expand Down Expand Up @@ -175,6 +187,11 @@ def serialize(o):
def createInstance(self):
return self.__class__()

def createCustomParametersWidget(self, parent):
if parent is None:
parent = iface.mainWindow()
return AlgorithmDialogWithSkipLogic(self, parent=parent)

# Cosmetic methods to allow less verbose definition of these propreties in child classes

def name(self):
Expand Down
72 changes: 71 additions & 1 deletion travel_time_platform_plugin/tests/tests_misc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from qgis.PyQt.QtWidgets import QDockWidget, QTreeView
from processing import createAlgorithmDialog
from qgis.core import QgsProcessingContext
from qgis.PyQt.QtWidgets import QApplication, QDockWidget, QTreeView, QWidget
from qgis.utils import iface

from ..utils import log
from .base import TestCaseBase


Expand Down Expand Up @@ -38,3 +41,70 @@ def test_loading_map_tiles(self):
model.data(treeview.currentIndex()),
"TravelTime - Lux",
)

def test_skip_logic(self):
dialog = createAlgorithmDialog("ttp_v4:time_map", {})
params_widget = dialog.mainWidget()
advanced_groupbox = params_widget.findChild(QWidget, "grpAdvanced")

def assert_visibility(field_name: str, expected_visibility: bool):
label = params_widget.wrappers[field_name].wrappedLabel()
widget = params_widget.wrappers[field_name].wrappedWidget()
self.assertEqual(label.isVisibleTo(dialog), expected_visibility)
self.assertEqual(widget.isVisibleTo(dialog), expected_visibility)

def set_value(field_name: str, value: str):
wrapper = params_widget.wrappers[field_name]
log(f"Value was {wrapper.widgetValue()}")
wrapper.setWidgetValue(value, QgsProcessingContext())
QApplication.processEvents()

def toggle_advanced():
advanced_groupbox.toggleCollapsed()
QApplication.processEvents()

# show the dialog
dialog.show()

# initially, dependent fields should be hidden
assert_visibility("INPUT_DEPARTURE_ID", False)
assert_visibility("INPUT_DEPARTURE_EXISTING_FIELDS_TO_KEEP", False)

# close and reopen advanced groupbox
toggle_advanced()
toggle_advanced()

# this should have no effect
assert_visibility("INPUT_DEPARTURE_ID", False)
assert_visibility("INPUT_DEPARTURE_EXISTING_FIELDS_TO_KEEP", False)

# we set a value for departure searches
set_value("INPUT_DEPARTURE_SEARCHES", "something")

# dependent fields should now be visible
assert_visibility("INPUT_DEPARTURE_ID", True)
assert_visibility("INPUT_DEPARTURE_EXISTING_FIELDS_TO_KEEP", True)

# close and reopen advanced groupbox
toggle_advanced()
toggle_advanced()

# this should have no effect
assert_visibility("INPUT_DEPARTURE_ID", True)
assert_visibility("INPUT_DEPARTURE_EXISTING_FIELDS_TO_KEEP", True)

# FIXME: setting an empty value has no effect, and couldn't find a workaround, meaning we stop the test here
# unset a value for departure searches
# set_value("INPUT_DEPARTURE_SEARCHES", "")

# dependent fields should be hidden again
# assert_visibility("INPUT_DEPARTURE_ID", False)
# assert_visibility("INPUT_DEPARTURE_EXISTING_FIELDS_TO_KEEP", False)

# close and reopen advanced groupbox
# toggle_advanced()
# toggle_advanced()

# this should have no effect
# assert_visibility("INPUT_DEPARTURE_ID", False)
# assert_visibility("INPUT_DEPARTURE_EXISTING_FIELDS_TO_KEEP", False)
45 changes: 45 additions & 0 deletions travel_time_platform_plugin/ui.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import os
import webbrowser

from processing.gui.AlgorithmDialog import AlgorithmDialog
from processing.gui.ParametersPanel import ParametersPanel
from qgis.gui import QgsAbstractProcessingParameterWidgetWrapper as Wrapper
from qgis.PyQt import uic
from qgis.PyQt.QtCore import QDate, QDateTime, QSettings, Qt, QTime, QUrl
from qgis.PyQt.QtWidgets import QDateTimeEdit, QDialog, QWidget
Expand Down Expand Up @@ -216,3 +219,45 @@ def setValue(self, value):

def value(self):
return self.widget.dateTime().toString(Qt.ISODate)


class AlgorithmDialogWithSkipLogic(AlgorithmDialog):
def getParametersPanel(self, alg, parent):
panel = ParametersPanel(parent, alg, self.in_place, self.active_layer)
skip_logic = self.algorithm().skip_logic

# callable that toggles visibility of a field's widget depending on another field
def toggler(wrapper: Wrapper, depends_on: Wrapper):
truthy = bool(depends_on.widgetValue())
wrapper.wrappedLabel().setVisible(truthy)
wrapper.wrappedWidget().setVisible(truthy)

# callable that toggles all the fields for initialisation
def toggle_all():
for field_name, wrapper in panel.wrappers.items():
depends_on_name = skip_logic.get(field_name, None)
if not depends_on_name:
continue
depends_on = panel.wrappers[depends_on_name]
toggler(wrapper, depends_on=depends_on)

# connect signals on source fields for skip logic
for field_name, wrapper in panel.wrappers.items():
depends_on_name = skip_logic[field_name]
if not depends_on_name:
continue
depends_on = panel.wrappers[depends_on_name]
depends_on.widgetValueHasChanged.connect(
lambda depends_on, wrapper=wrapper: toggler(
wrapper, depends_on=depends_on
)
)

# toggling groupbox resets it's children visibility, so we reinitialise it
advanced_groupbox = panel.findChild(QWidget, "grpAdvanced")
advanced_groupbox.collapsedStateChanged.connect(lambda _: toggle_all())

# initialise for opening state
toggle_all()

return panel