#include "tablemodel.h" #include #include #include #include "../formats/jsonparser.h" #include "../structs.h" #include "commands/edititemcommand.h" #include "commands/insertrowscommand.h" #include "commands/removerowscommand.h" #include "metadata.h" #include "modelitem.h" 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(MembershipNumberRole), row); itemObject.insert(ROLE_NAMES.value(LastNameRole), QString("Nachname%1").arg(row)); itemObject.insert(ROLE_NAMES.value(FirstNameRole), QString("Vorname%1").arg(row)); itemObject.insert(ROLE_NAMES.value(Bidding1Role), 100 + row); itemObject.insert(ROLE_NAMES.value(DepotWish1Role), QString("Depot X%1").arg(row)); itemObject.insert(ROLE_NAMES.value(DepotWish2Role), QString("Depot Y%1").arg(row)); itemObject.insert(ROLE_NAMES.value(MailRole), QString("%1@%2.com") .arg(itemObject.value(ROLE_NAMES.value(FirstNameRole)).toString(), itemObject.value(ROLE_NAMES.value(LastNameRole)).toString())); itemObject.insert(ROLE_NAMES.value(ShareAmountRole), 1); itemObject.insert(ROLE_NAMES.value(ShareTypeRole), SHARE_TYPES.at(row % 3)); itemObject.insert(ROLE_NAMES.value(BiddingTypeRole), BIDDING_TYPES.at(row % 4)); // 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) { connect(this, &TableModel::rowsInserted, this, &TableModel::onRowCountChanged); connect(this, &TableModel::rowsRemoved, this, &TableModel::onRowCountChanged); } 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(); } const int roleForColumn = GET_ROLE_FOR_COLUMN(column); switch (role) { case Qt::DisplayRole: case Qt::EditRole: return m_items.at(row)->data(roleForColumn); case Qt::ToolTipRole: return m_items.at(index.row())->data(ToStringRole); break; case MembershipNumberRole: case LastNameRole: case FirstNameRole: case FullNameRole: case Bidding1Role: case Bidding2Role: case Bidding3Role: case DepotWish1Role: case DepotWish2Role: case ShareAmountRole: case MailRole: case ShareTypeRole: case BiddingTypeRole: case OnlineIdRole: case AccessCodeRole: case ToStringRole: case JsonObjectRole: 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, JsonObjectRole).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; } } void TableModel::setOnlineCredentials(const QString& mail, const QString& uuid, const QString& token) { QModelIndex itemIndex = getIndexByRoleValue(mail, MailRole); setItemData(itemIndex, {{OnlineIdRole, uuid}, {AccessCodeRole, token}}); } QJsonDocument TableModel::getMailInviteJsonDoc(const QString& mail, const QString& serverUrl) const { QJsonDocument doc = QJsonDocument(); QModelIndex index = getIndexByRoleValue(mail, MailRole); if (index.isValid()) { QJsonObject rootObject; const QString user_id = data(index, OnlineIdRole).toString(); const QString email = data(index, MailRole).toString(); const QString name = data(index, FullNameRole).toString(); const QString token = data(index, AccessCodeRole).toString(); const QString accessUrl = serverUrl + "/" + token; QJsonObject userObject; userObject.insert("user_id", user_id); userObject.insert("email", email); userObject.insert("name", name); userObject.insert("access_url", accessUrl); rootObject.insert("user", userObject); doc.setObject(rootObject); } return doc; } void TableModel::updateBiddings(const QList biddings) { QListIterator i(biddings); while (i.hasNext()) { const bidding localBidding = i.next(); qInfo() << "Processing bidding:"; const int biddingRound = localBidding.biddingRound; if (biddingRound == 0 || biddingRound > 3) { qWarning() << "Bidding round exceeds valid bounds. Ignoring..."; continue; } const QString uuid = localBidding.userId; const QModelIndex itemIndex = getIndexByRoleValue(localBidding.userId, OnlineIdRole); if (itemIndex.isValid()) { qInfo() << "Found Uuid for user:" << itemIndex.data(MailRole); const QMap values = getItemValues(localBidding); setItemData(itemIndex, values); } } } 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); } qreal TableModel::nTotalExpectedShares() const { qreal result = 0; for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) { /// if BiddingTypeRole is not empty const QString biddingType = (*i)->data(BiddingTypeRole).toString(); const qreal shareAmount = (*i)->data(ShareAmountRole).toReal(); if (biddingType.isEmpty() || shareAmount == 0.0) { continue; } /// add amount const QString shareType = (*i)->data(ShareTypeRole).toString(); if (shareType == "bezahlt") { result += shareAmount; } else if (shareType == "teils/teils") { result += shareAmount / 2; } else { qInfo() << "Share type not 'bezahlt' nor 'teils/teils'. Will be ignored.."; qDebug() << "Share type:" << shareType; } } qInfo() << "Biddings expected for " << result << "shares!"; return result; } int TableModel::nExpectedBiddings() const { int result = 0; for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) { const QString biddingType = (*i)->data(BiddingTypeRole).toString(); if (biddingType.isEmpty()) { continue; } const double shareAmount = (*i)->data(ShareAmountRole).toDouble(); if (shareAmount == 0.0) { continue; } const QString shareType = (*i)->data(ShareTypeRole).toString(); if (shareType == "bezahlt" || shareType == "teils/teils") { result++; } } return result; } int TableModel::nPlacedBiddings1() const { return nPlacedBiddings(Bidding1Role); } int TableModel::nPlacedBiddings2() const { return nPlacedBiddings(Bidding2Role); } int TableModel::nPlacedBiddings3() const { return nPlacedBiddings(Bidding3Role); } int TableModel::biddingSum1() const { return biddingSum(Bidding1Role); } int TableModel::biddingSum2() const { return biddingSum(Bidding2Role); } int TableModel::biddingSum3() const { return biddingSum(Bidding3Role); } qreal TableModel::biddingAverage1() const { const UserRoles biddingRole = Bidding1Role; const qreal averageBidding = averageBiddingAmount(biddingRole); qInfo() << "average calculation (1):" << averageBidding; return averageBidding; } qreal TableModel::biddingAverage2() const { const UserRoles biddingRole = Bidding2Role; const qreal averageBidding = averageBiddingAmount(biddingRole); qInfo() << "average calculation (2):" << averageBidding; return averageBidding; } qreal TableModel::biddingAverage3() const { const UserRoles biddingRole = Bidding3Role; const qreal averageBidding = averageBiddingAmount(biddingRole); qInfo() << "average calculation (3):" << averageBidding; return averageBidding; } void TableModel::onRowCountChanged(const QModelIndex& parent, int first, int last) { Q_UNUSED(first); Q_UNUSED(last); if (parent != QModelIndex()) { return; } emit rowCountChanged(); emit nExpectedBiddingsChanged(); emit nTotalExpectedSharesChanged(); emit nPlacedBiddingsChanged(1); emit nPlacedBiddingsChanged(2); emit nPlacedBiddingsChanged(3); emit biddingSumChanged(1); emit biddingSumChanged(2); emit biddingSumChanged(3); emit biddingAverageChanged(1); emit biddingAverageChanged(2); emit biddingAverageChanged(3); } 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()); if (roles.contains(BiddingTypeRole) || roles.contains(ShareAmountRole) || roles.contains(ShareTypeRole)) { emit nExpectedBiddingsChanged(); emit nTotalExpectedSharesChanged(); } if (roles.contains(ShareAmountRole) || roles.contains(ShareTypeRole)) { emit nPlacedBiddingsChanged(1); emit nPlacedBiddingsChanged(2); emit nPlacedBiddingsChanged(3); emit biddingSumChanged(1); emit biddingSumChanged(2); emit biddingSumChanged(3); emit biddingAverageChanged(1); emit biddingAverageChanged(2); emit biddingAverageChanged(3); } else { /// no changes to share amount or type, but maybe to the biddings: if (roles.contains(Bidding1Role)) { emit nPlacedBiddingsChanged(1); emit biddingSumChanged(1); emit biddingAverageChanged(1); } if (roles.contains(Bidding2Role)) { emit nPlacedBiddingsChanged(2); emit biddingSumChanged(2); emit biddingAverageChanged(2); } if (roles.contains(Bidding3Role)) { emit nPlacedBiddingsChanged(3); emit biddingSumChanged(3); emit biddingAverageChanged(3); } } } } 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); 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? const UserRoles idRole = OnlineIdRole; 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 (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; } } int TableModel::nPlacedBiddings(const UserRoles biddingRole) const { int result = 0; for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) { const QString biddingType = (*i)->data(BiddingTypeRole).toString(); const qreal shareAmount = (*i)->data(ShareAmountRole).toReal(); const QString shareType = (*i)->data(ShareTypeRole).toString(); if (biddingType.isEmpty() || shareAmount == 0.0) { continue; } if (shareType == "erarbeitet" || shareType == "") { continue; } int localBidding = (*i)->data(biddingRole).toInt(); if (localBidding > 0) { result++; } } return result; } int TableModel::biddingSum(const UserRoles biddingRole) const { int result = 0; for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) { const QString biddingType = (*i)->data(BiddingTypeRole).toString(); const qreal shareAmount = (*i)->data(ShareAmountRole).toReal(); const QString shareType = (*i)->data(ShareTypeRole).toString(); if (biddingType.isEmpty() || shareAmount == 0.0) { continue; } if (shareType == "erarbeitet" || shareType == "") { continue; } result += (*i)->data(biddingRole).toInt(); } return result; } qreal TableModel::averageBiddingAmount(const UserRoles biddingRole) const { qInfo() << "Calculating average bidding for role:" << ROLE_NAMES.value(biddingRole); const qreal localTotalSharesWithBiddings = totalSharesWithBiddings(biddingRole); if (localTotalSharesWithBiddings <= 0) { qInfo() << "No biddings found. Aborting calculation..."; return 0; } const qreal localTotalBiddingAmount = biddingSum(biddingRole); const qreal alternateBiddingAverage = localTotalBiddingAmount / localTotalSharesWithBiddings; qDebug() << "Total bidding amount:" << localTotalBiddingAmount; qDebug() << "Total bidding shares:" << localTotalSharesWithBiddings; return alternateBiddingAverage; } qreal TableModel::totalSharesWithBiddings(const UserRoles biddingRole) const { qreal result = 0; for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) { const QString biddingType = (*i)->data(BiddingTypeRole).toString(); const qreal shareAmount = (*i)->data(ShareAmountRole).toReal(); if (biddingType.isEmpty() || shareAmount == 0.0) { continue; } const int bidding = (*i)->data(biddingRole).toInt(); const QString shareType = (*i)->data(ShareTypeRole).toString(); const bool isValid = bidding != 0 && shareAmount != 0.0; if (isValid) { // qInfo() << "Including entry in bidding average calculation. MailRole:" // << (*i)->data(MailRole); // qreal bidForWholeShare = 0; if (shareType == "bezahlt") { result += shareAmount; } else if (shareType == "teils/teils") { result += shareAmount / 2; } else { qInfo() << "Share type not 'bezahlt' nor 'teils/teils'. Will be ignored.."; qDebug() << "Share type:" << shareType; continue; } } } qInfo() << "Biddings exist for " << result << "shares!"; return result; } QModelIndex TableModel::getIndexByRoleValue(const QString& valueString, const int role) const { const QString text = QString("Searching list item with value '%1' for role '%2'").arg(valueString, role); qInfo() << text; int itemRow = -1; for (auto i = m_items.begin(), end = m_items.end(); i != end; ++i) { const QString mailValue = (*i)->data(role).toString(); qDebug() << "found value:" << mailValue; if (valueString == mailValue) { itemRow = i - m_items.constBegin(); qInfo() << "Found item at row:" << itemRow; break; } else { qDebug() << "Not the right item. Continuing search..."; continue; } } if (itemRow < 0 || itemRow >= m_items.length()) { qWarning() << "Couldn't find item with:" << valueString << " matching role" << role << "- Returning..."; return QModelIndex(); } const QModelIndex itemIndex = index(itemRow, 0); return itemIndex; } QMap TableModel::getItemValues(const bidding bid) { const int biddingRound = bid.biddingRound; const int amount = bid.amount; const QString depotWishOne = bid.depotWishOne; const QString depotWishTwo = bid.depotWishTwo; UserRoles biddingRole; switch (biddingRound) { case 1: biddingRole = Bidding1Role; break; case 2: biddingRole = Bidding2Role; break; case 3: biddingRole = Bidding3Role; break; default: break; } if (biddingRound == 1) { return {{biddingRole, amount}, {DepotWish1Role, depotWishOne}, {DepotWish2Role, depotWishTwo}}; } else { return {{biddingRole, amount}}; } }