1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2009-03-05
7  * Description : Qt item model for database entries
8  *
9  * Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
10  * Copyright (C) 2012-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
11  *
12  * This program is free software; you can redistribute it
13  * and/or modify it under the terms of the GNU General
14  * Public License as published by the Free Software Foundation;
15  * either version 2, or (at your option)
16  * any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * ============================================================ */
24 
25 #ifndef DIGIKAM_ITEM_MODEL_H
26 #define DIGIKAM_ITEM_MODEL_H
27 
28 // Qt includes
29 
30 #include <QAbstractListModel>
31 
32 // Local includes
33 
34 #include "dragdropimplementations.h"
35 #include "iteminfo.h"
36 #include "digikam_export.h"
37 
38 class QItemSelection;
39 
40 namespace Digikam
41 {
42 
43 class ImageChangeset;
44 class ImageTagChangeset;
45 
46 namespace DatabaseFields
47 {
48 class Set;
49 }
50 
51 class DIGIKAM_DATABASE_EXPORT ItemModel : public QAbstractListModel,
52                                           public DragDropModelImplementation
53 {
54     Q_OBJECT
55 
56 public:
57 
58     enum ItemModelRoles
59     {
60         /**
61          * An ItemModel* pointer to this model
62          */
63         ItemModelPointerRole    = Qt::UserRole,
64         ItemModelInternalId     = Qt::UserRole + 1,
65 
66         /**
67          * Returns a thumbnail pixmap. May be implemented by subclasses.
68          * Returns either a valid pixmap or a null QVariant.
69          */
70         ThumbnailRole           = Qt::UserRole + 2,
71 
72         /**
73          * Returns a QDateTime with the creation date
74          */
75         CreationDateRole        = Qt::UserRole + 3,
76 
77         /**
78          * Return (optional) extraData field
79          */
80         ExtraDataRole           = Qt::UserRole + 5,
81 
82         /**
83          * Returns the number of duplicate indexes for the same image id
84          */
85         ExtraDataDuplicateCount = Qt::UserRole + 6,
86 
87         /**
88          * Roles which are defined here but not implemented by ItemModel
89          * Returns position of item in Left Light Table preview.
90          */
91         LTLeftPanelRole         = Qt::UserRole + 50,
92 
93         /**
94          * Returns position of item in Right Light Table preview.
95          */
96         LTRightPanelRole        = Qt::UserRole + 51,
97 
98         /**
99          * For use by subclasses
100          */
101         SubclassRoles           = Qt::UserRole + 100,
102 
103         /**
104          * For use by filter models
105          */
106         FilterModelRoles        = Qt::UserRole + 500
107     };
108 
109 public:
110 
111     explicit ItemModel(QObject* const parent = nullptr);
112     ~ItemModel() override;
113 
114     /**
115      * If a cache is kept, lookup by file path is fast,
116      * without a cache it is O(n). Default is false.
117      */
118     void setKeepsFilePathCache(bool keepCache);
119     bool keepsFilePathCache()                                                                               const;
120 
121     /**
122      * Set a set of database fields to watch.
123      * If either of these is changed, dataChanged() will be emitted.
124      * Default is no flag (no signal will be emitted).
125      */
126     void setWatchFlags(const DatabaseFields::Set& set);
127 
128     /**
129      * Returns the ItemInfo object, reference or image id from the underlying data
130      * pointed to by the index.
131      * If the index is not valid, imageInfo will return a null ItemInfo, imageId will
132      * return 0, imageInfoRef must not be called with an invalid index.
133      */
134     ItemInfo         imageInfo(const QModelIndex& index)                                                    const;
135     ItemInfo&        imageInfoRef(const QModelIndex& index)                                                 const;
136     qlonglong        imageId(const QModelIndex& index)                                                      const;
137     QList<ItemInfo>  imageInfos(const QList<QModelIndex>& indexes)                                          const;
138     QList<qlonglong> imageIds(const QList<QModelIndex>& indexes)                                            const;
139 
140     /**
141      * Returns the ItemInfo object, reference or image id from the underlying data
142      * of the given row (parent is the invalid QModelIndex, column is 0).
143      * Note that imageInfoRef will crash if index is invalid.
144      */
145     ItemInfo   imageInfo(int row)                                                                           const;
146     ItemInfo&  imageInfoRef(int row)                                                                        const;
147     qlonglong  imageId(int row)                                                                             const;
148 
149     /**
150      * Return the index for the given ItemInfo or id, if contained in this model.
151      */
152     QModelIndex        indexForItemInfo(const ItemInfo& info)                                               const;
153     QModelIndex        indexForItemInfo(const ItemInfo& info, const QVariant& extraValue)                   const;
154     QModelIndex        indexForImageId(qlonglong id)                                                        const;
155     QModelIndex        indexForImageId(qlonglong id, const QVariant& extraValue)                            const;
156     QList<QModelIndex> indexesForItemInfo(const ItemInfo& info)                                             const;
157     QList<QModelIndex> indexesForImageId(qlonglong id)                                                      const;
158 
159     int numberOfIndexesForItemInfo(const ItemInfo& info)                                                    const;
160     int numberOfIndexesForImageId(qlonglong id)                                                             const;
161 
162     /**
163      * Returns the index or ItemInfo object from the underlying data
164      * for the given file path. This is fast if keepsFilePathCache is enabled.
165      * The file path is as returned by ItemInfo.filePath().
166      * In case of multiple occurrences of the same file, the simpler variants return
167      * any one found first, use the QList methods to retrieve all occurrences.
168      */
169     QModelIndex        indexForPath(const QString& filePath)                                                const;
170     ItemInfo           imageInfo(const QString& filePath)                                                   const;
171     QList<QModelIndex> indexesForPath(const QString& filePath)                                              const;
172     QList<ItemInfo>    imageInfos(const QString& filePath)                                                  const;
173 
174     /**
175      * Main entry point for subclasses adding image infos to the model.
176      * If you list entries not unique per image id, you must add an extraValue
177      * so that every entry is unique by imageId and extraValues.
178      * Please note that these methods do not prevent addition of duplicate entries.
179      */
180     void addItemInfo(const ItemInfo& info);
181     void addItemInfos(const QList<ItemInfo>& infos);
182     void addItemInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues);
183 
184     /**
185      * Clears image infos and resets model.
186      */
187     void clearItemInfos();
188 
189     /**
190      * Clears and adds the infos.
191      */
192     void setItemInfos(const QList<ItemInfo>& infos);
193 
194     /**
195      * Directly remove the given indexes or infos from the model.
196      */
197     void removeIndex(const QModelIndex& indexes);
198     void removeIndexes(const QList<QModelIndex>& indexes);
199     void removeItemInfo(const ItemInfo& info);
200     void removeItemInfos(const QList<ItemInfo>& infos);
201     void removeItemInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues);
202 
203     /**
204      * addItemInfo() is asynchronous if a prepocessor is set.
205      * This method first adds the info, synchronously.
206      * Only afterwards, the preprocessor will have the opportunity to process it.
207      * This method also bypasses any incremental updates.
208      * Please note that these methods do not prevent addition of duplicate entries.
209      */
210     void addItemInfoSynchronously(const ItemInfo& info);
211     void addItemInfosSynchronously(const QList<ItemInfo>& infos);
212     void addItemInfosSynchronously(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues);
213 
214     /**
215      * Add the given entries. Method returns immediately, the
216      * addition may happen later asynchronously.
217      * These methods prevent the addition of duplicate entries.
218      */
219     void ensureHasItemInfo(const ItemInfo& info);
220     void ensureHasItemInfos(const QList<ItemInfo>& infos);
221     void ensureHasItemInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues);
222 
223     /**
224      * Ensure that all images grouped on the given leader are contained in the model.
225      */
226     void ensureHasGroupedImages(const ItemInfo& groupLeader);
227 
228     QList<ItemInfo>  imageInfos()                                                                           const;
229     QList<qlonglong> imageIds()                                                                             const;
230     QList<ItemInfo>  uniqueItemInfos()                                                                      const;
231 
232     bool hasImage(qlonglong id)                                                                             const;
233     bool hasImage(const ItemInfo& info)                                                                     const;
234     bool hasImage(const ItemInfo& info, const QVariant& extraValue)                                         const;
235     bool hasImage(qlonglong id, const QVariant& extraValue)                                                 const;
236 
237     bool isEmpty()                                                                                          const;
238     int  itemCount()                                                                                        const;
239 
240     // Drag and Drop
241     DECLARE_MODEL_DRAG_DROP_METHODS
242 
243     /**
244      * Install an object as a preprocessor for ItemInfos added to this model.
245      * For every QList of ItemInfos added to addItemInfo, the signal preprocess()
246      * will be emitted. The preprocessor may process the items and shall then readd
247      * them by calling reAddItemInfos(). It may take some time to process.
248      * It shall discard any held infos when the modelReset() signal is sent.
249      * It shall call readdFinished() when no reset occurred and all infos on the way have been readded.
250      * This means that only after calling this method, you shall make three connections
251      * (preprocess -> your slot, your signal -> reAddItemInfos, your signal -> reAddingFinished)
252      * and make or already hold a connection modelReset() -> your slot.
253      * There is only one preprocessor at a time, a previously set object will be disconnected.
254      */
255     void setPreprocessor(QObject* const processor);
256     void unsetPreprocessor(QObject* const processor);
257 
258     /**
259      * Returns true if this model is currently refreshing.
260      * For a preprocessor this means that, although the preprocessor may currently have
261      * processed all it got, more batches are to be expected.
262      */
263     bool isRefreshing()                                                                                     const;
264 
265     /**
266      * Enable sending of imageInfosAboutToBeRemoved and imageInfosRemoved signals.
267      * Default: false
268      */
269     void setSendRemovalSignals(bool send);
270 
271     QVariant      data(const QModelIndex& index, int role = Qt::DisplayRole)                        const override;
272     QVariant      headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole)  const override;
273     int           rowCount(const QModelIndex& parent = QModelIndex())                               const override;
274     Qt::ItemFlags flags(const QModelIndex& index)                                                   const override;
275     QModelIndex   index(int row, int column = 0, const QModelIndex& parent = QModelIndex())         const override;
276 
277     /**
278      * Retrieves the imageInfo object from the data() method of the given index.
279      * The index may be from a QSortFilterProxyModel as long as an ItemModel is at the end.
280      */
281     static ItemInfo retrieveItemInfo(const QModelIndex& index);
282     static qlonglong retrieveImageId(const QModelIndex& index);
283 
284 Q_SIGNALS:
285 
286     /**
287      * Informs that ItemInfos will be added to the model.
288      * This signal is sent before the model data is changed and views are informed.
289      */
290     void imageInfosAboutToBeAdded(const QList<ItemInfo>& infos);
291 
292     /**
293      * Informs that ItemInfos have been added to the model.
294      * This signal is sent after the model data is changed and views are informed.
295      */
296     void imageInfosAdded(const QList<ItemInfo>& infos);
297 
298     /**
299      * Informs that ItemInfos will be removed from the model.
300      * This signal is sent before the model data is changed and views are informed.
301      * Note: You need to explicitly enable sending of this signal. It is not sent
302      * in clearItemInfos().
303      */
304     void imageInfosAboutToBeRemoved(const QList<ItemInfo>& infos);
305 
306     /**
307      * Informs that ItemInfos have been removed from the model.
308      * This signal is sent after the model data is changed and views are informed. *
309      * Note: You need to explicitly enable sending of this signal. It is not sent
310      * in clearItemInfos().
311      */
312     void imageInfosRemoved(const QList<ItemInfo>& infos);
313 
314     /**
315      * Connect to this signal only if you are the current preprocessor.
316      */
317     void preprocess(const QList<ItemInfo>& infos, const QList<QVariant>&);
318     void processAdded(const QList<ItemInfo>& infos, const QList<QVariant>&);
319 
320     /**
321      * If an ImageChangeset affected indexes of this model with changes as set in watchFlags(),
322      * this signal contains the changeset and the affected indexes.
323      */
324     void imageChange(const ImageChangeset&, const QItemSelection&);
325 
326     /**
327      * If an ImageTagChangeset affected indexes of this model,
328      * this signal contains the changeset and the affected indexes.
329      */
330     void imageTagChange(const ImageTagChangeset&, const QItemSelection&);
331 
332     /**
333      * Signals that the model is right now ready to start an incremental refresh.
334      * This is guaranteed only for the scope of emitting this signal.
335      */
336     void readyForIncrementalRefresh();
337 
338     /**
339      * Signals that the model has finished currently with all scheduled
340      * refreshing, full or incremental, and all preprocessing.
341      * The model is in polished, clean situation right now.
342      */
343     void allRefreshingFinished();
344 
345 public Q_SLOTS:
346 
347     void reAddItemInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues);
348     void reAddingFinished();
349 
350 protected:
351 
352     /**
353      * Subclasses that add ItemInfos in batches shall call startRefresh()
354      * when they start sending batches and finishRefresh() when they have finished.
355      * No incremental refreshes will be started while listing.
356      * A clearItemInfos() always stops listing, calling finishRefresh() is then not necessary.
357      */
358     void startRefresh();
359     void finishRefresh();
360 
361     /**
362      * As soon as the model is ready to start an incremental refresh, the signal
363      * readyForIncrementalRefresh() will be emitted. The signal will be emitted inline
364      * if the model is ready right now.
365      */
366     void requestIncrementalRefresh();
367     bool hasIncrementalRefreshPending()                                                                     const;
368 
369     /**
370      * Starts an incremental refresh operation. You shall only call this method from a slot
371      * connected to readyForIncrementalRefresh(). To initiate an incremental refresh,
372      * call requestIncrementalRefresh().
373      */
374     void startIncrementalRefresh();
375     void finishIncrementalRefresh();
376 
377     void emitDataChangedForAll();
378     void emitDataChangedForSelection(const QItemSelection& selection);
379 
380     /**
381      * Called when the internal storage is cleared
382      */
imageInfosCleared()383     virtual void imageInfosCleared() {};
384 
385     /**
386      * Called before rowsAboutToBeRemoved
387      */
imageInfosAboutToBeRemoved(int,int)388     virtual void imageInfosAboutToBeRemoved(int /*begin*/, int /*end*/) {};
389 
390 protected Q_SLOTS:
391 
392     virtual void slotImageChange(const ImageChangeset& changeset);
393     virtual void slotImageTagChange(const ImageTagChangeset& changeset);
394 
395 private:
396 
397     void appendInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues);
398     void appendInfosChecked(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues);
399     void publiciseInfos(const QList<ItemInfo>& infos, const QList<QVariant>& extraValues);
400     void cleanSituationChecks();
401     void removeRowPairsWithCheck(const QList<QPair<int, int> >& toRemove);
402     void removeRowPairs(const QList<QPair<int, int> >& toRemove);
403 
404 public:
405 
406     // Declared public because it's used in ItemModelIncrementalUpdater class
407     class Private;
408 
409 private:
410 
411     // Disable
412     ItemModel(const ItemModel&)            = delete;
413     ItemModel& operator=(const ItemModel&) = delete;
414 
415     Private* const d;
416 };
417 
418 } // namespace Digikam
419 
420 Q_DECLARE_METATYPE(Digikam::ItemModel*)
421 
422 #endif // DIGIKAM_ITEM_MODEL_H
423