File indexing completed on 2024-05-19 05:08:32

0001 /*
0002     SPDX-FileCopyrightText: 2003-2018 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "kmymoneyaccountselector.h"
0008 #include "kmymoneyselector_p.h"
0009 
0010 // ----------------------------------------------------------------------------
0011 // QT Includes
0012 
0013 #include <QIcon>
0014 #include <QList>
0015 #include <QPixmapCache>
0016 #include <QPushButton>
0017 #include <QVBoxLayout>
0018 
0019 // ----------------------------------------------------------------------------
0020 // KDE Includes
0021 
0022 #include <KLocalizedString>
0023 
0024 // ----------------------------------------------------------------------------
0025 // Project Includes
0026 
0027 #include "dialogenums.h"
0028 #include "icons.h"
0029 #include "mymoneyaccount.h"
0030 #include "mymoneyenums.h"
0031 #include "mymoneyfile.h"
0032 #include "widgetenums.h"
0033 
0034 using namespace Icons;
0035 using namespace eMyMoney;
0036 
0037 class KMyMoneyAccountSelectorPrivate : public KMyMoneySelectorPrivate
0038 {
0039     Q_DISABLE_COPY(KMyMoneyAccountSelectorPrivate)
0040 
0041 public:
0042     KMyMoneyAccountSelectorPrivate(KMyMoneyAccountSelector *qq) :
0043         KMyMoneySelectorPrivate(qq),
0044         m_allAccountsButton(0),
0045         m_noAccountButton(0),
0046         m_incomeCategoriesButton(0),
0047         m_expenseCategoriesButton(0)
0048     {
0049     }
0050 
0051     QPushButton*              m_allAccountsButton;
0052     QPushButton*              m_noAccountButton;
0053     QPushButton*              m_incomeCategoriesButton;
0054     QPushButton*              m_expenseCategoriesButton;
0055     QList<int>                m_typeList;
0056     QStringList               m_accountList;
0057 };
0058 
0059 KMyMoneyAccountSelector::KMyMoneyAccountSelector(QWidget *parent, Qt::WindowFlags flags, const bool createButtons) :
0060     KMyMoneySelector(*new KMyMoneyAccountSelectorPrivate(this), parent, flags)
0061 {
0062     Q_D(KMyMoneyAccountSelector);
0063     if (createButtons) {
0064         QVBoxLayout* buttonLayout = new QVBoxLayout();
0065         buttonLayout->setSpacing(6);
0066 
0067         d->m_allAccountsButton = new QPushButton(this);
0068         d->m_allAccountsButton->setObjectName("m_allAccountsButton");
0069         d->m_allAccountsButton->setText(i18nc("Select all accounts", "All"));
0070         buttonLayout->addWidget(d->m_allAccountsButton);
0071 
0072         d->m_incomeCategoriesButton = new QPushButton(this);
0073         d->m_incomeCategoriesButton->setObjectName("m_incomeCategoriesButton");
0074         d->m_incomeCategoriesButton->setText(i18n("Income"));
0075         buttonLayout->addWidget(d->m_incomeCategoriesButton);
0076 
0077         d->m_expenseCategoriesButton = new QPushButton(this);
0078         d->m_expenseCategoriesButton->setObjectName("m_expenseCategoriesButton");
0079         d->m_expenseCategoriesButton->setText(i18n("Expense"));
0080         buttonLayout->addWidget(d->m_expenseCategoriesButton);
0081 
0082         d->m_noAccountButton = new QPushButton(this);
0083         d->m_noAccountButton->setObjectName("m_noAccountButton");
0084         d->m_noAccountButton->setText(i18nc("No account", "None"));
0085         buttonLayout->addWidget(d->m_noAccountButton);
0086 
0087         QSpacerItem* spacer = new QSpacerItem(0, 67, QSizePolicy::Minimum, QSizePolicy::Expanding);
0088         buttonLayout->addItem(spacer);
0089         d->m_layout->addLayout(buttonLayout);
0090 
0091         connect(d->m_allAccountsButton, &QAbstractButton::clicked, this, &KMyMoneyAccountSelector::slotSelectAllAccounts);
0092         connect(d->m_noAccountButton, &QAbstractButton::clicked, this, &KMyMoneyAccountSelector::slotDeselectAllAccounts);
0093         connect(d->m_incomeCategoriesButton, &QAbstractButton::clicked, this, &KMyMoneyAccountSelector::slotSelectIncomeCategories);
0094         connect(d->m_expenseCategoriesButton, &QAbstractButton::clicked, this, &KMyMoneyAccountSelector::slotSelectExpenseCategories);
0095     }
0096 }
0097 
0098 KMyMoneyAccountSelector::~KMyMoneyAccountSelector()
0099 {
0100 }
0101 
0102 void KMyMoneyAccountSelector::removeButtons()
0103 {
0104     Q_D(KMyMoneyAccountSelector);
0105     delete d->m_allAccountsButton;
0106     delete d->m_incomeCategoriesButton;
0107     delete d->m_expenseCategoriesButton;
0108     delete d->m_noAccountButton;
0109 }
0110 
0111 void KMyMoneyAccountSelector::slotSelectAllAccounts()
0112 {
0113     selectAllItems(true);
0114 }
0115 
0116 void KMyMoneyAccountSelector::slotDeselectAllAccounts()
0117 {
0118     selectAllItems(false);
0119 }
0120 
0121 void KMyMoneyAccountSelector::selectCategories(const bool income, const bool expense)
0122 {
0123     Q_D(KMyMoneyAccountSelector);
0124     QTreeWidgetItemIterator it_v(d->m_treeWidget);
0125 
0126     for (; *it_v != 0; ++it_v) {
0127         if ((*it_v)->text(0) == i18n("Income categories"))
0128             selectAllSubItems(*it_v, income);
0129         else if ((*it_v)->text(0) == i18n("Expense categories"))
0130             selectAllSubItems(*it_v, expense);
0131     }
0132     Q_EMIT stateChanged();
0133 }
0134 
0135 void KMyMoneyAccountSelector::slotSelectIncomeCategories()
0136 {
0137     selectCategories(true, false);
0138 }
0139 
0140 void KMyMoneyAccountSelector::slotSelectExpenseCategories()
0141 {
0142     selectCategories(false, true);
0143 }
0144 
0145 void KMyMoneyAccountSelector::setSelectionMode(QTreeWidget::SelectionMode mode)
0146 {
0147     Q_D(KMyMoneyAccountSelector);
0148     d->m_incomeCategoriesButton->setHidden(mode == QTreeWidget::MultiSelection);
0149     d->m_expenseCategoriesButton->setHidden(mode == QTreeWidget::MultiSelection);
0150     KMyMoneySelector::setSelectionMode(mode);
0151 }
0152 
0153 QStringList KMyMoneyAccountSelector::accountList(const  QList<Account::Type>& filterList) const
0154 {
0155     Q_D(const KMyMoneyAccountSelector);
0156     QStringList    list;
0157     QTreeWidgetItemIterator it(d->m_treeWidget, QTreeWidgetItemIterator::Selectable);
0158 
0159     while (*it) {
0160         QVariant id = (*it)->data(0, (int)eWidgets::Selector::Role::Id);
0161         MyMoneyAccount acc = MyMoneyFile::instance()->account(id.toString());
0162         if (filterList.count() == 0 || filterList.contains(acc.accountType()))
0163             list << id.toString();
0164         it++;
0165     }
0166     return list;
0167 }
0168 
0169 QStringList KMyMoneyAccountSelector::accountList() const
0170 {
0171     return accountList(QList<eMyMoney::Account::Type>());
0172 }
0173 
0174 bool KMyMoneyAccountSelector::match(const QRegularExpression& exp, QTreeWidgetItem* item) const
0175 {
0176     if (!item->flags().testFlag(Qt::ItemIsSelectable))
0177         return false;
0178     const auto matchingItem(exp.match(item->data(0, static_cast<int>(eWidgets::Selector::Role::Key)).toString().mid(1)));
0179     return matchingItem.hasMatch();
0180 }
0181 
0182 bool KMyMoneyAccountSelector::contains(const QString& txt) const
0183 {
0184     Q_D(const KMyMoneyAccountSelector);
0185     QTreeWidgetItemIterator it(d->m_treeWidget, QTreeWidgetItemIterator::Selectable);
0186     QTreeWidgetItem* it_v;
0187 
0188     QString baseName = i18n("Asset") + '|' +
0189                        i18n("Liability") + '|' +
0190                        i18n("Income") + '|' +
0191                        i18n("Expense") + '|' +
0192                        i18n("Equity") + '|' +
0193                        i18n("Security");
0194 
0195     while ((it_v = *it) != 0) {
0196         const QRegularExpression exp(QString("^(?:%1):%2$").arg(baseName).arg(QRegularExpression::escape(txt)));
0197         const auto matchingItem(exp.match(it_v->data(0, static_cast<int>(eWidgets::Selector::Role::Key)).toString().mid(1)));
0198         if (matchingItem.hasMatch()) {
0199             return true;
0200         }
0201         it++;
0202     }
0203     return false;
0204 }
0205 
0206 QSize KMyMoneyAccountSelector::sizeHint() const
0207 {
0208     auto size = KMyMoneySelector::sizeHint();
0209     size.setWidth(400);
0210     return size;
0211 }
0212 
0213 class AccountSetPrivate
0214 {
0215     Q_DISABLE_COPY(AccountSetPrivate)
0216 
0217 public:
0218     AccountSetPrivate()
0219         : m_count(0)
0220         , m_file(MyMoneyFile::instance())
0221         , m_favorites(0)
0222         , m_hideClosedAccounts(true)
0223         , m_showInvestments(false)
0224     {
0225     }
0226 
0227     int                      m_count;
0228     MyMoneyFile*             m_file;
0229     QList<eMyMoney::Account::Type> m_typeList;
0230     QTreeWidgetItem*         m_favorites;
0231     bool                     m_hideClosedAccounts;
0232     bool                     m_showInvestments;
0233 };
0234 
0235 AccountSet::AccountSet() :
0236     d_ptr(new AccountSetPrivate)
0237 {
0238 }
0239 
0240 AccountSet::~AccountSet()
0241 {
0242     Q_D(AccountSet);
0243     delete d;
0244 }
0245 
0246 void AccountSet::setShowInvestments(bool show)
0247 {
0248     Q_D(AccountSet);
0249     d->m_showInvestments = show;
0250 }
0251 
0252 void AccountSet::addAccountGroup(Account::Type group)
0253 {
0254     Q_D(AccountSet);
0255     if (group == Account::Type::Asset) {
0256         d->m_typeList << Account::Type::Checkings;
0257         d->m_typeList << Account::Type::Savings;
0258         d->m_typeList << Account::Type::Cash;
0259         d->m_typeList << Account::Type::AssetLoan;
0260         d->m_typeList << Account::Type::CertificateDep;
0261         d->m_typeList << Account::Type::Investment;
0262         d->m_typeList << Account::Type::Stock;
0263         d->m_typeList << Account::Type::MoneyMarket;
0264         d->m_typeList << Account::Type::Asset;
0265         d->m_typeList << Account::Type::Currency;
0266 
0267     } else if (group == Account::Type::Liability) {
0268         d->m_typeList << Account::Type::CreditCard;
0269         d->m_typeList << Account::Type::Loan;
0270         d->m_typeList << Account::Type::Liability;
0271 
0272     } else if (group == Account::Type::Income) {
0273         d->m_typeList << Account::Type::Income;
0274 
0275     } else if (group == Account::Type::Expense) {
0276         d->m_typeList << Account::Type::Expense;
0277 
0278     } else if (group == Account::Type::Equity) {
0279         d->m_typeList << Account::Type::Equity;
0280     }
0281 }
0282 
0283 void AccountSet::addAccountType(Account::Type type)
0284 {
0285     Q_D(AccountSet);
0286     d->m_typeList << type;
0287 }
0288 
0289 void AccountSet::removeAccountType(Account::Type type)
0290 {
0291     Q_D(AccountSet);
0292     int index = d->m_typeList.indexOf(type);
0293     if (index != -1) {
0294         d->m_typeList.removeAt(index);
0295     }
0296 }
0297 
0298 void AccountSet::clear()
0299 {
0300     Q_D(AccountSet);
0301     d->m_typeList.clear();
0302 }
0303 
0304 int AccountSet::load(KMyMoneyAccountSelector* selector)
0305 {
0306     Q_D(AccountSet);
0307     QStringList list;
0308     QStringList::ConstIterator it_l;
0309     int count = 0;
0310     int typeMask = 0;
0311     QString currentId;
0312 
0313     if (selector->selectionMode() == QTreeWidget::SingleSelection) {
0314         selector->selectedItems(list);
0315         if (!list.isEmpty())
0316             currentId = list.first();
0317     }
0318     // clang-format off
0319     if (d->m_typeList.contains(Account::Type::Checkings)
0320             || d->m_typeList.contains(Account::Type::Savings)
0321             || d->m_typeList.contains(Account::Type::Cash)
0322             || d->m_typeList.contains(Account::Type::AssetLoan)
0323             || d->m_typeList.contains(Account::Type::CertificateDep)
0324             || d->m_typeList.contains(Account::Type::Investment)
0325             || d->m_typeList.contains(Account::Type::Stock)
0326             || d->m_typeList.contains(Account::Type::MoneyMarket)
0327             || d->m_typeList.contains(Account::Type::Asset)
0328             || d->m_typeList.contains(Account::Type::Currency))
0329         typeMask |= eDialogs::Category::asset;
0330 
0331     if (d->m_typeList.contains(Account::Type::CreditCard)
0332             || d->m_typeList.contains(Account::Type::Loan)
0333             || d->m_typeList.contains(Account::Type::Liability))
0334         typeMask |= eDialogs::Category::liability;
0335     // clang-format on
0336 
0337     if (d->m_typeList.contains(Account::Type::Income))
0338         typeMask |= eDialogs::Category::income;
0339 
0340     if (d->m_typeList.contains(Account::Type::Expense))
0341         typeMask |= eDialogs::Category::expense;
0342 
0343     if (d->m_typeList.contains(Account::Type::Equity))
0344         typeMask |= eDialogs::Category::equity;
0345 
0346     selector->clear();
0347     QTreeWidget* lv = selector->listView();
0348     d->m_count = 0;
0349     QString key;
0350     QTreeWidgetItem* after = 0;
0351 
0352     // create the favorite section first and sort it to the beginning
0353     key = QString("A%1").arg(i18n("Favorites"));
0354     d->m_favorites = selector->newItem(i18n("Favorites"), key);
0355 
0356     //get the account icon from cache or insert it if it is not there
0357     QPixmap accountPixmap;
0358     if (!QPixmapCache::find("account", &accountPixmap)) {
0359         QIcon icon = Icons::get(Icon::BankAccount);
0360         if (!icon.availableSizes().isEmpty())
0361             accountPixmap = icon.pixmap(icon.availableSizes().first());
0362         QPixmapCache::insert("account", accountPixmap);
0363     }
0364     d->m_favorites->setIcon(0, QIcon(accountPixmap));
0365 
0366     for (auto mask = 0x01; mask != eDialogs::Category::last; mask <<= 1) {
0367         QTreeWidgetItem* item = 0;
0368         if ((typeMask & mask & eDialogs::Category::asset) != 0) {
0369             ++d->m_count;
0370             key = QString("B%1").arg(i18n("Asset"));
0371             item = selector->newItem(i18n("Asset accounts"), key);
0372             item->setIcon(0, d->m_file->asset().accountIcon());
0373             list = d->m_file->asset().accountList();
0374         }
0375 
0376         if ((typeMask & mask & eDialogs::Category::liability) != 0) {
0377             ++d->m_count;
0378             key = QString("C%1").arg(i18n("Liability"));
0379             item = selector->newItem(i18n("Liability accounts"), key);
0380             item->setIcon(0, d->m_file->liability().accountIcon());
0381             list = d->m_file->liability().accountList();
0382         }
0383 
0384         if ((typeMask & mask & eDialogs::Category::income) != 0) {
0385             ++d->m_count;
0386             key = QString("D%1").arg(i18n("Income"));
0387             item = selector->newItem(i18n("Income categories"), key);
0388             item->setIcon(0, d->m_file->income().accountIcon());
0389             list = d->m_file->income().accountList();
0390             if (selector->selectionMode() == QTreeWidget::MultiSelection) {
0391                 selector->d_func()->m_incomeCategoriesButton->show();
0392             }
0393         }
0394 
0395         if ((typeMask & mask & eDialogs::Category::expense) != 0) {
0396             ++d->m_count;
0397             key = QString("E%1").arg(i18n("Expense"));
0398             item = selector->newItem(i18n("Expense categories"), key);
0399             item->setIcon(0, d->m_file->expense().accountIcon());
0400             list = d->m_file->expense().accountList();
0401             if (selector->selectionMode() == QTreeWidget::MultiSelection) {
0402                 selector->d_func()->m_expenseCategoriesButton->show();
0403             }
0404         }
0405 
0406         if ((typeMask & mask & eDialogs::Category::equity) != 0) {
0407             ++d->m_count;
0408             key = QString("F%1").arg(i18n("Equity"));
0409             item = selector->newItem(i18n("Equity accounts"), key);
0410             item->setIcon(0, d->m_file->equity().accountIcon());
0411             list = d->m_file->equity().accountList();
0412         }
0413 
0414         if (!after)
0415             after = item;
0416 
0417         if (item != 0) {
0418             // scan all matching accounts found in the engine
0419             for (it_l = list.constBegin(); it_l != list.constEnd(); ++it_l) {
0420                 const MyMoneyAccount& acc = d->m_file->account(*it_l);
0421                 ++d->m_count;
0422                 ++count;
0423                 //this will include an account if it matches the account type and
0424                 //if it is still open or it has been set to show closed accounts
0425                 if (includeAccount(acc)
0426                         && (!isHidingClosedAccounts() || !acc.isClosed())) {
0427                     QString tmpKey;
0428                     tmpKey = key + MyMoneyFile::AccountSeparator + acc.name();
0429                     QTreeWidgetItem* subItem = selector->newItem(item, acc.name(), tmpKey, acc.id());
0430                     subItem->setIcon(0, acc.accountIcon());
0431                     if (acc.value("PreferredAccount", false) && d->m_typeList.contains(acc.accountType())) {
0432                         selector->newItem(d->m_favorites, acc.name(), tmpKey, acc.id())->setIcon(0, acc.accountIcon());
0433                     }
0434                     if (acc.accountList().count() > 0) {
0435                         subItem->setExpanded(true);
0436                         count += loadSubAccounts(selector, subItem, tmpKey, acc.accountList());
0437                     }
0438 
0439                     // the item is not selectable if it has been added only because a subaccount matches the type
0440                     if (!d->m_typeList.contains(acc.accountType())) {
0441                         selector->setSelectable(subItem, false);
0442                     }
0443                     subItem->sortChildren(1, Qt::AscendingOrder);
0444                 }
0445             }
0446             item->sortChildren(1, Qt::AscendingOrder);
0447         }
0448     }
0449     d->m_favorites->sortChildren(1, Qt::AscendingOrder);
0450     lv->invisibleRootItem()->sortChildren(1, Qt::AscendingOrder);
0451 
0452     // if we don't have a favorite account or the selector is for multi-mode
0453     // we get rid of the favorite entry and subentries.
0454     if (d->m_favorites->childCount() == 0 || selector->selectionMode() == QTreeWidget::MultiSelection) {
0455         delete d->m_favorites;
0456         d->m_favorites = 0;
0457     }
0458 
0459     if (lv->itemAt(0, 0)) {
0460         if (currentId.isEmpty()) {
0461             lv->setCurrentItem(lv->itemAt(0, 0));
0462             lv->clearSelection();
0463         } else {
0464             selector->setSelected(currentId);
0465         }
0466     }
0467     selector->update();
0468     return count;
0469 }
0470 
0471 int AccountSet::load(KMyMoneyAccountSelector* selector, const QString& baseName, const QList<QString>& accountIdList, const bool clear)
0472 {
0473     Q_D(AccountSet);
0474     int count = 0;
0475     QTreeWidgetItem* item = 0;
0476 
0477     d->m_typeList.clear();
0478     if (clear) {
0479         d->m_count = 0;
0480         selector->clear();
0481     }
0482 
0483     item = selector->newItem(baseName);
0484     ++d->m_count;
0485 
0486     QList<QString>::ConstIterator it;
0487     for (it = accountIdList.constBegin(); it != accountIdList.constEnd(); ++it)   {
0488         const MyMoneyAccount& acc = d->m_file->account(*it);
0489         if (acc.isClosed())
0490             continue;
0491         QString tmpKey;
0492         // the first character must be preset. Since we don't know any sort order here, we just use A
0493         tmpKey = QString("A%1%2%3").arg(baseName, MyMoneyFile::AccountSeparator, acc.name());
0494         selector->newItem(item, acc.name(), tmpKey, acc.id())->setIcon(0, acc.accountIcon());
0495         ++d->m_count;
0496         ++count;
0497     }
0498 
0499     QTreeWidget* lv = selector->listView();
0500     if (lv->itemAt(0, 0)) {
0501         lv->setCurrentItem(lv->itemAt(0, 0));
0502         lv->clearSelection();
0503     }
0504 
0505     selector->update();
0506     return count;
0507 }
0508 
0509 int AccountSet::count() const
0510 {
0511     Q_D(const AccountSet);
0512     return d->m_count;
0513 }
0514 
0515 void AccountSet::setHideClosedAccounts(bool _bool)
0516 {
0517     Q_D(AccountSet);
0518     d->m_hideClosedAccounts = _bool;
0519 }
0520 bool AccountSet::isHidingClosedAccounts() const
0521 {
0522     Q_D(const AccountSet);
0523     return d->m_hideClosedAccounts;
0524 }
0525 
0526 int AccountSet::loadSubAccounts(KMyMoneyAccountSelector* selector, QTreeWidgetItem* parent, const QString& key, const QStringList& list)
0527 {
0528     Q_D(AccountSet);
0529     QStringList::ConstIterator it_l;
0530     int count = 0;
0531 
0532     for (it_l = list.constBegin(); it_l != list.constEnd(); ++it_l) {
0533         const MyMoneyAccount& acc = d->m_file->account(*it_l);
0534         // don't include stock accounts if not in expert mode
0535         if (acc.isInvest() && !d->m_showInvestments)
0536             continue;
0537 
0538         //this will include an account if it matches the account type and
0539         //if it is still open or it has been set to show closed accounts
0540         if (includeAccount(acc)
0541                 && (!isHidingClosedAccounts() || !acc.isClosed())) {
0542             QString tmpKey;
0543             tmpKey = key + MyMoneyFile::AccountSeparator + acc.name();
0544             ++count;
0545             ++d->m_count;
0546             QTreeWidgetItem* item = selector->newItem(parent, acc.name(), tmpKey, acc.id());
0547             item->setIcon(0, acc.accountIcon());
0548             if (acc.value("PreferredAccount", false) && d->m_typeList.contains(acc.accountType())) {
0549                 selector->newItem(d->m_favorites, acc.name(), tmpKey, acc.id())->setIcon(0, acc.accountIcon());
0550             }
0551             if (acc.accountList().count() > 0) {
0552                 item->setExpanded(true);
0553                 count += loadSubAccounts(selector, item, tmpKey, acc.accountList());
0554             }
0555 
0556             // the item is not selectable if it has been added only because a subaccount matches the type
0557             if (!d->m_typeList.contains(acc.accountType())) {
0558                 selector->setSelectable(item, false);
0559             }
0560             item->sortChildren(1, Qt::AscendingOrder);
0561         }
0562     }
0563     return count;
0564 }
0565 
0566 bool AccountSet::includeAccount(const MyMoneyAccount& acc)
0567 {
0568     Q_D(AccountSet);
0569     if (d->m_typeList.contains(acc.accountType()))
0570         return true;
0571 
0572     for (const auto& sAccount : acc.accountList())
0573         if (includeAccount(d->m_file->account(sAccount)))
0574             return true;
0575 
0576     return false;
0577 }