File indexing completed on 2025-01-19 03:53:21

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-03-25
0007  * Description : Tree View for album models
0008  *
0009  * SPDX-FileCopyrightText: 2009-2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  * SPDX-FileCopyrightText: 2010-2011 by Andi Clemens <andi dot clemens at gmail dot com>
0011  * SPDX-FileCopyrightText: 2009-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #ifndef DIGIKAM_ABSTRACT_ALBUM_TREE_VIEW_H
0018 #define DIGIKAM_ABSTRACT_ALBUM_TREE_VIEW_H
0019 
0020 // Qt includes
0021 
0022 #include <QTreeView>
0023 
0024 // Local includes
0025 
0026 #include "albummanager.h"
0027 #include "albummodel.h"
0028 #include "albumfiltermodel.h"
0029 #include "albumpointer.h"
0030 #include "statesavingobject.h"
0031 
0032 namespace Digikam
0033 {
0034 
0035 class ContextMenuHelper;
0036 class TagModificationHelper;
0037 
0038 // NOTE: This structure name can be in conflict with QAbstractItemView::State. FIXME later.
0039 struct State;
0040 
0041 /**
0042  * Base class for all tree views that display Album-based content provided by an
0043  * AbstractSpecificAlbumModel. This class enables various utility functions like
0044  * selecting albums on mouse actions or providing an infrastructure for
0045  * displaying a context menu for albums.
0046  *
0047  * Context menu handling is implemented as template methods with hook methods
0048  * that can be implemented by subclasses to provide a custom behavior. In
0049  * default mode no context menu is shown at all. It must be enabled via a call
0050  * to setEnableContextMenu.
0051  */
0052 class AbstractAlbumTreeView : public QTreeView,
0053                               public StateSavingObject
0054 {
0055     Q_OBJECT
0056 
0057 public:
0058 
0059     enum Flag
0060     {
0061         /**
0062          * Create a default model. Not supported by abstract classes.
0063          * Not part of default flags!
0064          */
0065         CreateDefaultModel,
0066 
0067         /**
0068          * Create a default filter model.
0069          */
0070         CreateDefaultFilterModel,
0071 
0072         /**
0073          * Create a delegate which paints according to settings.
0074          * If not set, the Qt default delegate of the view is used.
0075          */
0076         CreateDefaultDelegate,
0077 
0078         /**
0079          * Show the count according to the settings.
0080          * If not set, call setShowCount() on the model yourself.
0081          */
0082         ShowCountAccordingToSettings,
0083 
0084         /**
0085          * Always show the inclusive counts.
0086          * Not part of default flags!
0087          */
0088         AlwaysShowInclusiveCounts,
0089 
0090         DefaultFlags = CreateDefaultFilterModel | CreateDefaultDelegate | ShowCountAccordingToSettings
0091     };
0092     Q_DECLARE_FLAGS(Flags, Flag)
0093 
0094 public:
0095 
0096     /**
0097      * Constructs an album tree view.
0098      * If you give 0 for model, call setAlbumModel afterwards.
0099      * If you supply 0 for filterModel, call setAlbumFilterModel afterwards.
0100      */
0101     AbstractAlbumTreeView(QWidget* const parent, Flags flags);
0102     ~AbstractAlbumTreeView()                                                    override;
0103 
0104     AbstractSpecificAlbumModel* albumModel() const;
0105     AlbumFilterModel* albumFilterModel()     const;
0106 
0107     /**
0108      * Enable expanding of tree items on single click on the item (default: off)
0109      */
0110     void setExpandOnSingleClick(const bool doThat);
0111 
0112     /**
0113      * Expand an item when making it the new current item
0114      */
0115     void setExpandNewCurrentItem(const bool doThat);
0116 
0117     /**
0118      * Sets whether to select an album on click via the album manager or not.
0119      *
0120      * @param selectOnClick if true, a click on an item automatically sets this
0121      *                      item as the current album in the album manager
0122      */
0123     void setSelectAlbumOnClick(const bool selectOnClick);
0124 
0125     /**
0126      * Determines the global decision to show a popup menu or not. More detailed
0127      * decision at which position a menu can be shown and where not can be made
0128      * by implementing showContextMenuAt.
0129      *
0130      * @param enable if true, a context menu can be shown
0131      */
0132     void setEnableContextMenu(const bool enable);
0133 
0134     /**
0135      * Sets whether to select the album under the mouse cursor on a context menu
0136      * request (so that the album is shown using the album manager) or not
0137      *
0138      * Defaults to true.
0139      *
0140      * @param select true if a context menu request shall select the album
0141      */
0142     void setSelectOnContextMenu(const bool select);
0143 
0144     /**
0145      * Set the context menu title and icon.
0146      * This is used by the default implementation of contextMenuIcon()
0147      * and contextMenuTitle(). You can alternatively reimplement these methods.
0148      */
0149     void setContextMenuIcon(const QPixmap& pixmap);
0150     void setContextMenuTitle(const QString& title);
0151 
0152     /**
0153      * This is a combination of indexAt() checked with visualRect().
0154      * p must be in the viewport currently. Decoration will not be included.
0155      * Suitable for mouse click positions.
0156      */
0157     QModelIndex indexVisuallyAt(const QPoint& p);
0158 
0159     /**
0160      * Ensures that every current match is visible by expanding all parent
0161      * entries.
0162      *
0163      * @param index the index to start ensuring expansion state
0164      * @return <code>true</code> if there was a match under <code>index</code>.
0165      *         This return value can normally be ignored by the caller because
0166      *         it is only used for an internal recursion.
0167      */
0168     bool expandMatches(const QModelIndex& index);
0169 
0170     //@{
0171 
0172     /**
0173      * Implements state loading for the album tree view in a somewhat clumsy
0174      * procedure because the model may not be fully loaded when this method is
0175      * called. Therefore the config is first parsed into d->statesByAlbumId
0176      * which holds the state of all tree view entries indexed by album id.
0177      * Afterwards an initial sync run is done restoring the state of all model
0178      * entries that are already present at this time. Every processed entry
0179      * is removed from d->statesByAlbumId. If there are still entries left in
0180      * this map we assume that the model is not fully loaded at the moment.
0181      * Therefore the rowsInserted signal is connected to a slot that restores
0182      * the state of new rows based on the remaining entries in
0183      * d->statesByAlbumId.
0184      */
0185     void doLoadState()                                                          override;
0186     void doSaveState()                                                          override;
0187 
0188     //@}
0189 
0190     /**
0191      * Some treeviews shall control the global current album kept by AlbumManager.
0192      * Other treeview are self-contained and shall not change the current album.
0193      * Default: false
0194      */
0195     void setAlbumManagerCurrentAlbum(const bool setCurrentAlbum);
0196 
0197     /**
0198      * Add a context menu element which can add actions to the context
0199      * menu when the menu is generated.
0200      * First, addCustomContextMenuActions is called, then
0201      * all elements' addActions method is called in order of addition.
0202      */
0203     class ContextMenuElement
0204     {
0205     public:
0206 
0207         ContextMenuElement()                    = default;
0208         virtual ~ContextMenuElement()           = default;
0209 
0210         /**
0211          * Add actions to the context menu being generated
0212          *
0213          * @param view The AbstractAlbumTreeView which generates the menu
0214          * @param cmh helper object to create the context menu
0215          * @param album the album on which the context menu will be created. May be null if
0216          *              it is requested on no tag entry
0217          */
0218         virtual void addActions(AbstractAlbumTreeView* view,
0219                                 ContextMenuHelper& cmh,
0220                                 Album* album)   = 0;
0221 
0222     private:
0223 
0224         Q_DISABLE_COPY(ContextMenuElement)
0225     };
0226 
0227     void addContextMenuElement(ContextMenuElement* const element);
0228     void removeContextMenuElement(ContextMenuElement* const element);
0229     QList<ContextMenuElement*> contextMenuElements() const;
0230 
0231     template<class A>
0232     QList<A*> currentAlbums();
0233 
0234     /**
0235      * For internal use only.
0236      */
0237     bool viewportEvent(QEvent* event)                                           override;
0238 
0239     /**
0240      * @brief selectedItems() -
0241      */
0242     QList<Album*> selectedItems();
0243 
0244 public Q_SLOTS:
0245 
0246     void setSearchTextSettings(const SearchTextSettings& settings);
0247 
0248     /**
0249      * Selects the given album.
0250      *
0251      * @param albums the albums to select
0252      * @param selectInAlbumManager the album will be set as current album, if both
0253      * this parameter is true and setAlbumManagerCurrentAlbum() was set to true.
0254      */
0255     void setCurrentAlbums(const QList<Album*>& albums, bool selectInAlbumManager = true);
0256 
0257     /**
0258      * Adapt the column sizes to the contents of the tree view.
0259      */
0260     void adaptColumnsToContent();
0261 
0262     /**
0263      * Scrolls to the first selected album if there is one.
0264      */
0265     void scrollToSelectedAlbum();
0266 
0267     /**
0268      * Expands the complete tree under the given index.
0269      *
0270      * @param index the index to start expanding everything
0271      */
0272     void expandEverything(const QModelIndex& index);
0273 
0274     /**
0275      * @brief slotExpandNode - expands recursively selected nodes
0276      */
0277     void slotExpandNode();
0278 
0279     /**
0280      * @brief slotCollapseNode - collapse recursively selected nodes
0281      */
0282     void slotCollapseNode();
0283 
0284     /**
0285      * @brief slotCollapseAllNodes - collapse all nodes without root node
0286      */
0287     void slotCollapseAllNodes();
0288 
0289 Q_SIGNALS:
0290 
0291     /**
0292      * Emitted when the currently selected album changes
0293      */
0294     void currentAlbumChanged(Album* currentAlbum);
0295 
0296     /**
0297      * Emitted when the current selection changes. Use currentChanged unless in multi-selection mode.
0298      */
0299     void selectedAlbumsChanged(const QList<Album*>& selectedAlbums);
0300 
0301 protected Q_SLOTS:
0302 
0303     /**
0304      * override if implemented behavior is not as intended
0305      */
0306     virtual void slotRootAlbumAvailable();
0307 
0308     void slotSearchTextSettingsChanged(bool wasSearching, bool searching);
0309     void slotSearchTextSettingsAboutToChange(bool searched, bool willSearch);
0310     void slotCurrentChanged();
0311     void slotSelectionChanged();
0312 
0313     void albumSettingsChanged();
0314 
0315 protected:
0316 
0317     //@{
0318 
0319     // Context menu handling
0320 
0321     /**
0322      * Hook method to implement that determines if a context menu shall be
0323      * displayed for the given event at the position coded in the event.
0324      *
0325      * @param event context menu event to react on
0326      * @param albumForEvent the album at the mouse position or null if there is
0327      *                      no album at that position
0328      * @return true if a context menu shall be displayed at the event
0329      *         coordinates, else false
0330      */
0331     virtual bool showContextMenuAt(QContextMenuEvent* event, Album* albumForEvent);
0332 
0333     /**
0334      * Hook method that can be implemented to return a special icon used for the
0335      * context menu.
0336      *
0337      * @return the icon for the context menu
0338      */
0339     virtual QPixmap contextMenuIcon()  const;
0340 
0341     /**
0342      * Hook method to implement that returns the title for the context menu.
0343      *
0344      * @return title for the context menu
0345      */
0346     virtual QString contextMenuTitle() const;
0347 
0348     /**
0349      * Hook method to add custom actions to the generated context menu.
0350      *
0351      * @param cmh helper object to create the context menu
0352      * @param album tag on which the context menu will be created. May be null if
0353      *              it is requested on no tag entry
0354      */
0355     virtual void addCustomContextMenuActions(ContextMenuHelper& cmh, Album* album);
0356 
0357     /**
0358      * Hook method to handle the custom context menu actions that were added
0359      * with addCustomContextMenuActions.
0360      *
0361      * @param action the action that was chosen by the user, may be null if none
0362      *               of the custom actions were selected
0363      * @param album the tag on which the context menu was requested. May be null
0364      *              if there was no
0365      */
0366     virtual void handleCustomContextMenuAction(QAction* action,
0367                                                const AlbumPointer<Album>& album);
0368 
0369     //@}
0370 
0371     //@{
0372 
0373     /// Other helper methods.
0374 
0375     void mousePressEvent(QMouseEvent* e)                                        override;
0376 
0377     void rowsInserted(const QModelIndex& index, int start, int end)             override;
0378     void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)    override;
0379     void startDrag(Qt::DropActions supportedActions)                            override;
0380     void dragEnterEvent(QDragEnterEvent* e)                                     override;
0381     void dragMoveEvent(QDragMoveEvent* e)                                       override;
0382     void dragLeaveEvent(QDragLeaveEvent* e)                                     override;
0383     void dropEvent(QDropEvent* e)                                               override;
0384 
0385     virtual void middleButtonPressed(Album* a);
0386     virtual QPixmap pixmapForDrag(const QStyleOptionViewItem& option, QList<QModelIndex> indexes);
0387 
0388     void setAlbumFilterModel(AlbumFilterModel* const filterModel);
0389     void setAlbumModel(AbstractSpecificAlbumModel* const model);
0390 
0391     //@}
0392 
0393 protected:
0394 
0395     AbstractSpecificAlbumModel* m_albumModel;
0396     AlbumFilterModel*           m_albumFilterModel;
0397     AlbumModelDragDropHandler*  m_dragDropHandler;
0398 
0399     int                         m_lastScrollBarValue;
0400     bool                        m_checkOnMiddleClick;
0401     bool                        m_restoreCheckState;
0402     Flags                       m_flags;
0403 
0404 private:
0405 
0406     void saveStateRecursive(const QModelIndex& index,
0407                             QList<int>& selection, QList<int>& expansion);
0408 
0409     /**
0410      * Restores the state of the index and all sub-indexes if there is an entry
0411      * for this index in stateStore. Every album that is restored is removed
0412      * from the stateStore.
0413      *
0414      * @param index the index to start restoring
0415      * @param stateStore states indexed by album id
0416      */
0417     void restoreStateForHierarchy(const QModelIndex& index,
0418                                   const QMap<int, Digikam::State>& stateStore);
0419 
0420     /**
0421      * Restore the state for this index.
0422      */
0423     void restoreState(const QModelIndex& index,
0424                       const QMap<int, Digikam::State>& stateStore);
0425 
0426     /**
0427      * Creates the context menu.
0428      *
0429      * @param event the event that requested the menu
0430      */
0431     void contextMenuEvent(QContextMenuEvent* event)                             override;
0432 
0433 private Q_SLOTS:
0434 
0435     /**
0436      * Adapts the columns in between the given model indices to the content
0437      * size. This can be connected to dataChanged.
0438      *
0439      * @param topLeft top left index of changed data
0440      * @param bottomRight index of changed data
0441      */
0442     void adaptColumnsOnDataChange(const QModelIndex& topLeft, const QModelIndex& bottomRight);
0443 
0444     /**
0445      * Adapt the column sizes to new contents. This can be connected to all
0446      * signals indicating row changes.
0447      *
0448      * @param parent the parent index of changed rows
0449      * @param start the start row changed under the parent
0450      * @param end the end row changed under the parent
0451      */
0452     void adaptColumnsOnRowChange(const QModelIndex& parent, int start, int end);
0453 
0454     /**
0455      * Adapts the column sizes if the layout changes.
0456      */
0457     void adaptColumnsOnLayoutChange();
0458 
0459     /**
0460      * This slot is used to ensure that after searching for entries the correct
0461      * album is selected again. Therefore it tracks new selections.
0462      */
0463     void currentAlbumChangedForBackupSelection(Album* currentAlbum);
0464 
0465     /**
0466      * This slots is used to fix bug 400960.
0467      */
0468     void slotScrollBarValueChanged(int value);
0469     void slotScrollBarActionTriggered(int action);
0470 
0471 private:
0472 
0473     class Private;
0474     Private* d;
0475 };
0476 
0477 } // namespace Digikam
0478 
0479 Q_DECLARE_OPERATORS_FOR_FLAGS(Digikam::AbstractAlbumTreeView::Flags)
0480 
0481 #endif // DIGIKAM_ABSTRACT_ALBUM_TREE_VIEW_H