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