1 /*
2  * Copyright 2010-2014  Cristian Oneț <onet.cristian@gmail.com>
3  * Copyright 2017-2018  Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of
8  * the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "accountsproxymodel.h"
20 #include "accountsproxymodel_p.h"
21 
22 // ----------------------------------------------------------------------------
23 // QT Includes
24 
25 // ----------------------------------------------------------------------------
26 // KDE Includes
27 
28 // ----------------------------------------------------------------------------
29 // Project Includes
30 
31 #include "modelenums.h"
32 #include "mymoneyenums.h"
33 #include "mymoneyinstitution.h"
34 #include "mymoneyaccount.h"
35 #include "mymoneymoney.h"
36 
37 using namespace eAccountsModel;
38 
39 #if QT_VERSION < QT_VERSION_CHECK(5,10,0)
40 #define QSortFilterProxyModel KRecursiveFilterProxyModel
41 #endif
AccountsProxyModel(QObject * parent)42 AccountsProxyModel::AccountsProxyModel(QObject *parent) :
43   QSortFilterProxyModel(parent),
44   d_ptr(new AccountsProxyModelPrivate)
45 {
46   setRecursiveFilteringEnabled(true);
47   setDynamicSortFilter(true);
48   setSortLocaleAware(true);
49   setFilterCaseSensitivity(Qt::CaseInsensitive);
50 }
51 
AccountsProxyModel(AccountsProxyModelPrivate & dd,QObject * parent)52 AccountsProxyModel::AccountsProxyModel(AccountsProxyModelPrivate &dd, QObject *parent) :
53   QSortFilterProxyModel(parent), d_ptr(&dd)
54 {
55   setRecursiveFilteringEnabled(true);
56 }
57 #undef QSortFilterProxyModel
58 
~AccountsProxyModel()59 AccountsProxyModel::~AccountsProxyModel()
60 {
61 }
62 
63 /**
64   * This function was re-implemented so we could have a special display order (favorites first)
65   */
lessThan(const QModelIndex & left,const QModelIndex & right) const66 bool AccountsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
67 {
68   Q_D(const AccountsProxyModel);
69   if (!left.isValid() || !right.isValid())
70     return false;
71   // different sorting based on the column which is being sorted
72   switch (d->m_mdlColumns->at(left.column())) {
73       // for the accounts column sort based on the DisplayOrderRole
74     case Column::Account: {
75         const auto leftData = sourceModel()->data(left, (int)Role::DisplayOrder);
76         const auto rightData = sourceModel()->data(right, (int)Role::DisplayOrder);
77 
78         if (leftData.toInt() == rightData.toInt()) {
79           // sort items of the same display order alphabetically
80           return QSortFilterProxyModel::lessThan(left, right);
81         }
82         return leftData.toInt() < rightData.toInt();
83       }
84       // the total balance and value columns are sorted based on the value of the account
85     case Column::TotalBalance:
86     case Column::TotalValue: {
87         const auto leftData = sourceModel()->data(sourceModel()->index(left.row(), (int)Column::Account, left.parent()), (int)Role::TotalValue);
88         const auto rightData = sourceModel()->data(sourceModel()->index(right.row(), (int)Column::Account, right.parent()), (int)Role::TotalValue);
89         return leftData.value<MyMoneyMoney>() < rightData.value<MyMoneyMoney>();
90       }
91     default:
92       break;
93   }
94   return QSortFilterProxyModel::lessThan(left, right);
95 }
96 
97 /**
98   * This function was re-implemented to consider all the filtering aspects that we need in the application.
99   */
filterAcceptsRow(int source_row,const QModelIndex & source_parent) const100 bool AccountsProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
101 {
102   const auto index = sourceModel()->index(source_row, (int)Column::Account, source_parent);
103   return acceptSourceItem(index) && filterAcceptsRowOrChildRows(source_row, source_parent);
104 }
105 
106 /**
107   * This function implements a recursive matching. It is used to match a row even if it's values
108   * don't match the current filtering criteria but it has at least one child row that does match.
109   */
filterAcceptsRowOrChildRows(int source_row,const QModelIndex & source_parent) const110 bool AccountsProxyModel::filterAcceptsRowOrChildRows(int source_row, const QModelIndex &source_parent) const
111 {
112   if (QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent))
113     return true;
114 
115   const auto index = sourceModel()->index(source_row, (int)Column::Account, source_parent);
116   for (auto i = 0; i < sourceModel()->rowCount(index); ++i) {
117     if (filterAcceptsRowOrChildRows(i, index))
118       return true;
119   }
120   return false;
121 }
122 
123 /**
124   * Add the given account group to the filter.
125   * @param group The account group to be added.
126   * @see eMyMoney::Account
127   */
addAccountGroup(const QVector<eMyMoney::Account::Type> & groups)128 void AccountsProxyModel::addAccountGroup(const QVector<eMyMoney::Account::Type> &groups)
129 {
130   Q_D(AccountsProxyModel);
131   foreach (const auto group, groups) {
132     switch (group) {
133       case eMyMoney::Account::Type::Asset:
134         d->m_typeList << eMyMoney::Account::Type::Checkings;
135         d->m_typeList << eMyMoney::Account::Type::Savings;
136         d->m_typeList << eMyMoney::Account::Type::Cash;
137         d->m_typeList << eMyMoney::Account::Type::AssetLoan;
138         d->m_typeList << eMyMoney::Account::Type::CertificateDep;
139         d->m_typeList << eMyMoney::Account::Type::Investment;
140         d->m_typeList << eMyMoney::Account::Type::Stock;
141         d->m_typeList << eMyMoney::Account::Type::MoneyMarket;
142         d->m_typeList << eMyMoney::Account::Type::Asset;
143         d->m_typeList << eMyMoney::Account::Type::Currency;
144         break;
145       case eMyMoney::Account::Type::Liability:
146         d->m_typeList << eMyMoney::Account::Type::CreditCard;
147         d->m_typeList << eMyMoney::Account::Type::Loan;
148         d->m_typeList << eMyMoney::Account::Type::Liability;
149         break;
150       case eMyMoney::Account::Type::Income:
151         d->m_typeList << eMyMoney::Account::Type::Income;
152         break;
153       case eMyMoney::Account::Type::Expense:
154         d->m_typeList << eMyMoney::Account::Type::Expense;
155         break;
156       case eMyMoney::Account::Type::Equity:
157         d->m_typeList << eMyMoney::Account::Type::Equity;
158         break;
159       default:
160         d->m_typeList << group;
161         break;
162     }
163   }
164   invalidateFilter();
165 }
166 
167 /**
168   * Add the given account type to the filter.
169   * @param type The account type to be added.
170   * @see eMyMoney::Account
171   */
addAccountType(eMyMoney::Account::Type type)172 void AccountsProxyModel::addAccountType(eMyMoney::Account::Type type)
173 {
174   Q_D(AccountsProxyModel);
175   d->m_typeList << type;
176   invalidateFilter();
177 }
178 
179 /**
180   * Remove the given account type from the filter.
181   * @param type The account type to be removed.
182   * @see eMyMoney::Account
183   */
removeAccountType(eMyMoney::Account::Type type)184 void AccountsProxyModel::removeAccountType(eMyMoney::Account::Type type)
185 {
186   Q_D(AccountsProxyModel);
187   if (d->m_typeList.removeAll(type) > 0) {
188     invalidateFilter();
189   }
190 }
191 
192 /**
193   * Use this to reset the filter.
194   */
clear()195 void AccountsProxyModel::clear()
196 {
197   Q_D(AccountsProxyModel);
198   d->m_typeList.clear();
199   invalidateFilter();
200 }
201 
202 /**
203   * Implementation function that performs the actual filtering.
204   */
acceptSourceItem(const QModelIndex & source) const205 bool AccountsProxyModel::acceptSourceItem(const QModelIndex &source) const
206 {
207   Q_D(const AccountsProxyModel);
208   if (source.isValid()) {
209     const auto data = sourceModel()->data(source, (int)Role::Account);
210     if (data.isValid()) {
211       if (data.canConvert<MyMoneyAccount>()) {
212         const auto account = data.value<MyMoneyAccount>();
213         if ((hideClosedAccounts() && account.isClosed()))
214           return false;
215 
216         // we hide stock accounts if not in expert mode
217         if (account.isInvest() && hideEquityAccounts())
218           return false;
219 
220         // we hide equity accounts if not in expert mode
221         if (account.accountType() == eMyMoney::Account::Type::Equity && hideEquityAccounts())
222           return false;
223 
224         // we hide unused income and expense accounts if the specific flag is set
225         if ((account.accountType() == eMyMoney::Account::Type::Income || account.accountType() == eMyMoney::Account::Type::Expense) && hideUnusedIncomeExpenseAccounts()) {
226           const auto totalValue = sourceModel()->data(source, (int)Role::TotalValue);
227           if (totalValue.isValid() && totalValue.value<MyMoneyMoney>().isZero()) {
228             emit const_cast<AccountsProxyModel*>(this)->unusedIncomeExpenseAccountHidden();
229             return false;
230           }
231         }
232 
233         if (d->m_typeList.contains(account.accountType()))
234           return true;
235       } else if (data.canConvert<MyMoneyInstitution>() && sourceModel()->rowCount(source) == 0) {
236         return true;
237       }
238       // let the visibility of all other institutions (the ones with children) be controlled by the visibility of their children
239     }
240 
241     // all parents that have at least one visible child must be visible
242     const auto rowCount = sourceModel()->rowCount(source);
243     for (auto i = 0; i < rowCount; ++i) {
244       const auto index = sourceModel()->index(i, (int)(int)Column::Account, source);
245       if (acceptSourceItem(index))
246         return true;
247     }
248   }
249   return false;
250 }
251 
252 /**
253   * Set if closed accounts should be hidden or not.
254   * @param hideClosedAccounts
255   */
setHideClosedAccounts(bool hideClosedAccounts)256 void AccountsProxyModel::setHideClosedAccounts(bool hideClosedAccounts)
257 {
258   Q_D(AccountsProxyModel);
259   if (d->m_hideClosedAccounts != hideClosedAccounts) {
260     d->m_hideClosedAccounts = hideClosedAccounts;
261     invalidateFilter();
262   }
263 }
264 
265 /**
266   * Check if closed accounts are hidden or not.
267   */
hideClosedAccounts() const268 bool AccountsProxyModel::hideClosedAccounts() const
269 {
270   Q_D(const AccountsProxyModel);
271   return d->m_hideClosedAccounts;
272 }
273 
274 /**
275   * Set if equity and investment accounts should be hidden or not.
276   * @param hideEquityAccounts
277   */
setHideEquityAccounts(bool hideEquityAccounts)278 void AccountsProxyModel::setHideEquityAccounts(bool hideEquityAccounts)
279 {
280   Q_D(AccountsProxyModel);
281   if (d->m_hideEquityAccounts != hideEquityAccounts) {
282     d->m_hideEquityAccounts = hideEquityAccounts;
283     invalidateFilter();
284   }
285 }
286 
287 /**
288   * Check if equity and investment accounts are hidden or not.
289   */
hideEquityAccounts() const290 bool AccountsProxyModel::hideEquityAccounts() const
291 {
292   Q_D(const AccountsProxyModel);
293   return d->m_hideEquityAccounts;
294 }
295 
296 /**
297   * Set if empty categories should be hidden or not.
298   * @param hideUnusedIncomeExpenseAccounts
299   */
setHideUnusedIncomeExpenseAccounts(bool hideUnusedIncomeExpenseAccounts)300 void AccountsProxyModel::setHideUnusedIncomeExpenseAccounts(bool hideUnusedIncomeExpenseAccounts)
301 {
302   Q_D(AccountsProxyModel);
303   if (d->m_hideUnusedIncomeExpenseAccounts != hideUnusedIncomeExpenseAccounts) {
304     d->m_hideUnusedIncomeExpenseAccounts = hideUnusedIncomeExpenseAccounts;
305     invalidateFilter();
306   }
307 }
308 
309 /**
310   * Check if empty categories are hidden or not.
311   */
hideUnusedIncomeExpenseAccounts() const312 bool AccountsProxyModel::hideUnusedIncomeExpenseAccounts() const
313 {
314   Q_D(const AccountsProxyModel);
315   return d->m_hideUnusedIncomeExpenseAccounts;
316 }
317 
318 /**
319   * Returns the number of visible items after filtering. In case @a includeBaseAccounts
320   * is set to @c true, the 5 base accounts (asset, liability, income, expense and equity)
321   * will also be counted. The default is @c false.
322   */
visibleItems(bool includeBaseAccounts) const323 int AccountsProxyModel::visibleItems(bool includeBaseAccounts) const
324 {
325   auto rows = 0;
326   for (auto i = 0; i < rowCount(QModelIndex()); ++i) {
327     if(includeBaseAccounts) {
328       ++rows;
329     }
330     const auto childIndex = index(i, 0);
331     if (hasChildren(childIndex)) {
332       rows += visibleItems(childIndex);
333     }
334   }
335   return rows;
336 }
337 
338 /**
339   * Returns the number of visible items under the given @a index.
340   * The column of the @a index must be 0, otherwise no count will
341   * be returned (returns 0).
342   */
visibleItems(const QModelIndex & index) const343 int AccountsProxyModel::visibleItems(const QModelIndex& index) const
344 {
345   auto rows = 0;
346   if (index.isValid() && index.column() == (int)Column::Account) { // CAUTION! Assumption is being made that Account column number is always 0
347     const auto *model = index.model();
348     const auto rowCount = model->rowCount(index);
349     for (auto i = 0; i < rowCount; ++i) {
350       ++rows;
351       const auto childIndex = model->index(i, index.column(), index);
352       if (model->hasChildren(childIndex))
353         rows += visibleItems(childIndex);
354     }
355   }
356   return rows;
357 }
358 
setSourceColumns(QList<eAccountsModel::Column> * columns)359 void AccountsProxyModel::setSourceColumns(QList<eAccountsModel::Column> *columns)
360 {
361   Q_D(AccountsProxyModel);
362   d->m_mdlColumns = columns;
363 }
364