diff --git a/sources/ElementsCollection/elementcollectionitem.cpp b/sources/ElementsCollection/elementcollectionitem.cpp index 3e63c8c8c..ac63291b6 100644 --- a/sources/ElementsCollection/elementcollectionitem.cpp +++ b/sources/ElementsCollection/elementcollectionitem.cpp @@ -18,6 +18,9 @@ #include "elementcollectionitem.h" +#include +#include + /** @brief ElementCollectionItem::ElementCollectionItem Constructor @@ -247,6 +250,73 @@ QList ElementCollectionItem::items() const return list; } +/** + @brief onGuiThread + @return true when called from the thread the application lives in +*/ +static bool onGuiThread() +{ + return QThread::currentThread() == QCoreApplication::instance()->thread(); +} + +/** + @brief ElementCollectionItem::setData + ElementsCollectionModel::loadCollections() runs setUpData() for every + item on worker threads (QtConcurrent::map), while the items are + already in the model. QStandardItem::setData() updates the model and + emits its dataChanged() signal, which is not safe from a worker + thread. So a value set from a worker thread is kept on the item, and + the model applies it on the GUI thread with applyDeferredData() once + every item is set up. setText(), setFlags(), setToolTip() and setIcon() + all end up here. + @param value + @param role +*/ +void ElementCollectionItem::setData(const QVariant &value, int role) +{ + if (role == Qt::EditRole) + role = Qt::DisplayRole; + + if (onGuiThread()) + QStandardItem::setData(value, role); + else + m_deferred_data.insert(role, value); +} + +/** + @brief ElementCollectionItem::data + On a worker thread, a value set by setData() but not applied yet is + returned, so setUpData() reads back what it has just set + (localName() tests text() for example). + @param role + @return +*/ +QVariant ElementCollectionItem::data(int role) const +{ + if (role == Qt::EditRole) + role = Qt::DisplayRole; + + if (!onGuiThread()) + { + const auto it = m_deferred_data.constFind(role); + if (it != m_deferred_data.constEnd()) + return it.value(); + } + return QStandardItem::data(role); +} + +/** + @brief ElementCollectionItem::applyDeferredData + Apply the values set from a worker thread, see setData(). + Must be called on the GUI thread, after the worker is done. +*/ +void ElementCollectionItem::applyDeferredData() +{ + for (auto it = m_deferred_data.constBegin() ; it != m_deferred_data.constEnd() ; ++it) + QStandardItem::setData(it.value(), it.key()); + m_deferred_data.clear(); +} + void setUpData(ElementCollectionItem *eci) { eci->setUpData(); } diff --git a/sources/ElementsCollection/elementcollectionitem.h b/sources/ElementsCollection/elementcollectionitem.h index ad12477c1..057215662 100644 --- a/sources/ElementsCollection/elementcollectionitem.h +++ b/sources/ElementsCollection/elementcollectionitem.h @@ -18,6 +18,7 @@ #ifndef ELEMENTCOLLECTIONITEM2_H #define ELEMENTCOLLECTIONITEM2_H +#include #include /** @@ -56,6 +57,16 @@ class ElementCollectionItem : public QStandardItem QList elementsChild() const; QList directoriesChild() const; QList items() const; + + QVariant data(int role = Qt::UserRole + 1) const override; + void setData(const QVariant &value, int role = Qt::UserRole + 1) override; + void applyDeferredData(); + + private: + /// Values set by setUpData() while it runs on a worker thread, + /// see setData(). Only ever touched by that one worker thread, + /// until applyDeferredData() empties it on the GUI thread. + QHash m_deferred_data; }; void setUpData(ElementCollectionItem *eci); diff --git a/sources/ElementsCollection/elementscollectionmodel.cpp b/sources/ElementsCollection/elementscollectionmodel.cpp index 00ef0d2eb..440f10417 100644 --- a/sources/ElementsCollection/elementscollectionmodel.cpp +++ b/sources/ElementsCollection/elementscollectionmodel.cpp @@ -312,8 +312,15 @@ void ElementsCollectionModel::loadCollections(bool common_collection, this, &ElementsCollectionModel::loadingProgressValueChanged); connect(watcher, &QFutureWatcher::progressRangeChanged, this, &ElementsCollectionModel::loadingProgressRangeChanged); - connect(watcher, &QFutureWatcher::finished, - this, &ElementsCollectionModel::loadingFinished); + //setUpData() ran on worker threads, which only kept the values on + //the items (ElementCollectionItem::setData()): apply them here, on + //the GUI thread, before anyone is told the loading is finished. + connect(watcher, &QFutureWatcher::finished, this, [this]() + { + for (ElementCollectionItem *eci : std::as_const(m_items_list_to_setUp)) + eci->applyDeferredData(); + emit loadingFinished(); + }); connect(watcher, &QFutureWatcher::finished, watcher, &QFutureWatcher::deleteLater);