Skip to content

Commit 68aae3a

Browse files
committed
Replace processing modeler toolbox with common widget/model
1 parent 1a9f68e commit 68aae3a

File tree

7 files changed

+94
-225
lines changed

7 files changed

+94
-225
lines changed

python/gui/auto_generated/processing/qgsprocessingtoolboxmodel.sip.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,8 @@ level group containing recently used algorithms.
284284

285285
virtual QModelIndex parent( const QModelIndex &index ) const;
286286

287+
virtual QMimeData *mimeData( const QModelIndexList &indexes ) const;
288+
287289

288290
QgsProcessingToolboxModelNode *index2node( const QModelIndex &index ) const;
289291
%Docstring

python/gui/auto_generated/processing/qgsprocessingtoolboxtreeview.sip.in

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ Sets the processing ``registry`` associated with the view.
4949

5050
If ``recentLog`` is specified then it will be used to create a "Recently used" top
5151
level group containing recently used algorithms.
52+
%End
53+
54+
void setToolboxProxyModel( QgsProcessingToolboxProxyModel *model /Transfer/ );
55+
%Docstring
56+
Sets the toolbox proxy model used to drive the view.
5257
%End
5358

5459
const QgsProcessingAlgorithm *algorithmForIndex( const QModelIndex &index );

python/plugins/processing/modeler/ModelerDialog.py

Lines changed: 54 additions & 225 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,19 @@
3232
import warnings
3333

3434
from qgis.PyQt import uic
35-
from qgis.PyQt.QtCore import Qt, QCoreApplication, QRectF, QMimeData, QPoint, QPointF, QByteArray, QSize, QSizeF, pyqtSignal
35+
from qgis.PyQt.QtCore import (
36+
Qt,
37+
QCoreApplication,
38+
QRectF,
39+
QMimeData,
40+
QPoint,
41+
QPointF,
42+
QByteArray,
43+
QSize,
44+
QSizeF,
45+
pyqtSignal,
46+
QDataStream,
47+
QIODevice)
3648
from qgis.PyQt.QtWidgets import (QGraphicsView,
3749
QTreeWidget,
3850
QMessageBox,
@@ -67,7 +79,9 @@
6779
from qgis.gui import (QgsMessageBar,
6880
QgsDockWidget,
6981
QgsScrollArea,
70-
QgsFilterLineEdit)
82+
QgsFilterLineEdit,
83+
QgsProcessingToolboxTreeView,
84+
QgsProcessingToolboxProxyModel)
7185
from processing.gui.HelpEditionDialog import HelpEditionDialog
7286
from processing.gui.AlgorithmDialog import AlgorithmDialog
7387
from processing.modeler.ModelerParameterDefinitionDialog import ModelerParameterDefinitionDialog
@@ -84,6 +98,22 @@
8498
os.path.join(pluginPath, 'ui', 'DlgModeler.ui'))
8599

86100

101+
class ModelerToolboxModel(QgsProcessingToolboxProxyModel):
102+
103+
def __init__(self, parent=None, registry=None, recentLog=None):
104+
super().__init__(parent, registry, recentLog)
105+
106+
def flags(self, index):
107+
f = super().flags(index)
108+
source_index = self.mapToSource(index)
109+
if self.toolboxModel().isAlgorithm(source_index):
110+
f = f | Qt.ItemIsDragEnabled
111+
return f
112+
113+
def supportedDragActions(self):
114+
return Qt.CopyAction
115+
116+
87117
class ModelerDialog(BASE, WIDGET):
88118
ALG_ITEM = 'ALG_ITEM'
89119
PROVIDER_ITEM = 'PROVIDER_ITEM'
@@ -193,7 +223,8 @@ def __init__(self, model=None):
193223
self.verticalLayout_2.setSpacing(4)
194224
self.searchBox = QgsFilterLineEdit(self.scrollAreaWidgetContents_3)
195225
self.verticalLayout_2.addWidget(self.searchBox)
196-
self.algorithmTree = QTreeWidget(self.scrollAreaWidgetContents_3)
226+
self.algorithmTree = QgsProcessingToolboxTreeView(None,
227+
QgsApplication.processingRegistry())
197228
self.algorithmTree.setAlternatingRowColors(True)
198229
self.algorithmTree.header().setVisible(False)
199230
self.verticalLayout_2.addWidget(self.algorithmTree)
@@ -266,26 +297,31 @@ def __init__(self, model=None):
266297
self.view.ensureVisible(0, 0, 10, 10)
267298

268299
def _dragEnterEvent(event):
269-
if event.mimeData().hasText():
300+
if event.mimeData().hasText() or event.mimeData().hasFormat('application/x-vnd.qgis.qgis.algorithmid'):
270301
event.acceptProposedAction()
271302
else:
272303
event.ignore()
273304

274305
def _dropEvent(event):
275-
if event.mimeData().hasText():
306+
if event.mimeData().hasFormat('application/x-vnd.qgis.qgis.algorithmid'):
307+
data = event.mimeData().data('application/x-vnd.qgis.qgis.algorithmid')
308+
stream = QDataStream(data, QIODevice.ReadOnly)
309+
algorithm_id = stream.readQString()
310+
alg = QgsApplication.processingRegistry().createAlgorithmById(algorithm_id)
311+
if alg is not None:
312+
self._addAlgorithm(alg, event.pos())
313+
else:
314+
assert False, algorithm_id
315+
elif event.mimeData().hasText():
276316
itemId = event.mimeData().text()
277317
if itemId in [param.id() for param in QgsApplication.instance().processingRegistry().parameterTypes()]:
278318
self.addInputOfType(itemId, event.pos())
279-
else:
280-
alg = QgsApplication.processingRegistry().createAlgorithmById(itemId)
281-
if alg is not None:
282-
self._addAlgorithm(alg, event.pos())
283319
event.accept()
284320
else:
285321
event.ignore()
286322

287323
def _dragMoveEvent(event):
288-
if event.mimeData().hasText():
324+
if event.mimeData().hasText() or event.mimeData().hasFormat('application/x-vnd.qgis.qgis.algorithmid'):
289325
event.accept()
290326
else:
291327
event.ignore()
@@ -352,19 +388,13 @@ def _mimeDataInput(items):
352388
self.inputsTree.setDragDropMode(QTreeWidget.DragOnly)
353389
self.inputsTree.setDropIndicatorShown(True)
354390

355-
def _mimeDataAlgorithm(items):
356-
item = items[0]
357-
mimeData = None
358-
if isinstance(item, TreeAlgorithmItem):
359-
mimeData = QMimeData()
360-
mimeData.setText(item.alg.id())
361-
return mimeData
362-
363-
self.algorithmTree.mimeData = _mimeDataAlgorithm
364-
391+
self.algorithms_model = ModelerToolboxModel(self, QgsApplication.processingRegistry())
392+
self.algorithmTree.setToolboxProxyModel(self.algorithms_model)
365393
self.algorithmTree.setDragDropMode(QTreeWidget.DragOnly)
366394
self.algorithmTree.setDropIndicatorShown(True)
367395

396+
self.algorithmTree.setFilters(QgsProcessingToolboxProxyModel.FilterModeler)
397+
368398
if hasattr(self.searchBox, 'setPlaceholderText'):
369399
self.searchBox.setPlaceholderText(QCoreApplication.translate('ModelerDialog', 'Search…'))
370400
if hasattr(self.textName, 'setPlaceholderText'):
@@ -374,7 +404,7 @@ def _mimeDataAlgorithm(items):
374404

375405
# Connect signals and slots
376406
self.inputsTree.doubleClicked.connect(self.addInput)
377-
self.searchBox.textChanged.connect(self.textChanged)
407+
self.searchBox.textChanged.connect(self.algorithmTree.setFilterString)
378408
self.algorithmTree.doubleClicked.connect(self.addAlgorithm)
379409

380410
# Ctrl+= should also trigger a zoom in action
@@ -407,7 +437,6 @@ def _mimeDataAlgorithm(items):
407437
self.model.setProvider(QgsApplication.processingRegistry().providerById('model'))
408438

409439
self.fillInputsTree()
410-
self.fillTreeUsingProviders()
411440

412441
self.view.centerOn(0, 0)
413442
self.help = None
@@ -693,51 +722,6 @@ def getPositionForParameterItem(self):
693722
newX = MARGIN + BOX_WIDTH / 2
694723
return QPointF(newX, MARGIN + BOX_HEIGHT / 2)
695724

696-
def textChanged(self):
697-
text = self.searchBox.text().strip(' ').lower()
698-
for item in list(self.disabledProviderItems.values()):
699-
item.setHidden(True)
700-
self._filterItem(self.algorithmTree.invisibleRootItem(), [t for t in text.split(' ') if t])
701-
if text:
702-
self.algorithmTree.expandAll()
703-
self.disabledWithMatchingAlgs = []
704-
for provider in QgsApplication.processingRegistry().providers():
705-
if not provider.isActive():
706-
for alg in provider.algorithms():
707-
if text in alg.name():
708-
self.disabledWithMatchingAlgs.append(provider.id())
709-
break
710-
else:
711-
self.algorithmTree.collapseAll()
712-
713-
def _filterItem(self, item, text):
714-
if (item.childCount() > 0):
715-
show = False
716-
for i in range(item.childCount()):
717-
child = item.child(i)
718-
showChild = self._filterItem(child, text)
719-
show = (showChild or show) and item not in list(self.disabledProviderItems.values())
720-
item.setHidden(not show)
721-
return show
722-
elif isinstance(item, (TreeAlgorithmItem, TreeActionItem)):
723-
# hide if every part of text is not contained somewhere in either the item text or item user role
724-
item_text = [item.text(0).lower(), item.data(0, ModelerDialog.NAME_ROLE).lower()]
725-
if isinstance(item, TreeAlgorithmItem):
726-
item_text.append(item.alg.id().lower())
727-
if item.alg.shortDescription():
728-
item_text.append(item.alg.shortDescription().lower())
729-
item_text.extend([t.lower() for t in item.data(0, ModelerDialog.TAG_ROLE)])
730-
731-
hide = bool(text) and not all(
732-
any(part in t for t in item_text)
733-
for part in text)
734-
735-
item.setHidden(hide)
736-
return not hide
737-
else:
738-
item.setHidden(True)
739-
return False
740-
741725
def fillInputsTree(self):
742726
icon = QIcon(os.path.join(pluginPath, 'images', 'input.svg'))
743727
parametersItem = QTreeWidgetItem()
@@ -756,9 +740,9 @@ def fillInputsTree(self):
756740
parametersItem.setExpanded(True)
757741

758742
def addAlgorithm(self):
759-
item = self.algorithmTree.currentItem()
760-
if isinstance(item, TreeAlgorithmItem):
761-
alg = QgsApplication.processingRegistry().createAlgorithmById(item.alg.id())
743+
algorithm = self.algorithmTree.selectedAlgorithm()
744+
if algorithm is not None:
745+
alg = QgsApplication.processingRegistry().createAlgorithmById(algorithm.id())
762746
self._addAlgorithm(alg)
763747

764748
def _addAlgorithm(self, alg, pos=None):
@@ -791,158 +775,3 @@ def getPositionForAlgorithmItem(self):
791775
newX = MARGIN + BOX_WIDTH / 2
792776
newY = MARGIN * 2 + BOX_HEIGHT + BOX_HEIGHT / 2
793777
return QPointF(newX, newY)
794-
795-
def fillTreeUsingProviders(self):
796-
self.algorithmTree.clear()
797-
self.disabledProviderItems = {}
798-
799-
# TODO - replace with proper model for toolbox!
800-
801-
# first add qgis/native providers, since they create top level groups
802-
for provider in QgsApplication.processingRegistry().providers():
803-
if provider.id() in ('qgis', 'native', '3d'):
804-
self.addAlgorithmsFromProvider(provider, self.algorithmTree.invisibleRootItem())
805-
else:
806-
continue
807-
self.algorithmTree.sortItems(0, Qt.AscendingOrder)
808-
809-
for provider in QgsApplication.processingRegistry().providers():
810-
if provider.id() in ('qgis', 'native', '3d'):
811-
# already added
812-
continue
813-
else:
814-
providerItem = TreeProviderItem(provider, self.algorithmTree, self)
815-
816-
# insert non-native providers at end of tree, alphabetically
817-
for i in range(self.algorithmTree.invisibleRootItem().childCount()):
818-
child = self.algorithmTree.invisibleRootItem().child(i)
819-
if isinstance(child, TreeProviderItem):
820-
if child.text(0) > providerItem.text(0):
821-
break
822-
823-
self.algorithmTree.insertTopLevelItem(i + 1, providerItem)
824-
825-
if not provider.isActive():
826-
providerItem.setHidden(True)
827-
self.disabledProviderItems[provider.id()] = providerItem
828-
829-
def addAlgorithmsFromProvider(self, provider, parent):
830-
groups = {}
831-
count = 0
832-
algs = provider.algorithms()
833-
active = provider.isActive()
834-
835-
# Add algorithms
836-
for alg in algs:
837-
if alg.flags() & QgsProcessingAlgorithm.FlagHideFromModeler:
838-
continue
839-
groupItem = None
840-
if alg.group() in groups:
841-
groupItem = groups[alg.group()]
842-
else:
843-
# check if group already exists
844-
for i in range(parent.childCount()):
845-
if parent.child(i).text(0) == alg.group():
846-
groupItem = parent.child(i)
847-
groups[alg.group()] = groupItem
848-
break
849-
850-
if not groupItem:
851-
groupItem = TreeGroupItem(alg.group())
852-
if not active:
853-
groupItem.setInactive()
854-
if provider.id() in ('qgis', 'native', '3d'):
855-
groupItem.setIcon(0, provider.icon())
856-
groups[alg.group()] = groupItem
857-
algItem = TreeAlgorithmItem(alg)
858-
if not active:
859-
algItem.setForeground(0, Qt.darkGray)
860-
groupItem.addChild(algItem)
861-
count += 1
862-
863-
text = provider.name()
864-
865-
if not provider.id() in ('qgis', 'native', '3d'):
866-
if not active:
867-
def activateProvider():
868-
self.activateProvider(provider.id())
869-
870-
label = QLabel(text + "&nbsp;&nbsp;&nbsp;&nbsp;<a href='%s'>Activate</a>")
871-
label.setStyleSheet("QLabel {background-color: white; color: grey;}")
872-
label.linkActivated.connect(activateProvider)
873-
self.algorithmTree.setItemWidget(parent, 0, label)
874-
else:
875-
parent.setText(0, text)
876-
877-
for group, groupItem in sorted(groups.items(), key=operator.itemgetter(1)):
878-
parent.addChild(groupItem)
879-
880-
if not provider.id() in ('qgis', 'native', '3d'):
881-
parent.setHidden(parent.childCount() == 0)
882-
883-
884-
class TreeAlgorithmItem(QTreeWidgetItem):
885-
886-
def __init__(self, alg):
887-
QTreeWidgetItem.__init__(self)
888-
self.alg = alg
889-
icon = alg.icon()
890-
nameEn = alg.name()
891-
name = alg.displayName()
892-
name = name if name != '' else nameEn
893-
self.setIcon(0, icon)
894-
self.setToolTip(0, self.formatAlgorithmTooltip(alg))
895-
self.setText(0, name)
896-
self.setData(0, ModelerDialog.NAME_ROLE, nameEn)
897-
self.setData(0, ModelerDialog.TAG_ROLE, alg.tags())
898-
self.setData(0, ModelerDialog.TYPE_ROLE, ModelerDialog.ALG_ITEM)
899-
900-
def formatAlgorithmTooltip(self, alg):
901-
return '<p><b>{}</b></p>{}<p>{}</p>'.format(
902-
alg.displayName(),
903-
'<p>{}</p>'.format(alg.shortDescription()) if alg.shortDescription() else '',
904-
QCoreApplication.translate('Toolbox', 'Algorithm ID: ‘{}’').format('<i>{}</i>'.format(alg.id()))
905-
)
906-
907-
908-
class TreeGroupItem(QTreeWidgetItem):
909-
910-
def __init__(self, name):
911-
QTreeWidgetItem.__init__(self)
912-
self.setToolTip(0, name)
913-
self.setText(0, name)
914-
self.setData(0, ModelerDialog.NAME_ROLE, name)
915-
self.setData(0, ModelerDialog.TYPE_ROLE, ModelerDialog.GROUP_ITEM)
916-
917-
def setInactive(self):
918-
self.setForeground(0, Qt.darkGray)
919-
920-
921-
class TreeActionItem(QTreeWidgetItem):
922-
923-
def __init__(self, action):
924-
QTreeWidgetItem.__init__(self)
925-
self.action = action
926-
self.setText(0, action.name)
927-
self.setIcon(0, action.getIcon())
928-
self.setData(0, ModelerDialog.NAME_ROLE, action.name)
929-
930-
931-
class TreeProviderItem(QTreeWidgetItem):
932-
933-
def __init__(self, provider, tree, toolbox):
934-
QTreeWidgetItem.__init__(self, None)
935-
self.tree = tree
936-
self.toolbox = toolbox
937-
self.provider = provider
938-
self.setIcon(0, self.provider.icon())
939-
self.setData(0, ModelerDialog.TYPE_ROLE, ModelerDialog.PROVIDER_ITEM)
940-
self.setToolTip(0, self.provider.longName())
941-
self.populate()
942-
943-
def refresh(self):
944-
self.takeChildren()
945-
self.populate()
946-
947-
def populate(self):
948-
self.toolbox.addAlgorithmsFromProvider(self.provider, self)

0 commit comments

Comments
 (0)