File indexing completed on 2024-05-12 16:43:46
0001 /* 0002 SPDX-FileCopyrightText: 2006 Thomas Baumgart <ipwizard@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #ifndef KGLOBALLEDGERVIEW_P_H 0008 #define KGLOBALLEDGERVIEW_P_H 0009 0010 #include "kgloballedgerview.h" 0011 0012 // ---------------------------------------------------------------------------- 0013 // QT Includes 0014 0015 #include <QFrame> 0016 #include <QHBoxLayout> 0017 #include <QList> 0018 #include <QLabel> 0019 #include <QEvent> 0020 #include <QVBoxLayout> 0021 #include <QHeaderView> 0022 #include <QToolTip> 0023 #include <QMenu> 0024 #include <QWidgetAction> 0025 0026 // ---------------------------------------------------------------------------- 0027 // KDE Includes 0028 0029 #include <KLocalizedString> 0030 #include <KMessageBox> 0031 #include <KToolBar> 0032 #include <KPassivePopup> 0033 0034 // ---------------------------------------------------------------------------- 0035 // Project Includes 0036 0037 #include "accountsmodel.h" 0038 #include "fancydategroupmarkers.h" 0039 #include "icons.h" 0040 #include "kbalancewarning.h" 0041 #include "kendingbalancedlg.h" 0042 #include "kfindtransactiondlg.h" 0043 #include "kmergetransactionsdlg.h" 0044 #include "kmymoneyaccountcombo.h" 0045 #include "kmymoneyaccountselector.h" 0046 #include "kmymoneysettings.h" 0047 #include "kmymoneyutils.h" 0048 #include "kmymoneyviewbase_p.h" 0049 #include "menuenums.h" 0050 #include "modelenums.h" 0051 #include "models.h" 0052 #include "mymoneyaccount.h" 0053 #include "mymoneyenums.h" 0054 #include "mymoneyexception.h" 0055 #include "mymoneyfile.h" 0056 #include "mymoneymoney.h" 0057 #include "mymoneypayee.h" 0058 #include "mymoneyprice.h" 0059 #include "mymoneyschedule.h" 0060 #include "mymoneysecurity.h" 0061 #include "mymoneysplit.h" 0062 #include "mymoneytracer.h" 0063 #include "mymoneytransaction.h" 0064 #include "mymoneytransactionfilter.h" 0065 #include "register.h" 0066 #include "registersearchline.h" 0067 #include "scheduledtransaction.h" 0068 #include "selectedtransactions.h" 0069 #include "tabbar.h" 0070 #include "transaction.h" 0071 #include "transactioneditor.h" 0072 #include "transactionform.h" 0073 #include "transactionmatcher.h" 0074 #include "widgetenums.h" 0075 0076 #include <config-kmymoney.h> 0077 #ifdef KMM_DEBUG 0078 #include "mymoneyutils.h" 0079 #endif 0080 0081 using namespace eMenu; 0082 using namespace eMyMoney; 0083 0084 /** 0085 * helper class implementing an event filter to detect mouse button press 0086 * events on widgets outside a given set of widgets. This is used internally 0087 * to detect when to leave the edit mode. 0088 */ 0089 class MousePressFilter : public QObject 0090 { 0091 Q_OBJECT 0092 public: 0093 explicit MousePressFilter(QWidget* parent = nullptr) : 0094 QObject(parent), 0095 m_lastMousePressEvent(0), 0096 m_filterActive(true) 0097 { 0098 } 0099 0100 /** 0101 * Add widget @p w to the list of possible parent objects. See eventFilter() how 0102 * they will be used. 0103 */ 0104 void addWidget(QWidget* w) 0105 { 0106 m_parents.append(w); 0107 } 0108 0109 public Q_SLOTS: 0110 /** 0111 * This slot allows to activate/deactivate the filter. By default the 0112 * filter is active. 0113 * 0114 * @param state Allows to activate (@a true) or deactivate (@a false) the filter 0115 */ 0116 void setFilterActive(bool state = true) 0117 { 0118 m_filterActive = state; 0119 } 0120 0121 /** 0122 * This slot allows to activate/deactivate the filter. By default the 0123 * filter is active. 0124 * 0125 * @param state Allows to deactivate (@a true) or activate (@a false) the filter 0126 */ 0127 void setFilterDeactive(bool state = false) { 0128 setFilterActive(!state); 0129 } 0130 0131 protected: 0132 /** 0133 * This method checks if the widget @p child is a child of 0134 * the widget @p parent and returns either @a true or @a false. 0135 * 0136 * @param child pointer to child widget 0137 * @param parent pointer to parent widget 0138 * @retval true @p child points to widget which has @p parent as parent or grand-parent 0139 * @retval false @p child points to a widget which is not related to @p parent 0140 */ 0141 bool isChildOf(QWidget* child, QWidget* parent) 0142 { 0143 // QDialogs cannot be detected directly, but it can be assumed, 0144 // that events on a widget that do not have a parent widget within 0145 // our application are dialogs. 0146 if (!child->parentWidget()) 0147 return true; 0148 0149 while (child) { 0150 // if we are a child of the given parent, we have a match 0151 if (child == parent) 0152 return true; 0153 // if we are at the application level, we don't have a match 0154 if (child->inherits("KMyMoneyApp")) 0155 return false; 0156 // If one of the ancestors is a KPassivePopup or a KDialog or a popup widget then 0157 // it's as if it is a child of our own because these widgets could 0158 // appear during transaction entry (message boxes, completer widgets) 0159 if (dynamic_cast<KPassivePopup*>(child) || 0160 ((child->windowFlags() & Qt::Popup) && /*child != kmymoney*/ 0161 !child->parentWidget())) // has no parent, then it must be top-level window 0162 return true; 0163 child = child->parentWidget(); 0164 } 0165 return false; 0166 } 0167 0168 /** 0169 * Reimplemented from base class. Sends out the mousePressedOnExternalWidget() signal 0170 * if object @p o points to an object which is not a child widget of any added previously 0171 * using the addWidget() method. The signal is sent out only once for each event @p e. 0172 * 0173 * @param o pointer to QObject 0174 * @param e pointer to QEvent 0175 * @return always returns @a false 0176 */ 0177 bool eventFilter(QObject* o, QEvent* e) final override 0178 { 0179 if (m_filterActive) { 0180 if (e->type() == QEvent::MouseButtonPress && !m_lastMousePressEvent) { 0181 QWidget* w = qobject_cast<QWidget*>(o); 0182 if (!w) { 0183 return QObject::eventFilter(o, e); 0184 } 0185 QList<QWidget*>::const_iterator it_w; 0186 for (it_w = m_parents.constBegin(); it_w != m_parents.constEnd(); ++it_w) { 0187 if (isChildOf(w, (*it_w))) { 0188 m_lastMousePressEvent = e; 0189 break; 0190 } 0191 } 0192 if (it_w == m_parents.constEnd()) { 0193 m_lastMousePressEvent = e; 0194 bool rc = false; 0195 emit mousePressedOnExternalWidget(rc); 0196 } 0197 } 0198 0199 if (e->type() != QEvent::MouseButtonPress) { 0200 m_lastMousePressEvent = 0; 0201 } 0202 } 0203 return false; 0204 } 0205 0206 Q_SIGNALS: 0207 void mousePressedOnExternalWidget(bool&); 0208 0209 private: 0210 QList<QWidget*> m_parents; 0211 QEvent* m_lastMousePressEvent; 0212 bool m_filterActive; 0213 }; 0214 0215 class KGlobalLedgerViewPrivate : public KMyMoneyViewBasePrivate 0216 { 0217 Q_DECLARE_PUBLIC(KGlobalLedgerView) 0218 0219 public: 0220 explicit KGlobalLedgerViewPrivate(KGlobalLedgerView *qq) : 0221 q_ptr(qq), 0222 m_mousePressFilter(0), 0223 m_registerSearchLine(0), 0224 m_precision(2), 0225 m_recursion(false), 0226 m_showDetails(false), 0227 m_action(eWidgets::eRegister::Action::None), 0228 m_filterProxyModel(0), 0229 m_accountComboBox(0), 0230 m_balanceIsApproximated(false), 0231 m_toolbarFrame(nullptr), 0232 m_registerFrame(nullptr), 0233 m_buttonFrame(nullptr), 0234 m_formFrame(nullptr), 0235 m_summaryFrame(nullptr), 0236 m_register(nullptr), 0237 m_buttonbar(nullptr), 0238 m_leftSummaryLabel(nullptr), 0239 m_centerSummaryLabel(nullptr), 0240 m_rightSummaryLabel(nullptr), 0241 m_form(nullptr), 0242 m_needLoad(true), 0243 m_newAccountLoaded(true), 0244 m_inEditMode(false), 0245 m_transactionEditor(nullptr), 0246 m_balanceWarning(nullptr), 0247 m_moveToAccountSelector(nullptr), 0248 m_endingBalanceDlg(nullptr), 0249 m_searchDlg(nullptr) 0250 { 0251 } 0252 0253 ~KGlobalLedgerViewPrivate() 0254 { 0255 delete m_moveToAccountSelector; 0256 delete m_endingBalanceDlg; 0257 delete m_searchDlg; 0258 } 0259 0260 void init() 0261 { 0262 Q_Q(KGlobalLedgerView); 0263 m_needLoad = false; 0264 auto vbox = new QVBoxLayout(q); 0265 q->setLayout(vbox); 0266 vbox->setSpacing(6); 0267 vbox->setMargin(0); 0268 0269 m_mousePressFilter = new MousePressFilter((QWidget*)q); 0270 m_action = eWidgets::eRegister::Action::None; 0271 0272 // the proxy filter model 0273 m_filterProxyModel = new AccountNamesFilterProxyModel(q); 0274 m_filterProxyModel->addAccountGroup(QVector<eMyMoney::Account::Type>{eMyMoney::Account::Type::Asset, 0275 eMyMoney::Account::Type::Liability, 0276 eMyMoney::Account::Type::Equity, 0277 eMyMoney::Account::Type::Income, 0278 eMyMoney::Account::Type::Expense}); 0279 auto const model = Models::instance()->accountsModel(); 0280 m_filterProxyModel->setSourceColumns(model->getColumns()); 0281 m_filterProxyModel->setSourceModel(model); 0282 m_filterProxyModel->sort((int)eAccountsModel::Column::Account); 0283 0284 // create the toolbar frame at the top of the view 0285 m_toolbarFrame = new QFrame(); 0286 QHBoxLayout* toolbarLayout = new QHBoxLayout(m_toolbarFrame); 0287 toolbarLayout->setContentsMargins(0, 0, 0, 0); 0288 toolbarLayout->setSpacing(6); 0289 0290 // the account selector widget 0291 m_accountComboBox = new KMyMoneyAccountCombo(); 0292 m_accountComboBox->setModel(m_filterProxyModel); 0293 toolbarLayout->addWidget(m_accountComboBox); 0294 0295 vbox->addWidget(m_toolbarFrame); 0296 toolbarLayout->setStretchFactor(m_accountComboBox, 60); 0297 // create the register frame 0298 m_registerFrame = new QFrame(); 0299 QVBoxLayout* registerFrameLayout = new QVBoxLayout(m_registerFrame); 0300 registerFrameLayout->setContentsMargins(0, 0, 0, 0); 0301 registerFrameLayout->setSpacing(0); 0302 vbox->addWidget(m_registerFrame); 0303 vbox->setStretchFactor(m_registerFrame, 2); 0304 m_register = new KMyMoneyRegister::Register(m_registerFrame); 0305 m_register->setUsedWithEditor(true); 0306 registerFrameLayout->addWidget(m_register); 0307 m_register->installEventFilter(q); 0308 q->connect(m_register, &KMyMoneyRegister::Register::openContextMenu, q, &KGlobalLedgerView::slotTransactionsContextMenuRequested); 0309 q->connect(m_register, &KMyMoneyRegister::Register::transactionsSelected, q, &KGlobalLedgerView::slotUpdateSummaryLine); 0310 q->connect(m_register->horizontalHeader(), &QWidget::customContextMenuRequested, q, &KGlobalLedgerView::slotSortOptions); 0311 q->connect(m_register, &KMyMoneyRegister::Register::reconcileStateColumnClicked, q, &KGlobalLedgerView::slotToggleTransactionMark); 0312 0313 // insert search line widget 0314 0315 m_registerSearchLine = new KMyMoneyRegister::RegisterSearchLineWidget(m_register, m_toolbarFrame); 0316 toolbarLayout->addWidget(m_registerSearchLine); 0317 toolbarLayout->setStretchFactor(m_registerSearchLine, 100); 0318 // create the summary frame 0319 m_summaryFrame = new QFrame(); 0320 QHBoxLayout* summaryFrameLayout = new QHBoxLayout(m_summaryFrame); 0321 summaryFrameLayout->setContentsMargins(0, 0, 0, 0); 0322 summaryFrameLayout->setSpacing(0); 0323 m_leftSummaryLabel = new QLabel(m_summaryFrame); 0324 m_centerSummaryLabel = new QLabel(m_summaryFrame); 0325 m_rightSummaryLabel = new QLabel(m_summaryFrame); 0326 summaryFrameLayout->addWidget(m_leftSummaryLabel); 0327 QSpacerItem* spacer = new QSpacerItem(20, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); 0328 summaryFrameLayout->addItem(spacer); 0329 summaryFrameLayout->addWidget(m_centerSummaryLabel); 0330 spacer = new QSpacerItem(20, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); 0331 summaryFrameLayout->addItem(spacer); 0332 summaryFrameLayout->addWidget(m_rightSummaryLabel); 0333 vbox->addWidget(m_summaryFrame); 0334 0335 // create the button frame 0336 m_buttonFrame = new QFrame(q); 0337 QVBoxLayout* buttonLayout = new QVBoxLayout(m_buttonFrame); 0338 buttonLayout->setContentsMargins(0, 0, 0, 0); 0339 buttonLayout->setSpacing(0); 0340 vbox->addWidget(m_buttonFrame); 0341 m_buttonbar = new KToolBar(m_buttonFrame, 0, true); 0342 m_buttonbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 0343 buttonLayout->addWidget(m_buttonbar); 0344 0345 m_buttonbar->addAction(pActions[eMenu::Action::NewTransaction]); 0346 m_buttonbar->addAction(pActions[eMenu::Action::DeleteTransaction]); 0347 m_buttonbar->addAction(pActions[eMenu::Action::EditTransaction]); 0348 m_buttonbar->addAction(pActions[eMenu::Action::EnterTransaction]); 0349 m_buttonbar->addAction(pActions[eMenu::Action::CancelTransaction]); 0350 m_buttonbar->addAction(pActions[eMenu::Action::AcceptTransaction]); 0351 m_buttonbar->addAction(pActions[eMenu::Action::MatchTransaction]); 0352 0353 // create the transaction form frame 0354 m_formFrame = new QFrame(q); 0355 QVBoxLayout* frameLayout = new QVBoxLayout(m_formFrame); 0356 frameLayout->setContentsMargins(5, 5, 5, 5); 0357 frameLayout->setSpacing(0); 0358 m_form = new KMyMoneyTransactionForm::TransactionForm(m_formFrame); 0359 frameLayout->addWidget(m_form->getTabBar(m_formFrame)); 0360 frameLayout->addWidget(m_form); 0361 m_formFrame->setFrameShape(QFrame::Panel); 0362 m_formFrame->setFrameShadow(QFrame::Raised); 0363 vbox->addWidget(m_formFrame); 0364 0365 q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KGlobalLedgerView::refresh); 0366 q->connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, q, &KGlobalLedgerView::slotUpdateMoveToAccountMenu); 0367 q->connect(m_register, static_cast<void (KMyMoneyRegister::Register::*)(KMyMoneyRegister::Transaction *)>(&KMyMoneyRegister::Register::focusChanged), m_form, &KMyMoneyTransactionForm::TransactionForm::slotSetTransaction); 0368 q->connect(m_register, static_cast<void (KMyMoneyRegister::Register::*)()>(&KMyMoneyRegister::Register::focusChanged), q, &KGlobalLedgerView::updateLedgerActionsInternal); 0369 // q->connect(m_accountComboBox, &KMyMoneyAccountCombo::accountSelected, q, &KGlobalLedgerView::slotAccountSelected); 0370 q->connect(m_accountComboBox, &KMyMoneyAccountCombo::accountSelected, q, static_cast<void (KGlobalLedgerView::*)(const QString&)>(&KGlobalLedgerView::slotSelectAccount)); 0371 q->connect(m_accountComboBox, &KMyMoneyAccountCombo::accountSelected, q, &KGlobalLedgerView::slotUpdateMoveToAccountMenu); 0372 q->connect(m_register, &KMyMoneyRegister::Register::transactionsSelected, q, &KGlobalLedgerView::slotTransactionsSelected); 0373 q->connect(m_register, &KMyMoneyRegister::Register::transactionsSelected, q, &KGlobalLedgerView::slotUpdateMoveToAccountMenu); 0374 q->connect(m_register, &KMyMoneyRegister::Register::editTransaction, q, &KGlobalLedgerView::slotEditTransaction); 0375 q->connect(m_register, &KMyMoneyRegister::Register::emptyItemSelected, q, &KGlobalLedgerView::slotNewTransaction); 0376 q->connect(m_register, &KMyMoneyRegister::Register::aboutToSelectItem, q, &KGlobalLedgerView::slotAboutToSelectItem); 0377 q->connect(m_mousePressFilter, &MousePressFilter::mousePressedOnExternalWidget, q, &KGlobalLedgerView::slotCancelOrEnterTransactions); 0378 0379 q->connect(m_form, &KMyMoneyTransactionForm::TransactionForm::newTransaction, q, static_cast<void (KGlobalLedgerView::*)(eWidgets::eRegister::Action)>(&KGlobalLedgerView::slotNewTransactionForm)); 0380 0381 // setup mouse press filter 0382 m_mousePressFilter->addWidget(m_formFrame); 0383 m_mousePressFilter->addWidget(m_buttonFrame); 0384 m_mousePressFilter->addWidget(m_summaryFrame); 0385 m_mousePressFilter->addWidget(m_registerFrame); 0386 0387 m_tooltipPosn = QPoint(); 0388 } 0389 0390 /** 0391 * This method reloads the account selection combo box of the 0392 * view with all asset and liability accounts from the engine. 0393 * If the account id of the current account held in @p m_accountId is 0394 * empty or if the referenced account does not exist in the engine, 0395 * the first account found in the list will be made the current account. 0396 */ 0397 void loadAccounts() 0398 { 0399 const auto file = MyMoneyFile::instance(); 0400 0401 // check if the current account still exists and make it the 0402 // current account 0403 if (!m_lastSelectedAccountID.isEmpty()) { 0404 try { 0405 m_currentAccount = file->account(m_lastSelectedAccountID); 0406 } catch (const MyMoneyException &) { 0407 m_lastSelectedAccountID.clear(); 0408 m_currentAccount = MyMoneyAccount(); 0409 m_accountComboBox->setSelected(QString()); 0410 } 0411 } 0412 0413 // TODO: check why the invalidate is needed here 0414 m_filterProxyModel->invalidate(); 0415 m_filterProxyModel->sort((int)eAccountsModel::Column::Account); 0416 m_filterProxyModel->setHideClosedAccounts(!KMyMoneySettings::showAllAccounts()); 0417 m_filterProxyModel->setHideEquityAccounts(!KMyMoneySettings::expertMode()); 0418 m_accountComboBox->expandAll(); 0419 0420 if (m_currentAccount.id().isEmpty()) { 0421 // find the first favorite account 0422 QModelIndexList list = m_filterProxyModel->match(m_filterProxyModel->index(0, 0), 0423 (int)eAccountsModel::Role::Favorite, 0424 QVariant(true), 0425 1, 0426 Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive)); 0427 if (list.count() > 0) { 0428 QVariant accountId = list.front().data((int)eAccountsModel::Role::ID); 0429 if (accountId.isValid()) { 0430 m_currentAccount = file->account(accountId.toString()); 0431 } 0432 } 0433 0434 if (m_currentAccount.id().isEmpty()) { 0435 // there are no favorite accounts find any account 0436 list = m_filterProxyModel->match(m_filterProxyModel->index(0, 0), 0437 Qt::DisplayRole, 0438 QVariant(QString("*")), 0439 -1, 0440 Qt::MatchFlags(Qt::MatchWildcard | Qt::MatchRecursive)); 0441 for (QModelIndexList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { 0442 if (!it->parent().isValid()) 0443 continue; // skip the top level accounts 0444 QVariant accountId = (*it).data((int)eAccountsModel::Role::ID); 0445 if (accountId.isValid()) { 0446 MyMoneyAccount a = file->account(accountId.toString()); 0447 if (!a.isInvest() && !a.isClosed()) { 0448 m_currentAccount = a; 0449 break; 0450 } 0451 } 0452 } 0453 } 0454 } 0455 0456 if (!m_currentAccount.id().isEmpty()) { 0457 m_accountComboBox->setSelected(m_currentAccount.id()); 0458 try { 0459 m_precision = MyMoneyMoney::denomToPrec(m_currentAccount.fraction()); 0460 } catch (const MyMoneyException &) { 0461 qDebug("Security %s for account %s not found", qPrintable(m_currentAccount.currencyId()), qPrintable(m_currentAccount.name())); 0462 m_precision = 2; 0463 } 0464 } 0465 } 0466 0467 /** 0468 * This method clears the register, form, transaction list. See @sa m_register, 0469 * @sa m_transactionList 0470 */ 0471 void clear() 0472 { 0473 // clear current register contents 0474 m_register->clear(); 0475 0476 // setup header font 0477 QFont font = KMyMoneySettings::listHeaderFontEx(); 0478 QFontMetrics fm(font); 0479 int height = fm.lineSpacing() + 6; 0480 m_register->horizontalHeader()->setMinimumHeight(height); 0481 m_register->horizontalHeader()->setMaximumHeight(height); 0482 m_register->horizontalHeader()->setFont(font); 0483 0484 // setup cell font 0485 font = KMyMoneySettings::listCellFontEx(); 0486 m_register->setFont(font); 0487 0488 // clear the form 0489 m_form->clear(); 0490 0491 // the selected transactions list 0492 m_transactionList.clear(); 0493 0494 // and the selected account in the combo box 0495 m_accountComboBox->setSelected(QString()); 0496 0497 // fraction defaults to two digits 0498 m_precision = 2; 0499 } 0500 0501 void loadView() 0502 { 0503 MYMONEYTRACER(tracer); 0504 Q_Q(KGlobalLedgerView); 0505 0506 // setup form visibility 0507 m_formFrame->setVisible(KMyMoneySettings::transactionForm()); 0508 0509 // no account selected 0510 // emit q->objectSelected(MyMoneyAccount()); 0511 // no transaction selected 0512 KMyMoneyRegister::SelectedTransactions list; 0513 emit q->selectByVariant(QVariantList {QVariant::fromValue(list)}, eView::Intent::SelectRegisterTransactions); 0514 0515 QMap<QString, bool> isSelected; 0516 QString focusItemId; 0517 QString backUpFocusItemId; // in case the focus item is removed 0518 QString anchorItemId; 0519 QString backUpAnchorItemId; // in case the anchor item is removed 0520 0521 if (!m_newAccountLoaded) { 0522 // remember the current selected transactions 0523 KMyMoneyRegister::RegisterItem* item = m_register->firstItem(); 0524 for (; item; item = item->nextItem()) { 0525 if (item->isSelected()) { 0526 isSelected[item->id()] = true; 0527 } 0528 } 0529 // remember the item that has the focus 0530 storeId(m_register->focusItem(), focusItemId, backUpFocusItemId); 0531 // and the one that has the selection anchor 0532 storeId(m_register->anchorItem(), anchorItemId, backUpAnchorItemId); 0533 } else { 0534 m_registerSearchLine->searchLine()->clear(); 0535 } 0536 0537 // clear the current contents ... 0538 clear(); 0539 0540 // ... load the combobox widget and select current account ... 0541 loadAccounts(); 0542 0543 // ... setup the register columns ... 0544 m_register->setupRegister(m_currentAccount); 0545 0546 // ... setup the form ... 0547 m_form->setupForm(m_currentAccount); 0548 0549 if (m_currentAccount.id().isEmpty()) { 0550 // if we don't have an account we bail out 0551 q->setEnabled(false); 0552 return; 0553 } 0554 q->setEnabled(true); 0555 0556 m_register->setUpdatesEnabled(false); 0557 0558 // ... and recreate it 0559 KMyMoneyRegister::RegisterItem* focusItem = 0; 0560 KMyMoneyRegister::RegisterItem* anchorItem = 0; 0561 QMap<QString, MyMoneyMoney> actBalance, clearedBalance, futureBalance; 0562 QMap<QString, MyMoneyMoney>::iterator it_b; 0563 try { 0564 // setup the filter to select the transactions we want to display 0565 // and update the sort order 0566 QString sortOrder; 0567 QString key; 0568 QDate reconciliationDate = m_reconciliationDate; 0569 0570 MyMoneyTransactionFilter filter(m_currentAccount.id()); 0571 // if it's an investment account, we also take care of 0572 // the sub-accounts (stock accounts) 0573 if (m_currentAccount.accountType() == eMyMoney::Account::Type::Investment) 0574 filter.addAccount(m_currentAccount.accountList()); 0575 0576 if (isReconciliationAccount()) { 0577 key = "kmm-sort-reconcile"; 0578 sortOrder = KMyMoneySettings::sortReconcileView(); 0579 filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled); 0580 filter.addState((int)eMyMoney::TransactionFilter::State::Cleared); 0581 } else { 0582 filter.setDateFilter(KMyMoneySettings::startDate().date(), QDate()); 0583 key = "kmm-sort-std"; 0584 sortOrder = KMyMoneySettings::sortNormalView(); 0585 if (KMyMoneySettings::hideReconciledTransactions() 0586 && !m_currentAccount.isIncomeExpense()) { 0587 filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled); 0588 filter.addState((int)eMyMoney::TransactionFilter::State::Cleared); 0589 } 0590 } 0591 filter.setReportAllSplits(true); 0592 0593 // check if we have an account override of the sort order 0594 if (!m_currentAccount.value(key).isEmpty()) 0595 sortOrder = m_currentAccount.value(key); 0596 0597 // setup sort order 0598 m_register->setSortOrder(sortOrder); 0599 0600 // retrieve the list from the engine 0601 MyMoneyFile::instance()->transactionList(m_transactionList, filter); 0602 0603 emit q->slotStatusProgress(0, m_transactionList.count()); 0604 0605 // create the elements for the register 0606 QList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it; 0607 QMap<QString, int>uniqueMap; 0608 int i = 0; 0609 for (it = m_transactionList.constBegin(); it != m_transactionList.constEnd(); ++it) { 0610 uniqueMap[(*it).first.id()]++; 0611 KMyMoneyRegister::Transaction* t = KMyMoneyRegister::Register::transactionFactory(m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]); 0612 actBalance[t->split().accountId()] = MyMoneyMoney(); 0613 emit q->slotStatusProgress(++i, 0); 0614 // if we're in reconciliation and the state is cleared, we 0615 // force the item to show in dimmed intensity to get a visual focus 0616 // on those items, that we need to work on 0617 if (isReconciliationAccount() && (*it).second.reconcileFlag() == eMyMoney::Split::State::Cleared) { 0618 t->setReducedIntensity(true); 0619 } 0620 } 0621 0622 // create dummy entries for the scheduled transactions if sorted by postdate 0623 int period = KMyMoneySettings::schedulePreview(); 0624 if (m_register->primarySortKey() == eWidgets::SortField::PostDate) { 0625 // show scheduled transactions which have a scheduled postdate 0626 // within the next 'period' days. In reconciliation mode, the 0627 // period starts on the statement date. 0628 QDate endDate = QDate::currentDate().addDays(period); 0629 if (isReconciliationAccount()) 0630 endDate = reconciliationDate.addDays(period); 0631 QList<MyMoneySchedule> scheduleList = MyMoneyFile::instance()->scheduleList(m_currentAccount.id()); 0632 while (!scheduleList.isEmpty()) { 0633 MyMoneySchedule& s = scheduleList.first(); 0634 for (;;) { 0635 if (s.isFinished() || s.adjustedNextDueDate() > endDate) { 0636 break; 0637 } 0638 0639 MyMoneyTransaction t(s.id(), KMyMoneyUtils::scheduledTransaction(s)); 0640 if (s.isOverdue() && !KMyMoneySettings::showPlannedScheduleDates()) { 0641 // if the transaction is scheduled and overdue, it can't 0642 // certainly be posted in the past. So we take today's date 0643 // as the alternative 0644 t.setPostDate(s.adjustedDate(QDate::currentDate(), s.weekendOption())); 0645 } else { 0646 t.setPostDate(s.adjustedNextDueDate()); 0647 } 0648 foreach (const auto split, t.splits()) { 0649 if (split.accountId() == m_currentAccount.id()) { 0650 new KMyMoneyRegister::StdTransactionScheduled(m_register, t, split, uniqueMap[t.id()]); 0651 } 0652 } 0653 // keep track of this payment locally (not in the engine) 0654 if (s.isOverdue() && !KMyMoneySettings::showPlannedScheduleDates()) { 0655 s.setLastPayment(QDate::currentDate()); 0656 } else { 0657 s.setLastPayment(s.nextDueDate()); 0658 } 0659 0660 // if this is a one time schedule, we can bail out here as we're done 0661 if (s.occurrence() == eMyMoney::Schedule::Occurrence::Once) 0662 break; 0663 0664 // for all others, we check if the next payment date is still 'in range' 0665 QDate nextDueDate = s.nextPayment(s.nextDueDate()); 0666 if (nextDueDate.isValid()) { 0667 s.setNextDueDate(nextDueDate); 0668 } else { 0669 break; 0670 } 0671 } 0672 scheduleList.pop_front(); 0673 } 0674 } 0675 0676 // add the group markers 0677 m_register->addGroupMarkers(); 0678 0679 // sort the transactions according to the sort setting 0680 m_register->sortItems(); 0681 0682 // remove trailing and adjacent markers 0683 m_register->removeUnwantedGroupMarkers(); 0684 0685 // add special markers for reconciliation now so that they do not get 0686 // removed by m_register->removeUnwantedGroupMarkers(). Needs resorting 0687 // of items but that's ok. 0688 0689 KMyMoneyRegister::StatementGroupMarker* statement = 0; 0690 KMyMoneyRegister::StatementGroupMarker* dStatement = 0; 0691 KMyMoneyRegister::StatementGroupMarker* pStatement = 0; 0692 0693 if (isReconciliationAccount()) { 0694 switch (m_register->primarySortKey()) { 0695 case eWidgets::SortField::PostDate: 0696 statement = new KMyMoneyRegister::StatementGroupMarker(m_register, eWidgets::eRegister::CashFlowDirection::Deposit, reconciliationDate, i18n("Statement Details")); 0697 m_register->sortItems(); 0698 break; 0699 case eWidgets::SortField::Type: 0700 dStatement = new KMyMoneyRegister::StatementGroupMarker(m_register, eWidgets::eRegister::CashFlowDirection::Deposit, reconciliationDate, i18n("Statement Deposit Details")); 0701 pStatement = new KMyMoneyRegister::StatementGroupMarker(m_register, eWidgets::eRegister::CashFlowDirection::Payment, reconciliationDate, i18n("Statement Payment Details")); 0702 m_register->sortItems(); 0703 break; 0704 default: 0705 break; 0706 } 0707 } 0708 0709 // we need at least the balance for the account we currently show 0710 actBalance[m_currentAccount.id()] = MyMoneyMoney(); 0711 0712 if (m_currentAccount.accountType() == eMyMoney::Account::Type::Investment) 0713 foreach (const auto accountID, m_currentAccount.accountList()) 0714 actBalance[accountID] = MyMoneyMoney(); 0715 0716 // determine balances (actual, cleared). We do this by getting the actual 0717 // balance of all entered transactions from the engine and walk the list 0718 // of transactions backward. Also re-select a transaction if it was 0719 // selected before and setup the focus item. 0720 0721 MyMoneyMoney factor(1, 1); 0722 if (m_currentAccount.accountGroup() == eMyMoney::Account::Type::Liability 0723 || m_currentAccount.accountGroup() == eMyMoney::Account::Type::Equity) 0724 factor = -factor; 0725 0726 QMap<QString, int> deposits; 0727 QMap<QString, int> payments; 0728 QMap<QString, MyMoneyMoney> depositAmount; 0729 QMap<QString, MyMoneyMoney> paymentAmount; 0730 for (it_b = actBalance.begin(); it_b != actBalance.end(); ++it_b) { 0731 MyMoneyMoney balance = MyMoneyFile::instance()->balance(it_b.key()); 0732 balance = balance * factor; 0733 clearedBalance[it_b.key()] = 0734 futureBalance[it_b.key()] = 0735 (*it_b) = balance; 0736 deposits[it_b.key()] = payments[it_b.key()] = 0; 0737 depositAmount[it_b.key()] = MyMoneyMoney(); 0738 paymentAmount[it_b.key()] = MyMoneyMoney(); 0739 } 0740 0741 tracer.printf("total balance of %s = %s", qPrintable(m_currentAccount.name()), qPrintable(actBalance[m_currentAccount.id()].formatMoney("", 2))); 0742 tracer.printf("future balance of %s = %s", qPrintable(m_currentAccount.name()), qPrintable(futureBalance[m_currentAccount.id()].formatMoney("", 2))); 0743 tracer.printf("cleared balance of %s = %s", qPrintable(m_currentAccount.name()), qPrintable(clearedBalance[m_currentAccount.id()].formatMoney("", 2))); 0744 0745 KMyMoneyRegister::RegisterItem* p = m_register->lastItem(); 0746 focusItem = 0; 0747 0748 // take care of possibly trailing scheduled transactions (bump up the future balance) 0749 while (p) { 0750 if (p->isSelectable()) { 0751 KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p); 0752 if (t && t->isScheduled()) { 0753 MyMoneyMoney balance = futureBalance[t->split().accountId()]; 0754 const MyMoneySplit& split = t->split(); 0755 // if this split is a stock split, we can't just add the amount of shares 0756 if (t->transaction().isStockSplit()) { 0757 balance = MyMoneyFile::instance()->stockSplit(split.accountId(), 0758 balance, 0759 split.shares(), 0760 eMyMoney::StockSplitDirection::StockSplitForward); 0761 } else { 0762 balance += split.shares() * factor; 0763 } 0764 futureBalance[split.accountId()] = balance; 0765 } else if (t && !focusItem) 0766 focusItem = p; 0767 } 0768 p = p->prevItem(); 0769 } 0770 0771 p = m_register->lastItem(); 0772 while (p) { 0773 KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p); 0774 if (t) { 0775 if (isSelected.contains(t->id())) 0776 t->setSelected(true); 0777 0778 matchItemById(&focusItem, t, focusItemId, backUpFocusItemId); 0779 matchItemById(&anchorItem, t, anchorItemId, backUpAnchorItemId); 0780 0781 const MyMoneySplit& split = t->split(); 0782 MyMoneyMoney balance = futureBalance[split.accountId()]; 0783 t->setBalance(balance); 0784 0785 // if this split is a stock split, we can't just add the amount of shares 0786 if (t->transaction().isStockSplit()) { 0787 balance = 0788 MyMoneyFile::instance()->stockSplit(split.accountId(), balance, split.shares(), eMyMoney::StockSplitDirection::StockSplitBackward); 0789 } else { 0790 balance -= split.shares() * factor; 0791 } 0792 0793 if (!t->isScheduled()) { 0794 if (isReconciliationAccount() && t->transaction().postDate() <= reconciliationDate && split.reconcileFlag() == eMyMoney::Split::State::Cleared) { 0795 if (split.shares().isNegative()) { 0796 payments[split.accountId()]++; 0797 paymentAmount[split.accountId()] += split.shares(); 0798 } else { 0799 deposits[split.accountId()]++; 0800 depositAmount[split.accountId()] += split.shares(); 0801 } 0802 } 0803 0804 if (t->transaction().postDate() > QDate::currentDate()) { 0805 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))); 0806 actBalance[split.accountId()] -= split.shares() * factor; 0807 } 0808 } 0809 futureBalance[split.accountId()] = balance; 0810 } 0811 p = p->prevItem(); 0812 } 0813 0814 clearedBalance[m_currentAccount.id()] = MyMoneyFile::instance()->clearedBalance(m_currentAccount.id(), reconciliationDate); 0815 0816 tracer.printf("total balance of %s = %s", qPrintable(m_currentAccount.name()), qPrintable(actBalance[m_currentAccount.id()].formatMoney("", 2))); 0817 tracer.printf("future balance of %s = %s", qPrintable(m_currentAccount.name()), qPrintable(futureBalance[m_currentAccount.id()].formatMoney("", 2))); 0818 tracer.printf("cleared balance of %s = %s", qPrintable(m_currentAccount.name()), qPrintable(clearedBalance[m_currentAccount.id()].formatMoney("", 2))); 0819 0820 // update statement information 0821 if (statement) { 0822 const QString aboutDeposits = i18np("%1 deposit (%2)", "%1 deposits (%2)", 0823 deposits[m_currentAccount.id()], depositAmount[m_currentAccount.id()].abs().formatMoney(m_currentAccount.fraction())); 0824 const QString aboutCharges = i18np("%1 charge (%2)", "%1 charges (%2)", 0825 deposits[m_currentAccount.id()], depositAmount[m_currentAccount.id()].abs().formatMoney(m_currentAccount.fraction())); 0826 const QString aboutPayments = i18np("%1 payment (%2)", "%1 payments (%2)", 0827 payments[m_currentAccount.id()], paymentAmount[m_currentAccount.id()].abs().formatMoney(m_currentAccount.fraction())); 0828 0829 statement->setText(i18nc("%1 is a string, e.g. 7 deposits; %2 is a string, e.g. 4 payments", "%1, %2", 0830 m_currentAccount.accountType() == eMyMoney::Account::Type::CreditCard ? aboutCharges : aboutDeposits, 0831 aboutPayments)); 0832 } 0833 if (pStatement) { 0834 pStatement->setText(i18np("%1 payment (%2)", "%1 payments (%2)", payments[m_currentAccount.id()] 0835 , paymentAmount[m_currentAccount.id()].abs().formatMoney(m_currentAccount.fraction()))); 0836 } 0837 if (dStatement) { 0838 dStatement->setText(i18np("%1 deposit (%2)", "%1 deposits (%2)", deposits[m_currentAccount.id()] 0839 , depositAmount[m_currentAccount.id()].abs().formatMoney(m_currentAccount.fraction()))); 0840 } 0841 0842 // add a last empty entry for new transactions 0843 // leave some information about the current account 0844 MyMoneySplit split; 0845 split.setReconcileFlag(eMyMoney::Split::State::NotReconciled); 0846 // make sure to use the value specified in the option during reconciliation 0847 if (isReconciliationAccount()) 0848 split.setReconcileFlag(static_cast<eMyMoney::Split::State>(KMyMoneySettings::defaultReconciliationState())); 0849 MyMoneyTransaction emptyTransaction; 0850 emptyTransaction.setCommodity(m_currentAccount.currencyId()); 0851 KMyMoneyRegister::Register::transactionFactory(m_register, emptyTransaction, split, 0); 0852 0853 m_register->updateRegister(true); 0854 0855 if (focusItem) { 0856 // in case we have some selected items we just set the focus item 0857 // in other cases, we make the focusitem also the selected item 0858 if (anchorItem && (anchorItem != focusItem)) { 0859 m_register->setFocusItem(focusItem); 0860 m_register->setAnchorItem(anchorItem); 0861 } else 0862 m_register->selectItem(focusItem, true); 0863 } else { 0864 // just use the empty line at the end if nothing else exists in the ledger 0865 p = m_register->lastItem(); 0866 m_register->setFocusItem(p); 0867 m_register->selectItem(p); 0868 focusItem = p; 0869 } 0870 0871 updateSummaryLine(actBalance, clearedBalance); 0872 emit q->slotStatusProgress(-1, -1); 0873 0874 } catch (const MyMoneyException &) { 0875 m_currentAccount = MyMoneyAccount(); 0876 clear(); 0877 } 0878 0879 m_showDetails = KMyMoneySettings::showRegisterDetailed(); 0880 0881 // and tell everyone what's selected 0882 emit q->selectByObject(m_currentAccount, eView::Intent::None); 0883 KMyMoneyRegister::SelectedTransactions actualSelection(m_register); 0884 emit q->selectByVariant(QVariantList {QVariant::fromValue(actualSelection)}, eView::Intent::SelectRegisterTransactions); 0885 } 0886 0887 void selectTransaction(const QString& id) 0888 { 0889 if (!id.isEmpty()) { 0890 KMyMoneyRegister::RegisterItem* p = m_register->lastItem(); 0891 while (p) { 0892 KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p); 0893 if (t) { 0894 if (t->transaction().id() == id) { 0895 m_register->selectItem(t); 0896 m_register->ensureItemVisible(t); 0897 break; 0898 } 0899 } 0900 p = p->prevItem(); 0901 } 0902 } 0903 } 0904 0905 /** 0906 * @brief selects transactions for processing with slots 0907 * @param list of transactions 0908 * @return false if only schedule is to be selected 0909 */ 0910 bool selectTransactions(const KMyMoneyRegister::SelectedTransactions list) 0911 { 0912 Q_Q(KGlobalLedgerView); 0913 // list can either contain a list of transactions or a single selected scheduled transaction 0914 // in the latter case, the transaction id is actually the one of the schedule. In order 0915 // to differentiate between the two, we just ask for the schedule. If we don't find one - because 0916 // we passed the id of a real transaction - then we know that fact. We use the schedule here, 0917 // because the list of schedules is kept in a cache by MyMoneyFile. This way, we save some trips 0918 // to the backend which we would have to do if we check for the transaction. 0919 m_selectedTransactions.clear(); 0920 auto sch = MyMoneySchedule(); 0921 auto ret = true; 0922 0923 m_accountGoto.clear(); 0924 m_payeeGoto.clear(); 0925 if (!list.isEmpty() && !list.first().isScheduled()) { 0926 m_selectedTransactions = list; 0927 if (list.count() == 1) { 0928 const MyMoneySplit& sp = m_selectedTransactions[0].split(); 0929 if (!sp.payeeId().isEmpty()) { 0930 try { 0931 auto payee = MyMoneyFile::instance()->payee(sp.payeeId()); 0932 if (!payee.name().isEmpty()) { 0933 m_payeeGoto = payee.id(); 0934 auto name = payee.name(); 0935 name.replace(QRegExp("&(?!&)"), "&&"); 0936 pActions[Action::GoToPayee]->setText(i18n("Go to '%1'", name)); 0937 } 0938 } catch (const MyMoneyException &) { 0939 } 0940 } 0941 try { 0942 const auto& t = m_selectedTransactions[0].transaction(); 0943 // search the first non-income/non-expense account and use it for the 'goto account' 0944 const auto& selectedTransactionSplit = m_selectedTransactions[0].split(); 0945 foreach (const auto split, t.splits()) { 0946 if (split.id() != selectedTransactionSplit.id()) { 0947 auto acc = MyMoneyFile::instance()->account(split.accountId()); 0948 0949 // set default icon 0950 pActions[Action::GoToAccount]->setIcon(Icons::get(Icons::Icon::Accounts)); 0951 0952 // for stock accounts we show the portfolio account 0953 if (acc.isInvest()) { 0954 acc = MyMoneyFile::instance()->account(acc.parentAccountId()); 0955 } else if (acc.isIncomeExpense()) { 0956 pActions[Action::GoToAccount]->setIcon(Icons::get(Icons::Icon::FinancialCategories)); 0957 } 0958 0959 m_accountGoto = acc.id(); 0960 auto name = acc.name(); 0961 name.replace(QRegExp("&(?!&)"), "&&"); 0962 pActions[Action::GoToAccount]->setText(i18n("Go to '%1'", name)); 0963 0964 break; 0965 } 0966 } 0967 } catch (const MyMoneyException &) { 0968 } 0969 } 0970 } else if (!list.isEmpty()) { 0971 sch = MyMoneyFile::instance()->schedule(list.first().scheduleId()); 0972 m_selectedTransactions.append(list.first()); 0973 ret = false; 0974 } 0975 0976 emit q->selectByObject(sch, eView::Intent::None); 0977 0978 // make sure, we show some neutral menu entry if we don't have an object 0979 if (m_payeeGoto.isEmpty()) 0980 pActions[Action::GoToPayee]->setText(i18n("Go to payee")); 0981 if (m_accountGoto.isEmpty()) 0982 pActions[Action::GoToAccount]->setText(i18n("Go to account")); 0983 return ret; 0984 } 0985 0986 /** 0987 * Returns @a true if setReconciliationAccount() has been called for 0988 * the current loaded account. 0989 * 0990 * @retval true current account is in reconciliation mode 0991 * @retval false current account is not in reconciliation mode 0992 */ 0993 bool isReconciliationAccount() const 0994 { 0995 return m_currentAccount.id() == m_reconciliationAccount.id(); 0996 } 0997 0998 /** 0999 * Updates the values on the summary line beneath the register with 1000 * the given values. The contents shown differs between reconciliation 1001 * mode and normal mode. 1002 * 1003 * @param actBalance map of account indexed values to be used as actual balance 1004 * @param clearedBalance map of account indexed values to be used as cleared balance 1005 */ 1006 void updateSummaryLine(const QMap<QString, MyMoneyMoney>& actBalance, const QMap<QString, MyMoneyMoney>& clearedBalance) 1007 { 1008 Q_Q(KGlobalLedgerView); 1009 const auto file = MyMoneyFile::instance(); 1010 m_leftSummaryLabel->show(); 1011 m_centerSummaryLabel->show(); 1012 m_rightSummaryLabel->show(); 1013 1014 if (isReconciliationAccount()) { 1015 if (m_currentAccount.accountType() != eMyMoney::Account::Type::Investment) { 1016 m_leftSummaryLabel->setText(i18n("Statement: %1", m_endingBalance.formatMoney("", m_precision))); 1017 m_centerSummaryLabel->setText(i18nc("Cleared balance", "Cleared: %1", clearedBalance[m_currentAccount.id()].formatMoney("", m_precision))); 1018 m_totalBalance = clearedBalance[m_currentAccount.id()] - m_endingBalance; 1019 } 1020 } else { 1021 // update summary line in normal mode 1022 QDate reconcileDate = m_currentAccount.lastReconciliationDate(); 1023 1024 if (reconcileDate.isValid()) { 1025 m_leftSummaryLabel->setText(i18n("Last reconciled: %1", QLocale().toString(reconcileDate, QLocale::ShortFormat))); 1026 } else { 1027 m_leftSummaryLabel->setText(i18n("Never reconciled")); 1028 } 1029 1030 QPalette palette = m_rightSummaryLabel->palette(); 1031 palette.setColor(m_rightSummaryLabel->foregroundRole(), m_leftSummaryLabel->palette().color(q->foregroundRole())); 1032 if (m_currentAccount.accountType() != eMyMoney::Account::Type::Investment) { 1033 m_centerSummaryLabel->setText(i18nc("Cleared balance", "Cleared: %1", clearedBalance[m_currentAccount.id()].formatMoney("", m_precision))); 1034 m_totalBalance = actBalance[m_currentAccount.id()]; 1035 } else { 1036 m_centerSummaryLabel->hide(); 1037 MyMoneyMoney balance; 1038 MyMoneySecurity base = file->baseCurrency(); 1039 QMap<QString, MyMoneyMoney>::const_iterator it_b; 1040 // reset the approximated flag 1041 m_balanceIsApproximated = false; 1042 for (it_b = actBalance.begin(); it_b != actBalance.end(); ++it_b) { 1043 MyMoneyAccount stock = file->account(it_b.key()); 1044 QString currencyId = stock.currencyId(); 1045 MyMoneySecurity sec = file->security(currencyId); 1046 MyMoneyMoney rate(1, 1); 1047 1048 if (stock.isInvest()) { 1049 currencyId = sec.tradingCurrency(); 1050 const MyMoneyPrice &priceInfo = file->price(sec.id(), currencyId); 1051 m_balanceIsApproximated |= !priceInfo.isValid(); 1052 rate = priceInfo.rate(sec.tradingCurrency()); 1053 } 1054 1055 if (currencyId != base.id()) { 1056 const MyMoneyPrice &priceInfo = file->price(sec.tradingCurrency(), base.id()); 1057 m_balanceIsApproximated |= !priceInfo.isValid(); 1058 rate = (rate * priceInfo.rate(base.id())).convertPrecision(sec.pricePrecision()); 1059 } 1060 balance += ((*it_b) * rate).convert(base.smallestAccountFraction()); 1061 } 1062 m_totalBalance = balance; 1063 } 1064 m_rightSummaryLabel->setPalette(palette); 1065 } 1066 // determine the number of selected transactions 1067 KMyMoneyRegister::SelectedTransactions selection; 1068 m_register->selectedTransactions(selection); 1069 q->slotUpdateSummaryLine(selection); 1070 } 1071 1072 /** 1073 * setup the default action according to the current account type 1074 */ 1075 void setupDefaultAction() 1076 { 1077 switch (m_currentAccount.accountType()) { 1078 case eMyMoney::Account::Type::Asset: 1079 case eMyMoney::Account::Type::AssetLoan: 1080 case eMyMoney::Account::Type::Savings: 1081 m_action = eWidgets::eRegister::Action::Deposit; 1082 break; 1083 default: 1084 m_action = eWidgets::eRegister::Action::Withdrawal; 1085 break; 1086 } 1087 } 1088 1089 // used to store the id of an item and the id of an immediate unselected sibling 1090 void storeId(KMyMoneyRegister::RegisterItem *item, QString &id, QString &backupId) { 1091 if (item) { 1092 // the id of the item 1093 id = item->id(); 1094 // the id of the item's previous/next unselected item 1095 for (KMyMoneyRegister::RegisterItem *it = item->prevItem(); it != 0 && backupId.isEmpty(); it = it->prevItem()) { 1096 if (!it->isSelected()) { 1097 backupId = it->id(); 1098 } 1099 } 1100 // if we didn't found previous unselected items search trough the next items 1101 for (KMyMoneyRegister::RegisterItem *it = item->nextItem(); it != 0 && backupId.isEmpty(); it = it->nextItem()) { 1102 if (!it->isSelected()) { 1103 backupId = it->id(); 1104 } 1105 } 1106 } 1107 } 1108 1109 // use to match an item by it's id or a backup id which has a lower precedence 1110 void matchItemById(KMyMoneyRegister::RegisterItem **item, KMyMoneyRegister::Transaction* t, QString &id, QString &backupId) { 1111 if (!backupId.isEmpty() && t->id() == backupId) 1112 *item = t; 1113 if (!id.isEmpty() && t->id() == id) { 1114 // we found the real thing there's no need for the backup anymore 1115 backupId.clear(); 1116 *item = t; 1117 } 1118 } 1119 1120 bool canProcessTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const 1121 { 1122 if (m_register->focusItem() == 0) 1123 return false; 1124 1125 if (!m_register->focusItem()->isSelected()) { 1126 tooltip = i18n("Cannot process transaction with focus if it is not selected."); 1127 showTooltip(tooltip); 1128 return false; 1129 } 1130 tooltip.clear(); 1131 return !list.isEmpty(); 1132 } 1133 1134 void showTooltip(const QString msg) const 1135 { 1136 QToolTip::showText(m_tooltipPosn, msg); 1137 } 1138 1139 bool createNewTransaction() 1140 { 1141 Q_Q(KGlobalLedgerView); 1142 auto rc = false; 1143 QString txt; 1144 if (q->canCreateTransactions(txt)) { 1145 rc = q->selectEmptyTransaction(); 1146 } 1147 return rc; 1148 } 1149 1150 TransactionEditor* startEdit(const KMyMoneyRegister::SelectedTransactions& list) 1151 { 1152 Q_Q(KGlobalLedgerView); 1153 TransactionEditor* editor = 0; 1154 QString txt; 1155 if (q->canEditTransactions(list, txt) || q->canCreateTransactions(txt)) { 1156 editor = q->startEdit(list); 1157 } 1158 return editor; 1159 } 1160 1161 void doDeleteTransactions() 1162 { 1163 Q_Q(KGlobalLedgerView); 1164 KMyMoneyRegister::SelectedTransactions list = m_selectedTransactions; 1165 KMyMoneyRegister::SelectedTransactions::iterator it_t; 1166 int cnt = list.count(); 1167 int i = 0; 1168 emit q->slotStatusProgress(0, cnt); 1169 MyMoneyFileTransaction ft; 1170 const auto file = MyMoneyFile::instance(); 1171 try { 1172 it_t = list.begin(); 1173 while (it_t != list.end()) { 1174 const auto accountId = (*it_t).split().accountId(); 1175 const auto deletedNum = (*it_t).split().number(); 1176 const auto transactionId = (*it_t).transaction().id(); 1177 // only remove those transactions that do not reference a closed account 1178 if (!file->referencesClosedAccount((*it_t).transaction())) { 1179 file->removeTransaction((*it_t).transaction()); 1180 // remove all those references in the list of selected transactions 1181 // that refer to the same transaction we just removed so that we 1182 // will not be caught by an exception later on (see bko #285310) 1183 while (it_t != list.end()) { 1184 if (transactionId == (*it_t).transaction().id()) { 1185 it_t = list.erase(it_t); 1186 i++; // bump count of deleted transactions 1187 } else { 1188 ++it_t; 1189 } 1190 } 1191 1192 } else { 1193 list.erase(it_t); 1194 } 1195 it_t = list.begin(); 1196 1197 // need to ensure "nextCheckNumber" is still correct 1198 auto acc = file->account(accountId); 1199 1200 // the "lastNumberUsed" might have been the txn number deleted 1201 // so adjust it 1202 if (deletedNum == acc.value("lastNumberUsed")) { 1203 // decrement deletedNum and set new "lastNumberUsed" 1204 QString num = KMyMoneyUtils::getAdjacentNumber(deletedNum, -1); 1205 acc.setValue("lastNumberUsed", num); 1206 file->modifyAccount(acc); 1207 } 1208 1209 emit q->slotStatusProgress(i++, 0); 1210 } 1211 ft.commit(); 1212 1213 } catch (const MyMoneyException &e) { 1214 KMessageBox::detailedSorry(q, i18n("Unable to delete transaction(s)"), e.what()); 1215 } 1216 emit q->slotStatusProgress(-1, -1); 1217 } 1218 1219 void deleteTransactionEditor() 1220 { 1221 // make sure, we don't use the transaction editor pointer 1222 // anymore from now on 1223 auto p = m_transactionEditor; 1224 m_transactionEditor = nullptr; 1225 delete p; 1226 } 1227 1228 void transactionUnmatch() 1229 { 1230 Q_Q(KGlobalLedgerView); 1231 KMyMoneyRegister::SelectedTransactions::const_iterator it; 1232 MyMoneyFileTransaction ft; 1233 try { 1234 for (it = m_selectedTransactions.constBegin(); it != m_selectedTransactions.constEnd(); ++it) { 1235 if ((*it).split().isMatched()) { 1236 TransactionMatcher matcher(m_currentAccount); 1237 matcher.unmatch((*it).transaction(), (*it).split()); 1238 } 1239 } 1240 ft.commit(); 1241 1242 } catch (const MyMoneyException &e) { 1243 KMessageBox::detailedSorry(q, i18n("Unable to unmatch the selected transactions"), e.what()); 1244 } 1245 } 1246 1247 void transactionMatch() 1248 { 1249 Q_Q(KGlobalLedgerView); 1250 if (m_selectedTransactions.count() != 2) 1251 return; 1252 1253 KMyMoneyRegister::SelectedTransaction toBeDeleted; 1254 KMyMoneyRegister::SelectedTransaction remaining; 1255 for (const auto& it : m_selectedTransactions) { 1256 if (it.transaction().isImported()) { 1257 if (toBeDeleted.transaction().id().isEmpty()) { 1258 toBeDeleted = it; 1259 } else { 1260 //This is a second imported transaction, we still want to merge 1261 remaining = it; 1262 } 1263 } else if (!it.split().isMatched()) { 1264 if (remaining.transaction().id().isEmpty()) { 1265 remaining = it; 1266 } else { 1267 toBeDeleted = it; 1268 } 1269 } 1270 } 1271 1272 // the user selected two transactions but they might be 1273 // selected in the wrong order. We check here, if the 1274 // other way around makes more sense and simply exchange 1275 // the two. See bko #435512 1276 if ((remaining.transaction().splitCount() == 1) && (toBeDeleted.transaction().splitCount() > 1)) { 1277 swap(remaining, toBeDeleted); 1278 } 1279 1280 bool doMatch = true; 1281 // in case the transaction we are about to remove contains 1282 // more than one split, we ask the user if this is what 1283 // she really wants. 1284 if (toBeDeleted.transaction().splitCount() > 1) { 1285 KMergeTransactionsDlg dlg(m_currentAccount); 1286 dlg.addTransaction(remaining.transaction()); 1287 dlg.addTransaction(toBeDeleted.transaction()); 1288 doMatch = (dlg.exec() == QDialog::Accepted); 1289 } 1290 if (doMatch) { 1291 MyMoneyFileTransaction ft; 1292 try { 1293 if (remaining.transaction().id().isEmpty()) 1294 throw MYMONEYEXCEPTION(QString::fromLatin1("No manually entered transaction selected for matching")); 1295 if (toBeDeleted.transaction().id().isEmpty()) 1296 throw MYMONEYEXCEPTION(QString::fromLatin1("No imported transaction selected for matching")); 1297 1298 TransactionMatcher matcher(m_currentAccount); 1299 matcher.match(remaining.transaction(), remaining.split(), toBeDeleted.transaction(), toBeDeleted.split(), true); 1300 ft.commit(); 1301 } catch (const MyMoneyException &e) { 1302 KMessageBox::detailedSorry(q, i18n("Unable to match the selected transactions"), e.what()); 1303 } 1304 } 1305 } 1306 1307 /** 1308 * Mark the selected transactions as provided by @a flag. If 1309 * flag is @a MyMoneySplit::Unknown, the future state depends 1310 * on the current stat of the split's flag according to the 1311 * following table: 1312 * 1313 * - NotReconciled --> Cleared 1314 * - Cleared --> Reconciled 1315 * - Reconciled --> NotReconciled 1316 */ 1317 void markTransaction(eMyMoney::Split::State flag) 1318 { 1319 Q_Q(KGlobalLedgerView); 1320 auto list = m_selectedTransactions; 1321 KMyMoneyRegister::SelectedTransactions::const_iterator it_t; 1322 auto cnt = list.count(); 1323 auto i = 0; 1324 emit q->slotStatusProgress(0, cnt); 1325 MyMoneyFileTransaction ft; 1326 try { 1327 for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) { 1328 // turn on signals before we modify the last entry in the list 1329 cnt--; 1330 MyMoneyFile::instance()->blockSignals(cnt != 0); 1331 1332 // get a fresh copy 1333 auto t = MyMoneyFile::instance()->transaction((*it_t).transaction().id()); 1334 auto sp = t.splitById((*it_t).split().id()); 1335 if (sp.reconcileFlag() != flag) { 1336 if (flag == eMyMoney::Split::State::Unknown) { 1337 if (m_reconciliationAccount.id().isEmpty()) { 1338 // in normal mode we cycle through all states 1339 switch (sp.reconcileFlag()) { 1340 case eMyMoney::Split::State::NotReconciled: 1341 sp.setReconcileFlag(eMyMoney::Split::State::Cleared); 1342 break; 1343 case eMyMoney::Split::State::Cleared: 1344 sp.setReconcileFlag(eMyMoney::Split::State::Reconciled); 1345 break; 1346 case eMyMoney::Split::State::Reconciled: 1347 sp.setReconcileFlag(eMyMoney::Split::State::NotReconciled); 1348 break; 1349 default: 1350 break; 1351 } 1352 } else { 1353 // in reconciliation mode we skip the reconciled state 1354 switch (sp.reconcileFlag()) { 1355 case eMyMoney::Split::State::NotReconciled: 1356 sp.setReconcileFlag(eMyMoney::Split::State::Cleared); 1357 break; 1358 case eMyMoney::Split::State::Cleared: 1359 sp.setReconcileFlag(eMyMoney::Split::State::NotReconciled); 1360 break; 1361 default: 1362 break; 1363 } 1364 } 1365 } else { 1366 sp.setReconcileFlag(flag); 1367 } 1368 1369 t.modifySplit(sp); 1370 MyMoneyFile::instance()->modifyTransaction(t); 1371 } 1372 emit q->slotStatusProgress(i++, 0); 1373 } 1374 emit q->slotStatusProgress(-1, -1); 1375 ft.commit(); 1376 } catch (const MyMoneyException &e) { 1377 KMessageBox::detailedSorry(q, i18n("Unable to modify transaction"), e.what()); 1378 } 1379 } 1380 1381 // move a stock transaction from one investment account to another 1382 void moveInvestmentTransaction(const QString& /*fromId*/, 1383 const QString& toId, 1384 const MyMoneyTransaction& tx) 1385 { 1386 MyMoneyAccount toInvAcc = MyMoneyFile::instance()->account(toId); 1387 MyMoneyTransaction t(tx); 1388 // first determine which stock we are dealing with. 1389 // fortunately, investment transactions have only one stock involved 1390 QString stockAccountId; 1391 QString stockSecurityId; 1392 MyMoneySplit s; 1393 foreach (const auto split, t.splits()) { 1394 stockAccountId = split.accountId(); 1395 stockSecurityId = 1396 MyMoneyFile::instance()->account(stockAccountId).currencyId(); 1397 if (!MyMoneyFile::instance()->security(stockSecurityId).isCurrency()) { 1398 s = split; 1399 break; 1400 } 1401 } 1402 // Now check the target investment account to see if it 1403 // contains a stock with this id 1404 QString newStockAccountId; 1405 foreach (const auto sAccount, toInvAcc.accountList()) { 1406 if (MyMoneyFile::instance()->account(sAccount).currencyId() == 1407 stockSecurityId) { 1408 newStockAccountId = sAccount; 1409 break; 1410 } 1411 } 1412 // if it doesn't exist, we need to add it as a copy of the old one 1413 // no 'copyAccount()' function?? 1414 if (newStockAccountId.isEmpty()) { 1415 MyMoneyAccount stockAccount = 1416 MyMoneyFile::instance()->account(stockAccountId); 1417 MyMoneyAccount newStock; 1418 newStock.setName(stockAccount.name()); 1419 newStock.setNumber(stockAccount.number()); 1420 newStock.setDescription(stockAccount.description()); 1421 newStock.setInstitutionId(stockAccount.institutionId()); 1422 newStock.setOpeningDate(stockAccount.openingDate()); 1423 newStock.setAccountType(stockAccount.accountType()); 1424 newStock.setCurrencyId(stockAccount.currencyId()); 1425 newStock.setClosed(stockAccount.isClosed()); 1426 MyMoneyFile::instance()->addAccount(newStock, toInvAcc); 1427 newStockAccountId = newStock.id(); 1428 } 1429 // now update the split and the transaction 1430 s.setAccountId(newStockAccountId); 1431 t.modifySplit(s); 1432 MyMoneyFile::instance()->modifyTransaction(t); 1433 } 1434 1435 void createTransactionMoveMenu() 1436 { 1437 Q_Q(KGlobalLedgerView); 1438 if (!m_moveToAccountSelector) { 1439 auto menu = pMenus[eMenu::Menu::MoveTransaction]; 1440 if (menu ) { 1441 auto accountSelectorAction = new QWidgetAction(menu); 1442 m_moveToAccountSelector = new KMyMoneyAccountSelector(menu, 0, false); 1443 m_moveToAccountSelector->setObjectName("transaction_move_menu_selector"); 1444 accountSelectorAction->setDefaultWidget(m_moveToAccountSelector); 1445 menu->addAction(accountSelectorAction); 1446 q->connect(m_moveToAccountSelector, &QObject::destroyed, q, &KGlobalLedgerView::slotObjectDestroyed); 1447 q->connect(m_moveToAccountSelector, &KMyMoneySelector::itemSelected, q, &KGlobalLedgerView::slotMoveToAccount); 1448 } 1449 } 1450 } 1451 1452 QList<QPair<MyMoneyTransaction, MyMoneySplit> > automaticReconciliation(const MyMoneyAccount &account, 1453 const QList<QPair<MyMoneyTransaction, MyMoneySplit> > &transactions, 1454 const MyMoneyMoney &amount) 1455 { 1456 Q_Q(KGlobalLedgerView); 1457 static const int NR_OF_STEPS_LIMIT = 60000; 1458 static const int PROGRESSBAR_STEPS = 1000; 1459 QList<QPair<MyMoneyTransaction, MyMoneySplit> > result = transactions; 1460 1461 // optimize the most common case - all transactions should be cleared 1462 QListIterator<QPair<MyMoneyTransaction, MyMoneySplit> > itTransactionSplitResult(result); 1463 MyMoneyMoney transactionsBalance; 1464 while (itTransactionSplitResult.hasNext()) { 1465 const QPair<MyMoneyTransaction, MyMoneySplit> &transactionSplit = itTransactionSplitResult.next(); 1466 transactionsBalance += transactionSplit.second.shares(); 1467 } 1468 if (amount == transactionsBalance) { 1469 result = transactions; 1470 return result; 1471 } 1472 1473 // only one transaction is uncleared 1474 itTransactionSplitResult.toFront(); 1475 int index = 0; 1476 while (itTransactionSplitResult.hasNext()) { 1477 const QPair<MyMoneyTransaction, MyMoneySplit> &transactionSplit = itTransactionSplitResult.next(); 1478 if (transactionsBalance - transactionSplit.second.shares() == amount) { 1479 result.removeAt(index); 1480 return result; 1481 } 1482 index++; 1483 } 1484 1485 // more than one transaction is uncleared - apply the algorithm 1486 result.clear(); 1487 1488 const auto& security = MyMoneyFile::instance()->security(account.currencyId()); 1489 double precision = 0.1 / account.fraction(security); 1490 1491 QList<MyMoneyMoney> sumList; 1492 sumList << MyMoneyMoney(); 1493 1494 QMap<MyMoneyMoney, QList<QPair<QString, QString> > > sumToComponentsMap; 1495 1496 struct restoreStatusMsgHelper { 1497 restoreStatusMsgHelper(KGlobalLedgerView* qq) 1498 : q(qq) {} 1499 1500 ~restoreStatusMsgHelper() 1501 { 1502 q->slotStatusMsg(QString()); 1503 q->slotStatusProgress(-1, -1); 1504 } 1505 KGlobalLedgerView* q; 1506 } restoreHelper(q); 1507 1508 q->slotStatusMsg(i18n("Running automatic reconciliation")); 1509 q->slotStatusProgress(0, NR_OF_STEPS_LIMIT); 1510 1511 // compute the possible matches 1512 QListIterator<QPair<MyMoneyTransaction, MyMoneySplit> > it_ts(transactions); 1513 while (it_ts.hasNext()) { 1514 const QPair<MyMoneyTransaction, MyMoneySplit> &transactionSplit = it_ts.next(); 1515 QListIterator<MyMoneyMoney> itSum(sumList); 1516 QList<MyMoneyMoney> tempList; 1517 while (itSum.hasNext()) { 1518 const MyMoneyMoney &sum = itSum.next(); 1519 QList<QPair<QString, QString> > splitIds; 1520 splitIds << qMakePair<QString, QString>(transactionSplit.first.id(), transactionSplit.second.id()); 1521 if (sumToComponentsMap.contains(sum)) { 1522 if (sumToComponentsMap.value(sum).contains(qMakePair<QString, QString>(transactionSplit.first.id(), transactionSplit.second.id()))) { 1523 continue; 1524 } 1525 splitIds.append(sumToComponentsMap.value(sum)); 1526 } 1527 tempList << transactionSplit.second.shares() + sum; 1528 sumToComponentsMap[transactionSplit.second.shares() + sum] = splitIds; 1529 int size = sumToComponentsMap.size(); 1530 if (size % PROGRESSBAR_STEPS == 0) { 1531 q->slotStatusProgress(size, 0); 1532 } 1533 if (size > NR_OF_STEPS_LIMIT) { 1534 return result; // it's taking too much resources abort the algorithm 1535 } 1536 } 1537 QList<MyMoneyMoney> unionList; 1538 unionList.append(tempList); 1539 unionList.append(sumList); 1540 qSort(unionList); 1541 sumList.clear(); 1542 MyMoneyMoney smallestSumFromUnion = unionList.first(); 1543 sumList.append(smallestSumFromUnion); 1544 QListIterator<MyMoneyMoney> itUnion(unionList); 1545 while (itUnion.hasNext()) { 1546 MyMoneyMoney sumFromUnion = itUnion.next(); 1547 if (smallestSumFromUnion < MyMoneyMoney(1 - precision / transactions.size())*sumFromUnion) { 1548 smallestSumFromUnion = sumFromUnion; 1549 sumList.append(sumFromUnion); 1550 } 1551 } 1552 } 1553 1554 q->slotStatusProgress(NR_OF_STEPS_LIMIT / PROGRESSBAR_STEPS, 0); 1555 if (sumToComponentsMap.contains(amount)) { 1556 QListIterator<QPair<MyMoneyTransaction, MyMoneySplit> > itTransactionSplit(transactions); 1557 while (itTransactionSplit.hasNext()) { 1558 const QPair<MyMoneyTransaction, MyMoneySplit> &transactionSplit = itTransactionSplit.next(); 1559 const QList<QPair<QString, QString> > &splitIds = sumToComponentsMap.value(amount); 1560 if (splitIds.contains(qMakePair<QString, QString>(transactionSplit.first.id(), transactionSplit.second.id()))) { 1561 result.append(transactionSplit); 1562 } 1563 } 1564 } 1565 1566 #ifdef KMM_DEBUG 1567 qDebug("For the amount %s a number of %d possible sums where computed from the set of %d transactions: ", 1568 qPrintable(MyMoneyUtils::formatMoney(amount, security)), sumToComponentsMap.size(), transactions.size()); 1569 #endif 1570 1571 return result; 1572 } 1573 1574 KGlobalLedgerView *q_ptr; 1575 MousePressFilter *m_mousePressFilter; 1576 KMyMoneyRegister::RegisterSearchLineWidget* m_registerSearchLine; 1577 // QString m_reconciliationAccount; 1578 QDate m_reconciliationDate; 1579 MyMoneyMoney m_endingBalance; 1580 int m_precision; 1581 bool m_recursion; 1582 bool m_showDetails; 1583 eWidgets::eRegister::Action m_action; 1584 1585 // models 1586 AccountNamesFilterProxyModel *m_filterProxyModel; 1587 1588 // widgets 1589 KMyMoneyAccountCombo* m_accountComboBox; 1590 1591 MyMoneyMoney m_totalBalance; 1592 bool m_balanceIsApproximated; 1593 // frames 1594 QFrame* m_toolbarFrame; 1595 QFrame* m_registerFrame; 1596 QFrame* m_buttonFrame; 1597 QFrame* m_formFrame; 1598 QFrame* m_summaryFrame; 1599 1600 // widgets 1601 KMyMoneyRegister::Register* m_register; 1602 KToolBar* m_buttonbar; 1603 1604 /** 1605 * This member holds the currently selected account 1606 */ 1607 MyMoneyAccount m_currentAccount; 1608 QString m_lastSelectedAccountID; 1609 1610 MyMoneyAccount m_reconciliationAccount; 1611 1612 /** 1613 * This member holds the transaction list 1614 */ 1615 QList<QPair<MyMoneyTransaction, MyMoneySplit> > m_transactionList; 1616 1617 QLabel* m_leftSummaryLabel; 1618 QLabel* m_centerSummaryLabel; 1619 QLabel* m_rightSummaryLabel; 1620 1621 KMyMoneyTransactionForm::TransactionForm* m_form; 1622 1623 /** 1624 * This member holds the load state of page 1625 */ 1626 bool m_needLoad; 1627 1628 bool m_newAccountLoaded; 1629 bool m_inEditMode; 1630 1631 QWidgetList m_tabOrderWidgets; 1632 QPoint m_tooltipPosn; 1633 KMyMoneyRegister::SelectedTransactions m_selectedTransactions; 1634 /** 1635 * This member keeps the date that was used as the last posting date. 1636 * It will be updated whenever the user modifies the post date 1637 * and is used to preset the posting date when new transactions are created. 1638 * This member is initialised to the current date when the program is started. 1639 */ 1640 static QDate m_lastPostDate; 1641 // pointer to the current transaction editor 1642 QPointer<TransactionEditor> m_transactionEditor; 1643 1644 // id's that need to be remembered 1645 QString m_accountGoto, m_payeeGoto; 1646 QString m_lastPayeeEnteredId; 1647 QScopedPointer<KBalanceWarning> m_balanceWarning; 1648 KMyMoneyAccountSelector* m_moveToAccountSelector; 1649 1650 // Reconciliation dialog 1651 KEndingBalanceDlg* m_endingBalanceDlg; 1652 KFindTransactionDlg* m_searchDlg; 1653 }; 1654 1655 #endif