diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -664,6 +664,8 @@ {Action::UpdateAccount, QStringLiteral("account_online_update"), i18n("Update account..."), Icon::AccountUpdate}, {Action::UpdateAllAccounts, QStringLiteral("account_online_update_all"), i18n("Update all accounts..."), Icon::AccountUpdateAll}, {Action::AccountCreditTransfer, QStringLiteral("account_online_new_credit_transfer"), i18n("New credit transfer"), Icon::AccountCreditTransfer}, + {Action::AccountSetCustomIcon, QStringLiteral("account_set_custom_icon"), i18n("Set custom icon"), Icon::Empty}, + {Action::AccountRemoveCustomIcon, QStringLiteral("account_remove_custom_icon"), i18n("Remove custom icon"), Icon::Empty}, // ******************* // The categories menu // ******************* diff --git a/kmymoney/kmymoneyui.rc b/kmymoney/kmymoneyui.rc --- a/kmymoney/kmymoneyui.rc +++ b/kmymoney/kmymoneyui.rc @@ -135,6 +135,9 @@ + + + Category options diff --git a/kmymoney/menuenums.h b/kmymoney/menuenums.h --- a/kmymoney/menuenums.h +++ b/kmymoney/menuenums.h @@ -59,6 +59,7 @@ UpdateAccountMenu, UpdateAccount, UpdateAllAccounts, MapOnlineAccount, UnmapOnlineAccount, AccountCreditTransfer, + AccountSetCustomIcon, AccountRemoveCustomIcon, // ************* // The category menu // ************* diff --git a/kmymoney/mymoney/mymoneyaccount.h b/kmymoney/mymoney/mymoneyaccount.h --- a/kmymoney/mymoney/mymoneyaccount.h +++ b/kmymoney/mymoney/mymoneyaccount.h @@ -571,6 +571,41 @@ */ bool hasOnlineMapping() const; + /** + * Assign a custom icon to that account. + * @param pixmap QPixmap represenation of the custom icon + */ + void setCustomIcon(const QPixmap& pixmap); + + /** + * Assign a custom icon to that account. + * @param base64image A base64 encoded representation of the image to be used as the custom icon + */ + void setCustomIcon(const QString& base64image); + + /** + * Get rid of a previously assigned custom icon for this account + */ + void removeCustomIcon(); + + /** + * Retrieve the custom icon for that account. + * @return A QPixmap holding the custom icon assigned to the account. + */ + QPixmap customIcon() const; + + /** + * Retrieve the custom icon for that account. + * @return The image data of the custom icon, encoded in base64 + */ + QString customIconB64() const; + + /** + * Check whether this account has a custom icon assigned. + * @return @c true if account has a custom icon, @c false otherwise + */ + bool hasCustomIcon() const; + QDataStream &operator<<(const MyMoneyAccount &); QDataStream &operator>>(MyMoneyAccount &); }; diff --git a/kmymoney/mymoney/mymoneyaccount.cpp b/kmymoney/mymoney/mymoneyaccount.cpp --- a/kmymoney/mymoney/mymoneyaccount.cpp +++ b/kmymoney/mymoney/mymoneyaccount.cpp @@ -28,6 +28,7 @@ #include #include #include +#include // ---------------------------------------------------------------------------- // KDE Includes @@ -142,6 +143,10 @@ setValue(d->getAttrName(Account::Attribute::IBAN), value("IBAN")); deletePair("IBAN"); } + + if (node.hasAttribute(d->getAttrName(Account::Attribute::CustomIcon)) && !node.attribute(d->getAttrName(Account::Attribute::CustomIcon)).isEmpty()) { + setCustomIcon(node.attribute(d->getAttrName(Account::Attribute::CustomIcon)).toUtf8()); + } } MyMoneyAccount::MyMoneyAccount(const MyMoneyAccount& other) : @@ -327,7 +332,8 @@ (d->m_openingDate == d2->m_openingDate) && (d->m_parentAccount == d2->m_parentAccount) && (d->m_currencyId == d2->m_currencyId) && - (d->m_institution == d2->m_institution)); + (d->m_institution == d2->m_institution) && + (d->m_customIcon.toImage() == d2->m_customIcon.toImage())); } Account::Type MyMoneyAccount::accountGroup() const @@ -478,6 +484,9 @@ //Add in Key-Value Pairs for accounts. MyMoneyKeyValueContainer::writeXML(document, el); + if (!d->m_customIcon.isNull()) + el.setAttribute(d->getAttrName(Account::Attribute::CustomIcon), customIconB64()); + parent.appendChild(el); } @@ -584,6 +593,7 @@ QPixmap MyMoneyAccount::accountPixmap(const bool reconcileFlag, const int size) const { + Q_D(const MyMoneyAccount); static const QHash accToIco { {Account::Type::Asset, Icon::ViewAsset}, {Account::Type::Investment, Icon::ViewStock}, @@ -611,6 +621,9 @@ QPixmapCache::insert(kyIcon, pxIcon); } + if (!d->m_customIcon.isNull()) + pxIcon = d->m_customIcon; + if (isClosed()) ixIcon = Icon::AccountClosed; else if (reconcileFlag) @@ -745,3 +758,46 @@ Q_D(const MyMoneyAccount); return !d->m_onlineBankingSettings.value(QLatin1String("provider")).isEmpty(); } + +void MyMoneyAccount::setCustomIcon(const QPixmap& pixmap) +{ + Q_D(MyMoneyAccount); + if (!pixmap.isNull()) + d->m_customIcon = pixmap; +} + +void MyMoneyAccount::setCustomIcon(const QString& base64image) +{ + Q_D(MyMoneyAccount); + QByteArray iconBytes = QByteArray::fromBase64(base64image.toUtf8()); + d->m_customIcon.loadFromData(iconBytes); +} + +QPixmap MyMoneyAccount::customIcon() const +{ + Q_D(const MyMoneyAccount); + return d->m_customIcon; +} + +QString MyMoneyAccount::customIconB64() const +{ + Q_D(const MyMoneyAccount); + + QByteArray pixelBytes; + QBuffer iconBuffer(&pixelBytes); + d->m_customIcon.save(&iconBuffer, "PNG"); + auto b64string = QString(pixelBytes.toBase64()); + return b64string; +} + +void MyMoneyAccount::removeCustomIcon() +{ + Q_D(MyMoneyAccount); + d->m_customIcon = QPixmap(); +} + +bool MyMoneyAccount::hasCustomIcon() const +{ + Q_D(const MyMoneyAccount); + return !d->m_customIcon.isNull(); +} diff --git a/kmymoney/mymoney/mymoneyaccount_p.h b/kmymoney/mymoney/mymoneyaccount_p.h --- a/kmymoney/mymoney/mymoneyaccount_p.h +++ b/kmymoney/mymoney/mymoneyaccount_p.h @@ -25,6 +25,7 @@ #include #include #include +#include // ---------------------------------------------------------------------------- // KDE Includes @@ -64,6 +65,7 @@ OpeningBalance, IBAN, BIC, + CustomIcon, // insert new entries above this line LastAttribute }; @@ -109,6 +111,7 @@ {Account::Attribute::OpeningBalance, QStringLiteral("openingbalance")}, {Account::Attribute::IBAN, QStringLiteral("iban")}, {Account::Attribute::BIC, QStringLiteral("bic")}, + {Account::Attribute::CustomIcon, QStringLiteral("customicon")}, }; return attrNames[attr]; } @@ -201,6 +204,10 @@ */ QMap m_reconciliationHistory; + /** + * This member keeps a custom icon for this account. + */ + QPixmap m_customIcon; }; #endif diff --git a/kmymoney/mymoney/storage/mymoneydbdef.cpp b/kmymoney/mymoney/storage/mymoneydbdef.cpp --- a/kmymoney/mymoney/storage/mymoneydbdef.cpp +++ b/kmymoney/mymoney/storage/mymoneydbdef.cpp @@ -36,7 +36,7 @@ #include //***************** THE CURRENT VERSION OF THE DATABASE LAYOUT **************** -unsigned int MyMoneyDbDef::m_currentVersion = 12; +unsigned int MyMoneyDbDef::m_currentVersion = 13; // ************************* Build table descriptions **************************** MyMoneyDbDef::MyMoneyDbDef() @@ -214,6 +214,7 @@ appendField(MyMoneyDbTextColumn("balance")); appendField(MyMoneyDbTextColumn("balanceFormatted")); appendField(MyMoneyDbIntColumn("transactionCount", MyMoneyDbIntColumn::BIG, UNSIGNED, false, false, 1)); + appendField(MyMoneyDbTextColumn("customIcon", MyMoneyDbTextColumn::MEDIUM, false, false, 13)); MyMoneyDbTable t("kmmAccounts", fields); t.buildSQLStrings(); m_tables[t.name()] = t; diff --git a/kmymoney/mymoney/storage/mymoneystorageanon.cpp b/kmymoney/mymoney/storage/mymoneystorageanon.cpp --- a/kmymoney/mymoney/storage/mymoneystorageanon.cpp +++ b/kmymoney/mymoney/storage/mymoneystorageanon.cpp @@ -155,6 +155,7 @@ p.setNumber(hideString(p.number())); p.setName(p.id()); p.setDescription(hideString(p.description())); + p.removeCustomIcon(); fakeKeyValuePair(p); // Remove the online banking settings entirely. diff --git a/kmymoney/mymoney/storage/mymoneystoragedump.cpp b/kmymoney/mymoney/storage/mymoneystoragedump.cpp --- a/kmymoney/mymoney/storage/mymoneystoragedump.cpp +++ b/kmymoney/mymoney/storage/mymoneystoragedump.cpp @@ -217,6 +217,7 @@ s << " Last modified = " << (*it_a).lastModified().toString(Qt::ISODate) << "\n"; s << " Last reconciled = " << (*it_a).lastReconciliationDate().toString(Qt::ISODate) << "\n"; s << " Balance = " << (*it_a).balance().formatMoney("", 2) << "\n"; + s << " Custom Icon = " << (*it_a).customIconB64() << "\n"; dumpKVP(" KVP: ", s, *it_a); dumpKVP(" OnlineBankingSettings: ", s, (*it_a).onlineBankingSettings()); diff --git a/kmymoney/mymoney/storage/mymoneystoragesql.cpp b/kmymoney/mymoney/storage/mymoneystoragesql.cpp --- a/kmymoney/mymoney/storage/mymoneystoragesql.cpp +++ b/kmymoney/mymoney/storage/mymoneystoragesql.cpp @@ -1559,6 +1559,7 @@ static const int currencyIdCol = t.fieldNumber("currencyId"); static const int balanceCol = t.fieldNumber("balance"); static const int transactionCountCol = t.fieldNumber("transactionCount"); + static const int customIconCol = t.fieldNumber("customIcon"); while (query.next()) { QString aid; @@ -1579,6 +1580,7 @@ acc.setBalance(MyMoneyMoney(GETSTRING(balanceCol))); const_cast (this)->d_func()->m_transactionCountMap[aid] = (ulong) GETULL(transactionCountCol); + acc.setCustomIcon(GETSTRING(customIconCol)); // Process any key value pair if (idList.empty()) kvpAccountList.append(aid); diff --git a/kmymoney/mymoney/storage/mymoneystoragesql_p.h b/kmymoney/mymoney/storage/mymoneystoragesql_p.h --- a/kmymoney/mymoney/storage/mymoneystoragesql_p.h +++ b/kmymoney/mymoney/storage/mymoneystoragesql_p.h @@ -993,6 +993,7 @@ QVariantList balanceList; QVariantList balanceFormattedList; QVariantList transactionCountList; + QVariantList customIconList; QList > pairs; QList > onlineBankingPairs; @@ -1036,6 +1037,8 @@ } transactionCountList << quint64(m_transactionCountMap[a.id()]); + customIconList << a.customIconB64(); + //MMAccount inherits from KVPContainer AND has a KVPContainer member //so handle both pairs << a.pairs(); @@ -1058,6 +1061,7 @@ query.bindValue(":balance", balanceList); query.bindValue(":balanceFormatted", balanceFormattedList); query.bindValue(":transactionCount", transactionCountList); + query.bindValue(":customIcon", customIconList); if (!query.execBatch()) throw MYMONEYEXCEPTION(buildError(query, Q_FUNC_INFO, QString("writing Account"))); @@ -2094,6 +2098,10 @@ if ((rc = upgradeToV12()) != 0) return (1); ++m_dbVersion; break; + case 12: + if ((rc = upgradeToV13()) != 0) return (1); + ++m_dbVersion; + break; default: qWarning("Unknown version number in database - %d", m_dbVersion); } @@ -2459,6 +2467,20 @@ return 0; } + int upgradeToV13() + { + Q_Q(MyMoneyStorageSql); + MyMoneyDbTransaction dbtrans(*q, Q_FUNC_INFO); + + QSqlQuery query(*q); + + // add customIcon column to kmmAccounts + if (!alterTable(m_db.m_tables["kmmAccounts"], m_dbVersion)) { + return 1; + } + return 0; + } + int createTables() { Q_Q(MyMoneyStorageSql); diff --git a/kmymoney/views/kaccountsview.h b/kmymoney/views/kaccountsview.h --- a/kmymoney/views/kaccountsview.h +++ b/kmymoney/views/kaccountsview.h @@ -83,6 +83,8 @@ void slotChartAccountBalance(); void slotNewCategory(); void slotNewPayee(const QString& nameBase, QString& id); + void slotSetCustomIcon(); + void slotRemoveCustomIcon(); }; #endif diff --git a/kmymoney/views/kaccountsview.cpp b/kmymoney/views/kaccountsview.cpp --- a/kmymoney/views/kaccountsview.cpp +++ b/kmymoney/views/kaccountsview.cpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include // ---------------------------------------------------------------------------- // KDE Includes @@ -46,12 +48,14 @@ KAccountsView::KAccountsView(QWidget *parent) : KMyMoneyAccountsViewBase(*new KAccountsViewPrivate(this), parent) { - connect(pActions[eMenu::Action::NewAccount], &QAction::triggered, this, &KAccountsView::slotNewAccount); - connect(pActions[eMenu::Action::EditAccount], &QAction::triggered, this, &KAccountsView::slotEditAccount); - connect(pActions[eMenu::Action::DeleteAccount], &QAction::triggered, this, &KAccountsView::slotDeleteAccount); - connect(pActions[eMenu::Action::CloseAccount], &QAction::triggered, this, &KAccountsView::slotCloseAccount); - connect(pActions[eMenu::Action::ReopenAccount], &QAction::triggered, this, &KAccountsView::slotReopenAccount); - connect(pActions[eMenu::Action::ChartAccountBalance], &QAction::triggered, this, &KAccountsView::slotChartAccountBalance); + connect(pActions[eMenu::Action::NewAccount], &QAction::triggered, this, &KAccountsView::slotNewAccount); + connect(pActions[eMenu::Action::EditAccount], &QAction::triggered, this, &KAccountsView::slotEditAccount); + connect(pActions[eMenu::Action::DeleteAccount], &QAction::triggered, this, &KAccountsView::slotDeleteAccount); + connect(pActions[eMenu::Action::CloseAccount], &QAction::triggered, this, &KAccountsView::slotCloseAccount); + connect(pActions[eMenu::Action::ReopenAccount], &QAction::triggered, this, &KAccountsView::slotReopenAccount); + connect(pActions[eMenu::Action::ChartAccountBalance], &QAction::triggered, this, &KAccountsView::slotChartAccountBalance); + connect(pActions[eMenu::Action::AccountSetCustomIcon], &QAction::triggered, this, &KAccountsView::slotSetCustomIcon); + connect(pActions[eMenu::Action::AccountRemoveCustomIcon], &QAction::triggered, this, &KAccountsView::slotRemoveCustomIcon); } KAccountsView::~KAccountsView() @@ -117,7 +121,8 @@ eMenu::Action::NewAccount, eMenu::Action::EditAccount, eMenu::Action::DeleteAccount, eMenu::Action::CloseAccount, eMenu::Action::ReopenAccount, eMenu::Action::ChartAccountBalance, - eMenu::Action::UnmapOnlineAccount, eMenu::Action::MapOnlineAccount, eMenu::Action::UpdateAccount + eMenu::Action::UnmapOnlineAccount, eMenu::Action::MapOnlineAccount, eMenu::Action::UpdateAccount, + eMenu::Action::AccountRemoveCustomIcon, eMenu::Action::AccountSetCustomIcon }; for (const auto& a : actionsToBeDisabled) @@ -174,6 +179,10 @@ } else { pActions[eMenu::Action::MapOnlineAccount]->setEnabled(d->m_onlinePlugins && !d->m_onlinePlugins->isEmpty()); } + + if (d->m_currentAccount.hasCustomIcon()) + pActions[eMenu::Action::AccountRemoveCustomIcon]->setEnabled(true); + pActions[eMenu::Action::AccountSetCustomIcon]->setEnabled(true); break; } @@ -344,3 +353,16 @@ { KMyMoneyUtils::newPayee(nameBase, id); } + +void KAccountsView::slotSetCustomIcon() +{ + Q_D(KAccountsView); + auto fileName = QFileDialog::getOpenFileName(this, i18n("Select icon"), "", i18n("Image Files (*.png *.jpg *.jpeg *.bmp *.ico)")); + d->setCustomIcon(fileName); +} + +void KAccountsView::slotRemoveCustomIcon() +{ + Q_D(KAccountsView); + d->removeCustomIcon(); +} diff --git a/kmymoney/views/kaccountsview_p.h b/kmymoney/views/kaccountsview_p.h --- a/kmymoney/views/kaccountsview_p.h +++ b/kmymoney/views/kaccountsview_p.h @@ -23,6 +23,7 @@ // QT Includes #include +#include // ---------------------------------------------------------------------------- // KDE Includes @@ -311,6 +312,36 @@ } } + void setCustomIcon(const QString filename) + { + const auto file = MyMoneyFile::instance(); + if (file->isStandardAccount(m_currentAccount.id())) + return; + + if (!filename.isNull()) { + QPixmap pixmap = QPixmap(); + pixmap.load(filename); + if (!pixmap.isNull()) { + MyMoneyFileTransaction ft; + m_currentAccount.setCustomIcon(pixmap); + file->modifyAccount(m_currentAccount); + ft.commit(); + } + } + } + + void removeCustomIcon() + { + const auto file = MyMoneyFile::instance(); + if (file->isStandardAccount(m_currentAccount.id())) + return; + + MyMoneyFileTransaction ft; + m_currentAccount.removeCustomIcon(); + file->modifyAccount(m_currentAccount); + ft.commit(); + } + KAccountsView *q_ptr; Ui::KAccountsView *ui; bool m_haveUnusedCategories;