mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-10 00:04:23 -04:00
Also add new optional output a table containing calculated statistics similar to the Statistics by categories algorithm (fix #46241)
345 lines
16 KiB
Python
345 lines
16 KiB
Python
"""
|
|
***************************************************************************
|
|
menus.py
|
|
---------------------
|
|
Date : February 2016
|
|
Copyright : (C) 2016 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__ = 'February 2016'
|
|
__copyright__ = '(C) 2016, Victor Olaya'
|
|
|
|
import os
|
|
from qgis.PyQt.QtCore import QCoreApplication
|
|
from qgis.PyQt.QtWidgets import QAction, QMenu, QToolButton
|
|
from qgis.PyQt.QtGui import QIcon
|
|
from qgis.PyQt.QtWidgets import QApplication
|
|
from processing.core.ProcessingConfig import ProcessingConfig, Setting
|
|
from processing.gui.MessageDialog import MessageDialog
|
|
from processing.gui.AlgorithmDialog import AlgorithmDialog
|
|
from qgis.utils import iface
|
|
from qgis.core import QgsApplication, QgsMessageLog, QgsStringUtils, QgsProcessingAlgorithm
|
|
from qgis.gui import QgsGui
|
|
from processing.gui.MessageBarProgress import MessageBarProgress
|
|
from processing.gui.AlgorithmExecutor import execute
|
|
from processing.gui.Postprocessing import handleAlgorithmResults
|
|
from processing.core.Processing import Processing
|
|
from processing.tools import dataobjects
|
|
|
|
algorithmsToolbar = None
|
|
menusSettingsGroup = 'Menus'
|
|
defaultMenuEntries = {}
|
|
toolBarButtons = []
|
|
toolButton = None
|
|
toolButtonAction = None
|
|
|
|
|
|
def initMenusAndToolbars():
|
|
global defaultMenuEntries, toolBarButtons, toolButton, toolButtonAction
|
|
vectorMenu = iface.vectorMenu().title()
|
|
analysisToolsMenu = vectorMenu + "/" + Processing.tr('&Analysis Tools')
|
|
defaultMenuEntries.update({'qgis:distancematrix': analysisToolsMenu,
|
|
'native:sumlinelengths': analysisToolsMenu,
|
|
'native:countpointsinpolygon': analysisToolsMenu,
|
|
'qgis:listuniquevalues': analysisToolsMenu,
|
|
'native:basicstatisticsforfields': analysisToolsMenu,
|
|
'native:nearestneighbouranalysis': analysisToolsMenu,
|
|
'native:meancoordinates': analysisToolsMenu,
|
|
'native:lineintersections': analysisToolsMenu})
|
|
researchToolsMenu = vectorMenu + "/" + Processing.tr('&Research Tools')
|
|
defaultMenuEntries.update({'native:creategrid': researchToolsMenu,
|
|
'qgis:randomselection': researchToolsMenu,
|
|
'qgis:randomselectionwithinsubsets': researchToolsMenu,
|
|
'native:randompointsinextent': researchToolsMenu,
|
|
'qgis:randompointsinlayerbounds': researchToolsMenu,
|
|
'native:randompointsinpolygons': researchToolsMenu,
|
|
'qgis:randompointsinsidepolygons': researchToolsMenu,
|
|
'native:randompointsonlines': researchToolsMenu,
|
|
'qgis:regularpoints': researchToolsMenu,
|
|
'native:selectbylocation': researchToolsMenu,
|
|
'native:selectwithindistance': researchToolsMenu,
|
|
'native:polygonfromlayerextent': researchToolsMenu})
|
|
geoprocessingToolsMenu = vectorMenu + "/" + Processing.tr('&Geoprocessing Tools')
|
|
defaultMenuEntries.update({'native:buffer': geoprocessingToolsMenu,
|
|
'native:convexhull': geoprocessingToolsMenu,
|
|
'native:intersection': geoprocessingToolsMenu,
|
|
'native:union': geoprocessingToolsMenu,
|
|
'native:symmetricaldifference': geoprocessingToolsMenu,
|
|
'native:clip': geoprocessingToolsMenu,
|
|
'native:difference': geoprocessingToolsMenu,
|
|
'native:dissolve': geoprocessingToolsMenu,
|
|
'qgis:eliminateselectedpolygons': geoprocessingToolsMenu})
|
|
geometryToolsMenu = vectorMenu + "/" + Processing.tr('G&eometry Tools')
|
|
defaultMenuEntries.update({'qgis:checkvalidity': geometryToolsMenu,
|
|
'qgis:exportaddgeometrycolumns': geometryToolsMenu,
|
|
'native:centroids': geometryToolsMenu,
|
|
'native:delaunaytriangulation': geometryToolsMenu,
|
|
'native:voronoipolygons': geometryToolsMenu,
|
|
'native:simplifygeometries': geometryToolsMenu,
|
|
'native:densifygeometries': geometryToolsMenu,
|
|
'native:multiparttosingleparts': geometryToolsMenu,
|
|
'native:collect': geometryToolsMenu,
|
|
'native:polygonstolines': geometryToolsMenu,
|
|
'qgis:linestopolygons': geometryToolsMenu,
|
|
'native:extractvertices': geometryToolsMenu})
|
|
managementToolsMenu = vectorMenu + "/" + Processing.tr('&Data Management Tools')
|
|
defaultMenuEntries.update({'native:reprojectlayer': managementToolsMenu,
|
|
'native:joinattributesbylocation': managementToolsMenu,
|
|
'native:splitvectorlayer': managementToolsMenu,
|
|
'native:mergevectorlayers': managementToolsMenu,
|
|
'native:createspatialindex': managementToolsMenu})
|
|
|
|
rasterMenu = iface.rasterMenu().title()
|
|
defaultMenuEntries.update({'native:alignrasters': rasterMenu})
|
|
projectionsMenu = rasterMenu + "/" + Processing.tr('Projections')
|
|
defaultMenuEntries.update({'gdal:warpreproject': projectionsMenu,
|
|
'gdal:extractprojection': projectionsMenu,
|
|
'gdal:assignprojection': projectionsMenu})
|
|
conversionMenu = rasterMenu + "/" + Processing.tr('Conversion')
|
|
defaultMenuEntries.update({'gdal:rasterize': conversionMenu,
|
|
'gdal:polygonize': conversionMenu,
|
|
'gdal:translate': conversionMenu,
|
|
'gdal:rgbtopct': conversionMenu,
|
|
'gdal:pcttorgb': conversionMenu})
|
|
extractionMenu = rasterMenu + "/" + Processing.tr('Extraction')
|
|
defaultMenuEntries.update({'gdal:contour': extractionMenu,
|
|
'gdal:cliprasterbyextent': extractionMenu,
|
|
'gdal:cliprasterbymasklayer': extractionMenu})
|
|
analysisMenu = rasterMenu + "/" + Processing.tr('Analysis')
|
|
defaultMenuEntries.update({'gdal:sieve': analysisMenu,
|
|
'gdal:nearblack': analysisMenu,
|
|
'gdal:fillnodata': analysisMenu,
|
|
'gdal:proximity': analysisMenu,
|
|
'gdal:griddatametrics': analysisMenu,
|
|
'gdal:gridaverage': analysisMenu,
|
|
'gdal:gridinversedistance': analysisMenu,
|
|
'gdal:gridnearestneighbor': analysisMenu,
|
|
'gdal:aspect': analysisMenu,
|
|
'gdal:hillshade': analysisMenu,
|
|
'gdal:roughness': analysisMenu,
|
|
'gdal:slope': analysisMenu,
|
|
'gdal:tpitopographicpositionindex': analysisMenu,
|
|
'gdal:triterrainruggednessindex': analysisMenu})
|
|
miscMenu = rasterMenu + "/" + Processing.tr('Miscellaneous')
|
|
defaultMenuEntries.update({'gdal:buildvirtualraster': miscMenu,
|
|
'gdal:merge': miscMenu,
|
|
'gdal:gdalinfo': miscMenu,
|
|
'gdal:overviews': miscMenu,
|
|
'gdal:tileindex': miscMenu})
|
|
|
|
toolBarButtons = ['native:selectbylocation', 'native:selectwithindistance']
|
|
|
|
toolbar = iface.selectionToolBar()
|
|
toolButton = QToolButton(toolbar)
|
|
toolButton.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
|
|
toolButtonAction = toolbar.addWidget(toolButton)
|
|
|
|
|
|
if iface is not None:
|
|
initMenusAndToolbars()
|
|
|
|
|
|
def initializeMenus():
|
|
for m in defaultMenuEntries.keys():
|
|
alg = QgsApplication.processingRegistry().algorithmById(m)
|
|
if alg is None or alg.id() != m:
|
|
QgsMessageLog.logMessage(Processing.tr('Invalid algorithm ID for menu: {}').format(m),
|
|
Processing.tr('Processing'))
|
|
|
|
for provider in QgsApplication.processingRegistry().providers():
|
|
for alg in provider.algorithms():
|
|
d = defaultMenuEntries.get(alg.id(), "")
|
|
setting = Setting(menusSettingsGroup, "MENU_" + alg.id(),
|
|
"Menu path", d)
|
|
ProcessingConfig.addSetting(setting)
|
|
setting = Setting(menusSettingsGroup, "BUTTON_" + alg.id(),
|
|
"Add button", False)
|
|
ProcessingConfig.addSetting(setting)
|
|
setting = Setting(menusSettingsGroup, "ICON_" + alg.id(),
|
|
"Icon", "", valuetype=Setting.FILE)
|
|
ProcessingConfig.addSetting(setting)
|
|
|
|
ProcessingConfig.readSettings()
|
|
|
|
|
|
def updateMenus():
|
|
removeMenus()
|
|
QCoreApplication.processEvents()
|
|
createMenus()
|
|
|
|
|
|
def createMenus():
|
|
for alg in QgsApplication.processingRegistry().algorithms():
|
|
menuPath = ProcessingConfig.getSetting("MENU_" + alg.id())
|
|
addButton = ProcessingConfig.getSetting("BUTTON_" + alg.id())
|
|
icon = ProcessingConfig.getSetting("ICON_" + alg.id())
|
|
if icon and os.path.exists(icon):
|
|
icon = QIcon(icon)
|
|
else:
|
|
icon = None
|
|
if menuPath:
|
|
paths = menuPath.split("/")
|
|
subMenuName = paths[-1] if len(paths) > 1 else ""
|
|
addAlgorithmEntry(alg, paths[0], subMenuName, addButton=addButton, icon=icon)
|
|
|
|
|
|
def removeMenus():
|
|
for alg in QgsApplication.processingRegistry().algorithms():
|
|
menuPath = ProcessingConfig.getSetting("MENU_" + alg.id())
|
|
if menuPath:
|
|
paths = menuPath.split("/")
|
|
removeAlgorithmEntry(alg, paths[0], paths[-1])
|
|
|
|
|
|
def addAlgorithmEntry(alg, menuName, submenuName, actionText=None, icon=None, addButton=False):
|
|
if actionText is None:
|
|
if (QgsGui.higFlags() & QgsGui.HigFlag.HigMenuTextIsTitleCase) and not (
|
|
alg.flags() & QgsProcessingAlgorithm.Flag.FlagDisplayNameIsLiteral):
|
|
alg_title = QgsStringUtils.capitalize(alg.displayName(), QgsStringUtils.Capitalization.TitleCase)
|
|
else:
|
|
alg_title = alg.displayName()
|
|
actionText = alg_title + QCoreApplication.translate('Processing', '…')
|
|
action = QAction(icon or alg.icon(), actionText, iface.mainWindow())
|
|
alg_id = alg.id()
|
|
action.setData(alg_id)
|
|
action.triggered.connect(lambda: _executeAlgorithm(alg_id))
|
|
action.setObjectName("mProcessingUserMenu_%s" % alg_id)
|
|
|
|
if menuName:
|
|
menu = getMenu(menuName, iface.mainWindow().menuBar())
|
|
if submenuName:
|
|
submenu = getMenu(submenuName, menu)
|
|
submenu.addAction(action)
|
|
else:
|
|
menu.addAction(action)
|
|
|
|
if addButton:
|
|
global algorithmsToolbar
|
|
if algorithmsToolbar is None:
|
|
algorithmsToolbar = iface.addToolBar(QCoreApplication.translate('MainWindow', 'Processing Algorithms'))
|
|
algorithmsToolbar.setObjectName("ProcessingAlgorithms")
|
|
algorithmsToolbar.setToolTip(QCoreApplication.translate('MainWindow', 'Processing Algorithms Toolbar'))
|
|
algorithmsToolbar.addAction(action)
|
|
|
|
|
|
def removeAlgorithmEntry(alg, menuName, submenuName, delButton=True):
|
|
if menuName:
|
|
menu = getMenu(menuName, iface.mainWindow().menuBar())
|
|
subMenu = getMenu(submenuName, menu)
|
|
action = findAction(subMenu.actions(), alg)
|
|
if action is not None:
|
|
subMenu.removeAction(action)
|
|
|
|
if len(subMenu.actions()) == 0:
|
|
subMenu.deleteLater()
|
|
|
|
if delButton:
|
|
global algorithmsToolbar
|
|
if algorithmsToolbar is not None:
|
|
action = findAction(algorithmsToolbar.actions(), alg)
|
|
if action is not None:
|
|
algorithmsToolbar.removeAction(action)
|
|
|
|
|
|
def _executeAlgorithm(alg_id):
|
|
alg = QgsApplication.processingRegistry().createAlgorithmById(alg_id)
|
|
if alg is None:
|
|
dlg = MessageDialog()
|
|
dlg.setTitle(Processing.tr('Missing Algorithm'))
|
|
dlg.setMessage(
|
|
Processing.tr('The algorithm "{}" is no longer available. (Perhaps a plugin was uninstalled?)').format(
|
|
alg_id))
|
|
dlg.exec()
|
|
return
|
|
|
|
ok, message = alg.canExecute()
|
|
if not ok:
|
|
dlg = MessageDialog()
|
|
dlg.setTitle(Processing.tr('Missing Dependency'))
|
|
dlg.setMessage(
|
|
Processing.tr('<h3>Missing dependency. This algorithm cannot '
|
|
'be run :-( </h3>\n{0}').format(message))
|
|
dlg.exec()
|
|
return
|
|
|
|
if (alg.countVisibleParameters()) > 0:
|
|
dlg = alg.createCustomParametersWidget(parent=iface.mainWindow())
|
|
if not dlg:
|
|
dlg = AlgorithmDialog(alg, parent=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()
|
|
context = dataobjects.createContext(feedback)
|
|
parameters = {}
|
|
ret, results = execute(alg, parameters, context, feedback)
|
|
handleAlgorithmResults(alg, context, feedback)
|
|
feedback.close()
|
|
|
|
|
|
def getMenu(name, parent):
|
|
menus = [c for c in parent.children() if isinstance(c, QMenu) and c.title() == name]
|
|
if menus:
|
|
return menus[0]
|
|
else:
|
|
return parent.addMenu(name)
|
|
|
|
|
|
def findAction(actions, alg):
|
|
for action in actions:
|
|
if (isinstance(alg, str) and action.data() == alg) or (
|
|
isinstance(alg, QgsProcessingAlgorithm) and action.data() == alg.id()):
|
|
return action
|
|
return None
|
|
|
|
|
|
def addToolBarButton(index, algId, icon=None, tooltip=None):
|
|
alg = QgsApplication.processingRegistry().algorithmById(algId)
|
|
if alg is None or alg.id() != algId:
|
|
assert False, algId
|
|
|
|
if tooltip is None:
|
|
if (QgsGui.higFlags() & QgsGui.HigFlag.HigMenuTextIsTitleCase) and not (
|
|
alg.flags() & QgsProcessingAlgorithm.Flag.FlagDisplayNameIsLiteral):
|
|
tooltip = QgsStringUtils.capitalize(alg.displayName(), QgsStringUtils.Capitalization.TitleCase)
|
|
else:
|
|
tooltip = alg.displayName()
|
|
|
|
action = QAction(icon or alg.icon(), tooltip, iface.mainWindow())
|
|
algId = alg.id()
|
|
action.setData(algId)
|
|
action.triggered.connect(lambda: _executeAlgorithm(algId))
|
|
action.setObjectName("mProcessingAlg_%s" % algId)
|
|
|
|
toolButton.addAction(action)
|
|
if index == 0:
|
|
toolButton.setDefaultAction(action)
|
|
|
|
|
|
def createButtons():
|
|
toolbar = iface.selectionToolBar()
|
|
for index, algId in enumerate(toolBarButtons):
|
|
addToolBarButton(index, algId)
|
|
|
|
|
|
def removeButtons():
|
|
iface.selectionToolBar().removeAction(toolButtonAction)
|