Skip to content

Commit ad54073

Browse files
authored
[processing] list native QGIS algorithms first in modeler dialog
1 parent 13cb44f commit ad54073

File tree

1 file changed

+191
-71
lines changed

1 file changed

+191
-71
lines changed

python/plugins/processing/modeler/ModelerDialog.py

+191-71
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@
2727

2828
import codecs
2929
import sys
30+
import operator
3031
import os
3132
import math
3233

3334
from qgis.PyQt import uic
34-
from qgis.PyQt.QtCore import Qt, QRectF, QMimeData, QPoint, QPointF, QByteArray, QSize, QSizeF, pyqtSignal
35+
from qgis.PyQt.QtCore import Qt, QCoreApplication, QRectF, QMimeData, QPoint, QPointF, QByteArray, QSize, QSizeF, pyqtSignal
3536
from qgis.PyQt.QtWidgets import QGraphicsView, QTreeWidget, QMessageBox, QFileDialog, QTreeWidgetItem, QSizePolicy, QMainWindow, QShortcut
3637
from qgis.PyQt.QtGui import QIcon, QImage, QPainter, QKeySequence
3738
from qgis.PyQt.QtSvg import QSvgGenerator
@@ -62,6 +63,13 @@
6263

6364

6465
class ModelerDialog(BASE, WIDGET):
66+
ALG_ITEM = 'ALG_ITEM'
67+
PROVIDER_ITEM = 'PROVIDER_ITEM'
68+
GROUP_ITEM = 'GROUP_ITEM'
69+
70+
NAME_ROLE = Qt.UserRole
71+
TAG_ROLE = Qt.UserRole + 1
72+
TYPE_ROLE = Qt.UserRole + 2
6573

6674
CANVAS_SIZE = 4000
6775

@@ -239,7 +247,7 @@ def _mimeDataAlgorithm(items):
239247

240248
# Connect signals and slots
241249
self.inputsTree.doubleClicked.connect(self.addInput)
242-
self.searchBox.textChanged.connect(self.fillAlgorithmTree)
250+
self.searchBox.textChanged.connect(self.textChanged)
243251
self.algorithmTree.doubleClicked.connect(self.addAlgorithm)
244252

245253
# Ctrl+= should also trigger a zoom in action
@@ -272,7 +280,7 @@ def _mimeDataAlgorithm(items):
272280
self.model.setProvider(QgsApplication.processingRegistry().providerById('model'))
273281

274282
self.fillInputsTree()
275-
self.fillAlgorithmTree()
283+
self.fillTreeUsingProviders()
276284

277285
self.view.centerOn(0, 0)
278286
self.help = None
@@ -563,6 +571,49 @@ def getPositionForParameterItem(self):
563571
newX = MARGIN + BOX_WIDTH / 2
564572
return QPointF(newX, MARGIN + BOX_HEIGHT / 2)
565573

574+
def textChanged(self):
575+
text = self.searchBox.text().strip(' ').lower()
576+
for item in list(self.disabledProviderItems.values()):
577+
item.setHidden(True)
578+
self._filterItem(self.algorithmTree.invisibleRootItem(), [t for t in text.split(' ') if t])
579+
if text:
580+
self.algorithmTree.expandAll()
581+
self.disabledWithMatchingAlgs = []
582+
for provider in QgsApplication.processingRegistry().providers():
583+
if not provider.isActive():
584+
for alg in provider.algorithms():
585+
if text in alg.name():
586+
self.disabledWithMatchingAlgs.append(provider.id())
587+
break
588+
else:
589+
self.algorithmTree.collapseAll()
590+
591+
def _filterItem(self, item, text):
592+
if (item.childCount() > 0):
593+
show = False
594+
for i in range(item.childCount()):
595+
child = item.child(i)
596+
showChild = self._filterItem(child, text)
597+
show = (showChild or show) and item not in list(self.disabledProviderItems.values())
598+
item.setHidden(not show)
599+
return show
600+
elif isinstance(item, (TreeAlgorithmItem, TreeActionItem)):
601+
# hide if every part of text is not contained somewhere in either the item text or item user role
602+
item_text = [item.text(0).lower(), item.data(0, ModelerDialog.NAME_ROLE).lower()]
603+
if isinstance(item, TreeAlgorithmItem):
604+
item_text.append(item.alg.id())
605+
item_text.extend(item.data(0, ModelerDialog.TAG_ROLE))
606+
607+
hide = bool(text) and not all(
608+
any(part in t for t in item_text)
609+
for part in text)
610+
611+
item.setHidden(hide)
612+
return not hide
613+
else:
614+
item.setHidden(True)
615+
return False
616+
566617
def fillInputsTree(self):
567618
icon = QIcon(os.path.join(pluginPath, 'images', 'input.svg'))
568619
parametersItem = QTreeWidgetItem()
@@ -619,88 +670,157 @@ def getPositionForAlgorithmItem(self):
619670
newY = MARGIN * 2 + BOX_HEIGHT + BOX_HEIGHT / 2
620671
return QPointF(newX, newY)
621672

622-
def fillAlgorithmTree(self):
623-
self.fillAlgorithmTreeUsingProviders()
624-
self.algorithmTree.sortItems(0, Qt.AscendingOrder)
673+
def fillTreeUsingProviders(self):
674+
self.algorithmTree.clear()
675+
self.disabledProviderItems = {}
625676

626-
text = str(self.searchBox.text())
627-
if text != '':
628-
self.algorithmTree.expandAll()
677+
# TODO - replace with proper model for toolbox!
629678

630-
def fillAlgorithmTreeUsingProviders(self):
631-
self.algorithmTree.clear()
632-
text = str(self.searchBox.text())
633-
search_strings = text.split(' ')
634-
qgis_groups = {}
679+
# first add qgis/native providers, since they create top level groups
635680
for provider in QgsApplication.processingRegistry().providers():
636-
if not provider.isActive():
681+
if provider.id() in ('qgis', 'native', '3d'):
682+
self.addAlgorithmsFromProvider(provider, self.algorithmTree.invisibleRootItem())
683+
else:
637684
continue
638-
groups = {}
639-
640-
# Add algorithms
641-
for alg in provider.algorithms():
642-
if alg.flags() & QgsProcessingAlgorithm.FlagHideFromModeler:
643-
continue
644-
if alg.id() == self.model.id():
645-
continue
646-
647-
item_text = [alg.displayName().lower()]
648-
item_text.extend(alg.tags())
649-
650-
show = not search_strings or all(
651-
any(part in t for t in item_text)
652-
for part in search_strings)
653-
654-
if show:
655-
if alg.group() in groups:
656-
groupItem = groups[alg.group()]
657-
elif provider.id() in ('qgis', 'native', '3d') and alg.group() in qgis_groups:
658-
groupItem = qgis_groups[alg.group()]
659-
else:
660-
groupItem = QTreeWidgetItem()
661-
name = alg.group()
662-
groupItem.setText(0, name)
663-
groupItem.setToolTip(0, name)
664-
groupItem.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
665-
if provider.id() in ('qgis', 'native', '3d'):
666-
groupItem.setIcon(0, provider.icon())
667-
qgis_groups[alg.group()] = groupItem
668-
else:
669-
groups[alg.group()] = groupItem
670-
algItem = TreeAlgorithmItem(alg)
671-
groupItem.addChild(algItem)
672-
673-
if len(groups) > 0:
674-
providerItem = QTreeWidgetItem()
675-
providerItem.setText(0, provider.name())
676-
providerItem.setToolTip(0, provider.name())
677-
providerItem.setIcon(0, provider.icon())
678-
providerItem.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
679-
for groupItem in list(groups.values()):
680-
providerItem.addChild(groupItem)
681-
self.algorithmTree.addTopLevelItem(providerItem)
682-
providerItem.setExpanded(text != '')
683-
for groupItem in list(groups.values()):
684-
if text != '':
685-
groupItem.setExpanded(True)
686-
687-
if len(qgis_groups) > 0:
688-
for groupItem in list(qgis_groups.values()):
689-
self.algorithmTree.addTopLevelItem(groupItem)
690-
for groupItem in list(qgis_groups.values()):
691-
if text != '':
692-
groupItem.setExpanded(True)
693-
694685
self.algorithmTree.sortItems(0, Qt.AscendingOrder)
695686

687+
for provider in QgsApplication.processingRegistry().providers():
688+
if provider.id() in ('qgis', 'native', '3d'):
689+
# already added
690+
continue
691+
else:
692+
providerItem = TreeProviderItem(provider, self.algorithmTree, self)
693+
694+
if not provider.isActive():
695+
providerItem.setHidden(True)
696+
self.disabledProviderItems[provider.id()] = providerItem
697+
698+
# insert non-native providers at end of tree, alphabetically
699+
700+
for i in range(self.algorithmTree.invisibleRootItem().childCount()):
701+
child = self.algorithmTree.invisibleRootItem().child(i)
702+
if isinstance(child, TreeProviderItem):
703+
if child.text(0) > providerItem.text(0):
704+
break
705+
706+
self.algorithmTree.insertTopLevelItem(i + 1, providerItem)
707+
708+
def addAlgorithmsFromProvider(self, provider, parent):
709+
groups = {}
710+
count = 0
711+
algs = provider.algorithms()
712+
active = provider.isActive()
713+
714+
# Add algorithms
715+
for alg in algs:
716+
if alg.flags() & QgsProcessingAlgorithm.FlagHideFromToolbox:
717+
continue
718+
groupItem = None
719+
if alg.group() in groups:
720+
groupItem = groups[alg.group()]
721+
else:
722+
# check if group already exists
723+
for i in range(parent.childCount()):
724+
if parent.child(i).text(0) == alg.group():
725+
groupItem = parent.child(i)
726+
groups[alg.group()] = groupItem
727+
break
728+
729+
if not groupItem:
730+
groupItem = TreeGroupItem(alg.group())
731+
if not active:
732+
groupItem.setInactive()
733+
if provider.id() in ('qgis', 'native', '3d'):
734+
groupItem.setIcon(0, provider.icon())
735+
groups[alg.group()] = groupItem
736+
algItem = TreeAlgorithmItem(alg)
737+
if not active:
738+
algItem.setForeground(0, Qt.darkGray)
739+
groupItem.addChild(algItem)
740+
count += 1
741+
742+
text = provider.name()
743+
744+
if not provider.id() in ('qgis', 'native', '3d'):
745+
if not active:
746+
def activateProvider():
747+
self.activateProvider(provider.id())
748+
749+
label = QLabel(text + "&nbsp;&nbsp;&nbsp;&nbsp;<a href='%s'>Activate</a>")
750+
label.setStyleSheet("QLabel {background-color: white; color: grey;}")
751+
label.linkActivated.connect(activateProvider)
752+
self.algorithmTree.setItemWidget(parent, 0, label)
753+
else:
754+
parent.setText(0, text)
755+
756+
for group, groupItem in sorted(groups.items(), key=operator.itemgetter(1)):
757+
parent.addChild(groupItem)
758+
759+
if not provider.id() in ('qgis', 'native', '3d'):
760+
parent.setHidden(parent.childCount() == 0)
761+
696762

697763
class TreeAlgorithmItem(QTreeWidgetItem):
698764

699765
def __init__(self, alg):
700766
QTreeWidgetItem.__init__(self)
701767
self.alg = alg
702768
icon = alg.icon()
769+
nameEn = alg.name()
703770
name = alg.displayName()
771+
name = name if name != '' else nameEn
704772
self.setIcon(0, icon)
773+
self.setToolTip(0, self.formatAlgorithmTooltip(alg))
774+
self.setText(0, name)
775+
self.setData(0, ModelerDialog.NAME_ROLE, nameEn)
776+
self.setData(0, ModelerDialog.TAG_ROLE, alg.tags())
777+
self.setData(0, ModelerDialog.TYPE_ROLE, ModelerDialog.ALG_ITEM)
778+
779+
def formatAlgorithmTooltip(self, alg):
780+
return '<p><b>{}</b></p><p>{}</p>'.format(
781+
alg.displayName(),
782+
QCoreApplication.translate('Toolbox', 'Algorithm ID: ‘{}’').format('<i>{}</i>'.format(alg.id()))
783+
)
784+
785+
786+
class TreeGroupItem(QTreeWidgetItem):
787+
788+
def __init__(self, name):
789+
QTreeWidgetItem.__init__(self)
705790
self.setToolTip(0, name)
706791
self.setText(0, name)
792+
self.setData(0, ModelerDialog.NAME_ROLE, name)
793+
self.setData(0, ModelerDialog.TYPE_ROLE, ModelerDialog.GROUP_ITEM)
794+
795+
def setInactive(self):
796+
self.setForeground(0, Qt.darkGray)
797+
798+
799+
class TreeActionItem(QTreeWidgetItem):
800+
801+
def __init__(self, action):
802+
QTreeWidgetItem.__init__(self)
803+
self.action = action
804+
self.setText(0, action.i18n_name)
805+
self.setIcon(0, action.getIcon())
806+
self.setData(0, ModelerDialog.NAME_ROLE, action.name)
807+
808+
809+
class TreeProviderItem(QTreeWidgetItem):
810+
811+
def __init__(self, provider, tree, toolbox):
812+
QTreeWidgetItem.__init__(self, None)
813+
self.tree = tree
814+
self.toolbox = toolbox
815+
self.provider = provider
816+
self.setIcon(0, self.provider.icon())
817+
self.setData(0, ModelerDialog.TYPE_ROLE, ModelerDialog.PROVIDER_ITEM)
818+
self.setToolTip(0, self.provider.longName())
819+
self.populate()
820+
821+
def refresh(self):
822+
self.takeChildren()
823+
self.populate()
824+
825+
def populate(self):
826+
self.toolbox.addAlgorithmsFromProvider(self.provider, self)

0 commit comments

Comments
 (0)