menus.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. """
  2. ***************************************************************************
  3. menus.py
  4. ---------------------
  5. Date : February 2016
  6. Copyright : (C) 2016 by Victor Olaya
  7. Email : volayaf at gmail dot com
  8. ***************************************************************************
  9. * *
  10. * This program is free software; you can redistribute it and/or modify *
  11. * it under the terms of the GNU General Public License as published by *
  12. * the Free Software Foundation; either version 2 of the License, or *
  13. * (at your option) any later version. *
  14. * *
  15. ***************************************************************************
  16. """
  17. __author__ = 'Victor Olaya'
  18. __date__ = 'February 2016'
  19. __copyright__ = '(C) 2016, Victor Olaya'
  20. import os
  21. from qgis.PyQt.QtCore import QCoreApplication
  22. from qgis.PyQt.QtWidgets import QAction, QMenu, QToolButton
  23. from qgis.PyQt.QtGui import QIcon
  24. from qgis.PyQt.QtWidgets import QApplication
  25. from processing.core.ProcessingConfig import ProcessingConfig, Setting
  26. from processing.gui.MessageDialog import MessageDialog
  27. from processing.gui.AlgorithmDialog import AlgorithmDialog
  28. from qgis.utils import iface
  29. from qgis.core import QgsApplication, QgsMessageLog, QgsStringUtils, QgsProcessingAlgorithm
  30. from qgis.gui import QgsGui
  31. from processing.gui.MessageBarProgress import MessageBarProgress
  32. from processing.gui.AlgorithmExecutor import execute
  33. from processing.gui.Postprocessing import handleAlgorithmResults
  34. from processing.core.Processing import Processing
  35. from processing.tools import dataobjects
  36. algorithmsToolbar = None
  37. menusSettingsGroup = 'Menus'
  38. defaultMenuEntries = {}
  39. toolBarButtons = []
  40. toolButton = None
  41. toolButtonAction = None
  42. def initMenusAndToolbars():
  43. global defaultMenuEntries, toolBarButtons, toolButton, toolButtonAction
  44. vectorMenu = iface.vectorMenu().title()
  45. analysisToolsMenu = vectorMenu + "/" + Processing.tr('&Analysis Tools')
  46. defaultMenuEntries.update({'qgis:distancematrix': analysisToolsMenu,
  47. 'native:sumlinelengths': analysisToolsMenu,
  48. 'native:countpointsinpolygon': analysisToolsMenu,
  49. 'qgis:listuniquevalues': analysisToolsMenu,
  50. 'qgis:basicstatisticsforfields': analysisToolsMenu,
  51. 'native:nearestneighbouranalysis': analysisToolsMenu,
  52. 'native:meancoordinates': analysisToolsMenu,
  53. 'native:lineintersections': analysisToolsMenu})
  54. researchToolsMenu = vectorMenu + "/" + Processing.tr('&Research Tools')
  55. defaultMenuEntries.update({'native:creategrid': researchToolsMenu,
  56. 'qgis:randomselection': researchToolsMenu,
  57. 'qgis:randomselectionwithinsubsets': researchToolsMenu,
  58. 'native:randompointsinextent': researchToolsMenu,
  59. 'qgis:randompointsinlayerbounds': researchToolsMenu,
  60. 'native:randompointsinpolygons': researchToolsMenu,
  61. 'qgis:randompointsinsidepolygons': researchToolsMenu,
  62. 'native:randompointsonlines': researchToolsMenu,
  63. 'qgis:regularpoints': researchToolsMenu,
  64. 'native:selectbylocation': researchToolsMenu,
  65. 'native:selectwithindistance': researchToolsMenu,
  66. 'native:polygonfromlayerextent': researchToolsMenu})
  67. geoprocessingToolsMenu = vectorMenu + "/" + Processing.tr('&Geoprocessing Tools')
  68. defaultMenuEntries.update({'native:buffer': geoprocessingToolsMenu,
  69. 'native:convexhull': geoprocessingToolsMenu,
  70. 'native:intersection': geoprocessingToolsMenu,
  71. 'native:union': geoprocessingToolsMenu,
  72. 'native:symmetricaldifference': geoprocessingToolsMenu,
  73. 'native:clip.py': geoprocessingToolsMenu,
  74. 'native:difference': geoprocessingToolsMenu,
  75. 'native:dissolve': geoprocessingToolsMenu,
  76. 'qgis:eliminateselectedpolygons': geoprocessingToolsMenu})
  77. geometryToolsMenu = vectorMenu + "/" + Processing.tr('G&eometry Tools')
  78. defaultMenuEntries.update({'qgis:checkvalidity': geometryToolsMenu,
  79. 'qgis:exportaddgeometrycolumns': geometryToolsMenu,
  80. 'native:centroids': geometryToolsMenu,
  81. 'native:delaunaytriangulation': geometryToolsMenu,
  82. 'native:voronoipolygons': geometryToolsMenu,
  83. 'native:simplifygeometries': geometryToolsMenu,
  84. 'native:densifygeometries': geometryToolsMenu,
  85. 'native:multiparttosingleparts': geometryToolsMenu,
  86. 'native:collect': geometryToolsMenu,
  87. 'native:polygonstolines': geometryToolsMenu,
  88. 'qgis:linestopolygons': geometryToolsMenu,
  89. 'native:extractvertices': geometryToolsMenu})
  90. managementToolsMenu = vectorMenu + "/" + Processing.tr('&Data Management Tools')
  91. defaultMenuEntries.update({'native:reprojectlayer': managementToolsMenu,
  92. 'native:joinattributesbylocation': managementToolsMenu,
  93. 'native:splitvectorlayer': managementToolsMenu,
  94. 'native:mergevectorlayers': managementToolsMenu,
  95. 'native:createspatialindex': managementToolsMenu})
  96. rasterMenu = iface.rasterMenu().title()
  97. defaultMenuEntries.update({'native:alignrasters': rasterMenu})
  98. projectionsMenu = rasterMenu + "/" + Processing.tr('Projections')
  99. defaultMenuEntries.update({'gdal:warpreproject': projectionsMenu,
  100. 'gdal:extractprojection': projectionsMenu,
  101. 'gdal:assignprojection': projectionsMenu})
  102. conversionMenu = rasterMenu + "/" + Processing.tr('Conversion')
  103. defaultMenuEntries.update({'gdal:rasterize': conversionMenu,
  104. 'gdal:polygonize': conversionMenu,
  105. 'gdal:translate': conversionMenu,
  106. 'gdal:rgbtopct': conversionMenu,
  107. 'gdal:pcttorgb': conversionMenu})
  108. extractionMenu = rasterMenu + "/" + Processing.tr('Extraction')
  109. defaultMenuEntries.update({'gdal:contour': extractionMenu,
  110. 'gdal:cliprasterbyextent': extractionMenu,
  111. 'gdal:cliprasterbymasklayer': extractionMenu})
  112. analysisMenu = rasterMenu + "/" + Processing.tr('Analysis')
  113. defaultMenuEntries.update({'gdal:sieve': analysisMenu,
  114. 'gdal:nearblack': analysisMenu,
  115. 'gdal:fillnodata': analysisMenu,
  116. 'gdal:proximity': analysisMenu,
  117. 'gdal:griddatametrics': analysisMenu,
  118. 'gdal:gridaverage': analysisMenu,
  119. 'gdal:gridinversedistance': analysisMenu,
  120. 'gdal:gridnearestneighbor': analysisMenu,
  121. 'gdal:aspect': analysisMenu,
  122. 'gdal:hillshade': analysisMenu,
  123. 'gdal:roughness': analysisMenu,
  124. 'gdal:slope': analysisMenu,
  125. 'gdal:tpitopographicpositionindex': analysisMenu,
  126. 'gdal:triterrainruggednessindex': analysisMenu})
  127. miscMenu = rasterMenu + "/" + Processing.tr('Miscellaneous')
  128. defaultMenuEntries.update({'gdal:buildvirtualraster': miscMenu,
  129. 'gdal:merge': miscMenu,
  130. 'gdal:gdalinfo': miscMenu,
  131. 'gdal:overviews': miscMenu,
  132. 'gdal:tileindex': miscMenu})
  133. toolBarButtons = ['native:selectbylocation', 'native:selectwithindistance']
  134. toolbar = iface.selectionToolBar()
  135. toolButton = QToolButton(toolbar)
  136. toolButton.setPopupMode(QToolButton.MenuButtonPopup)
  137. toolButtonAction = toolbar.addWidget(toolButton)
  138. if iface is not None:
  139. initMenusAndToolbars()
  140. def initializeMenus():
  141. for m in defaultMenuEntries.keys():
  142. alg = QgsApplication.processingRegistry().algorithmById(m)
  143. if alg is None or alg.id() != m:
  144. QgsMessageLog.logMessage(Processing.tr('Invalid algorithm ID for menu: {}').format(m),
  145. Processing.tr('Processing'))
  146. for provider in QgsApplication.processingRegistry().providers():
  147. for alg in provider.algorithms():
  148. d = defaultMenuEntries.get(alg.id(), "")
  149. setting = Setting(menusSettingsGroup, "MENU_" + alg.id(),
  150. "Menu path", d)
  151. ProcessingConfig.addSetting(setting)
  152. setting = Setting(menusSettingsGroup, "BUTTON_" + alg.id(),
  153. "Add button", False)
  154. ProcessingConfig.addSetting(setting)
  155. setting = Setting(menusSettingsGroup, "ICON_" + alg.id(),
  156. "Icon", "", valuetype=Setting.FILE)
  157. ProcessingConfig.addSetting(setting)
  158. ProcessingConfig.readSettings()
  159. def updateMenus():
  160. removeMenus()
  161. QCoreApplication.processEvents()
  162. createMenus()
  163. def createMenus():
  164. for alg in QgsApplication.processingRegistry().algorithms():
  165. menuPath = ProcessingConfig.getSetting("MENU_" + alg.id())
  166. addButton = ProcessingConfig.getSetting("BUTTON_" + alg.id())
  167. icon = ProcessingConfig.getSetting("ICON_" + alg.id())
  168. if icon and os.path.exists(icon):
  169. icon = QIcon(icon)
  170. else:
  171. icon = None
  172. if menuPath:
  173. paths = menuPath.split("/")
  174. subMenuName = paths[-1] if len(paths) > 1 else ""
  175. addAlgorithmEntry(alg, paths[0], subMenuName, addButton=addButton, icon=icon)
  176. def removeMenus():
  177. for alg in QgsApplication.processingRegistry().algorithms():
  178. menuPath = ProcessingConfig.getSetting("MENU_" + alg.id())
  179. if menuPath:
  180. paths = menuPath.split("/")
  181. removeAlgorithmEntry(alg, paths[0], paths[-1])
  182. def addAlgorithmEntry(alg, menuName, submenuName, actionText=None, icon=None, addButton=False):
  183. if actionText is None:
  184. if (QgsGui.higFlags() & QgsGui.HigMenuTextIsTitleCase) and not (
  185. alg.flags() & QgsProcessingAlgorithm.FlagDisplayNameIsLiteral):
  186. alg_title = QgsStringUtils.capitalize(alg.displayName(), QgsStringUtils.TitleCase)
  187. else:
  188. alg_title = alg.displayName()
  189. actionText = alg_title + QCoreApplication.translate('Processing', '…')
  190. action = QAction(icon or alg.icon(), actionText, iface.mainWindow())
  191. alg_id = alg.id()
  192. action.setData(alg_id)
  193. action.triggered.connect(lambda: _executeAlgorithm(alg_id))
  194. action.setObjectName("mProcessingUserMenu_%s" % alg_id)
  195. if menuName:
  196. menu = getMenu(menuName, iface.mainWindow().menuBar())
  197. if submenuName:
  198. submenu = getMenu(submenuName, menu)
  199. submenu.addAction(action)
  200. else:
  201. menu.addAction(action)
  202. if addButton:
  203. global algorithmsToolbar
  204. if algorithmsToolbar is None:
  205. algorithmsToolbar = iface.addToolBar(QCoreApplication.translate('MainWindow', 'Processing Algorithms'))
  206. algorithmsToolbar.setObjectName("ProcessingAlgorithms")
  207. algorithmsToolbar.setToolTip(QCoreApplication.translate('MainWindow', 'Processing Algorithms Toolbar'))
  208. algorithmsToolbar.addAction(action)
  209. def removeAlgorithmEntry(alg, menuName, submenuName, delButton=True):
  210. if menuName:
  211. menu = getMenu(menuName, iface.mainWindow().menuBar())
  212. subMenu = getMenu(submenuName, menu)
  213. action = findAction(subMenu.actions(), alg)
  214. if action is not None:
  215. subMenu.removeAction(action)
  216. if len(subMenu.actions()) == 0:
  217. subMenu.deleteLater()
  218. if delButton:
  219. global algorithmsToolbar
  220. if algorithmsToolbar is not None:
  221. action = findAction(algorithmsToolbar.actions(), alg)
  222. if action is not None:
  223. algorithmsToolbar.removeAction(action)
  224. def _executeAlgorithm(alg_id):
  225. alg = QgsApplication.processingRegistry().createAlgorithmById(alg_id)
  226. if alg is None:
  227. dlg = MessageDialog()
  228. dlg.setTitle(Processing.tr('Missing Algorithm'))
  229. dlg.setMessage(
  230. Processing.tr('The algorithm "{}" is no longer available. (Perhaps a plugin was uninstalled?)').format(
  231. alg_id))
  232. dlg.exec_()
  233. return
  234. ok, message = alg.canExecute()
  235. if not ok:
  236. dlg = MessageDialog()
  237. dlg.setTitle(Processing.tr('Missing Dependency'))
  238. dlg.setMessage(
  239. Processing.tr('<h3>Missing dependency. This algorithm cannot '
  240. 'be run :-( </h3>\n{0}').format(message))
  241. dlg.exec_()
  242. return
  243. if (alg.countVisibleParameters()) > 0:
  244. dlg = alg.createCustomParametersWidget(parent=iface.mainWindow())
  245. if not dlg:
  246. dlg = AlgorithmDialog(alg, parent=iface.mainWindow())
  247. canvas = iface.mapCanvas()
  248. prevMapTool = canvas.mapTool()
  249. dlg.show()
  250. dlg.exec_()
  251. if canvas.mapTool() != prevMapTool:
  252. try:
  253. canvas.mapTool().reset()
  254. except:
  255. pass
  256. canvas.setMapTool(prevMapTool)
  257. else:
  258. feedback = MessageBarProgress()
  259. context = dataobjects.createContext(feedback)
  260. parameters = {}
  261. ret, results = execute(alg, parameters, context, feedback)
  262. handleAlgorithmResults(alg, context, feedback)
  263. feedback.close()
  264. def getMenu(name, parent):
  265. menus = [c for c in parent.children() if isinstance(c, QMenu) and c.title() == name]
  266. if menus:
  267. return menus[0]
  268. else:
  269. return parent.addMenu(name)
  270. def findAction(actions, alg):
  271. for action in actions:
  272. if (isinstance(alg, str) and action.data() == alg) or (
  273. isinstance(alg, QgsProcessingAlgorithm) and action.data() == alg.id()):
  274. return action
  275. return None
  276. def addToolBarButton(index, algId, icon=None, tooltip=None):
  277. alg = QgsApplication.processingRegistry().algorithmById(algId)
  278. if alg is None or alg.id() != algId:
  279. assert False, algId
  280. if tooltip is None:
  281. if (QgsGui.higFlags() & QgsGui.HigMenuTextIsTitleCase) and not (
  282. alg.flags() & QgsProcessingAlgorithm.FlagDisplayNameIsLiteral):
  283. tooltip = QgsStringUtils.capitalize(alg.displayName(), QgsStringUtils.TitleCase)
  284. else:
  285. tooltip = alg.displayName()
  286. action = QAction(icon or alg.icon(), tooltip, iface.mainWindow())
  287. algId = alg.id()
  288. action.setData(algId)
  289. action.triggered.connect(lambda: _executeAlgorithm(algId))
  290. action.setObjectName("mProcessingAlg_%s" % algId)
  291. toolButton.addAction(action)
  292. if index == 0:
  293. toolButton.setDefaultAction(action)
  294. def createButtons():
  295. toolbar = iface.selectionToolBar()
  296. for index, algId in enumerate(toolBarButtons):
  297. addToolBarButton(index, algId)
  298. def removeButtons():
  299. iface.selectionToolBar().removeAction(toolButtonAction)