mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-09 00:08:52 -04:00
418 lines
15 KiB
Python
418 lines
15 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(
|
|
{
|
|
"native:checkvalidity": geometryToolsMenu,
|
|
"native: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)
|