#include "tablemodel.h" #include "../formats/jsonparser.h" #include "commands/edititemcommand.h" #include "commands/insertrowscommand.h" #include "commands/removerowscommand.h" #include "metadata.h" #include "modelitem.h" #include #include #include QByteArray TableModel::generateExampleItems() { QJsonDocument doc = QJsonDocument(); QJsonObject rootObject; QJsonArray array; // TODO use JsonParser for the item generation for (int row = 0; row < 5; ++row) { QJsonObject itemObject; // itemObject.insert("uuid", m_uuid.toString()); // itemObject.insert("entryDateUTC", m_entryDateUTC.toString(Qt::ISODate)); itemObject.insert(ROLE_NAMES.value(NameRole), QString("Item %1").arg(row)); itemObject.insert(ROLE_NAMES.value(DescriptionRole), QString("This is item %1").arg(row)); itemObject.insert(ROLE_NAMES.value(InfoRole), QString("Info of item %1").arg(row)); itemObject.insert(ROLE_NAMES.value(AmountRole), row); itemObject.insert(ROLE_NAMES.value(FactorRole), row * 1.1); array.append(itemObject); } rootObject.insert(ITEMS_KEY_STRING, array); doc.setObject(rootObject); return doc.toJson(); } TableModel::TableModel(QUndoStack* undoStack, QObject* parent) : QAbstractTableModel{parent} , m_undoStack(undoStack) {} Qt::ItemFlags TableModel::flags(const QModelIndex& index) const { if (!index.isValid()) { return QAbstractTableModel::flags(index); } return Qt::ItemIsEditable | QAbstractTableModel::flags(index); } QHash TableModel::roleNames() const { return ROLE_NAMES; } int TableModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; /// no children } return m_items.size(); } int TableModel::columnCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; /// no children } return USER_FACING_ROLES.size(); } QVariant TableModel::data(const QModelIndex& index, int role) const { const int row = index.row(); const int column = index.column(); if (!index.isValid()) { return QVariant(); } if (row >= rowCount(QModelIndex()) || column >= columnCount(QModelIndex())) { return QVariant(); } int roleForColumn = GET_ROLE_FOR_COLUMN(column); switch (role) { case Qt::DisplayRole: case Qt::EditRole: return m_items.at(row)->data(roleForColumn); case ToStringRole: return m_items.at(row)->toString(); case ToJsonRole: return m_items.at(row)->toJsonObject(); case IdRole: return m_items.at(row)->data(role); default: return m_items.at(row)->data(role); } return QVariant(); } QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role == Qt::DisplayRole) { if (orientation == Qt::Horizontal) { const int columnRole = GET_ROLE_FOR_COLUMN(section); const QString headerName = ROLE_NAMES.value(columnRole); return QString("%1").arg(headerName); } else { return QString("%1").arg(section); } } return QVariant(); } bool TableModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (role == Qt::EditRole && checkIndex(index)) { const int column = index.column(); const int roleForColumn = GET_ROLE_FOR_COLUMN(column); return setItemData(index, {{roleForColumn, value}}); } return false; } bool TableModel::setItemData(const QModelIndex& index, const QMap& roles) { if (!checkIndex(index)) { return false; } // if (isRoleReadOnly(roleForColumn)) { // return false; // } QMap changedValues = onlyChangedValues(index, roles); if (changedValues.size() > 0) { EditItemCommand* editCommand = new EditItemCommand(this, index, changedValues); m_undoStack->push(editCommand); return true; } return false; } ModelItemValues TableModel::getItemValues(const QModelIndex& index) const { ModelItemValues values; QListIterator i(USER_FACING_ROLES); while (i.hasNext()) { const UserRoles role = i.next(); values.insert(role, data(index, role)); } return values; } QJsonDocument TableModel::getAllItemsAsJsonDoc() const { QJsonDocument doc = QJsonDocument(); QJsonObject rootObject; QJsonArray array; foreach (shared_ptr item, m_items) { QJsonObject itemObject = item->toJsonObject(); array.append(itemObject); } rootObject.insert(ITEMS_KEY_STRING, array); doc.setObject(rootObject); return doc; } QList TableModel::getItemsAsStringLists() const { QList result; foreach (shared_ptr item, m_items) { QStringList valueList; for (int column = 0; column < columnCount(); ++column) { QString value = item->data(GET_ROLE_FOR_COLUMN(column)).toString(); valueList.append(value); } result.append(valueList); } return result; } // TODO use item selection as parameter to wrap multiple items into JSON data structure QByteArray TableModel::jsonDataForServer(const QModelIndex& currentIndex) const { const QJsonObject itemObject = data(currentIndex, ToJsonRole).toJsonObject(); QJsonObject rootObject; rootObject.insert(ITEM_KEY_STRING, itemObject); const QJsonDocument jsonDoc(rootObject); return jsonDoc.toJson(QJsonDocument::Compact); } QString TableModel::updateItemsFromJson(const QByteArray& jsonData) { const QList valueList = JsonParser::toItemValuesList(jsonData, ITEM_KEY_STRING); int nChangedItems = 0; for (const ModelItemValues& itemValues : valueList) { bool updateHappened = updateItem(itemValues); if (updateHappened) { nChangedItems++; } } return QString("Found and updated %1 item(s).").arg(nChangedItems); } bool TableModel::updateItem(const ModelItemValues& itemValues) { QModelIndex foundIndex = searchItemIndex(itemValues); qDebug() << "Search done!"; if (foundIndex == QModelIndex()) { const QString errorMessage = "No matching item found!"; qWarning() << errorMessage; return false; } else { qInfo() << "Item found!"; /// update existing item setItemData(foundIndex, itemValues); return true; } } bool TableModel::removeRows(int firstRow, int nRows, const QModelIndex& parentIndex) { if (parentIndex != QModelIndex()) { qWarning() << "Removing of child rows is not supported yet!"; return false; } const int lastRow = firstRow + nRows - 1; if (firstRow < 0 || lastRow >= m_items.size()) { qWarning() << "Trying to remove rows is out of bounds!"; return false; } RemoveRowsCommand* removeCommand = new RemoveRowsCommand(this, firstRow, nRows); m_undoStack->push(removeCommand); return true; } void TableModel::appendItems(const QByteArray& jsonDoc) { insertItems(-1, jsonDoc, QModelIndex()); } void TableModel::insertItems(int startPosition, const QByteArray& jsonDoc, const QModelIndex& parentIndex) { const QList valueList = JsonParser::toItemValuesList(jsonDoc, ITEMS_KEY_STRING); if (valueList.empty()) { /// don't try inserting if no values to insert qDebug() << "No items found in JSON document. Not adding anything..."; return; } insertItems(startPosition, valueList, parentIndex); } void TableModel::insertItems(int startPosition, const QList& itemValuesList, const QModelIndex& parentIndex) { qInfo() << "Inserting item(s) into model..."; if (parentIndex != QModelIndex()) { qWarning() << "Using invalid parent index (no child support for now)! Using root index as parent..."; } if (startPosition == -1 || startPosition > m_items.size()) { /// Appending item(s) startPosition = m_items.size(); } InsertRowsCommand* insertCommand = new InsertRowsCommand(this, startPosition, itemValuesList); m_undoStack->push(insertCommand); } void TableModel::execInsertItems(const int firstRow, const QList valueList) { const int nRows = valueList.size(); qDebug() << "Inserting" << nRows << "items..."; const int lastRow = firstRow + nRows - 1; beginInsertRows(QModelIndex(), firstRow, lastRow); for (int row = 0; row < nRows; ++row) { const int rowPosition = firstRow + row; shared_ptr item = make_unique(valueList.at(row)); m_items.insert(rowPosition, std::move(item)); } endInsertRows(); } void TableModel::execRemoveItems(const int firstRow, const int nRows) { const int lastRow = firstRow + nRows - 1; beginRemoveRows(QModelIndex(), firstRow, lastRow); m_items.remove(firstRow, nRows); endRemoveRows(); } void TableModel::execEditItemData(const int row, const QMap& changedValues) { shared_ptr item = m_items.at(row); bool isDataChanged = item->setItemData(changedValues); if (isDataChanged) { /// FIXME due to the mapping from roles to the DisplayRole of different columns the complete row /// is getting notified about (potential) data changes; dataChanged should be called only for /// the affected columns const QModelIndex firstIndex = this->index(row, 0); const QModelIndex lastIndex = this->index(row, USER_FACING_ROLES.size() - 1); QList roles = changedValues.keys(); roles.insert(0, Qt::DisplayRole); emit dataChanged(firstIndex, lastIndex, roles.toVector()); } } QMap TableModel::onlyChangedValues(const QModelIndex& index, const QMap& roleValueMap) const { QMap result; QMap::const_iterator i; for (i = roleValueMap.constBegin(); i != roleValueMap.constEnd(); ++i) { const int role = i.key(); const QVariant newValue = i.value(); const QVariant oldValue = index.data(role); // TODO check if role is a editable role? if (oldValue != newValue) { bool emptyValueIsEqualToZero = isEmptyValueEqualToZero(role); // REFACTOR the next if statement is too complex if (emptyValueIsEqualToZero && oldValue == QVariant() && newValue == 0) { qDebug() << "oldValue:" << oldValue << "& newValue:" << newValue << "mean the same. Ignoring..."; continue; } qDebug() << "oldValue:" << oldValue << "!= newValue:" << newValue; result.insert(role, newValue); } else { qInfo() << "oldValue is already the same as newValue:" << oldValue << "-> ignoring..."; } } return result; } bool TableModel::isEmptyValueEqualToZero(const int role) const { if (INT_ROLES.contains(role)) { return true; } else if (DOUBLE_ROLES.contains(role)) { return true; } else { return false; } } QModelIndex TableModel::searchItemIndex(const ModelItemValues givenItemValues) const { // iterate over indexes to search item : see searchItem(...); // for (const shared_ptr& item : m_items) { for (int row = 0; row < rowCount(); ++row) { qDebug() << "Processing item at row" << row << "..."; QModelIndex itemIndex = index(row, 0); if (isItemEqualToItemValues(itemIndex, givenItemValues)) { qInfo() << "Found item at row" << row << "! Returning its index..."; return itemIndex; } } qDebug() << "No matching item found. Returning empty pointer..."; return {}; } bool TableModel::isItemEqualToItemValues(const QModelIndex& itemIndex, const ModelItemValues givenItemValues) const { /// do both have a UUID? QVariant idOfItem = data(itemIndex, IdRole); QVariant given = givenItemValues.value(IdRole); if (idOfItem.isValid() && given.isValid()) { /// are the UUIDs the same? if (idOfItem.toString() == given.toString()) { qInfo() << "UUIDs are the same."; return true; } else { qDebug() << "UUIDs are NOT the same."; return false; } } else { /// are all other values the same? (for now only USER_FACING_ROLES are checked) QListIterator i(USER_FACING_ROLES); while (i.hasNext()) { const UserRoles role = i.next(); const QString roleName = ROLE_NAMES.value(role); const QVariant valueOfItem = data(itemIndex, role); const QVariant givenValue = givenItemValues.value(role); if (STRING_ROLES.contains(role)) { if (valueOfItem.toString() != givenValue.toString()) { return false; } } else if (TYPE_ROLES.contains(role)) { if (valueOfItem.toString() != givenValue.toString()) { return false; } } else if (INT_ROLES.contains(role)) { if (valueOfItem.toInt() != givenValue.toInt()) { return false; } } else if (DOUBLE_ROLES.contains(role)) { if (valueOfItem.toDouble() != givenValue.toDouble()) { return false; } } else { qCritical() << QString("Can't find data type for role %1!!!").arg(role); } } return true; } }