mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-15 00:04:00 -04:00
[processing] In place editing triggers editing and select all
If the active layer is not editable, the executor will try to switch editing on. If there are no selected features, the executor will select all features before running.
This commit is contained in:
parent
223a87fbc5
commit
ca1c65d07a
@ -233,13 +233,13 @@ class ProcessingPlugin:
|
||||
|
||||
self.toolbox.processingToolbar.addSeparator()
|
||||
|
||||
self.editSelectedAction = QAction(
|
||||
self.editInPlaceAction = QAction(
|
||||
QgsApplication.getThemeIcon("/mActionProcessSelected.svg"),
|
||||
self.tr('Edit Selected Features'), self.iface.mainWindow())
|
||||
self.editSelectedAction.setObjectName('editSelectedFeatures')
|
||||
self.editSelectedAction.setCheckable(True)
|
||||
self.editSelectedAction.toggled.connect(self.editSelected)
|
||||
self.toolbox.processingToolbar.addAction(self.editSelectedAction)
|
||||
self.tr('Edit Features In-Place'), self.iface.mainWindow())
|
||||
self.editInPlaceAction.setObjectName('editInPlaceFeatures')
|
||||
self.editInPlaceAction.setCheckable(True)
|
||||
self.editInPlaceAction.toggled.connect(self.editSelected)
|
||||
self.toolbox.processingToolbar.addAction(self.editInPlaceAction)
|
||||
|
||||
self.toolbox.processingToolbar.addSeparator()
|
||||
|
||||
@ -266,18 +266,18 @@ class ProcessingPlugin:
|
||||
self.sync_in_place_button_state()
|
||||
|
||||
def sync_in_place_button_state(self, layer=None):
|
||||
"""Synchronise the button state with layer state and selection"""
|
||||
"""Synchronise the button state with layer state"""
|
||||
|
||||
if layer is None:
|
||||
layer = self.iface.activeLayer()
|
||||
|
||||
old_enabled_state = self.editSelectedAction.isEnabled()
|
||||
old_enabled_state = self.editInPlaceAction.isEnabled()
|
||||
|
||||
new_enabled_state = layer is not None and layer.type() == QgsMapLayer.VectorLayer and layer.isEditable() and layer.selectedFeatureCount()
|
||||
self.editSelectedAction.setEnabled(new_enabled_state)
|
||||
new_enabled_state = layer is not None and layer.type() == QgsMapLayer.VectorLayer
|
||||
self.editInPlaceAction.setEnabled(new_enabled_state)
|
||||
|
||||
if new_enabled_state != old_enabled_state:
|
||||
self.toolbox.set_in_place_edit_mode(new_enabled_state and self.editSelectedAction.isChecked())
|
||||
self.toolbox.set_in_place_edit_mode(new_enabled_state and self.editInPlaceAction.isChecked())
|
||||
|
||||
def openProcessingOptions(self):
|
||||
self.iface.showOptionsDialog(self.iface.mainWindow(), currentPage='processingOptions')
|
||||
|
@ -73,8 +73,6 @@ def execute(alg, parameters, context=None, feedback=None):
|
||||
def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=None, raise_exceptions=False):
|
||||
"""Executes an algorithm modifying features in-place in the input layer.
|
||||
|
||||
The input layer must be editable or an exception is raised.
|
||||
|
||||
:param alg: algorithm to run
|
||||
:type alg: QgsProcessingAlgorithm
|
||||
:param active_layer: the editable layer
|
||||
@ -82,10 +80,12 @@ def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=N
|
||||
:param parameters: parameters of the algorithm
|
||||
:type parameters: dict
|
||||
:param context: context, defaults to None
|
||||
:param context: QgsProcessingContext, optional
|
||||
:type context: QgsProcessingContext, optional
|
||||
:param feedback: feedback, defaults to None
|
||||
:param feedback: QgsProcessingFeedback, optional
|
||||
:raises QgsProcessingException: raised when the layer is not editable or the layer cannot be found in the current project
|
||||
:type feedback: QgsProcessingFeedback, optional
|
||||
:param raise_exceptions: useful for testing, if True exceptions are raised, normally exceptions will be forwarded to the feedback
|
||||
:type raise_exceptions: boo, default to False
|
||||
:raises QgsProcessingException: raised when there is no active layer, or it cannot be made editable
|
||||
:return: a tuple with true if success and results
|
||||
:rtype: tuple
|
||||
"""
|
||||
@ -95,14 +95,41 @@ def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=N
|
||||
if context is None:
|
||||
context = dataobjects.createContext(feedback)
|
||||
|
||||
if active_layer is None or not active_layer.isEditable():
|
||||
raise QgsProcessingException(tr("Layer is not editable or layer is None."))
|
||||
# Run some checks and prepare the layer for in-place execution by:
|
||||
# - getting the active layer and checking that it is a vector
|
||||
# - making the layer editable if it was not already
|
||||
# - selecting all features if none was selected
|
||||
# - checking in-place support for the active layer/alg/parameters
|
||||
# If one of the check fails and raise_exceptions is True an exception
|
||||
# is raised, else the execution is aborted and the error reported in
|
||||
# the feedback
|
||||
try:
|
||||
if active_layer is None:
|
||||
raise QgsProcessingException(tr("There is not active layer."))
|
||||
|
||||
if not alg.supportInPlaceEdit(active_layer):
|
||||
raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))
|
||||
if not active_layer.isEditable():
|
||||
if not active_layer.startEditing():
|
||||
raise QgsProcessingException(tr("Active layer is not editable (and editing could not be turned on)."))
|
||||
|
||||
if not alg.supportInPlaceEdit(active_layer):
|
||||
raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))
|
||||
except QgsProcessingException as e:
|
||||
if raise_exceptions:
|
||||
raise e
|
||||
QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical)
|
||||
if feedback is not None:
|
||||
feedback.reportError(getattr(e, 'msg', str(e)), fatalError=True)
|
||||
return False, {}
|
||||
|
||||
if not active_layer.selectedFeatureIds():
|
||||
active_layer.selectAll()
|
||||
|
||||
parameters['OUTPUT'] = 'memory:'
|
||||
|
||||
# Start the execution
|
||||
# If anything goes wrong and raise_exceptions is True an exception
|
||||
# is raised, else the execution is aborted and the error reported in
|
||||
# the feedback
|
||||
try:
|
||||
new_feature_ids = []
|
||||
|
||||
@ -190,7 +217,7 @@ def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=N
|
||||
raise e
|
||||
QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical)
|
||||
if feedback is not None:
|
||||
feedback.reportError(getattr(e, 'msg', str(e)))
|
||||
feedback.reportError(getattr(e, 'msg', str(e)), fatalError=True)
|
||||
|
||||
return False, {}
|
||||
|
||||
@ -198,8 +225,6 @@ def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=N
|
||||
def execute_in_place(alg, parameters, context=None, feedback=None):
|
||||
"""Executes an algorithm modifying features in-place in the active layer.
|
||||
|
||||
The input layer must be editable or an exception is raised.
|
||||
|
||||
:param alg: algorithm to run
|
||||
:type alg: QgsProcessingAlgorithm
|
||||
:param parameters: parameters of the algorithm
|
||||
|
@ -137,7 +137,7 @@ class InPlaceAlgorithmLocatorFilter(QgsLocatorFilter):
|
||||
# collect results in main thread, since this method is inexpensive and
|
||||
# accessing the processing registry/current layer is not thread safe
|
||||
|
||||
if iface.activeLayer() is None or iface.activeLayer().type() != QgsMapLayer.VectorLayer or not iface.activeLayer().selectedFeatureCount() or not iface.activeLayer().isEditable():
|
||||
if iface.activeLayer() is None or iface.activeLayer().type() != QgsMapLayer.VectorLayer:
|
||||
return
|
||||
|
||||
for a in QgsApplication.processingRegistry().algorithms():
|
||||
|
@ -363,12 +363,7 @@ class TestQgsProcessingInPlace(unittest.TestCase):
|
||||
feedback = ConsoleFeedBack()
|
||||
|
||||
input_layer.rollBack()
|
||||
with self.assertRaises(QgsProcessingException) as cm:
|
||||
execute_in_place_run(
|
||||
alg, input_layer, parameters, context=context, feedback=feedback, raise_exceptions=True)
|
||||
|
||||
ok = False
|
||||
input_layer.startEditing()
|
||||
ok, _ = execute_in_place_run(
|
||||
alg, input_layer, parameters, context=context, feedback=feedback, raise_exceptions=True)
|
||||
new_features = [f for f in input_layer.getFeatures()]
|
||||
@ -439,6 +434,40 @@ class TestQgsProcessingInPlace(unittest.TestCase):
|
||||
}
|
||||
)
|
||||
|
||||
def test_select_all_features(self):
|
||||
"""Check that if there is no selection, the alg will run on all features"""
|
||||
|
||||
self.vl.rollBack()
|
||||
self.vl.removeSelection()
|
||||
old_count = self.vl.featureCount()
|
||||
|
||||
context = QgsProcessingContext()
|
||||
context.setProject(QgsProject.instance())
|
||||
feedback = ConsoleFeedBack()
|
||||
|
||||
alg = self.registry.createAlgorithmById('native:translategeometry')
|
||||
|
||||
self.assertIsNotNone(alg)
|
||||
|
||||
parameters = {
|
||||
'DELTA_X': 1.1,
|
||||
'DELTA_Y': 1.1,
|
||||
}
|
||||
parameters['INPUT'] = QgsProcessingFeatureSourceDefinition(
|
||||
self.vl.id(), True)
|
||||
parameters['OUTPUT'] = 'memory:'
|
||||
|
||||
old_features = [f for f in self.vl.getFeatures()]
|
||||
|
||||
ok, _ = execute_in_place_run(
|
||||
alg, self.vl, parameters, context=context, feedback=feedback, raise_exceptions=True)
|
||||
new_features = [f for f in self.vl.getFeatures()]
|
||||
|
||||
self.assertEqual(len(new_features), old_count)
|
||||
|
||||
# Check all are selected
|
||||
self.assertEqual(len(self.vl.selectedFeatureIds()), old_count)
|
||||
|
||||
def test_multi_to_single(self):
|
||||
"""Check that the geometry type is still multi after the alg is run"""
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user