Messy mockup of feature

This commit is contained in:
Nyall Dawson 2018-07-23 13:58:11 +10:00
parent 34e29b545b
commit 681d44f11f
19 changed files with 301 additions and 14 deletions

View File

@ -44,6 +44,7 @@ Abstract base class for processing algorithms.
FlagRequiresMatchingCrs, FlagRequiresMatchingCrs,
FlagNoThreading, FlagNoThreading,
FlagDisplayNameIsLiteral, FlagDisplayNameIsLiteral,
FlagSupportsInPlaceEdits,
FlagDeprecated, FlagDeprecated,
}; };
typedef QFlags<QgsProcessingAlgorithm::Flag> Flags; typedef QFlags<QgsProcessingAlgorithm::Flag> Flags;
@ -853,6 +854,9 @@ algorithm in "chains", avoiding the need for temporary outputs in multi-step mod
Constructor for QgsProcessingFeatureBasedAlgorithm. Constructor for QgsProcessingFeatureBasedAlgorithm.
%End %End
virtual QgsProcessingAlgorithm::Flags flags() const;
protected: protected:
virtual void initAlgorithm( const QVariantMap &configuration = QVariantMap() ); virtual void initAlgorithm( const QVariantMap &configuration = QVariantMap() );

View File

@ -379,6 +379,7 @@ the results.
{ {
FilterToolbox, FilterToolbox,
FilterModeler, FilterModeler,
FilterInPlace,
}; };
typedef QFlags<QgsProcessingToolboxProxyModel::Filter> Filters; typedef QFlags<QgsProcessingToolboxProxyModel::Filter> Filters;
@ -417,6 +418,8 @@ Returns any filters that affect how toolbox content is filtered.
.. seealso:: :py:func:`setFilters` .. seealso:: :py:func:`setFilters`
%End %End
void setInPlaceLayerType( QgsWkbTypes::GeometryType type );
void setFilterString( const QString &filter ); void setFilterString( const QString &filter );
%Docstring %Docstring
Sets a ``filter`` string, such that only algorithms matching the Sets a ``filter`` string, such that only algorithms matching the

View File

@ -73,6 +73,8 @@ if no algorithm is currently selected.
Sets ``filters`` controlling the view's contents. Sets ``filters`` controlling the view's contents.
%End %End
void setInPlaceLayerType( QgsWkbTypes::GeometryType type );
public slots: public slots:
void setFilterString( const QString &filter ); void setFilterString( const QString &filter );

View File

@ -35,6 +35,7 @@ from qgis.core import (QgsApplication,
QgsDataItemProvider, QgsDataItemProvider,
QgsDataProvider, QgsDataProvider,
QgsDataItem, QgsDataItem,
QgsMapLayer,
QgsMimeDataUtils) QgsMimeDataUtils)
from qgis.gui import (QgsOptionsWidgetFactory, from qgis.gui import (QgsOptionsWidgetFactory,
QgsCustomDropHandler) QgsCustomDropHandler)
@ -48,7 +49,8 @@ from processing.gui.ProcessingToolbox import ProcessingToolbox
from processing.gui.HistoryDialog import HistoryDialog from processing.gui.HistoryDialog import HistoryDialog
from processing.gui.ConfigDialog import ConfigOptionsPage from processing.gui.ConfigDialog import ConfigOptionsPage
from processing.gui.ResultsDock import ResultsDock from processing.gui.ResultsDock import ResultsDock
from processing.gui.AlgorithmLocatorFilter import AlgorithmLocatorFilter from processing.gui.AlgorithmLocatorFilter import (AlgorithmLocatorFilter,
InPlaceAlgorithmLocatorFilter)
from processing.modeler.ModelerDialog import ModelerDialog from processing.modeler.ModelerDialog import ModelerDialog
from processing.tools.system import tempHelpFolder from processing.tools.system import tempHelpFolder
from processing.gui.menus import removeMenus, initializeMenus, createMenus from processing.gui.menus import removeMenus, initializeMenus, createMenus
@ -168,6 +170,8 @@ class ProcessingPlugin:
QgsApplication.dataItemProviderRegistry().addProvider(self.item_provider) QgsApplication.dataItemProviderRegistry().addProvider(self.item_provider)
self.locator_filter = AlgorithmLocatorFilter() self.locator_filter = AlgorithmLocatorFilter()
iface.registerLocatorFilter(self.locator_filter) iface.registerLocatorFilter(self.locator_filter)
self.edit_features_locator_filter = InPlaceAlgorithmLocatorFilter()
iface.registerLocatorFilter(self.edit_features_locator_filter)
Processing.initialize() Processing.initialize()
def initGui(self): def initGui(self):
@ -233,6 +237,14 @@ class ProcessingPlugin:
self.optionsAction.triggered.connect(self.openProcessingOptions) self.optionsAction.triggered.connect(self.openProcessingOptions)
self.toolbox.processingToolbar.addAction(self.optionsAction) self.toolbox.processingToolbar.addAction(self.optionsAction)
self.editSelectedAction = QAction(
QgsApplication.getThemeIcon("/mActionToggleEditing.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)
menuBar = self.iface.mainWindow().menuBar() menuBar = self.iface.mainWindow().menuBar()
menuBar.insertMenu( menuBar.insertMenu(
self.iface.firstRightStandardMenu().menuAction(), self.menu) self.iface.firstRightStandardMenu().menuAction(), self.menu)
@ -242,6 +254,15 @@ class ProcessingPlugin:
initializeMenus() initializeMenus()
createMenus() createMenus()
self.iface.currentLayerChanged.connect(self.layer_changed)
def layer_changed(self, layer):
if layer is None or layer.type() != QgsMapLayer.VectorLayer or not layer.isEditable() or not layer.selectedFeatureCount():
self.editSelectedAction.setChecked(False)
self.editSelectedAction.setEnabled(False)
else:
self.editSelectedAction.setEnabled(True)
def openProcessingOptions(self): def openProcessingOptions(self):
self.iface.showOptionsDialog(self.iface.mainWindow(), currentPage='processingOptions') self.iface.showOptionsDialog(self.iface.mainWindow(), currentPage='processingOptions')
@ -273,6 +294,7 @@ class ProcessingPlugin:
self.iface.unregisterOptionsWidgetFactory(self.options_factory) self.iface.unregisterOptionsWidgetFactory(self.options_factory)
self.iface.deregisterLocatorFilter(self.locator_filter) self.iface.deregisterLocatorFilter(self.locator_filter)
self.iface.deregisterLocatorFilter(self.edit_features_locator_filter)
self.iface.unregisterCustomDropHandler(self.drop_handler) self.iface.unregisterCustomDropHandler(self.drop_handler)
QgsApplication.dataItemProviderRegistry().removeProvider(self.item_provider) QgsApplication.dataItemProviderRegistry().removeProvider(self.item_provider)
@ -306,3 +328,6 @@ class ProcessingPlugin:
def tr(self, message): def tr(self, message):
return QCoreApplication.translate('ProcessingPlugin', message) return QCoreApplication.translate('ProcessingPlugin', message)
def editSelected(self, enabled):
self.toolbox.set_in_place_edit_mode(enabled)

View File

@ -28,6 +28,7 @@ __revision__ = '$Format:%H$'
from qgis.PyQt.QtCore import QVariant from qgis.PyQt.QtCore import QVariant
from qgis.core import (QgsField, from qgis.core import (QgsField,
QgsProcessing, QgsProcessing,
QgsProcessingAlgorithm,
QgsProcessingParameterString, QgsProcessingParameterString,
QgsProcessingParameterNumber, QgsProcessingParameterNumber,
QgsProcessingParameterEnum, QgsProcessingParameterEnum,
@ -57,6 +58,9 @@ class AddTableField(QgisFeatureBasedAlgorithm):
self.tr('String')] self.tr('String')]
self.field = None self.field = None
def flags(self):
return super().flags() & ~QgsProcessingAlgorithm.FlagSupportsInPlaceEdits
def initParameters(self, config=None): def initParameters(self, config=None):
self.addParameter(QgsProcessingParameterString(self.FIELD_NAME, self.addParameter(QgsProcessingParameterString(self.FIELD_NAME,
self.tr('Field name'))) self.tr('Field name')))

View File

@ -29,6 +29,7 @@ from qgis.PyQt.QtCore import QCoreApplication
from qgis.core import (QgsProcessingParameterField, from qgis.core import (QgsProcessingParameterField,
QgsProcessing, QgsProcessing,
QgsProcessingAlgorithm,
QgsProcessingFeatureSource) QgsProcessingFeatureSource)
from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm
@ -37,6 +38,9 @@ class DeleteColumn(QgisFeatureBasedAlgorithm):
COLUMNS = 'COLUMN' COLUMNS = 'COLUMN'
def flags(self):
return super().flags() & ~QgsProcessingAlgorithm.FlagSupportsInPlaceEdits
def tags(self): def tags(self):
return self.tr('drop,delete,remove,fields,columns,attributes').split(',') return self.tr('drop,delete,remove,fields,columns,attributes').split(',')

View File

@ -29,6 +29,7 @@ from qgis.core import (QgsWkbTypes,
QgsExpression, QgsExpression,
QgsGeometry, QgsGeometry,
QgsProcessing, QgsProcessing,
QgsProcessingAlgorithm,
QgsProcessingException, QgsProcessingException,
QgsProcessingParameterBoolean, QgsProcessingParameterBoolean,
QgsProcessingParameterEnum, QgsProcessingParameterEnum,
@ -51,6 +52,9 @@ class GeometryByExpression(QgisFeatureBasedAlgorithm):
def groupId(self): def groupId(self):
return 'vectorgeometry' return 'vectorgeometry'
def flags(self):
return super().flags() & ~QgsProcessingAlgorithm.FlagSupportsInPlaceEdits
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.geometry_types = [self.tr('Polygon'), self.geometry_types = [self.tr('Polygon'),

View File

@ -57,7 +57,7 @@ from processing.core.ProcessingResults import resultsList
from processing.gui.ParametersPanel import ParametersPanel from processing.gui.ParametersPanel import ParametersPanel
from processing.gui.BatchAlgorithmDialog import BatchAlgorithmDialog from processing.gui.BatchAlgorithmDialog import BatchAlgorithmDialog
from processing.gui.AlgorithmDialogBase import AlgorithmDialogBase from processing.gui.AlgorithmDialogBase import AlgorithmDialogBase
from processing.gui.AlgorithmExecutor import executeIterating, execute from processing.gui.AlgorithmExecutor import executeIterating, execute, execute_in_place
from processing.gui.Postprocessing import handleAlgorithmResults from processing.gui.Postprocessing import handleAlgorithmResults
from processing.gui.wrappers import WidgetWrapper from processing.gui.wrappers import WidgetWrapper
@ -66,19 +66,25 @@ from processing.tools import dataobjects
class AlgorithmDialog(QgsProcessingAlgorithmDialogBase): class AlgorithmDialog(QgsProcessingAlgorithmDialogBase):
def __init__(self, alg): def __init__(self, alg, in_place=False):
super().__init__() super().__init__()
self.feedback_dialog = None self.feedback_dialog = None
self.in_place = in_place
self.setAlgorithm(alg) self.setAlgorithm(alg)
self.setMainWidget(self.getParametersPanel(alg, self)) self.setMainWidget(self.getParametersPanel(alg, self))
self.runAsBatchButton = QPushButton(QCoreApplication.translate("AlgorithmDialog", "Run as Batch Process…")) if not self.in_place:
self.runAsBatchButton.clicked.connect(self.runAsBatch) self.runAsBatchButton = QPushButton(QCoreApplication.translate("AlgorithmDialog", "Run as Batch Process…"))
self.buttonBox().addButton(self.runAsBatchButton, QDialogButtonBox.ResetRole) # reset role to ensure left alignment self.runAsBatchButton.clicked.connect(self.runAsBatch)
self.buttonBox().addButton(self.runAsBatchButton, QDialogButtonBox.ResetRole) # reset role to ensure left alignment
else:
self.runAsBatchButton = None
self.buttonBox().button(QDialogButtonBox.Ok).setText('Modify Selected Features')
self.buttonBox().button(QDialogButtonBox.Close).setText('Cancel')
def getParametersPanel(self, alg, parent): def getParametersPanel(self, alg, parent):
return ParametersPanel(parent, alg) return ParametersPanel(parent, alg, self.in_place)
def runAsBatch(self): def runAsBatch(self):
self.close() self.close()
@ -118,10 +124,23 @@ class AlgorithmDialog(QgsProcessingAlgorithmDialogBase):
value = wrapper.parameterValue() value = wrapper.parameterValue()
parameters[param.name()] = value parameters[param.name()] = value
if self.in_place and param.name() == 'INPUT':
parameters[param.name()] = iface.activeLayer()
continue
wrapper = self.mainWidget().wrappers[param.name()]
value = None
if wrapper.widget is not None:
value = wrapper.value()
parameters[param.name()] = value
if not param.checkValueIsAcceptable(value): if not param.checkValueIsAcceptable(value):
raise AlgorithmDialogBase.InvalidParameterValue(param, widget) raise AlgorithmDialogBase.InvalidParameterValue(param, widget)
else: else:
if self.in_place and param.name() == 'OUTPUT':
parameters[param.name()] = 'memory:'
continue
dest_project = None dest_project = None
if not param.flags() & QgsProcessingParameterDefinition.FlagHidden and \ if not param.flags() & QgsProcessingParameterDefinition.FlagHidden and \
isinstance(param, (QgsProcessingParameterRasterDestination, isinstance(param, (QgsProcessingParameterRasterDestination,
@ -226,9 +245,12 @@ class AlgorithmDialog(QgsProcessingAlgorithmDialogBase):
self.cancelButton().setEnabled(False) self.cancelButton().setEnabled(False)
self.finish(ok, results, context, feedback) if not self.in_place:
self.finish(ok, results, context, feedback)
elif ok:
self.close()
if not (self.algorithm().flags() & QgsProcessingAlgorithm.FlagNoThreading): if not self.in_place and not (self.algorithm().flags() & QgsProcessingAlgorithm.FlagNoThreading):
# Make sure the Log tab is visible before executing the algorithm # Make sure the Log tab is visible before executing the algorithm
self.showLog() self.showLog()
@ -241,7 +263,10 @@ class AlgorithmDialog(QgsProcessingAlgorithmDialogBase):
feedback.progressChanged.connect(self.proxy_progress.setProxyProgress) feedback.progressChanged.connect(self.proxy_progress.setProxyProgress)
self.feedback_dialog = self.createProgressDialog() self.feedback_dialog = self.createProgressDialog()
self.feedback_dialog.show() self.feedback_dialog.show()
ok, results = execute(self.algorithm(), parameters, context, feedback) if self.in_place:
ok, results = execute_in_place(self.algorithm(), parameters, context, feedback)
else:
ok, results = execute(self.algorithm(), parameters, context, feedback)
feedback.progressChanged.disconnect() feedback.progressChanged.disconnect()
self.proxy_progress.finalize(ok) self.proxy_progress.finalize(ok)
on_complete(ok, results) on_complete(ok, results)

View File

@ -33,9 +33,11 @@ from qgis.core import (Qgis,
QgsProcessingUtils, QgsProcessingUtils,
QgsMessageLog, QgsMessageLog,
QgsProcessingException, QgsProcessingException,
QgsProcessingFeatureSourceDefinition,
QgsProcessingParameters) QgsProcessingParameters)
from processing.gui.Postprocessing import handleAlgorithmResults from processing.gui.Postprocessing import handleAlgorithmResults
from processing.tools import dataobjects from processing.tools import dataobjects
from qgis.utils import iface
def execute(alg, parameters, context=None, feedback=None): def execute(alg, parameters, context=None, feedback=None):
@ -61,6 +63,40 @@ def execute(alg, parameters, context=None, feedback=None):
return False, {} return False, {}
def execute_in_place(alg, parameters, context=None, feedback=None):
if feedback is None:
feedback = QgsProcessingFeedback()
if context is None:
context = dataobjects.createContext(feedback)
parameters['INPUT'] = QgsProcessingFeatureSourceDefinition(iface.activeLayer().id(), True)
parameters['OUTPUT'] = 'memory:'
try:
results, ok = alg.run(parameters, context, feedback)
layer = QgsProcessingUtils.mapLayerFromString(results['OUTPUT'], context)
iface.activeLayer().beginEditCommand('Edit features')
iface.activeLayer().deleteFeatures(iface.activeLayer().selectedFeatureIds())
features = []
for f in layer.getFeatures():
features.append(f)
iface.activeLayer().addFeatures(features)
new_selection = [f.id() for f in features]
iface.activeLayer().endEditCommand()
#iface.activeLayer().selectByIds(new_selection)
iface.activeLayer().triggerRepaint()
return ok, results
except QgsProcessingException as e:
QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical)
if feedback is not None:
feedback.reportError(e.msg)
return False, {}
def executeIterating(alg, parameters, paramToIter, context, feedback): def executeIterating(alg, parameters, paramToIter, context, feedback):
# Generate all single-feature layers # Generate all single-feature layers
parameter_definition = alg.parameterDefinition(paramToIter) parameter_definition = alg.parameterDefinition(paramToIter)

View File

@ -28,10 +28,16 @@ __revision__ = '$Format:%H$'
from qgis.core import (QgsApplication, from qgis.core import (QgsApplication,
QgsProcessingAlgorithm, QgsProcessingAlgorithm,
QgsProcessingFeatureBasedAlgorithm,
QgsLocatorFilter, QgsLocatorFilter,
QgsLocatorResult) QgsLocatorResult,
QgsProcessing,
QgsWkbTypes,
QgsMapLayer,
QgsFields)
from processing.gui.MessageDialog import MessageDialog from processing.gui.MessageDialog import MessageDialog
from processing.gui.AlgorithmDialog import AlgorithmDialog from processing.gui.AlgorithmDialog import AlgorithmDialog
from processing.gui.AlgorithmExecutor import execute_in_place
from qgis.utils import iface from qgis.utils import iface
@ -101,3 +107,91 @@ class AlgorithmLocatorFilter(QgsLocatorFilter):
except: except:
pass pass
canvas.setMapTool(prevMapTool) canvas.setMapTool(prevMapTool)
class InPlaceAlgorithmLocatorFilter(QgsLocatorFilter):
def __init__(self, parent=None):
super().__init__(parent)
def clone(self):
return InPlaceAlgorithmLocatorFilter()
def name(self):
return 'edit_features'
def displayName(self):
return self.tr('Edit Selected Features')
def priority(self):
return QgsLocatorFilter.Low
def prefix(self):
return 'ef'
def flags(self):
return QgsLocatorFilter.FlagFast
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():
return
for a in QgsApplication.processingRegistry().algorithms():
if not a.flags() & QgsProcessingAlgorithm.FlagSupportsInPlaceEdits:
continue
if a.inputLayerTypes() and \
QgsProcessing.TypeVector not in a.inputLayerTypes() \
and QgsProcessing.TypeVectorAnyGeometry not in a.inputLayerTypes() \
and (QgsWkbTypes.geometryType(iface.activeLayer().wkbType()) == QgsWkbTypes.PolygonGeometry and QgsProcessing.TypeVectorPolygon not in a.inputLayerTypes() or
QgsWkbTypes.geometryType(
iface.activeLayer().wkbType()) == QgsWkbTypes.LineGeometry and QgsProcessing.TypeVectorLine not in a.inputLayerTypes() or
QgsWkbTypes.geometryType(
iface.activeLayer().wkbType()) == QgsWkbTypes.PointGeometry and QgsProcessing.TypeVectorPoint not in a.inputLayerTypes()):
continue
if QgsLocatorFilter.stringMatches(a.displayName(), string) or [t for t in a.tags() if QgsLocatorFilter.stringMatches(t, string)] or \
(context.usingPrefix and not string):
result = QgsLocatorResult()
result.filter = self
result.displayString = a.displayName()
result.icon = a.icon()
result.userData = a.id()
if string and QgsLocatorFilter.stringMatches(a.displayName(), string):
result.score = float(len(string)) / len(a.displayName())
else:
result.score = 0
self.resultFetched.emit(result)
def triggerResult(self, result):
alg = QgsApplication.processingRegistry().createAlgorithmById(result.userData)
if alg:
ok, message = alg.canExecute()
if not ok:
dlg = MessageDialog()
dlg.setTitle(self.tr('Missing dependency'))
dlg.setMessage(message)
dlg.exec_()
return
if len(alg.parameterDefinitions()) > 2:
# hack!!
dlg = alg.createCustomParametersWidget(None)
if not dlg:
dlg = AlgorithmDialog(alg, True)
canvas = iface.mapCanvas()
prevMapTool = canvas.mapTool()
dlg.show()
dlg.exec_()
if canvas.mapTool() != prevMapTool:
try:
canvas.mapTool().reset()
except:
pass
canvas.setMapTool(prevMapTool)
else:
parameters = {}
execute_in_place(alg, parameters)

View File

@ -66,9 +66,10 @@ class ParametersPanel(BASE, WIDGET):
NOT_SELECTED = QCoreApplication.translate('ParametersPanel', '[Not selected]') NOT_SELECTED = QCoreApplication.translate('ParametersPanel', '[Not selected]')
def __init__(self, parent, alg): def __init__(self, parent, alg, in_place=False):
super(ParametersPanel, self).__init__(None) super(ParametersPanel, self).__init__(None)
self.setupUi(self) self.setupUi(self)
self.in_place = in_place
self.grpAdvanced.hide() self.grpAdvanced.hide()
@ -126,6 +127,9 @@ class ParametersPanel(BASE, WIDGET):
if param.flags() & QgsProcessingParameterDefinition.FlagHidden: if param.flags() & QgsProcessingParameterDefinition.FlagHidden:
continue continue
if self.in_place and param.name() in ('INPUT', 'OUTPUT'):
continue
if param.isDestination(): if param.isDestination():
continue continue
else: else:
@ -196,6 +200,9 @@ class ParametersPanel(BASE, WIDGET):
if output.flags() & QgsProcessingParameterDefinition.FlagHidden: if output.flags() & QgsProcessingParameterDefinition.FlagHidden:
continue continue
if self.in_place and param.name() in ('INPUT', 'OUTPUT'):
continue
label = QLabel(output.description()) label = QLabel(output.description())
widget = DestinationSelectionPanel(output, self.alg) widget = DestinationSelectionPanel(output, self.alg)
self.layoutMain.insertWidget(self.layoutMain.count() - 1, label) self.layoutMain.insertWidget(self.layoutMain.count() - 1, label)

View File

@ -33,7 +33,9 @@ from qgis.PyQt import uic
from qgis.PyQt.QtCore import Qt, QCoreApplication from qgis.PyQt.QtCore import Qt, QCoreApplication
from qgis.PyQt.QtWidgets import QToolButton, QMenu, QAction from qgis.PyQt.QtWidgets import QToolButton, QMenu, QAction
from qgis.utils import iface from qgis.utils import iface
from qgis.core import (QgsApplication, from qgis.core import (QgsWkbTypes,
QgsMapLayer,
QgsApplication,
QgsProcessingAlgorithm) QgsProcessingAlgorithm)
from qgis.gui import (QgsGui, from qgis.gui import (QgsGui,
QgsDockWidget, QgsDockWidget,
@ -50,6 +52,7 @@ from processing.gui.AlgorithmExecutor import execute
from processing.gui.ProviderActions import (ProviderActions, from processing.gui.ProviderActions import (ProviderActions,
ProviderContextMenuActions) ProviderContextMenuActions)
from processing.tools import dataobjects from processing.tools import dataobjects
from processing.gui.AlgorithmExecutor import execute_in_place
pluginPath = os.path.split(os.path.dirname(__file__))[0] pluginPath = os.path.split(os.path.dirname(__file__))[0]
@ -71,6 +74,7 @@ class ProcessingToolbox(QgsDockWidget, WIDGET):
def __init__(self): def __init__(self):
super(ProcessingToolbox, self).__init__(None) super(ProcessingToolbox, self).__init__(None)
self.tipWasClosed = False self.tipWasClosed = False
self.in_place_mode = False
self.setupUi(self) self.setupUi(self)
self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
self.processingToolbar.setIconSize(iface.iconSize(True)) self.processingToolbar.setIconSize(iface.iconSize(True))
@ -108,6 +112,20 @@ class ProcessingToolbox(QgsDockWidget, WIDGET):
QgsApplication.processingRegistry().providerRemoved.connect(self.addProvider) QgsApplication.processingRegistry().providerRemoved.connect(self.addProvider)
QgsApplication.processingRegistry().providerRemoved.connect(self.removeProvider) QgsApplication.processingRegistry().providerRemoved.connect(self.removeProvider)
iface.currentLayerChanged.connect(self.layer_changed)
def set_in_place_edit_mode(self, enabled):
if enabled:
self.algorithmTree.setFilters(QgsProcessingToolboxProxyModel.Filters(QgsProcessingToolboxProxyModel.FilterToolbox | QgsProcessingToolboxProxyModel.FilterInPlace))
else:
self.algorithmTree.setFilters(QgsProcessingToolboxProxyModel.FilterToolbox)
self.in_place_mode = enabled
def layer_changed(self, layer):
if layer is None or layer.type() != QgsMapLayer.VectorLayer:
return
self.algorithmTree.setInPlaceLayerType(QgsWkbTypes.geometryType(layer.wkbType()))
def disabledProviders(self): def disabledProviders(self):
showTip = ProcessingConfig.getSetting(ProcessingConfig.SHOW_PROVIDERS_TOOLTIP) showTip = ProcessingConfig.getSetting(ProcessingConfig.SHOW_PROVIDERS_TOOLTIP)
if not showTip or self.tipWasClosed: if not showTip or self.tipWasClosed:
@ -211,11 +229,17 @@ class ProcessingToolbox(QgsDockWidget, WIDGET):
dlg.exec_() dlg.exec_()
return return
if self.in_place_mode and len(alg.parameterDefinitions()) <= 2:
# hack
parameters = {}
execute_in_place(alg, parameters)
return
if alg.countVisibleParameters() > 0: if alg.countVisibleParameters() > 0:
dlg = alg.createCustomParametersWidget(self) dlg = alg.createCustomParametersWidget(self)
if not dlg: if not dlg:
dlg = AlgorithmDialog(alg) dlg = AlgorithmDialog(alg, self.in_place_mode)
canvas = iface.mapCanvas() canvas = iface.mapCanvas()
prevMapTool = canvas.mapTool() prevMapTool = canvas.mapTool()
dlg.show() dlg.show()

View File

@ -29,6 +29,7 @@ const QList<QString> QgsLocator::CORE_FILTERS = QList<QString>() << QStringLiter
<< QStringLiteral( "calculator" ) << QStringLiteral( "calculator" )
<< QStringLiteral( "bookmarks" ) << QStringLiteral( "bookmarks" )
<< QStringLiteral( "optionpages" ); << QStringLiteral( "optionpages" );
<< QStringLiteral( "edit_features" );
QgsLocator::QgsLocator( QObject *parent ) QgsLocator::QgsLocator( QObject *parent )
: QObject( parent ) : QObject( parent )

View File

@ -787,6 +787,13 @@ bool QgsProcessingAlgorithm::createAutoOutputForParameter( QgsProcessingParamete
// QgsProcessingFeatureBasedAlgorithm // QgsProcessingFeatureBasedAlgorithm
// //
QgsProcessingAlgorithm::Flags QgsProcessingFeatureBasedAlgorithm::flags() const
{
Flags f = QgsProcessingAlgorithm::flags();
f |= QgsProcessingAlgorithm::FlagSupportsInPlaceEdits;
return f;
}
void QgsProcessingFeatureBasedAlgorithm::initAlgorithm( const QVariantMap &config ) void QgsProcessingFeatureBasedAlgorithm::initAlgorithm( const QVariantMap &config )
{ {
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ), inputLayerTypes() ) ); addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ), inputLayerTypes() ) );

View File

@ -74,6 +74,7 @@ class CORE_EXPORT QgsProcessingAlgorithm
FlagRequiresMatchingCrs = 1 << 5, //!< Algorithm requires that all input layers have matching coordinate reference systems FlagRequiresMatchingCrs = 1 << 5, //!< Algorithm requires that all input layers have matching coordinate reference systems
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. 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.
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'. 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'.
FlagSupportsInPlaceEdits = 1 << 8, //!< Algorithm supports in-place editing
FlagDeprecated = FlagHideFromToolbox | FlagHideFromModeler, //!< Algorithm is deprecated FlagDeprecated = FlagHideFromToolbox | FlagHideFromModeler, //!< Algorithm is deprecated
}; };
Q_DECLARE_FLAGS( Flags, Flag ) Q_DECLARE_FLAGS( Flags, Flag )
@ -861,6 +862,8 @@ class CORE_EXPORT QgsProcessingFeatureBasedAlgorithm : public QgsProcessingAlgor
*/ */
QgsProcessingFeatureBasedAlgorithm() = default; QgsProcessingFeatureBasedAlgorithm() = default;
QgsProcessingAlgorithm::Flags flags() const override;
protected: protected:
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
@ -970,6 +973,7 @@ class CORE_EXPORT QgsProcessingFeatureBasedAlgorithm : public QgsProcessingAlgor
private: private:
std::unique_ptr< QgsProcessingFeatureSource > mSource; std::unique_ptr< QgsProcessingFeatureSource > mSource;
friend class QgsProcessingToolboxProxyModel;
}; };

View File

@ -655,6 +655,12 @@ void QgsProcessingToolboxProxyModel::setFilters( QgsProcessingToolboxProxyModel:
invalidateFilter(); invalidateFilter();
} }
void QgsProcessingToolboxProxyModel::setInPlaceLayerType( QgsWkbTypes::GeometryType type )
{
mInPlaceGeometryType = type;
invalidateFilter();
}
void QgsProcessingToolboxProxyModel::setFilterString( const QString &filter ) void QgsProcessingToolboxProxyModel::setFilterString( const QString &filter )
{ {
mFilterString = filter; mFilterString = filter;
@ -708,6 +714,32 @@ bool QgsProcessingToolboxProxyModel::filterAcceptsRow( int sourceRow, const QMod
} }
} }
if ( mFilters & FilterInPlace )
{
const bool supportsInPlace = sourceModel()->data( sourceIndex, QgsProcessingToolboxModel::RoleAlgorithmFlags ).toInt() & QgsProcessingAlgorithm::FlagSupportsInPlaceEdits;
if ( !supportsInPlace )
return false;
const QgsProcessingFeatureBasedAlgorithm *alg = dynamic_cast< const QgsProcessingFeatureBasedAlgorithm * >( mModel->algorithmForIndex( sourceIndex ) );
if ( !alg->inputLayerTypes().empty() &&
!alg->inputLayerTypes().contains( QgsProcessing::TypeVector ) &&
!alg->inputLayerTypes().contains( QgsProcessing::TypeVectorAnyGeometry ) &&
( ( mInPlaceGeometryType == QgsWkbTypes::PolygonGeometry && !alg->inputLayerTypes().contains( QgsProcessing::TypeVectorPolygon ) ) ||
( mInPlaceGeometryType == QgsWkbTypes::LineGeometry && !alg->inputLayerTypes().contains( QgsProcessing::TypeVectorLine ) ) ||
( mInPlaceGeometryType == QgsWkbTypes::PointGeometry && !alg->inputLayerTypes().contains( QgsProcessing::TypeVectorPoint ) ) ) )
return false;
QgsWkbTypes::Type type = QgsWkbTypes::Unknown;
if ( mInPlaceGeometryType == QgsWkbTypes::PointGeometry )
type = QgsWkbTypes::Point;
else if ( mInPlaceGeometryType == QgsWkbTypes::LineGeometry )
type = QgsWkbTypes::LineString;
else if ( mInPlaceGeometryType == QgsWkbTypes::PolygonGeometry )
type = QgsWkbTypes::Polygon;
if ( QgsWkbTypes::geometryType( alg->outputWkbType( type ) ) != mInPlaceGeometryType )
return false;
}
if ( mFilters & FilterModeler ) if ( mFilters & FilterModeler )
{ {
bool isHiddenFromModeler = sourceModel()->data( sourceIndex, QgsProcessingToolboxModel::RoleAlgorithmFlags ).toInt() & QgsProcessingAlgorithm::FlagHideFromModeler; bool isHiddenFromModeler = sourceModel()->data( sourceIndex, QgsProcessingToolboxModel::RoleAlgorithmFlags ).toInt() & QgsProcessingAlgorithm::FlagHideFromModeler;

View File

@ -423,6 +423,7 @@ class GUI_EXPORT QgsProcessingToolboxProxyModel: public QSortFilterProxyModel
{ {
FilterToolbox = 1 << 1, //!< Filters out any algorithms and content which should not be shown in the toolbox FilterToolbox = 1 << 1, //!< Filters out any algorithms and content which should not be shown in the toolbox
FilterModeler = 1 << 2, //!< Filters out any algorithms and content which should not be shown in the modeler FilterModeler = 1 << 2, //!< Filters out any algorithms and content which should not be shown in the modeler
FilterInPlace = 1 << 3, //!< Only show algorithms which support in-place edits
}; };
Q_DECLARE_FLAGS( Filters, Filter ) Q_DECLARE_FLAGS( Filters, Filter )
Q_FLAG( Filters ) Q_FLAG( Filters )
@ -459,6 +460,8 @@ class GUI_EXPORT QgsProcessingToolboxProxyModel: public QSortFilterProxyModel
*/ */
Filters filters() const { return mFilters; } Filters filters() const { return mFilters; }
void setInPlaceLayerType( QgsWkbTypes::GeometryType type );
/** /**
* Sets a \a filter string, such that only algorithms matching the * Sets a \a filter string, such that only algorithms matching the
* specified string will be shown. * specified string will be shown.
@ -485,6 +488,7 @@ class GUI_EXPORT QgsProcessingToolboxProxyModel: public QSortFilterProxyModel
QgsProcessingToolboxModel *mModel = nullptr; QgsProcessingToolboxModel *mModel = nullptr;
Filters mFilters = nullptr; Filters mFilters = nullptr;
QString mFilterString; QString mFilterString;
QgsWkbTypes::GeometryType mInPlaceGeometryType = QgsWkbTypes::UnknownGeometry;
}; };

View File

@ -93,6 +93,11 @@ void QgsProcessingToolboxTreeView::setFilters( QgsProcessingToolboxProxyModel::F
mModel->setFilters( filters ); mModel->setFilters( filters );
} }
void QgsProcessingToolboxTreeView::setInPlaceLayerType( QgsWkbTypes::GeometryType type )
{
mModel->setInPlaceLayerType( type );
}
QModelIndex QgsProcessingToolboxTreeView::findFirstVisibleAlgorithm( const QModelIndex &parent ) QModelIndex QgsProcessingToolboxTreeView::findFirstVisibleAlgorithm( const QModelIndex &parent )
{ {
for ( int r = 0; r < mModel->rowCount( parent ); ++r ) for ( int r = 0; r < mModel->rowCount( parent ); ++r )

View File

@ -85,6 +85,8 @@ class GUI_EXPORT QgsProcessingToolboxTreeView : public QTreeView
*/ */
void setFilters( QgsProcessingToolboxProxyModel::Filters filters ); void setFilters( QgsProcessingToolboxProxyModel::Filters filters );
void setInPlaceLayerType( QgsWkbTypes::GeometryType type );
public slots: public slots:
/** /**