File indexing completed on 2025-01-19 03:53:21

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-03-25
0007  * Description : Tree View for album models
0008  *
0009  * SPDX-FileCopyrightText: 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  * SPDX-FileCopyrightText: 2010-2011 by Andi Clemens <andi dot clemens at gmail dot com>
0011  * SPDX-FileCopyrightText: 2014      by Mohamed_Anwer <m_dot_anwer at gmx dot com>
0012  * SPDX-FileCopyrightText: 2014      by Michael G. Hansen <mike at mghansen dot de>
0013  * SPDX-FileCopyrightText: 2009-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0014  *
0015  * SPDX-License-Identifier: GPL-2.0-or-later
0016  *
0017  * ============================================================ */
0018 
0019 #include "abstractalbumtreeview_p.h"
0020 
0021 namespace Digikam
0022 {
0023 
0024 AbstractAlbumTreeView::AbstractAlbumTreeView(QWidget* const parent, Flags flags)
0025     : QTreeView             (parent),
0026       StateSavingObject     (this),
0027       m_albumModel          (nullptr),
0028       m_albumFilterModel    (nullptr),
0029       m_dragDropHandler     (nullptr),
0030       m_lastScrollBarValue  (0),
0031       m_checkOnMiddleClick  (false),
0032       m_restoreCheckState   (false),
0033       m_flags               (flags),
0034       d                     (new Private)
0035 {
0036     if (flags & CreateDefaultDelegate)
0037     {
0038         d->delegate = new AlbumTreeViewDelegate(this);
0039         setItemDelegate(d->delegate);
0040         setUniformRowHeights(true);
0041     }
0042 
0043     d->resizeColumnsTimer = new QTimer(this);
0044     d->resizeColumnsTimer->setInterval(200);
0045     d->resizeColumnsTimer->setSingleShot(true);
0046 
0047     d->contextMenuIcon  = QIcon::fromTheme(QLatin1String("digikam")).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize));
0048     d->contextMenuTitle = i18n("Context menu");
0049 
0050     connect(d->resizeColumnsTimer, SIGNAL(timeout()),
0051             this, SLOT(adaptColumnsToContent()));
0052 
0053     connect(ApplicationSettings::instance(), SIGNAL(setupChanged()),
0054             this, SLOT(albumSettingsChanged()));
0055 
0056     connect(this, SIGNAL(currentAlbumChanged(Album*)),
0057             this, SLOT(currentAlbumChangedForBackupSelection(Album*)));
0058 
0059     if (flags & CreateDefaultFilterModel)
0060     {
0061         setAlbumFilterModel(new AlbumFilterModel(this));
0062     }
0063 
0064     setSortingEnabled(true);
0065     albumSettingsChanged();
0066 }
0067 
0068 AbstractAlbumTreeView::~AbstractAlbumTreeView()
0069 {
0070     delete d;
0071 }
0072 
0073 void AbstractAlbumTreeView::setAlbumModel(AbstractSpecificAlbumModel* const model)
0074 {
0075     if (m_albumModel == model)
0076     {
0077         return;
0078     }
0079 
0080     if (m_albumModel)
0081     {
0082         disconnect(m_albumModel, nullptr, this, nullptr);
0083     }
0084 
0085     m_albumModel = model;
0086 
0087     if (m_albumFilterModel)
0088     {
0089         m_albumFilterModel->setSourceAlbumModel(m_albumModel);
0090     }
0091 
0092     if (m_albumModel)
0093     {
0094         if (!m_albumModel->rootAlbum())
0095         {
0096             connect(m_albumModel, SIGNAL(rootAlbumAvailable()),
0097                     this, SLOT(slotRootAlbumAvailable()));
0098         }
0099 
0100         if (m_albumFilterModel)
0101         {
0102             expand(m_albumFilterModel->rootAlbumIndex());
0103         }
0104     }
0105 }
0106 
0107 void AbstractAlbumTreeView::setAlbumFilterModel(AlbumFilterModel* const filterModel)
0108 {
0109     if (filterModel == m_albumFilterModel)
0110     {
0111         return;
0112     }
0113 
0114     if (m_albumFilterModel)
0115     {
0116         disconnect(m_albumFilterModel);
0117     }
0118 
0119     if (selectionModel())
0120     {
0121         disconnect(selectionModel());
0122     }
0123 
0124     m_albumFilterModel = filterModel;
0125     setModel(m_albumFilterModel);
0126 
0127     if (m_albumFilterModel)
0128     {
0129         m_albumFilterModel->setSourceAlbumModel(m_albumModel);
0130 
0131         connect(m_albumFilterModel, SIGNAL(searchTextSettingsAboutToChange(bool,bool)),
0132                 this, SLOT(slotSearchTextSettingsAboutToChange(bool,bool)));
0133 
0134         connect(m_albumFilterModel, SIGNAL(searchTextSettingsChanged(bool,bool)),
0135                 this, SLOT(slotSearchTextSettingsChanged(bool,bool)));
0136 
0137         // NOTE: When only single selection was available, everything was
0138         //       implemented using currentAlbum() which was equal with selectedAlbum()
0139         //       after enabling multiple selection they are no longer the same
0140         //       and some options must use selected others only currentAlbum
0141         //       Now AlbumManager implementation is a little bit of mess
0142         //       because selected are now currentAlbums()...
0143 
0144         connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
0145                 this, SLOT(slotCurrentChanged()));
0146 
0147         connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
0148                 this, SLOT(slotSelectionChanged()));
0149 
0150         connect(m_albumFilterModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
0151                 this, SLOT(adaptColumnsOnDataChange(QModelIndex,QModelIndex)));
0152 
0153         connect(m_albumFilterModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
0154                 this, SLOT(adaptColumnsOnRowChange(QModelIndex,int,int)));
0155 
0156         connect(m_albumFilterModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
0157                 this, SLOT(adaptColumnsOnRowChange(QModelIndex,int,int)));
0158 
0159         connect(m_albumFilterModel, SIGNAL(layoutChanged()),
0160                 this, SLOT(adaptColumnsOnLayoutChange()));
0161 
0162         connect(horizontalScrollBar(), SIGNAL(valueChanged(int)),
0163                 this, SLOT(slotScrollBarValueChanged(int)));
0164 
0165         connect(horizontalScrollBar(), SIGNAL(actionTriggered(int)),
0166                 this, SLOT(slotScrollBarActionTriggered(int)));
0167 
0168         adaptColumnsToContent();
0169 
0170         if (m_albumModel)
0171         {
0172             expand(m_albumFilterModel->rootAlbumIndex());
0173         }
0174 /*
0175         m_albumFilterModel->setDynamicSortFilter(true);
0176 */
0177     }
0178 }
0179 
0180 AbstractSpecificAlbumModel* AbstractAlbumTreeView::albumModel() const
0181 {
0182     return m_albumModel;
0183 }
0184 
0185 AlbumFilterModel* AbstractAlbumTreeView::albumFilterModel() const
0186 {
0187     return m_albumFilterModel;
0188 }
0189 
0190 void AbstractAlbumTreeView::setExpandOnSingleClick(const bool doThat)
0191 {
0192     d->expandOnSingleClick = doThat;
0193 }
0194 
0195 void AbstractAlbumTreeView::setExpandNewCurrentItem(const bool doThat)
0196 {
0197     d->expandNewCurrent = doThat;
0198 }
0199 
0200 void AbstractAlbumTreeView::setSelectAlbumOnClick(const bool selectOnClick)
0201 {
0202     d->selectAlbumOnClick = selectOnClick;
0203 }
0204 
0205 QModelIndex AbstractAlbumTreeView::indexVisuallyAt(const QPoint& p)
0206 {
0207     if (viewport()->rect().contains(p))
0208     {
0209         const QModelIndex index = indexAt(p);
0210 
0211         if (index.isValid() && visualRect(index).contains(p))
0212         {
0213             return index;
0214         }
0215     }
0216 
0217     return QModelIndex();
0218 }
0219 
0220 template<class A>
0221 QList<A*> AbstractAlbumTreeView::currentAlbums()
0222 {
0223     QList<A*> albums;
0224     const QList<Album*> currentAl = AlbumManager::instance()->currentAlbums();
0225 
0226     for (QList<Album*>::const_iterator it = currentAl.constBegin() ; it != currentAl.constEnd() ; ++it)
0227     {
0228         A* const item = dynamic_cast<A*>(*it);
0229 
0230         if (item)
0231         {
0232             albums.append(item);
0233         }
0234     }
0235 
0236     return albums;
0237 }
0238 
0239 void AbstractAlbumTreeView::slotSearchTextSettingsAboutToChange(bool searched, bool willSearch)
0240 {
0241     // backup before we begin searching
0242 
0243     if (!searched && willSearch && d->searchBackup.isEmpty())
0244     {
0245         qCDebug(DIGIKAM_GENERAL_LOG) << "Searching started, backing up state";
0246 
0247         QList<int> selection, expansion;
0248         saveStateRecursive(QModelIndex(), selection, expansion);
0249 
0250         // selection is ignored here because the user may have changed this
0251         // while searching
0252 
0253         Q_FOREACH (const int& expandedId, expansion)
0254         {
0255             d->searchBackup[expandedId].expanded = true;
0256         }
0257 
0258         // also backup the last selected album in case this didn't work via the slot
0259 
0260         const QList<Album*> selList = selectedAlbums<Album>(selectionModel(),
0261                                                             m_albumFilterModel);
0262         if (!selList.isEmpty())
0263         {
0264             d->lastSelectedAlbum = selList.first();
0265         }
0266     }
0267 }
0268 
0269 void AbstractAlbumTreeView::slotSearchTextSettingsChanged(bool wasSearching, bool searched)
0270 {
0271     // ensure that all search results are visible if there is currently a search working
0272 
0273     if (searched)
0274     {
0275         qCDebug(DIGIKAM_GENERAL_LOG) << "Searched, expanding all results";
0276         expandMatches(QModelIndex());
0277     }
0278 
0279     // Restore the tree view state if searching finished
0280 
0281     if (wasSearching && !searched && !d->searchBackup.isEmpty())
0282     {
0283 
0284         qCDebug(DIGIKAM_GENERAL_LOG) << "Searching finished, restoring tree view state";
0285 
0286         collapseAll();
0287         restoreStateForHierarchy(QModelIndex(), d->searchBackup);
0288         d->searchBackup.clear();
0289 
0290         if (d->lastSelectedAlbum)
0291         {
0292             setCurrentAlbums(QList<Album*>() << d->lastSelectedAlbum, false);
0293 
0294             // Doing this twice somehow ensures that all parents are expanded
0295             // and we are at the right position. Maybe a hack... ;)
0296 
0297             scrollTo(m_albumFilterModel->indexForAlbum(d->lastSelectedAlbum));
0298             scrollTo(m_albumFilterModel->indexForAlbum(d->lastSelectedAlbum));
0299         }
0300     }
0301 }
0302 
0303 void AbstractAlbumTreeView::currentAlbumChangedForBackupSelection(Album* currentAlbum)
0304 {
0305     d->lastSelectedAlbum = currentAlbum;
0306 }
0307 
0308 void AbstractAlbumTreeView::slotRootAlbumAvailable()
0309 {
0310     expand(m_albumFilterModel->rootAlbumIndex());
0311 }
0312 
0313 bool AbstractAlbumTreeView::expandMatches(const QModelIndex& index)
0314 {
0315     bool anyMatch = false;
0316 
0317     // Expand index if a child matches
0318 
0319     const QModelIndex source_index             = m_albumFilterModel->mapToSource(index);
0320     const AlbumFilterModel::MatchResult result = m_albumFilterModel->matchResult(source_index);
0321 
0322     switch (result)
0323     {
0324         case AlbumFilterModel::NoMatch:
0325         {
0326             if (index != rootIndex())
0327             {
0328                 return false;
0329             }
0330 
0331             break;
0332         }
0333 
0334         case AlbumFilterModel::ParentMatch:
0335         {
0336             // Does not rule out additional child match, return value is unknown
0337 
0338             break;
0339         }
0340 
0341         case AlbumFilterModel::DirectMatch:
0342         {
0343             // Does not rule out additional child match, but we know we will return true
0344 
0345             anyMatch = true;
0346             break;
0347         }
0348 
0349         case AlbumFilterModel::ChildMatch:
0350         case AlbumFilterModel::SpecialMatch:
0351         {
0352             // We know already to expand, and we know already we will return true.
0353 
0354             anyMatch = true;
0355             expand(index);
0356 
0357             break;
0358         }
0359     }
0360 
0361     // Recurse. Expand if children if have an (indirect) match
0362 
0363     const int rows = m_albumFilterModel->rowCount(index);
0364 
0365     for (int i = 0 ; i < rows ; ++i)
0366     {
0367         const QModelIndex child = m_albumFilterModel->index(i, 0, index);
0368         const bool childResult  = expandMatches(child);
0369 
0370         if (childResult)
0371         {
0372             anyMatch = true;
0373 
0374             // if there is a direct match _and_ a child match, do not forget to expand the parent
0375 
0376             expand(index);
0377         }
0378     }
0379 
0380     return anyMatch;
0381 }
0382 
0383 void AbstractAlbumTreeView::setSearchTextSettings(const SearchTextSettings& settings)
0384 {
0385     m_albumFilterModel->setSearchTextSettings(settings);
0386 }
0387 
0388 void AbstractAlbumTreeView::setAlbumManagerCurrentAlbum(const bool set)
0389 {
0390     d->setInAlbumManager = set;
0391 }
0392 
0393 void AbstractAlbumTreeView::setCurrentAlbums(const QList<Album*>& albums, bool selectInAlbumManager)
0394 {
0395     if (!model())
0396     {
0397         return;
0398     }
0399 
0400     if (selectInAlbumManager && d->setInAlbumManager)
0401     {
0402         AlbumManager::instance()->setCurrentAlbums(albums);
0403     }
0404 
0405     setCurrentIndex(albumFilterModel()->indexForAlbum(albums.first()));
0406 
0407     QItemSelectionModel* const model = selectionModel();
0408     model->clearSelection();
0409 
0410     for (int it = 0 ; it < albums.size() ; ++it)
0411     {
0412         model->select(albumFilterModel()->indexForAlbum(albums.at(it)),
0413                       model->Select);
0414     }
0415 }
0416 
0417 void AbstractAlbumTreeView::slotCurrentChanged()
0418 {
0419     // It seems that QItemSelectionModel::selectedIndexes() has not been updated at this point
0420     // and returns the previously selected items. Therefore the line below did not work.
0421 /*
0422     QList<Album*> selected = selectedAlbums<Album>(selectionModel(),
0423                                                    m_albumFilterModel);
0424 */
0425     // Instead, we call QItemSelectionModel::currentIndex to get the current index.
0426 
0427     const QModelIndex cIndex = selectionModel()->currentIndex();
0428 
0429     if (!cIndex.isValid())
0430     {
0431         return;
0432     }
0433 
0434     Album* const cAlbum = m_albumFilterModel->albumForIndex(cIndex);
0435 
0436     if (!cAlbum)
0437     {
0438         return;
0439     }
0440 
0441     Q_EMIT currentAlbumChanged(cAlbum);
0442 }
0443 
0444 void AbstractAlbumTreeView::slotSelectionChanged()
0445 {
0446     // FIXME: Dead signal? Nobody listens to it
0447 /*
0448     Q_EMIT selectedAlbumsChanged(selectedAlbums<Album>(selectionModel(), m_albumFilterModel));
0449 */
0450     if (d->selectAlbumOnClick)
0451     {
0452         AlbumManager::instance()->setCurrentAlbums(selectedAlbums<Album>(selectionModel(),
0453                                                                          m_albumFilterModel));
0454     }
0455 }
0456 
0457 void AbstractAlbumTreeView::mousePressEvent(QMouseEvent* e)
0458 {
0459     const QModelIndex currentBefor = currentIndex();
0460 
0461     QTreeView::mousePressEvent(e);
0462 
0463     if      ((d->expandOnSingleClick || d->expandNewCurrent) && (e->button() == Qt::LeftButton))
0464     {
0465         const QModelIndex index = indexVisuallyAt(e->pos());
0466 
0467         if (index.isValid())
0468         {
0469             if (d->expandOnSingleClick)
0470             {
0471                 // See bug #126871: collapse/expand treeview using left mouse button single click.
0472                 // Exception: If a newly selected item is already expanded, do not collapse on selection.
0473 
0474                 const bool expanded = isExpanded(index);
0475 
0476                 if ((index == currentIndex()) || !expanded)
0477                 {
0478                     setExpanded(index, !expanded);
0479                 }
0480             }
0481             else
0482             {
0483                 if (currentBefor != currentIndex())
0484                 {
0485                     expand(index);
0486                 }
0487             }
0488         }
0489     }
0490     else if (m_checkOnMiddleClick && (e->button() == Qt::MiddleButton))
0491     {
0492         Album* const a = m_albumFilterModel->albumForIndex(indexAt(e->pos()));
0493 
0494         if (a)
0495         {
0496             middleButtonPressed(a);
0497         }
0498     }
0499 }
0500 
0501 void AbstractAlbumTreeView::middleButtonPressed(Album*)
0502 {
0503     // reimplement if needed
0504 }
0505 
0506 void AbstractAlbumTreeView::startDrag(Qt::DropActions supportedActions)
0507 {
0508     const QModelIndexList indexes = selectedIndexes();
0509 
0510     if (indexes.count() > 0)
0511     {
0512         QMimeData* const data = m_albumFilterModel->mimeData(indexes);
0513 
0514         if (!data)
0515         {
0516             return;
0517         }
0518 
0519         QStyleOptionViewItem option;
0520 
0521 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0522 
0523         initViewItemOption(&option);
0524 
0525 #else
0526 
0527         option = viewOptions();
0528 
0529 #endif
0530 
0531         option.rect                 = viewport()->rect();
0532         const QPixmap pixmap        = /*m_delegate->*/pixmapForDrag(option, indexes);
0533         QDrag* const drag           = new QDrag(this);
0534         drag->setPixmap(pixmap);
0535         drag->setMimeData(data);
0536         drag->exec(supportedActions, Qt::CopyAction);
0537     }
0538 }
0539 
0540 /**
0541  *TODO: Move to delegate, when we have one.
0542  *      Copy code from image delegate for creating icons when dragging multiple items
0543  */
0544 QPixmap AbstractAlbumTreeView::pixmapForDrag(const QStyleOptionViewItem&, QList<QModelIndex> indexes)
0545 {
0546     if (indexes.isEmpty())
0547     {
0548         return QPixmap();
0549     }
0550 
0551     const QVariant decoration = indexes.first().data(Qt::DecorationRole);
0552 
0553     return (decoration.value<QPixmap>());
0554 }
0555 
0556 void AbstractAlbumTreeView::dragEnterEvent(QDragEnterEvent* e)
0557 {
0558     AlbumModelDragDropHandler* const handler = m_albumModel->dragDropHandler();
0559 
0560     if (handler && handler->acceptsMimeData(e->mimeData()))
0561     {
0562         setState(DraggingState);
0563         e->accept();
0564     }
0565     else
0566     {
0567         e->ignore();
0568     }
0569 }
0570 
0571 void AbstractAlbumTreeView::dragMoveEvent(QDragMoveEvent* e)
0572 {
0573     QTreeView::dragMoveEvent(e);
0574     AlbumModelDragDropHandler* const handler = m_albumModel->dragDropHandler();
0575 
0576     if (handler)
0577     {
0578 
0579 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0580 
0581         const QModelIndex index     = indexVisuallyAt(e->position().toPoint());
0582 
0583 #else
0584 
0585         const QModelIndex index     = indexVisuallyAt(e->pos());
0586 
0587 #endif
0588 
0589         const QModelIndex source    = m_albumFilterModel->mapToSourceAlbumModel(index);
0590         const Qt::DropAction action = handler->accepts(e, source);
0591 
0592         if (action == Qt::IgnoreAction)
0593         {
0594             m_albumModel->setDropIndex(QModelIndex());
0595             e->ignore();
0596         }
0597         else
0598         {
0599             m_albumModel->setDropIndex(source);
0600             e->setDropAction(action);
0601             e->accept();
0602         }
0603     }
0604 }
0605 
0606 void AbstractAlbumTreeView::dragLeaveEvent(QDragLeaveEvent* e)
0607 {
0608     QTreeView::dragLeaveEvent(e);
0609 
0610     m_albumModel->setDropIndex(QModelIndex());
0611 }
0612 
0613 void AbstractAlbumTreeView::dropEvent(QDropEvent* e)
0614 {
0615     QTreeView::dropEvent(e);
0616     AlbumModelDragDropHandler* const handler = m_albumModel->dragDropHandler();
0617 
0618     if (handler)
0619     {
0620 
0621 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0622 
0623         const QModelIndex index = indexVisuallyAt(e->position().toPoint());
0624 
0625 #else
0626 
0627         const QModelIndex index = indexVisuallyAt(e->pos());
0628 
0629 #endif
0630 
0631         if (handler->dropEvent(this, e, m_albumFilterModel->mapToSourceAlbumModel(index)))
0632         {
0633             e->accept();
0634         }
0635     }
0636 
0637     m_albumModel->setDropIndex(QModelIndex());
0638 }
0639 
0640 bool AbstractAlbumTreeView::viewportEvent(QEvent* event)
0641 {
0642     return QTreeView::viewportEvent(event);
0643 }
0644 
0645 QList<Album*> AbstractAlbumTreeView::selectedItems()
0646 {
0647     return selectedAlbums<Album>(selectionModel(), m_albumFilterModel);
0648 }
0649 
0650 void AbstractAlbumTreeView::doLoadState()
0651 {
0652     KConfigGroup configGroup = getConfigGroup();
0653 /*
0654     qCDebug(DIGIKAM_GENERAL_LOG) << "Loading view state from " << this << configGroup.name() << objectName();
0655 */
0656     // extract the selection from the config
0657 
0658     const QStringList selection = configGroup.readEntry(entryName(d->configSelectionEntry), QStringList());
0659 /*
0660     qCDebug(DIGIKAM_GENERAL_LOG) << "selection: " << selection;
0661 */
0662     Q_FOREACH (const QString& key, selection)
0663     {
0664         bool validId;
0665         const int id = key.toInt(&validId);
0666 
0667         if (validId)
0668         {
0669             d->statesByAlbumId[id].selected = true;
0670         }
0671     }
0672 
0673     // extract expansion state from config
0674 
0675     const QStringList expansion = configGroup.readEntry(entryName(d->configExpansionEntry), QStringList());
0676 /*
0677     qCDebug(DIGIKAM_GENERAL_LOG) << "expansion: " << expansion;
0678 */
0679     // If no expansion was done, at least expand the root albums
0680 
0681     if (expansion.isEmpty())
0682     {
0683         QList<AlbumRootInfo> roots = CoreDbAccess().db()->getAlbumRoots();
0684 
0685         Q_FOREACH (const AlbumRootInfo& info, roots)
0686         {
0687             int albumId = CoreDbAccess().db()->getAlbumForPath(info.id, QLatin1String("/"), false);
0688 
0689             if (albumId != -1)
0690             {
0691                 d->statesByAlbumId[albumId].expanded = true;
0692             }
0693         }
0694     }
0695     else
0696     {
0697         Q_FOREACH (const QString& key, expansion)
0698         {
0699             bool validId;
0700             const int id = key.toInt(&validId);
0701 
0702             if (validId)
0703             {
0704                 d->statesByAlbumId[id].expanded = true;
0705             }
0706         }
0707     }
0708 
0709     // extract current index from config
0710 
0711     const QString key = configGroup.readEntry(entryName(d->configCurrentIndexEntry), QString());
0712 /*
0713     qCDebug(DIGIKAM_GENERAL_LOG) << "currentIndex: " << key;
0714 */
0715     bool validId;
0716     const int id      = key.toInt(&validId);
0717 
0718     if (validId)
0719     {
0720         d->statesByAlbumId[id].currentIndex = true;
0721     }
0722 
0723 /*
0724     for (QMap<int, Digikam::State>::iterator it = d->statesByAlbumId.begin() ; it
0725         != d->statesByAlbumId.end() ; ++it)
0726     {
0727         qCDebug(DIGIKAM_GENERAL_LOG) << "id = " << it.key() << ": recovered state (selected = "
0728         << it.value().selected << ", expanded = "
0729         << it.value().expanded << ", currentIndex = "
0730         << it.value().currentIndex << ")";
0731     }
0732 */
0733 
0734     // initial restore run, for everything already loaded
0735 /*
0736     qCDebug(DIGIKAM_GENERAL_LOG) << "initial restore run with " << model()->rowCount() << " rows";
0737 */
0738     restoreStateForHierarchy(QModelIndex(), d->statesByAlbumId);
0739 
0740     // also restore the sorting order
0741 
0742     sortByColumn(configGroup.readEntry(entryName(d->configSortColumnEntry), 0),
0743                  (Qt::SortOrder) configGroup.readEntry(entryName(d->configSortOrderEntry), (int)Qt::AscendingOrder));
0744 
0745     // use a timer to scroll to the first possible selected album
0746 
0747     QTimer::singleShot(200, this, SLOT(scrollToSelectedAlbum()));
0748 }
0749 
0750 void AbstractAlbumTreeView::restoreStateForHierarchy(const QModelIndex& index, const QMap<int, Digikam::State>& stateStore)
0751 {
0752     restoreState(index, stateStore);
0753 
0754     // do a recursive call of the state restoration
0755 
0756     for (int i = 0 ; i < model()->rowCount(index) ; ++i)
0757     {
0758         const QModelIndex child = model()->index(i, 0, index);
0759         restoreStateForHierarchy(child, stateStore);
0760     }
0761 }
0762 
0763 void AbstractAlbumTreeView::restoreState(const QModelIndex& index, const QMap<int, Digikam::State>& stateStore)
0764 {
0765     Album* const album = albumFilterModel()->albumForIndex(index);
0766 
0767     if (album && stateStore.contains(album->id()))
0768     {
0769 
0770         Digikam::State state = stateStore.value(album->id());
0771 
0772 /*
0773         qCDebug(DIGIKAM_GENERAL_LOG) << "Trying to restore state of album " << album->title() << "(" <<album->id() << ")"
0774                  << ": state(selected = " << state.selected
0775                  << ", expanded = " << state.expanded
0776                  << ", currentIndex = " << state.currentIndex << ")" << this;
0777 */
0778         // Block signals to prevent that the searches started when the last
0779         // selected index is restored when loading the GUI
0780 
0781         selectionModel()->blockSignals(true);
0782 
0783         if (state.selected)
0784         {
0785 /*
0786             qCDebug(DIGIKAM_GENERAL_LOG) << "Selecting" << album->title();
0787 */
0788             selectionModel()->select(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
0789         }
0790 
0791         // Restore expansion state but ensure that the root album is always expanded
0792 
0793         if (!album->isRoot())
0794         {
0795             setExpanded(index, state.expanded);
0796         }
0797         else
0798         {
0799             setExpanded(index, true);
0800         }
0801 
0802         // Restore the current index
0803 
0804         if (state.currentIndex)
0805         {
0806 /*
0807             qCDebug(DIGIKAM_GENERAL_LOG) << "Setting current index" << album->title() << "(" << album->id() << ")";
0808 */
0809             selectionModel()->setCurrentIndex(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
0810         }
0811 
0812         selectionModel()->blockSignals(false);
0813     }
0814 }
0815 
0816 void AbstractAlbumTreeView::rowsInserted(const QModelIndex& parent, int start, int end)
0817 {
0818     QTreeView::rowsInserted(parent, start, end);
0819 
0820     if (!d->statesByAlbumId.isEmpty())
0821     {
0822 /*
0823         qCDebug(DIGIKAM_GENERAL_LOG) << "slot rowInserted called with index = " << index
0824                                      << ", start = " << start << ", end = " << end
0825                                      << "remaining ids" << d->statesByAlbumId.keys();
0826 */
0827         // Restore state for parent a second time - expansion can only be restored if there are children
0828 
0829         restoreState(parent, d->statesByAlbumId);
0830 
0831         for (int i = start ; i <= end ; ++i)
0832         {
0833             const QModelIndex child = model()->index(i, 0, parent);
0834             restoreState(child, d->statesByAlbumId);
0835         }
0836     }
0837 }
0838 
0839 void AbstractAlbumTreeView::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
0840 {
0841     QTreeView::rowsAboutToBeRemoved(parent, start, end);
0842 
0843     // Clean up map if album id is reused for a new album
0844 
0845     if (!d->statesByAlbumId.isEmpty())
0846     {
0847         for (int i = start ; i <= end ; ++i)
0848         {
0849             const QModelIndex child = model()->index(i, 0, parent);
0850             Album* const album      = albumModel()->albumForIndex(child);
0851 
0852             if (album)
0853             {
0854                 d->statesByAlbumId.remove(album->id());
0855             }
0856         }
0857     }
0858 }
0859 
0860 void AbstractAlbumTreeView::adaptColumnsToContent()
0861 {
0862     resizeColumnToContents(0);
0863 }
0864 
0865 void AbstractAlbumTreeView::scrollToSelectedAlbum()
0866 {
0867     const QModelIndexList selected = selectedIndexes();
0868 
0869     if (!selected.isEmpty())
0870     {
0871         scrollTo(selected.first(), PositionAtCenter);
0872         horizontalScrollBar()->setValue(0);
0873     }
0874 }
0875 
0876 void AbstractAlbumTreeView::expandEverything(const QModelIndex& index)
0877 {
0878     for (int row = 0 ; row < albumFilterModel()->rowCount(index) ; ++row)
0879     {
0880         const QModelIndex rowIndex = albumFilterModel()->index(row, 0, index);
0881         expand(rowIndex);
0882         expandEverything(rowIndex);
0883     }
0884 }
0885 
0886 void AbstractAlbumTreeView::slotExpandNode()
0887 {
0888     QItemSelectionModel* const model = selectionModel();
0889     QModelIndexList selected         = model->selectedIndexes();
0890 
0891     Q_FOREACH (const QModelIndex& index, selected)
0892     {
0893         expandRecursively(index);
0894     }
0895 }
0896 
0897 void AbstractAlbumTreeView::slotCollapseNode()
0898 {
0899     QItemSelectionModel* const model = selectionModel();
0900     QModelIndexList selected         = model->selectedIndexes();
0901 
0902     QQueue<QModelIndex> greyNodes;
0903 
0904     Q_FOREACH (const QModelIndex& index, selected)
0905     {
0906         greyNodes.append(index);
0907         collapse(index);
0908     }
0909 
0910     while (!greyNodes.isEmpty())
0911     {
0912         QModelIndex current = greyNodes.dequeue();
0913 
0914         if (!current.isValid())
0915         {
0916             continue;
0917         }
0918 
0919         int it              = 0;
0920         QModelIndex child   = current.model()->index(it++, 0, current);
0921 
0922         while (child.isValid())
0923         {
0924             collapse(child);
0925             greyNodes.enqueue(child);
0926             child = current.model()->index(it++, 0, current);
0927         }
0928     }
0929 }
0930 
0931 void AbstractAlbumTreeView::slotCollapseAllNodes()
0932 {
0933     QQueue<QModelIndex> greyNodes;
0934     greyNodes.append(m_albumFilterModel->rootAlbumIndex());
0935 
0936     while (!greyNodes.isEmpty())
0937     {
0938         QModelIndex current = greyNodes.dequeue();
0939 
0940         if (!current.isValid())
0941         {
0942             continue;
0943         }
0944 
0945         int it              = 0;
0946         QModelIndex child   = current.model()->index(it++, 0, current);
0947 
0948         while (child.isValid())
0949         {
0950             collapse(child);
0951             greyNodes.enqueue(child);
0952             child = current.model()->index(it++, 0, current);
0953         }
0954     }
0955 }
0956 
0957 void AbstractAlbumTreeView::adaptColumnsOnDataChange(const QModelIndex& topLeft, const QModelIndex& bottomRight)
0958 {
0959     Q_UNUSED(topLeft);
0960     Q_UNUSED(bottomRight);
0961 
0962     if (!d->resizeColumnsTimer->isActive())
0963     {
0964         d->resizeColumnsTimer->start();
0965     }
0966 }
0967 
0968 void AbstractAlbumTreeView::adaptColumnsOnRowChange(const QModelIndex& parent, int start, int end)
0969 {
0970     Q_UNUSED(parent);
0971     Q_UNUSED(start);
0972     Q_UNUSED(end);
0973 
0974     if (!d->resizeColumnsTimer->isActive())
0975     {
0976         d->resizeColumnsTimer->start();
0977     }
0978 }
0979 
0980 void AbstractAlbumTreeView::adaptColumnsOnLayoutChange()
0981 {
0982     if (!d->resizeColumnsTimer->isActive())
0983     {
0984         d->resizeColumnsTimer->start();
0985     }
0986 }
0987 
0988 void AbstractAlbumTreeView::doSaveState()
0989 {
0990     KConfigGroup configGroup = getConfigGroup();
0991 
0992     QList<int> selection, expansion;
0993 
0994     for (int i = 0 ; i < model()->rowCount() ; ++i)
0995     {
0996         const QModelIndex index = model()->index(i, 0);
0997         saveStateRecursive(index, selection, expansion);
0998     }
0999 
1000     Album* const selectedAlbum = albumFilterModel()->albumForIndex(selectionModel()->currentIndex());
1001     QString currentIndex;
1002 
1003     if (selectedAlbum)
1004     {
1005         currentIndex = QString::number(selectedAlbum->id());
1006     }
1007 
1008     configGroup.writeEntry(entryName(d->configSelectionEntry),    selection);
1009     configGroup.writeEntry(entryName(d->configExpansionEntry),    expansion);
1010     configGroup.writeEntry(entryName(d->configCurrentIndexEntry), currentIndex);
1011     configGroup.writeEntry(entryName(d->configSortColumnEntry),   albumFilterModel()->sortColumn());
1012 
1013     // A dummy way to force the tree view to resort if the album sort role changed
1014 
1015     if (ApplicationSettings::instance()->getAlbumSortChanged())
1016     {
1017         if (int(albumFilterModel()->sortOrder()) == 0)
1018         {
1019             configGroup.writeEntry(entryName(d->configSortOrderEntry), 1);
1020         }
1021         else
1022         {
1023             configGroup.writeEntry(entryName(d->configSortOrderEntry), 0);
1024         }
1025     }
1026     else
1027     {
1028         configGroup.writeEntry(entryName(d->configSortOrderEntry), int(albumFilterModel()->sortOrder()));
1029     }
1030 }
1031 
1032 void AbstractAlbumTreeView::saveStateRecursive(const QModelIndex& index, QList<int>& selection, QList<int>& expansion)
1033 {
1034     Album* const album = albumFilterModel()->albumForIndex(index);
1035 
1036     if (album)
1037     {
1038         const int id = album->id();
1039 
1040         if (selectionModel()->isSelected(index))
1041         {
1042             selection.append(id);
1043         }
1044 
1045         if (isExpanded(index))
1046         {
1047             expansion.append(id);
1048         }
1049     }
1050 
1051     for (int i = 0 ; i < model()->rowCount(index) ; ++i)
1052     {
1053         const QModelIndex child = model()->index(i, 0, index);
1054         saveStateRecursive(child, selection, expansion);
1055     }
1056 }
1057 
1058 void AbstractAlbumTreeView::setEnableContextMenu(const bool enable)
1059 {
1060     d->enableContextMenu = enable;
1061 }
1062 
1063 bool AbstractAlbumTreeView::showContextMenuAt(QContextMenuEvent* event, Album* albumForEvent)
1064 {
1065     Q_UNUSED(event);
1066 
1067     return albumForEvent;
1068 }
1069 
1070 void AbstractAlbumTreeView::setContextMenuIcon(const QPixmap& pixmap)
1071 {
1072     d->contextMenuIcon = pixmap;
1073 }
1074 
1075 void AbstractAlbumTreeView::setContextMenuTitle(const QString& title)
1076 {
1077     d->contextMenuTitle = title;
1078 }
1079 
1080 QPixmap AbstractAlbumTreeView::contextMenuIcon() const
1081 {
1082     return d->contextMenuIcon;
1083 }
1084 
1085 QString AbstractAlbumTreeView::contextMenuTitle() const
1086 {
1087     return d->contextMenuTitle;
1088 }
1089 
1090 void AbstractAlbumTreeView::addContextMenuElement(ContextMenuElement* element)
1091 {
1092     d->contextMenuElements << element;
1093 }
1094 
1095 void AbstractAlbumTreeView::removeContextMenuElement(ContextMenuElement* element)
1096 {
1097     d->contextMenuElements.removeAll(element);
1098 }
1099 
1100 QList<AbstractAlbumTreeView::ContextMenuElement*> AbstractAlbumTreeView::contextMenuElements() const
1101 {
1102     return d->contextMenuElements;
1103 }
1104 
1105 void AbstractAlbumTreeView::contextMenuEvent(QContextMenuEvent* event)
1106 {
1107     if (!d->enableContextMenu)
1108     {
1109         return;
1110     }
1111 
1112     Album* const album = albumFilterModel()->albumForIndex(indexAt(event->pos()));
1113 
1114     if (!album)
1115     {
1116         return;
1117     }
1118 
1119     if (album->isTrashAlbum())
1120     {
1121         QMenu* const trashAlbumMenu = new QMenu(this);
1122         ContextMenuHelper cmhelper(trashAlbumMenu);
1123         addCustomContextMenuActions(cmhelper, album);
1124 
1125         QAction* emptyTrashAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")),
1126                                         i18n("Empty Trash"), this);
1127 
1128         connect(emptyTrashAction, SIGNAL(triggered()),
1129                 AlbumManager::instance(), SIGNAL(signalEmptyTrash()));
1130 
1131         cmhelper.addAction(emptyTrashAction);
1132         AlbumPointer<Album> albumPointer(album);
1133         QAction* const choice = cmhelper.exec(QCursor::pos());
1134         handleCustomContextMenuAction(choice, albumPointer);
1135 
1136         return;
1137     }
1138 
1139     if (!showContextMenuAt(event, album))
1140     {
1141         return;
1142     }
1143 
1144     // switch to the selected album if need
1145 
1146     if (d->selectOnContextMenu)
1147     {
1148         setCurrentAlbums(QList<Album*>() << album);
1149     }
1150 
1151     // --------------------------------------------------------
1152 
1153     QMenu* const popmenu = new QMenu(this);
1154     popmenu->addSection(contextMenuIcon(), contextMenuTitle());
1155     ContextMenuHelper cmhelper(popmenu);
1156 
1157     addCustomContextMenuActions(cmhelper, album);
1158 
1159     Q_FOREACH (ContextMenuElement* const element, d->contextMenuElements)
1160     {
1161         element->addActions(this, cmhelper, album);
1162     }
1163 
1164     AlbumPointer<Album> albumPointer(album);
1165     QAction* const choice = cmhelper.exec(QCursor::pos());
1166     handleCustomContextMenuAction(choice, albumPointer);
1167 }
1168 
1169 void AbstractAlbumTreeView::setSelectOnContextMenu(const bool select)
1170 {
1171     d->selectOnContextMenu = select;
1172 }
1173 
1174 void AbstractAlbumTreeView::addCustomContextMenuActions(ContextMenuHelper& cmh, Album* album)
1175 {
1176     Q_UNUSED(cmh);
1177     Q_UNUSED(album);
1178 }
1179 
1180 void AbstractAlbumTreeView::handleCustomContextMenuAction(QAction* action, const AlbumPointer<Album>& album)
1181 {
1182     Q_UNUSED(action);
1183     Q_UNUSED(album);
1184 }
1185 
1186 void AbstractAlbumTreeView::albumSettingsChanged()
1187 {
1188     setFont(ApplicationSettings::instance()->getTreeViewFont());
1189 
1190     if (d->delegate)
1191     {
1192         d->delegate->updateHeight();
1193     }
1194 }
1195 
1196 void AbstractAlbumTreeView::slotScrollBarValueChanged(int value)
1197 {
1198     if (m_lastScrollBarValue == -1)
1199     {
1200         m_lastScrollBarValue = value;
1201     }
1202 
1203     if ((value == 0) && (m_lastScrollBarValue > 0))
1204     {
1205         horizontalScrollBar()->setValue(m_lastScrollBarValue);
1206     }
1207 }
1208 
1209 void AbstractAlbumTreeView::slotScrollBarActionTriggered(int action)
1210 {
1211     if ((action == QAbstractSlider::SliderMove)        ||
1212         (action == QAbstractSlider::SliderToMinimum)   ||
1213         (action == QAbstractSlider::SliderPageStepSub) ||
1214         (action == QAbstractSlider::SliderSingleStepSub))
1215     {
1216         m_lastScrollBarValue = -1;
1217     }
1218 }
1219 
1220 } // namespace Digikam
1221 
1222 #include "moc_abstractalbumtreeview.cpp"
1223 
1224 #include "moc_abstractalbumtreeview_p.cpp"