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"