File indexing completed on 2025-01-26 03:51:08

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2013-02-12
0007  * Description : Wrapper model for table view
0008  *
0009  * SPDX-FileCopyrightText: 2017-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2013      by Michael G. Hansen <mike at mghansen dot de>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "tableview_model.h"
0017 
0018 // C++ includes
0019 
0020 #include <functional>
0021 #include <valarray>
0022 
0023 // Qt includes
0024 
0025 #include <QTimer>
0026 
0027 // Local includes
0028 
0029 #include "digikam_debug.h"
0030 #include "coredb.h"
0031 #include "coredbaccess.h"
0032 #include "coredbfields.h"
0033 #include "coredbwatch.h"
0034 #include "itemfiltermodel.h"
0035 #include "itemfiltersettings.h"
0036 #include "applicationsettings.h"
0037 #include "iteminfo.h"
0038 #include "tableview_columnfactory.h"
0039 #include "tableview_selection_model_syncer.h"
0040 
0041 #ifdef DIGIKAM_ENABLE_MODELTEST
0042 #   include "../../../tests/modeltest/modeltest.h"
0043 #endif // DIGIKAM_ENABLE_MODELTEST
0044 
0045 #define ASSERT_MODEL(index, modelPointer) if (index.isValid()) { Q_ASSERT(index.model() == modelPointer); }
0046 
0047 namespace Digikam
0048 {
0049 
0050 TableViewModel::Item::Item()
0051   : imageId (0),
0052     parent  (nullptr),
0053     children()
0054 {
0055 }
0056 
0057 TableViewModel::Item::~Item()
0058 {
0059     qDeleteAll(children);
0060 }
0061 
0062 void TableViewModel::Item::addChild(TableViewModel::Item* const newChild)
0063 {
0064     newChild->parent = this;
0065 
0066     children << newChild;
0067 }
0068 
0069 void TableViewModel::Item::insertChild(const int pos, TableViewModel::Item* const newChild)
0070 {
0071     newChild->parent = this;
0072 
0073     children.insert(pos, newChild);
0074 }
0075 
0076 void TableViewModel::Item::takeChild(TableViewModel::Item* const oldChild)
0077 {
0078     children.removeOne(oldChild);
0079 }
0080 
0081 TableViewModel::Item* TableViewModel::Item::findChildWithImageId(const qlonglong searchImageId)
0082 {
0083     if (imageId == searchImageId)
0084     {
0085         return this;
0086     }
0087 
0088     Q_FOREACH (Item* const item, children)
0089     {
0090         Item* const iItem = item->findChildWithImageId(searchImageId);
0091 
0092         if (iItem)
0093         {
0094             return iItem;
0095         }
0096     }
0097 
0098     return nullptr;
0099 }
0100 
0101 // ----------------------------------------------------------------------------------------------
0102 
0103 class Q_DECL_HIDDEN TableViewModel::Private
0104 {
0105 public:
0106 
0107     explicit Private()
0108       : columnObjects      (),
0109         rootItem           (nullptr),
0110         imageFilterSettings(),
0111         sortColumn         (0),
0112         sortOrder          (Qt::AscendingOrder),
0113         sortRequired       (false),
0114         groupingMode       (GroupingShowSubItems),
0115         outdated           (true)
0116     {
0117     }
0118 
0119     QList<TableViewColumn*>     columnObjects;
0120     TableViewModel::Item*       rootItem;
0121     ItemFilterSettings          imageFilterSettings;
0122     int                         sortColumn;
0123     Qt::SortOrder               sortOrder;
0124     bool                        sortRequired;
0125     GroupingMode                groupingMode;
0126     bool                        outdated;
0127 };
0128 
0129 TableViewModel::TableViewModel(TableViewShared* const sharedObject, QObject* const parent)
0130     : QAbstractItemModel(parent),
0131       s                 (sharedObject),
0132       d                 (new Private())
0133 {
0134     d->rootItem            = new Item();
0135     d->imageFilterSettings = s->imageFilterModel->imageFilterSettings();
0136 
0137     connect(s->imageModel, SIGNAL(modelAboutToBeReset()),
0138             this, SLOT(slotSourceModelAboutToBeReset()));
0139 
0140     connect(s->imageModel, SIGNAL(modelReset()),
0141             this, SLOT(slotSourceModelReset()));
0142 
0143     connect(s->imageModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
0144             this, SLOT(slotSourceRowsAboutToBeInserted(QModelIndex,int,int)));
0145 
0146     connect(s->imageModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
0147             this, SLOT(slotSourceRowsInserted(QModelIndex,int,int)));
0148 
0149     connect(s->imageModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
0150             this, SLOT(slotSourceRowsAboutToBeRemoved(QModelIndex,int,int)));
0151 
0152     connect(s->imageModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
0153             this, SLOT(slotSourceRowsRemoved(QModelIndex,int,int)));
0154 
0155     connect(s->imageModel, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)),
0156             this, SLOT(slotSourceRowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)));
0157 
0158     connect(s->imageModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
0159             this, SLOT(slotSourceRowsMoved(QModelIndex,int,int,QModelIndex,int)));
0160 
0161     connect(s->imageModel, SIGNAL(layoutAboutToBeChanged()),
0162             this, SLOT(slotSourceLayoutAboutToBeChanged()));
0163 
0164     connect(s->imageModel, SIGNAL(layoutChanged()),
0165             this, SLOT(slotSourceLayoutChanged()));
0166 
0167     connect(s->imageFilterModel, SIGNAL(filterSettingsChanged(ItemFilterSettings)),
0168             this, SLOT(slotFilterSettingsChanged(ItemFilterSettings)));
0169 
0170     // We do not connect to ItemFilterModel::dataChanged, because we monitor changes directly from the database.
0171 
0172     connect(CoreDbAccess::databaseWatch(), SIGNAL(imageChange(ImageChangeset)),
0173             this, SLOT(slotDatabaseImageChanged(ImageChangeset)), Qt::QueuedConnection);
0174 
0175 #ifdef DIGIKAM_ENABLE_MODELTEST
0176 
0177     new ModelTest(this, this);
0178 
0179 #endif // DIGIKAM_ENABLE_MODELTEST
0180 
0181     // We only have to trigger population of the model if data is in the source model,
0182     // otherwise the source model will tell us about any new data.
0183 
0184     const int itemsInItemModel = s->imageModel->rowCount();
0185 
0186     if (itemsInItemModel > 0)
0187     {
0188         // populate the model once later, not now
0189 
0190         QTimer::singleShot(0, this, SLOT(slotPopulateModelWithNotifications()));
0191     }
0192 }
0193 
0194 TableViewModel::~TableViewModel()
0195 {
0196     delete d->rootItem;
0197 }
0198 
0199 int TableViewModel::columnCount(const QModelIndex& i) const
0200 {
0201     if (i.column() > 0)
0202     {
0203         return 0;
0204     }
0205 
0206     return d->columnObjects.count();
0207 }
0208 
0209 QModelIndex TableViewModel::toItemFilterModelIndex(const QModelIndex& i) const
0210 {
0211     Item* const item = itemFromIndex(i);
0212 
0213     if (!item)
0214     {
0215         return QModelIndex();
0216     }
0217 
0218     return s->imageFilterModel->indexForImageId(item->imageId);
0219 }
0220 
0221 QModelIndex TableViewModel::toItemModelIndex(const QModelIndex& i) const
0222 {
0223     Item* const item = itemFromIndex(i);
0224 
0225     if (!item)
0226     {
0227         return QModelIndex();
0228     }
0229 
0230     return s->imageModel->indexForImageId(item->imageId);
0231 }
0232 
0233 QVariant TableViewModel::data(const QModelIndex& i, int role) const
0234 {
0235     Item* const item = itemFromIndex(i);
0236 
0237     if (!item)
0238     {
0239         return QVariant();
0240     }
0241 
0242     const int columnNumber          = i.column();
0243     TableViewColumn* const myColumn = d->columnObjects.at(columnNumber);
0244 
0245     return myColumn->data(item, role);
0246 }
0247 
0248 QModelIndex TableViewModel::index(int row, int column, const QModelIndex& parent) const
0249 {
0250     Item* parentItem = d->rootItem;
0251 
0252     if (parent.isValid())
0253     {
0254         if (parent.column() > 0)
0255         {
0256             // only column 0 can have children, the other columns can not
0257 
0258             return QModelIndex();
0259         }
0260 
0261         parentItem = itemFromIndex(parent);
0262     }
0263 
0264     // test for valid row/column values
0265 
0266     if (
0267         (row < 0)                            ||
0268         (column < 0)                         ||
0269         (column >= d->columnObjects.count()) ||
0270         (row >= parentItem->children.count())
0271        )
0272     {
0273         return QModelIndex();
0274     }
0275 
0276     Item* const itemPointer = parentItem->children.at(row);
0277 
0278     return createIndex(row, column, itemPointer);
0279 }
0280 
0281 QModelIndex TableViewModel::parent(const QModelIndex& childIndex) const
0282 {
0283     if (!childIndex.isValid())
0284     {
0285         return QModelIndex();
0286     }
0287 
0288     Item* const childItem  = itemFromIndex(childIndex);
0289     Item* const parentItem = childItem->parent;
0290 
0291     if (parentItem == d->rootItem)
0292     {
0293         return QModelIndex();
0294     }
0295 
0296     Item* const grandParentItem = parentItem->parent;
0297     const int rowIndex          = grandParentItem->children.indexOf(parentItem);
0298 
0299     /// @todo What should be the column number?
0300 
0301     return createIndex(rowIndex, 0, parentItem);
0302 }
0303 
0304 int TableViewModel::rowCount(const QModelIndex& parent) const
0305 {
0306     if (parent.column() > 0)
0307     {
0308         return 0;
0309     }
0310 
0311     Item* parentItem = d->rootItem;
0312 
0313     if (parent.isValid())
0314     {
0315         parentItem = itemFromIndex(parent);
0316     }
0317 
0318     return parentItem->children.count();
0319 }
0320 
0321 QVariant TableViewModel::headerData(int section, Qt::Orientation orientation, int role) const
0322 {
0323     // test for valid input ranges
0324 
0325     if ((section < 0) || (section >= d->columnObjects.count()))
0326     {
0327         return QVariant();
0328     }
0329 
0330     if ((orientation != Qt::Horizontal) || (role != Qt::DisplayRole))
0331     {
0332         return QVariant();
0333     }
0334 
0335     TableViewColumn* const myColumn = d->columnObjects.at(section);
0336 
0337     return myColumn->getTitle();
0338 }
0339 
0340 void TableViewModel::addColumnAt(const TableViewColumnDescription& description, const int targetColumn)
0341 {
0342     /// @todo take additional configuration data of the column into account
0343 
0344     TableViewColumnConfiguration newConfiguration = description.toConfiguration();
0345 
0346     addColumnAt(newConfiguration, targetColumn);
0347 }
0348 
0349 void TableViewModel::addColumnAt(const TableViewColumnConfiguration& configuration, const int targetColumn)
0350 {
0351     TableViewColumn* const newColumn = s->columnFactory->getColumn(configuration);
0352 
0353     if (!newColumn)
0354     {
0355         return;
0356     }
0357 
0358     int newColumnIndex = targetColumn;
0359 
0360     if (targetColumn < 0)
0361     {
0362         // a negative column index means "append after last column"
0363 
0364         newColumnIndex = d->columnObjects.count();
0365     }
0366 
0367     beginInsertColumns(QModelIndex(), newColumnIndex, newColumnIndex);
0368 
0369     if (newColumnIndex >= d->columnObjects.count())
0370     {
0371         d->columnObjects.append(newColumn);
0372     }
0373     else
0374     {
0375         d->columnObjects.insert(newColumnIndex, newColumn);
0376     }
0377 
0378     endInsertColumns();
0379 
0380     connect(newColumn, SIGNAL(signalDataChanged(qlonglong)),
0381             this, SLOT(slotColumnDataChanged(qlonglong)));
0382 
0383     connect(newColumn, SIGNAL(signalAllDataChanged()),
0384             this, SLOT(slotColumnAllDataChanged()));
0385 
0386     const QModelIndex changedIndexTopLeft     = index(0, 0, QModelIndex());
0387     const QModelIndex changedIndexBottomRight = index(rowCount(QModelIndex())-1,
0388                                                       columnCount(QModelIndex())-1, QModelIndex());
0389 
0390     Q_EMIT dataChanged(changedIndexTopLeft, changedIndexBottomRight);
0391 }
0392 
0393 void TableViewModel::slotColumnDataChanged(const qlonglong imageId)
0394 {
0395     Item* const item = itemFromImageId(imageId);
0396 
0397     if (!item)
0398     {
0399         return;
0400     }
0401 
0402     Item* const parentItem              = item->parent;
0403 
0404     /// @todo This is a waste of time because itemFromImageId already did this search.
0405     ///       We should modify it to also give the row index.
0406 
0407     const int rowIndex                  = parentItem->children.indexOf(item);
0408     const int columnIndex               = columnCount(QModelIndex())-1;
0409     const QModelIndex changedIndexLeft  = index(rowIndex, 0, QModelIndex());
0410     const QModelIndex changedIndexRight = index(rowIndex, columnIndex, QModelIndex());
0411 
0412     Q_EMIT dataChanged(changedIndexLeft, changedIndexRight);
0413 }
0414 
0415 void TableViewModel::slotColumnAllDataChanged()
0416 {
0417     TableViewColumn* const senderColumn = qobject_cast<TableViewColumn*>(sender());
0418 
0419     /// @todo find a faster way to find the column number
0420 
0421     const int iColumn                   = d->columnObjects.indexOf(senderColumn);
0422 
0423     if (iColumn < 0)
0424     {
0425         return;
0426     }
0427 
0428     const QModelIndex changedIndexTopLeft     = index(0, iColumn, QModelIndex());
0429     const QModelIndex changedIndexBottomRight = index(rowCount(QModelIndex())-1, iColumn, QModelIndex());
0430 
0431     Q_EMIT dataChanged(changedIndexTopLeft, changedIndexBottomRight);
0432 }
0433 
0434 void TableViewModel::removeColumnAt(const int columnIndex)
0435 {
0436     beginRemoveColumns(QModelIndex(), columnIndex, columnIndex);
0437     TableViewColumn* const removedColumn = d->columnObjects.takeAt(columnIndex);
0438     endRemoveColumns();
0439 
0440     delete removedColumn;
0441 
0442     const QModelIndex changedIndexTopLeft     = index(0, 0, QModelIndex());
0443     const QModelIndex changedIndexBottomRight = index(rowCount(QModelIndex())-1,
0444                                                       columnCount(QModelIndex())-1, QModelIndex());
0445 
0446     Q_EMIT dataChanged(changedIndexTopLeft, changedIndexBottomRight);
0447 }
0448 
0449 TableViewColumn* TableViewModel::getColumnObject(const int columnIndex)
0450 {
0451     return d->columnObjects.at(columnIndex);
0452 }
0453 
0454 TableViewColumnProfile TableViewModel::getColumnProfile() const
0455 {
0456     TableViewColumnProfile profile;
0457 
0458     for (int i = 0 ; i < d->columnObjects.count() ; ++i)
0459     {
0460         TableViewColumnConfiguration ic = d->columnObjects.at(i)->getConfiguration();
0461         profile.columnConfigurationList << ic;
0462     }
0463 
0464     return profile;
0465 }
0466 
0467 void TableViewModel::loadColumnProfile(const TableViewColumnProfile& columnProfile)
0468 {
0469     while (!d->columnObjects.isEmpty())
0470     {
0471         removeColumnAt(0);
0472     }
0473 
0474     /// @todo disable updating of the model while this happens
0475 
0476     for (int i = 0 ; i < columnProfile.columnConfigurationList.count() ; ++i)
0477     {
0478         addColumnAt(columnProfile.columnConfigurationList.at(i), -1);
0479     }
0480 }
0481 
0482 void TableViewModel::slotSourceModelAboutToBeReset()
0483 {
0484     if (!s->isActive)
0485     {
0486         slotClearModel(true);
0487         return;
0488     }
0489 
0490     // the source model is about to be reset. Propagate that change:
0491 
0492     beginResetModel();
0493 }
0494 
0495 void TableViewModel::slotSourceModelReset()
0496 {
0497     if (!s->isActive)
0498     {
0499         return;
0500     }
0501 
0502     // the source model is done resetting.
0503 
0504     slotPopulateModel(false);
0505     endResetModel();
0506 }
0507 
0508 void TableViewModel::slotSourceRowsAboutToBeInserted(const QModelIndex& parent, int start, int end)
0509 {
0510     Q_UNUSED(parent)
0511     Q_UNUSED(start)
0512     Q_UNUSED(end)
0513 }
0514 
0515 void TableViewModel::slotSourceRowsInserted(const QModelIndex& parent, int start, int end)
0516 {
0517     if (!s->isActive)
0518     {
0519         slotClearModel(true);
0520         return;
0521     }
0522 
0523     for (int i = start ; i <= end ; ++i)
0524     {
0525         const QModelIndex sourceIndex = s->imageModel->index(i, 0, parent);
0526 
0527         addSourceModelIndex(sourceIndex, true);
0528     }
0529 }
0530 
0531 void TableViewModel::slotSourceRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
0532 {
0533     if (!s->isActive)
0534     {
0535         slotClearModel(true);
0536         return;
0537     }
0538 
0539     bool needToResort = false;
0540 
0541     for (int i = start ; i <= end ; ++i)
0542     {
0543         const QModelIndex imageModelIndex = s->imageModel->index(i, 0, parent);
0544         const qlonglong imageId           = s->imageModel->imageId(imageModelIndex);
0545         const QModelIndex tableViewIndex  = indexFromImageId(imageId, 0);
0546 
0547         if (!tableViewIndex.isValid())
0548         {
0549             continue;
0550         }
0551 
0552         Item* const item                  = itemFromIndex(tableViewIndex);
0553 
0554         if (!item)
0555         {
0556             continue;
0557         }
0558 
0559         beginRemoveRows(tableViewIndex.parent(), tableViewIndex.row(), tableViewIndex.row());
0560         item->parent->takeChild(item);
0561 
0562         // move child items to parent item
0563 
0564         while (!item->children.isEmpty())
0565         {
0566             item->parent->addChild(item->children.takeFirst());
0567             needToResort = true;
0568         }
0569 
0570         // child items will be deleted when item is deleted
0571 
0572         delete item;
0573         endRemoveRows();
0574     }
0575 
0576     if (needToResort)
0577     {
0578         scheduleResort();
0579     }
0580 }
0581 
0582 void TableViewModel::slotSourceRowsRemoved(const QModelIndex& parent, int start, int end)
0583 {
0584     /// @todo Do we need to do anything here?
0585 
0586     Q_UNUSED(parent)
0587     Q_UNUSED(start)
0588     Q_UNUSED(end)
0589 }
0590 
0591 void TableViewModel::slotSourceRowsAboutToBeMoved(const QModelIndex& sourceParent, int sourceStart, int sourceEnd,
0592                                                   const QModelIndex& destinationParent, int destinationRow)
0593 {
0594 /*
0595     beginMoveRows(sourceParent, sourceStart, sourceEnd, destinationParent, destinationRow);
0596 */
0597     /// @todo For our items, moving stuff around does not matter --> remove this slot
0598 
0599     Q_UNUSED(sourceParent)
0600     Q_UNUSED(sourceStart)
0601     Q_UNUSED(sourceEnd)
0602     Q_UNUSED(destinationParent)
0603     Q_UNUSED(destinationRow)
0604 }
0605 
0606 void TableViewModel::slotSourceRowsMoved(const QModelIndex& sourceParent, int sourceStart, int sourceEnd,
0607                                          const QModelIndex& destinationParent, int destinationRow)
0608 {
0609     /// @todo For our items, moving stuff around does not matter --> remove this slot
0610 
0611     Q_UNUSED(sourceParent)
0612     Q_UNUSED(sourceStart)
0613     Q_UNUSED(sourceEnd)
0614     Q_UNUSED(destinationParent)
0615     Q_UNUSED(destinationRow)
0616 /*
0617     endMoveRows();
0618 */
0619 }
0620 
0621 void TableViewModel::slotSourceLayoutAboutToBeChanged()
0622 {
0623     if (!s->isActive)
0624     {
0625         slotClearModel(true);
0626         return;
0627     }
0628 
0629     /// @todo Emitting layoutAboutToBeChanged and layoutChanged is tricky,
0630     ///       because we do not know what will change.
0631     ///       It looks like ItemFilterModel emits layoutAboutToBeChanged and layoutChanged
0632     ///       even when the resulting dataset will be empty, and ModelTest does not like that.
0633     ///       For now, the easiest workaround is resetting the model
0634 /*
0635     Q_EMIT layoutAboutToBeChanged();
0636 */
0637     beginResetModel();
0638 }
0639 
0640 void TableViewModel::slotSourceLayoutChanged()
0641 {
0642     if (!s->isActive)
0643     {
0644         return;
0645     }
0646 
0647     /// @todo See note in TableViewModel#slotSourceLayoutAboutToBeChanged
0648 
0649     slotPopulateModel(false);
0650 
0651     endResetModel();
0652 }
0653 
0654 void TableViewModel::slotDatabaseImageChanged(const ImageChangeset& imageChangeset)
0655 {
0656     if (!s->isActive)
0657     {
0658         slotClearModel(true);
0659         return;
0660     }
0661 /*
0662     const DatabaseFields::Set changes = imageChangeset.changes();
0663 */
0664     /// @todo Decide which changes are relevant here or
0665     ///       let the TableViewColumn object decide which are relevant
0666     /// @todo Re-population of the model is also triggered, thus making this
0667     ///       check irrelevant. Needs to be fixed.
0668     /// @todo If the user has never set which column should define the sorting,
0669     ///       the sortColumn is invalid. We should set a useful default.
0670 
0671     bool needToResort = false;
0672 
0673     if ((d->sortColumn >= 0) && (d->sortColumn < d->columnObjects.count()))
0674     {
0675         TableViewColumn* const sortColumnObject = d->columnObjects.at(d->sortColumn);
0676         needToResort                            = sortColumnObject->columnAffectedByChangeset(imageChangeset);
0677     }
0678 
0679     Q_FOREACH (const qlonglong& id, imageChangeset.ids())
0680     {
0681         // first clear the item's cached values
0682         /// @todo Clear only the fields which were changed
0683 
0684         Item* const item = itemFromImageId(id);
0685 
0686         if (!item)
0687         {
0688             // Item is not in this model. If it is in the ItemModel,
0689             // it has been filtered out and we have to re-check the filtering.
0690 
0691             const QModelIndex& imageModelIndex = s->imageModel->indexForImageId(id);
0692 
0693             if (!imageModelIndex.isValid())
0694             {
0695                 continue;
0696             }
0697 
0698             const ItemInfo imageInfo           = s->imageModel->imageInfo(imageModelIndex);
0699 
0700             if (d->imageFilterSettings.matches(imageInfo))
0701             {
0702                 // need to add the item
0703 
0704                 addSourceModelIndex(imageModelIndex, true);
0705             }
0706 
0707             continue;
0708         }
0709 
0710         // Re-check filtering for this item.
0711 
0712         const QModelIndex changedIndexTopLeft = indexFromImageId(id, 0);
0713 
0714         if (!changedIndexTopLeft.isValid())
0715         {
0716             continue;
0717         }
0718 
0719         const ItemInfo myItemInfo = imageInfo(changedIndexTopLeft);
0720 
0721         if (!d->imageFilterSettings.matches(myItemInfo))
0722         {
0723             // Filter does not match, remove the item.
0724 
0725             beginRemoveRows(changedIndexTopLeft.parent(), changedIndexTopLeft.row(), changedIndexTopLeft.row());
0726             item->parent->takeChild(item);
0727 
0728             delete item;
0729             endRemoveRows();
0730 
0731             continue;
0732         }
0733 
0734         /// @todo Re-check grouping for this item
0735 
0736         // only update now if we do not resort later anyway
0737 
0738         if (!needToResort)
0739         {
0740             const QModelIndex changedIndexBottomRight = index(changedIndexTopLeft.row(),
0741                                                               columnCount(changedIndexTopLeft.parent())-1,
0742                                                               changedIndexTopLeft.parent());
0743 
0744             if (changedIndexBottomRight.isValid())
0745             {
0746                 Q_EMIT dataChanged(changedIndexTopLeft, changedIndexBottomRight);
0747             }
0748         }
0749     }
0750 
0751     if (needToResort)
0752     {
0753         scheduleResort();
0754     }
0755 }
0756 
0757 Qt::ItemFlags TableViewModel::flags(const QModelIndex& index) const
0758 {
0759     const Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
0760 
0761     /// @todo Handle read-only files etc. which can not be moved
0762 
0763     if (index.isValid())
0764     {
0765         return (Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags);
0766     }
0767 
0768     return (Qt::ItemIsDropEnabled | defaultFlags);
0769 }
0770 
0771 QList<TableViewColumn*> TableViewModel::getColumnObjects()
0772 {
0773     return d->columnObjects;
0774 }
0775 
0776 void TableViewModel::slotPopulateModelWithNotifications()
0777 {
0778     slotPopulateModel(true);
0779 }
0780 
0781 void TableViewModel::slotClearModel(const bool sendNotifications)
0782 {
0783     if (d->outdated)
0784     {
0785         return;
0786     }
0787 
0788     d->outdated = true;
0789 
0790     if (sendNotifications)
0791     {
0792         beginResetModel();
0793     }
0794 
0795     if (d->rootItem)
0796     {
0797         delete d->rootItem;
0798     }
0799 
0800     d->rootItem = new Item();
0801 
0802     if (sendNotifications)
0803     {
0804         endResetModel();
0805     }
0806 }
0807 
0808 void TableViewModel::slotPopulateModel(const bool sendNotifications)
0809 {
0810     if (!s->isActive)
0811     {
0812         slotClearModel(sendNotifications);
0813         return;
0814     }
0815 
0816     if (sendNotifications)
0817     {
0818         beginResetModel();
0819     }
0820 
0821     if (d->rootItem)
0822     {
0823         delete d->rootItem;
0824     }
0825 
0826     d->rootItem     = new Item();
0827     d->outdated     = false;
0828     d->sortRequired = false;
0829 
0830     const int sourceRowCount = s->imageModel->rowCount(QModelIndex());
0831 
0832     for (int i = 0 ; i < sourceRowCount ; ++i)
0833     {
0834         const QModelIndex sourceModelIndex = s->imageModel->index(i, 0);
0835 
0836         // do not send notifications in addSourceModelIndex, because this function here
0837         // already started a model reset
0838 
0839         addSourceModelIndex(sourceModelIndex, false);
0840     }
0841 
0842     if (sendNotifications)
0843     {
0844         endResetModel();
0845     }
0846 }
0847 
0848 TableViewModel::Item* TableViewModel::createItemFromSourceIndex(const QModelIndex& imageModelIndex)
0849 {
0850     ASSERT_MODEL(imageModelIndex, s->imageModel);
0851 
0852     Item* const item = new Item();
0853     item->imageId    = s->imageModel->imageId(imageModelIndex);
0854 
0855     return item;
0856 }
0857 
0858 void TableViewModel::addSourceModelIndex(const QModelIndex& imageModelIndex, const bool sendNotifications)
0859 {
0860     ASSERT_MODEL(imageModelIndex, s->imageModel);
0861 
0862     const ItemInfo imageInfo = s->imageModel->imageInfo(imageModelIndex);
0863     const bool passedFilter  = d->imageFilterSettings.matches(imageInfo);
0864     const bool allGroupsOpen = ApplicationSettings::instance()->getAllGroupsOpen();
0865 
0866     if (!passedFilter)
0867     {
0868         return;
0869     }
0870 
0871     /// @todo Implement Grouping and sorting
0872 
0873     Item* const parentItem = d->rootItem;
0874 
0875     if (!allGroupsOpen && imageInfo.isGrouped())
0876     {
0877         switch (d->groupingMode)
0878         {
0879             case GroupingHideGrouped:
0880 
0881                 // we do not show grouped items at all
0882 
0883                 return;
0884 
0885             case GroupingIgnoreGrouping:
0886 
0887                 // nothing to do, we just add it to the root item
0888 
0889                 break;
0890 
0891             case GroupingShowSubItems:
0892 
0893                 // we do not add this subitem, because it has been automatically added to the group leader
0894 
0895                 return;
0896         }
0897     }
0898 
0899     Item* const item = createItemFromSourceIndex(imageModelIndex);
0900 
0901     // Normally we do the sorting of items here on insertion.
0902     // However, if the sorting is currently outdated, we just
0903     // append the items because the model will be resorted later.
0904 
0905     int newRowIndex  = parentItem->children.count();
0906 
0907     if (!d->sortRequired)
0908     {
0909         newRowIndex = findChildSortedPosition(parentItem, item);
0910     }
0911 
0912     if (sendNotifications)
0913     {
0914         const QModelIndex parentIndex = itemIndex(parentItem);
0915         beginInsertRows(parentIndex, newRowIndex, newRowIndex);
0916     }
0917 
0918     parentItem->insertChild(newRowIndex, item);
0919 
0920     if (sendNotifications)
0921     {
0922         endInsertRows();
0923     }
0924 
0925     if (!allGroupsOpen && (d->groupingMode == GroupingShowSubItems) && imageInfo.hasGroupedImages())
0926     {
0927         // the item was a group leader, add its subitems
0928 
0929         const QList<ItemInfo> groupedImages = imageInfo.groupedImages();
0930 
0931         if (sendNotifications)
0932         {
0933             const QModelIndex groupLeaderIndex = itemIndex(item);
0934             beginInsertRows(groupLeaderIndex, 0, groupedImages.count()-1);
0935         }
0936 
0937         Q_FOREACH (const ItemInfo& groupedInfo, groupedImages)
0938         {
0939             /// @todo Grouped items are currently not filtered. Should they?
0940 
0941             Item* const groupedItem = new Item();
0942             groupedItem->imageId    = groupedInfo.id();
0943 
0944             // Normally we do the sorting of items here on insertion.
0945             // However, if the sorting is currently outdated, we just
0946             // append the items because the model will be resorted later.
0947 
0948             int index = item->children.count();
0949 
0950             if (!d->sortRequired)
0951             {
0952                 index = findChildSortedPosition(item, groupedItem);
0953             }
0954 
0955             item->insertChild(index, groupedItem);
0956         }
0957 
0958         if (sendNotifications)
0959         {
0960             endInsertRows();
0961         }
0962     }
0963 }
0964 
0965 TableViewModel::Item* TableViewModel::itemFromImageId(const qlonglong imageId) const
0966 {
0967     return d->rootItem->findChildWithImageId(imageId);
0968 }
0969 
0970 TableViewModel::Item* TableViewModel::itemFromIndex(const QModelIndex& i) const
0971 {
0972     if (!i.isValid())
0973     {
0974         return nullptr;
0975     }
0976 
0977     Q_ASSERT(i.model() == this);
0978 
0979     Item* const item = static_cast<Item*>(i.internalPointer());
0980 
0981     return item;
0982 }
0983 
0984 QModelIndex TableViewModel::fromItemFilterModelIndex(const QModelIndex& imageFilterModelIndex)
0985 {
0986     ASSERT_MODEL(imageFilterModelIndex, s->imageFilterModel);
0987 
0988     const qlonglong imageId = s->imageFilterModel->imageId(imageFilterModelIndex);
0989 
0990     if (!imageId)
0991     {
0992         return QModelIndex();
0993     }
0994 
0995     return indexFromImageId(imageId, 0);
0996 }
0997 
0998 QModelIndex TableViewModel::fromItemModelIndex(const QModelIndex& imageModelIndex)
0999 {
1000     ASSERT_MODEL(imageModelIndex, s->imageModel);
1001 
1002     const qlonglong imageId = s->imageModel->imageId(imageModelIndex);
1003 
1004     if (!imageId)
1005     {
1006         return QModelIndex();
1007     }
1008 
1009     return indexFromImageId(imageId, 0);
1010 }
1011 
1012 ItemInfo TableViewModel::infoFromItem(TableViewModel::Item* const item) const
1013 {
1014     /// @todo Is there a way to do it without first looking up the index in the ItemModel?
1015 
1016     const QModelIndex imageModelIndex = s->imageModel->indexForImageId(item->imageId);
1017     const ItemInfo info               = s->imageModel->imageInfo(imageModelIndex);
1018 
1019     return info;
1020 }
1021 
1022 ItemInfoList TableViewModel::infosFromItems(const QList<TableViewModel::Item*>& items) const
1023 {
1024     ItemInfoList infos;
1025 
1026     Q_FOREACH (TableViewModel::Item* const item, items)
1027     {
1028         infos << infoFromItem(item);
1029     }
1030 
1031     return infos;
1032 }
1033 
1034 TableViewModel::DatabaseFieldsHashRaw TableViewModel::itemDatabaseFieldsRaw(TableViewModel::Item* const item,
1035                                                                             const DatabaseFields::Set& requestedSet)
1036 {
1037     const ItemInfo itemItemInfo = infoFromItem(item);
1038 
1039     return itemItemInfo.getDatabaseFieldsRaw(requestedSet);
1040 }
1041 
1042 QVariant TableViewModel::itemDatabaseFieldRaw(TableViewModel::Item* const item,
1043                                               const DatabaseFields::Set& requestedField)
1044 {
1045     const TableViewModel::DatabaseFieldsHashRaw rawHash = itemDatabaseFieldsRaw(item, requestedField);
1046 
1047     if (requestedField.hasFieldsFromImageMetadata())
1048     {
1049         const DatabaseFields::ImageMetadata requestedFieldFlag = requestedField;
1050         const QVariant value                                   = rawHash.value(requestedFieldFlag);
1051 
1052         return value;
1053     }
1054 
1055     if (requestedField.hasFieldsFromVideoMetadata())
1056     {
1057         const DatabaseFields::VideoMetadata requestedFieldFlag = requestedField;
1058         const QVariant value                                   = rawHash.value(requestedFieldFlag);
1059 
1060         return value;
1061     }
1062 
1063     return QVariant();
1064 }
1065 
1066 QModelIndex TableViewModel::indexFromImageId(const qlonglong imageId, const int columnIndex) const
1067 {
1068     Item* const item = itemFromImageId(imageId);
1069 
1070     if (!item)
1071     {
1072         return QModelIndex();
1073     }
1074 
1075     Item* const parentItem = item->parent;
1076 
1077     /// @todo This is a waste of time because itemFromImageId already did this search.
1078     ///       We should modify it to also give the row index.
1079 
1080     const int rowIndex     = parentItem->children.indexOf(item);
1081 
1082     return createIndex(rowIndex, columnIndex, item);
1083 }
1084 
1085 QList<qlonglong> TableViewModel::imageIds(const QModelIndexList& indexList) const
1086 {
1087     QList<qlonglong> idList;
1088 
1089     Q_FOREACH (const QModelIndex& index, indexList)
1090     {
1091         ASSERT_MODEL(index, this);
1092 
1093         if (index.column() > 0)
1094         {
1095             continue;
1096         }
1097 
1098         const Item* const item = itemFromIndex(index);
1099 
1100         if (!item)
1101         {
1102             continue;
1103         }
1104 
1105         idList << item->imageId;
1106     }
1107 
1108     return idList;
1109 }
1110 
1111 QList<ItemInfo> TableViewModel::imageInfos(const QModelIndexList& indexList) const
1112 {
1113     QList<ItemInfo> infoList;
1114 
1115     Q_FOREACH (const QModelIndex& index, indexList)
1116     {
1117         ASSERT_MODEL(index, this);
1118 
1119         if (index.column() > 0)
1120         {
1121             continue;
1122         }
1123 
1124         Item* const item = itemFromIndex(index);
1125 
1126         if (!item)
1127         {
1128             continue;
1129         }
1130 
1131         infoList << infoFromItem(item);
1132     }
1133 
1134     return infoList;
1135 }
1136 
1137 ItemInfo TableViewModel::imageInfo(const QModelIndex& index) const
1138 {
1139     ASSERT_MODEL(index, this);
1140 
1141     Item* const item = itemFromIndex(index);
1142 
1143     if (!item)
1144     {
1145         return ItemInfo();
1146     }
1147 
1148     return infoFromItem(item);
1149 }
1150 
1151 void TableViewModel::slotFilterSettingsChanged(const ItemFilterSettings& settings)
1152 {
1153     d->imageFilterSettings = settings;
1154 
1155     slotPopulateModel(true);
1156 }
1157 
1158 class Q_DECL_HIDDEN TableViewModel::LessThan
1159 {
1160 public:
1161 
1162     explicit LessThan(TableViewModel* const model)
1163       : m(model)
1164     {
1165     }
1166 
1167     bool operator()(const TableViewModel::Item* const itemA, const TableViewModel::Item* const itemB)
1168     {
1169         const bool compareResult = m->lessThan(const_cast<Item*>(itemA), const_cast<Item*>(itemB));
1170 
1171         if (m->d->sortOrder == Qt::DescendingOrder)
1172         {
1173             return !compareResult;
1174         }
1175 
1176         return compareResult;
1177     }
1178 
1179 public:
1180 
1181     TableViewModel* m;
1182 };
1183 
1184 QList<TableViewModel::Item*> TableViewModel::sortItems(const QList<TableViewModel::Item*>& itemList)
1185 {
1186     QList<Item*> sortedList = itemList;
1187 
1188     std::sort(sortedList.begin(),
1189               sortedList.end(),
1190               LessThan(this));
1191 
1192     return sortedList;
1193 }
1194 
1195 void TableViewModel::sort(int column, Qt::SortOrder order)
1196 {
1197     d->sortColumn = column;
1198     d->sortOrder  = order;
1199 
1200     /// @todo re-sort items
1201 
1202     QList<Item*> itemsRequiringSorting;
1203     itemsRequiringSorting << d->rootItem;
1204 
1205     beginResetModel();
1206 
1207     while (!itemsRequiringSorting.isEmpty())
1208     {
1209         Item* const itemToSort = itemsRequiringSorting.takeFirst();
1210 
1211         Q_FOREACH (Item* const itemToCheck, itemToSort->children)
1212         {
1213             if (!itemToCheck->children.isEmpty())
1214             {
1215                 itemsRequiringSorting << itemToCheck;
1216             }
1217         }
1218 
1219         itemToSort->children = sortItems(itemToSort->children);
1220     }
1221 
1222     endResetModel();
1223 }
1224 
1225 bool TableViewModel::lessThan(TableViewModel::Item* const itemA, TableViewModel::Item* const itemB)
1226 {
1227     if ((d->sortColumn < 0) || (d->sortColumn >= d->columnObjects.count()))
1228     {
1229         return (itemA->imageId < itemB->imageId);
1230     }
1231 
1232     const TableViewColumn* columnObject = s->tableViewModel->getColumnObject(d->sortColumn);
1233 
1234     if (!columnObject->getColumnFlags().testFlag(TableViewColumn::ColumnCustomSorting))
1235     {
1236         const QString stringA = columnObject->data(itemA, Qt::DisplayRole).toString();
1237         const QString stringB = columnObject->data(itemB, Qt::DisplayRole).toString();
1238 
1239         if ((stringA == stringB) || (stringA.isEmpty() && stringB.isEmpty()))
1240         {
1241             return (itemA->imageId < itemB->imageId);
1242         }
1243 
1244         return (stringA < stringB);
1245     }
1246 
1247     TableViewColumn::ColumnCompareResult cmpResult = columnObject->compare(itemA, itemB);
1248 
1249     if (cmpResult == TableViewColumn::CmpEqual)
1250     {
1251         // compared items are equal, use the image id to enforce a repeatable sorting
1252 
1253         const qlonglong imageIdA = itemA->imageId;
1254         const qlonglong imageIdB = itemB->imageId;
1255 
1256         return (imageIdA < imageIdB);
1257     }
1258 
1259     return (cmpResult == TableViewColumn::CmpALessB);
1260 }
1261 
1262 QMimeData* TableViewModel::mimeData(const QModelIndexList& indexes) const
1263 {
1264     // we pack the mime data via ItemModel's drag-drop handler
1265 
1266     AbstractItemDragDropHandler* const ddHandler = s->imageModel->dragDropHandler();
1267 
1268     QModelIndexList imageModelIndexList;
1269 
1270     Q_FOREACH (const QModelIndex& i, indexes)
1271     {
1272         if (i.column() > 0)
1273         {
1274             continue;
1275         }
1276 
1277         const QModelIndex imageModelIndex = toItemModelIndex(i);
1278 
1279         if (imageModelIndex.isValid())
1280         {
1281             imageModelIndexList << imageModelIndex;
1282         }
1283     }
1284 
1285     QMimeData* const imageModelMimeData   = ddHandler->createMimeData(imageModelIndexList);
1286 
1287     return imageModelMimeData;
1288 }
1289 
1290 Qt::DropActions TableViewModel::supportedDropActions() const
1291 {
1292     return (Qt::CopyAction | Qt::MoveAction);
1293 }
1294 
1295 QStringList TableViewModel::mimeTypes() const
1296 {
1297     AbstractItemDragDropHandler* const ddHandler = s->imageModel->dragDropHandler();
1298 
1299     if (ddHandler)
1300     {
1301         return ddHandler->mimeTypes();
1302     }
1303 
1304     return QStringList();
1305 }
1306 
1307 bool TableViewModel::dropMimeData(const QMimeData* data,
1308                                   Qt::DropAction action,
1309                                   int row, int column,
1310                                   const QModelIndex& parent)
1311 {
1312     Q_UNUSED(data)
1313     Q_UNUSED(action)
1314     Q_UNUSED(row)
1315     Q_UNUSED(column)
1316     Q_UNUSED(parent)
1317 
1318     // the drop is handled by the drag-drop handler, therefore we return false here
1319 
1320     return false;
1321 }
1322 
1323 void TableViewModel::slotResortModel()
1324 {
1325     if (!d->sortRequired)
1326     {
1327         return;
1328     }
1329 
1330     beginResetModel();
1331     sort(d->sortColumn, d->sortOrder);
1332     endResetModel();
1333 
1334     d->sortRequired = false;
1335 }
1336 
1337 void TableViewModel::scheduleResort()
1338 {
1339     if (d->sortRequired)
1340     {
1341         return;
1342     }
1343 
1344     d->sortRequired = true;
1345 
1346     QTimer::singleShot(100, this, SLOT(slotResortModel()));
1347 }
1348 
1349 QModelIndex TableViewModel::itemIndex(TableViewModel::Item* const item) const
1350 {
1351     if ((!item) || (item==d->rootItem))
1352     {
1353         return QModelIndex();
1354     }
1355 
1356     const int rowIndex = item->parent->children.indexOf(item);
1357 
1358     return createIndex(rowIndex, 0, item);
1359 }
1360 
1361 bool TableViewModel::hasChildren(const QModelIndex& parent) const
1362 {
1363     Item* parentItem = d->rootItem;
1364 
1365     if (parent.isValid())
1366     {
1367         if (parent.column() > 0)
1368         {
1369             // only column 0 can have children
1370 
1371             return false;
1372         }
1373 
1374         parentItem = itemFromIndex(parent);
1375     }
1376 
1377     return !parentItem->children.isEmpty();
1378 }
1379 
1380 qlonglong TableViewModel::imageId(const QModelIndex& anIndex) const
1381 {
1382     const Item* const anItem = itemFromIndex(anIndex);
1383 
1384     if (!anItem)
1385     {
1386         return (-1);
1387     }
1388 
1389     return anItem->imageId;
1390 }
1391 
1392 QList<ItemInfo> TableViewModel::allItemInfo() const
1393 {
1394     return infosFromItems(d->rootItem->children);
1395 }
1396 
1397 TableViewModel::GroupingMode TableViewModel::groupingMode() const
1398 {
1399     return d->groupingMode;
1400 }
1401 
1402 void TableViewModel::setGroupingMode(const TableViewModel::GroupingMode newGroupingMode)
1403 {
1404     if (d->groupingMode != newGroupingMode)
1405     {
1406         d->groupingMode = newGroupingMode;
1407         QTimer::singleShot(100, this, SLOT(slotPopulateModelWithNotifications()));
1408 
1409         Q_EMIT signalGroupingModeChanged();
1410     }
1411 }
1412 
1413 QModelIndex TableViewModel::deepRowIndex(const int rowNumber) const
1414 {
1415     int targetRowNumber = rowNumber;
1416 
1417     if (rowNumber < 0)
1418     {
1419         targetRowNumber += deepRowCount();
1420     }
1421 
1422     QModelIndex cIndex = index(0, 0);
1423 
1424     for (int i = 0 ; i < targetRowNumber ; ++i)
1425     {
1426         if (hasChildren(cIndex))
1427         {
1428             cIndex = index(0, 0, cIndex);
1429         }
1430         else
1431         {
1432             QModelIndex candidateIndex = cIndex.sibling(cIndex.row() + 1, 0);
1433 
1434             if (!candidateIndex.isValid())
1435             {
1436                 QModelIndex parentIndex = cIndex.parent();
1437 
1438                 if (!parentIndex.isValid())
1439                 {
1440                     return QModelIndex();
1441                 }
1442 
1443                 candidateIndex = parentIndex.sibling(parentIndex.row() + 1, 0);
1444             }
1445 
1446             cIndex = candidateIndex;
1447         }
1448     }
1449 
1450     return cIndex;
1451 }
1452 
1453 int TableViewModel::indexToDeepRowNumber(const QModelIndex& rowIndex) const
1454 {
1455     const QModelIndex column0Index = toCol0(rowIndex);
1456     int deepRowNumber              = 0;
1457     QModelIndex cIndex             = index(0, 0);
1458 
1459     while (cIndex.isValid())
1460     {
1461         if (cIndex == column0Index)
1462         {
1463             break;
1464         }
1465 
1466         ++deepRowNumber;
1467 
1468         if (hasChildren(cIndex))
1469         {
1470             cIndex = index(0, 0, cIndex);
1471         }
1472         else
1473         {
1474             QModelIndex candidateIndex = cIndex.sibling(cIndex.row() + 1, 0);
1475 
1476             if (!candidateIndex.isValid())
1477             {
1478                 QModelIndex parentIndex = cIndex.parent();
1479 
1480                 if (!parentIndex.isValid())
1481                 {
1482                     break;
1483                 }
1484 
1485                 candidateIndex          = parentIndex.sibling(parentIndex.row() + 1, 0);
1486             }
1487 
1488             cIndex = candidateIndex;
1489         }
1490     }
1491 
1492     if (!cIndex.isValid())
1493     {
1494         return (-1);
1495     }
1496 
1497     return deepRowNumber;
1498 }
1499 
1500 int TableViewModel::deepRowCount() const
1501 {
1502     int deepRowNumber  = 0;
1503     QModelIndex cIndex = index(0, 0);
1504 
1505     while (cIndex.isValid())
1506     {
1507         ++deepRowNumber;
1508 
1509         if (hasChildren(cIndex))
1510         {
1511             cIndex = index(0, 0, cIndex);
1512         }
1513         else
1514         {
1515             QModelIndex candidateIndex = cIndex.sibling(cIndex.row() + 1, 0);
1516 
1517             if (!candidateIndex.isValid())
1518             {
1519                 QModelIndex parentIndex = cIndex.parent();
1520 
1521                 if (!parentIndex.isValid())
1522                 {
1523                     break;
1524                 }
1525 
1526                 candidateIndex          = parentIndex.sibling(parentIndex.row() + 1, 0);
1527             }
1528 
1529             cIndex = candidateIndex;
1530         }
1531     }
1532 
1533     return deepRowNumber;
1534 }
1535 
1536 QModelIndex TableViewModel::toCol0(const QModelIndex& anIndex) const
1537 {
1538     return anIndex.sibling(anIndex.row(), 0);
1539 }
1540 
1541 int TableViewModel::firstDeepRowNotInList(const QList<QModelIndex>& needleList)
1542 {
1543     int currentNeedlePos           = 0;
1544     QModelIndex currentNeedleIndex = toCol0(needleList.first());
1545     int deepRowNumber              = 0;
1546     QModelIndex cIndex             = index(0, 0);
1547 
1548     while (cIndex.isValid())
1549     {
1550         if (cIndex != currentNeedleIndex)
1551         {
1552             return deepRowNumber;
1553         }
1554 
1555         if (hasChildren(cIndex))
1556         {
1557             cIndex = index(0, 0, cIndex);
1558         }
1559         else
1560         {
1561             QModelIndex candidateIndex = cIndex.sibling(cIndex.row() + 1, 0);
1562 
1563             if (!candidateIndex.isValid())
1564             {
1565                 QModelIndex parentIndex = cIndex.parent();
1566 
1567                 if (!parentIndex.isValid())
1568                 {
1569                     break;
1570                 }
1571 
1572                 candidateIndex = parentIndex.sibling(parentIndex.row() + 1, 0);
1573             }
1574 
1575             cIndex = candidateIndex;
1576         }
1577 
1578         if (cIndex.isValid())
1579         {
1580             ++deepRowNumber;
1581             ++currentNeedlePos;
1582 
1583             if (currentNeedlePos >= needleList.count())
1584             {
1585                 return deepRowNumber;
1586             }
1587 
1588             currentNeedleIndex = toCol0(needleList.at(currentNeedlePos));
1589         }
1590     }
1591 
1592     return -1;
1593 }
1594 
1595 void TableViewModel::slotSetActive(const bool isActive)
1596 {
1597     if (isActive)
1598     {
1599         if (d->outdated)
1600         {
1601             // populate the model once later, not now
1602 
1603             QTimer::singleShot(0, this, SLOT(slotPopulateModelWithNotifications()));
1604         }
1605     }
1606 }
1607 
1608 int TableViewModel::findChildSortedPosition(TableViewModel::Item* const parentItem, TableViewModel::Item* const childItem)
1609 {
1610     if (parentItem->children.isEmpty())
1611     {
1612         return 0;
1613     }
1614 
1615     // nChildren is guaranteed to be >=1
1616 
1617     const int nChildren = parentItem->children.count();
1618     int stepSize        = nChildren / 2;
1619 
1620     // make sure pos is at least 0 if there is only one item
1621 
1622     int pos             = qMin(nChildren - 1, stepSize);
1623 
1624     while (true)
1625     {
1626         stepSize = stepSize / 2;
1627 
1628         if (stepSize == 0)
1629         {
1630             stepSize = 1;
1631         }
1632 
1633         bool isLessThanUpper = lessThan(childItem, parentItem->children.at(pos));
1634 
1635         if (d->sortOrder == Qt::DescendingOrder)
1636         {
1637             isLessThanUpper = !isLessThanUpper;
1638         }
1639 
1640         if (!isLessThanUpper)
1641         {
1642             // need to jump up, quit if we can not jump up by 1
1643 
1644             if ((pos + 1) >= nChildren)
1645             {
1646                 pos = nChildren;
1647                 break;
1648             }
1649 
1650             // jump up by stepSize and make sure we do not jump over the end
1651 
1652             pos += stepSize;
1653 
1654             if (pos >= nChildren)
1655             {
1656                 pos = nChildren - 1;
1657             }
1658 
1659             continue;
1660         }
1661 
1662         // can we go lower?
1663         const bool lowerThere = (pos > 0);
1664 
1665         if (!lowerThere)
1666         {
1667             // no, stop
1668 
1669             pos = 0;
1670             break;
1671         }
1672 
1673         bool isLessThanLower = lessThan(childItem, parentItem->children.at(pos-1));
1674 
1675         if (d->sortOrder == Qt::DescendingOrder)
1676         {
1677             isLessThanLower = !isLessThanLower;
1678         }
1679 
1680         if (isLessThanLower)
1681         {
1682             // go lower and make sure we do not jump too low
1683 
1684             pos -= stepSize;
1685 
1686             if (pos < 0)
1687             {
1688                 pos = 0;
1689             }
1690 
1691             continue;
1692         }
1693 
1694         break;
1695     }
1696 
1697     return pos;
1698 }
1699 
1700 } // namespace Digikam
1701 
1702 #include "moc_tableview_model.cpp"