diff --git a/python/PyQt6/gui/auto_additions/qgsprocessingtoolboxmodel.py b/python/PyQt6/gui/auto_additions/qgsprocessingtoolboxmodel.py index 41622574a96d..705de40aea05 100644 --- a/python/PyQt6/gui/auto_additions/qgsprocessingtoolboxmodel.py +++ b/python/PyQt6/gui/auto_additions/qgsprocessingtoolboxmodel.py @@ -16,7 +16,10 @@ QgsProcessingToolboxModelNode.NodeType.NodeRecent = QgsProcessingToolboxModelNode.NodeType.Recent QgsProcessingToolboxModelNode.NodeRecent.is_monkey_patched = True QgsProcessingToolboxModelNode.NodeRecent.__doc__ = "Recent algorithms node" -QgsProcessingToolboxModelNode.NodeType.__doc__ = "Enumeration of possible model node types\n\n" + '* ``NodeProvider``: ' + QgsProcessingToolboxModelNode.NodeType.Provider.__doc__ + '\n' + '* ``NodeGroup``: ' + QgsProcessingToolboxModelNode.NodeType.Group.__doc__ + '\n' + '* ``NodeAlgorithm``: ' + QgsProcessingToolboxModelNode.NodeType.Algorithm.__doc__ + '\n' + '* ``NodeRecent``: ' + QgsProcessingToolboxModelNode.NodeType.Recent.__doc__ +QgsProcessingToolboxModelNode.Favorite = QgsProcessingToolboxModelNode.NodeType.Favorite +QgsProcessingToolboxModelNode.Favorite.is_monkey_patched = True +QgsProcessingToolboxModelNode.Favorite.__doc__ = "Favorites algorithms node, since QGIS 3.40" +QgsProcessingToolboxModelNode.NodeType.__doc__ = "Enumeration of possible model node types\n\n" + '* ``NodeProvider``: ' + QgsProcessingToolboxModelNode.NodeType.Provider.__doc__ + '\n' + '* ``NodeGroup``: ' + QgsProcessingToolboxModelNode.NodeType.Group.__doc__ + '\n' + '* ``NodeAlgorithm``: ' + QgsProcessingToolboxModelNode.NodeType.Algorithm.__doc__ + '\n' + '* ``NodeRecent``: ' + QgsProcessingToolboxModelNode.NodeType.Recent.__doc__ + '\n' + '* ``Favorite``: ' + QgsProcessingToolboxModelNode.NodeType.Favorite.__doc__ # -- QgsProcessingToolboxModelNode.NodeType.baseClass = QgsProcessingToolboxModelNode QgsProcessingToolboxModel.Roles = QgsProcessingToolboxModel.CustomRole diff --git a/python/PyQt6/gui/auto_generated/processing/qgsprocessingfavoritealgorithmmanager.sip.in b/python/PyQt6/gui/auto_generated/processing/qgsprocessingfavoritealgorithmmanager.sip.in new file mode 100644 index 000000000000..d229df8197d8 --- /dev/null +++ b/python/PyQt6/gui/auto_generated/processing/qgsprocessingfavoritealgorithmmanager.sip.in @@ -0,0 +1,91 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/processing/qgsprocessingfavoritealgorithmmanager.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +class QgsProcessingFavoriteAlgorithmManager : QObject +{ +%Docstring(signature="appended") +A manager for tracking favorite Processing algorithms. + +:py:class:`QgsProcessingFavoriteAlgorithmManager` is not usually directly created, instead +use the instance accessible through :py:func:`QgsGui.processingFavoriteAlgorithmManager()`. + +The favorite algorithms are saved and restored via :py:class:`QgsSettings`. + +.. note:: + + Not stable API + +.. versionadded:: 3.40 +%End + +%TypeHeaderCode +#include "qgsprocessingfavoritealgorithmmanager.h" +%End + public: + + QgsProcessingFavoriteAlgorithmManager( QObject *parent = 0 ); +%Docstring +Constructor for QgsProcessingFavoriteAlgorithmManager, with the specified +``parent`` object. +%End + + QStringList favoriteAlgorithmIds() const; +%Docstring +Returns a list of the IDs of favorite Processing algorithms. +%End + + void add( const QString &id ); +%Docstring +Adds the algorithm with matching ``id`` to the favorite algorithms list. + +If this changes the list of favorite algorithm IDs then the :py:func:`~QgsProcessingFavoriteAlgorithmManager.changed` signal +will be emitted. +%End + + void remove( const QString &id ); +%Docstring +Removes the algorithm with matching ``id`` from the favorite algorithms list. + +If this changes the list of favorite algorithm IDs then the :py:func:`~QgsProcessingFavoriteAlgorithmManager.changed` signal +will be emitted. +%End + + void clear(); +%Docstring +Clears list of favorite Processing algorithms +%End + + bool isFavorite( const QString &id ); +%Docstring +Returns ``True`` if the algorithm with matching ``id`` is in a favorite list. +%End + + + signals: + + void changed(); +%Docstring +Emitted when the list of favorite algorithms is changed, e.g. when +a new algorithm ID is added to the list or an existing algorithm ID +is removed from the list. +%End + +}; + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/processing/qgsprocessingfavoritealgorithmmanager.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/PyQt6/gui/auto_generated/processing/qgsprocessingtoolboxmodel.sip.in b/python/PyQt6/gui/auto_generated/processing/qgsprocessingtoolboxmodel.sip.in index bef60ce3c10d..5672bea9e42b 100644 --- a/python/PyQt6/gui/auto_generated/processing/qgsprocessingtoolboxmodel.sip.in +++ b/python/PyQt6/gui/auto_generated/processing/qgsprocessingtoolboxmodel.sip.in @@ -38,6 +38,8 @@ Abstract base class for nodes contained within a :py:class:`QgsProcessingToolbox sipType = sipType_QgsProcessingToolboxModelAlgorithmNode; else if ( node->nodeType() == QgsProcessingToolboxModelNode::NodeType::Recent ) sipType = sipType_QgsProcessingToolboxModelRecentNode; + else if ( node->nodeType() == QgsProcessingToolboxModelNode::NodeType::Favorite ) + sipType = sipType_QgsProcessingToolboxModelFavoriteNode; } else sipType = 0; @@ -51,6 +53,7 @@ Abstract base class for nodes contained within a :py:class:`QgsProcessingToolbox Group, Algorithm, Recent, + Favorite, }; ~QgsProcessingToolboxModelNode(); @@ -123,6 +126,32 @@ Constructor for QgsProcessingToolboxModelRecentNode. }; +class QgsProcessingToolboxModelFavoriteNode : QgsProcessingToolboxModelNode +{ +%Docstring(signature="appended") +Processing toolbox model node corresponding to the favorite algorithms group + +.. warning:: + + Not part of stable API and may change in future QGIS releases. + +.. versionadded:: 3.40 +%End + +%TypeHeaderCode +#include "qgsprocessingtoolboxmodel.h" +%End + public: + + QgsProcessingToolboxModelFavoriteNode(); +%Docstring +Constructor for :py:class:`QgsProcessingToolboxModelRecentNode`. +%End + + virtual NodeType nodeType() const; + +}; + class QgsProcessingToolboxModelProviderNode : QgsProcessingToolboxModelNode { %Docstring(signature="appended") @@ -262,7 +291,8 @@ of this model. }; QgsProcessingToolboxModel( QObject *parent /TransferThis/ = 0, QgsProcessingRegistry *registry = 0, - QgsProcessingRecentAlgorithmLog *recentLog = 0 ); + QgsProcessingRecentAlgorithmLog *recentLog = 0, + QgsProcessingFavoriteAlgorithmManager *favoriteManager = 0 ); %Docstring Constructor for QgsProcessingToolboxModel, with the given ``parent`` object. @@ -273,6 +303,9 @@ by the model. If ``recentLog`` is specified then it will be used to create a "Recently used" top level group containing recently used algorithms. + +If ``favoriteManager`` is specified then it will be used to create a "Favorites" top +level group containing favorite algorithms. Since QGIS 3.40 %End virtual Qt::ItemFlags flags( const QModelIndex &index ) const; @@ -358,6 +391,11 @@ Returns the index corresponding to the parent of a given node. void recentAlgorithmAdded(); %Docstring Emitted whenever recent algorithms are added to the model. +%End + + void favoriteAlgorithmAdded(); +%Docstring +Emitted whenever favorite algorithms are added to the model. %End }; @@ -391,7 +429,8 @@ the results. explicit QgsProcessingToolboxProxyModel( QObject *parent /TransferThis/ = 0, QgsProcessingRegistry *registry = 0, - QgsProcessingRecentAlgorithmLog *recentLog = 0 ); + QgsProcessingRecentAlgorithmLog *recentLog = 0, + QgsProcessingFavoriteAlgorithmManager *favoriteManager = 0 ); %Docstring Constructor for QgsProcessingToolboxProxyModel, with the given ``parent`` object. @@ -402,6 +441,9 @@ by the model. If ``recentLog`` is specified then it will be used to create a "Recently used" top level group containing recently used algorithms. + +If ``favoriteManager`` is specified then it will be used to create a "Favorites" top +level group containing favorite algorithms. SInce QGIS 3.40 %End QgsProcessingToolboxModel *toolboxModel(); diff --git a/python/PyQt6/gui/auto_generated/processing/qgsprocessingtoolboxtreeview.sip.in b/python/PyQt6/gui/auto_generated/processing/qgsprocessingtoolboxtreeview.sip.in index d2bebf03bf75..0a3e18b6a8d7 100644 --- a/python/PyQt6/gui/auto_generated/processing/qgsprocessingtoolboxtreeview.sip.in +++ b/python/PyQt6/gui/auto_generated/processing/qgsprocessingtoolboxtreeview.sip.in @@ -29,7 +29,8 @@ Processing toolbox tree view, showing algorithms and providers in a tree structu QgsProcessingToolboxTreeView( QWidget *parent /TransferThis/ = 0, QgsProcessingRegistry *registry = 0, - QgsProcessingRecentAlgorithmLog *recentLog = 0 ); + QgsProcessingRecentAlgorithmLog *recentLog = 0, + QgsProcessingFavoriteAlgorithmManager *favoriteManager = 0 ); %Docstring Constructor for QgsProcessingToolboxTreeView, with the specified ``parent`` widget. @@ -39,16 +40,23 @@ to associate a registry with the view. If ``recentLog`` is specified then it will be used to create a "Recently used" top level group containing recently used algorithms. + +If ``favoriteManager`` is specified then it will be used to create a "Favorites" top +level group containing favorite algorithms. Since QGIS 3.40 %End void setRegistry( QgsProcessingRegistry *registry, - QgsProcessingRecentAlgorithmLog *recentLog = 0 ); + QgsProcessingRecentAlgorithmLog *recentLog = 0, + QgsProcessingFavoriteAlgorithmManager *favoriteManager = 0 ); %Docstring Sets the processing ``registry`` associated with the view. If ``recentLog`` is specified then it will be used to create a "Recently used" top level group containing recently used algorithms. + +If ``favoriteManager`` is specified then it will be used to create a "Favorites" top +level group containing favorite algorithms. Since QGIS 3.40 %End void setToolboxProxyModel( QgsProcessingToolboxProxyModel *model /Transfer/ ); diff --git a/python/PyQt6/gui/auto_generated/qgsgui.sip.in b/python/PyQt6/gui/auto_generated/qgsgui.sip.in index c93c5e11dd11..cb9ec38ce450 100644 --- a/python/PyQt6/gui/auto_generated/qgsgui.sip.in +++ b/python/PyQt6/gui/auto_generated/qgsgui.sip.in @@ -106,6 +106,13 @@ Returns the global code editor color scheme registry, used for registering the c Returns the global processing recent algorithm log, used for tracking recently used processing algorithms. .. versionadded:: 3.4 +%End + + static QgsProcessingFavoriteAlgorithmManager *processingFavoriteAlgorithmManager(); +%Docstring +Returns the global Processing favorite algorithm manager, used for tracking favorite Processing algorithms. + +.. versionadded:: 3.40 %End static QgsDataItemGuiProviderRegistry *dataItemGuiProviderRegistry() /KeepReference/; diff --git a/python/PyQt6/gui/gui_auto.sip b/python/PyQt6/gui/gui_auto.sip index 46ca4ea13219..2b21fef5fe1d 100644 --- a/python/PyQt6/gui/gui_auto.sip +++ b/python/PyQt6/gui/gui_auto.sip @@ -421,6 +421,7 @@ %Include auto_generated/processing/qgsprocessingalgorithmconfigurationwidget.sip %Include auto_generated/processing/qgsprocessingalgorithmdialogbase.sip %Include auto_generated/processing/qgsprocessingbatchalgorithmdialogbase.sip +%Include auto_generated/processing/qgsprocessingfavoritealgorithmmanager.sip %Include auto_generated/processing/qgsprocessinggui.sip %Include auto_generated/processing/qgsprocessingguiregistry.sip %Include auto_generated/processing/qgsprocessinghistoryprovider.sip diff --git a/python/gui/auto_additions/qgsprocessingtoolboxmodel.py b/python/gui/auto_additions/qgsprocessingtoolboxmodel.py index d45da5a84cc1..f7ba8d3008d3 100644 --- a/python/gui/auto_additions/qgsprocessingtoolboxmodel.py +++ b/python/gui/auto_additions/qgsprocessingtoolboxmodel.py @@ -16,7 +16,10 @@ QgsProcessingToolboxModelNode.NodeType.NodeRecent = QgsProcessingToolboxModelNode.NodeType.Recent QgsProcessingToolboxModelNode.NodeRecent.is_monkey_patched = True QgsProcessingToolboxModelNode.NodeRecent.__doc__ = "Recent algorithms node" -QgsProcessingToolboxModelNode.NodeType.__doc__ = "Enumeration of possible model node types\n\n" + '* ``NodeProvider``: ' + QgsProcessingToolboxModelNode.NodeType.Provider.__doc__ + '\n' + '* ``NodeGroup``: ' + QgsProcessingToolboxModelNode.NodeType.Group.__doc__ + '\n' + '* ``NodeAlgorithm``: ' + QgsProcessingToolboxModelNode.NodeType.Algorithm.__doc__ + '\n' + '* ``NodeRecent``: ' + QgsProcessingToolboxModelNode.NodeType.Recent.__doc__ +QgsProcessingToolboxModelNode.Favorite = QgsProcessingToolboxModelNode.NodeType.Favorite +QgsProcessingToolboxModelNode.Favorite.is_monkey_patched = True +QgsProcessingToolboxModelNode.Favorite.__doc__ = "Favorites algorithms node, since QGIS 3.40" +QgsProcessingToolboxModelNode.NodeType.__doc__ = "Enumeration of possible model node types\n\n" + '* ``NodeProvider``: ' + QgsProcessingToolboxModelNode.NodeType.Provider.__doc__ + '\n' + '* ``NodeGroup``: ' + QgsProcessingToolboxModelNode.NodeType.Group.__doc__ + '\n' + '* ``NodeAlgorithm``: ' + QgsProcessingToolboxModelNode.NodeType.Algorithm.__doc__ + '\n' + '* ``NodeRecent``: ' + QgsProcessingToolboxModelNode.NodeType.Recent.__doc__ + '\n' + '* ``Favorite``: ' + QgsProcessingToolboxModelNode.NodeType.Favorite.__doc__ # -- QgsProcessingToolboxModelNode.NodeType.baseClass = QgsProcessingToolboxModelNode QgsProcessingToolboxModel.Roles = QgsProcessingToolboxModel.CustomRole diff --git a/python/gui/auto_generated/processing/qgsprocessingfavoritealgorithmmanager.sip.in b/python/gui/auto_generated/processing/qgsprocessingfavoritealgorithmmanager.sip.in new file mode 100644 index 000000000000..d229df8197d8 --- /dev/null +++ b/python/gui/auto_generated/processing/qgsprocessingfavoritealgorithmmanager.sip.in @@ -0,0 +1,91 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/processing/qgsprocessingfavoritealgorithmmanager.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +class QgsProcessingFavoriteAlgorithmManager : QObject +{ +%Docstring(signature="appended") +A manager for tracking favorite Processing algorithms. + +:py:class:`QgsProcessingFavoriteAlgorithmManager` is not usually directly created, instead +use the instance accessible through :py:func:`QgsGui.processingFavoriteAlgorithmManager()`. + +The favorite algorithms are saved and restored via :py:class:`QgsSettings`. + +.. note:: + + Not stable API + +.. versionadded:: 3.40 +%End + +%TypeHeaderCode +#include "qgsprocessingfavoritealgorithmmanager.h" +%End + public: + + QgsProcessingFavoriteAlgorithmManager( QObject *parent = 0 ); +%Docstring +Constructor for QgsProcessingFavoriteAlgorithmManager, with the specified +``parent`` object. +%End + + QStringList favoriteAlgorithmIds() const; +%Docstring +Returns a list of the IDs of favorite Processing algorithms. +%End + + void add( const QString &id ); +%Docstring +Adds the algorithm with matching ``id`` to the favorite algorithms list. + +If this changes the list of favorite algorithm IDs then the :py:func:`~QgsProcessingFavoriteAlgorithmManager.changed` signal +will be emitted. +%End + + void remove( const QString &id ); +%Docstring +Removes the algorithm with matching ``id`` from the favorite algorithms list. + +If this changes the list of favorite algorithm IDs then the :py:func:`~QgsProcessingFavoriteAlgorithmManager.changed` signal +will be emitted. +%End + + void clear(); +%Docstring +Clears list of favorite Processing algorithms +%End + + bool isFavorite( const QString &id ); +%Docstring +Returns ``True`` if the algorithm with matching ``id`` is in a favorite list. +%End + + + signals: + + void changed(); +%Docstring +Emitted when the list of favorite algorithms is changed, e.g. when +a new algorithm ID is added to the list or an existing algorithm ID +is removed from the list. +%End + +}; + + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/processing/qgsprocessingfavoritealgorithmmanager.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/auto_generated/processing/qgsprocessingtoolboxmodel.sip.in b/python/gui/auto_generated/processing/qgsprocessingtoolboxmodel.sip.in index a5a3a3dda3a7..6cac42efe97c 100644 --- a/python/gui/auto_generated/processing/qgsprocessingtoolboxmodel.sip.in +++ b/python/gui/auto_generated/processing/qgsprocessingtoolboxmodel.sip.in @@ -38,6 +38,8 @@ Abstract base class for nodes contained within a :py:class:`QgsProcessingToolbox sipType = sipType_QgsProcessingToolboxModelAlgorithmNode; else if ( node->nodeType() == QgsProcessingToolboxModelNode::NodeType::Recent ) sipType = sipType_QgsProcessingToolboxModelRecentNode; + else if ( node->nodeType() == QgsProcessingToolboxModelNode::NodeType::Favorite ) + sipType = sipType_QgsProcessingToolboxModelFavoriteNode; } else sipType = 0; @@ -51,6 +53,7 @@ Abstract base class for nodes contained within a :py:class:`QgsProcessingToolbox Group, Algorithm, Recent, + Favorite, }; ~QgsProcessingToolboxModelNode(); @@ -123,6 +126,32 @@ Constructor for QgsProcessingToolboxModelRecentNode. }; +class QgsProcessingToolboxModelFavoriteNode : QgsProcessingToolboxModelNode +{ +%Docstring(signature="appended") +Processing toolbox model node corresponding to the favorite algorithms group + +.. warning:: + + Not part of stable API and may change in future QGIS releases. + +.. versionadded:: 3.40 +%End + +%TypeHeaderCode +#include "qgsprocessingtoolboxmodel.h" +%End + public: + + QgsProcessingToolboxModelFavoriteNode(); +%Docstring +Constructor for :py:class:`QgsProcessingToolboxModelRecentNode`. +%End + + virtual NodeType nodeType() const; + +}; + class QgsProcessingToolboxModelProviderNode : QgsProcessingToolboxModelNode { %Docstring(signature="appended") @@ -262,7 +291,8 @@ of this model. }; QgsProcessingToolboxModel( QObject *parent /TransferThis/ = 0, QgsProcessingRegistry *registry = 0, - QgsProcessingRecentAlgorithmLog *recentLog = 0 ); + QgsProcessingRecentAlgorithmLog *recentLog = 0, + QgsProcessingFavoriteAlgorithmManager *favoriteManager = 0 ); %Docstring Constructor for QgsProcessingToolboxModel, with the given ``parent`` object. @@ -273,6 +303,9 @@ by the model. If ``recentLog`` is specified then it will be used to create a "Recently used" top level group containing recently used algorithms. + +If ``favoriteManager`` is specified then it will be used to create a "Favorites" top +level group containing favorite algorithms. Since QGIS 3.40 %End virtual Qt::ItemFlags flags( const QModelIndex &index ) const; @@ -358,6 +391,11 @@ Returns the index corresponding to the parent of a given node. void recentAlgorithmAdded(); %Docstring Emitted whenever recent algorithms are added to the model. +%End + + void favoriteAlgorithmAdded(); +%Docstring +Emitted whenever favorite algorithms are added to the model. %End }; @@ -391,7 +429,8 @@ the results. explicit QgsProcessingToolboxProxyModel( QObject *parent /TransferThis/ = 0, QgsProcessingRegistry *registry = 0, - QgsProcessingRecentAlgorithmLog *recentLog = 0 ); + QgsProcessingRecentAlgorithmLog *recentLog = 0, + QgsProcessingFavoriteAlgorithmManager *favoriteManager = 0 ); %Docstring Constructor for QgsProcessingToolboxProxyModel, with the given ``parent`` object. @@ -402,6 +441,9 @@ by the model. If ``recentLog`` is specified then it will be used to create a "Recently used" top level group containing recently used algorithms. + +If ``favoriteManager`` is specified then it will be used to create a "Favorites" top +level group containing favorite algorithms. SInce QGIS 3.40 %End QgsProcessingToolboxModel *toolboxModel(); diff --git a/python/gui/auto_generated/processing/qgsprocessingtoolboxtreeview.sip.in b/python/gui/auto_generated/processing/qgsprocessingtoolboxtreeview.sip.in index d2bebf03bf75..0a3e18b6a8d7 100644 --- a/python/gui/auto_generated/processing/qgsprocessingtoolboxtreeview.sip.in +++ b/python/gui/auto_generated/processing/qgsprocessingtoolboxtreeview.sip.in @@ -29,7 +29,8 @@ Processing toolbox tree view, showing algorithms and providers in a tree structu QgsProcessingToolboxTreeView( QWidget *parent /TransferThis/ = 0, QgsProcessingRegistry *registry = 0, - QgsProcessingRecentAlgorithmLog *recentLog = 0 ); + QgsProcessingRecentAlgorithmLog *recentLog = 0, + QgsProcessingFavoriteAlgorithmManager *favoriteManager = 0 ); %Docstring Constructor for QgsProcessingToolboxTreeView, with the specified ``parent`` widget. @@ -39,16 +40,23 @@ to associate a registry with the view. If ``recentLog`` is specified then it will be used to create a "Recently used" top level group containing recently used algorithms. + +If ``favoriteManager`` is specified then it will be used to create a "Favorites" top +level group containing favorite algorithms. Since QGIS 3.40 %End void setRegistry( QgsProcessingRegistry *registry, - QgsProcessingRecentAlgorithmLog *recentLog = 0 ); + QgsProcessingRecentAlgorithmLog *recentLog = 0, + QgsProcessingFavoriteAlgorithmManager *favoriteManager = 0 ); %Docstring Sets the processing ``registry`` associated with the view. If ``recentLog`` is specified then it will be used to create a "Recently used" top level group containing recently used algorithms. + +If ``favoriteManager`` is specified then it will be used to create a "Favorites" top +level group containing favorite algorithms. Since QGIS 3.40 %End void setToolboxProxyModel( QgsProcessingToolboxProxyModel *model /Transfer/ ); diff --git a/python/gui/auto_generated/qgsgui.sip.in b/python/gui/auto_generated/qgsgui.sip.in index bad47f83fbf1..ed54f941aae9 100644 --- a/python/gui/auto_generated/qgsgui.sip.in +++ b/python/gui/auto_generated/qgsgui.sip.in @@ -106,6 +106,13 @@ Returns the global code editor color scheme registry, used for registering the c Returns the global processing recent algorithm log, used for tracking recently used processing algorithms. .. versionadded:: 3.4 +%End + + static QgsProcessingFavoriteAlgorithmManager *processingFavoriteAlgorithmManager(); +%Docstring +Returns the global Processing favorite algorithm manager, used for tracking favorite Processing algorithms. + +.. versionadded:: 3.40 %End static QgsDataItemGuiProviderRegistry *dataItemGuiProviderRegistry() /KeepReference/; diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index 46ca4ea13219..2b21fef5fe1d 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -421,6 +421,7 @@ %Include auto_generated/processing/qgsprocessingalgorithmconfigurationwidget.sip %Include auto_generated/processing/qgsprocessingalgorithmdialogbase.sip %Include auto_generated/processing/qgsprocessingbatchalgorithmdialogbase.sip +%Include auto_generated/processing/qgsprocessingfavoritealgorithmmanager.sip %Include auto_generated/processing/qgsprocessinggui.sip %Include auto_generated/processing/qgsprocessingguiregistry.sip %Include auto_generated/processing/qgsprocessinghistoryprovider.sip diff --git a/python/plugins/processing/gui/ProcessingToolbox.py b/python/plugins/processing/gui/ProcessingToolbox.py index 3983fb2745dd..c380d1ccd5a9 100644 --- a/python/plugins/processing/gui/ProcessingToolbox.py +++ b/python/plugins/processing/gui/ProcessingToolbox.py @@ -73,7 +73,8 @@ def __init__(self): self.processingToolbar.setIconSize(iface.iconSize(True)) self.algorithmTree.setRegistry(QgsApplication.processingRegistry(), - QgsGui.instance().processingRecentAlgorithmLog()) + QgsGui.instance().processingRecentAlgorithmLog(), + QgsGui.instance().processingFavoriteAlgorithmManager()) filters = QgsProcessingToolboxProxyModel.Filters(QgsProcessingToolboxProxyModel.Filter.FilterToolbox) if ProcessingConfig.getSetting(ProcessingConfig.SHOW_ALGORITHMS_KNOWN_ISSUES): filters |= QgsProcessingToolboxProxyModel.Filter.FilterShowKnownIssues @@ -196,6 +197,15 @@ def showPopupMenu(self, point): editRenderingStylesAction.triggered.connect( self.editRenderingStyles) popupmenu.addAction(editRenderingStylesAction) + + popupmenu.addSeparator() + actionText = QCoreApplication.translate('ProcessingToolbox', 'Add to Favorites') + if QgsGui.instance().processingFavoriteAlgorithmManager().isFavorite(alg.id()): + actionText = QCoreApplication.translate('ProcessingToolbox', 'Remove from Favorites') + favoriteAction = QAction(actionText, popupmenu) + favoriteAction.triggered.connect(self.toggleFavorite) + popupmenu.addAction(favoriteAction) + actions = ProviderContextMenuActions.actions if len(actions) > 0: popupmenu.addSeparator() @@ -230,3 +240,11 @@ def executeAlgorithm(self): alg = self.algorithmTree.selectedAlgorithm() if alg is not None: self.executeWithGui.emit(alg.id(), self, self.in_place_mode, False) + + def toggleFavorite(self): + alg = self.algorithmTree.selectedAlgorithm() + if alg is not None: + if QgsGui.instance().processingFavoriteAlgorithmManager().isFavorite(alg.id()): + QgsGui.instance().processingFavoriteAlgorithmManager().remove(alg.id()) + else: + QgsGui.instance().processingFavoriteAlgorithmManager().add(alg.id()) diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 51ebb2eb5181..8d9466a78d15 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -380,6 +380,7 @@ set(QGIS_GUI_SRCS processing/qgsprocessingconfigurationwidgets.cpp processing/qgsprocessingdxflayerswidgetwrapper.cpp processing/qgsprocessingenummodelerwidget.cpp + processing/qgsprocessingfavoritealgorithmmanager.cpp processing/qgsprocessingfeaturesourceoptionswidget.cpp processing/qgsprocessingfieldmapwidgetwrapper.cpp processing/qgsprocessingguiregistry.cpp @@ -1352,6 +1353,7 @@ set(QGIS_GUI_HDRS processing/qgsprocessingconfigurationwidgets.h processing/qgsprocessingdxflayerswidgetwrapper.h processing/qgsprocessingenummodelerwidget.h + processing/qgsprocessingfavoritealgorithmmanager.h processing/qgsprocessingfeaturesourceoptionswidget.h processing/qgsprocessingfieldmapwidgetwrapper.h processing/qgsprocessinggui.h diff --git a/src/gui/processing/qgsprocessingfavoritealgorithmmanager.cpp b/src/gui/processing/qgsprocessingfavoritealgorithmmanager.cpp new file mode 100644 index 000000000000..01e6f9c1ba9e --- /dev/null +++ b/src/gui/processing/qgsprocessingfavoritealgorithmmanager.cpp @@ -0,0 +1,71 @@ +/*************************************************************************** + qgsprocessingfavoritealgorithmmanager.cpp + ------------------------------------ + Date : February 2024 + Copyright : (C) 2024 Alexander Bruy + Email : alexander dot bruy 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. * + * * + ***************************************************************************/ + +#include "qgsprocessingfavoritealgorithmmanager.h" +#include "qgssettingstree.h" +#include "qgssettingsentryimpl.h" + +///@cond PRIVATE + +const QgsSettingsEntryStringList *QgsProcessingFavoriteAlgorithmManager::settingsFavoriteAlgorithms = new QgsSettingsEntryStringList( QStringLiteral( "favorite-algorithms" ), QgsSettingsTree::sTreeProcessing, QStringList(), QObject::tr( "Favorite Processing algorithms" ) ); + +QgsProcessingFavoriteAlgorithmManager::QgsProcessingFavoriteAlgorithmManager( QObject *parent ) + : QObject( parent ) +{ + mFavoriteAlgorithmIds = QgsProcessingFavoriteAlgorithmManager::settingsFavoriteAlgorithms->value(); +} + +QStringList QgsProcessingFavoriteAlgorithmManager::favoriteAlgorithmIds() const +{ + return mFavoriteAlgorithmIds; +} + +void QgsProcessingFavoriteAlgorithmManager::add( const QString &id ) +{ + if ( mFavoriteAlgorithmIds.contains( id ) ) + { + return; + } + + mFavoriteAlgorithmIds << id; + QgsProcessingFavoriteAlgorithmManager::settingsFavoriteAlgorithms->setValue( mFavoriteAlgorithmIds ); + emit changed(); +} + +void QgsProcessingFavoriteAlgorithmManager::remove( const QString &id ) +{ + if ( !mFavoriteAlgorithmIds.contains( id ) ) + { + return; + } + + mFavoriteAlgorithmIds.removeAll( id ); + QgsProcessingFavoriteAlgorithmManager::settingsFavoriteAlgorithms->setValue( mFavoriteAlgorithmIds ); + emit changed(); +} + +void QgsProcessingFavoriteAlgorithmManager::clear() +{ + mFavoriteAlgorithmIds.clear(); + QgsProcessingFavoriteAlgorithmManager::settingsFavoriteAlgorithms->setValue( mFavoriteAlgorithmIds ); + emit changed(); +} + +bool QgsProcessingFavoriteAlgorithmManager::isFavorite( const QString &id ) +{ + return mFavoriteAlgorithmIds.contains( id ); +} + +///@endcond diff --git a/src/gui/processing/qgsprocessingfavoritealgorithmmanager.h b/src/gui/processing/qgsprocessingfavoritealgorithmmanager.h new file mode 100644 index 000000000000..6fc83bd35b5f --- /dev/null +++ b/src/gui/processing/qgsprocessingfavoritealgorithmmanager.h @@ -0,0 +1,103 @@ +/*************************************************************************** + qgsprocessingfavoritealgorithmmanager.h + ---------------------------------- + Date : February 2024 + Copyright : (C) 2024 Alexander Bruy + Email : alexander dot bruy 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. * + * * + ***************************************************************************/ + +#ifndef QGSPROCESSINGFAVORITEALGORITHMMANAGER_H +#define QGSPROCESSINGFAVORITEALGORITHMMANAGER_H + +#include "qgis.h" +#include "qgis_gui.h" + +class QgsSettingsEntryStringList; + +///@cond NOT_STABLE + +/** + * \ingroup gui + * \brief A manager for tracking favorite Processing algorithms. + * + * QgsProcessingFavoriteAlgorithmManager is not usually directly created, instead + * use the instance accessible through QgsGui::processingFavoriteAlgorithmManager(). + * + * The favorite algorithms are saved and restored via QgsSettings. + * + * \note Not stable API + * \since QGIS 3.40 + */ +class GUI_EXPORT QgsProcessingFavoriteAlgorithmManager : public QObject +{ + Q_OBJECT + + public: + + /** + * Constructor for QgsProcessingFavoriteAlgorithmManager, with the specified + * \a parent object. + */ + QgsProcessingFavoriteAlgorithmManager( QObject *parent = nullptr ); + + /** + * Returns a list of the IDs of favorite Processing algorithms. + */ + QStringList favoriteAlgorithmIds() const; + + /** + * Adds the algorithm with matching \a id to the favorite algorithms list. + * + * If this changes the list of favorite algorithm IDs then the changed() signal + * will be emitted. + */ + void add( const QString &id ); + + /** + * Removes the algorithm with matching \a id from the favorite algorithms list. + * + * If this changes the list of favorite algorithm IDs then the changed() signal + * will be emitted. + */ + void remove( const QString &id ); + + /** + * Clears list of favorite Processing algorithms + */ + void clear(); + + /** + * Returns TRUE if the algorithm with matching \a id is in a favorite list. + */ + bool isFavorite( const QString &id ); + +#ifndef SIP_RUN + //! Settings entry favorite algorithms + static const QgsSettingsEntryStringList *settingsFavoriteAlgorithms; +#endif + + signals: + + /** + * Emitted when the list of favorite algorithms is changed, e.g. when + * a new algorithm ID is added to the list or an existing algorithm ID + * is removed from the list. + */ + void changed(); + + private: + + QStringList mFavoriteAlgorithmIds; + +}; + +///@endcond + +#endif // QGSPROCESSINGFAVORITEALGORITHMMANAGER_H diff --git a/src/gui/processing/qgsprocessingtoolboxmodel.cpp b/src/gui/processing/qgsprocessingtoolboxmodel.cpp index f7ae0b43d801..49daf21c2776 100644 --- a/src/gui/processing/qgsprocessingtoolboxmodel.cpp +++ b/src/gui/processing/qgsprocessingtoolboxmodel.cpp @@ -18,6 +18,7 @@ #include "qgsvectorlayer.h" #include "qgsprocessingregistry.h" #include "qgsprocessingrecentalgorithmlog.h" +#include "qgsprocessingfavoritealgorithmmanager.h" #include #include #include @@ -115,10 +116,11 @@ const QgsProcessingAlgorithm *QgsProcessingToolboxModelAlgorithmNode::algorithm( // QgsProcessingToolboxModel // -QgsProcessingToolboxModel::QgsProcessingToolboxModel( QObject *parent, QgsProcessingRegistry *registry, QgsProcessingRecentAlgorithmLog *recentLog ) +QgsProcessingToolboxModel::QgsProcessingToolboxModel( QObject *parent, QgsProcessingRegistry *registry, QgsProcessingRecentAlgorithmLog *recentLog, QgsProcessingFavoriteAlgorithmManager *favoriteManager ) : QAbstractItemModel( parent ) , mRegistry( registry ? registry : QgsApplication::processingRegistry() ) , mRecentLog( recentLog ) + , mFavoriteManager( favoriteManager ) , mRootNode( std::make_unique< QgsProcessingToolboxModelGroupNode >( QString(), QString() ) ) { rebuild(); @@ -126,6 +128,9 @@ QgsProcessingToolboxModel::QgsProcessingToolboxModel( QObject *parent, QgsProces if ( mRecentLog ) connect( mRecentLog, &QgsProcessingRecentAlgorithmLog::changed, this, [ = ] { repopulateRecentAlgorithms(); } ); + if ( mFavoriteManager ) + connect( mFavoriteManager, &QgsProcessingFavoriteAlgorithmManager::changed, this, [ = ] { repopulateFavoriteAlgorithms(); } ); + connect( mRegistry, &QgsProcessingRegistry::providerAdded, this, &QgsProcessingToolboxModel::rebuild ); connect( mRegistry, &QgsProcessingRegistry::providerRemoved, this, &QgsProcessingToolboxModel::providerRemoved ); } @@ -136,6 +141,7 @@ void QgsProcessingToolboxModel::rebuild() mRootNode->deleteChildren(); mRecentNode = nullptr; + mFavoriteNode = nullptr; if ( mRecentLog ) { @@ -146,6 +152,15 @@ void QgsProcessingToolboxModel::rebuild() repopulateRecentAlgorithms( true ); } + if ( mFavoriteManager ) + { + std::unique_ptr< QgsProcessingToolboxModelFavoriteNode > favoriteNode = std::make_unique< QgsProcessingToolboxModelFavoriteNode >(); + // cppcheck-suppress danglingLifetime + mFavoriteNode = favoriteNode.get(); + mRootNode->addChildNode( favoriteNode.release() ); + repopulateFavoriteAlgorithms( true ); + } + if ( mRegistry ) { const QList< QgsProcessingProvider * > providers = mRegistry->providers(); @@ -213,6 +228,66 @@ void QgsProcessingToolboxModel::repopulateRecentAlgorithms( bool resetting ) } } +void QgsProcessingToolboxModel::repopulateFavoriteAlgorithms( bool resetting ) +{ + if ( !mFavoriteNode || !mFavoriteManager ) + return; + + // favorite node should be under the Recent node if it is present or + // the first top-level item in the toolbox if Recent node is not present + int idx = ( mRecentNode && mRecentLog ) ? 1 : 0; + + QModelIndex favoriteIndex = index( idx, 0 ); + const int prevCount = rowCount( favoriteIndex ); + if ( !resetting && prevCount > 0 ) + { + beginRemoveRows( favoriteIndex, 0, prevCount - 1 ); + mFavoriteNode->deleteChildren(); + endRemoveRows(); + } + + if ( !mRegistry ) + { + if ( !resetting ) + emit favoriteAlgorithmAdded(); + return; + } + + const QStringList favoriteAlgIds = mFavoriteManager->favoriteAlgorithmIds(); + QList< const QgsProcessingAlgorithm * > favoriteAlgorithms; + favoriteAlgorithms.reserve( favoriteAlgIds.count() ); + for ( const QString &id : favoriteAlgIds ) + { + const QgsProcessingAlgorithm *algorithm = mRegistry->algorithmById( id ); + if ( algorithm ) + favoriteAlgorithms << algorithm; + } + + if ( favoriteAlgorithms.empty() ) + { + if ( !resetting ) + emit favoriteAlgorithmAdded(); + return; + } + + if ( !resetting ) + { + beginInsertRows( favoriteIndex, 0, favoriteAlgorithms.count() - 1 ); + } + + for ( const QgsProcessingAlgorithm *algorithm : std::as_const( favoriteAlgorithms ) ) + { + std::unique_ptr< QgsProcessingToolboxModelAlgorithmNode > algorithmNode = std::make_unique< QgsProcessingToolboxModelAlgorithmNode >( algorithm ); + mFavoriteNode->addChildNode( algorithmNode.release() ); + } + + if ( !resetting ) + { + endInsertRows(); + emit favoriteAlgorithmAdded(); + } +} + void QgsProcessingToolboxModel::providerAdded( const QString &id ) { if ( !mRegistry ) @@ -356,6 +431,10 @@ QVariant QgsProcessingToolboxModel::data( const QModelIndex &index, int role ) c if ( QgsProcessingToolboxModelNode *node = index2node( index ) ) isRecentNode = node->nodeType() == QgsProcessingToolboxModelNode::NodeType::Recent; + bool isFavoriteNode = false; + if ( QgsProcessingToolboxModelNode *node = index2node( index ) ) + isFavoriteNode = node->nodeType() == QgsProcessingToolboxModelNode::NodeType::Favorite; + QgsProcessingProvider *provider = providerForIndex( index ); QgsProcessingToolboxModelGroupNode *groupNode = qobject_cast< QgsProcessingToolboxModelGroupNode * >( index2node( index ) ); const QgsProcessingAlgorithm *algorithm = algorithmForIndex( index ); @@ -375,6 +454,8 @@ QVariant QgsProcessingToolboxModel::data( const QModelIndex &index, int role ) c return groupNode->name(); else if ( isRecentNode ) return tr( "Recently used" ); + else if ( isFavoriteNode ) + return tr( "Favorites" ); else return QVariant(); @@ -420,6 +501,8 @@ QVariant QgsProcessingToolboxModel::data( const QModelIndex &index, int role ) c } else if ( isRecentNode ) return QgsApplication::getThemeIcon( QStringLiteral( "/mIconHistory.svg" ) ); + else if ( isFavoriteNode ) + return QgsApplication::getThemeIcon( QStringLiteral( "/mIconFavorites.svg" ) ); else if ( !index.parent().isValid() ) // top level groups get the QGIS icon return QgsApplication::getThemeIcon( QStringLiteral( "/providerQgis.svg" ) ); @@ -675,9 +758,9 @@ QModelIndex QgsProcessingToolboxModel::indexOfParentTreeNode( QgsProcessingToolb // QgsProcessingToolboxProxyModel::QgsProcessingToolboxProxyModel( QObject *parent, QgsProcessingRegistry *registry, - QgsProcessingRecentAlgorithmLog *recentLog ) + QgsProcessingRecentAlgorithmLog *recentLog, QgsProcessingFavoriteAlgorithmManager *favoriteManager ) : QSortFilterProxyModel( parent ) - , mModel( new QgsProcessingToolboxModel( this, registry, recentLog ) ) + , mModel( new QgsProcessingToolboxModel( this, registry, recentLog, favoriteManager ) ) { setSourceModel( mModel ); setDynamicSortFilter( true ); @@ -686,6 +769,7 @@ QgsProcessingToolboxProxyModel::QgsProcessingToolboxProxyModel( QObject *parent, sort( 0 ); connect( mModel, &QgsProcessingToolboxModel::recentAlgorithmAdded, this, [ = ] { invalidateFilter(); } ); + connect( mModel, &QgsProcessingToolboxModel::favoriteAlgorithmAdded, this, [ = ] { invalidateFilter(); } ); } QgsProcessingToolboxModel *QgsProcessingToolboxProxyModel::toolboxModel() @@ -817,6 +901,10 @@ bool QgsProcessingToolboxProxyModel::lessThan( const QModelIndex &left, const QM return true; else if ( rightType == QgsProcessingToolboxModelNode::NodeType::Recent ) return false; + else if ( leftType == QgsProcessingToolboxModelNode::NodeType::Favorite ) + return true; + else if ( rightType == QgsProcessingToolboxModelNode::NodeType::Favorite ) + return false; else if ( leftType != rightType ) { if ( leftType == QgsProcessingToolboxModelNode::NodeType::Provider ) @@ -846,7 +934,6 @@ bool QgsProcessingToolboxProxyModel::lessThan( const QModelIndex &left, const QM return left.row() < right.row(); } - // default mode is alphabetical order QString leftStr = sourceModel()->data( left ).toString(); QString rightStr = sourceModel()->data( right ).toString(); diff --git a/src/gui/processing/qgsprocessingtoolboxmodel.h b/src/gui/processing/qgsprocessingtoolboxmodel.h index 3c43349c1d0e..404107a07277 100644 --- a/src/gui/processing/qgsprocessingtoolboxmodel.h +++ b/src/gui/processing/qgsprocessingtoolboxmodel.h @@ -28,6 +28,7 @@ class QgsProcessingProvider; class QgsProcessingAlgorithm; class QgsProcessingToolboxModelGroupNode; class QgsProcessingRecentAlgorithmLog; +class QgsProcessingFavoriteAlgorithmManager; ///@cond PRIVATE @@ -55,6 +56,8 @@ class GUI_EXPORT QgsProcessingToolboxModelNode : public QObject sipType = sipType_QgsProcessingToolboxModelAlgorithmNode; else if ( node->nodeType() == QgsProcessingToolboxModelNode::NodeType::Recent ) sipType = sipType_QgsProcessingToolboxModelRecentNode; + else if ( node->nodeType() == QgsProcessingToolboxModelNode::NodeType::Favorite ) + sipType = sipType_QgsProcessingToolboxModelFavoriteNode; } else sipType = 0; @@ -72,6 +75,7 @@ class GUI_EXPORT QgsProcessingToolboxModelNode : public QObject Group SIP_MONKEYPATCH_COMPAT_NAME( NodeGroup ), //!< Group node Algorithm SIP_MONKEYPATCH_COMPAT_NAME( NodeAlgorithm ), //!< Algorithm node Recent SIP_MONKEYPATCH_COMPAT_NAME( NodeRecent ), //!< Recent algorithms node + Favorite, //!< Favorites algorithms node, since QGIS 3.40 }; Q_ENUM( NodeType ) // *INDENT-ON* @@ -152,6 +156,27 @@ class GUI_EXPORT QgsProcessingToolboxModelRecentNode : public QgsProcessingToolb }; +/** + * \brief Processing toolbox model node corresponding to the favorite algorithms group + * \ingroup gui + * \warning Not part of stable API and may change in future QGIS releases. + * \since QGIS 3.40 + */ +class GUI_EXPORT QgsProcessingToolboxModelFavoriteNode : public QgsProcessingToolboxModelNode +{ + Q_OBJECT + + public: + + /** + * Constructor for QgsProcessingToolboxModelRecentNode. + */ + QgsProcessingToolboxModelFavoriteNode() = default; + + NodeType nodeType() const override { return NodeType::Favorite; } + +}; + /** * \brief Processing toolbox model node corresponding to a Processing provider. * \ingroup gui @@ -312,9 +337,13 @@ class GUI_EXPORT QgsProcessingToolboxModel : public QAbstractItemModel * * If \a recentLog is specified then it will be used to create a "Recently used" top * level group containing recently used algorithms. + * + * If \a favoriteManager is specified then it will be used to create a "Favorites" top + * level group containing favorite algorithms. Since QGIS 3.40 */ QgsProcessingToolboxModel( QObject *parent SIP_TRANSFERTHIS = nullptr, QgsProcessingRegistry *registry = nullptr, - QgsProcessingRecentAlgorithmLog *recentLog = nullptr ); + QgsProcessingRecentAlgorithmLog *recentLog = nullptr, + QgsProcessingFavoriteAlgorithmManager *favoriteManager = nullptr ); Qt::ItemFlags flags( const QModelIndex &index ) const override; QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override; @@ -388,10 +417,16 @@ class GUI_EXPORT QgsProcessingToolboxModel : public QAbstractItemModel */ void recentAlgorithmAdded(); + /** + * Emitted whenever favorite algorithms are added to the model. + */ + void favoriteAlgorithmAdded(); + private slots: void rebuild(); void repopulateRecentAlgorithms( bool resetting = false ); + void repopulateFavoriteAlgorithms( bool resetting = false ); void providerAdded( const QString &id ); void providerRemoved( const QString &id ); @@ -399,9 +434,11 @@ class GUI_EXPORT QgsProcessingToolboxModel : public QAbstractItemModel QPointer< QgsProcessingRegistry > mRegistry; QPointer< QgsProcessingRecentAlgorithmLog > mRecentLog; + QPointer< QgsProcessingFavoriteAlgorithmManager > mFavoriteManager; std::unique_ptr< QgsProcessingToolboxModelGroupNode > mRootNode; QgsProcessingToolboxModelRecentNode *mRecentNode = nullptr; + QgsProcessingToolboxModelFavoriteNode *mFavoriteNode = nullptr; void addProvider( QgsProcessingProvider *provider ); @@ -458,10 +495,14 @@ class GUI_EXPORT QgsProcessingToolboxProxyModel: public QSortFilterProxyModel * * If \a recentLog is specified then it will be used to create a "Recently used" top * level group containing recently used algorithms. + * + * If \a favoriteManager is specified then it will be used to create a "Favorites" top + * level group containing favorite algorithms. SInce QGIS 3.40 */ explicit QgsProcessingToolboxProxyModel( QObject *parent SIP_TRANSFERTHIS = nullptr, QgsProcessingRegistry *registry = nullptr, - QgsProcessingRecentAlgorithmLog *recentLog = nullptr ); + QgsProcessingRecentAlgorithmLog *recentLog = nullptr, + QgsProcessingFavoriteAlgorithmManager *favoriteManager = nullptr ); /** * Returns the underlying source Processing toolbox model. diff --git a/src/gui/processing/qgsprocessingtoolboxtreeview.cpp b/src/gui/processing/qgsprocessingtoolboxtreeview.cpp index 0e948b710d67..a99b93bad0ce 100644 --- a/src/gui/processing/qgsprocessingtoolboxtreeview.cpp +++ b/src/gui/processing/qgsprocessingtoolboxtreeview.cpp @@ -22,17 +22,18 @@ QgsProcessingToolboxTreeView::QgsProcessingToolboxTreeView( QWidget *parent, QgsProcessingRegistry *registry, - QgsProcessingRecentAlgorithmLog *recentLog ) + QgsProcessingRecentAlgorithmLog *recentLog, + QgsProcessingFavoriteAlgorithmManager *favoriteManager ) : QTreeView( parent ) { - mModel = new QgsProcessingToolboxProxyModel( this, registry, recentLog ); + mModel = new QgsProcessingToolboxProxyModel( this, registry, recentLog, favoriteManager ); mToolboxModel = mModel->toolboxModel(); setModel( mModel ); } -void QgsProcessingToolboxTreeView::setRegistry( QgsProcessingRegistry *registry, QgsProcessingRecentAlgorithmLog *recentLog ) +void QgsProcessingToolboxTreeView::setRegistry( QgsProcessingRegistry *registry, QgsProcessingRecentAlgorithmLog *recentLog, QgsProcessingFavoriteAlgorithmManager *favoriteManager ) { - QgsProcessingToolboxProxyModel *newModel = new QgsProcessingToolboxProxyModel( this, registry, recentLog ); + QgsProcessingToolboxProxyModel *newModel = new QgsProcessingToolboxProxyModel( this, registry, recentLog, favoriteManager ); mToolboxModel = newModel->toolboxModel(); setModel( newModel ); mModel->deleteLater(); diff --git a/src/gui/processing/qgsprocessingtoolboxtreeview.h b/src/gui/processing/qgsprocessingtoolboxtreeview.h index 479bd3269525..b407148089c4 100644 --- a/src/gui/processing/qgsprocessingtoolboxtreeview.h +++ b/src/gui/processing/qgsprocessingtoolboxtreeview.h @@ -24,6 +24,7 @@ class QgsProcessingRegistry; class QgsProcessingRecentAlgorithmLog; class QgsProcessingAlgorithm; +class QgsProcessingFavoriteAlgorithmManager; ///@cond PRIVATE @@ -48,20 +49,28 @@ class GUI_EXPORT QgsProcessingToolboxTreeView : public QTreeView * * If \a recentLog is specified then it will be used to create a "Recently used" top * level group containing recently used algorithms. + * + * If \a favoriteManager is specified then it will be used to create a "Favorites" top + * level group containing favorite algorithms. Since QGIS 3.40 */ QgsProcessingToolboxTreeView( QWidget *parent SIP_TRANSFERTHIS = nullptr, QgsProcessingRegistry *registry = nullptr, - QgsProcessingRecentAlgorithmLog *recentLog = nullptr ); + QgsProcessingRecentAlgorithmLog *recentLog = nullptr, + QgsProcessingFavoriteAlgorithmManager *favoriteManager = nullptr ); /** * Sets the processing \a registry associated with the view. * * If \a recentLog is specified then it will be used to create a "Recently used" top * level group containing recently used algorithms. + * + * If \a favoriteManager is specified then it will be used to create a "Favorites" top + * level group containing favorite algorithms. Since QGIS 3.40 */ void setRegistry( QgsProcessingRegistry *registry, - QgsProcessingRecentAlgorithmLog *recentLog = nullptr ); + QgsProcessingRecentAlgorithmLog *recentLog = nullptr, + QgsProcessingFavoriteAlgorithmManager *favoriteManager = nullptr ); /** * Sets the toolbox proxy model used to drive the view. diff --git a/src/gui/qgsgui.cpp b/src/gui/qgsgui.cpp index f0e7a1176c42..2ec344bd8f76 100644 --- a/src/gui/qgsgui.cpp +++ b/src/gui/qgsgui.cpp @@ -43,6 +43,7 @@ #include "qgsshortcutsmanager.h" #include "qgswidgetstatehelper_p.h" #include "qgslogger.h" +#include "qgsprocessingfavoritealgorithmmanager.h" #include "qgsprocessingrecentalgorithmlog.h" #include "qgswindowmanagerinterface.h" #include "qgssettings.h" @@ -156,6 +157,11 @@ QgsCodeEditorColorSchemeRegistry *QgsGui::codeEditorColorSchemeRegistry() return instance()->mCodeEditorColorSchemeRegistry; } +QgsProcessingFavoriteAlgorithmManager *QgsGui::processingFavoriteAlgorithmManager() +{ + return instance()->mProcessingFavoriteAlgorithmManager; +} + QgsProcessingRecentAlgorithmLog *QgsGui::processingRecentAlgorithmLog() { return instance()->mProcessingRecentAlgorithmLog; @@ -231,6 +237,7 @@ QgsGui::~QgsGui() { delete mProcessingGuiRegistry; delete mDataItemGuiProviderRegistry; + delete mProcessingFavoriteAlgorithmManager; delete mProcessingRecentAlgorithmLog; delete mLayoutItemGuiRegistry; delete mAnnotationItemGuiRegistry; @@ -344,6 +351,7 @@ QgsGui::QgsGui() mAnnotationItemGuiRegistry->addDefaultItems(); mWidgetStateHelper = new QgsWidgetStateHelper(); + mProcessingFavoriteAlgorithmManager = new QgsProcessingFavoriteAlgorithmManager(); mProcessingRecentAlgorithmLog = new QgsProcessingRecentAlgorithmLog(); mProcessingGuiRegistry = new QgsProcessingGuiRegistry(); diff --git a/src/gui/qgsgui.h b/src/gui/qgsgui.h index 30d3f08af9f6..cd7145a6d8c3 100644 --- a/src/gui/qgsgui.h +++ b/src/gui/qgsgui.h @@ -34,6 +34,7 @@ class QgsLayoutItemGuiRegistry; class QgsAnnotationItemGuiRegistry; class QgsWidgetStateHelper; class QgsProcessingGuiRegistry; +class QgsProcessingFavoriteAlgorithmManager; class QgsProcessingRecentAlgorithmLog; class QgsWindowManagerInterface; class QgsDataItemGuiProviderRegistry; @@ -157,6 +158,12 @@ class GUI_EXPORT QgsGui : public QObject */ static QgsProcessingRecentAlgorithmLog *processingRecentAlgorithmLog(); + /** + * Returns the global Processing favorite algorithm manager, used for tracking favorite Processing algorithms. + * \since QGIS 3.40 + */ + static QgsProcessingFavoriteAlgorithmManager *processingFavoriteAlgorithmManager(); + /** * Returns the global data item GUI provider registry, used for tracking providers which affect the browser * GUI. @@ -323,6 +330,7 @@ class GUI_EXPORT QgsGui : public QObject QgsLayoutItemGuiRegistry *mLayoutItemGuiRegistry = nullptr; QgsAnnotationItemGuiRegistry *mAnnotationItemGuiRegistry = nullptr; QgsProcessingGuiRegistry *mProcessingGuiRegistry = nullptr; + QgsProcessingFavoriteAlgorithmManager *mProcessingFavoriteAlgorithmManager = nullptr; QgsProcessingRecentAlgorithmLog *mProcessingRecentAlgorithmLog = nullptr; QgsNumericFormatGuiRegistry *mNumericFormatGuiRegistry = nullptr; QgsDataItemGuiProviderRegistry *mDataItemGuiProviderRegistry = nullptr; diff --git a/tests/src/gui/testqgsprocessingmodel.cpp b/tests/src/gui/testqgsprocessingmodel.cpp index 5ae1dad7e1b3..772eebe6b3ec 100644 --- a/tests/src/gui/testqgsprocessingmodel.cpp +++ b/tests/src/gui/testqgsprocessingmodel.cpp @@ -18,6 +18,7 @@ #include "qgsprocessingregistry.h" #include "qgsprocessingtoolboxmodel.h" #include "qgsprocessingrecentalgorithmlog.h" +#include "qgsprocessingfavoritealgorithmmanager.h" #include "qgsprocessingtoolboxtreeview.h" #include "qgssettings.h" @@ -141,19 +142,22 @@ void TestQgsProcessingModel::testModel() { QgsProcessingRegistry registry; QgsProcessingRecentAlgorithmLog recentLog; - QgsProcessingToolboxModel model( nullptr, ®istry, &recentLog ); + QgsProcessingFavoriteAlgorithmManager favoriteManager; + QgsProcessingToolboxModel model( nullptr, ®istry, &recentLog, &favoriteManager ); #ifdef ENABLE_MODELTEST new ModelTest( &model, this ); // for model validity checking #endif QCOMPARE( model.columnCount(), 1 ); - QCOMPARE( model.rowCount(), 1 ); + QCOMPARE( model.rowCount(), 2 ); QVERIFY( model.hasChildren() ); QCOMPARE( model.data( model.index( 0, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "Recently used" ) ); + QCOMPARE( model.data( model.index( 1, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "Favorites" ) ); QCOMPARE( model.rowCount( model.index( 0, 0, QModelIndex() ) ), 0 ); QVERIFY( !model.providerForIndex( model.index( 0, 0, QModelIndex() ) ) ); QVERIFY( !model.providerForIndex( model.index( 1, 0, QModelIndex() ) ) ); + QVERIFY( !model.providerForIndex( model.index( 2, 0, QModelIndex() ) ) ); QVERIFY( !model.indexForProvider( nullptr ).isValid() ); QVERIFY( model.index2node( QModelIndex() ) ); // root node QCOMPARE( model.index2node( QModelIndex() )->nodeType(), QgsProcessingToolboxModelNode::NodeType::Group ); @@ -163,43 +167,45 @@ void TestQgsProcessingModel::testModel() // add a provider DummyProvider *p1 = new DummyProvider( "p1", "provider1" ); registry.addProvider( p1 ); - QCOMPARE( model.rowCount(), 2 ); + QCOMPARE( model.rowCount(), 3 ); QVERIFY( model.hasChildren() ); QVERIFY( model.index( 0, 0, QModelIndex() ).isValid() ); QCOMPARE( model.data( model.index( 0, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "Recently used" ) ); - QCOMPARE( model.providerForIndex( model.index( 1, 0, QModelIndex() ) ), p1 ); - QVERIFY( !model.providerForIndex( model.index( 2, 0, QModelIndex() ) ) ); - QCOMPARE( model.indexForProvider( p1->id() ), model.index( 1, 0, QModelIndex() ) ); + QVERIFY( model.index( 1, 0, QModelIndex() ).isValid() ); + QCOMPARE( model.data( model.index( 1, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "Favorites" ) ); + QCOMPARE( model.providerForIndex( model.index( 2, 0, QModelIndex() ) ), p1 ); + QVERIFY( !model.providerForIndex( model.index( 3, 0, QModelIndex() ) ) ); + QCOMPARE( model.indexForProvider( p1->id() ), model.index( 2, 0, QModelIndex() ) ); QVERIFY( !model.indexForProvider( nullptr ).isValid() ); - QCOMPARE( model.rowCount( model.index( 1, 0, QModelIndex() ) ), 0 ); - QVERIFY( !model.hasChildren( model.index( 1, 0, QModelIndex() ) ) ); - QCOMPARE( model.data( model.index( 1, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "provider1" ) ); - QCOMPARE( model.data( model.index( 1, 0, QModelIndex() ), Qt::ToolTipRole ).toString(), QStringLiteral( "long name provider1" ) ); - QVERIFY( !model.data( model.index( 2, 0, QModelIndex() ), Qt::DisplayRole ).isValid() ); - QVERIFY( !model.data( model.index( 2, 0, QModelIndex() ), Qt::ToolTipRole ).isValid() ); + QCOMPARE( model.rowCount( model.index( 2, 0, QModelIndex() ) ), 0 ); + QVERIFY( !model.hasChildren( model.index( 2, 0, QModelIndex() ) ) ); + QCOMPARE( model.data( model.index( 2, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "provider1" ) ); + QCOMPARE( model.data( model.index( 2, 0, QModelIndex() ), Qt::ToolTipRole ).toString(), QStringLiteral( "long name provider1" ) ); + QVERIFY( !model.data( model.index( 3, 0, QModelIndex() ), Qt::DisplayRole ).isValid() ); + QVERIFY( !model.data( model.index( 3, 0, QModelIndex() ), Qt::ToolTipRole ).isValid() ); // second provider DummyProvider *p2 = new DummyProvider( "p2", "provider2" ); registry.addProvider( p2 ); - QCOMPARE( model.rowCount(), 3 ); + QCOMPARE( model.rowCount(), 4 ); QVERIFY( model.hasChildren() ); - QVERIFY( model.index( 2, 0, QModelIndex() ).isValid() ); - QCOMPARE( model.providerForIndex( model.index( 1, 0, QModelIndex() ) ), p1 ); - QCOMPARE( model.providerForIndex( model.index( 2, 0, QModelIndex() ) ), p2 ); - QVERIFY( !model.providerForIndex( model.index( 3, 0, QModelIndex() ) ) ); - QCOMPARE( model.indexForProvider( p1->id() ), model.index( 1, 0, QModelIndex() ) ); - QCOMPARE( model.indexForProvider( p2->id() ), model.index( 2, 0, QModelIndex() ) ); + QVERIFY( model.index( 3, 0, QModelIndex() ).isValid() ); + QCOMPARE( model.providerForIndex( model.index( 2, 0, QModelIndex() ) ), p1 ); + QCOMPARE( model.providerForIndex( model.index( 3, 0, QModelIndex() ) ), p2 ); + QVERIFY( !model.providerForIndex( model.index( 4, 0, QModelIndex() ) ) ); + QCOMPARE( model.indexForProvider( p1->id() ), model.index( 2, 0, QModelIndex() ) ); + QCOMPARE( model.indexForProvider( p2->id() ), model.index( 3, 0, QModelIndex() ) ); QVERIFY( !model.indexForProvider( nullptr ).isValid() ); - QVERIFY( !model.hasChildren( model.index( 2, 0, QModelIndex() ) ) ); + QVERIFY( !model.hasChildren( model.index( 3, 0, QModelIndex() ) ) ); - QCOMPARE( model.data( model.index( 1, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "provider1" ) ); - QCOMPARE( model.data( model.index( 1, 0, QModelIndex() ), Qt::ToolTipRole ).toString(), QStringLiteral( "long name provider1" ) ); - QCOMPARE( model.data( model.index( 2, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "provider2" ) ); - QCOMPARE( model.data( model.index( 2, 0, QModelIndex() ), Qt::ToolTipRole ).toString(), QStringLiteral( "long name provider2" ) ); - QVERIFY( !model.data( model.index( 3, 0, QModelIndex() ), Qt::DisplayRole ).isValid() ); - QVERIFY( !model.data( model.index( 3, 0, QModelIndex() ), Qt::ToolTipRole ).isValid() ); + QCOMPARE( model.data( model.index( 2, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "provider1" ) ); + QCOMPARE( model.data( model.index( 2, 0, QModelIndex() ), Qt::ToolTipRole ).toString(), QStringLiteral( "long name provider1" ) ); + QCOMPARE( model.data( model.index( 3, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "provider2" ) ); + QCOMPARE( model.data( model.index( 3, 0, QModelIndex() ), Qt::ToolTipRole ).toString(), QStringLiteral( "long name provider2" ) ); + QVERIFY( !model.data( model.index( 4, 0, QModelIndex() ), Qt::DisplayRole ).isValid() ); + QVERIFY( !model.data( model.index( 4, 0, QModelIndex() ), Qt::ToolTipRole ).isValid() ); // provider with algs and groups DummyAlgorithm *a1 = new DummyAlgorithm( "a1", "group1", Qgis::ProcessingAlgorithmFlag::HideFromModeler, QStringLiteral( "tag1,tag2" ), QStringLiteral( "short desc a" ) ); @@ -207,20 +213,20 @@ void TestQgsProcessingModel::testModel() DummyProvider *p3 = new DummyProvider( "p3", "provider3", QList< QgsProcessingAlgorithm * >() << a1 << a2 ); registry.addProvider( p3 ); - QCOMPARE( model.rowCount(), 4 ); + QCOMPARE( model.rowCount(), 5 ); QVERIFY( model.hasChildren() ); - QVERIFY( model.index( 3, 0, QModelIndex() ).isValid() ); - QCOMPARE( model.providerForIndex( model.index( 3, 0, QModelIndex() ) ), p3 ); - QCOMPARE( model.indexForProvider( p1->id() ), model.index( 1, 0, QModelIndex() ) ); - QCOMPARE( model.indexForProvider( p2->id() ), model.index( 2, 0, QModelIndex() ) ); - QCOMPARE( model.indexForProvider( p3->id() ), model.index( 3, 0, QModelIndex() ) ); - QCOMPARE( model.rowCount( model.index( 2, 0, QModelIndex() ) ), 0 ); - QCOMPARE( model.rowCount( model.index( 3, 0, QModelIndex() ) ), 2 ); - QVERIFY( !model.hasChildren( model.index( 2, 0, QModelIndex() ) ) ); - QVERIFY( model.hasChildren( model.index( 3, 0, QModelIndex() ) ) ); - QModelIndex providerIndex = model.index( 3, 0, QModelIndex() ); - QVERIFY( !model.providerForIndex( model.index( 1, 0, providerIndex ) ) ); + QVERIFY( model.index( 4, 0, QModelIndex() ).isValid() ); + QCOMPARE( model.providerForIndex( model.index( 4, 0, QModelIndex() ) ), p3 ); + QCOMPARE( model.indexForProvider( p1->id() ), model.index( 2, 0, QModelIndex() ) ); + QCOMPARE( model.indexForProvider( p2->id() ), model.index( 3, 0, QModelIndex() ) ); + QCOMPARE( model.indexForProvider( p3->id() ), model.index( 4, 0, QModelIndex() ) ); + QCOMPARE( model.rowCount( model.index( 3, 0, QModelIndex() ) ), 0 ); + QCOMPARE( model.rowCount( model.index( 4, 0, QModelIndex() ) ), 2 ); + QVERIFY( !model.hasChildren( model.index( 3, 0, QModelIndex() ) ) ); + QVERIFY( model.hasChildren( model.index( 4, 0, QModelIndex() ) ) ); + QModelIndex providerIndex = model.index( 4, 0, QModelIndex() ); QVERIFY( !model.providerForIndex( model.index( 2, 0, providerIndex ) ) ); + QVERIFY( !model.providerForIndex( model.index( 3, 0, providerIndex ) ) ); QCOMPARE( model.data( model.index( 0, 0, providerIndex ), Qt::DisplayRole ).toString(), QStringLiteral( "group1" ) ); QCOMPARE( model.data( model.index( 0, 0, providerIndex ), Qt::ToolTipRole ).toString(), QStringLiteral( "group1" ) ); @@ -274,7 +280,7 @@ void TestQgsProcessingModel::testModel() DummyAlgorithm *a7 = new DummyAlgorithm( "a7", "group2" ); DummyProvider *p5 = new DummyProvider( "p5", "provider5", QList< QgsProcessingAlgorithm * >() << a5 << a6 << a7 ); registry.addProvider( p5 ); - QCOMPARE( model.rowCount(), 6 ); + QCOMPARE( model.rowCount(), 7 ); QModelIndex p5ProviderIndex = model.indexForProvider( p5->id() ); QCOMPARE( model.rowCount( p5ProviderIndex ), 3 ); @@ -292,7 +298,7 @@ void TestQgsProcessingModel::testModel() // reload provider p5->refreshAlgorithms(); - QCOMPARE( model.rowCount(), 6 ); + QCOMPARE( model.rowCount(), 7 ); p5ProviderIndex = model.indexForProvider( p5->id() ); QCOMPARE( model.rowCount( p5ProviderIndex ), 3 ); @@ -332,30 +338,63 @@ void TestQgsProcessingModel::testModel() QCOMPARE( model.data( model.index( 0, 0, recentIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p4:a3" ) ); QCOMPARE( model.data( model.index( 1, 0, recentIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p5:a5" ) ); + // favorite algorithms + QModelIndex favoriteIndex = model.index( 1, 0, QModelIndex() ); + QCOMPARE( model.data( favoriteIndex, Qt::DisplayRole ).toString(), QStringLiteral( "Favorites" ) ); + QCOMPARE( model.rowCount( favoriteIndex ), 0 ); + favoriteManager.add( QStringLiteral( "p5:a5" ) ); + QCOMPARE( model.rowCount( favoriteIndex ), 1 ); + QCOMPARE( model.data( model.index( 0, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p5:a5" ) ); + favoriteManager.add( QStringLiteral( "not valid" ) ); + QCOMPARE( model.rowCount( favoriteIndex ), 1 ); + QCOMPARE( model.data( model.index( 0, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p5:a5" ) ); + favoriteManager.add( QStringLiteral( "p4:a3" ) ); + QCOMPARE( model.rowCount( favoriteIndex ), 2 ); + QCOMPARE( model.data( model.index( 0, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p5:a5" ) ); + QCOMPARE( model.data( model.index( 1, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p4:a3" ) ); + favoriteManager.remove( QStringLiteral( "p5:a5" ) ); + QCOMPARE( model.rowCount( favoriteIndex ), 1 ); + QCOMPARE( model.data( model.index( 0, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p4:a3" ) ); + favoriteManager.clear(); + QCOMPARE( model.rowCount( favoriteIndex ), 0 ); + favoriteManager.add( QStringLiteral( "p5:a5" ) ); + favoriteManager.add( QStringLiteral( "p4:a3" ) ); + QCOMPARE( model.rowCount( favoriteIndex ), 2 ); + QCOMPARE( model.data( model.index( 0, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p5:a5" ) ); + QCOMPARE( model.data( model.index( 1, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p4:a3" ) ); + // remove a provider registry.removeProvider( p1 ); - QCOMPARE( model.rowCount(), 5 ); + QCOMPARE( model.rowCount(), 6 ); QVERIFY( model.index( 0, 0, QModelIndex() ).isValid() ); - QCOMPARE( model.providerForIndex( model.index( 1, 0, QModelIndex() ) ), p2 ); - QCOMPARE( model.data( model.index( 1, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "provider2" ) ); + QCOMPARE( model.providerForIndex( model.index( 2, 0, QModelIndex() ) ), p2 ); + QCOMPARE( model.data( model.index( 2, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "provider2" ) ); registry.removeProvider( p5 ); - QCOMPARE( model.rowCount(), 4 ); + QCOMPARE( model.rowCount(), 5 ); recentIndex = model.index( 0, 0, QModelIndex() ); QCOMPARE( model.rowCount( recentIndex ), 1 ); QCOMPARE( model.data( model.index( 0, 0, recentIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p4:a3" ) ); + favoriteIndex = model.index( 1, 0, QModelIndex() ); + QCOMPARE( model.rowCount( favoriteIndex ), 1 ); + QCOMPARE( model.data( model.index( 0, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p4:a3" ) ); registry.removeProvider( p2 ); - QCOMPARE( model.rowCount(), 3 ); + QCOMPARE( model.rowCount(), 4 ); registry.removeProvider( p3 ); - QCOMPARE( model.rowCount(), 2 ); + QCOMPARE( model.rowCount(), 3 ); recentIndex = model.index( 0, 0, QModelIndex() ); QCOMPARE( model.rowCount( recentIndex ), 1 ); + favoriteIndex = model.index( 1, 0, QModelIndex() ); + QCOMPARE( model.rowCount( favoriteIndex ), 1 ); registry.removeProvider( p4 ); recentIndex = model.index( 0, 0, QModelIndex() ); QCOMPARE( model.rowCount( recentIndex ), 0 ); - QCOMPARE( model.rowCount(), 1 ); + favoriteIndex = model.index( 1, 0, QModelIndex() ); + QCOMPARE( model.rowCount( favoriteIndex ), 0 ); + QCOMPARE( model.rowCount(), 2 ); QCOMPARE( model.columnCount(), 1 ); QVERIFY( model.hasChildren() ); QCOMPARE( model.data( model.index( 0, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "Recently used" ) ); + QCOMPARE( model.data( model.index( 1, 0, QModelIndex() ), Qt::DisplayRole ).toString(), QStringLiteral( "Favorites" ) ); QVERIFY( !model.providerForIndex( model.index( 0, 0, QModelIndex() ) ) ); QVERIFY( !model.providerForIndex( model.index( 1, 0, QModelIndex() ) ) ); QVERIFY( !model.indexForProvider( nullptr ).isValid() ); @@ -392,7 +431,8 @@ void TestQgsProcessingModel::testProxyModel() QgsSettings().clear(); QgsProcessingRegistry registry; QgsProcessingRecentAlgorithmLog recentLog; - QgsProcessingToolboxProxyModel model( nullptr, ®istry, &recentLog ); + QgsProcessingFavoriteAlgorithmManager favoriteManager; + QgsProcessingToolboxProxyModel model( nullptr, ®istry, &recentLog, &favoriteManager ); #ifdef ENABLE_MODELTEST new ModelTest( &model, this ); // for model validity checking @@ -548,13 +588,39 @@ void TestQgsProcessingModel::testProxyModel() QCOMPARE( model.data( model.index( 1, 0, recentIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "qgis:a1" ) ); QCOMPARE( model.data( model.index( 2, 0, recentIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p1:a2" ) ); + // check sort order of favorite algorithms + favoriteManager.add( QStringLiteral( "p2:a1" ) ); + QCOMPARE( model.rowCount(), 6 ); + const QModelIndex favoriteIndex = model.index( 1, 0, QModelIndex() ); + QCOMPARE( model.data( favoriteIndex, Qt::DisplayRole ).toString(), QStringLiteral( "Favorites" ) ); + QCOMPARE( model.rowCount( favoriteIndex ), 1 ); + QCOMPARE( model.data( model.index( 0, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p2:a1" ) ); + favoriteManager.add( QStringLiteral( "p1:a2" ) ); + QCOMPARE( model.rowCount( favoriteIndex ), 2 ); + QCOMPARE( model.data( model.index( 0, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p2:a1" ) ); + QCOMPARE( model.data( model.index( 1, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p1:a2" ) ); + favoriteManager.add( QStringLiteral( "qgis:a1" ) ); + QCOMPARE( model.rowCount( favoriteIndex ), 3 ); + QCOMPARE( model.data( model.index( 0, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p2:a1" ) ); + QCOMPARE( model.data( model.index( 1, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "qgis:a1" ) ); + QCOMPARE( model.data( model.index( 2, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p1:a2" ) ); + favoriteManager.remove( QStringLiteral( "p2:a1" ) ); + QCOMPARE( model.rowCount( favoriteIndex ), 2 ); + QCOMPARE( model.data( model.index( 0, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "qgis:a1" ) ); + QCOMPARE( model.data( model.index( 1, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p1:a2" ) ); + favoriteManager.add( QStringLiteral( "p2:a1" ) ); + QCOMPARE( model.rowCount( favoriteIndex ), 3 ); + QCOMPARE( model.data( model.index( 0, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "qgis:a1" ) ); + QCOMPARE( model.data( model.index( 1, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p2:a1" ) ); + QCOMPARE( model.data( model.index( 2, 0, favoriteIndex ), static_cast< int >( QgsProcessingToolboxModel::CustomRole::AlgorithmId ) ).toString(), QStringLiteral( "p1:a2" ) ); + // inactive provider - should not be visible - QCOMPARE( model.rowCount(), 5 ); + QCOMPARE( model.rowCount(), 6 ); DummyAlgorithm *qgisA31 = new DummyAlgorithm( "a3", "group1" ); DummyProvider *p3 = new DummyProvider( "p3", "provider3", QList< QgsProcessingAlgorithm * >() << qgisA31 ); p3->mActive = false; registry.addProvider( p3 ); - QCOMPARE( model.rowCount(), 5 ); + QCOMPARE( model.rowCount(), 6 ); } void TestQgsProcessingModel::testView() @@ -562,7 +628,8 @@ void TestQgsProcessingModel::testView() QgsSettings().clear(); QgsProcessingRegistry registry; QgsProcessingRecentAlgorithmLog recentLog; - QgsProcessingToolboxTreeView view( nullptr, ®istry, &recentLog ); + QgsProcessingFavoriteAlgorithmManager favoriteManager; + QgsProcessingToolboxTreeView view( nullptr, ®istry, &recentLog, &favoriteManager ); // Check view model consistency QVERIFY( view.mModel ); @@ -660,13 +727,14 @@ void TestQgsProcessingModel::testKnownIssues() { QgsProcessingRegistry registry; QgsProcessingRecentAlgorithmLog recentLog; - const QgsProcessingToolboxModel model( nullptr, ®istry, &recentLog ); + QgsProcessingFavoriteAlgorithmManager favoriteManager; + const QgsProcessingToolboxModel model( nullptr, ®istry, &recentLog, &favoriteManager ); DummyAlgorithm *a1 = new DummyAlgorithm( "a1", "group1", Qgis::ProcessingAlgorithmFlag::KnownIssues, QStringLiteral( "tag1,tag2" ), QStringLiteral( "short desc a" ) ); DummyAlgorithm *a2 = new DummyAlgorithm( "b1", "group1", Qgis::ProcessingAlgorithmFlags(), QStringLiteral( "tag1,tag2" ), QStringLiteral( "short desc b" ) ); DummyProvider *p = new DummyProvider( "p3", "provider3", QList< QgsProcessingAlgorithm * >() << a1 << a2 ); registry.addProvider( p ); - QModelIndex providerIndex = model.index( 1, 0, QModelIndex() ); + QModelIndex providerIndex = model.index( 2, 0, QModelIndex() ); QModelIndex group1Index = model.index( 0, 0, providerIndex ); QCOMPARE( model.data( model.index( 0, 0, group1Index ), Qt::DisplayRole ).toString(), QStringLiteral( "a1" ) ); QVERIFY( model.data( model.index( 0, 0, group1Index ), Qt::ToolTipRole ).toString().contains( QStringLiteral( "known issues" ) ) ); @@ -675,7 +743,7 @@ void TestQgsProcessingModel::testKnownIssues() QVERIFY( !model.data( model.index( 1, 0, group1Index ), Qt::ToolTipRole ).toString().contains( QStringLiteral( "known issues" ) ) ); QCOMPARE( model.data( model.index( 1, 0, group1Index ), Qt::ForegroundRole ).value< QBrush >().color().name(), QStringLiteral( "#000000" ) ); - QgsProcessingToolboxProxyModel proxyModel( nullptr, ®istry, &recentLog ); + QgsProcessingToolboxProxyModel proxyModel( nullptr, ®istry, &recentLog, &favoriteManager ); providerIndex = proxyModel.index( 0, 0, QModelIndex() ); group1Index = proxyModel.index( 0, 0, providerIndex ); // by default known issues are filtered out @@ -691,8 +759,6 @@ void TestQgsProcessingModel::testKnownIssues() QCOMPARE( proxyModel.data( proxyModel.index( 1, 0, group1Index ), Qt::DisplayRole ).toString(), QStringLiteral( "b1" ) ); QVERIFY( !proxyModel.data( proxyModel.index( 1, 0, group1Index ), Qt::ToolTipRole ).toString().contains( QStringLiteral( "known issues" ) ) ); QCOMPARE( proxyModel.data( proxyModel.index( 1, 0, group1Index ), Qt::ForegroundRole ).value< QBrush >().color().name(), QStringLiteral( "#000000" ) ); - - } QGSTEST_MAIN( TestQgsProcessingModel ) diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index 9b56ac5f6aa2..4d01f11d51cd 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -480,6 +480,7 @@ if (WITH_GUI) ADD_PYTHON_TEST(PyQgsPanelWidget test_qgspanelwidget.py) ADD_PYTHON_TEST(PyQgsPanelWidgetStack test_qgspanelwidgetstack.py) ADD_PYTHON_TEST(PyQgsPointCloudAttributeComboBox test_qgspointcloudattributecombobox.py) + ADD_PYTHON_TEST(PyQgsProcessingFavoriteAlgorithmManager test_qgsprocessingfavoritealgorithmmanager.py) ADD_PYTHON_TEST(PyQgsProcessingRecentAlgorithmLog test_qgsprocessingrecentalgorithmslog.py) ADD_PYTHON_TEST(PyQgsProjectionSelectionWidgets test_qgsprojectionselectionwidgets.py) ADD_PYTHON_TEST(PyQgsProjectViewSettings test_qgsprojectviewsettings.py) diff --git a/tests/src/python/test_qgsprocessingfavoritealgorithmmanager.py b/tests/src/python/test_qgsprocessingfavoritealgorithmmanager.py new file mode 100644 index 000000000000..a09e9ecebd11 --- /dev/null +++ b/tests/src/python/test_qgsprocessingfavoritealgorithmmanager.py @@ -0,0 +1,93 @@ +"""QGIS Unit tests for QgsProcessingFavoriteAlgorithmManager. + +.. note:: 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__ = 'Alexander Bruy' +__date__ = '2024-02' +__copyright__ = 'Copyright 2024, The QGIS Project' + +from qgis.PyQt.QtCore import QCoreApplication +from qgis.PyQt.QtTest import QSignalSpy +from qgis.core import QgsSettings +from qgis.gui import QgsGui, QgsProcessingFavoriteAlgorithmManager +import unittest +from qgis.testing import start_app, QgisTestCase + +start_app() + + +class TestQgsProcessingFavoriteAlgorithmManager(QgisTestCase): + + @classmethod + def setUpClass(cls): + """Run before all tests""" + super().setUpClass() + QCoreApplication.setOrganizationName("QGIS_Test") + QCoreApplication.setOrganizationDomain(cls.__name__) + QCoreApplication.setApplicationName(cls.__name__) + QgsSettings().clear() + + def test_log(self): + log = QgsProcessingFavoriteAlgorithmManager() + self.assertFalse(log.favoriteAlgorithmIds()) + spy = QSignalSpy(log.changed) + + log.add('test') + self.assertEqual(log.favoriteAlgorithmIds(), ['test']) + self.assertEqual(len(spy), 1) + log.add('test') + self.assertEqual(log.favoriteAlgorithmIds(), ['test']) + self.assertEqual(len(spy), 1) + + log.add('test2') + self.assertEqual(log.favoriteAlgorithmIds(), ['test', 'test2']) + self.assertEqual(len(spy), 2) + + log.remove('test') + self.assertEqual(log.favoriteAlgorithmIds(), ['test2']) + self.assertEqual(len(spy), 3) + + log.add('test') + self.assertEqual(log.favoriteAlgorithmIds(), ['test2', 'test']) + self.assertEqual(len(spy), 4) + + log.add('test3') + self.assertEqual(log.favoriteAlgorithmIds(), ['test2', 'test', 'test3']) + self.assertEqual(len(spy), 5) + + log.add('test4') + self.assertEqual(log.favoriteAlgorithmIds(), ['test2', 'test', 'test3', 'test4']) + self.assertEqual(len(spy), 6) + + log.add('test5') + self.assertEqual(log.favoriteAlgorithmIds(), ['test2', 'test', 'test3', 'test4', 'test5']) + self.assertEqual(len(spy), 7) + + log.add('test6') + self.assertEqual(log.favoriteAlgorithmIds(), ['test2', 'test', 'test3', 'test4', 'test5', 'test6']) + self.assertEqual(len(spy), 8) + + log.add('test7') + self.assertEqual(log.favoriteAlgorithmIds(), ['test2', 'test', 'test3', 'test4', 'test5', 'test6', 'test7']) + self.assertEqual(len(spy), 9) + + log.add('test3') + self.assertEqual(log.favoriteAlgorithmIds(), ['test2', 'test', 'test3', 'test4', 'test5', 'test6', 'test7']) + self.assertEqual(len(spy), 9) + + # test that log has been saved to QgsSettings + log2 = QgsProcessingFavoriteAlgorithmManager() + self.assertEqual(log2.favoriteAlgorithmIds(), ['test2', 'test', 'test3', 'test4', 'test5', 'test6', 'test7']) + + log2.clear() + self.assertEqual(log2.favoriteAlgorithmIds(), []) + + def test_gui_instance(self): + self.assertIsNotNone(QgsGui.instance().processingFavoriteAlgorithmManager()) + + +if __name__ == '__main__': + unittest.main()