File indexing completed on 2024-05-12 16:44:00

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 <QList>
0014 #include <QVBoxLayout>
0015 #include <QPixmapCache>
0016 #include <QPushButton>
0017 #include <QIcon>
0018 
0019 // ----------------------------------------------------------------------------
0020 // KDE Includes
0021 
0022 #include <KLocalizedString>
0023 
0024 // ----------------------------------------------------------------------------
0025 // Project Includes
0026 
0027 #include "mymoneyfile.h"
0028 #include "mymoneyaccount.h"
0029 #include "icons/icons.h"
0030 #include "mymoneyenums.h"
0031 #include "dialogenums.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     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 QRegExp& exp, QTreeWidgetItem* item) const
0175 {
0176     if (!item->flags().testFlag(Qt::ItemIsSelectable))
0177         return false;
0178     return exp.indexIn(item->data(0, (int)eWidgets::Selector::Role::Key).toString().mid(1)) != -1;
0179 }
0180 
0181 bool KMyMoneyAccountSelector::contains(const QString& txt) const
0182 {
0183     Q_D(const KMyMoneyAccountSelector);
0184     QTreeWidgetItemIterator it(d->m_treeWidget, QTreeWidgetItemIterator::Selectable);
0185     QTreeWidgetItem* it_v;
0186 
0187     QString baseName = i18n("Asset") + '|' +
0188                        i18n("Liability") + '|' +
0189                        i18n("Income") + '|' +
0190                        i18n("Expense") + '|' +
0191                        i18n("Equity") + '|' +
0192                        i18n("Security");
0193 
0194     while ((it_v = *it) != 0) {
0195         QRegExp exp(QString("^(?:%1):%2$").arg(baseName).arg(QRegExp::escape(txt)));
0196         if (exp.indexIn(it_v->data(0, (int)eWidgets::Selector::Role::Key).toString().mid(1)) != -1) {
0197             return true;
0198         }
0199         it++;
0200     }
0201     return false;
0202 }
0203 
0204 class AccountSetPrivate
0205 {
0206     Q_DISABLE_COPY(AccountSetPrivate)
0207 
0208 public:
0209     AccountSetPrivate()
0210         : m_count(0)
0211         , m_file(MyMoneyFile::instance())
0212         , m_favorites(0)
0213         , m_hideClosedAccounts(true)
0214         , m_showInvestments(false)
0215     {
0216     }
0217 
0218     int                      m_count;
0219     MyMoneyFile*             m_file;
0220     QList<eMyMoney::Account::Type> m_typeList;
0221     QTreeWidgetItem*         m_favorites;
0222     bool                     m_hideClosedAccounts;
0223     bool                     m_showInvestments;
0224 };
0225 
0226 AccountSet::AccountSet() :
0227     d_ptr(new AccountSetPrivate)
0228 {
0229 }
0230 
0231 AccountSet::~AccountSet()
0232 {
0233     Q_D(AccountSet);
0234     delete d;
0235 }
0236 
0237 void AccountSet::setShowInvestments(bool show)
0238 {
0239     Q_D(AccountSet);
0240     d->m_showInvestments = show;
0241 }
0242 
0243 void AccountSet::addAccountGroup(Account::Type group)
0244 {
0245     Q_D(AccountSet);
0246     if (group == Account::Type::Asset) {
0247         d->m_typeList << Account::Type::Checkings;
0248         d->m_typeList << Account::Type::Savings;
0249         d->m_typeList << Account::Type::Cash;
0250         d->m_typeList << Account::Type::AssetLoan;
0251         d->m_typeList << Account::Type::CertificateDep;
0252         d->m_typeList << Account::Type::Investment;
0253         d->m_typeList << Account::Type::Stock;
0254         d->m_typeList << Account::Type::MoneyMarket;
0255         d->m_typeList << Account::Type::Asset;
0256         d->m_typeList << Account::Type::Currency;
0257 
0258     } else if (group == Account::Type::Liability) {
0259         d->m_typeList << Account::Type::CreditCard;
0260         d->m_typeList << Account::Type::Loan;
0261         d->m_typeList << Account::Type::Liability;
0262 
0263     } else if (group == Account::Type::Income) {
0264         d->m_typeList << Account::Type::Income;
0265 
0266     } else if (group == Account::Type::Expense) {
0267         d->m_typeList << Account::Type::Expense;
0268 
0269     } else if (group == Account::Type::Equity) {
0270         d->m_typeList << Account::Type::Equity;
0271     }
0272 }
0273 
0274 void AccountSet::addAccountType(Account::Type type)
0275 {
0276     Q_D(AccountSet);
0277     d->m_typeList << type;
0278 }
0279 
0280 void AccountSet::removeAccountType(Account::Type type)
0281 {
0282     Q_D(AccountSet);
0283     int index = d->m_typeList.indexOf(type);
0284     if (index != -1) {
0285         d->m_typeList.removeAt(index);
0286     }
0287 }
0288 
0289 void AccountSet::clear()
0290 {
0291     Q_D(AccountSet);
0292     d->m_typeList.clear();
0293 }
0294 
0295 int AccountSet::load(KMyMoneyAccountSelector* selector)
0296 {
0297     Q_D(AccountSet);
0298     QStringList list;
0299     QStringList::ConstIterator it_l;
0300     int count = 0;
0301     int typeMask = 0;
0302     QString currentId;
0303 
0304     if (selector->selectionMode() == QTreeWidget::SingleSelection) {
0305         selector->selectedItems(list);
0306         if (!list.isEmpty())
0307             currentId = list.first();
0308     }
0309     // clang-format off
0310     if (d->m_typeList.contains(Account::Type::Checkings)
0311             || d->m_typeList.contains(Account::Type::Savings)
0312             || d->m_typeList.contains(Account::Type::Cash)
0313             || d->m_typeList.contains(Account::Type::AssetLoan)
0314             || d->m_typeList.contains(Account::Type::CertificateDep)
0315             || d->m_typeList.contains(Account::Type::Investment)
0316             || d->m_typeList.contains(Account::Type::Stock)
0317             || d->m_typeList.contains(Account::Type::MoneyMarket)
0318             || d->m_typeList.contains(Account::Type::Asset)
0319             || d->m_typeList.contains(Account::Type::Currency))
0320         typeMask |= eDialogs::Category::asset;
0321 
0322     if (d->m_typeList.contains(Account::Type::CreditCard)
0323             || d->m_typeList.contains(Account::Type::Loan)
0324             || d->m_typeList.contains(Account::Type::Liability))
0325         typeMask |= eDialogs::Category::liability;
0326     // clang-format on
0327 
0328     if (d->m_typeList.contains(Account::Type::Income))
0329         typeMask |= eDialogs::Category::income;
0330 
0331     if (d->m_typeList.contains(Account::Type::Expense))
0332         typeMask |= eDialogs::Category::expense;
0333 
0334     if (d->m_typeList.contains(Account::Type::Equity))
0335         typeMask |= eDialogs::Category::equity;
0336 
0337     selector->clear();
0338     QTreeWidget* lv = selector->listView();
0339     d->m_count = 0;
0340     QString key;
0341     QTreeWidgetItem* after = 0;
0342 
0343     // create the favorite section first and sort it to the beginning
0344     key = QString("A%1").arg(i18n("Favorites"));
0345     d->m_favorites = selector->newItem(i18n("Favorites"), key);
0346 
0347     //get the account icon from cache or insert it if it is not there
0348     QPixmap accountPixmap;
0349     if (!QPixmapCache::find("account", accountPixmap)) {
0350         QIcon icon = Icons::get(Icon::BankAccount);
0351         if (!icon.availableSizes().isEmpty())
0352             accountPixmap = icon.pixmap(icon.availableSizes().first());
0353         QPixmapCache::insert("account", accountPixmap);
0354     }
0355     d->m_favorites->setIcon(0, QIcon(accountPixmap));
0356 
0357     for (auto mask = 0x01; mask != eDialogs::Category::last; mask <<= 1) {
0358         QTreeWidgetItem* item = 0;
0359         if ((typeMask & mask & eDialogs::Category::asset) != 0) {
0360             ++d->m_count;
0361             key = QString("B%1").arg(i18n("Asset"));
0362             item = selector->newItem(i18n("Asset accounts"), key);
0363             item->setIcon(0, d->m_file->asset().accountPixmap());
0364             list = d->m_file->asset().accountList();
0365         }
0366 
0367         if ((typeMask & mask & eDialogs::Category::liability) != 0) {
0368             ++d->m_count;
0369             key = QString("C%1").arg(i18n("Liability"));
0370             item = selector->newItem(i18n("Liability accounts"), key);
0371             item->setIcon(0, d->m_file->liability().accountPixmap());
0372             list = d->m_file->liability().accountList();
0373         }
0374 
0375         if ((typeMask & mask & eDialogs::Category::income) != 0) {
0376             ++d->m_count;
0377             key = QString("D%1").arg(i18n("Income"));
0378             item = selector->newItem(i18n("Income categories"), key);
0379             item->setIcon(0, d->m_file->income().accountPixmap());
0380             list = d->m_file->income().accountList();
0381             if (selector->selectionMode() == QTreeWidget::MultiSelection) {
0382                 selector->d_func()->m_incomeCategoriesButton->show();
0383             }
0384         }
0385 
0386         if ((typeMask & mask & eDialogs::Category::expense) != 0) {
0387             ++d->m_count;
0388             key = QString("E%1").arg(i18n("Expense"));
0389             item = selector->newItem(i18n("Expense categories"), key);
0390             item->setIcon(0, d->m_file->expense().accountPixmap());
0391             list = d->m_file->expense().accountList();
0392             if (selector->selectionMode() == QTreeWidget::MultiSelection) {
0393                 selector->d_func()->m_expenseCategoriesButton->show();
0394             }
0395         }
0396 
0397         if ((typeMask & mask & eDialogs::Category::equity) != 0) {
0398             ++d->m_count;
0399             key = QString("F%1").arg(i18n("Equity"));
0400             item = selector->newItem(i18n("Equity accounts"), key);
0401             item->setIcon(0, d->m_file->equity().accountPixmap());
0402             list = d->m_file->equity().accountList();
0403         }
0404 
0405         if (!after)
0406             after = item;
0407 
0408         if (item != 0) {
0409             // scan all matching accounts found in the engine
0410             for (it_l = list.constBegin(); it_l != list.constEnd(); ++it_l) {
0411                 const MyMoneyAccount& acc = d->m_file->account(*it_l);
0412                 ++d->m_count;
0413                 ++count;
0414                 //this will include an account if it matches the account type and
0415                 //if it is still open or it has been set to show closed accounts
0416                 if (includeAccount(acc)
0417                         && (!isHidingClosedAccounts() || !acc.isClosed())) {
0418                     QString tmpKey;
0419                     tmpKey = key + MyMoneyFile::AccountSeparator + acc.name();
0420                     QTreeWidgetItem* subItem = selector->newItem(item, acc.name(), tmpKey, acc.id());
0421                     subItem->setIcon(0, acc.accountPixmap());
0422                     if (acc.value("PreferredAccount") == "Yes"
0423                             && d->m_typeList.contains(acc.accountType())) {
0424                         selector->newItem(d->m_favorites, acc.name(), tmpKey, acc.id())->setIcon(0, acc.accountPixmap());;
0425                     }
0426                     if (acc.accountList().count() > 0) {
0427                         subItem->setExpanded(true);
0428                         count += loadSubAccounts(selector, subItem, tmpKey, acc.accountList());
0429                     }
0430 
0431                     // the item is not selectable if it has been added only because a subaccount matches the type
0432                     if (!d->m_typeList.contains(acc.accountType())) {
0433                         selector->setSelectable(subItem, false);
0434                     }
0435                     subItem->sortChildren(1, Qt::AscendingOrder);
0436                 }
0437             }
0438             item->sortChildren(1, Qt::AscendingOrder);
0439         }
0440     }
0441     d->m_favorites->sortChildren(1, Qt::AscendingOrder);
0442     lv->invisibleRootItem()->sortChildren(1, Qt::AscendingOrder);
0443 
0444     // if we don't have a favorite account or the selector is for multi-mode
0445     // we get rid of the favorite entry and subentries.
0446     if (d->m_favorites->childCount() == 0 || selector->selectionMode() == QTreeWidget::MultiSelection) {
0447         delete d->m_favorites;
0448         d->m_favorites = 0;
0449     }
0450 
0451     if (lv->itemAt(0, 0)) {
0452         if (currentId.isEmpty()) {
0453             lv->setCurrentItem(lv->itemAt(0, 0));
0454             lv->clearSelection();
0455         } else {
0456             selector->setSelected(currentId);
0457         }
0458     }
0459     selector->update();
0460     return count;
0461 }
0462 
0463 int AccountSet::load(KMyMoneyAccountSelector* selector, const QString& baseName, const QList<QString>& accountIdList, const bool clear)
0464 {
0465     Q_D(AccountSet);
0466     int count = 0;
0467     QTreeWidgetItem* item = 0;
0468 
0469     d->m_typeList.clear();
0470     if (clear) {
0471         d->m_count = 0;
0472         selector->clear();
0473     }
0474 
0475     item = selector->newItem(baseName);
0476     ++d->m_count;
0477 
0478     QList<QString>::ConstIterator it;
0479     for (it = accountIdList.constBegin(); it != accountIdList.constEnd(); ++it)   {
0480         const MyMoneyAccount& acc = d->m_file->account(*it);
0481         if (acc.isClosed())
0482             continue;
0483         QString tmpKey;
0484         // the first character must be preset. Since we don't know any sort order here, we just use A
0485         tmpKey = QString("A%1%2%3").arg(baseName, MyMoneyFile::AccountSeparator, acc.name());
0486         selector->newItem(item, acc.name(), tmpKey, acc.id())->setIcon(0, acc.accountPixmap());
0487         ++d->m_count;
0488         ++count;
0489     }
0490 
0491     QTreeWidget* lv = selector->listView();
0492     if (lv->itemAt(0, 0)) {
0493         lv->setCurrentItem(lv->itemAt(0, 0));
0494         lv->clearSelection();
0495     }
0496 
0497     selector->update();
0498     return count;
0499 }
0500 
0501 int AccountSet::count() const
0502 {
0503     Q_D(const AccountSet);
0504     return d->m_count;
0505 }
0506 
0507 void AccountSet::setHideClosedAccounts(bool _bool)
0508 {
0509     Q_D(AccountSet);
0510     d->m_hideClosedAccounts = _bool;
0511 }
0512 bool AccountSet::isHidingClosedAccounts() const
0513 {
0514     Q_D(const AccountSet);
0515     return d->m_hideClosedAccounts;
0516 }
0517 
0518 int AccountSet::loadSubAccounts(KMyMoneyAccountSelector* selector, QTreeWidgetItem* parent, const QString& key, const QStringList& list)
0519 {
0520     Q_D(AccountSet);
0521     QStringList::ConstIterator it_l;
0522     int count = 0;
0523 
0524     for (it_l = list.constBegin(); it_l != list.constEnd(); ++it_l) {
0525         const MyMoneyAccount& acc = d->m_file->account(*it_l);
0526         // don't include stock accounts if not in expert mode
0527         if (acc.isInvest() && !d->m_showInvestments)
0528             continue;
0529 
0530         //this will include an account if it matches the account type and
0531         //if it is still open or it has been set to show closed accounts
0532         if (includeAccount(acc)
0533                 && (!isHidingClosedAccounts() || !acc.isClosed())) {
0534             QString tmpKey;
0535             tmpKey = key + MyMoneyFile::AccountSeparator + acc.name();
0536             ++count;
0537             ++d->m_count;
0538             QTreeWidgetItem* item = selector->newItem(parent, acc.name(), tmpKey, acc.id());
0539             item->setIcon(0, acc.accountPixmap());
0540             if (acc.value("PreferredAccount") == "Yes"
0541                     && d->m_typeList.contains(acc.accountType())) {
0542                 selector->newItem(d->m_favorites, acc.name(), tmpKey, acc.id())->setIcon(0, acc.accountPixmap());
0543             }
0544             if (acc.accountList().count() > 0) {
0545                 item->setExpanded(true);
0546                 count += loadSubAccounts(selector, item, tmpKey, acc.accountList());
0547             }
0548 
0549             // the item is not selectable if it has been added only because a subaccount matches the type
0550             if (!d->m_typeList.contains(acc.accountType())) {
0551                 selector->setSelectable(item, false);
0552             }
0553             item->sortChildren(1, Qt::AscendingOrder);
0554         }
0555     }
0556     return count;
0557 }
0558 
0559 bool AccountSet::includeAccount(const MyMoneyAccount& acc)
0560 {
0561     Q_D(AccountSet);
0562     if (d->m_typeList.contains(acc.accountType()))
0563         return true;
0564 
0565     foreach (const auto sAccount, acc.accountList())
0566         if (includeAccount(d->m_file->account(sAccount)))
0567             return true;
0568 
0569     return false;
0570 }