Skip to content
Permalink
Browse files

[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.
  • Loading branch information
elpaso committed Oct 4, 2018
1 parent 223a87f commit ca1c65d07a59e219d5b3ce020cf365f5710f17af
@@ -233,13 +233,13 @@ def initGui(self):

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 @@ def initGui(self):
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,19 +73,19 @@ 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
:type active_layer: QgsVectoLayer
: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 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 alg.supportInPlaceEdit(active_layer):
raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))
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,16 +217,14 @@ 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, {}


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 @@ def fetchResults(self, string, context, feedback):
# 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 @@ def _alg_tester(self, alg_name, input_layer, parameters):
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 @@ def test_execute_in_place_run(self):
}
)

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"""

0 comments on commit ca1c65d

Please sign in to comment.
You can’t perform that action at this time.