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