1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2009-03-25
7  * Description : Tree View for album models
8  *
9  * Copyright (C) 2009-2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
10  * Copyright (C) 2010-2011 by Andi Clemens <andi dot clemens at gmail dot com>
11  * Copyright (C) 2009-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
12  *
13  * This program is free software; you can redistribute it
14  * and/or modify it under the terms of the GNU General
15  * Public License as published by the Free Software Foundation;
16  * either version 2, or (at your option)
17  * any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * ============================================================ */
25 
26 #ifndef DIGIKAM_ABSTRACT_ALBUM_TREE_VIEW_H
27 #define DIGIKAM_ABSTRACT_ALBUM_TREE_VIEW_H
28 
29 // Qt includes
30 
31 #include <QTreeView>
32 
33 // Local includes
34 
35 #include "albummanager.h"
36 #include "albummodel.h"
37 #include "albumfiltermodel.h"
38 #include "albumpointer.h"
39 #include "statesavingobject.h"
40 
41 namespace Digikam
42 {
43 
44 class ContextMenuHelper;
45 class TagModificationHelper;
46 
47 // NOTE: This structure name can be in conflict with QAbstractItemView::State.
48 struct State;
49 
50 class AbstractAlbumTreeView;
51 
52 /**
53  * Base class for all tree views that display Album-based content provided by an
54  * AbstractSpecificAlbumModel. This class enables various utility functions like
55  * selecting albums on mouse actions or providing an infrastructure for
56  * displaying a context menu for albums.
57  *
58  * Context menu handling is implemented as template methods with hook methods
59  * that can be implemented by subclasses to provide a custom behavior. In
60  * default mode no context menu is shown at all. It must be enabled via a call
61  * to setEnableContextMenu.
62  */
63 class AbstractAlbumTreeView : public QTreeView,
64                               public StateSavingObject
65 {
66     Q_OBJECT
67 
68 public:
69 
70     enum Flag
71     {
72         /**
73          * Create a default model. Not supported by abstract classes.
74          * Not part of default flags!
75          */
76         CreateDefaultModel,
77 
78         /**
79          * Create a default filter model.
80          */
81         CreateDefaultFilterModel,
82 
83         /**
84          * Create a delegate which paints according to settings.
85          * If not set, the Qt default delegate of the view is used.
86          */
87         CreateDefaultDelegate,
88 
89         /**
90          * Show the count according to the settings.
91          * If not set, call setShowCount() on the model yourself.
92          */
93         ShowCountAccordingToSettings,
94 
95         /**
96          * Always show the inclusive counts.
97          * Not part of default flags!
98          */
99         AlwaysShowInclusiveCounts,
100 
101         DefaultFlags = CreateDefaultFilterModel | CreateDefaultDelegate | ShowCountAccordingToSettings
102     };
103     Q_DECLARE_FLAGS(Flags, Flag)
104 
105 public:
106 
107     /**
108      * Constructs an album tree view.
109      * If you give 0 for model, call setAlbumModel afterwards.
110      * If you supply 0 for filterModel, call setAlbumFilterModel afterwards.
111      */
112     AbstractAlbumTreeView(QWidget* const parent, Flags flags);
113     ~AbstractAlbumTreeView()                                                    override;
114 
115     AbstractSpecificAlbumModel* albumModel() const;
116     AlbumFilterModel* albumFilterModel()     const;
117 
118     /// Enable expanding of tree items on single click on the item (default: off)
119     void setExpandOnSingleClick(const bool doThat);
120 
121     /// Expand an item when making it the new current item
122     void setExpandNewCurrentItem(const bool doThat);
123 
124     /**
125      * Sets whether to select an album on click via the album manager or not.
126      *
127      * @param selectOnClick if true, a click on an item automatically sets this
128      *                      item as the current album in the album manager
129      */
130     void setSelectAlbumOnClick(const bool selectOnClick);
131 
132     /**
133      * Determines the global decision to show a popup menu or not. More detailed
134      * decision at which position a menu can be shown and where not can be made
135      * by implementing showContextMenuAt.
136      *
137      * @param enable if true, a context menu can be shown
138      */
139     void setEnableContextMenu(const bool enable);
140 
141     /**
142      * Sets whether to select the album under the mouse cursor on a context menu
143      * request (so that the album is shown using the album manager) or not
144      *
145      * Defaults to true.
146      *
147      * @param select true if a context menu request shall select the album
148      */
149     void setSelectOnContextMenu(const bool select);
150 
151     /**
152      * Set the context menu title and icon.
153      * This is used by the default implementation of contextMenuIcon()
154      * and contextMenuTitle(). You can alternatively reimplement these methods.
155      */
156     void setContextMenuIcon(const QPixmap& pixmap);
157     void setContextMenuTitle(const QString& title);
158 
159     /**
160      * This is a combination of indexAt() checked with visualRect().
161      * p must be in the viewport currently. Decoration will not be included.
162      * Suitable for mouse click positions.
163      */
164     QModelIndex indexVisuallyAt(const QPoint& p);
165 
166     /**
167      * Ensures that every current match is visible by expanding all parent
168      * entries.
169      *
170      * @param index the index to start ensuring expansion state
171      * @return <code>true</code> if there was a match under <code>index</code>.
172      *         This return value can normally be ignored by the caller because
173      *         it is only used for an internal recursion.
174      */
175     bool expandMatches(const QModelIndex& index);
176 
177     //@{
178     /**
179      * Implements state loading for the album tree view in a somewhat clumsy
180      * procedure because the model may not be fully loaded when this method is
181      * called. Therefore the config is first parsed into d->statesByAlbumId
182      * which holds the state of all tree view entries indexed by album id.
183      * Afterwards an initial sync run is done restoring the state of all model
184      * entries that are already present at this time. Every processed entry
185      * is removed from d->statesByAlbumId. If there are still entries left in
186      * this map we assume that the model is not fully loaded at the moment.
187      * Therefore the rowsInserted signal is connected to a slot that restores
188      * the state of new rows based on the remaining entries in
189      * d->statesByAlbumId.
190      */
191     void doLoadState()                                                          override;
192     void doSaveState()                                                          override;
193     //@}
194 
195     /**
196      * Some treeviews shall control the global current album kept by AlbumManager.
197      * Other treeview are self-contained and shall not change the current album.
198      * Default: false
199      */
200     void setAlbumManagerCurrentAlbum(const bool setCurrentAlbum);
201 
202     /**
203      * Add a context menu element which can add actions to the context
204      * menu when the menu is generated.
205      * First, addCustomContextMenuActions is called, then
206      * all elements' addActions method is called in order of addition.
207      */
208     class ContextMenuElement
209     {
210     public:
211 
212         ContextMenuElement()                    = default;
213         virtual ~ContextMenuElement()           = default;
214 
215         /**
216          * Add actions to the context menu being generated
217          *
218          * @param view The AbstractAlbumTreeView which generates the menu
219          * @param cmh helper object to create the context menu
220          * @param album the album on which the context menu will be created. May be null if
221          *              it is requested on no tag entry
222          */
223         virtual void addActions(AbstractAlbumTreeView* view,
224                                 ContextMenuHelper& cmh,
225                                 Album* album)   = 0;
226 
227     private:
228 
229         Q_DISABLE_COPY(ContextMenuElement)
230     };
231 
232     void addContextMenuElement(ContextMenuElement* const element);
233     void removeContextMenuElement(ContextMenuElement* const element);
234     QList<ContextMenuElement*> contextMenuElements() const;
235 
236     template<class A>
237     QList<A*> currentAlbums();
238 
239     // for internal use: public viewportEvent
240     bool viewportEvent(QEvent* event)                                           override;
241 
242     /**
243      * @brief selectedItems() -
244      */
245     QList<Album*> selectedItems();
246 
247 public Q_SLOTS:
248 
249     void setSearchTextSettings(const SearchTextSettings& settings);
250 
251     /**
252      * Selects the given album.
253      *
254      * @param albums the albums to select
255      * @param selectInAlbumManager the album will be set as current album, if both
256      * this parameter is true and setAlbumManagerCurrentAlbum() was set to true.
257      */
258     void setCurrentAlbums(const QList<Album*>& albums, bool selectInAlbumManager = true);
259 
260     /**
261      * Adapt the column sizes to the contents of the tree view.
262      */
263     void adaptColumnsToContent();
264 
265     /**
266      * Scrolls to the first selected album if there is one.
267      */
268     void scrollToSelectedAlbum();
269 
270     /**
271      * Expands the complete tree under the given index.
272      *
273      * @param index the index to start expanding everything
274      */
275     void expandEverything(const QModelIndex& index);
276 
277     /**
278      * @brief slotExpandNode - expands recursively selected nodes
279      */
280     void slotExpandNode();
281 
282     /**
283      * @brief slotCollapseNode - collapse recursively selected nodes
284      */
285     void slotCollapseNode();
286 
287 Q_SIGNALS:
288 
289     /// Emitted when the currently selected album changes
290     void currentAlbumChanged(Album* currentAlbum);
291 
292     /// Emitted when the current selection changes. Use currentChanged unless in multi-selection mode.
293     void selectedAlbumsChanged(const QList<Album*>& selectedAlbums);
294 
295 protected Q_SLOTS:
296 
297     // override if implemented behavior is not as intended
298     virtual void slotRootAlbumAvailable();
299 
300     void slotSearchTextSettingsChanged(bool wasSearching, bool searching);
301     void slotSearchTextSettingsAboutToChange(bool searched, bool willSearch);
302     void slotCurrentChanged();
303     void slotSelectionChanged();
304 
305     void albumSettingsChanged();
306 
307 protected:
308 
309     // context menu handling
310 
311     /**
312      * Hook method to implement that determines if a context menu shall be
313      * displayed for the given event at the position coded in the event.
314      *
315      * @param event context menu event to react on
316      * @param albumForEvent the album at the mouse position or null if there is
317      *                      no album at that position
318      * @return true if a context menu shall be displayed at the event
319      *         coordinates, else false
320      */
321     virtual bool showContextMenuAt(QContextMenuEvent* event, Album* albumForEvent);
322 
323     /**
324      * Hook method that can be implemented to return a special icon used for the
325      * context menu.
326      *
327      * @return the icon for the context menu
328      */
329     virtual QPixmap contextMenuIcon()  const;
330 
331     /**
332      * Hook method to implement that returns the title for the context menu.
333      *
334      * @return title for the context menu
335      */
336     virtual QString contextMenuTitle() const;
337 
338     /**
339      * Hook method to add custom actions to the generated context menu.
340      *
341      * @param cmh helper object to create the context menu
342      * @param album tag on which the context menu will be created. May be null if
343      *              it is requested on no tag entry
344      */
345     virtual void addCustomContextMenuActions(ContextMenuHelper& cmh, Album* album);
346 
347     /**
348      * Hook method to handle the custom context menu actions that were added
349      * with addCustomContextMenuActions.
350      *
351      * @param action the action that was chosen by the user, may be null if none
352      *               of the custom actions were selected
353      * @param album the tag on which the context menu was requested. May be null
354      *              if there was no
355      */
356     virtual void handleCustomContextMenuAction(QAction* action,
357                                                const AlbumPointer<Album>& album);
358 
359     // other stuff
360 
361     void mousePressEvent(QMouseEvent* e)                                        override;
362 
363     void rowsInserted(const QModelIndex& index, int start, int end)             override;
364     void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)    override;
365     void startDrag(Qt::DropActions supportedActions)                            override;
366     void dragEnterEvent(QDragEnterEvent* e)                                     override;
367     void dragMoveEvent(QDragMoveEvent* e)                                       override;
368     void dragLeaveEvent(QDragLeaveEvent* e)                                     override;
369     void dropEvent(QDropEvent* e)                                               override;
370 
371     virtual void middleButtonPressed(Album* a);
372     virtual QPixmap pixmapForDrag(const QStyleOptionViewItem& option, QList<QModelIndex> indexes);
373 
374     void setAlbumFilterModel(AlbumFilterModel* const filterModel);
375     void setAlbumModel(AbstractSpecificAlbumModel* const model);
376 
377 protected:
378 
379     AbstractSpecificAlbumModel* m_albumModel;
380     AlbumFilterModel*           m_albumFilterModel;
381     AlbumModelDragDropHandler*  m_dragDropHandler;
382 
383     int                         m_lastScrollBarValue;
384     bool                        m_checkOnMiddleClick;
385     bool                        m_restoreCheckState;
386     Flags                       m_flags;
387 
388 private:
389 
390     void saveStateRecursive(const QModelIndex& index,
391                             QList<int>& selection, QList<int>& expansion);
392 
393     /**
394      * Restores the state of the index and all sub-indexes if there is an entry
395      * for this index in stateStore. Every album that is restored is removed
396      * from the stateStore.
397      *
398      * @param index the index to start restoring
399      * @param stateStore states indexed by album id
400      */
401     void restoreStateForHierarchy(const QModelIndex& index,
402                                   const QMap<int, Digikam::State>& stateStore);
403 
404     /**
405      * Restore the state for this index.
406      */
407     void restoreState(const QModelIndex& index,
408                       const QMap<int, Digikam::State>& stateStore);
409 
410     /**
411      * Creates the context menu.
412      *
413      * @param event the event that requested the menu
414      */
415     void contextMenuEvent(QContextMenuEvent* event)                             override;
416 
417 private Q_SLOTS:
418 
419     /**
420      * Adapts the columns in between the given model indices to the content
421      * size. This can be connected to dataChanged.
422      *
423      * @param topLeft top left index of changed data
424      * @param bottomRight index of changed data
425      */
426     void adaptColumnsOnDataChange(const QModelIndex& topLeft, const QModelIndex& bottomRight);
427 
428     /**
429      * Adapt the column sizes to new contents. This can be connected to all
430      * signals indicating row changes.
431      *
432      * @param parent the parent index of changed rows
433      * @param start the start row changed under the parent
434      * @param end the end row changed under the parent
435      */
436     void adaptColumnsOnRowChange(const QModelIndex& parent, int start, int end);
437 
438     /**
439      * Adapts the column sizes if the layout changes.
440      */
441     void adaptColumnsOnLayoutChange();
442 
443     /**
444      * This slot is used to ensure that after searching for entries the correct
445      * album is selected again. Therefore it tracks new selections.
446      */
447     void currentAlbumChangedForBackupSelection(Album* currentAlbum);
448 
449     /**
450      * This slots is used to fix bug 400960.
451      */
452     void slotScrollBarValueChanged(int value);
453     void slotScrollBarActionTriggered(int action);
454 
455 private:
456 
457     class Private;
458     Private* d;
459 };
460 
461 } // namespace Digikam
462 
463 Q_DECLARE_OPERATORS_FOR_FLAGS(Digikam::AbstractAlbumTreeView::Flags)
464 
465 #endif // DIGIKAM_ABSTRACT_ALBUM_TREE_VIEW_H
466