1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2010-03-22
7  * Description : A view to display a list of items with GPS info.
8  *
9  * Copyright (C) 2010-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10  * Copyright (C) 2010      by Michael G. Hansen <mike at mghansen dot de>
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 #include "gpsitemlist.h"
26 
27 // Qt includes
28 
29 #include <QDrag>
30 #include <QHeaderView>
31 #include <QWheelEvent>
32 #include <QMenu>
33 #include <QAction>
34 
35 // KDE includes
36 
37 #include <kconfiggroup.h>
38 
39 // Local includes
40 
41 #include "digikam_debug.h"
42 #include "gpsitemdelegate.h"
43 #include "gpsitemlistdragdrophandler.h"
44 
45 namespace Digikam
46 {
47 
48 class Q_DECL_HIDDEN GPSItemList::Private
49 {
50 public:
51 
Private()52     explicit Private()
53       : editEnabled         (true),
54         dragEnabled         (false),
55         model               (nullptr),
56         selectionModel      (nullptr),
57         itemDelegate        (nullptr),
58         imageSortProxyModel (nullptr),
59         dragDropHandler     (nullptr)
60     {
61     }
62 
63     bool                     editEnabled;
64     bool                     dragEnabled;
65     GPSItemModel*            model;
66     QItemSelectionModel*     selectionModel;
67     GPSItemDelegate*         itemDelegate;
68     GPSItemSortProxyModel*   imageSortProxyModel;
69     ItemListDragDropHandler* dragDropHandler;
70 };
71 
GPSItemList(QWidget * const parent)72 GPSItemList::GPSItemList(QWidget* const parent)
73     : QTreeView(parent),
74       d(new Private())
75 {
76     header()->setSectionsMovable(true);
77     setUniformRowHeights(true);
78     setRootIsDecorated(false);
79     setAlternatingRowColors(true);
80 
81     d->itemDelegate = new GPSItemDelegate(this, this);
82     setItemDelegate(d->itemDelegate);
83     setThumbnailSize(60);
84     slotUpdateActionsEnabled();
85 
86     header()->installEventFilter(this);
87 }
88 
~GPSItemList()89 GPSItemList::~GPSItemList()
90 {
91     delete d;
92 }
93 
startDrag(Qt::DropActions supportedActions)94 void GPSItemList::startDrag(Qt::DropActions supportedActions)
95 {
96     if (!d->dragDropHandler)
97     {
98         QTreeView::startDrag(supportedActions);
99         return;
100     }
101 
102     // NOTE: read the selected indices from the source selection model, not our selection model,
103     // which is for the sorted model!
104 
105     const QList<QModelIndex> selectedIndicesFromModel = d->selectionModel->selectedIndexes();
106     QList<QPersistentModelIndex> selectedIndices;
107 
108     for (int i = 0 ; i < selectedIndicesFromModel.count() ; ++i)
109     {
110         selectedIndices << selectedIndicesFromModel.at(i);
111     }
112 
113     QMimeData* const dragMimeData = d->dragDropHandler->createMimeData(selectedIndices);
114 
115     if (!dragMimeData)
116     {
117         return;
118     }
119 
120     QDrag* const drag = new QDrag(this);
121     drag->setMimeData(dragMimeData);
122     drag->exec(Qt::CopyAction);
123 }
124 
setModelAndSelectionModel(GPSItemModel * const model,QItemSelectionModel * const selectionModel)125 void GPSItemList::setModelAndSelectionModel(GPSItemModel* const model, QItemSelectionModel* const selectionModel)
126 {
127     d->model               = model;
128     d->selectionModel      = selectionModel;
129     d->imageSortProxyModel = new GPSItemSortProxyModel(d->model, d->selectionModel);
130     setModel(d->imageSortProxyModel);
131 
132     connect(d->model, SIGNAL(signalThumbnailForIndexAvailable(QPersistentModelIndex,QPixmap)),
133             this, SLOT(slotThumbnailFromModel(QPersistentModelIndex,QPixmap)));
134 
135     connect(d->selectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)),
136             this, SLOT(slotInternalTreeViewImageActivated(QModelIndex,QModelIndex)));
137 
138     if (d->imageSortProxyModel->mappedSelectionModel())
139     {
140         setSelectionModel(d->imageSortProxyModel->mappedSelectionModel());
141     }
142 }
143 
setDragDropHandler(ItemListDragDropHandler * const dragDropHandler)144 void GPSItemList::setDragDropHandler(ItemListDragDropHandler* const dragDropHandler)
145 {
146     d->dragDropHandler = dragDropHandler;
147 }
148 
getModel() const149 GPSItemModel* GPSItemList::getModel() const
150 {
151     return d->model;
152 }
153 
setThumbnailSize(const int size)154 void GPSItemList::setThumbnailSize(const int size)
155 {
156     d->itemDelegate->setThumbnailSize(size);
157     setColumnWidth(GPSItemContainer::ColumnThumbnail, size);
158 }
159 
slotIncreaseThumbnailSize()160 void GPSItemList::slotIncreaseThumbnailSize()
161 {
162     // TODO: pick reasonable limits and make sure we stay on multiples of 5
163 
164     const int currentThumbnailSize = d->itemDelegate->getThumbnailSize();
165 
166     if (currentThumbnailSize < 200)
167     {
168         setThumbnailSize(currentThumbnailSize + 5);
169     }
170 }
171 
slotDecreaseThumbnailSize()172 void GPSItemList::slotDecreaseThumbnailSize()
173 {
174     const int currentThumbnailSize = d->itemDelegate->getThumbnailSize();
175 
176     if (currentThumbnailSize > 30)
177     {
178         setThumbnailSize(currentThumbnailSize - 5);
179     }
180 }
181 
wheelEvent(QWheelEvent * we)182 void GPSItemList::wheelEvent(QWheelEvent* we)
183 {
184     if ((we->modifiers() & Qt::ControlModifier) == 0)
185     {
186         QTreeView::wheelEvent(we);
187         return;
188     }
189 
190     we->accept();
191 
192     if      (we->angleDelta().y() > 0)
193     {
194         slotIncreaseThumbnailSize();
195     }
196     else if (we->angleDelta().y() < 0)
197     {
198         slotDecreaseThumbnailSize();
199     }
200 }
201 
slotThumbnailFromModel(const QPersistentModelIndex & index,const QPixmap &)202 void GPSItemList::slotThumbnailFromModel(const QPersistentModelIndex& index, const QPixmap& /*pixmap*/)
203 {
204     // TODO: verify that the size corresponds to the size of our thumbnails!
205 
206     update(d->imageSortProxyModel->mapFromSource(index));
207 }
208 
saveSettingsToGroup(KConfigGroup * const group)209 void GPSItemList::saveSettingsToGroup(KConfigGroup* const group)
210 {
211     group->writeEntry("Image List Thumbnail Size", d->itemDelegate->getThumbnailSize());
212     group->writeEntry("Header State",              header()->saveState());
213 }
214 
readSettingsFromGroup(const KConfigGroup * const group)215 void GPSItemList::readSettingsFromGroup(const KConfigGroup* const group)
216 {
217     setThumbnailSize(group->readEntry("Image List Thumbnail Size", 60));
218 
219     const QByteArray headerState = group->readEntry("Header State", QByteArray());
220 
221     if (!headerState.isEmpty())
222     {
223         header()->restoreState(headerState);
224     }
225     else
226     {
227         // by default, hide the advanced columns:
228 
229         header()->setSectionHidden(GPSItemContainer::ColumnDOP,         true);
230         header()->setSectionHidden(GPSItemContainer::ColumnFixType,     true);
231         header()->setSectionHidden(GPSItemContainer::ColumnNSatellites, true);
232     }
233 }
234 
getSelectionModel() const235 QItemSelectionModel* GPSItemList::getSelectionModel() const
236 {
237     return d->selectionModel;
238 }
239 
slotInternalTreeViewImageActivated(const QModelIndex & current,const QModelIndex &)240 void GPSItemList::slotInternalTreeViewImageActivated(const QModelIndex& current,
241                                                      const QModelIndex& /*previous*/)
242 {
243     // qCDebug(DIGIKAM_GENERAL_LOG) << current;
244 
245     emit signalImageActivated(current);
246 }
247 
getSortProxyModel() const248 GPSItemSortProxyModel* GPSItemList::getSortProxyModel() const
249 {
250     return d->imageSortProxyModel;
251 }
252 
setEditEnabled(const bool state)253 void GPSItemList::setEditEnabled(const bool state)
254 {
255     d->editEnabled = state;
256     slotUpdateActionsEnabled();
257 }
258 
setDragEnabled(const bool state)259 void GPSItemList::setDragEnabled(const bool state)
260 {
261     d->dragEnabled = state;
262     slotUpdateActionsEnabled();
263 }
264 
slotUpdateActionsEnabled()265 void GPSItemList::slotUpdateActionsEnabled()
266 {
267     QTreeView::setDragEnabled(d->dragEnabled && d->editEnabled);
268 
269     if (d->dragEnabled && d->editEnabled)
270     {
271         QTreeView::setDragDropMode(QAbstractItemView::DragOnly);
272     }
273 }
274 
eventFilter(QObject * watched,QEvent * event)275 bool GPSItemList::eventFilter(QObject* watched, QEvent* event)
276 {
277     QHeaderView* const headerView = header();
278 
279     if (!d->model || (watched != headerView) || (event->type() != QEvent::ContextMenu))
280     {
281         return QWidget::eventFilter(watched, event);
282     }
283 
284     QMenu* const menu               = new QMenu(this);
285 
286     // add action for all the columns
287 
288     for (int i = 0 ; i < d->model->columnCount() ; ++i)
289     {
290         const QString columnName    = d->model->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString();
291         const bool isVisible        = !headerView->isSectionHidden(i);
292 
293         QAction* const columnAction = new QAction(columnName, menu);
294         columnAction->setCheckable(true);
295         columnAction->setChecked(isVisible);
296         columnAction->setData(i);
297 
298         menu->addAction(columnAction);
299     }
300 
301     connect(menu, SIGNAL(triggered(QAction*)),
302             this, SLOT(slotColumnVisibilityActionTriggered(QAction*)));
303 
304     QContextMenuEvent* const e = static_cast<QContextMenuEvent*>(event);
305     menu->exec(e->globalPos());
306 
307     return true;
308 }
309 
slotColumnVisibilityActionTriggered(QAction * action)310 void GPSItemList::slotColumnVisibilityActionTriggered(QAction* action)
311 {
312     const int columnNumber     = action->data().toInt();
313     const bool columnIsVisible = action->isChecked();
314 
315     header()->setSectionHidden(columnNumber, !columnIsVisible);
316 }
317 
318 } // namespace Digikam
319