mirror of
				https://github.com/qgis/QGIS.git
				synced 2025-10-30 00:07:09 -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(
 | |
|         {
 | |
|             "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)
 |