1 /***************************************************************************
2                           kgloballedgerview_p.h  -  description
3                              -------------------
4     begin                : Wed Jul 26 2006
5     copyright            : (C) 2006 by Thomas Baumgart
6     email                : Thomas Baumgart <ipwizard@users.sourceforge.net>
7                            (C) 2017 by Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
8  ***************************************************************************/
9 
10 /***************************************************************************
11  *                                                                         *
12  *   This program is free software; you can redistribute it and/or modify  *
13  *   it under the terms of the GNU General Public License as published by  *
14  *   the Free Software Foundation; either version 2 of the License, or     *
15  *   (at your option) any later version.                                   *
16  *                                                                         *
17  ***************************************************************************/
18 
19 #ifndef KGLOBALLEDGERVIEW_P_H
20 #define KGLOBALLEDGERVIEW_P_H
21 
22 #include "kgloballedgerview.h"
23 
24 // ----------------------------------------------------------------------------
25 // QT Includes
26 
27 #include <QFrame>
28 #include <QHBoxLayout>
29 #include <QList>
30 #include <QLabel>
31 #include <QEvent>
32 #include <QVBoxLayout>
33 #include <QHeaderView>
34 #include <QToolTip>
35 #include <QMenu>
36 #include <QWidgetAction>
37 
38 // ----------------------------------------------------------------------------
39 // KDE Includes
40 
41 #include <KLocalizedString>
42 #include <KMessageBox>
43 #include <KToolBar>
44 #include <KPassivePopup>
45 
46 // ----------------------------------------------------------------------------
47 // Project Includes
48 
49 #include "kmymoneyviewbase_p.h"
50 #include "kendingbalancedlg.h"
51 #include "kfindtransactiondlg.h"
52 #include "kmymoneyaccountselector.h"
53 #include "kmymoneyutils.h"
54 #include "mymoneyexception.h"
55 #include "mymoneymoney.h"
56 #include "mymoneyaccount.h"
57 #include "mymoneyfile.h"
58 #include "kmymoneyaccountcombo.h"
59 #include "kbalancewarning.h"
60 #include "transactionmatcher.h"
61 #include "tabbar.h"
62 #include "register.h"
63 #include "transactioneditor.h"
64 #include "selectedtransactions.h"
65 #include "kmymoneysettings.h"
66 #include "registersearchline.h"
67 #include "scheduledtransaction.h"
68 #include "accountsmodel.h"
69 #include "models.h"
70 #include "mymoneyprice.h"
71 #include "mymoneyschedule.h"
72 #include "mymoneysecurity.h"
73 #include "mymoneytransaction.h"
74 #include "mymoneytransactionfilter.h"
75 #include "mymoneysplit.h"
76 #include "mymoneypayee.h"
77 #include "mymoneytracer.h"
78 #include "transaction.h"
79 #include "transactionform.h"
80 #include "fancydategroupmarkers.h"
81 #include "widgetenums.h"
82 #include "mymoneyenums.h"
83 #include "modelenums.h"
84 #include "menuenums.h"
85 
86 #include <config-kmymoney.h>
87 #ifdef KMM_DEBUG
88 #include "mymoneyutils.h"
89 #endif
90 
91 using namespace eMenu;
92 using namespace eMyMoney;
93 
94 /**
95   * helper class implementing an event filter to detect mouse button press
96   * events on widgets outside a given set of widgets. This is used internally
97   * to detect when to leave the edit mode.
98   */
99 class MousePressFilter : public QObject
100 {
101   Q_OBJECT
102 public:
103   explicit MousePressFilter(QWidget* parent = nullptr) :
QObject(parent)104     QObject(parent),
105     m_lastMousePressEvent(0),
106     m_filterActive(true)
107   {
108   }
109 
110   /**
111     * Add widget @p w to the list of possible parent objects. See eventFilter() how
112     * they will be used.
113     */
addWidget(QWidget * w)114   void addWidget(QWidget* w)
115   {
116     m_parents.append(w);
117   }
118 
119 public Q_SLOTS:
120   /**
121     * This slot allows to activate/deactivate the filter. By default the
122     * filter is active.
123     *
124     * @param state Allows to activate (@a true) or deactivate (@a false) the filter
125     */
126   void setFilterActive(bool state = true)
127   {
128     m_filterActive = state;
129   }
130 
131   /**
132     * This slot allows to activate/deactivate the filter. By default the
133     * filter is active.
134     *
135     * @param state Allows to deactivate (@a true) or activate (@a false) the filter
136     */
137   void setFilterDeactive(bool state = false) {
138     setFilterActive(!state);
139   }
140 
141 protected:
142   /**
143     * This method checks if the widget @p child is a child of
144     * the widget @p parent and returns either @a true or @a false.
145     *
146     * @param child pointer to child widget
147     * @param parent pointer to parent widget
148     * @retval true @p child points to widget which has @p parent as parent or grand-parent
149     * @retval false @p child points to a widget which is not related to @p parent
150     */
isChildOf(QWidget * child,QWidget * parent)151   bool isChildOf(QWidget* child, QWidget* parent)
152   {
153     // QDialogs cannot be detected directly, but it can be assumed,
154     // that events on a widget that do not have a parent widget within
155     // our application are dialogs.
156     if (!child->parentWidget())
157       return true;
158 
159     while (child) {
160       // if we are a child of the given parent, we have a match
161       if (child == parent)
162         return true;
163       // if we are at the application level, we don't have a match
164       if (child->inherits("KMyMoneyApp"))
165         return false;
166       // If one of the ancestors is a KPassivePopup or a KDialog or a popup widget then
167       // it's as if it is a child of our own because these widgets could
168       // appear during transaction entry (message boxes, completer widgets)
169       if (dynamic_cast<KPassivePopup*>(child) ||
170           ((child->windowFlags() & Qt::Popup) && /*child != kmymoney*/
171            !child->parentWidget())) // has no parent, then it must be top-level window
172         return true;
173       child = child->parentWidget();
174     }
175     return false;
176   }
177 
178   /**
179     * Reimplemented from base class. Sends out the mousePressedOnExternalWidget() signal
180     * if object @p o points to an object which is not a child widget of any added previously
181     * using the addWidget() method. The signal is sent out only once for each event @p e.
182     *
183     * @param o pointer to QObject
184     * @param e pointer to QEvent
185     * @return always returns @a false
186     */
eventFilter(QObject * o,QEvent * e)187   bool eventFilter(QObject* o, QEvent* e) final override
188   {
189     if (m_filterActive) {
190       if (e->type() == QEvent::MouseButtonPress && !m_lastMousePressEvent) {
191         QWidget* w = qobject_cast<QWidget*>(o);
192         if (!w) {
193           return QObject::eventFilter(o, e);
194         }
195         QList<QWidget*>::const_iterator it_w;
196         for (it_w = m_parents.constBegin(); it_w != m_parents.constEnd(); ++it_w) {
197           if (isChildOf(w, (*it_w))) {
198             m_lastMousePressEvent = e;
199             break;
200           }
201         }
202         if (it_w == m_parents.constEnd()) {
203           m_lastMousePressEvent = e;
204           bool rc = false;
205           emit mousePressedOnExternalWidget(rc);
206         }
207       }
208 
209       if (e->type() != QEvent::MouseButtonPress) {
210         m_lastMousePressEvent = 0;
211       }
212     }
213     return false;
214   }
215 
216 Q_SIGNALS:
217   void mousePressedOnExternalWidget(bool&);
218 
219 private:
220   QList<QWidget*>      m_parents;
221   QEvent*              m_lastMousePressEvent;
222   bool                 m_filterActive;
223 };
224 
225 class KGlobalLedgerViewPrivate : public KMyMoneyViewBasePrivate
226 {
Q_DECLARE_PUBLIC(KGlobalLedgerView)227   Q_DECLARE_PUBLIC(KGlobalLedgerView)
228 
229 public:
230   explicit KGlobalLedgerViewPrivate(KGlobalLedgerView *qq) :
231     q_ptr(qq),
232     m_mousePressFilter(0),
233     m_registerSearchLine(0),
234     m_precision(2),
235     m_recursion(false),
236     m_showDetails(false),
237     m_action(eWidgets::eRegister::Action::None),
238     m_filterProxyModel(0),
239     m_accountComboBox(0),
240     m_balanceIsApproximated(false),
241     m_toolbarFrame(nullptr),
242     m_registerFrame(nullptr),
243     m_buttonFrame(nullptr),
244     m_formFrame(nullptr),
245     m_summaryFrame(nullptr),
246     m_register(nullptr),
247     m_buttonbar(nullptr),
248     m_leftSummaryLabel(nullptr),
249     m_centerSummaryLabel(nullptr),
250     m_rightSummaryLabel(nullptr),
251     m_form(nullptr),
252     m_needLoad(true),
253     m_newAccountLoaded(true),
254     m_inEditMode(false),
255     m_transactionEditor(nullptr),
256     m_balanceWarning(nullptr),
257     m_moveToAccountSelector(nullptr),
258     m_endingBalanceDlg(nullptr),
259     m_searchDlg(nullptr)
260   {
261   }
262 
~KGlobalLedgerViewPrivate()263   ~KGlobalLedgerViewPrivate()
264   {
265     delete m_moveToAccountSelector;
266     delete m_endingBalanceDlg;
267     delete m_searchDlg;
268   }
269 
init()270   void init()
271   {
272     Q_Q(KGlobalLedgerView);
273     m_needLoad = false;
274     auto vbox = new QVBoxLayout(q);
275     q->setLayout(vbox);
276     vbox->setSpacing(6);
277     vbox->setMargin(0);
278 
279     m_mousePressFilter = new MousePressFilter((QWidget*)q);
280     m_action = eWidgets::eRegister::Action::None;
281 
282     // the proxy filter model
283     m_filterProxyModel = new AccountNamesFilterProxyModel(q);
284     m_filterProxyModel->addAccountGroup(QVector<eMyMoney::Account::Type> {eMyMoney::Account::Type::Asset, eMyMoney::Account::Type::Liability, eMyMoney::Account::Type::Equity});
285     auto const model = Models::instance()->accountsModel();
286     m_filterProxyModel->setSourceModel(model);
287     m_filterProxyModel->setSourceColumns(model->getColumns());
288     m_filterProxyModel->sort((int)eAccountsModel::Column::Account);
289 
290     // create the toolbar frame at the top of the view
291     m_toolbarFrame = new QFrame();
292     QHBoxLayout* toolbarLayout = new QHBoxLayout(m_toolbarFrame);
293     toolbarLayout->setContentsMargins(0, 0, 0, 0);
294     toolbarLayout->setSpacing(6);
295 
296     // the account selector widget
297     m_accountComboBox = new KMyMoneyAccountCombo();
298     m_accountComboBox->setModel(m_filterProxyModel);
299     toolbarLayout->addWidget(m_accountComboBox);
300 
301     vbox->addWidget(m_toolbarFrame);
302     toolbarLayout->setStretchFactor(m_accountComboBox, 60);
303     // create the register frame
304     m_registerFrame = new QFrame();
305     QVBoxLayout* registerFrameLayout = new QVBoxLayout(m_registerFrame);
306     registerFrameLayout->setContentsMargins(0, 0, 0, 0);
307     registerFrameLayout->setSpacing(0);
308     vbox->addWidget(m_registerFrame);
309     vbox->setStretchFactor(m_registerFrame, 2);
310     m_register = new KMyMoneyRegister::Register(m_registerFrame);
311     m_register->setUsedWithEditor(true);
312     registerFrameLayout->addWidget(m_register);
313     m_register->installEventFilter(q);
314     q->connect(m_register, &KMyMoneyRegister::Register::openContextMenu, q, &KGlobalLedgerView::slotTransactionsContextMenuRequested);
315     q->connect(m_register, &KMyMoneyRegister::Register::transactionsSelected, q, &KGlobalLedgerView::slotUpdateSummaryLine);
316     q->connect(m_register->horizontalHeader(), &QWidget::customContextMenuRequested, q, &KGlobalLedgerView::slotSortOptions);
317     q->connect(m_register, &KMyMoneyRegister::Register::reconcileStateColumnClicked, q, &KGlobalLedgerView::slotToggleTransactionMark);
318 
319     // insert search line widget
320 
321     m_registerSearchLine = new KMyMoneyRegister::RegisterSearchLineWidget(m_register, m_toolbarFrame);
322     toolbarLayout->addWidget(m_registerSearchLine);
323     toolbarLayout->setStretchFactor(m_registerSearchLine, 100);
324     // create the summary frame
325     m_summaryFrame = new QFrame();
326     QHBoxLayout* summaryFrameLayout = new QHBoxLayout(m_summaryFrame);
327     summaryFrameLayout->setContentsMargins(0, 0, 0, 0);
328     summaryFrameLayout->setSpacing(0);
329     m_leftSummaryLabel = new QLabel(m_summaryFrame);
330     m_centerSummaryLabel = new QLabel(m_summaryFrame);
331     m_rightSummaryLabel = new QLabel(m_summaryFrame);
332     summaryFrameLayout->addWidget(m_leftSummaryLabel);
333     QSpacerItem* spacer = new QSpacerItem(20, 1, QSizePolicy::Expanding, QSizePolicy::Minimum);
334     summaryFrameLayout->addItem(spacer);
335     summaryFrameLayout->addWidget(m_centerSummaryLabel);
336     spacer = new QSpacerItem(20, 1, QSizePolicy::Expanding, QSizePolicy::Minimum);
337     summaryFrameLayout->addItem(spacer);
338     summaryFrameLayout->addWidget(m_rightSummaryLabel);
339     vbox->addWidget(m_summaryFrame);
340 
341     // create the button frame
342     m_buttonFrame = new QFrame(q);
343     QVBoxLayout* buttonLayout = new QVBoxLayout(m_buttonFrame);
344     buttonLayout->setContentsMargins(0, 0, 0, 0);
345     buttonLayout->setSpacing(0);
346     vbox->addWidget(m_buttonFrame);
347     m_buttonbar = new KToolBar(m_buttonFrame, 0, true);
348     m_buttonbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
349     buttonLayout->addWidget(m_buttonbar);
350 
351     m_buttonbar->addAction(pActions[eMenu::Action::NewTransaction]);
352     m_buttonbar->addAction(pActions[eMenu::Action::DeleteTransaction]);
353     m_buttonbar->addAction(pActions[eMenu::Action::EditTransaction]);
354     m_buttonbar->addAction(pActions[eMenu::Action::EnterTransaction]);
355     m_buttonbar->addAction(pActions[eMenu::Action::CancelTransaction]);
356     m_buttonbar->addAction(pActions[eMenu::Action::AcceptTransaction]);
357     m_buttonbar->addAction(pActions[eMenu::Action::MatchTransaction]);
358 
359     // create the transaction form frame
360     m_formFrame = new QFrame(q);
361     QVBoxLayout* frameLayout = new QVBoxLayout(m_formFrame);
362     frameLayout->setContentsMargins(5, 5, 5, 5);
363     frameLayout->setSpacing(0);
364     m_form = new KMyMoneyTransactionForm::TransactionForm(m_formFrame);
365     frameLayout->addWidget(m_form->getTabBar(m_formFrame));
366     frameLayout->addWidget(m_form);
367     m_formFrame->setFrameShape(QFrame::Panel);
368     m_formFrame->setFrameShadow(QFrame::Raised);
369     vbox->addWidget(m_formFrame);
370 
371     q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KGlobalLedgerView::refresh);
372     q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KGlobalLedgerView::slotUpdateMoveToAccountMenu);
373     q->connect(m_register, static_cast<void (KMyMoneyRegister::Register::*)(KMyMoneyRegister::Transaction *)>(&KMyMoneyRegister::Register::focusChanged), m_form, &KMyMoneyTransactionForm::TransactionForm::slotSetTransaction);
374     q->connect(m_register, static_cast<void (KMyMoneyRegister::Register::*)()>(&KMyMoneyRegister::Register::focusChanged), q, &KGlobalLedgerView::updateLedgerActionsInternal);
375 //    q->connect(m_accountComboBox, &KMyMoneyAccountCombo::accountSelected, q, &KGlobalLedgerView::slotAccountSelected);
376     q->connect(m_accountComboBox, &KMyMoneyAccountCombo::accountSelected, q, static_cast<void (KGlobalLedgerView::*)(const QString&)>(&KGlobalLedgerView::slotSelectAccount));
377     q->connect(m_accountComboBox, &KMyMoneyAccountCombo::accountSelected, q, &KGlobalLedgerView::slotUpdateMoveToAccountMenu);
378     q->connect(m_register, &KMyMoneyRegister::Register::transactionsSelected, q, &KGlobalLedgerView::slotTransactionsSelected);
379     q->connect(m_register, &KMyMoneyRegister::Register::transactionsSelected, q, &KGlobalLedgerView::slotUpdateMoveToAccountMenu);
380     q->connect(m_register, &KMyMoneyRegister::Register::editTransaction, q, &KGlobalLedgerView::slotEditTransaction);
381     q->connect(m_register, &KMyMoneyRegister::Register::emptyItemSelected, q, &KGlobalLedgerView::slotNewTransaction);
382     q->connect(m_register, &KMyMoneyRegister::Register::aboutToSelectItem, q, &KGlobalLedgerView::slotAboutToSelectItem);
383     q->connect(m_mousePressFilter, &MousePressFilter::mousePressedOnExternalWidget, q, &KGlobalLedgerView::slotCancelOrEnterTransactions);
384 
385     q->connect(m_form, &KMyMoneyTransactionForm::TransactionForm::newTransaction, q, static_cast<void (KGlobalLedgerView::*)(eWidgets::eRegister::Action)>(&KGlobalLedgerView::slotNewTransactionForm));
386 
387     // setup mouse press filter
388     m_mousePressFilter->addWidget(m_formFrame);
389     m_mousePressFilter->addWidget(m_buttonFrame);
390     m_mousePressFilter->addWidget(m_summaryFrame);
391     m_mousePressFilter->addWidget(m_registerFrame);
392 
393     m_tooltipPosn = QPoint();
394   }
395 
396   /**
397     * This method reloads the account selection combo box of the
398     * view with all asset and liability accounts from the engine.
399     * If the account id of the current account held in @p m_accountId is
400     * empty or if the referenced account does not exist in the engine,
401     * the first account found in the list will be made the current account.
402     */
loadAccounts()403   void loadAccounts()
404   {
405     const auto file = MyMoneyFile::instance();
406 
407     // check if the current account still exists and make it the
408     // current account
409     if (!m_lastSelectedAccountID.isEmpty()) {
410       try {
411         m_currentAccount = file->account(m_lastSelectedAccountID);
412       } catch (const MyMoneyException &) {
413         m_lastSelectedAccountID.clear();
414         m_currentAccount = MyMoneyAccount();
415         m_accountComboBox->setSelected(QString());
416       }
417     }
418 
419     // TODO: check why the invalidate is needed here
420     m_filterProxyModel->invalidate();
421     m_filterProxyModel->sort((int)eAccountsModel::Column::Account);
422     m_filterProxyModel->setHideClosedAccounts(KMyMoneySettings::hideClosedAccounts() && !KMyMoneySettings::showAllAccounts());
423     m_filterProxyModel->setHideEquityAccounts(!KMyMoneySettings::expertMode());
424     m_accountComboBox->expandAll();
425 
426     if (m_currentAccount.id().isEmpty()) {
427       // find the first favorite account
428       QModelIndexList list = m_filterProxyModel->match(m_filterProxyModel->index(0, 0),
429                              (int)eAccountsModel::Role::Favorite,
430                              QVariant(true),
431                              1,
432                              Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
433       if (list.count() > 0) {
434         QVariant accountId = list.front().data((int)eAccountsModel::Role::ID);
435         if (accountId.isValid()) {
436           m_currentAccount = file->account(accountId.toString());
437         }
438       }
439 
440       if (m_currentAccount.id().isEmpty()) {
441         // there are no favorite accounts find any account
442         list = m_filterProxyModel->match(m_filterProxyModel->index(0, 0),
443                                Qt::DisplayRole,
444                                QVariant(QString("*")),
445                                -1,
446                                Qt::MatchFlags(Qt::MatchWildcard | Qt::MatchRecursive));
447         for (QModelIndexList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) {
448           if (!it->parent().isValid())
449             continue; // skip the top level accounts
450           QVariant accountId = (*it).data((int)eAccountsModel::Role::ID);
451           if (accountId.isValid()) {
452             MyMoneyAccount a = file->account(accountId.toString());
453             if (!a.isInvest() && !a.isClosed()) {
454               m_currentAccount = a;
455               break;
456             }
457           }
458         }
459       }
460     }
461 
462     if (!m_currentAccount.id().isEmpty()) {
463       m_accountComboBox->setSelected(m_currentAccount.id());
464       try {
465         m_precision = MyMoneyMoney::denomToPrec(m_currentAccount.fraction());
466       } catch (const MyMoneyException &) {
467         qDebug("Security %s for account %s not found", qPrintable(m_currentAccount.currencyId()), qPrintable(m_currentAccount.name()));
468         m_precision = 2;
469       }
470     }
471   }
472 
473   /**
474     * This method clears the register, form, transaction list. See @sa m_register,
475     * @sa m_transactionList
476     */
clear()477   void clear()
478   {
479     // clear current register contents
480     m_register->clear();
481 
482     // setup header font
483     QFont font = KMyMoneySettings::listHeaderFontEx();
484     QFontMetrics fm(font);
485     int height = fm.lineSpacing() + 6;
486     m_register->horizontalHeader()->setMinimumHeight(height);
487     m_register->horizontalHeader()->setMaximumHeight(height);
488     m_register->horizontalHeader()->setFont(font);
489 
490     // setup cell font
491     font = KMyMoneySettings::listCellFontEx();
492     m_register->setFont(font);
493 
494     // clear the form
495     m_form->clear();
496 
497     // the selected transactions list
498     m_transactionList.clear();
499 
500     // and the selected account in the combo box
501     m_accountComboBox->setSelected(QString());
502 
503     // fraction defaults to two digits
504     m_precision = 2;
505   }
506 
loadView()507   void loadView()
508   {
509     MYMONEYTRACER(tracer);
510     Q_Q(KGlobalLedgerView);
511 
512     // setup form visibility
513     m_formFrame->setVisible(KMyMoneySettings::transactionForm());
514 
515     // no account selected
516 //    emit q->objectSelected(MyMoneyAccount());
517     // no transaction selected
518     KMyMoneyRegister::SelectedTransactions list;
519     emit q->selectByVariant(QVariantList {QVariant::fromValue(list)}, eView::Intent::SelectRegisterTransactions);
520 
521     QMap<QString, bool> isSelected;
522     QString focusItemId;
523     QString backUpFocusItemId;  // in case the focus item is removed
524     QString anchorItemId;
525     QString backUpAnchorItemId; // in case the anchor item is removed
526 
527     if (!m_newAccountLoaded) {
528       // remember the current selected transactions
529       KMyMoneyRegister::RegisterItem* item = m_register->firstItem();
530       for (; item; item = item->nextItem()) {
531         if (item->isSelected()) {
532           isSelected[item->id()] = true;
533         }
534       }
535       // remember the item that has the focus
536       storeId(m_register->focusItem(), focusItemId, backUpFocusItemId);
537       // and the one that has the selection anchor
538       storeId(m_register->anchorItem(), anchorItemId, backUpAnchorItemId);
539     } else {
540       m_registerSearchLine->searchLine()->clear();
541     }
542 
543     // clear the current contents ...
544     clear();
545 
546     // ... load the combobox widget and select current account ...
547     loadAccounts();
548 
549     // ... setup the register columns ...
550     m_register->setupRegister(m_currentAccount);
551 
552     // ... setup the form ...
553     m_form->setupForm(m_currentAccount);
554 
555     if (m_currentAccount.id().isEmpty()) {
556       // if we don't have an account we bail out
557       q->setEnabled(false);
558       return;
559     }
560     q->setEnabled(true);
561 
562     m_register->setUpdatesEnabled(false);
563 
564     // ... and recreate it
565     KMyMoneyRegister::RegisterItem* focusItem = 0;
566     KMyMoneyRegister::RegisterItem* anchorItem = 0;
567     QMap<QString, MyMoneyMoney> actBalance, clearedBalance, futureBalance;
568     QMap<QString, MyMoneyMoney>::iterator it_b;
569     try {
570       // setup the filter to select the transactions we want to display
571       // and update the sort order
572       QString sortOrder;
573       QString key;
574       QDate reconciliationDate = m_reconciliationDate;
575 
576       MyMoneyTransactionFilter filter(m_currentAccount.id());
577       // if it's an investment account, we also take care of
578       // the sub-accounts (stock accounts)
579       if (m_currentAccount.accountType() == eMyMoney::Account::Type::Investment)
580         filter.addAccount(m_currentAccount.accountList());
581 
582       if (isReconciliationAccount()) {
583         key = "kmm-sort-reconcile";
584         sortOrder = KMyMoneySettings::sortReconcileView();
585         filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled);
586         filter.addState((int)eMyMoney::TransactionFilter::State::Cleared);
587       } else {
588         filter.setDateFilter(KMyMoneySettings::startDate().date(), QDate());
589         key = "kmm-sort-std";
590         sortOrder = KMyMoneySettings::sortNormalView();
591         if (KMyMoneySettings::hideReconciledTransactions()
592             && !m_currentAccount.isIncomeExpense()) {
593           filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled);
594           filter.addState((int)eMyMoney::TransactionFilter::State::Cleared);
595         }
596       }
597       filter.setReportAllSplits(true);
598 
599       // check if we have an account override of the sort order
600       if (!m_currentAccount.value(key).isEmpty())
601         sortOrder = m_currentAccount.value(key);
602 
603       // setup sort order
604       m_register->setSortOrder(sortOrder);
605 
606       // retrieve the list from the engine
607       MyMoneyFile::instance()->transactionList(m_transactionList, filter);
608 
609       emit q->slotStatusProgress(0, m_transactionList.count());
610 
611       // create the elements for the register
612       QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it;
613       QMap<QString, int>uniqueMap;
614       int i = 0;
615       for (it = m_transactionList.constBegin(); it != m_transactionList.constEnd(); ++it) {
616         uniqueMap[(*it).first.id()]++;
617         KMyMoneyRegister::Transaction* t = KMyMoneyRegister::Register::transactionFactory(m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]);
618         actBalance[t->split().accountId()] = MyMoneyMoney();
619         emit q->slotStatusProgress(++i, 0);
620         // if we're in reconciliation and the state is cleared, we
621         // force the item to show in dimmed intensity to get a visual focus
622         // on those items, that we need to work on
623         if (isReconciliationAccount() && (*it).second.reconcileFlag() == eMyMoney::Split::State::Cleared) {
624           t->setReducedIntensity(true);
625         }
626       }
627 
628       // create dummy entries for the scheduled transactions if sorted by postdate
629       int period = KMyMoneySettings::schedulePreview();
630       if (m_register->primarySortKey() == eWidgets::SortField::PostDate) {
631         // show scheduled transactions which have a scheduled postdate
632         // within the next 'period' days. In reconciliation mode, the
633         // period starts on the statement date.
634         QDate endDate = QDate::currentDate().addDays(period);
635         if (isReconciliationAccount())
636           endDate = reconciliationDate.addDays(period);
637         QList<MyMoneySchedule> scheduleList = MyMoneyFile::instance()->scheduleList(m_currentAccount.id());
638         while (!scheduleList.isEmpty()) {
639           MyMoneySchedule& s = scheduleList.first();
640           for (;;) {
641             if (s.isFinished() || s.adjustedNextDueDate() > endDate) {
642               break;
643             }
644 
645             MyMoneyTransaction t(s.id(), KMyMoneyUtils::scheduledTransaction(s));
646             if (s.isOverdue() && !KMyMoneySettings::showPlannedScheduleDates()) {
647               // if the transaction is scheduled and overdue, it can't
648               // certainly be posted in the past. So we take today's date
649               // as the alternative
650               t.setPostDate(s.adjustedDate(QDate::currentDate(), s.weekendOption()));
651             } else {
652               t.setPostDate(s.adjustedNextDueDate());
653             }
654             foreach (const auto split, t.splits()) {
655               if (split.accountId() == m_currentAccount.id()) {
656                 new KMyMoneyRegister::StdTransactionScheduled(m_register, t, split, uniqueMap[t.id()]);
657               }
658             }
659             // keep track of this payment locally (not in the engine)
660             if (s.isOverdue() && !KMyMoneySettings::showPlannedScheduleDates()) {
661               s.setLastPayment(QDate::currentDate());
662             } else {
663               s.setLastPayment(s.nextDueDate());
664             }
665 
666             // if this is a one time schedule, we can bail out here as we're done
667             if (s.occurrence() == eMyMoney::Schedule::Occurrence::Once)
668               break;
669 
670             // for all others, we check if the next payment date is still 'in range'
671             QDate nextDueDate = s.nextPayment(s.nextDueDate());
672             if (nextDueDate.isValid()) {
673               s.setNextDueDate(nextDueDate);
674             } else {
675               break;
676             }
677           }
678           scheduleList.pop_front();
679         }
680       }
681 
682       // add the group markers
683       m_register->addGroupMarkers();
684 
685       // sort the transactions according to the sort setting
686       m_register->sortItems();
687 
688       // remove trailing and adjacent markers
689       m_register->removeUnwantedGroupMarkers();
690 
691       // add special markers for reconciliation now so that they do not get
692       // removed by m_register->removeUnwantedGroupMarkers(). Needs resorting
693       // of items but that's ok.
694 
695       KMyMoneyRegister::StatementGroupMarker* statement = 0;
696       KMyMoneyRegister::StatementGroupMarker* dStatement = 0;
697       KMyMoneyRegister::StatementGroupMarker* pStatement = 0;
698 
699       if (isReconciliationAccount()) {
700         switch (m_register->primarySortKey()) {
701           case eWidgets::SortField::PostDate:
702             statement = new KMyMoneyRegister::StatementGroupMarker(m_register, eWidgets::eRegister::CashFlowDirection::Deposit, reconciliationDate, i18n("Statement Details"));
703             m_register->sortItems();
704             break;
705           case eWidgets::SortField::Type:
706             dStatement = new KMyMoneyRegister::StatementGroupMarker(m_register, eWidgets::eRegister::CashFlowDirection::Deposit, reconciliationDate, i18n("Statement Deposit Details"));
707             pStatement = new KMyMoneyRegister::StatementGroupMarker(m_register, eWidgets::eRegister::CashFlowDirection::Payment, reconciliationDate, i18n("Statement Payment Details"));
708             m_register->sortItems();
709             break;
710           default:
711             break;
712         }
713       }
714 
715       // we need at least the balance for the account we currently show
716       actBalance[m_currentAccount.id()] = MyMoneyMoney();
717 
718       if (m_currentAccount.accountType() == eMyMoney::Account::Type::Investment)
719         foreach (const auto accountID, m_currentAccount.accountList())
720           actBalance[accountID] = MyMoneyMoney();
721 
722       // determine balances (actual, cleared). We do this by getting the actual
723       // balance of all entered transactions from the engine and walk the list
724       // of transactions backward. Also re-select a transaction if it was
725       // selected before and setup the focus item.
726 
727       MyMoneyMoney factor(1, 1);
728       if (m_currentAccount.accountGroup() == eMyMoney::Account::Type::Liability
729           || m_currentAccount.accountGroup() == eMyMoney::Account::Type::Equity)
730         factor = -factor;
731 
732       QMap<QString, int> deposits;
733       QMap<QString, int> payments;
734       QMap<QString, MyMoneyMoney> depositAmount;
735       QMap<QString, MyMoneyMoney> paymentAmount;
736       for (it_b = actBalance.begin(); it_b != actBalance.end(); ++it_b) {
737         MyMoneyMoney balance = MyMoneyFile::instance()->balance(it_b.key());
738         balance = balance * factor;
739         clearedBalance[it_b.key()] =
740           futureBalance[it_b.key()] =
741             (*it_b) = balance;
742         deposits[it_b.key()] = payments[it_b.key()] = 0;
743         depositAmount[it_b.key()] = MyMoneyMoney();
744         paymentAmount[it_b.key()] = MyMoneyMoney();
745       }
746 
747       tracer.printf("total balance of %s = %s", qPrintable(m_currentAccount.name()), qPrintable(actBalance[m_currentAccount.id()].formatMoney("", 2)));
748       tracer.printf("future balance of %s = %s", qPrintable(m_currentAccount.name()), qPrintable(futureBalance[m_currentAccount.id()].formatMoney("", 2)));
749       tracer.printf("cleared balance of %s = %s", qPrintable(m_currentAccount.name()), qPrintable(clearedBalance[m_currentAccount.id()].formatMoney("", 2)));
750 
751       KMyMoneyRegister::RegisterItem* p = m_register->lastItem();
752       focusItem = 0;
753 
754       // take care of possibly trailing scheduled transactions (bump up the future balance)
755       while (p) {
756         if (p->isSelectable()) {
757           KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p);
758           if (t && t->isScheduled()) {
759             MyMoneyMoney balance = futureBalance[t->split().accountId()];
760             const MyMoneySplit& split = t->split();
761             // if this split is a stock split, we can't just add the amount of shares
762             if (t->transaction().isStockSplit()) {
763               balance = balance * split.shares();
764             } else {
765               balance += split.shares() * factor;
766             }
767             futureBalance[split.accountId()] = balance;
768           } else if (t && !focusItem)
769             focusItem = p;
770         }
771         p = p->prevItem();
772       }
773 
774       p = m_register->lastItem();
775       while (p) {
776         KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p);
777         if (t) {
778           if (isSelected.contains(t->id()))
779             t->setSelected(true);
780 
781           matchItemById(&focusItem, t, focusItemId, backUpFocusItemId);
782           matchItemById(&anchorItem, t, anchorItemId, backUpAnchorItemId);
783 
784           const MyMoneySplit& split = t->split();
785           MyMoneyMoney balance = futureBalance[split.accountId()];
786           t->setBalance(balance);
787 
788           // if this split is a stock split, we can't just add the amount of shares
789           if (t->transaction().isStockSplit()) {
790             balance /= split.shares();
791           } else {
792             balance -= split.shares() * factor;
793           }
794 
795           if (!t->isScheduled()) {
796             if (isReconciliationAccount() && t->transaction().postDate() <= reconciliationDate && split.reconcileFlag() == eMyMoney::Split::State::Cleared) {
797               if (split.shares().isNegative()) {
798                 payments[split.accountId()]++;
799                 paymentAmount[split.accountId()] += split.shares();
800               } else {
801                 deposits[split.accountId()]++;
802                 depositAmount[split.accountId()] += split.shares();
803               }
804             }
805 
806             if (t->transaction().postDate() > QDate::currentDate()) {
807               tracer.printf("Reducing actual balance by %s because %s/%s(%s) is in the future", qPrintable((split.shares() * factor).formatMoney("", 2)), qPrintable(t->transaction().id()), qPrintable(split.id()), qPrintable(t->transaction().postDate().toString(Qt::ISODate)));
808               actBalance[split.accountId()] -= split.shares() * factor;
809             }
810           }
811           futureBalance[split.accountId()] = balance;
812         }
813         p = p->prevItem();
814       }
815 
816       clearedBalance[m_currentAccount.id()] = MyMoneyFile::instance()->clearedBalance(m_currentAccount.id(), reconciliationDate);
817 
818       tracer.printf("total balance of %s = %s", qPrintable(m_currentAccount.name()), qPrintable(actBalance[m_currentAccount.id()].formatMoney("", 2)));
819       tracer.printf("future balance of %s = %s", qPrintable(m_currentAccount.name()), qPrintable(futureBalance[m_currentAccount.id()].formatMoney("", 2)));
820       tracer.printf("cleared balance of %s = %s", qPrintable(m_currentAccount.name()), qPrintable(clearedBalance[m_currentAccount.id()].formatMoney("", 2)));
821 
822       // update statement information
823       if (statement) {
824         const QString aboutDeposits = i18np("%1 deposit (%2)", "%1 deposits (%2)",
825                                             deposits[m_currentAccount.id()], depositAmount[m_currentAccount.id()].abs().formatMoney(m_currentAccount.fraction()));
826         const QString aboutCharges = i18np("%1 charge (%2)", "%1 charges (%2)",
827                                            deposits[m_currentAccount.id()], depositAmount[m_currentAccount.id()].abs().formatMoney(m_currentAccount.fraction()));
828         const QString aboutPayments = i18np("%1 payment (%2)", "%1 payments (%2)",
829                                             payments[m_currentAccount.id()], paymentAmount[m_currentAccount.id()].abs().formatMoney(m_currentAccount.fraction()));
830 
831         statement->setText(i18nc("%1 is a string, e.g. 7 deposits; %2 is a string, e.g. 4 payments", "%1, %2",
832                                  m_currentAccount.accountType() == eMyMoney::Account::Type::CreditCard ? aboutCharges : aboutDeposits,
833                                  aboutPayments));
834       }
835       if (pStatement) {
836         pStatement->setText(i18np("%1 payment (%2)", "%1 payments (%2)", payments[m_currentAccount.id()]
837                                   , paymentAmount[m_currentAccount.id()].abs().formatMoney(m_currentAccount.fraction())));
838       }
839       if (dStatement) {
840         dStatement->setText(i18np("%1 deposit (%2)", "%1 deposits (%2)", deposits[m_currentAccount.id()]
841                                   , depositAmount[m_currentAccount.id()].abs().formatMoney(m_currentAccount.fraction())));
842       }
843 
844       // add a last empty entry for new transactions
845       // leave some information about the current account
846       MyMoneySplit split;
847       split.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
848       // make sure to use the value specified in the option during reconciliation
849       if (isReconciliationAccount())
850         split.setReconcileFlag(static_cast<eMyMoney::Split::State>(KMyMoneySettings::defaultReconciliationState()));
851       MyMoneyTransaction emptyTransaction;
852       emptyTransaction.setCommodity(m_currentAccount.currencyId());
853       KMyMoneyRegister::Register::transactionFactory(m_register, emptyTransaction, split, 0);
854 
855       m_register->updateRegister(true);
856 
857       if (focusItem) {
858         // in case we have some selected items we just set the focus item
859         // in other cases, we make the focusitem also the selected item
860         if (anchorItem && (anchorItem != focusItem)) {
861           m_register->setFocusItem(focusItem);
862           m_register->setAnchorItem(anchorItem);
863         } else
864           m_register->selectItem(focusItem, true);
865       } else {
866         // just use the empty line at the end if nothing else exists in the ledger
867         p = m_register->lastItem();
868         m_register->setFocusItem(p);
869         m_register->selectItem(p);
870         focusItem = p;
871       }
872 
873       updateSummaryLine(actBalance, clearedBalance);
874       emit q->slotStatusProgress(-1, -1);
875 
876     } catch (const MyMoneyException &) {
877       m_currentAccount = MyMoneyAccount();
878       clear();
879     }
880 
881     m_showDetails = KMyMoneySettings::showRegisterDetailed();
882 
883     // and tell everyone what's selected
884     emit q->selectByObject(m_currentAccount, eView::Intent::None);
885     KMyMoneyRegister::SelectedTransactions actualSelection(m_register);
886     emit q->selectByVariant(QVariantList {QVariant::fromValue(actualSelection)}, eView::Intent::SelectRegisterTransactions);
887   }
888 
selectTransaction(const QString & id)889   void selectTransaction(const QString& id)
890   {
891     if (!id.isEmpty()) {
892       KMyMoneyRegister::RegisterItem* p = m_register->lastItem();
893       while (p) {
894         KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p);
895         if (t) {
896           if (t->transaction().id() == id) {
897             m_register->selectItem(t);
898             m_register->ensureItemVisible(t);
899             break;
900           }
901         }
902         p = p->prevItem();
903       }
904     }
905   }
906 
907   /**
908    * @brief selects transactions for processing with slots
909    * @param list of transactions
910    * @return false if only schedule is to be selected
911    */
selectTransactions(const KMyMoneyRegister::SelectedTransactions list)912   bool selectTransactions(const KMyMoneyRegister::SelectedTransactions list)
913   {
914     Q_Q(KGlobalLedgerView);
915     // list can either contain a list of transactions or a single selected scheduled transaction
916     // in the latter case, the transaction id is actually the one of the schedule. In order
917     // to differentiate between the two, we just ask for the schedule. If we don't find one - because
918     // we passed the id of a real transaction - then we know that fact.  We use the schedule here,
919     // because the list of schedules is kept in a cache by MyMoneyFile. This way, we save some trips
920     // to the backend which we would have to do if we check for the transaction.
921     m_selectedTransactions.clear();
922     auto sch = MyMoneySchedule();
923     auto ret = true;
924 
925     m_accountGoto.clear();
926     m_payeeGoto.clear();
927     if (!list.isEmpty() && !list.first().isScheduled()) {
928         m_selectedTransactions = list;
929         if (list.count() == 1) {
930             const MyMoneySplit& sp = m_selectedTransactions[0].split();
931             if (!sp.payeeId().isEmpty()) {
932                 try {
933                   auto payee = MyMoneyFile::instance()->payee(sp.payeeId());
934                   if (!payee.name().isEmpty()) {
935                       m_payeeGoto = payee.id();
936                       auto name = payee.name();
937                       name.replace(QRegExp("&(?!&)"), "&&");
938                       pActions[Action::GoToPayee]->setText(i18n("Go to '%1'", name));
939                     }
940                 } catch (const MyMoneyException &) {
941                 }
942               }
943             try {
944               const auto& t = m_selectedTransactions[0].transaction();
945               // search the first non-income/non-expense account and use it for the 'goto account'
946               const auto& selectedTransactionSplit = m_selectedTransactions[0].split();
947               foreach (const auto split, t.splits()) {
948                   if (split.id() != selectedTransactionSplit.id()) {
949                       auto acc = MyMoneyFile::instance()->account(split.accountId());
950                       if (!acc.isIncomeExpense()) {
951                           // for stock accounts we show the portfolio account
952                           if (acc.isInvest()) {
953                               acc = MyMoneyFile::instance()->account(acc.parentAccountId());
954                             }
955                           m_accountGoto = acc.id();
956                           auto name = acc.name();
957                           name.replace(QRegExp("&(?!&)"), "&&");
958                           pActions[Action::GoToAccount]->setText(i18n("Go to '%1'", name));
959                           break;
960                         }
961                     }
962                 }
963             } catch (const MyMoneyException &) {
964             }
965           }
966       } else if (!list.isEmpty()) {
967         sch = MyMoneyFile::instance()->schedule(list.first().scheduleId());
968         m_selectedTransactions.append(list.first());
969         ret = false;
970       }
971 
972     emit q->selectByObject(sch, eView::Intent::None);
973 
974     // make sure, we show some neutral menu entry if we don't have an object
975     if (m_payeeGoto.isEmpty())
976       pActions[Action::GoToPayee]->setText(i18n("Go to payee"));
977     if (m_accountGoto.isEmpty())
978       pActions[Action::GoToAccount]->setText(i18n("Go to account"));
979     return ret;
980   }
981 
982   /**
983     * Returns @a true if setReconciliationAccount() has been called for
984     * the current loaded account.
985     *
986     * @retval true current account is in reconciliation mode
987     * @retval false current account is not in reconciliation mode
988     */
isReconciliationAccount()989   bool isReconciliationAccount() const
990   {
991     return m_currentAccount.id() == m_reconciliationAccount.id();
992   }
993 
994   /**
995     * Updates the values on the summary line beneath the register with
996     * the given values. The contents shown differs between reconciliation
997     * mode and normal mode.
998     *
999     * @param actBalance map of account indexed values to be used as actual balance
1000     * @param clearedBalance map of account indexed values to be used as cleared balance
1001     */
updateSummaryLine(const QMap<QString,MyMoneyMoney> & actBalance,const QMap<QString,MyMoneyMoney> & clearedBalance)1002   void updateSummaryLine(const QMap<QString, MyMoneyMoney>& actBalance, const QMap<QString, MyMoneyMoney>& clearedBalance)
1003   {
1004     Q_Q(KGlobalLedgerView);
1005     const auto file = MyMoneyFile::instance();
1006     m_leftSummaryLabel->show();
1007     m_centerSummaryLabel->show();
1008     m_rightSummaryLabel->show();
1009 
1010     if (isReconciliationAccount()) {
1011       if (m_currentAccount.accountType() != eMyMoney::Account::Type::Investment) {
1012         m_leftSummaryLabel->setText(i18n("Statement: %1", m_endingBalance.formatMoney("", m_precision)));
1013         m_centerSummaryLabel->setText(i18nc("Cleared balance", "Cleared: %1", clearedBalance[m_currentAccount.id()].formatMoney("", m_precision)));
1014         m_totalBalance = clearedBalance[m_currentAccount.id()] - m_endingBalance;
1015       }
1016     } else {
1017       // update summary line in normal mode
1018       QDate reconcileDate = m_currentAccount.lastReconciliationDate();
1019 
1020       if (reconcileDate.isValid()) {
1021         m_leftSummaryLabel->setText(i18n("Last reconciled: %1", QLocale().toString(reconcileDate, QLocale::ShortFormat)));
1022       } else {
1023         m_leftSummaryLabel->setText(i18n("Never reconciled"));
1024       }
1025 
1026       QPalette palette = m_rightSummaryLabel->palette();
1027       palette.setColor(m_rightSummaryLabel->foregroundRole(), m_leftSummaryLabel->palette().color(q->foregroundRole()));
1028       if (m_currentAccount.accountType() != eMyMoney::Account::Type::Investment) {
1029         m_centerSummaryLabel->setText(i18nc("Cleared balance", "Cleared: %1", clearedBalance[m_currentAccount.id()].formatMoney("", m_precision)));
1030         m_totalBalance = actBalance[m_currentAccount.id()];
1031       } else {
1032         m_centerSummaryLabel->hide();
1033         MyMoneyMoney balance;
1034         MyMoneySecurity base = file->baseCurrency();
1035         QMap<QString, MyMoneyMoney>::const_iterator it_b;
1036         // reset the approximated flag
1037         m_balanceIsApproximated = false;
1038         for (it_b = actBalance.begin(); it_b != actBalance.end(); ++it_b) {
1039           MyMoneyAccount stock = file->account(it_b.key());
1040           QString currencyId = stock.currencyId();
1041           MyMoneySecurity sec = file->security(currencyId);
1042           MyMoneyMoney rate(1, 1);
1043 
1044           if (stock.isInvest()) {
1045             currencyId = sec.tradingCurrency();
1046             const MyMoneyPrice &priceInfo = file->price(sec.id(), currencyId);
1047             m_balanceIsApproximated |= !priceInfo.isValid();
1048             rate = priceInfo.rate(sec.tradingCurrency());
1049           }
1050 
1051           if (currencyId != base.id()) {
1052             const MyMoneyPrice &priceInfo = file->price(sec.tradingCurrency(), base.id());
1053             m_balanceIsApproximated |= !priceInfo.isValid();
1054             rate = (rate * priceInfo.rate(base.id())).convertPrecision(sec.pricePrecision());
1055           }
1056           balance += ((*it_b) * rate).convert(base.smallestAccountFraction());
1057         }
1058         m_totalBalance = balance;
1059       }
1060       m_rightSummaryLabel->setPalette(palette);
1061     }
1062     // determine the number of selected transactions
1063     KMyMoneyRegister::SelectedTransactions selection;
1064     m_register->selectedTransactions(selection);
1065     q->slotUpdateSummaryLine(selection);
1066   }
1067 
1068   /**
1069     * setup the default action according to the current account type
1070     */
setupDefaultAction()1071   void setupDefaultAction()
1072   {
1073     switch (m_currentAccount.accountType()) {
1074       case eMyMoney::Account::Type::Asset:
1075       case eMyMoney::Account::Type::AssetLoan:
1076       case eMyMoney::Account::Type::Savings:
1077         m_action = eWidgets::eRegister::Action::Deposit;
1078         break;
1079       default:
1080         m_action = eWidgets::eRegister::Action::Withdrawal;
1081         break;
1082     }
1083   }
1084 
1085   // used to store the id of an item and the id of an immediate unselected sibling
storeId(KMyMoneyRegister::RegisterItem * item,QString & id,QString & backupId)1086   void storeId(KMyMoneyRegister::RegisterItem *item, QString &id, QString &backupId) {
1087     if (item) {
1088       // the id of the item
1089       id = item->id();
1090       // the id of the item's previous/next unselected item
1091       for (KMyMoneyRegister::RegisterItem *it = item->prevItem(); it != 0 && backupId.isEmpty(); it = it->prevItem()) {
1092         if (!it->isSelected()) {
1093           backupId = it->id();
1094         }
1095       }
1096       // if we didn't found previous unselected items search trough the next items
1097       for (KMyMoneyRegister::RegisterItem *it = item->nextItem(); it != 0 && backupId.isEmpty(); it = it->nextItem()) {
1098         if (!it->isSelected()) {
1099           backupId = it->id();
1100         }
1101       }
1102     }
1103   }
1104 
1105   // use to match an item by it's id or a backup id which has a lower precedence
matchItemById(KMyMoneyRegister::RegisterItem ** item,KMyMoneyRegister::Transaction * t,QString & id,QString & backupId)1106   void matchItemById(KMyMoneyRegister::RegisterItem **item, KMyMoneyRegister::Transaction* t, QString &id, QString &backupId) {
1107     if (!backupId.isEmpty() && t->id() == backupId)
1108       *item = t;
1109     if (!id.isEmpty() && t->id() == id) {
1110       // we found the real thing there's no need for the backup anymore
1111       backupId.clear();
1112       *item = t;
1113     }
1114   }
1115 
canProcessTransactions(const KMyMoneyRegister::SelectedTransactions & list,QString & tooltip)1116   bool canProcessTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const
1117   {
1118     if (m_register->focusItem() == 0)
1119       return false;
1120 
1121     bool rc = true;
1122     if (list.warnLevel() == KMyMoneyRegister::SelectedTransaction::OneAccountClosed) {
1123       // scan all splits for the first closed account
1124       QString closedAccount;
1125       foreach(const auto selectedTransaction, list) {
1126         foreach(const auto split, selectedTransaction.transaction().splits()) {
1127           const auto id = split.accountId();
1128           const auto acc = MyMoneyFile::instance()->account(id);
1129           if (acc.isClosed()) {
1130             closedAccount = acc.name();
1131             // we're done
1132             rc = false;
1133             break;
1134           }
1135         }
1136         if(!rc)
1137           break;
1138       }
1139       tooltip = i18n("Cannot process transactions in account %1, which is closed.", closedAccount);
1140       showTooltip(tooltip);
1141       return false;
1142     }
1143 
1144     if (!m_register->focusItem()->isSelected()) {
1145       tooltip = i18n("Cannot process transaction with focus if it is not selected.");
1146       showTooltip(tooltip);
1147       return false;
1148     }
1149     tooltip.clear();
1150     return !list.isEmpty();
1151   }
1152 
showTooltip(const QString msg)1153   void showTooltip(const QString msg) const
1154   {
1155     QToolTip::showText(m_tooltipPosn, msg);
1156   }
1157 
createNewTransaction()1158   bool createNewTransaction()
1159   {
1160     Q_Q(KGlobalLedgerView);
1161     auto rc = false;
1162     QString txt;
1163     if (q->canCreateTransactions(txt)) {
1164       rc = q->selectEmptyTransaction();
1165     }
1166     return rc;
1167   }
1168 
startEdit(const KMyMoneyRegister::SelectedTransactions & list)1169   TransactionEditor* startEdit(const KMyMoneyRegister::SelectedTransactions& list)
1170   {
1171     Q_Q(KGlobalLedgerView);
1172     TransactionEditor* editor = 0;
1173     QString txt;
1174     if (q->canEditTransactions(list, txt) || q->canCreateTransactions(txt)) {
1175       editor = q->startEdit(list);
1176     }
1177     return editor;
1178   }
1179 
doDeleteTransactions()1180   void doDeleteTransactions()
1181   {
1182     Q_Q(KGlobalLedgerView);
1183     KMyMoneyRegister::SelectedTransactions list = m_selectedTransactions;
1184     KMyMoneyRegister::SelectedTransactions::iterator it_t;
1185     int cnt = list.count();
1186     int i = 0;
1187     emit q->slotStatusProgress(0, cnt);
1188     MyMoneyFileTransaction ft;
1189     const auto file = MyMoneyFile::instance();
1190     try {
1191       it_t = list.begin();
1192       while (it_t != list.end()) {
1193         const auto accountId = (*it_t).split().accountId();
1194         const auto deletedNum = (*it_t).split().number();
1195         const auto transactionId = (*it_t).transaction().id();
1196         // only remove those transactions that do not reference a closed account
1197         if (!file->referencesClosedAccount((*it_t).transaction())) {
1198           file->removeTransaction((*it_t).transaction());
1199           // remove all those references in the list of selected transactions
1200           // that refer to the same transaction we just removed so that we
1201           // will not be caught by an exception later on (see bko #285310)
1202           while (it_t != list.end()) {
1203             if (transactionId == (*it_t).transaction().id()) {
1204               it_t = list.erase(it_t);
1205               i++; // bump count of deleted transactions
1206             } else {
1207               ++it_t;
1208             }
1209           }
1210 
1211         } else {
1212           list.erase(it_t);
1213         }
1214         it_t = list.begin();
1215 
1216         // need to ensure "nextCheckNumber" is still correct
1217         auto acc = file->account(accountId);
1218 
1219         // the "lastNumberUsed" might have been the txn number deleted
1220         // so adjust it
1221         if (deletedNum == acc.value("lastNumberUsed")) {
1222           // decrement deletedNum and set new "lastNumberUsed"
1223           QString num = KMyMoneyUtils::getAdjacentNumber(deletedNum, -1);
1224           acc.setValue("lastNumberUsed", num);
1225           file->modifyAccount(acc);
1226         }
1227 
1228         emit q->slotStatusProgress(i++, 0);
1229       }
1230       ft.commit();
1231 
1232     } catch (const MyMoneyException &e) {
1233       KMessageBox::detailedSorry(q, i18n("Unable to delete transaction(s)"), e.what());
1234     }
1235     emit q->slotStatusProgress(-1, -1);
1236   }
1237 
deleteTransactionEditor()1238   void deleteTransactionEditor()
1239   {
1240     // make sure, we don't use the transaction editor pointer
1241     // anymore from now on
1242     auto p = m_transactionEditor;
1243     m_transactionEditor = nullptr;
1244     delete p;
1245   }
1246 
transactionUnmatch()1247   void transactionUnmatch()
1248   {
1249     Q_Q(KGlobalLedgerView);
1250     KMyMoneyRegister::SelectedTransactions::const_iterator it;
1251     MyMoneyFileTransaction ft;
1252     try {
1253       for (it = m_selectedTransactions.constBegin(); it != m_selectedTransactions.constEnd(); ++it) {
1254         if ((*it).split().isMatched()) {
1255           TransactionMatcher matcher(m_currentAccount);
1256           matcher.unmatch((*it).transaction(), (*it).split());
1257         }
1258       }
1259       ft.commit();
1260 
1261     } catch (const MyMoneyException &e) {
1262       KMessageBox::detailedSorry(q, i18n("Unable to unmatch the selected transactions"), e.what());
1263     }
1264   }
1265 
transactionMatch()1266   void transactionMatch()
1267   {
1268     Q_Q(KGlobalLedgerView);
1269     if (m_selectedTransactions.count() != 2)
1270       return;
1271 
1272     MyMoneyTransaction startMatchTransaction;
1273     MyMoneyTransaction endMatchTransaction;
1274     MyMoneySplit startSplit;
1275     MyMoneySplit endSplit;
1276 
1277     KMyMoneyRegister::SelectedTransactions::const_iterator it;
1278     KMyMoneyRegister::SelectedTransactions toBeDeleted;
1279     for (it = m_selectedTransactions.constBegin(); it != m_selectedTransactions.constEnd(); ++it) {
1280       if ((*it).transaction().isImported()) {
1281         if (endMatchTransaction.id().isEmpty()) {
1282           endMatchTransaction = (*it).transaction();
1283           endSplit = (*it).split();
1284           toBeDeleted << *it;
1285         } else {
1286           //This is a second imported transaction, we still want to merge
1287           startMatchTransaction = (*it).transaction();
1288           startSplit = (*it).split();
1289         }
1290       } else if (!(*it).split().isMatched()) {
1291         if (startMatchTransaction.id().isEmpty()) {
1292           startMatchTransaction = (*it).transaction();
1293           startSplit = (*it).split();
1294         } else {
1295           endMatchTransaction = (*it).transaction();
1296           endSplit = (*it).split();
1297           toBeDeleted << *it;
1298         }
1299       }
1300     }
1301 
1302   #if 0
1303     KMergeTransactionsDlg dlg(m_selectedAccount);
1304     dlg.addTransaction(startMatchTransaction);
1305     dlg.addTransaction(endMatchTransaction);
1306     if (dlg.exec() == QDialog::Accepted)
1307   #endif
1308     {
1309       MyMoneyFileTransaction ft;
1310       try {
1311         if (startMatchTransaction.id().isEmpty())
1312           throw MYMONEYEXCEPTION(QString::fromLatin1("No manually entered transaction selected for matching"));
1313         if (endMatchTransaction.id().isEmpty())
1314           throw MYMONEYEXCEPTION(QString::fromLatin1("No imported transaction selected for matching"));
1315 
1316         TransactionMatcher matcher(m_currentAccount);
1317         matcher.match(startMatchTransaction, startSplit, endMatchTransaction, endSplit, true);
1318         ft.commit();
1319       } catch (const MyMoneyException &e) {
1320         KMessageBox::detailedSorry(q, i18n("Unable to match the selected transactions"), e.what());
1321       }
1322     }
1323   }
1324 
1325   /**
1326     * Mark the selected transactions as provided by @a flag. If
1327     * flag is @a MyMoneySplit::Unknown, the future state depends
1328     * on the current stat of the split's flag according to the
1329     * following table:
1330     *
1331     * - NotReconciled --> Cleared
1332     * - Cleared --> Reconciled
1333     * - Reconciled --> NotReconciled
1334     */
markTransaction(eMyMoney::Split::State flag)1335   void markTransaction(eMyMoney::Split::State flag)
1336   {
1337     Q_Q(KGlobalLedgerView);
1338     auto list = m_selectedTransactions;
1339     KMyMoneyRegister::SelectedTransactions::const_iterator it_t;
1340     auto cnt = list.count();
1341     auto i = 0;
1342     emit q->slotStatusProgress(0, cnt);
1343     MyMoneyFileTransaction ft;
1344     try {
1345       for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) {
1346         // turn on signals before we modify the last entry in the list
1347         cnt--;
1348         MyMoneyFile::instance()->blockSignals(cnt != 0);
1349 
1350         // get a fresh copy
1351         auto t = MyMoneyFile::instance()->transaction((*it_t).transaction().id());
1352         auto sp = t.splitById((*it_t).split().id());
1353         if (sp.reconcileFlag() != flag) {
1354           if (flag == eMyMoney::Split::State::Unknown) {
1355             if (m_reconciliationAccount.id().isEmpty()) {
1356               // in normal mode we cycle through all states
1357               switch (sp.reconcileFlag()) {
1358                 case eMyMoney::Split::State::NotReconciled:
1359                   sp.setReconcileFlag(eMyMoney::Split::State::Cleared);
1360                   break;
1361                 case eMyMoney::Split::State::Cleared:
1362                   sp.setReconcileFlag(eMyMoney::Split::State::Reconciled);
1363                   break;
1364                 case eMyMoney::Split::State::Reconciled:
1365                   sp.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
1366                   break;
1367                 default:
1368                   break;
1369               }
1370             } else {
1371               // in reconciliation mode we skip the reconciled state
1372               switch (sp.reconcileFlag()) {
1373                 case eMyMoney::Split::State::NotReconciled:
1374                   sp.setReconcileFlag(eMyMoney::Split::State::Cleared);
1375                   break;
1376                 case eMyMoney::Split::State::Cleared:
1377                   sp.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
1378                   break;
1379                 default:
1380                   break;
1381               }
1382             }
1383           } else {
1384             sp.setReconcileFlag(flag);
1385           }
1386 
1387           t.modifySplit(sp);
1388           MyMoneyFile::instance()->modifyTransaction(t);
1389         }
1390         emit q->slotStatusProgress(i++, 0);
1391       }
1392       emit q->slotStatusProgress(-1, -1);
1393       ft.commit();
1394     } catch (const MyMoneyException &e) {
1395       KMessageBox::detailedSorry(q, i18n("Unable to modify transaction"), e.what());
1396     }
1397   }
1398 
1399   // move a stock transaction from one investment account to another
moveInvestmentTransaction(const QString &,const QString & toId,const MyMoneyTransaction & tx)1400   void moveInvestmentTransaction(const QString& /*fromId*/,
1401       const QString& toId,
1402       const MyMoneyTransaction& tx)
1403   {
1404     MyMoneyAccount toInvAcc = MyMoneyFile::instance()->account(toId);
1405     MyMoneyTransaction t(tx);
1406     // first determine which stock we are dealing with.
1407     // fortunately, investment transactions have only one stock involved
1408     QString stockAccountId;
1409     QString stockSecurityId;
1410     MyMoneySplit s;
1411     foreach (const auto split, t.splits()) {
1412       stockAccountId = split.accountId();
1413       stockSecurityId =
1414         MyMoneyFile::instance()->account(stockAccountId).currencyId();
1415       if (!MyMoneyFile::instance()->security(stockSecurityId).isCurrency()) {
1416         s = split;
1417         break;
1418       }
1419     }
1420     // Now check the target investment account to see if it
1421     // contains a stock with this id
1422     QString newStockAccountId;
1423     foreach (const auto sAccount, toInvAcc.accountList()) {
1424       if (MyMoneyFile::instance()->account(sAccount).currencyId() ==
1425           stockSecurityId) {
1426         newStockAccountId = sAccount;
1427         break;
1428       }
1429     }
1430     // if it doesn't exist, we need to add it as a copy of the old one
1431     // no 'copyAccount()' function??
1432     if (newStockAccountId.isEmpty()) {
1433       MyMoneyAccount stockAccount =
1434         MyMoneyFile::instance()->account(stockAccountId);
1435       MyMoneyAccount newStock;
1436       newStock.setName(stockAccount.name());
1437       newStock.setNumber(stockAccount.number());
1438       newStock.setDescription(stockAccount.description());
1439       newStock.setInstitutionId(stockAccount.institutionId());
1440       newStock.setOpeningDate(stockAccount.openingDate());
1441       newStock.setAccountType(stockAccount.accountType());
1442       newStock.setCurrencyId(stockAccount.currencyId());
1443       newStock.setClosed(stockAccount.isClosed());
1444       MyMoneyFile::instance()->addAccount(newStock, toInvAcc);
1445       newStockAccountId = newStock.id();
1446     }
1447     // now update the split and the transaction
1448     s.setAccountId(newStockAccountId);
1449     t.modifySplit(s);
1450     MyMoneyFile::instance()->modifyTransaction(t);
1451   }
1452 
createTransactionMoveMenu()1453   void createTransactionMoveMenu()
1454   {
1455     Q_Q(KGlobalLedgerView);
1456     if (!m_moveToAccountSelector) {
1457       auto menu = pMenus[eMenu::Menu::MoveTransaction];
1458       if (menu ) {
1459         auto accountSelectorAction = new QWidgetAction(menu);
1460         m_moveToAccountSelector = new KMyMoneyAccountSelector(menu, 0, false);
1461         m_moveToAccountSelector->setObjectName("transaction_move_menu_selector");
1462         accountSelectorAction->setDefaultWidget(m_moveToAccountSelector);
1463         menu->addAction(accountSelectorAction);
1464         q->connect(m_moveToAccountSelector, &QObject::destroyed, q, &KGlobalLedgerView::slotObjectDestroyed);
1465         q->connect(m_moveToAccountSelector, &KMyMoneySelector::itemSelected, q, &KGlobalLedgerView::slotMoveToAccount);
1466       }
1467     }
1468   }
1469 
automaticReconciliation(const MyMoneyAccount & account,const QList<QPair<MyMoneyTransaction,MyMoneySplit>> & transactions,const MyMoneyMoney & amount)1470   QList<QPair<MyMoneyTransaction, MyMoneySplit> > automaticReconciliation(const MyMoneyAccount &account,
1471       const QList<QPair<MyMoneyTransaction, MyMoneySplit> > &transactions,
1472       const MyMoneyMoney &amount)
1473   {
1474     Q_Q(KGlobalLedgerView);
1475     static const int NR_OF_STEPS_LIMIT = 60000;
1476     static const int PROGRESSBAR_STEPS = 1000;
1477     QList<QPair<MyMoneyTransaction, MyMoneySplit> > result = transactions;
1478 
1479     // optimize the most common case - all transactions should be cleared
1480     QListIterator<QPair<MyMoneyTransaction, MyMoneySplit> > itTransactionSplitResult(result);
1481     MyMoneyMoney transactionsBalance;
1482     while (itTransactionSplitResult.hasNext()) {
1483       const QPair<MyMoneyTransaction, MyMoneySplit> &transactionSplit = itTransactionSplitResult.next();
1484       transactionsBalance += transactionSplit.second.shares();
1485     }
1486     if (amount == transactionsBalance) {
1487       result = transactions;
1488       return result;
1489     }
1490 
1491     // only one transaction is uncleared
1492     itTransactionSplitResult.toFront();
1493     int index = 0;
1494     while (itTransactionSplitResult.hasNext()) {
1495       const QPair<MyMoneyTransaction, MyMoneySplit> &transactionSplit = itTransactionSplitResult.next();
1496       if (transactionsBalance - transactionSplit.second.shares() == amount) {
1497         result.removeAt(index);
1498         return result;
1499       }
1500       index++;
1501     }
1502 
1503     // more than one transaction is uncleared - apply the algorithm
1504     result.clear();
1505 
1506     const auto& security = MyMoneyFile::instance()->security(account.currencyId());
1507     double precision = 0.1 / account.fraction(security);
1508 
1509     QList<MyMoneyMoney> sumList;
1510     sumList << MyMoneyMoney();
1511 
1512     QMap<MyMoneyMoney, QList<QPair<QString, QString> > > sumToComponentsMap;
1513 
1514     struct restoreStatusMsgHelper {
1515       restoreStatusMsgHelper(KGlobalLedgerView* qq)
1516       : q(qq) {}
1517 
1518       ~restoreStatusMsgHelper()
1519       {
1520         q->slotStatusMsg(QString());
1521         q->slotStatusProgress(-1, -1);
1522       }
1523       KGlobalLedgerView* q;
1524     } restoreHelper(q);
1525 
1526     q->slotStatusMsg(i18n("Running automatic reconciliation"));
1527     q->slotStatusProgress(0, NR_OF_STEPS_LIMIT);
1528 
1529     // compute the possible matches
1530     QListIterator<QPair<MyMoneyTransaction, MyMoneySplit> > it_ts(transactions);
1531     while (it_ts.hasNext()) {
1532       const QPair<MyMoneyTransaction, MyMoneySplit> &transactionSplit = it_ts.next();
1533       QListIterator<MyMoneyMoney> itSum(sumList);
1534       QList<MyMoneyMoney> tempList;
1535       while (itSum.hasNext()) {
1536         const MyMoneyMoney &sum = itSum.next();
1537         QList<QPair<QString, QString> > splitIds;
1538         splitIds << qMakePair<QString, QString>(transactionSplit.first.id(), transactionSplit.second.id());
1539         if (sumToComponentsMap.contains(sum)) {
1540           if (sumToComponentsMap.value(sum).contains(qMakePair<QString, QString>(transactionSplit.first.id(), transactionSplit.second.id()))) {
1541             continue;
1542           }
1543           splitIds.append(sumToComponentsMap.value(sum));
1544         }
1545         tempList << transactionSplit.second.shares() + sum;
1546         sumToComponentsMap[transactionSplit.second.shares() + sum] = splitIds;
1547         int size = sumToComponentsMap.size();
1548         if (size % PROGRESSBAR_STEPS == 0) {
1549           q->slotStatusProgress(size, 0);
1550         }
1551         if (size > NR_OF_STEPS_LIMIT) {
1552           return result; // it's taking too much resources abort the algorithm
1553         }
1554       }
1555       QList<MyMoneyMoney> unionList;
1556       unionList.append(tempList);
1557       unionList.append(sumList);
1558       qSort(unionList);
1559       sumList.clear();
1560       MyMoneyMoney smallestSumFromUnion = unionList.first();
1561       sumList.append(smallestSumFromUnion);
1562       QListIterator<MyMoneyMoney> itUnion(unionList);
1563       while (itUnion.hasNext()) {
1564         MyMoneyMoney sumFromUnion = itUnion.next();
1565         if (smallestSumFromUnion < MyMoneyMoney(1 - precision / transactions.size())*sumFromUnion) {
1566           smallestSumFromUnion = sumFromUnion;
1567           sumList.append(sumFromUnion);
1568         }
1569       }
1570     }
1571 
1572     q->slotStatusProgress(NR_OF_STEPS_LIMIT / PROGRESSBAR_STEPS, 0);
1573     if (sumToComponentsMap.contains(amount)) {
1574       QListIterator<QPair<MyMoneyTransaction, MyMoneySplit> > itTransactionSplit(transactions);
1575       while (itTransactionSplit.hasNext()) {
1576         const QPair<MyMoneyTransaction, MyMoneySplit> &transactionSplit = itTransactionSplit.next();
1577         const QList<QPair<QString, QString> > &splitIds = sumToComponentsMap.value(amount);
1578         if (splitIds.contains(qMakePair<QString, QString>(transactionSplit.first.id(), transactionSplit.second.id()))) {
1579           result.append(transactionSplit);
1580         }
1581       }
1582     }
1583 
1584   #ifdef KMM_DEBUG
1585     qDebug("For the amount %s a number of %d possible sums where computed from the set of %d transactions: ",
1586            qPrintable(MyMoneyUtils::formatMoney(amount, security)), sumToComponentsMap.size(), transactions.size());
1587   #endif
1588 
1589     return result;
1590   }
1591 
1592   KGlobalLedgerView   *q_ptr;
1593   MousePressFilter    *m_mousePressFilter;
1594   KMyMoneyRegister::RegisterSearchLineWidget* m_registerSearchLine;
1595 //  QString              m_reconciliationAccount;
1596   QDate                m_reconciliationDate;
1597   MyMoneyMoney         m_endingBalance;
1598   int                  m_precision;
1599   bool                 m_recursion;
1600   bool                 m_showDetails;
1601   eWidgets::eRegister::Action m_action;
1602 
1603   // models
1604   AccountNamesFilterProxyModel *m_filterProxyModel;
1605 
1606   // widgets
1607   KMyMoneyAccountCombo* m_accountComboBox;
1608 
1609   MyMoneyMoney         m_totalBalance;
1610   bool                 m_balanceIsApproximated;
1611   // frames
1612   QFrame*                       m_toolbarFrame;
1613   QFrame*                       m_registerFrame;
1614   QFrame*                       m_buttonFrame;
1615   QFrame*                       m_formFrame;
1616   QFrame*                       m_summaryFrame;
1617 
1618   // widgets
1619   KMyMoneyRegister::Register*   m_register;
1620   KToolBar*                     m_buttonbar;
1621 
1622   /**
1623     * This member holds the currently selected account
1624     */
1625   MyMoneyAccount m_currentAccount;
1626   QString m_lastSelectedAccountID;
1627 
1628   MyMoneyAccount m_reconciliationAccount;
1629 
1630   /**
1631     * This member holds the transaction list
1632     */
1633   QList<QPair<MyMoneyTransaction, MyMoneySplit> >  m_transactionList;
1634 
1635   QLabel*                         m_leftSummaryLabel;
1636   QLabel*                         m_centerSummaryLabel;
1637   QLabel*                         m_rightSummaryLabel;
1638 
1639   KMyMoneyTransactionForm::TransactionForm* m_form;
1640 
1641   /**
1642     * This member holds the load state of page
1643     */
1644   bool                            m_needLoad;
1645 
1646   bool                            m_newAccountLoaded;
1647   bool                            m_inEditMode;
1648 
1649   QWidgetList                     m_tabOrderWidgets;
1650   QPoint                          m_tooltipPosn;
1651   KMyMoneyRegister::SelectedTransactions m_selectedTransactions;
1652   /**
1653     * This member keeps the date that was used as the last posting date.
1654     * It will be updated whenever the user modifies the post date
1655     * and is used to preset the posting date when new transactions are created.
1656     * This member is initialised to the current date when the program is started.
1657     */
1658   static QDate         m_lastPostDate;
1659   // pointer to the current transaction editor
1660   QPointer<TransactionEditor> m_transactionEditor;
1661 
1662   // id's that need to be remembered
1663   QString  m_accountGoto, m_payeeGoto;
1664   QString  m_lastPayeeEnteredId;
1665   QScopedPointer<KBalanceWarning> m_balanceWarning;
1666   KMyMoneyAccountSelector* m_moveToAccountSelector;
1667 
1668   // Reconciliation dialog
1669   KEndingBalanceDlg*    m_endingBalanceDlg;
1670   KFindTransactionDlg*  m_searchDlg;
1671 };
1672 
1673 #endif
1674