File indexing completed on 2024-05-12 16:42:17

0001 /*
0002     SPDX-FileCopyrightText: 2010-2014 Cristian Oneț <onet.cristian@gmail.com>
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 "accountsproxymodel.h"
0008 #include "accountsproxymodel_p.h"
0009 
0010 // ----------------------------------------------------------------------------
0011 // QT Includes
0012 
0013 // ----------------------------------------------------------------------------
0014 // KDE Includes
0015 
0016 // ----------------------------------------------------------------------------
0017 // Project Includes
0018 
0019 #include "modelenums.h"
0020 #include "mymoneyenums.h"
0021 #include "mymoneyinstitution.h"
0022 #include "mymoneyaccount.h"
0023 #include "mymoneymoney.h"
0024 
0025 using namespace eAccountsModel;
0026 
0027 #if QT_VERSION < QT_VERSION_CHECK(5,10,0)
0028 #define QSortFilterProxyModel KRecursiveFilterProxyModel
0029 #endif
0030 AccountsProxyModel::AccountsProxyModel(QObject *parent) :
0031     QSortFilterProxyModel(parent),
0032     d_ptr(new AccountsProxyModelPrivate)
0033 {
0034     setRecursiveFilteringEnabled(true);
0035     setDynamicSortFilter(true);
0036     setSortLocaleAware(true);
0037     setFilterCaseSensitivity(Qt::CaseInsensitive);
0038 }
0039 
0040 AccountsProxyModel::AccountsProxyModel(AccountsProxyModelPrivate &dd, QObject *parent) :
0041     QSortFilterProxyModel(parent), d_ptr(&dd)
0042 {
0043     setRecursiveFilteringEnabled(true);
0044 }
0045 #undef QSortFilterProxyModel
0046 
0047 AccountsProxyModel::~AccountsProxyModel()
0048 {
0049 }
0050 
0051 /**
0052   * This function was re-implemented so we could have a special display order (favorites first)
0053   */
0054 bool AccountsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
0055 {
0056     Q_D(const AccountsProxyModel);
0057     if (!left.isValid() || !right.isValid())
0058         return false;
0059     // different sorting based on the column which is being sorted
0060     switch (d->m_mdlColumns->at(left.column())) {
0061     // for the accounts column sort based on the DisplayOrderRole
0062     case Column::Account: {
0063         const auto leftData = sourceModel()->data(left, (int)Role::DisplayOrder);
0064         const auto rightData = sourceModel()->data(right, (int)Role::DisplayOrder);
0065 
0066         if (leftData.toInt() == rightData.toInt()) {
0067             // sort items of the same display order alphabetically
0068             return QSortFilterProxyModel::lessThan(left, right);
0069         }
0070         return leftData.toInt() < rightData.toInt();
0071     }
0072     // the total balance and value columns are sorted based on the value of the account
0073     case Column::TotalBalance:
0074     case Column::TotalValue: {
0075         const auto leftData = sourceModel()->data(sourceModel()->index(left.row(), (int)Column::Account, left.parent()), (int)Role::TotalValue);
0076         const auto rightData = sourceModel()->data(sourceModel()->index(right.row(), (int)Column::Account, right.parent()), (int)Role::TotalValue);
0077         return leftData.value<MyMoneyMoney>() < rightData.value<MyMoneyMoney>();
0078     }
0079     default:
0080         break;
0081     }
0082     return QSortFilterProxyModel::lessThan(left, right);
0083 }
0084 
0085 /**
0086   * This function was re-implemented to consider all the filtering aspects that we need in the application.
0087   */
0088 bool AccountsProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
0089 {
0090     const auto index = sourceModel()->index(source_row, (int)Column::Account, source_parent);
0091     return acceptSourceItem(index) && filterAcceptsRowOrChildRows(source_row, source_parent);
0092 }
0093 
0094 /**
0095   * This function implements a recursive matching. It is used to match a row even if it's values
0096   * don't match the current filtering criteria but it has at least one child row that does match.
0097   */
0098 bool AccountsProxyModel::filterAcceptsRowOrChildRows(int source_row, const QModelIndex &source_parent) const
0099 {
0100     if (QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent))
0101         return true;
0102 
0103     const auto index = sourceModel()->index(source_row, (int)Column::Account, source_parent);
0104     for (auto i = 0; i < sourceModel()->rowCount(index); ++i) {
0105         if (filterAcceptsRowOrChildRows(i, index))
0106             return true;
0107     }
0108     return false;
0109 }
0110 
0111 /**
0112   * Add the given account group to the filter.
0113   * @param group The account group to be added.
0114   * @see eMyMoney::Account
0115   */
0116 void AccountsProxyModel::addAccountGroup(const QVector<eMyMoney::Account::Type> &groups)
0117 {
0118     Q_D(AccountsProxyModel);
0119     foreach (const auto group, groups) {
0120         switch (group) {
0121         case eMyMoney::Account::Type::Asset:
0122             d->m_typeList << eMyMoney::Account::Type::Checkings;
0123             d->m_typeList << eMyMoney::Account::Type::Savings;
0124             d->m_typeList << eMyMoney::Account::Type::Cash;
0125             d->m_typeList << eMyMoney::Account::Type::AssetLoan;
0126             d->m_typeList << eMyMoney::Account::Type::CertificateDep;
0127             d->m_typeList << eMyMoney::Account::Type::Investment;
0128             d->m_typeList << eMyMoney::Account::Type::Stock;
0129             d->m_typeList << eMyMoney::Account::Type::MoneyMarket;
0130             d->m_typeList << eMyMoney::Account::Type::Asset;
0131             d->m_typeList << eMyMoney::Account::Type::Currency;
0132             break;
0133         case eMyMoney::Account::Type::Liability:
0134             d->m_typeList << eMyMoney::Account::Type::CreditCard;
0135             d->m_typeList << eMyMoney::Account::Type::Loan;
0136             d->m_typeList << eMyMoney::Account::Type::Liability;
0137             break;
0138         case eMyMoney::Account::Type::Income:
0139             d->m_typeList << eMyMoney::Account::Type::Income;
0140             break;
0141         case eMyMoney::Account::Type::Expense:
0142             d->m_typeList << eMyMoney::Account::Type::Expense;
0143             break;
0144         case eMyMoney::Account::Type::Equity:
0145             d->m_typeList << eMyMoney::Account::Type::Equity;
0146             break;
0147         default:
0148             d->m_typeList << group;
0149             break;
0150         }
0151     }
0152     invalidateFilter();
0153 }
0154 
0155 /**
0156   * Add the given account type to the filter.
0157   * @param type The account type to be added.
0158   * @see eMyMoney::Account
0159   */
0160 void AccountsProxyModel::addAccountType(eMyMoney::Account::Type type)
0161 {
0162     Q_D(AccountsProxyModel);
0163     d->m_typeList << type;
0164     invalidateFilter();
0165 }
0166 
0167 /**
0168   * Remove the given account type from the filter.
0169   * @param type The account type to be removed.
0170   * @see eMyMoney::Account
0171   */
0172 void AccountsProxyModel::removeAccountType(eMyMoney::Account::Type type)
0173 {
0174     Q_D(AccountsProxyModel);
0175     if (d->m_typeList.removeAll(type) > 0) {
0176         invalidateFilter();
0177     }
0178 }
0179 
0180 /**
0181   * Use this to reset the filter.
0182   */
0183 void AccountsProxyModel::clear()
0184 {
0185     Q_D(AccountsProxyModel);
0186     d->m_typeList.clear();
0187     invalidateFilter();
0188 }
0189 
0190 /**
0191   * Implementation function that performs the actual filtering.
0192   */
0193 bool AccountsProxyModel::acceptSourceItem(const QModelIndex &source) const
0194 {
0195     Q_D(const AccountsProxyModel);
0196     if (source.isValid()) {
0197         const auto data = sourceModel()->data(source, (int)Role::Account);
0198         if (data.isValid()) {
0199             if (data.canConvert<MyMoneyAccount>()) {
0200                 const auto account = data.value<MyMoneyAccount>();
0201                 if ((hideClosedAccounts() && account.isClosed()))
0202                     return false;
0203 
0204                 // we hide stock accounts if not in expert mode
0205                 if (account.isInvest() && hideEquityAccounts())
0206                     return false;
0207 
0208                 // we hide equity accounts if not in expert mode
0209                 if (account.accountType() == eMyMoney::Account::Type::Equity && hideEquityAccounts())
0210                     return false;
0211 
0212                 // we hide unused income and expense accounts if the specific flag is set
0213                 if ((account.accountType() == eMyMoney::Account::Type::Income || account.accountType() == eMyMoney::Account::Type::Expense) && hideUnusedIncomeExpenseAccounts()) {
0214                     const auto totalValue = sourceModel()->data(source, (int)Role::TotalValue);
0215                     if (totalValue.isValid() && totalValue.value<MyMoneyMoney>().isZero()) {
0216                         emit const_cast<AccountsProxyModel*>(this)->unusedIncomeExpenseAccountHidden();
0217                         return false;
0218                     }
0219                 }
0220 
0221                 if (d->m_typeList.contains(account.accountType()))
0222                     return true;
0223             } else if (data.canConvert<MyMoneyInstitution>() && sourceModel()->rowCount(source) == 0) {
0224                 return true;
0225             }
0226             // let the visibility of all other institutions (the ones with children) be controlled by the visibility of their children
0227         }
0228 
0229         // all parents that have at least one visible child must be visible
0230         const auto rowCount = sourceModel()->rowCount(source);
0231         for (auto i = 0; i < rowCount; ++i) {
0232             const auto index = sourceModel()->index(i, (int)(int)Column::Account, source);
0233             if (acceptSourceItem(index))
0234                 return true;
0235         }
0236     }
0237     return false;
0238 }
0239 
0240 /**
0241   * Set if closed accounts should be hidden or not.
0242   * @param hideClosedAccounts
0243   */
0244 void AccountsProxyModel::setHideClosedAccounts(bool hideClosedAccounts)
0245 {
0246     Q_D(AccountsProxyModel);
0247     if (d->m_hideClosedAccounts != hideClosedAccounts) {
0248         d->m_hideClosedAccounts = hideClosedAccounts;
0249         invalidateFilter();
0250     }
0251 }
0252 
0253 /**
0254   * Check if closed accounts are hidden or not.
0255   */
0256 bool AccountsProxyModel::hideClosedAccounts() const
0257 {
0258     Q_D(const AccountsProxyModel);
0259     return d->m_hideClosedAccounts;
0260 }
0261 
0262 /**
0263   * Set if equity and investment accounts should be hidden or not.
0264   * @param hideEquityAccounts
0265   */
0266 void AccountsProxyModel::setHideEquityAccounts(bool hideEquityAccounts)
0267 {
0268     Q_D(AccountsProxyModel);
0269     if (d->m_hideEquityAccounts != hideEquityAccounts) {
0270         d->m_hideEquityAccounts = hideEquityAccounts;
0271         invalidateFilter();
0272     }
0273 }
0274 
0275 /**
0276   * Check if equity and investment accounts are hidden or not.
0277   */
0278 bool AccountsProxyModel::hideEquityAccounts() const
0279 {
0280     Q_D(const AccountsProxyModel);
0281     return d->m_hideEquityAccounts;
0282 }
0283 
0284 /**
0285   * Set if empty categories should be hidden or not.
0286   * @param hideUnusedIncomeExpenseAccounts
0287   */
0288 void AccountsProxyModel::setHideUnusedIncomeExpenseAccounts(bool hideUnusedIncomeExpenseAccounts)
0289 {
0290     Q_D(AccountsProxyModel);
0291     if (d->m_hideUnusedIncomeExpenseAccounts != hideUnusedIncomeExpenseAccounts) {
0292         d->m_hideUnusedIncomeExpenseAccounts = hideUnusedIncomeExpenseAccounts;
0293         invalidateFilter();
0294     }
0295 }
0296 
0297 /**
0298   * Check if empty categories are hidden or not.
0299   */
0300 bool AccountsProxyModel::hideUnusedIncomeExpenseAccounts() const
0301 {
0302     Q_D(const AccountsProxyModel);
0303     return d->m_hideUnusedIncomeExpenseAccounts;
0304 }
0305 
0306 /**
0307   * Returns the number of visible items after filtering. In case @a includeBaseAccounts
0308   * is set to @c true, the 5 base accounts (asset, liability, income, expense and equity)
0309   * will also be counted. The default is @c false.
0310   */
0311 int AccountsProxyModel::visibleItems(bool includeBaseAccounts) const
0312 {
0313     auto rows = 0;
0314     for (auto i = 0; i < rowCount(QModelIndex()); ++i) {
0315         if(includeBaseAccounts) {
0316             ++rows;
0317         }
0318         const auto childIndex = index(i, 0);
0319         if (hasChildren(childIndex)) {
0320             rows += visibleItems(childIndex);
0321         }
0322     }
0323     return rows;
0324 }
0325 
0326 /**
0327   * Returns the number of visible items under the given @a index.
0328   * The column of the @a index must be 0, otherwise no count will
0329   * be returned (returns 0).
0330   */
0331 int AccountsProxyModel::visibleItems(const QModelIndex& index) const
0332 {
0333     auto rows = 0;
0334     if (index.isValid() && index.column() == (int)Column::Account) { // CAUTION! Assumption is being made that Account column number is always 0
0335         const auto *model = index.model();
0336         const auto rowCount = model->rowCount(index);
0337         for (auto i = 0; i < rowCount; ++i) {
0338             ++rows;
0339             const auto childIndex = model->index(i, index.column(), index);
0340             if (model->hasChildren(childIndex))
0341                 rows += visibleItems(childIndex);
0342         }
0343     }
0344     return rows;
0345 }
0346 
0347 void AccountsProxyModel::setSourceColumns(QList<eAccountsModel::Column> *columns)
0348 {
0349     Q_D(AccountsProxyModel);
0350     d->m_mdlColumns = columns;
0351 }