status-desktop/ui/StatusQ/src/modelentry.cpp

375 lines
8.8 KiB
C++

#include "StatusQ/modelentry.h"
#include "StatusQ/snapshotmodel.h"
#include "StatusQ/snapshotobject.h"
ModelEntry::ModelEntry(QObject* parent)
: QObject(parent)
, m_item(new QQmlPropertyMap(this))
{ }
QQmlPropertyMap* ModelEntry::item() const
{
return m_item.data();
}
QAbstractItemModel* ModelEntry::sourceModel() const
{
return m_sourceModel.data();
}
QString ModelEntry::key() const
{
return m_key;
}
QVariant ModelEntry::value() const
{
return m_value;
}
bool ModelEntry::available() const
{
return m_available;
}
const QStringList& ModelEntry::roles() const
{
return m_roles;
}
int ModelEntry::row() const
{
return m_row;
}
bool ModelEntry::cacheOnRemoval() const
{
return m_cacheOnRemoval;
}
bool ModelEntry::itemRemovedFromModel() const
{
return m_itemRemovedFromModel;
}
void ModelEntry::setSourceModel(QAbstractItemModel* sourceModel)
{
if(m_sourceModel == sourceModel) return;
if(m_sourceModel)
{
disconnect(m_sourceModel, nullptr, this, nullptr);
}
m_sourceModel = sourceModel;
resetCachedItem();
resetIndex();
if(!m_sourceModel)
{
emit sourceModelChanged();
return;
}
connect(m_sourceModel, &QAbstractItemModel::modelReset, this, [this]() { resetIndex(); });
connect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, [this]() {
if(!m_index.isValid())
{
resetIndex();
}
});
connect(m_sourceModel,
&QAbstractItemModel::rowsMoved,
this,
[this](const QModelIndex& parent, int start, int end, const QModelIndex& destination, int row) {
if(!m_index.isValid()) return;
if(m_index.row() >= destination.row() && m_index.row() <= destination.row() + (end - start))
{
emit rowChanged();
}
});
connect(m_sourceModel,
&QAbstractItemModel::rowsAboutToBeRemoved,
this,
[this](const QModelIndex& parent, int first, int last) {
if(!m_index.isValid()) return;
if(m_index.row() < first || m_index.row() > last) return;
if(m_cacheOnRemoval)
{
cacheItem();
setItemRemovedFromModel(true);
setRow(-1);
return;
}
setAvailable(false);
setIndex({});
});
connect(m_sourceModel,
&QAbstractItemModel::dataChanged,
this,
[this](const QModelIndex& topLeft, const QModelIndex& bottomRight, const QList<int>& roles = QList<int>()) {
if(!m_index.isValid())
{
auto index = findIndexInRange(topLeft.row(), bottomRight.row() + 1);
setIndex(index);
return;
}
// Check if the index is still valid
auto index = findIndexInRange(m_index.row(), m_index.row() + 1, roles);
if(index != m_index)
{
setIndex(index);
return;
}
if(topLeft.row() <= m_index.row() && m_index.row() <= bottomRight.row())
{
updateItem(roles);
}
});
connect(m_sourceModel, &QAbstractItemModel::layoutChanged, this, [this]() {
if(!m_index.isValid())
{
// Resetting just to cover cases where the rows are removed after the layout change
resetItem();
}
setRow(m_index.row());
});
emit sourceModelChanged();
}
void ModelEntry::setKey(const QString& key)
{
if(m_key == key) return;
m_key = key;
resetIndex();
emit keyChanged();
}
void ModelEntry::setValue(const QVariant& value)
{
if(m_value == value) return;
m_value = value;
resetIndex();
emit valueChanged();
}
void ModelEntry::setIndex(const QModelIndex& index)
{
if(m_index == index) return;
m_index = index;
setRow(m_index.row());
tryItemResetOrUpdate();
}
void ModelEntry::setAvailable(bool available)
{
if(available == m_available) return;
m_available = available;
emit availableChanged();
}
void ModelEntry::setRoles(const QStringList& roles)
{
if(m_roles.size() == roles.size() && !m_roles.empty() &&
std::all_of(roles.begin(), roles.end(), [this](const QString& role) { return m_roles.contains(role); }))
return;
m_roles = roles;
emit rolesChanged();
}
void ModelEntry::setRow(int row)
{
if(m_row == row) return;
m_row = row;
emit rowChanged();
}
void ModelEntry::setCacheOnRemoval(bool cacheOnRemoval)
{
if(m_cacheOnRemoval == cacheOnRemoval) return;
resetCachedItem();
m_cacheOnRemoval = cacheOnRemoval;
emit cacheOnRemovalChanged();
}
void ModelEntry::setItemRemovedFromModel(bool itemRemovedFromModel)
{
if(m_itemRemovedFromModel == itemRemovedFromModel) return;
m_itemRemovedFromModel = itemRemovedFromModel;
emit itemRemovedFromModelChanged();
}
QModelIndex ModelEntry::findIndexInRange(int start, int end, const QList<int>& roles) const
{
if(!m_sourceModel || m_key.isEmpty()) return {};
auto keysForRole = m_sourceModel->roleNames().keys(m_key.toUtf8());
// no matching roles found
if(keysForRole.isEmpty() || (!roles.isEmpty() && !roles.contains(keysForRole.first()))) return {};
for(int i = start; i < end; i++)
{
auto index = m_sourceModel->index(i, 0);
auto data = index.data(keysForRole.first());
if(data == m_value) return index;
}
return {};
}
void ModelEntry::resetIndex()
{
auto index = QModelIndex();
if(m_sourceModel) index = findIndexInRange(0, m_sourceModel->rowCount());
setIndex(index);
}
void ModelEntry::tryItemResetOrUpdate()
{
if(!m_index.isValid() || !itemHasCorrectRoles())
{
resetItem();
return;
}
updateItem();
setAvailable(true);
}
void ModelEntry::resetItem()
{
// Signal order is important here
if(!m_index.isValid())
{
setAvailable(false);
}
m_item.reset(new QQmlPropertyMap());
fillItem();
if(!m_index.isValid())
{
setRoles(m_item->keys());
}
emit itemChanged();
if(m_index.isValid())
{
setRoles(m_item->keys());
setAvailable(true);
}
}
void ModelEntry::updateItem(const QList<int>& roles /*{}*/)
{
const auto updatedRoles = fillItem(roles);
notifyItemChanges(updatedRoles);
setItemRemovedFromModel(false);
}
QStringList ModelEntry::fillItem(const QList<int>& roles /*{}*/)
{
if(!m_index.isValid() || !m_sourceModel) return {};
QStringList filledRoles;
const auto& rolesRef = roles.isEmpty() ? m_sourceModel->roleNames().keys() : roles;
for(auto role : rolesRef)
{
auto roleName = m_sourceModel->roleNames().value(role);
auto roleValue = m_index.data(role);
if(roleValue == m_item->value(roleName)) continue;
filledRoles.append(roleName);
m_item->insert(roleName, roleValue);
}
return filledRoles;
}
void ModelEntry::notifyItemChanges(const QStringList& roles)
{
if (roles.contains(m_key))
{
emit itemChanged();
return;
}
for(auto role : roles)
{
auto value = m_item->value(role);
emit m_item->valueChanged(role, value);
}
}
bool ModelEntry::itemHasCorrectRoles() const
{
if(!m_sourceModel || !m_item) return false;
auto itemKeys = m_item->keys();
auto modelRoles = m_sourceModel->roleNames().values();
return std::all_of(modelRoles.cbegin(),
modelRoles.cend(),
[itemKeys](const QByteArray& role) { return itemKeys.contains(role); }) &&
itemKeys.size() == modelRoles.size();
}
void ModelEntry::cacheItem()
{
if(!m_cacheOnRemoval) return;
for(const auto& role : qAsConst(m_roles))
{
auto roleNames = m_sourceModel->roleNames().keys(role.toUtf8());
if (roleNames.isEmpty()) continue;
auto roleName = roleNames.first();
auto roleValue = m_index.data(roleName);
if(roleValue.canConvert<QAbstractItemModel*>())
{
m_item->insert(role,
QVariant::fromValue(new SnapshotModel(*roleValue.value<QAbstractItemModel*>(), true, m_item.data())));
}
else if(roleValue.canConvert<QObject*>())
{
const auto obj = roleValue.value<QObject*>();
const auto snapshot = new SnapshotObject(obj, m_item.data());
m_item->insert(role, QVariant::fromValue(snapshot->snapshot()));
}
}
}
void ModelEntry::resetCachedItem()
{
if(!m_cacheOnRemoval || !m_itemRemovedFromModel) return;
resetIndex();
tryItemResetOrUpdate();
setItemRemovedFromModel(false);
}