mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-23 00:04:25 -04:00
(e.g. parameters) when they are run in the "edit in-place" mode This allows algorithms to dynamically adapt their behavior to make them compatible with in-place mode. Previously, some useful algorithms could not be run in-place because they alter a layer's structure (e.g. adding new fields). Now, these algorithms have a means to detect that they are being run in-place and change their input parameters accordingly. E.g. an algorithm which usually adds new fields to store calculated values (such as "add xy fields to layer") could instead expose field parameter choices to ask the user to pick from existing fields in which to store the calculated values, thereby avoiding the need to change the table structure and making them eligable for running in-place mode. Note that this needs to be handled algorithm-by-algorithm, it's not automatic! It's just the raw api to allow this...
283 lines
12 KiB
Python
283 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
***************************************************************************
|
|
ProcessingToolbox.py
|
|
---------------------
|
|
Date : August 2012
|
|
Copyright : (C) 2012 by Victor Olaya
|
|
Email : volayaf at gmail dot com
|
|
***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************
|
|
"""
|
|
|
|
__author__ = 'Victor Olaya'
|
|
__date__ = 'August 2012'
|
|
__copyright__ = '(C) 2012, Victor Olaya'
|
|
|
|
import operator
|
|
import os
|
|
import warnings
|
|
|
|
from qgis.PyQt import uic
|
|
from qgis.PyQt.QtCore import Qt, QCoreApplication
|
|
from qgis.PyQt.QtWidgets import QToolButton, QMenu, QAction
|
|
from qgis.utils import iface
|
|
from qgis.core import (QgsWkbTypes,
|
|
QgsMapLayerType,
|
|
QgsApplication,
|
|
QgsProcessingAlgorithm)
|
|
from qgis.gui import (QgsGui,
|
|
QgsDockWidget,
|
|
QgsProcessingToolboxProxyModel)
|
|
|
|
from processing.gui.Postprocessing import handleAlgorithmResults
|
|
from processing.core.ProcessingConfig import ProcessingConfig
|
|
from processing.gui.MessageDialog import MessageDialog
|
|
from processing.gui.AlgorithmDialog import AlgorithmDialog
|
|
from processing.gui.BatchAlgorithmDialog import BatchAlgorithmDialog
|
|
from processing.gui.EditRenderingStylesDialog import EditRenderingStylesDialog
|
|
from processing.gui.MessageBarProgress import MessageBarProgress
|
|
from processing.gui.AlgorithmExecutor import execute
|
|
from processing.gui.ProviderActions import (ProviderActions,
|
|
ProviderContextMenuActions)
|
|
from processing.tools import dataobjects
|
|
from processing.gui.AlgorithmExecutor import execute_in_place
|
|
|
|
pluginPath = os.path.split(os.path.dirname(__file__))[0]
|
|
|
|
with warnings.catch_warnings():
|
|
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
WIDGET, BASE = uic.loadUiType(
|
|
os.path.join(pluginPath, 'ui', 'ProcessingToolbox.ui'))
|
|
|
|
|
|
class ProcessingToolbox(QgsDockWidget, WIDGET):
|
|
ALG_ITEM = 'ALG_ITEM'
|
|
PROVIDER_ITEM = 'PROVIDER_ITEM'
|
|
GROUP_ITEM = 'GROUP_ITEM'
|
|
|
|
NAME_ROLE = Qt.UserRole
|
|
TAG_ROLE = Qt.UserRole + 1
|
|
TYPE_ROLE = Qt.UserRole + 2
|
|
|
|
def __init__(self):
|
|
super(ProcessingToolbox, self).__init__(None)
|
|
self.tipWasClosed = False
|
|
self.in_place_mode = False
|
|
self.setupUi(self)
|
|
self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
|
|
self.processingToolbar.setIconSize(iface.iconSize(True))
|
|
|
|
self.algorithmTree.setRegistry(QgsApplication.processingRegistry(),
|
|
QgsGui.instance().processingRecentAlgorithmLog())
|
|
filters = QgsProcessingToolboxProxyModel.Filters(QgsProcessingToolboxProxyModel.FilterToolbox)
|
|
if ProcessingConfig.getSetting(ProcessingConfig.SHOW_ALGORITHMS_KNOWN_ISSUES):
|
|
filters |= QgsProcessingToolboxProxyModel.FilterShowKnownIssues
|
|
self.algorithmTree.setFilters(filters)
|
|
|
|
self.searchBox.setShowSearchIcon(True)
|
|
|
|
self.searchBox.textChanged.connect(self.set_filter_string)
|
|
self.searchBox.returnPressed.connect(self.activateCurrent)
|
|
self.algorithmTree.customContextMenuRequested.connect(
|
|
self.showPopupMenu)
|
|
self.algorithmTree.doubleClicked.connect(self.executeAlgorithm)
|
|
self.txtTip.setVisible(self.disabledProviders())
|
|
|
|
def openSettings(url):
|
|
if url == "close":
|
|
self.txtTip.setVisible(False)
|
|
self.tipWasClosed = True
|
|
else:
|
|
iface.showOptionsDialog(iface.mainWindow(), 'processingOptions')
|
|
self.txtTip.setVisible(self.disabledProviders())
|
|
|
|
self.txtTip.linkActivated.connect(openSettings)
|
|
if hasattr(self.searchBox, 'setPlaceholderText'):
|
|
self.searchBox.setPlaceholderText(QCoreApplication.translate('ProcessingToolbox', 'Search…'))
|
|
|
|
# connect to existing providers
|
|
for p in QgsApplication.processingRegistry().providers():
|
|
if p.isActive():
|
|
self.addProviderActions(p)
|
|
|
|
QgsApplication.processingRegistry().providerRemoved.connect(self.addProvider)
|
|
QgsApplication.processingRegistry().providerRemoved.connect(self.removeProvider)
|
|
|
|
iface.currentLayerChanged.connect(self.layer_changed)
|
|
|
|
def set_filter_string(self, string):
|
|
filters = self.algorithmTree.filters()
|
|
if ProcessingConfig.getSetting(ProcessingConfig.SHOW_ALGORITHMS_KNOWN_ISSUES):
|
|
filters |= QgsProcessingToolboxProxyModel.FilterShowKnownIssues
|
|
else:
|
|
filters &= ~QgsProcessingToolboxProxyModel.FilterShowKnownIssues
|
|
self.algorithmTree.setFilters(filters)
|
|
self.algorithmTree.setFilterString(string)
|
|
|
|
def set_in_place_edit_mode(self, enabled):
|
|
filters = QgsProcessingToolboxProxyModel.Filters(QgsProcessingToolboxProxyModel.FilterToolbox)
|
|
if ProcessingConfig.getSetting(ProcessingConfig.SHOW_ALGORITHMS_KNOWN_ISSUES):
|
|
filters |= QgsProcessingToolboxProxyModel.FilterShowKnownIssues
|
|
|
|
if enabled:
|
|
self.algorithmTree.setFilters(filters | QgsProcessingToolboxProxyModel.FilterInPlace)
|
|
else:
|
|
self.algorithmTree.setFilters(filters)
|
|
self.in_place_mode = enabled
|
|
|
|
def layer_changed(self, layer):
|
|
if layer is None or layer.type() != QgsMapLayerType.VectorLayer:
|
|
return
|
|
self.algorithmTree.setInPlaceLayer(layer)
|
|
|
|
def disabledProviders(self):
|
|
showTip = ProcessingConfig.getSetting(ProcessingConfig.SHOW_PROVIDERS_TOOLTIP)
|
|
if not showTip or self.tipWasClosed:
|
|
return False
|
|
|
|
for provider in QgsApplication.processingRegistry().providers():
|
|
if not provider.isActive() and provider.canBeActivated():
|
|
return True
|
|
|
|
return False
|
|
|
|
def addProviderActions(self, provider):
|
|
if provider.id() in ProviderActions.actions:
|
|
toolbarButton = QToolButton()
|
|
toolbarButton.setObjectName('provideraction_' + provider.id())
|
|
toolbarButton.setIcon(provider.icon())
|
|
toolbarButton.setToolTip(provider.name())
|
|
toolbarButton.setPopupMode(QToolButton.InstantPopup)
|
|
|
|
actions = ProviderActions.actions[provider.id()]
|
|
menu = QMenu(provider.name(), self)
|
|
for action in actions:
|
|
action.setData(self)
|
|
act = QAction(action.name, menu)
|
|
act.triggered.connect(action.execute)
|
|
menu.addAction(act)
|
|
toolbarButton.setMenu(menu)
|
|
self.processingToolbar.addWidget(toolbarButton)
|
|
|
|
def addProvider(self, provider_id):
|
|
provider = QgsApplication.processingRegistry().providerById(provider_id)
|
|
if provider is not None:
|
|
self.addProviderActions(provider)
|
|
|
|
def removeProvider(self, provider_id):
|
|
button = self.findChild(QToolButton, 'provideraction-' + provider_id)
|
|
if button:
|
|
self.processingToolbar.removeChild(button)
|
|
|
|
def showPopupMenu(self, point):
|
|
index = self.algorithmTree.indexAt(point)
|
|
popupmenu = QMenu()
|
|
alg = self.algorithmTree.algorithmForIndex(index)
|
|
if alg is not None:
|
|
executeAction = QAction(QCoreApplication.translate('ProcessingToolbox', 'Execute…'), popupmenu)
|
|
executeAction.triggered.connect(self.executeAlgorithm)
|
|
popupmenu.addAction(executeAction)
|
|
if alg.flags() & QgsProcessingAlgorithm.FlagSupportsBatch:
|
|
executeBatchAction = QAction(
|
|
QCoreApplication.translate('ProcessingToolbox', 'Execute as Batch Process…'),
|
|
popupmenu)
|
|
executeBatchAction.triggered.connect(
|
|
self.executeAlgorithmAsBatchProcess)
|
|
popupmenu.addAction(executeBatchAction)
|
|
popupmenu.addSeparator()
|
|
editRenderingStylesAction = QAction(
|
|
QCoreApplication.translate('ProcessingToolbox', 'Edit Rendering Styles for Outputs…'),
|
|
popupmenu)
|
|
editRenderingStylesAction.triggered.connect(
|
|
self.editRenderingStyles)
|
|
popupmenu.addAction(editRenderingStylesAction)
|
|
actions = ProviderContextMenuActions.actions
|
|
if len(actions) > 0:
|
|
popupmenu.addSeparator()
|
|
for action in actions:
|
|
action.setData(alg, self)
|
|
if action.is_separator:
|
|
popupmenu.addSeparator()
|
|
elif action.isEnabled():
|
|
contextMenuAction = QAction(action.name,
|
|
popupmenu)
|
|
contextMenuAction.setIcon(action.icon())
|
|
contextMenuAction.triggered.connect(action.execute)
|
|
popupmenu.addAction(contextMenuAction)
|
|
|
|
popupmenu.exec_(self.algorithmTree.mapToGlobal(point))
|
|
|
|
def editRenderingStyles(self):
|
|
alg = self.algorithmTree.selectedAlgorithm().create() if self.algorithmTree.selectedAlgorithm() is not None else None
|
|
if alg is not None:
|
|
dlg = EditRenderingStylesDialog(alg)
|
|
dlg.exec_()
|
|
|
|
def activateCurrent(self):
|
|
self.executeAlgorithm()
|
|
|
|
def executeAlgorithmAsBatchProcess(self):
|
|
alg = self.algorithmTree.selectedAlgorithm().create() if self.algorithmTree.selectedAlgorithm() is not None else None
|
|
if alg is not None:
|
|
dlg = BatchAlgorithmDialog(alg, iface.mainWindow())
|
|
dlg.setAttribute(Qt.WA_DeleteOnClose)
|
|
dlg.show()
|
|
dlg.exec_()
|
|
|
|
def executeAlgorithm(self):
|
|
config = {}
|
|
if self.in_place_mode:
|
|
config['IN_PLACE'] = True
|
|
alg = self.algorithmTree.selectedAlgorithm().create(config) if self.algorithmTree.selectedAlgorithm() is not None else None
|
|
if alg is not None:
|
|
ok, message = alg.canExecute()
|
|
if not ok:
|
|
dlg = MessageDialog()
|
|
dlg.setTitle(self.tr('Error executing algorithm'))
|
|
dlg.setMessage(
|
|
self.tr('<h3>This algorithm cannot '
|
|
'be run :-( </h3>\n{0}').format(message))
|
|
dlg.exec_()
|
|
return
|
|
|
|
if self.in_place_mode and not [d for d in alg.parameterDefinitions() if d.name() not in ('INPUT', 'OUTPUT')]:
|
|
parameters = {}
|
|
feedback = MessageBarProgress(algname=alg.displayName())
|
|
ok, results = execute_in_place(alg, parameters, feedback=feedback)
|
|
if ok:
|
|
iface.messageBar().pushSuccess('', self.tr('{} complete').format(alg.displayName()))
|
|
feedback.close()
|
|
# MessageBarProgress handles errors
|
|
return
|
|
|
|
if alg.countVisibleParameters() > 0:
|
|
dlg = alg.createCustomParametersWidget(self)
|
|
|
|
if not dlg:
|
|
dlg = AlgorithmDialog(alg, self.in_place_mode, iface.mainWindow())
|
|
canvas = iface.mapCanvas()
|
|
prevMapTool = canvas.mapTool()
|
|
dlg.show()
|
|
dlg.exec_()
|
|
if canvas.mapTool() != prevMapTool:
|
|
try:
|
|
canvas.mapTool().reset()
|
|
except:
|
|
pass
|
|
canvas.setMapTool(prevMapTool)
|
|
else:
|
|
feedback = MessageBarProgress(algname=alg.displayName())
|
|
context = dataobjects.createContext(feedback)
|
|
parameters = {}
|
|
ret, results = execute(alg, parameters, context, feedback)
|
|
handleAlgorithmResults(alg, context, feedback)
|
|
feedback.close()
|