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-2011 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) 2014 by Mohamed_Anwer <m_dot_anwer at gmx dot com>
12 * Copyright (C) 2014 by Michael G. Hansen <mike at mghansen dot de>
13 * Copyright (C) 2009-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
14 *
15 * This program is free software; you can redistribute it
16 * and/or modify it under the terms of the GNU General
17 * Public License as published by the Free Software Foundation;
18 * either version 2, or (at your option)
19 * any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * ============================================================ */
27
28 #include "abstractalbumtreeview_p.h"
29
30 // KDE includes
31
32 #include <kconfiggroup.h>
33
34 namespace Digikam
35 {
36
AbstractAlbumTreeView(QWidget * const parent,Flags flags)37 AbstractAlbumTreeView::AbstractAlbumTreeView(QWidget* const parent, Flags flags)
38 : QTreeView (parent),
39 StateSavingObject (this),
40 m_albumModel (nullptr),
41 m_albumFilterModel (nullptr),
42 m_dragDropHandler (nullptr),
43 m_lastScrollBarValue (0),
44 m_checkOnMiddleClick (false),
45 m_restoreCheckState (false),
46 m_flags (flags),
47 d (new Private)
48 {
49 if (flags & CreateDefaultDelegate)
50 {
51 d->delegate = new AlbumTreeViewDelegate(this);
52 setItemDelegate(d->delegate);
53 setUniformRowHeights(true);
54 }
55
56 d->resizeColumnsTimer = new QTimer(this);
57 d->resizeColumnsTimer->setInterval(200);
58 d->resizeColumnsTimer->setSingleShot(true);
59
60 d->contextMenuIcon = QIcon::fromTheme(QLatin1String("digikam")).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize));
61 d->contextMenuTitle = i18n("Context menu");
62
63 connect(d->resizeColumnsTimer, SIGNAL(timeout()),
64 this, SLOT(adaptColumnsToContent()));
65
66 connect(ApplicationSettings::instance(), SIGNAL(setupChanged()),
67 this, SLOT(albumSettingsChanged()));
68
69 connect(this, SIGNAL(currentAlbumChanged(Album*)),
70 this, SLOT(currentAlbumChangedForBackupSelection(Album*)));
71
72 if (flags & CreateDefaultFilterModel)
73 {
74 setAlbumFilterModel(new AlbumFilterModel(this));
75 }
76
77 setSortingEnabled(true);
78 albumSettingsChanged();
79 }
80
~AbstractAlbumTreeView()81 AbstractAlbumTreeView::~AbstractAlbumTreeView()
82 {
83 delete d;
84 }
85
setAlbumModel(AbstractSpecificAlbumModel * const model)86 void AbstractAlbumTreeView::setAlbumModel(AbstractSpecificAlbumModel* const model)
87 {
88 if (m_albumModel == model)
89 {
90 return;
91 }
92
93 if (m_albumModel)
94 {
95 disconnect(m_albumModel, nullptr, this, nullptr);
96 }
97
98 m_albumModel = model;
99
100 if (m_albumFilterModel)
101 {
102 m_albumFilterModel->setSourceAlbumModel(m_albumModel);
103 }
104
105 if (m_albumModel)
106 {
107 if (!m_albumModel->rootAlbum())
108 {
109 connect(m_albumModel, SIGNAL(rootAlbumAvailable()),
110 this, SLOT(slotRootAlbumAvailable()));
111 }
112
113 if (m_albumFilterModel)
114 {
115 expand(m_albumFilterModel->rootAlbumIndex());
116 }
117 }
118 }
119
setAlbumFilterModel(AlbumFilterModel * const filterModel)120 void AbstractAlbumTreeView::setAlbumFilterModel(AlbumFilterModel* const filterModel)
121 {
122 if (filterModel == m_albumFilterModel)
123 {
124 return;
125 }
126
127 if (m_albumFilterModel)
128 {
129 disconnect(m_albumFilterModel);
130 }
131
132 if (selectionModel())
133 {
134 disconnect(selectionModel());
135 }
136
137 m_albumFilterModel = filterModel;
138 setModel(m_albumFilterModel);
139
140 if (m_albumFilterModel)
141 {
142 m_albumFilterModel->setSourceAlbumModel(m_albumModel);
143
144 connect(m_albumFilterModel, SIGNAL(searchTextSettingsAboutToChange(bool,bool)),
145 this, SLOT(slotSearchTextSettingsAboutToChange(bool,bool)));
146
147 connect(m_albumFilterModel, SIGNAL(searchTextSettingsChanged(bool,bool)),
148 this, SLOT(slotSearchTextSettingsChanged(bool,bool)));
149
150 // NOTE: When only single selection was available, everything was
151 // implemented using currentAlbum() which was equal with selectedAlbum()
152 // after enabling multiple selection they are no longer the same
153 // and some options must use selected others only currentAlbum
154 // Now AlbumManager implementation is a little bit of mess
155 // because selected are now currentAlbums()...
156
157 connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
158 this, SLOT(slotCurrentChanged()));
159
160 connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
161 this, SLOT(slotSelectionChanged()));
162
163 connect(m_albumFilterModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
164 this, SLOT(adaptColumnsOnDataChange(QModelIndex,QModelIndex)));
165
166 connect(m_albumFilterModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
167 this, SLOT(adaptColumnsOnRowChange(QModelIndex,int,int)));
168
169 connect(m_albumFilterModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
170 this, SLOT(adaptColumnsOnRowChange(QModelIndex,int,int)));
171
172 connect(m_albumFilterModel, SIGNAL(layoutChanged()),
173 this, SLOT(adaptColumnsOnLayoutChange()));
174
175 connect(horizontalScrollBar(), SIGNAL(valueChanged(int)),
176 this, SLOT(slotScrollBarValueChanged(int)));
177
178 connect(horizontalScrollBar(), SIGNAL(actionTriggered(int)),
179 this, SLOT(slotScrollBarActionTriggered(int)));
180
181 adaptColumnsToContent();
182
183 if (m_albumModel)
184 {
185 expand(m_albumFilterModel->rootAlbumIndex());
186 }
187 /*
188 m_albumFilterModel->setDynamicSortFilter(true);
189 */
190 }
191 }
192
albumModel() const193 AbstractSpecificAlbumModel* AbstractAlbumTreeView::albumModel() const
194 {
195 return m_albumModel;
196 }
197
albumFilterModel() const198 AlbumFilterModel* AbstractAlbumTreeView::albumFilterModel() const
199 {
200 return m_albumFilterModel;
201 }
202
setExpandOnSingleClick(const bool doThat)203 void AbstractAlbumTreeView::setExpandOnSingleClick(const bool doThat)
204 {
205 d->expandOnSingleClick = doThat;
206 }
207
setExpandNewCurrentItem(const bool doThat)208 void AbstractAlbumTreeView::setExpandNewCurrentItem(const bool doThat)
209 {
210 d->expandNewCurrent = doThat;
211 }
212
setSelectAlbumOnClick(const bool selectOnClick)213 void AbstractAlbumTreeView::setSelectAlbumOnClick(const bool selectOnClick)
214 {
215 d->selectAlbumOnClick = selectOnClick;
216 }
217
indexVisuallyAt(const QPoint & p)218 QModelIndex AbstractAlbumTreeView::indexVisuallyAt(const QPoint& p)
219 {
220 if (viewport()->rect().contains(p))
221 {
222 const QModelIndex index = indexAt(p);
223
224 if (index.isValid() && visualRect(index).contains(p))
225 {
226 return index;
227 }
228 }
229
230 return QModelIndex();
231 }
232
233 template<class A>
currentAlbums()234 QList<A*> AbstractAlbumTreeView::currentAlbums()
235 {
236 QList<A*> albums;
237 const QList<Album*> currentAl = AlbumManager::instance()->currentAlbums();
238
239 for (QList<Album*>::const_iterator it = currentAl.constBegin() ; it != currentAl.constEnd() ; ++it)
240 {
241 A* const item = dynamic_cast<A*>(*it);
242
243 if (item)
244 {
245 albums.append(item);
246 }
247 }
248
249 return albums;
250 }
251
slotSearchTextSettingsAboutToChange(bool searched,bool willSearch)252 void AbstractAlbumTreeView::slotSearchTextSettingsAboutToChange(bool searched, bool willSearch)
253 {
254 // backup before we begin searching
255
256 if (!searched && willSearch && d->searchBackup.isEmpty())
257 {
258 qCDebug(DIGIKAM_GENERAL_LOG) << "Searching started, backing up state";
259
260 QList<int> selection, expansion;
261 saveStateRecursive(QModelIndex(), selection, expansion);
262
263 // selection is ignored here because the user may have changed this
264 // while searching
265
266 foreach (const int& expandedId, expansion)
267 {
268 d->searchBackup[expandedId].expanded = true;
269 }
270
271 // also backup the last selected album in case this didn't work via the slot
272
273 const QList<Album*> selList = selectedAlbums<Album>(selectionModel(),
274 m_albumFilterModel);
275 if (!selList.isEmpty())
276 {
277 d->lastSelectedAlbum = selList.first();
278 }
279 }
280 }
281
slotSearchTextSettingsChanged(bool wasSearching,bool searched)282 void AbstractAlbumTreeView::slotSearchTextSettingsChanged(bool wasSearching, bool searched)
283 {
284 // ensure that all search results are visible if there is currently a search working
285
286 if (searched)
287 {
288 qCDebug(DIGIKAM_GENERAL_LOG) << "Searched, expanding all results";
289 expandMatches(QModelIndex());
290 }
291
292 // restore the tree view state if searching finished
293
294 if (wasSearching && !searched && !d->searchBackup.isEmpty())
295 {
296
297 qCDebug(DIGIKAM_GENERAL_LOG) << "Searching finished, restoring tree view state";
298
299 collapseAll();
300 restoreStateForHierarchy(QModelIndex(), d->searchBackup);
301 d->searchBackup.clear();
302
303 if (d->lastSelectedAlbum)
304 {
305 setCurrentAlbums(QList<Album*>() << d->lastSelectedAlbum, false);
306
307 // doing this twice somehow ensures that all parents are expanded
308 // and we are at the right position. Maybe a hack... ;)
309
310 scrollTo(m_albumFilterModel->indexForAlbum(d->lastSelectedAlbum));
311 scrollTo(m_albumFilterModel->indexForAlbum(d->lastSelectedAlbum));
312 }
313 }
314 }
315
currentAlbumChangedForBackupSelection(Album * currentAlbum)316 void AbstractAlbumTreeView::currentAlbumChangedForBackupSelection(Album* currentAlbum)
317 {
318 d->lastSelectedAlbum = currentAlbum;
319 }
320
slotRootAlbumAvailable()321 void AbstractAlbumTreeView::slotRootAlbumAvailable()
322 {
323 expand(m_albumFilterModel->rootAlbumIndex());
324 }
325
expandMatches(const QModelIndex & index)326 bool AbstractAlbumTreeView::expandMatches(const QModelIndex& index)
327 {
328 bool anyMatch = false;
329
330 // expand index if a child matches
331
332 const QModelIndex source_index = m_albumFilterModel->mapToSource(index);
333 const AlbumFilterModel::MatchResult result = m_albumFilterModel->matchResult(source_index);
334
335 switch (result)
336 {
337 case AlbumFilterModel::NoMatch:
338
339 if (index != rootIndex())
340 {
341 return false;
342 }
343
344 break;
345
346 case AlbumFilterModel::ParentMatch:
347
348 // Does not rule out additional child match, return value is unknown
349
350 break;
351
352 case AlbumFilterModel::DirectMatch:
353
354 // Does not rule out additional child match, but we know we will return true
355
356 anyMatch = true;
357 break;
358
359 case AlbumFilterModel::ChildMatch:
360 case AlbumFilterModel::SpecialMatch:
361
362 // We know already to expand, and we know already we will return true.
363
364 anyMatch = true;
365 expand(index);
366
367 break;
368 }
369
370 // Recurse. Expand if children if have an (indirect) match
371
372 const int rows = m_albumFilterModel->rowCount(index);
373
374 for (int i = 0 ; i < rows ; ++i)
375 {
376 const QModelIndex child = m_albumFilterModel->index(i, 0, index);
377 const bool childResult = expandMatches(child);
378
379 if (childResult)
380 {
381 anyMatch = true;
382
383 // if there is a direct match _and_ a child match, do not forget to expand the parent
384
385 expand(index);
386 }
387 }
388
389 return anyMatch;
390 }
391
setSearchTextSettings(const SearchTextSettings & settings)392 void AbstractAlbumTreeView::setSearchTextSettings(const SearchTextSettings& settings)
393 {
394 m_albumFilterModel->setSearchTextSettings(settings);
395 }
396
setAlbumManagerCurrentAlbum(const bool set)397 void AbstractAlbumTreeView::setAlbumManagerCurrentAlbum(const bool set)
398 {
399 d->setInAlbumManager = set;
400 }
401
setCurrentAlbums(const QList<Album * > & albums,bool selectInAlbumManager)402 void AbstractAlbumTreeView::setCurrentAlbums(const QList<Album*>& albums, bool selectInAlbumManager)
403 {
404 if (!model())
405 {
406 return;
407 }
408
409 if (selectInAlbumManager && d->setInAlbumManager)
410 {
411 AlbumManager::instance()->setCurrentAlbums(albums);
412 }
413
414 setCurrentIndex(albumFilterModel()->indexForAlbum(albums.first()));
415
416 QItemSelectionModel* const model = selectionModel();
417 model->clearSelection();
418
419 for (int it = 0 ; it < albums.size() ; ++it)
420 {
421 model->select(albumFilterModel()->indexForAlbum(albums.at(it)),
422 model->Select);
423 }
424 }
425
slotCurrentChanged()426 void AbstractAlbumTreeView::slotCurrentChanged()
427 {
428 // It seems that QItemSelectionModel::selectedIndexes() has not been updated at this point
429 // and returns the previously selected items. Therefore the line below did not work.
430 /*
431 QList<Album*> selected = selectedAlbums<Album>(selectionModel(),
432 m_albumFilterModel);
433 */
434 // Instead, we call QItemSelectionModel::currentIndex to get the current index.
435
436 const QModelIndex cIndex = selectionModel()->currentIndex();
437
438 if (!cIndex.isValid())
439 {
440 return;
441 }
442
443 Album* const cAlbum = m_albumFilterModel->albumForIndex(cIndex);
444
445 if (!cAlbum)
446 {
447 return;
448 }
449
450 emit currentAlbumChanged(cAlbum);
451 }
452
slotSelectionChanged()453 void AbstractAlbumTreeView::slotSelectionChanged()
454 {
455 // FIXME: Dead signal? Nobody listens to it
456 /*
457 emit selectedAlbumsChanged(selectedAlbums<Album>(selectionModel(), m_albumFilterModel));
458 */
459 if (d->selectAlbumOnClick)
460 {
461 AlbumManager::instance()->setCurrentAlbums(selectedAlbums<Album>(selectionModel(),
462 m_albumFilterModel));
463 }
464 }
465
mousePressEvent(QMouseEvent * e)466 void AbstractAlbumTreeView::mousePressEvent(QMouseEvent* e)
467 {
468 const QModelIndex currentBefor = currentIndex();
469
470 QTreeView::mousePressEvent(e);
471
472 if ((d->expandOnSingleClick || d->expandNewCurrent) && (e->button() == Qt::LeftButton))
473 {
474 const QModelIndex index = indexVisuallyAt(e->pos());
475
476 if (index.isValid())
477 {
478 if (d->expandOnSingleClick)
479 {
480 // See bug #126871: collapse/expand treeview using left mouse button single click.
481 // Exception: If a newly selected item is already expanded, do not collapse on selection.
482
483 const bool expanded = isExpanded(index);
484
485 if ((index == currentIndex()) || !expanded)
486 {
487 setExpanded(index, !expanded);
488 }
489 }
490 else
491 {
492 if (currentBefor != currentIndex())
493 {
494 expand(index);
495 }
496 }
497 }
498 }
499 else if (m_checkOnMiddleClick && (e->button() == Qt::MidButton))
500 {
501 Album* const a = m_albumFilterModel->albumForIndex(indexAt(e->pos()));
502
503 if (a)
504 {
505 middleButtonPressed(a);
506 }
507 }
508 }
509
middleButtonPressed(Album *)510 void AbstractAlbumTreeView::middleButtonPressed(Album*)
511 {
512 // reimplement if needed
513 }
514
startDrag(Qt::DropActions supportedActions)515 void AbstractAlbumTreeView::startDrag(Qt::DropActions supportedActions)
516 {
517 const QModelIndexList indexes = selectedIndexes();
518
519 if (indexes.count() > 0)
520 {
521 QMimeData* const data = m_albumFilterModel->mimeData(indexes);
522
523 if (!data)
524 {
525 return;
526 }
527
528 QStyleOptionViewItem option = viewOptions();
529 option.rect = viewport()->rect();
530 const QPixmap pixmap = /*m_delegate->*/pixmapForDrag(option, indexes);
531 QDrag* const drag = new QDrag(this);
532 drag->setPixmap(pixmap);
533 drag->setMimeData(data);
534 drag->exec(supportedActions, Qt::CopyAction);
535 }
536 }
537
538 /**
539 *TODO: Move to delegate, when we have one.
540 * Copy code from image delegate for creating icons when dragging multiple items
541 */
pixmapForDrag(const QStyleOptionViewItem &,QList<QModelIndex> indexes)542 QPixmap AbstractAlbumTreeView::pixmapForDrag(const QStyleOptionViewItem&, QList<QModelIndex> indexes)
543 {
544 if (indexes.isEmpty())
545 {
546 return QPixmap();
547 }
548
549 const QVariant decoration = indexes.first().data(Qt::DecorationRole);
550
551 return decoration.value<QPixmap>();
552 }
553
dragEnterEvent(QDragEnterEvent * e)554 void AbstractAlbumTreeView::dragEnterEvent(QDragEnterEvent* e)
555 {
556 AlbumModelDragDropHandler* const handler = m_albumModel->dragDropHandler();
557
558 if (handler && handler->acceptsMimeData(e->mimeData()))
559 {
560 setState(DraggingState);
561 e->accept();
562 }
563 else
564 {
565 e->ignore();
566 }
567 }
568
dragMoveEvent(QDragMoveEvent * e)569 void AbstractAlbumTreeView::dragMoveEvent(QDragMoveEvent* e)
570 {
571 QTreeView::dragMoveEvent(e);
572 AlbumModelDragDropHandler* const handler = m_albumModel->dragDropHandler();
573
574 if (handler)
575 {
576 const QModelIndex index = indexVisuallyAt(e->pos());
577 const Qt::DropAction action = handler->accepts(e, m_albumFilterModel->mapToSourceAlbumModel(index));
578
579 if (action == Qt::IgnoreAction)
580 {
581 e->ignore();
582 }
583 else
584 {
585 e->setDropAction(action);
586 e->accept();
587 }
588 }
589 }
590
dragLeaveEvent(QDragLeaveEvent * e)591 void AbstractAlbumTreeView::dragLeaveEvent(QDragLeaveEvent* e)
592 {
593 QTreeView::dragLeaveEvent(e);
594 }
595
dropEvent(QDropEvent * e)596 void AbstractAlbumTreeView::dropEvent(QDropEvent* e)
597 {
598 QTreeView::dropEvent(e);
599 AlbumModelDragDropHandler* const handler = m_albumModel->dragDropHandler();
600
601 if (handler)
602 {
603 const QModelIndex index = indexVisuallyAt(e->pos());
604
605 if (handler->dropEvent(this, e, m_albumFilterModel->mapToSourceAlbumModel(index)))
606 {
607 e->accept();
608 }
609 }
610 }
611
viewportEvent(QEvent * event)612 bool AbstractAlbumTreeView::viewportEvent(QEvent* event)
613 {
614 return QTreeView::viewportEvent(event);
615 }
616
selectedItems()617 QList<Album*> AbstractAlbumTreeView::selectedItems()
618 {
619 return selectedAlbums<Album>(selectionModel(), m_albumFilterModel);
620 }
621
doLoadState()622 void AbstractAlbumTreeView::doLoadState()
623 {
624 KConfigGroup configGroup = getConfigGroup();
625 /*
626 qCDebug(DIGIKAM_GENERAL_LOG) << "Loading view state from " << this << configGroup.name() << objectName();
627 */
628 // extract the selection from the config
629
630 const QStringList selection = configGroup.readEntry(entryName(d->configSelectionEntry), QStringList());
631 /*
632 qCDebug(DIGIKAM_GENERAL_LOG) << "selection: " << selection;
633 */
634 foreach (const QString& key, selection)
635 {
636 bool validId;
637 const int id = key.toInt(&validId);
638
639 if (validId)
640 {
641 d->statesByAlbumId[id].selected = true;
642 }
643 }
644
645 // extract expansion state from config
646
647 const QStringList expansion = configGroup.readEntry(entryName(d->configExpansionEntry), QStringList());
648 /*
649 qCDebug(DIGIKAM_GENERAL_LOG) << "expansion: " << expansion;
650 */
651 // If no expansion was done, at least expand the root albums
652
653 if (expansion.isEmpty())
654 {
655 QList<AlbumRootInfo> roots = CoreDbAccess().db()->getAlbumRoots();
656
657 foreach (const AlbumRootInfo& info, roots)
658 {
659 int albumId = CoreDbAccess().db()->getAlbumForPath(info.id, QLatin1String("/"), false);
660
661 if (albumId != -1)
662 {
663 d->statesByAlbumId[albumId].expanded = true;
664 }
665 }
666 }
667 else
668 {
669 foreach (const QString& key, expansion)
670 {
671 bool validId;
672 const int id = key.toInt(&validId);
673
674 if (validId)
675 {
676 d->statesByAlbumId[id].expanded = true;
677 }
678 }
679 }
680
681 // extract current index from config
682
683 const QString key = configGroup.readEntry(entryName(d->configCurrentIndexEntry), QString());
684 /*
685 qCDebug(DIGIKAM_GENERAL_LOG) << "currentIndex: " << key;
686 */
687 bool validId;
688 const int id = key.toInt(&validId);
689
690 if (validId)
691 {
692 d->statesByAlbumId[id].currentIndex = true;
693 }
694
695 /*
696 for (QMap<int, Digikam::State>::iterator it = d->statesByAlbumId.begin() ; it
697 != d->statesByAlbumId.end() ; ++it)
698 {
699 qCDebug(DIGIKAM_GENERAL_LOG) << "id = " << it.key() << ": recovered state (selected = "
700 << it.value().selected << ", expanded = "
701 << it.value().expanded << ", currentIndex = "
702 << it.value().currentIndex << ")";
703 }
704 */
705
706 // initial restore run, for everything already loaded
707 /*
708 qCDebug(DIGIKAM_GENERAL_LOG) << "initial restore run with " << model()->rowCount() << " rows";
709 */
710 restoreStateForHierarchy(QModelIndex(), d->statesByAlbumId);
711
712 // also restore the sorting order
713
714 sortByColumn(configGroup.readEntry(entryName(d->configSortColumnEntry), 0),
715 (Qt::SortOrder) configGroup.readEntry(entryName(d->configSortOrderEntry), (int)Qt::AscendingOrder));
716
717 // use a timer to scroll to the first possible selected album
718
719 QTimer::singleShot(200, this, SLOT(scrollToSelectedAlbum()));
720 }
721
restoreStateForHierarchy(const QModelIndex & index,const QMap<int,Digikam::State> & stateStore)722 void AbstractAlbumTreeView::restoreStateForHierarchy(const QModelIndex& index, const QMap<int, Digikam::State>& stateStore)
723 {
724 restoreState(index, stateStore);
725
726 // do a recursive call of the state restoration
727
728 for (int i = 0 ; i < model()->rowCount(index) ; ++i)
729 {
730 const QModelIndex child = model()->index(i, 0, index);
731 restoreStateForHierarchy(child, stateStore);
732 }
733 }
734
restoreState(const QModelIndex & index,const QMap<int,Digikam::State> & stateStore)735 void AbstractAlbumTreeView::restoreState(const QModelIndex& index, const QMap<int, Digikam::State>& stateStore)
736 {
737 Album* const album = albumFilterModel()->albumForIndex(index);
738
739 if (album && stateStore.contains(album->id()))
740 {
741
742 Digikam::State state = stateStore.value(album->id());
743
744 /*
745 qCDebug(DIGIKAM_GENERAL_LOG) << "Trying to restore state of album " << album->title() << "(" <<album->id() << ")"
746 << ": state(selected = " << state.selected
747 << ", expanded = " << state.expanded
748 << ", currentIndex = " << state.currentIndex << ")" << this;
749 */
750 // Block signals to prevent that the searches started when the last
751 // selected index is restored when loading the GUI
752
753 selectionModel()->blockSignals(true);
754
755 if (state.selected)
756 {
757 /*
758 qCDebug(DIGIKAM_GENERAL_LOG) << "Selecting" << album->title();
759 */
760 selectionModel()->select(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
761 }
762
763 // Restore expansion state but ensure that the root album is always expanded
764
765 if (!album->isRoot())
766 {
767 setExpanded(index, state.expanded);
768 }
769 else
770 {
771 setExpanded(index, true);
772 }
773
774 // restore the current index
775
776 if (state.currentIndex)
777 {
778 /*
779 qCDebug(DIGIKAM_GENERAL_LOG) << "Setting current index" << album->title() << "(" << album->id() << ")";
780 */
781 selectionModel()->setCurrentIndex(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
782 }
783
784 selectionModel()->blockSignals(false);
785 }
786 }
787
rowsInserted(const QModelIndex & parent,int start,int end)788 void AbstractAlbumTreeView::rowsInserted(const QModelIndex& parent, int start, int end)
789 {
790 QTreeView::rowsInserted(parent, start, end);
791
792 if (!d->statesByAlbumId.isEmpty())
793 {
794 /*
795 qCDebug(DIGIKAM_GENERAL_LOG) << "slot rowInserted called with index = " << index
796 << ", start = " << start << ", end = " << end
797 << "remaining ids" << d->statesByAlbumId.keys();
798 */
799 // Restore state for parent a second time - expansion can only be restored if there are children
800
801 restoreState(parent, d->statesByAlbumId);
802
803 for (int i = start ; i <= end ; ++i)
804 {
805 const QModelIndex child = model()->index(i, 0, parent);
806 restoreState(child, d->statesByAlbumId);
807 }
808 }
809 }
810
rowsAboutToBeRemoved(const QModelIndex & parent,int start,int end)811 void AbstractAlbumTreeView::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
812 {
813 QTreeView::rowsAboutToBeRemoved(parent, start, end);
814
815 // Clean up map if album id is reused for a new album
816
817 if (!d->statesByAlbumId.isEmpty())
818 {
819 for (int i = start ; i <= end ; ++i)
820 {
821 const QModelIndex child = model()->index(i, 0, parent);
822 Album* const album = albumModel()->albumForIndex(child);
823
824 if (album)
825 {
826 d->statesByAlbumId.remove(album->id());
827 }
828 }
829 }
830 }
831
adaptColumnsToContent()832 void AbstractAlbumTreeView::adaptColumnsToContent()
833 {
834 resizeColumnToContents(0);
835 }
836
scrollToSelectedAlbum()837 void AbstractAlbumTreeView::scrollToSelectedAlbum()
838 {
839 const QModelIndexList selected = selectedIndexes();
840
841 if (!selected.isEmpty())
842 {
843 scrollTo(selected.first(), PositionAtCenter);
844 horizontalScrollBar()->setValue(0);
845 }
846 }
847
expandEverything(const QModelIndex & index)848 void AbstractAlbumTreeView::expandEverything(const QModelIndex& index)
849 {
850 for (int row = 0 ; row < albumFilterModel()->rowCount(index) ; ++row)
851 {
852 const QModelIndex rowIndex = albumFilterModel()->index(row, 0, index);
853 expand(rowIndex);
854 expandEverything(rowIndex);
855 }
856 }
857
slotExpandNode()858 void AbstractAlbumTreeView::slotExpandNode()
859 {
860 QItemSelectionModel* const model = selectionModel();
861 QModelIndexList selected = model->selectedIndexes();
862
863 QQueue<QModelIndex> greyNodes;
864
865 foreach (const QModelIndex& index, selected)
866 {
867 greyNodes.append(index);
868 expand(index);
869 }
870
871 while (!greyNodes.isEmpty())
872 {
873 QModelIndex current = greyNodes.dequeue();
874
875 if (!current.isValid())
876 {
877 continue;
878 }
879
880 int it = 0;
881 QModelIndex child = current.model()->index(it++, 0, current);
882
883 while (child.isValid())
884 {
885 expand(child);
886 greyNodes.enqueue(child);
887 child = current.model()->index(it++, 0, current);
888 }
889 }
890 }
891
slotCollapseNode()892 void AbstractAlbumTreeView::slotCollapseNode()
893 {
894 QItemSelectionModel* const model = selectionModel();
895 QModelIndexList selected = model->selectedIndexes();
896
897 QQueue<QModelIndex> greyNodes;
898
899 foreach (const QModelIndex& index, selected)
900 {
901 greyNodes.append(index);
902 collapse(index);
903 }
904
905 while (!greyNodes.isEmpty())
906 {
907 QModelIndex current = greyNodes.dequeue();
908
909 if (!current.isValid())
910 {
911 continue;
912 }
913
914 int it = 0;
915 QModelIndex child = current.model()->index(it++, 0, current);
916
917 while (child.isValid())
918 {
919 collapse(child);
920 greyNodes.enqueue(child);
921 child = current.model()->index(it++, 0, current);
922 }
923 }
924 }
925
adaptColumnsOnDataChange(const QModelIndex & topLeft,const QModelIndex & bottomRight)926 void AbstractAlbumTreeView::adaptColumnsOnDataChange(const QModelIndex& topLeft, const QModelIndex& bottomRight)
927 {
928 Q_UNUSED(topLeft);
929 Q_UNUSED(bottomRight);
930
931 if (!d->resizeColumnsTimer->isActive())
932 {
933 d->resizeColumnsTimer->start();
934 }
935 }
936
adaptColumnsOnRowChange(const QModelIndex & parent,int start,int end)937 void AbstractAlbumTreeView::adaptColumnsOnRowChange(const QModelIndex& parent, int start, int end)
938 {
939 Q_UNUSED(parent);
940 Q_UNUSED(start);
941 Q_UNUSED(end);
942
943 if (!d->resizeColumnsTimer->isActive())
944 {
945 d->resizeColumnsTimer->start();
946 }
947 }
948
adaptColumnsOnLayoutChange()949 void AbstractAlbumTreeView::adaptColumnsOnLayoutChange()
950 {
951 if (!d->resizeColumnsTimer->isActive())
952 {
953 d->resizeColumnsTimer->start();
954 }
955 }
956
doSaveState()957 void AbstractAlbumTreeView::doSaveState()
958 {
959 KConfigGroup configGroup = getConfigGroup();
960
961 QList<int> selection, expansion;
962
963 for (int i = 0 ; i < model()->rowCount() ; ++i)
964 {
965 const QModelIndex index = model()->index(i, 0);
966 saveStateRecursive(index, selection, expansion);
967 }
968
969 Album* const selectedAlbum = albumFilterModel()->albumForIndex(selectionModel()->currentIndex());
970 QString currentIndex;
971
972 if (selectedAlbum)
973 {
974 currentIndex = QString::number(selectedAlbum->id());
975 }
976
977 configGroup.writeEntry(entryName(d->configSelectionEntry), selection);
978 configGroup.writeEntry(entryName(d->configExpansionEntry), expansion);
979 configGroup.writeEntry(entryName(d->configCurrentIndexEntry), currentIndex);
980 configGroup.writeEntry(entryName(d->configSortColumnEntry), albumFilterModel()->sortColumn());
981
982 // A dummy way to force the tree view to resort if the album sort role changed
983
984 if (ApplicationSettings::instance()->getAlbumSortChanged())
985 {
986 if (int(albumFilterModel()->sortOrder()) == 0)
987 {
988 configGroup.writeEntry(entryName(d->configSortOrderEntry), 1);
989 }
990 else
991 {
992 configGroup.writeEntry(entryName(d->configSortOrderEntry), 0);
993 }
994 }
995 else
996 {
997 configGroup.writeEntry(entryName(d->configSortOrderEntry), int(albumFilterModel()->sortOrder()));
998 }
999 }
1000
saveStateRecursive(const QModelIndex & index,QList<int> & selection,QList<int> & expansion)1001 void AbstractAlbumTreeView::saveStateRecursive(const QModelIndex& index, QList<int>& selection, QList<int>& expansion)
1002 {
1003 Album* const album = albumFilterModel()->albumForIndex(index);
1004
1005 if (album)
1006 {
1007 const int id = album->id();
1008
1009 if (selectionModel()->isSelected(index))
1010 {
1011 selection.append(id);
1012 }
1013
1014 if (isExpanded(index))
1015 {
1016 expansion.append(id);
1017 }
1018 }
1019
1020 for (int i = 0 ; i < model()->rowCount(index) ; ++i)
1021 {
1022 const QModelIndex child = model()->index(i, 0, index);
1023 saveStateRecursive(child, selection, expansion);
1024 }
1025 }
1026
setEnableContextMenu(const bool enable)1027 void AbstractAlbumTreeView::setEnableContextMenu(const bool enable)
1028 {
1029 d->enableContextMenu = enable;
1030 }
1031
showContextMenuAt(QContextMenuEvent * event,Album * albumForEvent)1032 bool AbstractAlbumTreeView::showContextMenuAt(QContextMenuEvent* event, Album* albumForEvent)
1033 {
1034 Q_UNUSED(event);
1035 return albumForEvent;
1036 }
1037
setContextMenuIcon(const QPixmap & pixmap)1038 void AbstractAlbumTreeView::setContextMenuIcon(const QPixmap& pixmap)
1039 {
1040 d->contextMenuIcon = pixmap;
1041 }
1042
setContextMenuTitle(const QString & title)1043 void AbstractAlbumTreeView::setContextMenuTitle(const QString& title)
1044 {
1045 d->contextMenuTitle = title;
1046 }
1047
contextMenuIcon() const1048 QPixmap AbstractAlbumTreeView::contextMenuIcon() const
1049 {
1050 return d->contextMenuIcon;
1051 }
1052
contextMenuTitle() const1053 QString AbstractAlbumTreeView::contextMenuTitle() const
1054 {
1055 return d->contextMenuTitle;
1056 }
1057
addContextMenuElement(ContextMenuElement * element)1058 void AbstractAlbumTreeView::addContextMenuElement(ContextMenuElement* element)
1059 {
1060 d->contextMenuElements << element;
1061 }
1062
removeContextMenuElement(ContextMenuElement * element)1063 void AbstractAlbumTreeView::removeContextMenuElement(ContextMenuElement* element)
1064 {
1065 d->contextMenuElements.removeAll(element);
1066 }
1067
contextMenuElements() const1068 QList<AbstractAlbumTreeView::ContextMenuElement*> AbstractAlbumTreeView::contextMenuElements() const
1069 {
1070 return d->contextMenuElements;
1071 }
1072
contextMenuEvent(QContextMenuEvent * event)1073 void AbstractAlbumTreeView::contextMenuEvent(QContextMenuEvent* event)
1074 {
1075 if (!d->enableContextMenu)
1076 {
1077 return;
1078 }
1079
1080 Album* const album = albumFilterModel()->albumForIndex(indexAt(event->pos()));
1081
1082 if (!album)
1083 {
1084 return;
1085 }
1086
1087 if (album->isTrashAlbum())
1088 {
1089 // For the moment, disabling context menu for trash.
1090 // TODO : customize it.
1091
1092 return;
1093 }
1094
1095 if (!showContextMenuAt(event, album))
1096 {
1097 return;
1098 }
1099
1100 // switch to the selected album if need
1101
1102 if (d->selectOnContextMenu)
1103 {
1104 setCurrentAlbums(QList<Album*>() << album);
1105 }
1106
1107 // --------------------------------------------------------
1108
1109 QMenu* const popmenu = new QMenu(this);
1110 popmenu->addSection(contextMenuIcon(), contextMenuTitle());
1111 ContextMenuHelper cmhelper(popmenu);
1112
1113 addCustomContextMenuActions(cmhelper, album);
1114
1115 foreach (ContextMenuElement* const element, d->contextMenuElements)
1116 {
1117 element->addActions(this, cmhelper, album);
1118 }
1119
1120 AlbumPointer<Album> albumPointer(album);
1121 QAction* const choice = cmhelper.exec(QCursor::pos());
1122 handleCustomContextMenuAction(choice, albumPointer);
1123 }
1124
setSelectOnContextMenu(const bool select)1125 void AbstractAlbumTreeView::setSelectOnContextMenu(const bool select)
1126 {
1127 d->selectOnContextMenu = select;
1128 }
1129
addCustomContextMenuActions(ContextMenuHelper & cmh,Album * album)1130 void AbstractAlbumTreeView::addCustomContextMenuActions(ContextMenuHelper& cmh, Album* album)
1131 {
1132 Q_UNUSED(cmh);
1133 Q_UNUSED(album);
1134 }
1135
handleCustomContextMenuAction(QAction * action,const AlbumPointer<Album> & album)1136 void AbstractAlbumTreeView::handleCustomContextMenuAction(QAction* action, const AlbumPointer<Album>& album)
1137 {
1138 Q_UNUSED(action);
1139 Q_UNUSED(album);
1140 }
1141
albumSettingsChanged()1142 void AbstractAlbumTreeView::albumSettingsChanged()
1143 {
1144 setFont(ApplicationSettings::instance()->getTreeViewFont());
1145
1146 if (d->delegate)
1147 {
1148 d->delegate->updateHeight();
1149 }
1150 }
1151
slotScrollBarValueChanged(int value)1152 void AbstractAlbumTreeView::slotScrollBarValueChanged(int value)
1153 {
1154 if (m_lastScrollBarValue == -1)
1155 {
1156 m_lastScrollBarValue = value;
1157 }
1158
1159 if ((value == 0) && (m_lastScrollBarValue > 0))
1160 {
1161 horizontalScrollBar()->setValue(m_lastScrollBarValue);
1162 }
1163 }
1164
slotScrollBarActionTriggered(int action)1165 void AbstractAlbumTreeView::slotScrollBarActionTriggered(int action)
1166 {
1167 if ((action == QAbstractSlider::SliderMove) ||
1168 (action == QAbstractSlider::SliderToMinimum) ||
1169 (action == QAbstractSlider::SliderPageStepSub) ||
1170 (action == QAbstractSlider::SliderSingleStepSub))
1171 {
1172 m_lastScrollBarValue = -1;
1173 }
1174 }
1175
1176 } // namespace Digikam
1177