Skip to content

Commit 681d44f

Browse files
committedSep 14, 2018
Messy mockup of feature
1 parent 34e29b5 commit 681d44f

19 files changed

+301
-14
lines changed
 

‎python/core/auto_generated/processing/qgsprocessingalgorithm.sip.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Abstract base class for processing algorithms.
4444
FlagRequiresMatchingCrs,
4545
FlagNoThreading,
4646
FlagDisplayNameIsLiteral,
47+
FlagSupportsInPlaceEdits,
4748
FlagDeprecated,
4849
};
4950
typedef QFlags<QgsProcessingAlgorithm::Flag> Flags;
@@ -853,6 +854,9 @@ algorithm in "chains", avoiding the need for temporary outputs in multi-step mod
853854
Constructor for QgsProcessingFeatureBasedAlgorithm.
854855
%End
855856

857+
virtual QgsProcessingAlgorithm::Flags flags() const;
858+
859+
856860
protected:
857861

858862
virtual void initAlgorithm( const QVariantMap &configuration = QVariantMap() );

‎python/gui/auto_generated/processing/qgsprocessingtoolboxmodel.sip.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ the results.
379379
{
380380
FilterToolbox,
381381
FilterModeler,
382+
FilterInPlace,
382383
};
383384
typedef QFlags<QgsProcessingToolboxProxyModel::Filter> Filters;
384385

@@ -417,6 +418,8 @@ Returns any filters that affect how toolbox content is filtered.
417418
.. seealso:: :py:func:`setFilters`
418419
%End
419420

421+
void setInPlaceLayerType( QgsWkbTypes::GeometryType type );
422+
420423
void setFilterString( const QString &filter );
421424
%Docstring
422425
Sets a ``filter`` string, such that only algorithms matching the

‎python/gui/auto_generated/processing/qgsprocessingtoolboxtreeview.sip.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ if no algorithm is currently selected.
7373
Sets ``filters`` controlling the view's contents.
7474
%End
7575

76+
void setInPlaceLayerType( QgsWkbTypes::GeometryType type );
77+
7678
public slots:
7779

7880
void setFilterString( const QString &filter );

‎python/plugins/processing/ProcessingPlugin.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
QgsDataItemProvider,
3636
QgsDataProvider,
3737
QgsDataItem,
38+
QgsMapLayer,
3839
QgsMimeDataUtils)
3940
from qgis.gui import (QgsOptionsWidgetFactory,
4041
QgsCustomDropHandler)
@@ -48,7 +49,8 @@
4849
from processing.gui.HistoryDialog import HistoryDialog
4950
from processing.gui.ConfigDialog import ConfigOptionsPage
5051
from processing.gui.ResultsDock import ResultsDock
51-
from processing.gui.AlgorithmLocatorFilter import AlgorithmLocatorFilter
52+
from processing.gui.AlgorithmLocatorFilter import (AlgorithmLocatorFilter,
53+
InPlaceAlgorithmLocatorFilter)
5254
from processing.modeler.ModelerDialog import ModelerDialog
5355
from processing.tools.system import tempHelpFolder
5456
from processing.gui.menus import removeMenus, initializeMenus, createMenus
@@ -168,6 +170,8 @@ def __init__(self, iface):
168170
QgsApplication.dataItemProviderRegistry().addProvider(self.item_provider)
169171
self.locator_filter = AlgorithmLocatorFilter()
170172
iface.registerLocatorFilter(self.locator_filter)
173+
self.edit_features_locator_filter = InPlaceAlgorithmLocatorFilter()
174+
iface.registerLocatorFilter(self.edit_features_locator_filter)
171175
Processing.initialize()
172176

173177
def initGui(self):
@@ -233,6 +237,14 @@ def initGui(self):
233237
self.optionsAction.triggered.connect(self.openProcessingOptions)
234238
self.toolbox.processingToolbar.addAction(self.optionsAction)
235239

240+
self.editSelectedAction = QAction(
241+
QgsApplication.getThemeIcon("/mActionToggleEditing.svg"),
242+
self.tr('Edit Selected Features'), self.iface.mainWindow())
243+
self.editSelectedAction.setObjectName('editSelectedFeatures')
244+
self.editSelectedAction.setCheckable(True)
245+
self.editSelectedAction.toggled.connect(self.editSelected)
246+
self.toolbox.processingToolbar.addAction(self.editSelectedAction)
247+
236248
menuBar = self.iface.mainWindow().menuBar()
237249
menuBar.insertMenu(
238250
self.iface.firstRightStandardMenu().menuAction(), self.menu)
@@ -242,6 +254,15 @@ def initGui(self):
242254
initializeMenus()
243255
createMenus()
244256

257+
self.iface.currentLayerChanged.connect(self.layer_changed)
258+
259+
def layer_changed(self, layer):
260+
if layer is None or layer.type() != QgsMapLayer.VectorLayer or not layer.isEditable() or not layer.selectedFeatureCount():
261+
self.editSelectedAction.setChecked(False)
262+
self.editSelectedAction.setEnabled(False)
263+
else:
264+
self.editSelectedAction.setEnabled(True)
265+
245266
def openProcessingOptions(self):
246267
self.iface.showOptionsDialog(self.iface.mainWindow(), currentPage='processingOptions')
247268

@@ -273,6 +294,7 @@ def unload(self):
273294

274295
self.iface.unregisterOptionsWidgetFactory(self.options_factory)
275296
self.iface.deregisterLocatorFilter(self.locator_filter)
297+
self.iface.deregisterLocatorFilter(self.edit_features_locator_filter)
276298
self.iface.unregisterCustomDropHandler(self.drop_handler)
277299
QgsApplication.dataItemProviderRegistry().removeProvider(self.item_provider)
278300

@@ -306,3 +328,6 @@ def openHistory(self):
306328

307329
def tr(self, message):
308330
return QCoreApplication.translate('ProcessingPlugin', message)
331+
332+
def editSelected(self, enabled):
333+
self.toolbox.set_in_place_edit_mode(enabled)

‎python/plugins/processing/algs/qgis/AddTableField.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from qgis.PyQt.QtCore import QVariant
2929
from qgis.core import (QgsField,
3030
QgsProcessing,
31+
QgsProcessingAlgorithm,
3132
QgsProcessingParameterString,
3233
QgsProcessingParameterNumber,
3334
QgsProcessingParameterEnum,
@@ -57,6 +58,9 @@ def __init__(self):
5758
self.tr('String')]
5859
self.field = None
5960

61+
def flags(self):
62+
return super().flags() & ~QgsProcessingAlgorithm.FlagSupportsInPlaceEdits
63+
6064
def initParameters(self, config=None):
6165
self.addParameter(QgsProcessingParameterString(self.FIELD_NAME,
6266
self.tr('Field name')))

‎python/plugins/processing/algs/qgis/DeleteColumn.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
from qgis.core import (QgsProcessingParameterField,
3131
QgsProcessing,
32+
QgsProcessingAlgorithm,
3233
QgsProcessingFeatureSource)
3334
from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm
3435

@@ -37,6 +38,9 @@ class DeleteColumn(QgisFeatureBasedAlgorithm):
3738

3839
COLUMNS = 'COLUMN'
3940

41+
def flags(self):
42+
return super().flags() & ~QgsProcessingAlgorithm.FlagSupportsInPlaceEdits
43+
4044
def tags(self):
4145
return self.tr('drop,delete,remove,fields,columns,attributes').split(',')
4246

‎python/plugins/processing/algs/qgis/GeometryByExpression.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
QgsExpression,
3030
QgsGeometry,
3131
QgsProcessing,
32+
QgsProcessingAlgorithm,
3233
QgsProcessingException,
3334
QgsProcessingParameterBoolean,
3435
QgsProcessingParameterEnum,
@@ -51,6 +52,9 @@ def group(self):
5152
def groupId(self):
5253
return 'vectorgeometry'
5354

55+
def flags(self):
56+
return super().flags() & ~QgsProcessingAlgorithm.FlagSupportsInPlaceEdits
57+
5458
def __init__(self):
5559
super().__init__()
5660
self.geometry_types = [self.tr('Polygon'),

‎python/plugins/processing/gui/AlgorithmDialog.py

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
from processing.gui.ParametersPanel import ParametersPanel
5858
from processing.gui.BatchAlgorithmDialog import BatchAlgorithmDialog
5959
from processing.gui.AlgorithmDialogBase import AlgorithmDialogBase
60-
from processing.gui.AlgorithmExecutor import executeIterating, execute
60+
from processing.gui.AlgorithmExecutor import executeIterating, execute, execute_in_place
6161
from processing.gui.Postprocessing import handleAlgorithmResults
6262
from processing.gui.wrappers import WidgetWrapper
6363

@@ -66,19 +66,25 @@
6666

6767
class AlgorithmDialog(QgsProcessingAlgorithmDialogBase):
6868

69-
def __init__(self, alg):
69+
def __init__(self, alg, in_place=False):
7070
super().__init__()
7171
self.feedback_dialog = None
72+
self.in_place = in_place
7273

7374
self.setAlgorithm(alg)
7475
self.setMainWidget(self.getParametersPanel(alg, self))
7576

76-
self.runAsBatchButton = QPushButton(QCoreApplication.translate("AlgorithmDialog", "Run as Batch Process…"))
77-
self.runAsBatchButton.clicked.connect(self.runAsBatch)
78-
self.buttonBox().addButton(self.runAsBatchButton, QDialogButtonBox.ResetRole) # reset role to ensure left alignment
77+
if not self.in_place:
78+
self.runAsBatchButton = QPushButton(QCoreApplication.translate("AlgorithmDialog", "Run as Batch Process…"))
79+
self.runAsBatchButton.clicked.connect(self.runAsBatch)
80+
self.buttonBox().addButton(self.runAsBatchButton, QDialogButtonBox.ResetRole) # reset role to ensure left alignment
81+
else:
82+
self.runAsBatchButton = None
83+
self.buttonBox().button(QDialogButtonBox.Ok).setText('Modify Selected Features')
84+
self.buttonBox().button(QDialogButtonBox.Close).setText('Cancel')
7985

8086
def getParametersPanel(self, alg, parent):
81-
return ParametersPanel(parent, alg)
87+
return ParametersPanel(parent, alg, self.in_place)
8288

8389
def runAsBatch(self):
8490
self.close()
@@ -118,10 +124,23 @@ def getParameterValues(self):
118124

119125
value = wrapper.parameterValue()
120126
parameters[param.name()] = value
127+
if self.in_place and param.name() == 'INPUT':
128+
parameters[param.name()] = iface.activeLayer()
129+
continue
130+
131+
wrapper = self.mainWidget().wrappers[param.name()]
132+
value = None
133+
if wrapper.widget is not None:
134+
value = wrapper.value()
135+
parameters[param.name()] = value
121136

122137
if not param.checkValueIsAcceptable(value):
123138
raise AlgorithmDialogBase.InvalidParameterValue(param, widget)
124139
else:
140+
if self.in_place and param.name() == 'OUTPUT':
141+
parameters[param.name()] = 'memory:'
142+
continue
143+
125144
dest_project = None
126145
if not param.flags() & QgsProcessingParameterDefinition.FlagHidden and \
127146
isinstance(param, (QgsProcessingParameterRasterDestination,
@@ -226,9 +245,12 @@ def on_complete(ok, results):
226245

227246
self.cancelButton().setEnabled(False)
228247

229-
self.finish(ok, results, context, feedback)
248+
if not self.in_place:
249+
self.finish(ok, results, context, feedback)
250+
elif ok:
251+
self.close()
230252

231-
if not (self.algorithm().flags() & QgsProcessingAlgorithm.FlagNoThreading):
253+
if not self.in_place and not (self.algorithm().flags() & QgsProcessingAlgorithm.FlagNoThreading):
232254
# Make sure the Log tab is visible before executing the algorithm
233255
self.showLog()
234256

@@ -241,7 +263,10 @@ def on_complete(ok, results):
241263
feedback.progressChanged.connect(self.proxy_progress.setProxyProgress)
242264
self.feedback_dialog = self.createProgressDialog()
243265
self.feedback_dialog.show()
244-
ok, results = execute(self.algorithm(), parameters, context, feedback)
266+
if self.in_place:
267+
ok, results = execute_in_place(self.algorithm(), parameters, context, feedback)
268+
else:
269+
ok, results = execute(self.algorithm(), parameters, context, feedback)
245270
feedback.progressChanged.disconnect()
246271
self.proxy_progress.finalize(ok)
247272
on_complete(ok, results)

‎python/plugins/processing/gui/AlgorithmExecutor.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@
3333
QgsProcessingUtils,
3434
QgsMessageLog,
3535
QgsProcessingException,
36+
QgsProcessingFeatureSourceDefinition,
3637
QgsProcessingParameters)
3738
from processing.gui.Postprocessing import handleAlgorithmResults
3839
from processing.tools import dataobjects
40+
from qgis.utils import iface
3941

4042

4143
def execute(alg, parameters, context=None, feedback=None):
@@ -61,6 +63,40 @@ def execute(alg, parameters, context=None, feedback=None):
6163
return False, {}
6264

6365

66+
def execute_in_place(alg, parameters, context=None, feedback=None):
67+
68+
if feedback is None:
69+
feedback = QgsProcessingFeedback()
70+
if context is None:
71+
context = dataobjects.createContext(feedback)
72+
73+
parameters['INPUT'] = QgsProcessingFeatureSourceDefinition(iface.activeLayer().id(), True)
74+
75+
parameters['OUTPUT'] = 'memory:'
76+
77+
try:
78+
results, ok = alg.run(parameters, context, feedback)
79+
80+
layer = QgsProcessingUtils.mapLayerFromString(results['OUTPUT'], context)
81+
iface.activeLayer().beginEditCommand('Edit features')
82+
iface.activeLayer().deleteFeatures(iface.activeLayer().selectedFeatureIds())
83+
features = []
84+
for f in layer.getFeatures():
85+
features.append(f)
86+
iface.activeLayer().addFeatures(features)
87+
new_selection = [f.id() for f in features]
88+
iface.activeLayer().endEditCommand()
89+
#iface.activeLayer().selectByIds(new_selection)
90+
iface.activeLayer().triggerRepaint()
91+
92+
return ok, results
93+
except QgsProcessingException as e:
94+
QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical)
95+
if feedback is not None:
96+
feedback.reportError(e.msg)
97+
return False, {}
98+
99+
64100
def executeIterating(alg, parameters, paramToIter, context, feedback):
65101
# Generate all single-feature layers
66102
parameter_definition = alg.parameterDefinition(paramToIter)

‎python/plugins/processing/gui/AlgorithmLocatorFilter.py

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,16 @@
2828

2929
from qgis.core import (QgsApplication,
3030
QgsProcessingAlgorithm,
31+
QgsProcessingFeatureBasedAlgorithm,
3132
QgsLocatorFilter,
32-
QgsLocatorResult)
33+
QgsLocatorResult,
34+
QgsProcessing,
35+
QgsWkbTypes,
36+
QgsMapLayer,
37+
QgsFields)
3338
from processing.gui.MessageDialog import MessageDialog
3439
from processing.gui.AlgorithmDialog import AlgorithmDialog
40+
from processing.gui.AlgorithmExecutor import execute_in_place
3541
from qgis.utils import iface
3642

3743

@@ -101,3 +107,91 @@ def triggerResult(self, result):
101107
except:
102108
pass
103109
canvas.setMapTool(prevMapTool)
110+
111+
112+
class InPlaceAlgorithmLocatorFilter(QgsLocatorFilter):
113+
114+
def __init__(self, parent=None):
115+
super().__init__(parent)
116+
117+
def clone(self):
118+
return InPlaceAlgorithmLocatorFilter()
119+
120+
def name(self):
121+
return 'edit_features'
122+
123+
def displayName(self):
124+
return self.tr('Edit Selected Features')
125+
126+
def priority(self):
127+
return QgsLocatorFilter.Low
128+
129+
def prefix(self):
130+
return 'ef'
131+
132+
def flags(self):
133+
return QgsLocatorFilter.FlagFast
134+
135+
def fetchResults(self, string, context, feedback):
136+
# collect results in main thread, since this method is inexpensive and
137+
# accessing the processing registry/current layer is not thread safe
138+
139+
if iface.activeLayer() is None or iface.activeLayer().type() != QgsMapLayer.VectorLayer or not iface.activeLayer().selectedFeatureCount():
140+
return
141+
142+
for a in QgsApplication.processingRegistry().algorithms():
143+
if not a.flags() & QgsProcessingAlgorithm.FlagSupportsInPlaceEdits:
144+
continue
145+
146+
if a.inputLayerTypes() and \
147+
QgsProcessing.TypeVector not in a.inputLayerTypes() \
148+
and QgsProcessing.TypeVectorAnyGeometry not in a.inputLayerTypes() \
149+
and (QgsWkbTypes.geometryType(iface.activeLayer().wkbType()) == QgsWkbTypes.PolygonGeometry and QgsProcessing.TypeVectorPolygon not in a.inputLayerTypes() or
150+
QgsWkbTypes.geometryType(
151+
iface.activeLayer().wkbType()) == QgsWkbTypes.LineGeometry and QgsProcessing.TypeVectorLine not in a.inputLayerTypes() or
152+
QgsWkbTypes.geometryType(
153+
iface.activeLayer().wkbType()) == QgsWkbTypes.PointGeometry and QgsProcessing.TypeVectorPoint not in a.inputLayerTypes()):
154+
continue
155+
156+
if QgsLocatorFilter.stringMatches(a.displayName(), string) or [t for t in a.tags() if QgsLocatorFilter.stringMatches(t, string)] or \
157+
(context.usingPrefix and not string):
158+
result = QgsLocatorResult()
159+
result.filter = self
160+
result.displayString = a.displayName()
161+
result.icon = a.icon()
162+
result.userData = a.id()
163+
if string and QgsLocatorFilter.stringMatches(a.displayName(), string):
164+
result.score = float(len(string)) / len(a.displayName())
165+
else:
166+
result.score = 0
167+
self.resultFetched.emit(result)
168+
169+
def triggerResult(self, result):
170+
alg = QgsApplication.processingRegistry().createAlgorithmById(result.userData)
171+
if alg:
172+
ok, message = alg.canExecute()
173+
if not ok:
174+
dlg = MessageDialog()
175+
dlg.setTitle(self.tr('Missing dependency'))
176+
dlg.setMessage(message)
177+
dlg.exec_()
178+
return
179+
180+
if len(alg.parameterDefinitions()) > 2:
181+
# hack!!
182+
dlg = alg.createCustomParametersWidget(None)
183+
if not dlg:
184+
dlg = AlgorithmDialog(alg, True)
185+
canvas = iface.mapCanvas()
186+
prevMapTool = canvas.mapTool()
187+
dlg.show()
188+
dlg.exec_()
189+
if canvas.mapTool() != prevMapTool:
190+
try:
191+
canvas.mapTool().reset()
192+
except:
193+
pass
194+
canvas.setMapTool(prevMapTool)
195+
else:
196+
parameters = {}
197+
execute_in_place(alg, parameters)

‎python/plugins/processing/gui/ParametersPanel.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,10 @@ class ParametersPanel(BASE, WIDGET):
6666

6767
NOT_SELECTED = QCoreApplication.translate('ParametersPanel', '[Not selected]')
6868

69-
def __init__(self, parent, alg):
69+
def __init__(self, parent, alg, in_place=False):
7070
super(ParametersPanel, self).__init__(None)
7171
self.setupUi(self)
72+
self.in_place = in_place
7273

7374
self.grpAdvanced.hide()
7475

@@ -126,6 +127,9 @@ def initWidgets(self):
126127
if param.flags() & QgsProcessingParameterDefinition.FlagHidden:
127128
continue
128129

130+
if self.in_place and param.name() in ('INPUT', 'OUTPUT'):
131+
continue
132+
129133
if param.isDestination():
130134
continue
131135
else:
@@ -196,6 +200,9 @@ def initWidgets(self):
196200
if output.flags() & QgsProcessingParameterDefinition.FlagHidden:
197201
continue
198202

203+
if self.in_place and param.name() in ('INPUT', 'OUTPUT'):
204+
continue
205+
199206
label = QLabel(output.description())
200207
widget = DestinationSelectionPanel(output, self.alg)
201208
self.layoutMain.insertWidget(self.layoutMain.count() - 1, label)

‎python/plugins/processing/gui/ProcessingToolbox.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
from qgis.PyQt.QtCore import Qt, QCoreApplication
3434
from qgis.PyQt.QtWidgets import QToolButton, QMenu, QAction
3535
from qgis.utils import iface
36-
from qgis.core import (QgsApplication,
36+
from qgis.core import (QgsWkbTypes,
37+
QgsMapLayer,
38+
QgsApplication,
3739
QgsProcessingAlgorithm)
3840
from qgis.gui import (QgsGui,
3941
QgsDockWidget,
@@ -50,6 +52,7 @@
5052
from processing.gui.ProviderActions import (ProviderActions,
5153
ProviderContextMenuActions)
5254
from processing.tools import dataobjects
55+
from processing.gui.AlgorithmExecutor import execute_in_place
5356

5457
pluginPath = os.path.split(os.path.dirname(__file__))[0]
5558

@@ -71,6 +74,7 @@ class ProcessingToolbox(QgsDockWidget, WIDGET):
7174
def __init__(self):
7275
super(ProcessingToolbox, self).__init__(None)
7376
self.tipWasClosed = False
77+
self.in_place_mode = False
7478
self.setupUi(self)
7579
self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
7680
self.processingToolbar.setIconSize(iface.iconSize(True))
@@ -108,6 +112,20 @@ def openSettings(url):
108112
QgsApplication.processingRegistry().providerRemoved.connect(self.addProvider)
109113
QgsApplication.processingRegistry().providerRemoved.connect(self.removeProvider)
110114

115+
iface.currentLayerChanged.connect(self.layer_changed)
116+
117+
def set_in_place_edit_mode(self, enabled):
118+
if enabled:
119+
self.algorithmTree.setFilters(QgsProcessingToolboxProxyModel.Filters(QgsProcessingToolboxProxyModel.FilterToolbox | QgsProcessingToolboxProxyModel.FilterInPlace))
120+
else:
121+
self.algorithmTree.setFilters(QgsProcessingToolboxProxyModel.FilterToolbox)
122+
self.in_place_mode = enabled
123+
124+
def layer_changed(self, layer):
125+
if layer is None or layer.type() != QgsMapLayer.VectorLayer:
126+
return
127+
self.algorithmTree.setInPlaceLayerType(QgsWkbTypes.geometryType(layer.wkbType()))
128+
111129
def disabledProviders(self):
112130
showTip = ProcessingConfig.getSetting(ProcessingConfig.SHOW_PROVIDERS_TOOLTIP)
113131
if not showTip or self.tipWasClosed:
@@ -211,11 +229,17 @@ def executeAlgorithm(self):
211229
dlg.exec_()
212230
return
213231

232+
if self.in_place_mode and len(alg.parameterDefinitions()) <= 2:
233+
# hack
234+
parameters = {}
235+
execute_in_place(alg, parameters)
236+
return
237+
214238
if alg.countVisibleParameters() > 0:
215239
dlg = alg.createCustomParametersWidget(self)
216240

217241
if not dlg:
218-
dlg = AlgorithmDialog(alg)
242+
dlg = AlgorithmDialog(alg, self.in_place_mode)
219243
canvas = iface.mapCanvas()
220244
prevMapTool = canvas.mapTool()
221245
dlg.show()

‎src/core/locator/qgslocator.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const QList<QString> QgsLocator::CORE_FILTERS = QList<QString>() << QStringLiter
2929
<< QStringLiteral( "calculator" )
3030
<< QStringLiteral( "bookmarks" )
3131
<< QStringLiteral( "optionpages" );
32+
<< QStringLiteral( "edit_features" );
3233

3334
QgsLocator::QgsLocator( QObject *parent )
3435
: QObject( parent )

‎src/core/processing/qgsprocessingalgorithm.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,13 @@ bool QgsProcessingAlgorithm::createAutoOutputForParameter( QgsProcessingParamete
787787
// QgsProcessingFeatureBasedAlgorithm
788788
//
789789

790+
QgsProcessingAlgorithm::Flags QgsProcessingFeatureBasedAlgorithm::flags() const
791+
{
792+
Flags f = QgsProcessingAlgorithm::flags();
793+
f |= QgsProcessingAlgorithm::FlagSupportsInPlaceEdits;
794+
return f;
795+
}
796+
790797
void QgsProcessingFeatureBasedAlgorithm::initAlgorithm( const QVariantMap &config )
791798
{
792799
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ), inputLayerTypes() ) );

‎src/core/processing/qgsprocessingalgorithm.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class CORE_EXPORT QgsProcessingAlgorithm
7474
FlagRequiresMatchingCrs = 1 << 5, //!< Algorithm requires that all input layers have matching coordinate reference systems
7575
FlagNoThreading = 1 << 6, //!< Algorithm is not thread safe and cannot be run in a background thread, e.g. for algorithms which manipulate the current project, layer selections, or with external dependencies which are not thread-safe.
7676
FlagDisplayNameIsLiteral = 1 << 7, //!< Algorithm's display name is a static literal string, and should not be translated or automatically formatted. For use with algorithms named after commands, e.g. GRASS 'v.in.ogr'.
77+
FlagSupportsInPlaceEdits = 1 << 8, //!< Algorithm supports in-place editing
7778
FlagDeprecated = FlagHideFromToolbox | FlagHideFromModeler, //!< Algorithm is deprecated
7879
};
7980
Q_DECLARE_FLAGS( Flags, Flag )
@@ -861,6 +862,8 @@ class CORE_EXPORT QgsProcessingFeatureBasedAlgorithm : public QgsProcessingAlgor
861862
*/
862863
QgsProcessingFeatureBasedAlgorithm() = default;
863864

865+
QgsProcessingAlgorithm::Flags flags() const override;
866+
864867
protected:
865868

866869
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
@@ -970,6 +973,7 @@ class CORE_EXPORT QgsProcessingFeatureBasedAlgorithm : public QgsProcessingAlgor
970973
private:
971974

972975
std::unique_ptr< QgsProcessingFeatureSource > mSource;
976+
friend class QgsProcessingToolboxProxyModel;
973977

974978
};
975979

‎src/gui/processing/qgsprocessingtoolboxmodel.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,12 @@ void QgsProcessingToolboxProxyModel::setFilters( QgsProcessingToolboxProxyModel:
655655
invalidateFilter();
656656
}
657657

658+
void QgsProcessingToolboxProxyModel::setInPlaceLayerType( QgsWkbTypes::GeometryType type )
659+
{
660+
mInPlaceGeometryType = type;
661+
invalidateFilter();
662+
}
663+
658664
void QgsProcessingToolboxProxyModel::setFilterString( const QString &filter )
659665
{
660666
mFilterString = filter;
@@ -708,6 +714,32 @@ bool QgsProcessingToolboxProxyModel::filterAcceptsRow( int sourceRow, const QMod
708714
}
709715
}
710716

717+
if ( mFilters & FilterInPlace )
718+
{
719+
const bool supportsInPlace = sourceModel()->data( sourceIndex, QgsProcessingToolboxModel::RoleAlgorithmFlags ).toInt() & QgsProcessingAlgorithm::FlagSupportsInPlaceEdits;
720+
if ( !supportsInPlace )
721+
return false;
722+
723+
const QgsProcessingFeatureBasedAlgorithm *alg = dynamic_cast< const QgsProcessingFeatureBasedAlgorithm * >( mModel->algorithmForIndex( sourceIndex ) );
724+
725+
if ( !alg->inputLayerTypes().empty() &&
726+
!alg->inputLayerTypes().contains( QgsProcessing::TypeVector ) &&
727+
!alg->inputLayerTypes().contains( QgsProcessing::TypeVectorAnyGeometry ) &&
728+
( ( mInPlaceGeometryType == QgsWkbTypes::PolygonGeometry && !alg->inputLayerTypes().contains( QgsProcessing::TypeVectorPolygon ) ) ||
729+
( mInPlaceGeometryType == QgsWkbTypes::LineGeometry && !alg->inputLayerTypes().contains( QgsProcessing::TypeVectorLine ) ) ||
730+
( mInPlaceGeometryType == QgsWkbTypes::PointGeometry && !alg->inputLayerTypes().contains( QgsProcessing::TypeVectorPoint ) ) ) )
731+
return false;
732+
733+
QgsWkbTypes::Type type = QgsWkbTypes::Unknown;
734+
if ( mInPlaceGeometryType == QgsWkbTypes::PointGeometry )
735+
type = QgsWkbTypes::Point;
736+
else if ( mInPlaceGeometryType == QgsWkbTypes::LineGeometry )
737+
type = QgsWkbTypes::LineString;
738+
else if ( mInPlaceGeometryType == QgsWkbTypes::PolygonGeometry )
739+
type = QgsWkbTypes::Polygon;
740+
if ( QgsWkbTypes::geometryType( alg->outputWkbType( type ) ) != mInPlaceGeometryType )
741+
return false;
742+
}
711743
if ( mFilters & FilterModeler )
712744
{
713745
bool isHiddenFromModeler = sourceModel()->data( sourceIndex, QgsProcessingToolboxModel::RoleAlgorithmFlags ).toInt() & QgsProcessingAlgorithm::FlagHideFromModeler;

‎src/gui/processing/qgsprocessingtoolboxmodel.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ class GUI_EXPORT QgsProcessingToolboxProxyModel: public QSortFilterProxyModel
423423
{
424424
FilterToolbox = 1 << 1, //!< Filters out any algorithms and content which should not be shown in the toolbox
425425
FilterModeler = 1 << 2, //!< Filters out any algorithms and content which should not be shown in the modeler
426+
FilterInPlace = 1 << 3, //!< Only show algorithms which support in-place edits
426427
};
427428
Q_DECLARE_FLAGS( Filters, Filter )
428429
Q_FLAG( Filters )
@@ -459,6 +460,8 @@ class GUI_EXPORT QgsProcessingToolboxProxyModel: public QSortFilterProxyModel
459460
*/
460461
Filters filters() const { return mFilters; }
461462

463+
void setInPlaceLayerType( QgsWkbTypes::GeometryType type );
464+
462465
/**
463466
* Sets a \a filter string, such that only algorithms matching the
464467
* specified string will be shown.
@@ -485,6 +488,7 @@ class GUI_EXPORT QgsProcessingToolboxProxyModel: public QSortFilterProxyModel
485488
QgsProcessingToolboxModel *mModel = nullptr;
486489
Filters mFilters = nullptr;
487490
QString mFilterString;
491+
QgsWkbTypes::GeometryType mInPlaceGeometryType = QgsWkbTypes::UnknownGeometry;
488492
};
489493

490494

‎src/gui/processing/qgsprocessingtoolboxtreeview.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ void QgsProcessingToolboxTreeView::setFilters( QgsProcessingToolboxProxyModel::F
9393
mModel->setFilters( filters );
9494
}
9595

96+
void QgsProcessingToolboxTreeView::setInPlaceLayerType( QgsWkbTypes::GeometryType type )
97+
{
98+
mModel->setInPlaceLayerType( type );
99+
}
100+
96101
QModelIndex QgsProcessingToolboxTreeView::findFirstVisibleAlgorithm( const QModelIndex &parent )
97102
{
98103
for ( int r = 0; r < mModel->rowCount( parent ); ++r )

‎src/gui/processing/qgsprocessingtoolboxtreeview.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ class GUI_EXPORT QgsProcessingToolboxTreeView : public QTreeView
8585
*/
8686
void setFilters( QgsProcessingToolboxProxyModel::Filters filters );
8787

88+
void setInPlaceLayerType( QgsWkbTypes::GeometryType type );
89+
8890
public slots:
8991

9092
/**

0 commit comments

Comments
 (0)
Please sign in to comment.