32
32
import warnings
33
33
34
34
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 )
36
48
from qgis .PyQt .QtWidgets import (QGraphicsView ,
37
49
QTreeWidget ,
38
50
QMessageBox ,
67
79
from qgis .gui import (QgsMessageBar ,
68
80
QgsDockWidget ,
69
81
QgsScrollArea ,
70
- QgsFilterLineEdit )
82
+ QgsFilterLineEdit ,
83
+ QgsProcessingToolboxTreeView ,
84
+ QgsProcessingToolboxProxyModel )
71
85
from processing .gui .HelpEditionDialog import HelpEditionDialog
72
86
from processing .gui .AlgorithmDialog import AlgorithmDialog
73
87
from processing .modeler .ModelerParameterDefinitionDialog import ModelerParameterDefinitionDialog
84
98
os .path .join (pluginPath , 'ui' , 'DlgModeler.ui' ))
85
99
86
100
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
+
87
117
class ModelerDialog (BASE , WIDGET ):
88
118
ALG_ITEM = 'ALG_ITEM'
89
119
PROVIDER_ITEM = 'PROVIDER_ITEM'
@@ -193,7 +223,8 @@ def __init__(self, model=None):
193
223
self .verticalLayout_2 .setSpacing (4 )
194
224
self .searchBox = QgsFilterLineEdit (self .scrollAreaWidgetContents_3 )
195
225
self .verticalLayout_2 .addWidget (self .searchBox )
196
- self .algorithmTree = QTreeWidget (self .scrollAreaWidgetContents_3 )
226
+ self .algorithmTree = QgsProcessingToolboxTreeView (None ,
227
+ QgsApplication .processingRegistry ())
197
228
self .algorithmTree .setAlternatingRowColors (True )
198
229
self .algorithmTree .header ().setVisible (False )
199
230
self .verticalLayout_2 .addWidget (self .algorithmTree )
@@ -266,26 +297,31 @@ def __init__(self, model=None):
266
297
self .view .ensureVisible (0 , 0 , 10 , 10 )
267
298
268
299
def _dragEnterEvent (event ):
269
- if event .mimeData ().hasText ():
300
+ if event .mimeData ().hasText () or event . mimeData (). hasFormat ( 'application/x-vnd.qgis.qgis.algorithmid' ) :
270
301
event .acceptProposedAction ()
271
302
else :
272
303
event .ignore ()
273
304
274
305
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 ():
276
316
itemId = event .mimeData ().text ()
277
317
if itemId in [param .id () for param in QgsApplication .instance ().processingRegistry ().parameterTypes ()]:
278
318
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 ())
283
319
event .accept ()
284
320
else :
285
321
event .ignore ()
286
322
287
323
def _dragMoveEvent (event ):
288
- if event .mimeData ().hasText ():
324
+ if event .mimeData ().hasText () or event . mimeData (). hasFormat ( 'application/x-vnd.qgis.qgis.algorithmid' ) :
289
325
event .accept ()
290
326
else :
291
327
event .ignore ()
@@ -352,19 +388,13 @@ def _mimeDataInput(items):
352
388
self .inputsTree .setDragDropMode (QTreeWidget .DragOnly )
353
389
self .inputsTree .setDropIndicatorShown (True )
354
390
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 )
365
393
self .algorithmTree .setDragDropMode (QTreeWidget .DragOnly )
366
394
self .algorithmTree .setDropIndicatorShown (True )
367
395
396
+ self .algorithmTree .setFilters (QgsProcessingToolboxProxyModel .FilterModeler )
397
+
368
398
if hasattr (self .searchBox , 'setPlaceholderText' ):
369
399
self .searchBox .setPlaceholderText (QCoreApplication .translate ('ModelerDialog' , 'Search…' ))
370
400
if hasattr (self .textName , 'setPlaceholderText' ):
@@ -374,7 +404,7 @@ def _mimeDataAlgorithm(items):
374
404
375
405
# Connect signals and slots
376
406
self .inputsTree .doubleClicked .connect (self .addInput )
377
- self .searchBox .textChanged .connect (self .textChanged )
407
+ self .searchBox .textChanged .connect (self .algorithmTree . setFilterString )
378
408
self .algorithmTree .doubleClicked .connect (self .addAlgorithm )
379
409
380
410
# Ctrl+= should also trigger a zoom in action
@@ -407,7 +437,6 @@ def _mimeDataAlgorithm(items):
407
437
self .model .setProvider (QgsApplication .processingRegistry ().providerById ('model' ))
408
438
409
439
self .fillInputsTree ()
410
- self .fillTreeUsingProviders ()
411
440
412
441
self .view .centerOn (0 , 0 )
413
442
self .help = None
@@ -693,51 +722,6 @@ def getPositionForParameterItem(self):
693
722
newX = MARGIN + BOX_WIDTH / 2
694
723
return QPointF (newX , MARGIN + BOX_HEIGHT / 2 )
695
724
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
-
741
725
def fillInputsTree (self ):
742
726
icon = QIcon (os .path .join (pluginPath , 'images' , 'input.svg' ))
743
727
parametersItem = QTreeWidgetItem ()
@@ -756,9 +740,9 @@ def fillInputsTree(self):
756
740
parametersItem .setExpanded (True )
757
741
758
742
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 ())
762
746
self ._addAlgorithm (alg )
763
747
764
748
def _addAlgorithm (self , alg , pos = None ):
@@ -791,158 +775,3 @@ def getPositionForAlgorithmItem(self):
791
775
newX = MARGIN + BOX_WIDTH / 2
792
776
newY = MARGIN * 2 + BOX_HEIGHT + BOX_HEIGHT / 2
793
777
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 + " <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