1# -*- coding: utf-8 -*-
2
3"""
4***************************************************************************
5    ProcessingToolbox.py
6    ---------------------
7    Date                 : August 2012
8    Copyright            : (C) 2012 by Victor Olaya
9    Email                : volayaf at gmail dot com
10***************************************************************************
11*                                                                         *
12*   This program is free software; you can redistribute it and/or modify  *
13*   it under the terms of the GNU General Public License as published by  *
14*   the Free Software Foundation; either version 2 of the License, or     *
15*   (at your option) any later version.                                   *
16*                                                                         *
17***************************************************************************
18"""
19
20__author__ = 'Victor Olaya'
21__date__ = 'August 2012'
22__copyright__ = '(C) 2012, Victor Olaya'
23
24import operator
25import os
26import warnings
27
28from qgis.PyQt import uic
29from qgis.PyQt.QtCore import Qt, QCoreApplication
30from qgis.PyQt.QtWidgets import QToolButton, QMenu, QAction
31from qgis.utils import iface
32from qgis.core import (QgsWkbTypes,
33                       QgsMapLayerType,
34                       QgsApplication,
35                       QgsProcessingAlgorithm)
36from qgis.gui import (QgsGui,
37                      QgsDockWidget,
38                      QgsProcessingToolboxProxyModel)
39
40from processing.gui.Postprocessing import handleAlgorithmResults
41from processing.core.ProcessingConfig import ProcessingConfig
42from processing.gui.MessageDialog import MessageDialog
43from processing.gui.AlgorithmDialog import AlgorithmDialog
44from processing.gui.BatchAlgorithmDialog import BatchAlgorithmDialog
45from processing.gui.EditRenderingStylesDialog import EditRenderingStylesDialog
46from processing.gui.MessageBarProgress import MessageBarProgress
47from processing.gui.AlgorithmExecutor import execute
48from processing.gui.ProviderActions import (ProviderActions,
49                                            ProviderContextMenuActions)
50from processing.tools import dataobjects
51from processing.gui.AlgorithmExecutor import execute_in_place
52
53pluginPath = os.path.split(os.path.dirname(__file__))[0]
54
55with warnings.catch_warnings():
56    warnings.filterwarnings("ignore", category=DeprecationWarning)
57    WIDGET, BASE = uic.loadUiType(
58        os.path.join(pluginPath, 'ui', 'ProcessingToolbox.ui'))
59
60
61class ProcessingToolbox(QgsDockWidget, WIDGET):
62    ALG_ITEM = 'ALG_ITEM'
63    PROVIDER_ITEM = 'PROVIDER_ITEM'
64    GROUP_ITEM = 'GROUP_ITEM'
65
66    NAME_ROLE = Qt.UserRole
67    TAG_ROLE = Qt.UserRole + 1
68    TYPE_ROLE = Qt.UserRole + 2
69
70    def __init__(self):
71        super(ProcessingToolbox, self).__init__(None)
72        self.tipWasClosed = False
73        self.in_place_mode = False
74        self.setupUi(self)
75        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
76        self.processingToolbar.setIconSize(iface.iconSize(True))
77
78        self.algorithmTree.setRegistry(QgsApplication.processingRegistry(),
79                                       QgsGui.instance().processingRecentAlgorithmLog())
80        filters = QgsProcessingToolboxProxyModel.Filters(QgsProcessingToolboxProxyModel.FilterToolbox)
81        if ProcessingConfig.getSetting(ProcessingConfig.SHOW_ALGORITHMS_KNOWN_ISSUES):
82            filters |= QgsProcessingToolboxProxyModel.FilterShowKnownIssues
83        self.algorithmTree.setFilters(filters)
84
85        self.searchBox.setShowSearchIcon(True)
86
87        self.searchBox.textChanged.connect(self.set_filter_string)
88        self.searchBox.returnPressed.connect(self.activateCurrent)
89        self.algorithmTree.customContextMenuRequested.connect(
90            self.showPopupMenu)
91        self.algorithmTree.doubleClicked.connect(self.executeAlgorithm)
92        self.txtTip.setVisible(self.disabledProviders())
93
94        def openSettings(url):
95            if url == "close":
96                self.txtTip.setVisible(False)
97                self.tipWasClosed = True
98            else:
99                iface.showOptionsDialog(iface.mainWindow(), 'processingOptions')
100                self.txtTip.setVisible(self.disabledProviders())
101
102        self.txtTip.linkActivated.connect(openSettings)
103        if hasattr(self.searchBox, 'setPlaceholderText'):
104            self.searchBox.setPlaceholderText(QCoreApplication.translate('ProcessingToolbox', 'Search…'))
105
106        # connect to existing providers
107        for p in QgsApplication.processingRegistry().providers():
108            if p.isActive():
109                self.addProviderActions(p)
110
111        QgsApplication.processingRegistry().providerRemoved.connect(self.addProvider)
112        QgsApplication.processingRegistry().providerRemoved.connect(self.removeProvider)
113
114        iface.currentLayerChanged.connect(self.layer_changed)
115
116    def set_filter_string(self, string):
117        filters = self.algorithmTree.filters()
118        if ProcessingConfig.getSetting(ProcessingConfig.SHOW_ALGORITHMS_KNOWN_ISSUES):
119            filters |= QgsProcessingToolboxProxyModel.FilterShowKnownIssues
120        else:
121            filters &= ~QgsProcessingToolboxProxyModel.FilterShowKnownIssues
122        self.algorithmTree.setFilters(filters)
123        self.algorithmTree.setFilterString(string)
124
125    def set_in_place_edit_mode(self, enabled):
126        filters = QgsProcessingToolboxProxyModel.Filters(QgsProcessingToolboxProxyModel.FilterToolbox)
127        if ProcessingConfig.getSetting(ProcessingConfig.SHOW_ALGORITHMS_KNOWN_ISSUES):
128            filters |= QgsProcessingToolboxProxyModel.FilterShowKnownIssues
129
130        if enabled:
131            self.algorithmTree.setFilters(filters | QgsProcessingToolboxProxyModel.FilterInPlace)
132        else:
133            self.algorithmTree.setFilters(filters)
134        self.in_place_mode = enabled
135
136    def layer_changed(self, layer):
137        if layer is None or layer.type() != QgsMapLayerType.VectorLayer:
138            return
139        self.algorithmTree.setInPlaceLayer(layer)
140
141    def disabledProviders(self):
142        showTip = ProcessingConfig.getSetting(ProcessingConfig.SHOW_PROVIDERS_TOOLTIP)
143        if not showTip or self.tipWasClosed:
144            return False
145
146        for provider in QgsApplication.processingRegistry().providers():
147            if not provider.isActive() and provider.canBeActivated():
148                return True
149
150        return False
151
152    def addProviderActions(self, provider):
153        if provider.id() in ProviderActions.actions:
154            toolbarButton = QToolButton()
155            toolbarButton.setObjectName('provideraction_' + provider.id())
156            toolbarButton.setIcon(provider.icon())
157            toolbarButton.setToolTip(provider.name())
158            toolbarButton.setPopupMode(QToolButton.InstantPopup)
159
160            actions = ProviderActions.actions[provider.id()]
161            menu = QMenu(provider.name(), self)
162            for action in actions:
163                action.setData(self)
164                act = QAction(action.name, menu)
165                act.triggered.connect(action.execute)
166                menu.addAction(act)
167            toolbarButton.setMenu(menu)
168            self.processingToolbar.addWidget(toolbarButton)
169
170    def addProvider(self, provider_id):
171        provider = QgsApplication.processingRegistry().providerById(provider_id)
172        if provider is not None:
173            self.addProviderActions(provider)
174
175    def removeProvider(self, provider_id):
176        button = self.findChild(QToolButton, 'provideraction-' + provider_id)
177        if button:
178            self.processingToolbar.removeChild(button)
179
180    def showPopupMenu(self, point):
181        index = self.algorithmTree.indexAt(point)
182        popupmenu = QMenu()
183        alg = self.algorithmTree.algorithmForIndex(index)
184        if alg is not None:
185            executeAction = QAction(QCoreApplication.translate('ProcessingToolbox', 'Execute…'), popupmenu)
186            executeAction.triggered.connect(self.executeAlgorithm)
187            popupmenu.addAction(executeAction)
188            if alg.flags() & QgsProcessingAlgorithm.FlagSupportsBatch:
189                executeBatchAction = QAction(
190                    QCoreApplication.translate('ProcessingToolbox', 'Execute as Batch Process…'),
191                    popupmenu)
192                executeBatchAction.triggered.connect(
193                    self.executeAlgorithmAsBatchProcess)
194                popupmenu.addAction(executeBatchAction)
195            popupmenu.addSeparator()
196            editRenderingStylesAction = QAction(
197                QCoreApplication.translate('ProcessingToolbox', 'Edit Rendering Styles for Outputs…'),
198                popupmenu)
199            editRenderingStylesAction.triggered.connect(
200                self.editRenderingStyles)
201            popupmenu.addAction(editRenderingStylesAction)
202            actions = ProviderContextMenuActions.actions
203            if len(actions) > 0:
204                popupmenu.addSeparator()
205            for action in actions:
206                action.setData(alg, self)
207                if action.is_separator:
208                    popupmenu.addSeparator()
209                elif action.isEnabled():
210                    contextMenuAction = QAction(action.name,
211                                                popupmenu)
212                    contextMenuAction.setIcon(action.icon())
213                    contextMenuAction.triggered.connect(action.execute)
214                    popupmenu.addAction(contextMenuAction)
215
216            popupmenu.exec_(self.algorithmTree.mapToGlobal(point))
217
218    def editRenderingStyles(self):
219        alg = self.algorithmTree.selectedAlgorithm().create() if self.algorithmTree.selectedAlgorithm() is not None else None
220        if alg is not None:
221            dlg = EditRenderingStylesDialog(alg)
222            dlg.exec_()
223
224    def activateCurrent(self):
225        self.executeAlgorithm()
226
227    def executeAlgorithmAsBatchProcess(self):
228        alg = self.algorithmTree.selectedAlgorithm().create() if self.algorithmTree.selectedAlgorithm() is not None else None
229        if alg is not None:
230            dlg = BatchAlgorithmDialog(alg, iface.mainWindow())
231            dlg.setAttribute(Qt.WA_DeleteOnClose)
232            dlg.show()
233            dlg.exec_()
234
235    def executeAlgorithm(self):
236        config = {}
237        if self.in_place_mode:
238            config['IN_PLACE'] = True
239        alg = self.algorithmTree.selectedAlgorithm().create(config) if self.algorithmTree.selectedAlgorithm() is not None else None
240        if alg is not None:
241            ok, message = alg.canExecute()
242            if not ok:
243                dlg = MessageDialog()
244                dlg.setTitle(self.tr('Error executing algorithm'))
245                dlg.setMessage(
246                    self.tr('<h3>This algorithm cannot '
247                            'be run :-( </h3>\n{0}').format(message))
248                dlg.exec_()
249                return
250
251            in_place_input_parameter_name = 'INPUT'
252            if hasattr(alg, 'inputParameterName'):
253                in_place_input_parameter_name = alg.inputParameterName()
254
255            if self.in_place_mode and not [d for d in alg.parameterDefinitions() if d.name() not in (in_place_input_parameter_name, 'OUTPUT')]:
256                parameters = {}
257                feedback = MessageBarProgress(algname=alg.displayName())
258                ok, results = execute_in_place(alg, parameters, feedback=feedback)
259                if ok:
260                    iface.messageBar().pushSuccess('', self.tr('{algname} completed. %n feature(s) processed.', n=results['__count']).format(algname=alg.displayName()))
261                feedback.close()
262                # MessageBarProgress handles errors
263                return
264
265            if alg.countVisibleParameters() > 0:
266                dlg = alg.createCustomParametersWidget(self)
267
268                if not dlg:
269                    dlg = AlgorithmDialog(alg, self.in_place_mode, iface.mainWindow())
270                canvas = iface.mapCanvas()
271                prevMapTool = canvas.mapTool()
272                dlg.show()
273                dlg.exec_()
274                if canvas.mapTool() != prevMapTool:
275                    try:
276                        canvas.mapTool().reset()
277                    except:
278                        pass
279                    canvas.setMapTool(prevMapTool)
280            else:
281                feedback = MessageBarProgress(algname=alg.displayName())
282                context = dataobjects.createContext(feedback)
283                parameters = {}
284                ret, results = execute(alg, parameters, context, feedback)
285                handleAlgorithmResults(alg, context, feedback)
286                feedback.close()
287