1 /***************************************************************************
2 kgloballedgerview.cpp - 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 #include "kgloballedgerview_p.h"
20
21 #include <typeinfo>
22
23 // ----------------------------------------------------------------------------
24 // QT Includes
25
26 #include <QFrame>
27 #include <QList>
28 #include <QLabel>
29 #include <QEvent>
30 #include <QApplication>
31 #include <QTimer>
32 #include <QMenu>
33 #include <QClipboard>
34
35 // ----------------------------------------------------------------------------
36 // KDE Includes
37
38 #include <KLocalizedString>
39 #include <KMessageBox>
40 #include <KToolBar>
41 #include <KActionCollection>
42 #include <KXmlGuiWindow>
43
44 // ----------------------------------------------------------------------------
45 // Project Includes
46
47 #include "mymoneyaccount.h"
48 #include "mymoneyfile.h"
49 #include "kmymoneyaccountcombo.h"
50 #include "kmymoneypayeecombo.h"
51 #include "keditscheduledlg.h"
52 #include "kendingbalancedlg.h"
53 #include "register.h"
54 #include "transactioneditor.h"
55 #include "selectedtransactions.h"
56 #include "kmymoneysettings.h"
57 #include "registersearchline.h"
58 #include "kfindtransactiondlg.h"
59 #include "accountsmodel.h"
60 #include "models.h"
61 #include "mymoneyschedule.h"
62 #include "mymoneysecurity.h"
63 #include "mymoneytransaction.h"
64 #include "mymoneytransactionfilter.h"
65 #include "mymoneysplit.h"
66 #include "transaction.h"
67 #include "transactionform.h"
68 #include "widgetenums.h"
69 #include "mymoneyenums.h"
70 #include "menuenums.h"
71
72 using namespace eMenu;
73
74 QDate KGlobalLedgerViewPrivate::m_lastPostDate;
75
KGlobalLedgerView(QWidget * parent)76 KGlobalLedgerView::KGlobalLedgerView(QWidget *parent) :
77 KMyMoneyViewBase(*new KGlobalLedgerViewPrivate(this), parent)
78 {
79 const QHash<Action, std::function<void()>> actionConnections {
80 {Action::NewTransaction, [this](){ KGlobalLedgerView::slotNewTransaction(); }},
81 {Action::EditTransaction, [this](){ KGlobalLedgerView::slotEditTransaction(); }},
82 {Action::DeleteTransaction, [this](){ KGlobalLedgerView::slotDeleteTransaction(); }},
83 {Action::DuplicateTransaction, [this](){ KGlobalLedgerView::slotDuplicateTransaction(); }},
84 {Action::AddReversingTransaction, [this](){ KGlobalLedgerView::slotDuplicateTransaction(true); }},
85 {Action::EnterTransaction, [this](){ KGlobalLedgerView::slotEnterTransaction(); }},
86 {Action::AcceptTransaction, [this](){ KGlobalLedgerView::slotAcceptTransaction(); }},
87 {Action::CancelTransaction, [this](){ KGlobalLedgerView::slotCancelTransaction(); }},
88 {Action::EditSplits, [this](){ KGlobalLedgerView::slotEditSplits(); }},
89 {Action::CopySplits, [this](){ KGlobalLedgerView::slotCopySplits(); }},
90 {Action::GoToPayee, [this](){ KGlobalLedgerView::slotGoToPayee(); }},
91 {Action::GoToAccount, [this](){ KGlobalLedgerView::slotGoToAccount(); }},
92 {Action::MatchTransaction, [this](){ KGlobalLedgerView::slotMatchTransactions(); }},
93 {Action::CombineTransactions, [this](){ KGlobalLedgerView::slotCombineTransactions(); }},
94 {Action::ToggleReconciliationFlag, [this](){ KGlobalLedgerView::slotToggleReconciliationFlag(); }},
95 {Action::MarkCleared, [this](){ KGlobalLedgerView::slotMarkCleared(); }},
96 {Action::MarkReconciled, [this](){ KGlobalLedgerView::slotMarkReconciled(); }},
97 {Action::MarkNotReconciled, [this](){ KGlobalLedgerView::slotMarkNotReconciled(); }},
98 {Action::SelectAllTransactions, [this](){ KGlobalLedgerView::slotSelectAllTransactions(); }},
99 {Action::NewScheduledTransaction, [this](){ KGlobalLedgerView::slotCreateScheduledTransaction(); }},
100 {Action::AssignTransactionsNumber, [this](){ KGlobalLedgerView::slotAssignNumber(); }},
101 {Action::StartReconciliation, [this](){ KGlobalLedgerView::slotStartReconciliation(); }},
102 {Action::FinishReconciliation, [this](){ KGlobalLedgerView::slotFinishReconciliation(); }},
103 {Action::PostponeReconciliation, [this](){ KGlobalLedgerView::slotPostponeReconciliation(); }},
104 {Action::OpenAccount, [this](){ KGlobalLedgerView::slotOpenAccount(); }},
105 {Action::EditFindTransaction, [this](){ KGlobalLedgerView::slotFindTransaction(); }},
106 };
107
108 for (auto a = actionConnections.cbegin(); a != actionConnections.cend(); ++a)
109 connect(pActions[a.key()], &QAction::triggered, this, a.value());
110
111 KXmlGuiWindow* mw = KMyMoneyUtils::mainWindow();
112 KStandardAction::copy(this, &KGlobalLedgerView::slotCopyTransactionToClipboard, mw->actionCollection());
113
114 Q_D(KGlobalLedgerView);
115 d->m_balanceWarning.reset(new KBalanceWarning(this));
116 }
117
~KGlobalLedgerView()118 KGlobalLedgerView::~KGlobalLedgerView()
119 {
120 }
121
executeCustomAction(eView::Action action)122 void KGlobalLedgerView::executeCustomAction(eView::Action action)
123 {
124 Q_D(KGlobalLedgerView);
125 switch(action) {
126 case eView::Action::Refresh:
127 refresh();
128 break;
129
130 case eView::Action::SetDefaultFocus:
131 // delay the setFocus call until the event loop is running
132 QMetaObject::invokeMethod(d->m_registerSearchLine->searchLine(), "setFocus", Qt::QueuedConnection);
133 break;
134
135 case eView::Action::DisableViewDepenedendActions:
136 pActions[Action::SelectAllTransactions]->setEnabled(false);
137 break;
138
139 case eView::Action::InitializeAfterFileOpen:
140 d->m_lastSelectedAccountID.clear();
141 d->m_currentAccount = MyMoneyAccount();
142 if (d->m_accountComboBox) {
143 d->m_accountComboBox->setSelected(QString());
144 }
145 break;
146
147 case eView::Action::CleanupBeforeFileClose:
148 if (d->m_inEditMode) {
149 d->deleteTransactionEditor();
150 }
151 break;
152
153 default:
154 break;
155 }
156 }
157
refresh()158 void KGlobalLedgerView::refresh()
159 {
160 Q_D(KGlobalLedgerView);
161 if (isVisible()) {
162 if (!d->m_inEditMode) {
163 setUpdatesEnabled(false);
164 d->loadView();
165 setUpdatesEnabled(true);
166 d->m_needsRefresh = false;
167 // force a new account if the current one is empty
168 d->m_newAccountLoaded = d->m_currentAccount.id().isEmpty();
169 }
170 } else {
171 d->m_needsRefresh = true;
172 }
173 }
174
showEvent(QShowEvent * event)175 void KGlobalLedgerView::showEvent(QShowEvent* event)
176 {
177 if (MyMoneyFile::instance()->storageAttached()) {
178 Q_D(KGlobalLedgerView);
179 if (d->m_needLoad)
180 d->init();
181
182 emit customActionRequested(View::Ledgers, eView::Action::AboutToShow);
183
184 if (d->m_needsRefresh) {
185 if (!d->m_inEditMode) {
186 setUpdatesEnabled(false);
187 d->loadView();
188 setUpdatesEnabled(true);
189 d->m_needsRefresh = false;
190 d->m_newAccountLoaded = false;
191 }
192
193 } else {
194 if (!d->m_lastSelectedAccountID.isEmpty()) {
195 try {
196 const auto acc = MyMoneyFile::instance()->account(d->m_lastSelectedAccountID);
197 slotSelectAccount(acc.id());
198 } catch (const MyMoneyException &) {
199 d->m_lastSelectedAccountID.clear(); // account is invalid
200 }
201 } else {
202 slotSelectAccount(d->m_accountComboBox->getSelected());
203 }
204
205 KMyMoneyRegister::SelectedTransactions list(d->m_register);
206 updateLedgerActions(list);
207 emit selectByVariant(QVariantList {QVariant::fromValue(list)}, eView::Intent::SelectRegisterTransactions);
208 }
209 }
210
211 pActions[Action::SelectAllTransactions]->setEnabled(true);
212 // don't forget base class implementation
213 QWidget::showEvent(event);
214 }
215
updateActions(const MyMoneyObject & obj)216 void KGlobalLedgerView::updateActions(const MyMoneyObject& obj)
217 {
218 Q_D(KGlobalLedgerView);
219 // if (typeid(obj) != typeid(MyMoneyAccount) &&
220 // (obj.id().isEmpty() && d->m_currentAccount.id().isEmpty())) // do not disable actions that were already disabled))
221 // return;
222
223 const auto& acc = static_cast<const MyMoneyAccount&>(obj);
224
225 const QVector<Action> actionsToBeDisabled {
226 Action::StartReconciliation,
227 Action::FinishReconciliation,
228 Action::PostponeReconciliation,
229 Action::OpenAccount,
230 Action::NewTransaction
231 };
232
233 for (const auto& a : actionsToBeDisabled)
234 pActions[a]->setEnabled(false);
235
236 auto b = acc.isClosed() ? false : true;
237 pMenus[Menu::MoveTransaction]->setEnabled(b);
238
239 QString tooltip;
240 pActions[Action::NewTransaction]->setEnabled(canCreateTransactions(tooltip) || !isVisible());
241 pActions[Action::NewTransaction]->setToolTip(tooltip);
242
243 const auto file = MyMoneyFile::instance();
244 if (!acc.id().isEmpty() && !file->isStandardAccount(acc.id())) {
245 switch (acc.accountGroup()) {
246 case eMyMoney::Account::Type::Asset:
247 case eMyMoney::Account::Type::Liability:
248 case eMyMoney::Account::Type::Equity:
249 pActions[Action::OpenAccount]->setEnabled(true);
250 if (acc.accountGroup() != eMyMoney::Account::Type::Equity) {
251 if (d->m_reconciliationAccount.id().isEmpty()) {
252 pActions[Action::StartReconciliation]->setEnabled(true);
253 pActions[Action::StartReconciliation]->setToolTip(i18n("Reconcile"));
254 } else {
255 auto tip = i18n("Reconcile - disabled because you are currently reconciling <b>%1</b>", d->m_reconciliationAccount.name());
256 pActions[Action::StartReconciliation]->setToolTip(tip);
257 if (!d->m_transactionEditor) {
258 pActions[Action::FinishReconciliation]->setEnabled(acc.id() == d->m_reconciliationAccount.id());
259 pActions[Action::PostponeReconciliation]->setEnabled(acc.id() == d->m_reconciliationAccount.id());
260 }
261 }
262 }
263 break;
264 case eMyMoney::Account::Type::Income :
265 case eMyMoney::Account::Type::Expense :
266 pActions[Action::OpenAccount]->setEnabled(true);
267 break;
268 default:
269 break;
270 }
271 }
272
273 d->m_currentAccount = acc;
274 // slotSelectAccount(acc);
275 }
276
updateLedgerActions(const KMyMoneyRegister::SelectedTransactions & list)277 void KGlobalLedgerView::updateLedgerActions(const KMyMoneyRegister::SelectedTransactions& list)
278 {
279 Q_D(KGlobalLedgerView);
280
281 d->selectTransactions(list);
282 updateLedgerActionsInternal();
283 }
284
updateLedgerActionsInternal()285 void KGlobalLedgerView::updateLedgerActionsInternal()
286 {
287 Q_D(KGlobalLedgerView);
288 const QVector<Action> actionsToBeDisabled {
289 Action::EditTransaction, Action::EditSplits, Action::EnterTransaction,
290 Action::CancelTransaction, Action::DeleteTransaction, Action::MatchTransaction,
291 Action::AcceptTransaction, Action::DuplicateTransaction, Action::AddReversingTransaction, Action::ToggleReconciliationFlag, Action::MarkCleared,
292 Action::GoToAccount, Action::GoToPayee, Action::AssignTransactionsNumber, Action::NewScheduledTransaction,
293 Action::CombineTransactions, Action::CopySplits,
294 };
295
296 for (const auto& a : actionsToBeDisabled)
297 pActions[a]->setEnabled(false);
298
299 const auto file = MyMoneyFile::instance();
300
301 pActions[Action::MatchTransaction]->setText(i18nc("Button text for match transaction", "Match"));
302 // pActions[Action::TransactionNew]->setToolTip(i18n("Create a new transaction"));
303
304 pMenus[Menu::MoveTransaction]->setEnabled(false);
305 pMenus[Menu::MarkTransaction]->setEnabled(false);
306 pMenus[Menu::MarkTransactionContext]->setEnabled(false);
307
308 if (!d->m_selectedTransactions.isEmpty() && !d->m_selectedTransactions.first().isScheduled()) {
309 // enable 'delete transaction' only if at least one of the
310 // selected transactions does not reference a closed account
311 bool enable = false;
312 KMyMoneyRegister::SelectedTransactions::const_iterator it_t;
313 for (it_t = d->m_selectedTransactions.constBegin(); (enable == false) && (it_t != d->m_selectedTransactions.constEnd()); ++it_t) {
314 enable = !(*it_t).transaction().id().isEmpty() && !file->referencesClosedAccount((*it_t).transaction());
315 }
316 pActions[Action::DeleteTransaction]->setEnabled(enable);
317
318 if (!d->m_transactionEditor) {
319 QString tooltip = i18n("Duplicate the current selected transactions");
320 pActions[Action::DuplicateTransaction]->setEnabled(canDuplicateTransactions(d->m_selectedTransactions, tooltip) && !d->m_selectedTransactions[0].transaction().id().isEmpty());
321 pActions[Action::DuplicateTransaction]->setToolTip(tooltip);
322
323 tooltip = i18n("Add reversing transactions to the currently selected");
324 pActions[Action::AddReversingTransaction]->setEnabled(canDuplicateTransactions(d->m_selectedTransactions, tooltip) && !d->m_selectedTransactions[0].transaction().id().isEmpty());
325 pActions[Action::AddReversingTransaction]->setToolTip(tooltip);
326
327 if (canEditTransactions(d->m_selectedTransactions, tooltip)) {
328 pActions[Action::EditTransaction]->setEnabled(true);
329 // editing splits is allowed only if we have one transaction selected
330 if (d->m_selectedTransactions.count() == 1) {
331 pActions[Action::EditSplits]->setEnabled(true);
332 }
333 if (d->m_currentAccount.isAssetLiability() && d->m_currentAccount.accountType() != eMyMoney::Account::Type::Investment) {
334 pActions[Action::NewScheduledTransaction]->setEnabled(d->m_selectedTransactions.count() == 1);
335 }
336 }
337 pActions[Action::EditTransaction]->setToolTip(tooltip);
338
339 if (!d->m_currentAccount.isClosed())
340 pMenus[Menu::MoveTransaction]->setEnabled(true);
341
342 pMenus[Menu::MarkTransaction]->setEnabled(true);
343 pMenus[Menu::MarkTransactionContext]->setEnabled(true);
344
345 // Allow marking the transaction if at least one is selected
346 pActions[Action::MarkCleared]->setEnabled(true);
347 pActions[Action::MarkReconciled]->setEnabled(true);
348 pActions[Action::MarkNotReconciled]->setEnabled(true);
349 pActions[Action::ToggleReconciliationFlag]->setEnabled(true);
350
351 if (!d->m_accountGoto.isEmpty())
352 pActions[Action::GoToAccount]->setEnabled(true);
353 if (!d->m_payeeGoto.isEmpty())
354 pActions[Action::GoToPayee]->setEnabled(true);
355
356 // Matching is enabled as soon as one regular and one imported transaction is selected
357 int matchedCount = 0;
358 int importedCount = 0;
359 KMyMoneyRegister::SelectedTransactions::const_iterator it;
360 for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) {
361 if ((*it).transaction().isImported())
362 ++importedCount;
363 if ((*it).split().isMatched())
364 ++matchedCount;
365 }
366
367 if (d->m_selectedTransactions.count() == 2 /* && pActions[Action::TransactionEdit]->isEnabled() */) {
368 pActions[Action::MatchTransaction]->setEnabled(true);
369 }
370 if (importedCount != 0 || matchedCount != 0)
371 pActions[Action::AcceptTransaction]->setEnabled(true);
372 if (matchedCount != 0) {
373 pActions[Action::MatchTransaction]->setEnabled(true);
374 pActions[Action::MatchTransaction]->setText(i18nc("Button text for unmatch transaction", "Unmatch"));
375 pActions[Action::MatchTransaction]->setIcon(QIcon("process-stop"));
376 }
377
378 if (d->m_selectedTransactions.count() > 1) {
379 pActions[Action::CombineTransactions]->setEnabled(true);
380 }
381 if (d->m_selectedTransactions.count() >= 2) {
382 int singleSplitTransactions = 0;
383 int multipleSplitTransactions = 0;
384 foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) {
385 switch (st.transaction().splitCount()) {
386 case 0:
387 break;
388 case 1:
389 singleSplitTransactions++;
390 break;
391 default:
392 multipleSplitTransactions++;
393 break;
394 }
395 }
396 if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) {
397 pActions[Action::CopySplits]->setEnabled(true);
398 }
399 }
400 if (d->m_selectedTransactions.count() >= 2) {
401 int singleSplitTransactions = 0;
402 int multipleSplitTransactions = 0;
403 foreach(const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) {
404 switch(st.transaction().splitCount()) {
405 case 0:
406 break;
407 case 1:
408 singleSplitTransactions++;
409 break;
410 default:
411 multipleSplitTransactions++;
412 break;
413 }
414 }
415 if(singleSplitTransactions > 0 && multipleSplitTransactions == 1) {
416 pActions[Action::CopySplits]->setEnabled(true);
417 }
418 }
419 } else {
420 pActions[Action::AssignTransactionsNumber]->setEnabled(d->m_transactionEditor->canAssignNumber());
421 pActions[Action::NewTransaction]->setEnabled(false);
422 pActions[Action::DeleteTransaction]->setEnabled(false);
423 QString reason;
424 pActions[Action::EnterTransaction]->setEnabled(d->m_transactionEditor->isComplete(reason));
425 //FIXME: Port to KDE4
426 // the next line somehow worked in KDE3 but does not have
427 // any influence under KDE4
428 /// Works for me when 'reason' is set. Allan
429 pActions[Action::EnterTransaction]->setToolTip(reason);
430 pActions[Action::CancelTransaction]->setEnabled(true);
431 }
432 }
433 }
434
slotAboutToSelectItem(KMyMoneyRegister::RegisterItem * item,bool & okToSelect)435 void KGlobalLedgerView::slotAboutToSelectItem(KMyMoneyRegister::RegisterItem* item, bool& okToSelect)
436 {
437 Q_UNUSED(item);
438 slotCancelOrEnterTransactions(okToSelect);
439 }
440
slotUpdateSummaryLine(const KMyMoneyRegister::SelectedTransactions & selection)441 void KGlobalLedgerView::slotUpdateSummaryLine(const KMyMoneyRegister::SelectedTransactions& selection)
442 {
443 Q_D(KGlobalLedgerView);
444 if (selection.count() > 1) {
445 MyMoneyMoney balance;
446 foreach (const KMyMoneyRegister::SelectedTransaction& t, selection) {
447 if (!t.isScheduled()) {
448 balance += t.split().shares();
449 }
450 }
451 d->m_rightSummaryLabel->setText(QString("%1: %2").arg(QChar(0x2211), balance.formatMoney("", -1)));
452
453 } else {
454 if (d->isReconciliationAccount()) {
455 d->m_rightSummaryLabel->setText(i18n("Difference: %1", d->m_totalBalance.formatMoney("", d->m_precision)));
456
457 } else {
458 if (d->m_currentAccount.accountType() != eMyMoney::Account::Type::Investment) {
459 d->m_rightSummaryLabel->setText(i18n("Balance: %1", d->m_totalBalance.formatMoney("", d->m_precision)));
460 bool showNegative = d->m_totalBalance.isNegative();
461 if (d->m_currentAccount.accountGroup() == eMyMoney::Account::Type::Liability && !d->m_totalBalance.isZero())
462 showNegative = !showNegative;
463 if (showNegative) {
464 QPalette palette = d->m_rightSummaryLabel->palette();
465 palette.setColor(d->m_rightSummaryLabel->foregroundRole(), KMyMoneySettings::schemeColor(SchemeColor::Negative));
466 d->m_rightSummaryLabel->setPalette(palette);
467 }
468 } else {
469 d->m_rightSummaryLabel->setText(i18n("Investment value: %1%2",
470 d->m_balanceIsApproximated ? "~" : "",
471 d->m_totalBalance.formatMoney(MyMoneyFile::instance()->baseCurrency().tradingSymbol(), d->m_precision)));
472 }
473 }
474 }
475 }
476
resizeEvent(QResizeEvent * ev)477 void KGlobalLedgerView::resizeEvent(QResizeEvent* ev)
478 {
479 if (MyMoneyFile::instance()->storageAttached()) {
480 Q_D(KGlobalLedgerView);
481 if (d->m_needLoad)
482 d->init();
483
484 d->m_register->resize((int)eWidgets::eTransaction::Column::Detail);
485 d->m_form->resize((int)eWidgets::eTransactionForm::Column::Value1);
486 }
487 KMyMoneyViewBase::resizeEvent(ev);
488 }
489
slotSetReconcileAccount(const MyMoneyAccount & acc,const QDate & reconciliationDate,const MyMoneyMoney & endingBalance)490 void KGlobalLedgerView::slotSetReconcileAccount(const MyMoneyAccount& acc, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance)
491 {
492 Q_D(KGlobalLedgerView);
493 if(d->m_needLoad)
494 d->init();
495
496 if (d->m_reconciliationAccount.id() != acc.id()) {
497 // make sure the account is selected
498 if (!acc.id().isEmpty())
499 slotSelectAccount(acc.id());
500
501 d->m_reconciliationAccount = acc;
502 d->m_reconciliationDate = reconciliationDate;
503 d->m_endingBalance = endingBalance;
504 if (acc.accountGroup() == eMyMoney::Account::Type::Liability)
505 d->m_endingBalance = -endingBalance;
506
507 d->m_newAccountLoaded = true;
508
509 if (acc.id().isEmpty()) {
510 d->m_buttonbar->removeAction(pActions[Action::PostponeReconciliation]);
511 d->m_buttonbar->removeAction(pActions[Action::FinishReconciliation]);
512 } else {
513 d->m_buttonbar->addAction(pActions[Action::PostponeReconciliation]);
514 d->m_buttonbar->addAction(pActions[Action::FinishReconciliation]);
515 // when we start reconciliation, we need to reload the view
516 // because no data has been changed. When postponing or finishing
517 // reconciliation, the data change in the engine takes care of updating
518 // the view.
519 refresh();
520 }
521 }
522 }
523
slotSetReconcileAccount(const MyMoneyAccount & acc,const QDate & reconciliationDate)524 void KGlobalLedgerView::slotSetReconcileAccount(const MyMoneyAccount& acc, const QDate& reconciliationDate)
525 {
526 slotSetReconcileAccount(acc, reconciliationDate, MyMoneyMoney());
527 }
528
slotSetReconcileAccount(const MyMoneyAccount & acc)529 void KGlobalLedgerView::slotSetReconcileAccount(const MyMoneyAccount& acc)
530 {
531 slotSetReconcileAccount(acc, QDate(), MyMoneyMoney());
532 }
533
slotSetReconcileAccount()534 void KGlobalLedgerView::slotSetReconcileAccount()
535 {
536 slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney());
537 }
538
slotShowTransactionMenu(const MyMoneySplit & sp)539 void KGlobalLedgerView::slotShowTransactionMenu(const MyMoneySplit& sp)
540 {
541 Q_UNUSED(sp)
542 pMenus[Menu::Transaction]->exec(QCursor::pos());
543 }
544
slotContinueReconciliation()545 void KGlobalLedgerView::slotContinueReconciliation()
546 {
547 Q_D(KGlobalLedgerView);
548 const auto file = MyMoneyFile::instance();
549 MyMoneyAccount account;
550
551 try {
552 account = file->account(d->m_currentAccount.id());
553 // get rid of previous run.
554 delete d->m_endingBalanceDlg;
555 d->m_endingBalanceDlg = new KEndingBalanceDlg(account, this);
556 if (account.isAssetLiability()) {
557
558 if (d->m_endingBalanceDlg->exec() == QDialog::Accepted) {
559 if (KMyMoneySettings::autoReconciliation()) {
560 MyMoneyMoney startBalance = d->m_endingBalanceDlg->previousBalance();
561 MyMoneyMoney endBalance = d->m_endingBalanceDlg->endingBalance();
562 QDate endDate = d->m_endingBalanceDlg->statementDate();
563
564 QList<QPair<MyMoneyTransaction, MyMoneySplit> > transactionList;
565 MyMoneyTransactionFilter filter(account.id());
566 filter.addState((int)eMyMoney::TransactionFilter::State::Cleared);
567 filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled);
568 filter.setDateFilter(QDate(), endDate);
569 filter.setConsiderCategory(false);
570 filter.setReportAllSplits(true);
571 file->transactionList(transactionList, filter);
572 QList<QPair<MyMoneyTransaction, MyMoneySplit> > result = d->automaticReconciliation(account, transactionList, endBalance - startBalance);
573
574 if (!result.empty()) {
575 QString message = i18n("KMyMoney has detected transactions matching your reconciliation data.\nWould you like KMyMoney to clear these transactions for you?");
576 if (KMessageBox::questionYesNo(this,
577 message,
578 i18n("Automatic reconciliation"),
579 KStandardGuiItem::yes(),
580 KStandardGuiItem::no(),
581 "AcceptAutomaticReconciliation") == KMessageBox::Yes) {
582 // mark the transactions cleared
583 KMyMoneyRegister::SelectedTransactions oldSelection = d->m_selectedTransactions;
584 d->m_selectedTransactions.clear();
585 QListIterator<QPair<MyMoneyTransaction, MyMoneySplit> > itTransactionSplitResult(result);
586 while (itTransactionSplitResult.hasNext()) {
587 const QPair<MyMoneyTransaction, MyMoneySplit> &transactionSplit = itTransactionSplitResult.next();
588 d->m_selectedTransactions.append(KMyMoneyRegister::SelectedTransaction(transactionSplit.first, transactionSplit.second, QString()));
589 }
590 // mark all transactions in d->m_selectedTransactions as 'Cleared'
591 d->markTransaction(eMyMoney::Split::State::Cleared);
592 d->m_selectedTransactions = oldSelection;
593 }
594 }
595 }
596
597 if (!file->isStandardAccount(account.id()) &&
598 account.isAssetLiability()) {
599 if (!isVisible())
600 emit customActionRequested(View::Ledgers, eView::Action::SwitchView);
601 Models::instance()->accountsModel()->slotReconcileAccount(account, d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->endingBalance());
602 slotSetReconcileAccount(account, d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->endingBalance());
603
604 // check if the user requests us to create interest
605 // or charge transactions.
606 auto ti = d->m_endingBalanceDlg->interestTransaction();
607 auto tc = d->m_endingBalanceDlg->chargeTransaction();
608 MyMoneyFileTransaction ft;
609 try {
610 if (ti != MyMoneyTransaction()) {
611 MyMoneyFile::instance()->addTransaction(ti);
612 }
613 if (tc != MyMoneyTransaction()) {
614 MyMoneyFile::instance()->addTransaction(tc);
615 }
616 ft.commit();
617
618 } catch (const MyMoneyException &e) {
619 qWarning("interest transaction not stored: '%s'", e.what());
620 }
621
622 // reload the account object as it might have changed in the meantime
623 d->m_reconciliationAccount = file->account(account.id());
624 updateActions(d->m_currentAccount);
625 updateLedgerActionsInternal();
626 // slotUpdateActions();
627 }
628 }
629 }
630 } catch (const MyMoneyException &) {
631 }
632 }
633
slotLedgerSelected(const QString & _accId,const QString & transaction)634 void KGlobalLedgerView::slotLedgerSelected(const QString& _accId, const QString& transaction)
635 {
636 auto acc = MyMoneyFile::instance()->account(_accId);
637 QString accId(_accId);
638
639 switch (acc.accountType()) {
640 case Account::Type::Stock:
641 // if a stock account is selected, we show the
642 // the corresponding parent (investment) account
643 acc = MyMoneyFile::instance()->account(acc.parentAccountId());
644 accId = acc.id();
645 // intentional fall through
646
647 case Account::Type::Checkings:
648 case Account::Type::Savings:
649 case Account::Type::Cash:
650 case Account::Type::CreditCard:
651 case Account::Type::Loan:
652 case Account::Type::Asset:
653 case Account::Type::Liability:
654 case Account::Type::AssetLoan:
655 case Account::Type::Income:
656 case Account::Type::Expense:
657 case Account::Type::Investment:
658 case Account::Type::Equity:
659 if (!isVisible())
660 emit customActionRequested(View::Ledgers, eView::Action::SwitchView);
661 slotSelectAccount(accId, transaction);
662 break;
663
664 case Account::Type::CertificateDep:
665 case Account::Type::MoneyMarket:
666 case Account::Type::Currency:
667 qDebug("No ledger view available for account type %d", (int)acc.accountType());
668 break;
669
670 default:
671 qDebug("Unknown account type %d in KMyMoneyView::slotLedgerSelected", (int)acc.accountType());
672 break;
673 }
674 }
675
slotSelectByObject(const MyMoneyObject & obj,eView::Intent intent)676 void KGlobalLedgerView::slotSelectByObject(const MyMoneyObject& obj, eView::Intent intent)
677 {
678 switch(intent) {
679 case eView::Intent::UpdateActions:
680 updateActions(obj);
681 break;
682
683 case eView::Intent::FinishEnteringOverdueScheduledTransactions:
684 slotContinueReconciliation();
685 break;
686
687 case eView::Intent::SynchronizeAccountInLedgersView:
688 slotSelectAccount(obj);
689 break;
690
691 default:
692 break;
693 }
694 }
695
slotSelectByVariant(const QVariantList & variant,eView::Intent intent)696 void KGlobalLedgerView::slotSelectByVariant(const QVariantList& variant, eView::Intent intent)
697 {
698 switch(intent) {
699 case eView::Intent::ShowTransaction:
700 if (variant.count() == 2)
701 slotLedgerSelected(variant.at(0).toString(), variant.at(1).toString());
702 break;
703 case eView::Intent::SelectRegisterTransactions:
704 if (variant.count() == 1)
705 updateLedgerActions(variant.at(0).value<KMyMoneyRegister::SelectedTransactions>());
706 break;
707 default:
708 break;
709 }
710 }
711
slotSelectAccount(const MyMoneyObject & obj)712 void KGlobalLedgerView::slotSelectAccount(const MyMoneyObject& obj)
713 {
714 Q_D(KGlobalLedgerView);
715 if (typeid(obj) != typeid(MyMoneyAccount))
716 return/* false */;
717
718 d->m_lastSelectedAccountID = obj.id();
719 }
720
slotSelectAccount(const QString & id)721 void KGlobalLedgerView::slotSelectAccount(const QString& id)
722 {
723 slotSelectAccount(id, QString());
724 }
725
slotSelectAccount(const QString & id,const QString & transactionId)726 bool KGlobalLedgerView::slotSelectAccount(const QString& id, const QString& transactionId)
727 {
728 Q_D(KGlobalLedgerView);
729 auto rc = true;
730
731 if (!id.isEmpty()) {
732 if (d->m_currentAccount.id() != id) {
733 try {
734 d->m_currentAccount = MyMoneyFile::instance()->account(id);
735 // if a stock account is selected, we show the
736 // the corresponding parent (investment) account
737 if (d->m_currentAccount.isInvest()) {
738 d->m_currentAccount = MyMoneyFile::instance()->account(d->m_currentAccount.parentAccountId());
739 }
740 d->m_lastSelectedAccountID = d->m_currentAccount.id();
741 d->m_newAccountLoaded = true;
742 refresh();
743 } catch (const MyMoneyException &) {
744 qDebug("Unable to retrieve account %s", qPrintable(id));
745 rc = false;
746 }
747 } else {
748 // we need to refresh m_account.m_accountList, a child could have been deleted
749 d->m_currentAccount = MyMoneyFile::instance()->account(id);
750
751 emit selectByObject(d->m_currentAccount, eView::Intent::None);
752 emit selectByObject(d->m_currentAccount, eView::Intent::SynchronizeAccountInInvestmentView);
753 }
754 d->selectTransaction(transactionId);
755 }
756 return rc;
757 }
758
selectEmptyTransaction()759 bool KGlobalLedgerView::selectEmptyTransaction()
760 {
761 Q_D(KGlobalLedgerView);
762 bool rc = false;
763
764 if (!d->m_inEditMode) {
765 // in case we don't know the type of transaction to be created,
766 // have at least one selected transaction and the id of
767 // this transaction is not empty, we take it as template for the
768 // transaction to be created
769 KMyMoneyRegister::SelectedTransactions list(d->m_register);
770 if ((d->m_action == eWidgets::eRegister::Action::None) && (!list.isEmpty()) && (!list[0].transaction().id().isEmpty())) {
771 // the new transaction to be created will have the same type
772 // as the one that currently has the focus
773 KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(d->m_register->focusItem());
774 if (t)
775 d->m_action = t->actionType();
776 d->m_register->clearSelection();
777 }
778
779 // if we still don't have an idea which type of transaction
780 // to create, we use the default.
781 if (d->m_action == eWidgets::eRegister::Action::None) {
782 d->setupDefaultAction();
783 }
784
785 d->m_register->selectItem(d->m_register->lastItem());
786 d->m_register->updateRegister();
787 rc = true;
788 }
789 return rc;
790 }
791
startEdit(const KMyMoneyRegister::SelectedTransactions & list)792 TransactionEditor* KGlobalLedgerView::startEdit(const KMyMoneyRegister::SelectedTransactions& list)
793 {
794 Q_D(KGlobalLedgerView);
795 // we use the warnlevel to keep track, if we have to warn the
796 // user that some or all splits have been reconciled or if the
797 // user cannot modify the transaction if at least one split
798 // has the status frozen. The following value are used:
799 //
800 // 0 - no sweat, user can modify
801 // 1 - user should be warned that at least one split has been reconciled
802 // already
803 // 2 - user will be informed, that this transaction cannot be changed anymore
804
805 int warnLevel = list.warnLevel();
806 Q_ASSERT(warnLevel < 2); // otherwise the edit action should not be enabled
807
808 switch (warnLevel) {
809 case 0:
810 break;
811
812 case 1:
813 if (KMessageBox::warningContinueCancel(this,
814 i18n(
815 "At least one split of the selected transactions has been reconciled. "
816 "Do you wish to continue to edit the transactions anyway?"
817 ),
818 i18n("Transaction already reconciled"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
819 "EditReconciledTransaction") == KMessageBox::Cancel) {
820 warnLevel = 2;
821 }
822 break;
823
824 case 2:
825 KMessageBox::sorry(this,
826 i18n("At least one split of the selected transactions has been frozen. "
827 "Editing the transactions is therefore prohibited."),
828 i18n("Transaction already frozen"));
829 break;
830
831 case 3:
832 KMessageBox::sorry(this,
833 i18n("At least one split of the selected transaction references an account that has been closed. "
834 "Editing the transactions is therefore prohibited."),
835 i18n("Account closed"));
836 break;
837 }
838
839 if (warnLevel > 1) {
840 d->m_register->endEdit();
841 return 0;
842 }
843
844
845 TransactionEditor* editor = 0;
846 KMyMoneyRegister::Transaction* item = dynamic_cast<KMyMoneyRegister::Transaction*>(d->m_register->focusItem());
847
848 if (item) {
849 // in case the current focus item is not selected, we move the focus to the first selected transaction
850 if (!item->isSelected()) {
851 KMyMoneyRegister::RegisterItem* p;
852 for (p = d->m_register->firstItem(); p; p = p->nextItem()) {
853 KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p);
854 if (t && t->isSelected()) {
855 d->m_register->setFocusItem(t);
856 item = t;
857 break;
858 }
859 }
860 }
861
862 // decide, if we edit in the register or in the form
863 TransactionEditorContainer* parent;
864 if (d->m_formFrame->isVisible())
865 parent = d->m_form;
866 else {
867 parent = d->m_register;
868 }
869
870 editor = item->createEditor(parent, list, KGlobalLedgerViewPrivate::m_lastPostDate);
871
872 // check that we use the same transaction commodity in all selected transactions
873 // if not, we need to update this in the editor's list. The user can also bail out
874 // of this operation which means that we have to stop editing here.
875 if (editor) {
876 if (!editor->fixTransactionCommodity(d->m_currentAccount)) {
877 // if the user wants to quit, we need to destroy the editor
878 // and bail out
879 delete editor;
880 editor = 0;
881 }
882 }
883
884 if (editor) {
885 if (parent == d->m_register) {
886 // make sure, the height of the table is correct
887 d->m_register->updateRegister(KMyMoneySettings::ledgerLens() | !KMyMoneySettings::transactionForm());
888 }
889
890 d->m_inEditMode = true;
891 connect(editor, &TransactionEditor::transactionDataSufficient, pActions[Action::EnterTransaction], &QAction::setEnabled);
892 connect(editor, &TransactionEditor::returnPressed, pActions[Action::EnterTransaction], &QAction::trigger);
893 connect(editor, &TransactionEditor::escapePressed, pActions[Action::CancelTransaction], &QAction::trigger);
894
895 connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, editor, &TransactionEditor::slotReloadEditWidgets);
896 connect(editor, &TransactionEditor::finishEdit, this, &KGlobalLedgerView::slotLeaveEditMode);
897 connect(editor, &TransactionEditor::objectCreation, d->m_mousePressFilter, &MousePressFilter::setFilterDeactive);
898 connect(editor, &TransactionEditor::lastPostDateUsed, this, &KGlobalLedgerView::slotKeepPostDate);
899
900 // create the widgets, place them in the parent and load them with data
901 // setup tab order
902 d->m_tabOrderWidgets.clear();
903 editor->setup(d->m_tabOrderWidgets, d->m_currentAccount, d->m_action);
904
905 Q_ASSERT(!d->m_tabOrderWidgets.isEmpty());
906
907 // install event filter in all taborder widgets
908 QWidgetList::const_iterator it_w = d->m_tabOrderWidgets.constBegin();
909 for (; it_w != d->m_tabOrderWidgets.constEnd(); ++it_w) {
910 (*it_w)->installEventFilter(this);
911 }
912 // Install a filter that checks if a mouse press happened outside
913 // of one of our own widgets.
914 qApp->installEventFilter(d->m_mousePressFilter);
915
916 // Check if the editor has some preference on where to set the focus
917 // If not, set the focus to the first widget in the tab order
918 QWidget* focusWidget = editor->firstWidget();
919 if (!focusWidget)
920 focusWidget = d->m_tabOrderWidgets.first();
921
922 // for some reason, this only works reliably if delayed a bit
923 QTimer::singleShot(10, focusWidget, SLOT(setFocus()));
924
925 // preset to 'I have no idea which type to create' for the next round.
926 d->m_action = eWidgets::eRegister::Action::None;
927 }
928 }
929 return editor;
930 }
931
slotTransactionsContextMenuRequested()932 void KGlobalLedgerView::slotTransactionsContextMenuRequested()
933 {
934 Q_D(KGlobalLedgerView);
935 auto transactions = d->m_selectedTransactions;
936 updateLedgerActionsInternal();
937 // emit transactionsSelected(d->m_selectedTransactions); // that should select MyMoneySchedule in KScheduledView
938 if (!transactions.isEmpty() && transactions.first().isScheduled())
939 emit selectByObject(MyMoneyFile::instance()->schedule(transactions.first().scheduleId()), eView::Intent::OpenContextMenu);
940 else
941 slotShowTransactionMenu(MyMoneySplit());
942 }
943
slotLeaveEditMode(const KMyMoneyRegister::SelectedTransactions & list)944 void KGlobalLedgerView::slotLeaveEditMode(const KMyMoneyRegister::SelectedTransactions& list)
945 {
946 Q_D(KGlobalLedgerView);
947 d->m_inEditMode = false;
948 qApp->removeEventFilter(d->m_mousePressFilter);
949
950 // a possible focusOut event may have removed the focus, so we
951 // install it back again.
952 d->m_register->focusItem()->setFocus(true);
953
954 // if we come back from editing a new item, we make sure that
955 // we always select the very last known transaction entry no
956 // matter if the transaction has been created or not.
957
958 if (list.count() && list[0].transaction().id().isEmpty()) {
959 // block signals to prevent some infinite loops that might occur here.
960 d->m_register->blockSignals(true);
961 d->m_register->clearSelection();
962 KMyMoneyRegister::RegisterItem* p = d->m_register->lastItem();
963 if (p && p->prevItem())
964 p = p->prevItem();
965 d->m_register->selectItem(p);
966 d->m_register->updateRegister(true);
967 d->m_register->blockSignals(false);
968 // we need to update the form manually as sending signals was blocked
969 KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p);
970 if (t)
971 d->m_form->slotSetTransaction(t);
972 } else {
973 if (!KMyMoneySettings::transactionForm()) {
974 // update the row height of the transactions because it might differ between viewing/editing mode when not using the transaction form
975 d->m_register->blockSignals(true);
976 d->m_register->updateRegister(true);
977 d->m_register->blockSignals(false);
978 }
979 }
980 d->m_needsRefresh = true; // TODO: Why transaction in view doesn't update without this?
981 if (d->m_needsRefresh)
982 refresh();
983
984 d->m_register->endEdit();
985 d->m_register->setFocus();
986 }
987
focusNextPrevChild(bool next)988 bool KGlobalLedgerView::focusNextPrevChild(bool next)
989 {
990 Q_D(KGlobalLedgerView);
991 bool rc = false;
992 // qDebug() << "----------------------------------------------------------";
993 // qDebug() << "KGlobalLedgerView::focusNextPrevChild, editmode=" << d->m_inEditMode;
994 if (d->m_inEditMode) {
995 QWidget *w = 0;
996
997 w = qApp->focusWidget();
998 int currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w);
999 const auto startIndex = currentWidgetIndex;
1000 // qDebug() << "Focus is at currentWidgetIndex" << currentWidgetIndex << w->objectName();
1001 do {
1002 while (w && currentWidgetIndex == -1) {
1003 // qDebug() << w->objectName() << "not in list, use parent";
1004 w = w->parentWidget();
1005 currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w);
1006 }
1007 // qDebug() << "Focus is at currentWidgetIndex" << currentWidgetIndex << w->objectName();
1008
1009 if (currentWidgetIndex != -1) {
1010 // if(w) qDebug() << "tab order is at" << w->objectName();
1011 currentWidgetIndex += next ? 1 : -1;
1012 if (currentWidgetIndex < 0)
1013 currentWidgetIndex = d->m_tabOrderWidgets.size() - 1;
1014 else if (currentWidgetIndex >= d->m_tabOrderWidgets.size())
1015 currentWidgetIndex = 0;
1016
1017 w = d->m_tabOrderWidgets[currentWidgetIndex];
1018 // qDebug() << "currentWidgetIndex" << currentWidgetIndex << w->objectName() << w->isVisible();
1019
1020 if (((w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && w->isVisible() && w->isEnabled()) {
1021 // qDebug() << "Set focus to" << w->objectName();
1022 w->setFocus(next ? Qt::TabFocusReason: Qt::BacktabFocusReason);
1023 rc = true;
1024 break;
1025 }
1026 } else {
1027 break;
1028 }
1029 } while(currentWidgetIndex != startIndex);
1030 } else
1031 rc = KMyMoneyViewBase::focusNextPrevChild(next);
1032 return rc;
1033 }
1034
eventFilter(QObject * o,QEvent * e)1035 bool KGlobalLedgerView::eventFilter(QObject* o, QEvent* e)
1036 {
1037 Q_D(KGlobalLedgerView);
1038 bool rc = false;
1039 // Need to capture mouse position here as QEvent::ToolTip is too slow
1040 d->m_tooltipPosn = QCursor::pos();
1041
1042 if (e->type() == QEvent::KeyPress) {
1043 if (d->m_inEditMode) {
1044 // qDebug("object = %s, key = %d", o->className(), k->key());
1045 if (o == d->m_register) {
1046 // we hide all key press events from the register
1047 // while editing a transaction
1048 rc = true;
1049 }
1050 }
1051 }
1052
1053 if (!rc)
1054 rc = KMyMoneyViewBase::eventFilter(o, e);
1055
1056 return rc;
1057 }
1058
slotSortOptions()1059 void KGlobalLedgerView::slotSortOptions()
1060 {
1061 Q_D(KGlobalLedgerView);
1062 QPointer<KSortOptionDlg> dlg = new KSortOptionDlg(this);
1063
1064 QString key;
1065 QString sortOrder, def;
1066 if (d->isReconciliationAccount()) {
1067 key = "kmm-sort-reconcile";
1068 def = KMyMoneySettings::sortReconcileView();
1069 } else {
1070 key = "kmm-sort-std";
1071 def = KMyMoneySettings::sortNormalView();
1072 }
1073
1074 // check if we have an account override of the sort order
1075 if (!d->m_currentAccount.value(key).isEmpty())
1076 sortOrder = d->m_currentAccount.value(key);
1077
1078 QString oldOrder = sortOrder;
1079
1080 dlg->setSortOption(sortOrder, def);
1081
1082 if (dlg->exec() == QDialog::Accepted) {
1083 if (dlg != 0) {
1084 sortOrder = dlg->sortOption();
1085 if (sortOrder != oldOrder) {
1086 if (sortOrder.isEmpty()) {
1087 d->m_currentAccount.deletePair(key);
1088 } else {
1089 d->m_currentAccount.setValue(key, sortOrder);
1090 }
1091 MyMoneyFileTransaction ft;
1092 try {
1093 MyMoneyFile::instance()->modifyAccount(d->m_currentAccount);
1094 ft.commit();
1095 } catch (const MyMoneyException &e) {
1096 qDebug("Unable to update sort order for account '%s': %s", qPrintable(d->m_currentAccount.name()), e.what());
1097 }
1098 }
1099 }
1100 }
1101 delete dlg;
1102 }
1103
slotToggleTransactionMark(KMyMoneyRegister::Transaction *)1104 void KGlobalLedgerView::slotToggleTransactionMark(KMyMoneyRegister::Transaction* /* t */)
1105 {
1106 Q_D(KGlobalLedgerView);
1107 if (!d->m_inEditMode) {
1108 slotToggleReconciliationFlag();
1109 }
1110 }
1111
slotKeepPostDate(const QDate & date)1112 void KGlobalLedgerView::slotKeepPostDate(const QDate& date)
1113 {
1114 KGlobalLedgerViewPrivate::m_lastPostDate = date;
1115 }
1116
accountId() const1117 QString KGlobalLedgerView::accountId() const
1118 {
1119 Q_D(const KGlobalLedgerView);
1120 return d->m_currentAccount.id();
1121 }
1122
canCreateTransactions(QString & tooltip) const1123 bool KGlobalLedgerView::canCreateTransactions(QString& tooltip) const
1124 {
1125 Q_D(const KGlobalLedgerView);
1126 bool rc = true;
1127
1128 if (d->m_currentAccount.id().isEmpty()) {
1129 tooltip = i18n("Cannot create transactions when no account is selected.");
1130 rc = false;
1131 }
1132 if (d->m_currentAccount.accountGroup() == eMyMoney::Account::Type::Income
1133 || d->m_currentAccount.accountGroup() == eMyMoney::Account::Type::Expense) {
1134 tooltip = i18n("Cannot create transactions in the context of a category.");
1135 d->showTooltip(tooltip);
1136 rc = false;
1137 }
1138 if (d->m_currentAccount.isClosed()) {
1139 tooltip = i18n("Cannot create transactions in a closed account.");
1140 d->showTooltip(tooltip);
1141 rc = false;
1142 }
1143 return rc;
1144 }
1145
canModifyTransactions(const KMyMoneyRegister::SelectedTransactions & list,QString & tooltip) const1146 bool KGlobalLedgerView::canModifyTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const
1147 {
1148 Q_D(const KGlobalLedgerView);
1149 return d->canProcessTransactions(list, tooltip) && list.canModify();
1150 }
1151
canDuplicateTransactions(const KMyMoneyRegister::SelectedTransactions & list,QString & tooltip) const1152 bool KGlobalLedgerView::canDuplicateTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const
1153 {
1154 Q_D(const KGlobalLedgerView);
1155 return d->canProcessTransactions(list, tooltip) && list.canDuplicate();
1156 }
1157
canEditTransactions(const KMyMoneyRegister::SelectedTransactions & list,QString & tooltip) const1158 bool KGlobalLedgerView::canEditTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const
1159 {
1160 Q_D(const KGlobalLedgerView);
1161 // check if we can edit the list of transactions. We can edit, if
1162 //
1163 // a) no mix of standard and investment transactions exist
1164 // b) if a split transaction is selected, this is the only selection
1165 // c) none of the splits is frozen
1166 // d) the transaction having the current focus is selected
1167
1168 // check for d)
1169 if (!d->canProcessTransactions(list, tooltip))
1170 return false;
1171 // check for c)
1172 if (list.warnLevel() == 2) {
1173 tooltip = i18n("Cannot edit transactions with frozen splits.");
1174 d->showTooltip(tooltip);
1175 return false;
1176 }
1177
1178 bool rc = true;
1179 int investmentTransactions = 0;
1180 int normalTransactions = 0;
1181
1182 if (d->m_currentAccount.accountGroup() == eMyMoney::Account::Type::Income
1183 || d->m_currentAccount.accountGroup() == eMyMoney::Account::Type::Expense) {
1184 tooltip = i18n("Cannot edit transactions in the context of a category.");
1185 d->showTooltip(tooltip);
1186 rc = false;
1187 }
1188
1189 if (d->m_currentAccount.isClosed()) {
1190 tooltip = i18n("Cannot create or edit any transactions in Account %1 as it is closed", d->m_currentAccount.name());
1191 d->showTooltip(tooltip);
1192 rc = false;
1193 }
1194
1195 KMyMoneyRegister::SelectedTransactions::const_iterator it_t;
1196 QString action;
1197 for (it_t = list.begin(); rc && it_t != list.end(); ++it_t) {
1198 if ((*it_t).transaction().id().isEmpty()) {
1199 tooltip.clear();
1200 rc = false;
1201 continue;
1202 }
1203
1204 if (KMyMoneyUtils::transactionType((*it_t).transaction()) == KMyMoneyUtils::InvestmentTransaction) {
1205 if (action.isEmpty()) {
1206 action = (*it_t).split().action();
1207 continue;
1208 }
1209 if (action == (*it_t).split().action()) {
1210 continue;
1211 } else {
1212 tooltip = (i18n("Cannot edit mixed investment action/type transactions together."));
1213 d->showTooltip(tooltip);
1214 rc = false;
1215 break;
1216 }
1217 }
1218
1219 if (KMyMoneyUtils::transactionType((*it_t).transaction()) == KMyMoneyUtils::InvestmentTransaction)
1220 ++investmentTransactions;
1221 else
1222 ++normalTransactions;
1223
1224 // check for a)
1225 if (investmentTransactions != 0 && normalTransactions != 0) {
1226 tooltip = i18n("Cannot edit investment transactions and non-investment transactions together.");
1227 d->showTooltip(tooltip);
1228 rc = false;
1229 break;
1230 }
1231
1232 // check for b) but only for normalTransactions
1233 if ((*it_t).transaction().splitCount() > 2 && normalTransactions != 0) {
1234 if (list.count() > 1) {
1235 tooltip = i18n("Cannot edit multiple split transactions at once.");
1236 d->showTooltip(tooltip);
1237 rc = false;
1238 break;
1239 }
1240 }
1241 }
1242
1243 // check for multiple transactions being selected in an investment account
1244 // we do not allow editing in this case: https://bugs.kde.org/show_bug.cgi?id=240816
1245 // later on, we might allow to edit investment transactions of the same type
1246 /// Can now disable the following check.
1247
1248 /* if (rc == true && investmentTransactions > 1) {
1249 tooltip = i18n("Cannot edit multiple investment transactions at once");
1250 rc = false;
1251 }*/
1252
1253 // now check that we have the correct account type for investment transactions
1254 if (rc == true && investmentTransactions != 0) {
1255 if (d->m_currentAccount.accountType() != eMyMoney::Account::Type::Investment) {
1256 tooltip = i18n("Cannot edit investment transactions in the context of this account.");
1257 rc = false;
1258 }
1259 }
1260 return rc;
1261 }
1262
slotMoveToAccount(const QString & id)1263 void KGlobalLedgerView::slotMoveToAccount(const QString& id)
1264 {
1265 Q_D(KGlobalLedgerView);
1266 // close the menu, if it is still open
1267 if (pMenus[Menu::Transaction]->isVisible())
1268 pMenus[Menu::Transaction]->close();
1269
1270 if (!d->m_selectedTransactions.isEmpty()) {
1271 const auto file = MyMoneyFile::instance();
1272 MyMoneyFileTransaction ft;
1273 try {
1274 foreach (const auto selection, d->m_selectedTransactions) {
1275 if (d->m_currentAccount.accountType() == eMyMoney::Account::Type::Investment) {
1276 d->moveInvestmentTransaction(d->m_currentAccount.id(), id, selection.transaction());
1277 } else {
1278 // we get the data afresh from the engine as
1279 // it might have changed by a previous iteration
1280 // in this loop. Use case: two splits point to
1281 // the same account and both are selected.
1282 auto tid = selection.transaction().id();
1283 auto sid = selection.split().id();
1284 auto t = file->transaction(tid);
1285 auto s = t.splitById(sid);
1286 s.setAccountId(id);
1287 t.modifySplit(s);
1288 file->modifyTransaction(t);
1289 }
1290 }
1291 ft.commit();
1292 } catch (const MyMoneyException &) {
1293 }
1294 }
1295 }
1296
slotUpdateMoveToAccountMenu()1297 void KGlobalLedgerView::slotUpdateMoveToAccountMenu()
1298 {
1299 Q_D(KGlobalLedgerView);
1300 d->createTransactionMoveMenu();
1301
1302 // in case we were not able to create the selector, we
1303 // better get out of here. Anything else would cause
1304 // a crash later on (accountSet.load)
1305 if (!d->m_moveToAccountSelector)
1306 return;
1307
1308 if (!d->m_currentAccount.id().isEmpty()) {
1309 AccountSet accountSet;
1310 if (d->m_currentAccount.accountType() == eMyMoney::Account::Type::Investment) {
1311 accountSet.addAccountType(eMyMoney::Account::Type::Investment);
1312 } else if (d->m_currentAccount.isAssetLiability()) {
1313
1314 accountSet.addAccountType(eMyMoney::Account::Type::Checkings);
1315 accountSet.addAccountType(eMyMoney::Account::Type::Savings);
1316 accountSet.addAccountType(eMyMoney::Account::Type::Cash);
1317 accountSet.addAccountType(eMyMoney::Account::Type::AssetLoan);
1318 accountSet.addAccountType(eMyMoney::Account::Type::CertificateDep);
1319 accountSet.addAccountType(eMyMoney::Account::Type::MoneyMarket);
1320 accountSet.addAccountType(eMyMoney::Account::Type::Asset);
1321 accountSet.addAccountType(eMyMoney::Account::Type::Currency);
1322 accountSet.addAccountType(eMyMoney::Account::Type::CreditCard);
1323 accountSet.addAccountType(eMyMoney::Account::Type::Loan);
1324 accountSet.addAccountType(eMyMoney::Account::Type::Liability);
1325 } else if (d->m_currentAccount.isIncomeExpense()) {
1326 accountSet.addAccountType(eMyMoney::Account::Type::Income);
1327 accountSet.addAccountType(eMyMoney::Account::Type::Expense);
1328 }
1329
1330 accountSet.load(d->m_moveToAccountSelector);
1331 // remove those accounts that we currently reference
1332 // with the selected items
1333 foreach (const auto selection, d->m_selectedTransactions) {
1334 d->m_moveToAccountSelector->removeItem(selection.split().accountId());
1335 }
1336 // remove those accounts from the list that are denominated
1337 // in a different currency
1338 auto list = d->m_moveToAccountSelector->accountList();
1339 QList<QString>::const_iterator it_a;
1340 for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) {
1341 auto acc = MyMoneyFile::instance()->account(*it_a);
1342 if (acc.currencyId() != d->m_currentAccount.currencyId())
1343 d->m_moveToAccountSelector->removeItem((*it_a));
1344 }
1345 }
1346 }
1347
slotObjectDestroyed(QObject * o)1348 void KGlobalLedgerView::slotObjectDestroyed(QObject* o)
1349 {
1350 Q_D(KGlobalLedgerView);
1351 if (o == d->m_moveToAccountSelector) {
1352 d->m_moveToAccountSelector = nullptr;
1353 }
1354 }
1355
slotCancelOrEnterTransactions(bool & okToSelect)1356 void KGlobalLedgerView::slotCancelOrEnterTransactions(bool& okToSelect)
1357 {
1358 Q_D(KGlobalLedgerView);
1359 static bool oneTime = false;
1360 if (!oneTime) {
1361 oneTime = true;
1362 auto dontShowAgain = "CancelOrEditTransaction";
1363 // qDebug("KMyMoneyApp::slotCancelOrEndEdit");
1364 if (d->m_transactionEditor) {
1365 if (KMyMoneySettings::focusChangeIsEnter() && pActions[Action::EnterTransaction]->isEnabled()) {
1366 slotEnterTransaction();
1367 if (d->m_transactionEditor) {
1368 // if at this stage the editor is still there that means that entering the transaction was cancelled
1369 // for example by pressing cancel on the exchange rate editor so we must stay in edit mode
1370 okToSelect = false;
1371 }
1372 } else {
1373 // okToSelect is preset to true if a cancel of the dialog is useful and false if it is not
1374 int rc;
1375 KGuiItem noGuiItem = KStandardGuiItem::save();
1376 KGuiItem yesGuiItem = KStandardGuiItem::discard();
1377 KGuiItem cancelGuiItem = KStandardGuiItem::cont();
1378
1379 // if the transaction can't be entered make sure that it can't be entered by pressing no either
1380 if (!pActions[Action::EnterTransaction]->isEnabled()) {
1381 noGuiItem.setEnabled(false);
1382 noGuiItem.setToolTip(pActions[Action::EnterTransaction]->toolTip());
1383 }
1384
1385 // in case we have a new transaction and cannot save it we simply cancel
1386 if (!pActions[Action::EnterTransaction]->isEnabled() && d->m_transactionEditor && d->m_transactionEditor->createNewTransaction()) {
1387 rc = KMessageBox::Yes;
1388
1389 } else if (okToSelect == true) {
1390 rc = KMessageBox::warningYesNoCancel(this, i18n("<p>Please select what you want to do: discard the changes, save the changes or continue to edit the transaction.</p><p>You can also set an option to save the transaction automatically when e.g. selecting another transaction.</p>"), i18n("End transaction edit"), yesGuiItem, noGuiItem, cancelGuiItem, dontShowAgain);
1391
1392 } else {
1393 rc = KMessageBox::warningYesNo(this, i18n("<p>Please select what you want to do: discard or save the changes.</p><p>You can also set an option to save the transaction automatically when e.g. selecting another transaction.</p>"), i18n("End transaction edit"), yesGuiItem, noGuiItem, dontShowAgain);
1394 }
1395
1396 switch (rc) {
1397 case KMessageBox::Yes:
1398 slotCancelTransaction();
1399 break;
1400 case KMessageBox::No:
1401 slotEnterTransaction();
1402 // make sure that we'll see this message the next time no matter
1403 // if the user has chosen the 'Don't show again' checkbox
1404 KMessageBox::enableMessage(dontShowAgain);
1405 if (d->m_transactionEditor) {
1406 // if at this stage the editor is still there that means that entering the transaction was cancelled
1407 // for example by pressing cancel on the exchange rate editor so we must stay in edit mode
1408 okToSelect = false;
1409 }
1410 break;
1411 case KMessageBox::Cancel:
1412 // make sure that we'll see this message the next time no matter
1413 // if the user has chosen the 'Don't show again' checkbox
1414 KMessageBox::enableMessage(dontShowAgain);
1415 okToSelect = false;
1416 break;
1417 }
1418 }
1419 }
1420 oneTime = false;
1421 }
1422 }
1423
slotNewSchedule(const MyMoneyTransaction & _t,eMyMoney::Schedule::Occurrence occurrence)1424 void KGlobalLedgerView::slotNewSchedule(const MyMoneyTransaction& _t, eMyMoney::Schedule::Occurrence occurrence)
1425 {
1426 KEditScheduleDlg::newSchedule(_t, occurrence);
1427 }
1428
slotNewTransactionForm(eWidgets::eRegister::Action id)1429 void KGlobalLedgerView::slotNewTransactionForm(eWidgets::eRegister::Action id)
1430 {
1431 Q_D(KGlobalLedgerView);
1432 if (!d->m_inEditMode) {
1433 d->m_action = id;
1434 // since we jump here via code, we have to make sure to react only
1435 // if the action is enabled
1436 if (pActions[Action::NewTransaction]->isEnabled()) {
1437 if (d->createNewTransaction()) {
1438 d->m_transactionEditor = d->startEdit(d->m_selectedTransactions);
1439 if (d->m_transactionEditor) {
1440 KMyMoneyMVCCombo::setSubstringSearchForChildren(this/*d->m_myMoneyView*/, !KMyMoneySettings::stringMatchFromStart());
1441 KMyMoneyPayeeCombo* payeeEdit = dynamic_cast<KMyMoneyPayeeCombo*>(d->m_transactionEditor->haveWidget("payee"));
1442 if (payeeEdit && !d->m_lastPayeeEnteredId.isEmpty()) {
1443 // in case we entered a new transaction before and used a payee,
1444 // we reuse it here. Save the text to the edit widget, select it
1445 // so that hitting any character will start entering another payee.
1446 payeeEdit->setSelectedItem(d->m_lastPayeeEnteredId);
1447 payeeEdit->lineEdit()->selectAll();
1448 }
1449 if (d->m_transactionEditor) {
1450 connect(d->m_transactionEditor.data(), &TransactionEditor::statusProgress, this, &KGlobalLedgerView::slotStatusProgress);
1451 connect(d->m_transactionEditor.data(), &TransactionEditor::statusMsg, this, &KGlobalLedgerView::slotStatusMsg);
1452 connect(d->m_transactionEditor.data(), &TransactionEditor::scheduleTransaction, this, &KGlobalLedgerView::slotNewSchedule);
1453 }
1454 updateLedgerActionsInternal();
1455 // emit transactionsSelected(d->m_selectedTransactions);
1456 }
1457 }
1458 }
1459 }
1460 }
1461
slotNewTransaction()1462 void KGlobalLedgerView::slotNewTransaction()
1463 {
1464 // in case the view is not visible ...
1465 if (!isVisible()) {
1466 // we switch to it
1467 pActions[Action::ShowLedgersView]->activate(QAction::ActionEvent::Trigger);
1468 QString tooltip;
1469 if (!canCreateTransactions(tooltip)) {
1470 // and inform the user via a dialog about the reason
1471 // why a transaction cannot be created
1472 KMessageBox::sorry(this, tooltip);
1473 return;
1474 }
1475 }
1476 slotNewTransactionForm(eWidgets::eRegister::Action::None);
1477 }
1478
slotEditTransaction()1479 void KGlobalLedgerView::slotEditTransaction()
1480 {
1481 Q_D(KGlobalLedgerView);
1482 // qDebug("KMyMoneyApp::slotTransactionsEdit()");
1483 // since we jump here via code, we have to make sure to react only
1484 // if the action is enabled
1485 if (pActions[Action::EditTransaction]->isEnabled()) {
1486 // as soon as we edit a transaction, we don't remember the last payee entered
1487 d->m_lastPayeeEnteredId.clear();
1488 d->m_transactionEditor = d->startEdit(d->m_selectedTransactions);
1489 KMyMoneyMVCCombo::setSubstringSearchForChildren(this/*d->m_myMoneyView*/, !KMyMoneySettings::stringMatchFromStart());
1490 updateLedgerActionsInternal();
1491 }
1492 }
1493
slotDeleteTransaction()1494 void KGlobalLedgerView::slotDeleteTransaction()
1495 {
1496 Q_D(KGlobalLedgerView);
1497 // since we may jump here via code, we have to make sure to react only
1498 // if the action is enabled
1499 if (!pActions[Action::DeleteTransaction]->isEnabled())
1500 return;
1501 if (d->m_selectedTransactions.isEmpty())
1502 return;
1503 if (d->m_selectedTransactions.warnLevel() == 1) {
1504 if (KMessageBox::warningContinueCancel(this,
1505 i18n("At least one split of the selected transactions has been reconciled. "
1506 "Do you wish to delete the transactions anyway?"),
1507 i18n("Transaction already reconciled")) == KMessageBox::Cancel)
1508 return;
1509 }
1510 auto msg =
1511 i18np("Do you really want to delete the selected transaction?",
1512 "Do you really want to delete all %1 selected transactions?",
1513 d->m_selectedTransactions.count());
1514
1515 if (KMessageBox::questionYesNo(this, msg, i18n("Delete transaction")) == KMessageBox::Yes) {
1516 //KMSTATUS(i18n("Deleting transactions"));
1517 d->doDeleteTransactions();
1518 }
1519 }
1520
slotDuplicateTransaction(bool reverse)1521 void KGlobalLedgerView::slotDuplicateTransaction(bool reverse)
1522 {
1523 Q_D(KGlobalLedgerView);
1524 // since we may jump here via code, we have to make sure to react only
1525 // if the action is enabled
1526 if (pActions[Action::DuplicateTransaction]->isEnabled()) {
1527 KMyMoneyRegister::SelectedTransactions selectionList = d->m_selectedTransactions;
1528 KMyMoneyRegister::SelectedTransactions::iterator it_t;
1529
1530 int i = 0;
1531 int cnt = d->m_selectedTransactions.count();
1532 // KMSTATUS(i18n("Duplicating transactions"));
1533 emit selectByVariant(QVariantList {QVariant(0), QVariant(cnt)}, eView::Intent::ReportProgress);
1534 MyMoneyFileTransaction ft;
1535 MyMoneyTransaction lt;
1536 try {
1537 foreach (const auto selection, selectionList) {
1538 auto t = selection.transaction();
1539 // wipe out any reconciliation information
1540 for (auto& split : t.splits()) {
1541 split.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
1542 split.setReconcileDate(QDate());
1543 split.setBankID(QString());
1544 }
1545 // clear invalid data
1546 t.setEntryDate(QDate());
1547 t.clearId();
1548
1549 if (reverse)
1550 // reverse transaction
1551 t.reverse();
1552 else
1553 // set the post date to today
1554 t.setPostDate(QDate::currentDate());
1555
1556 MyMoneyFile::instance()->addTransaction(t);
1557 lt = t;
1558 emit selectByVariant(QVariantList {QVariant(i++), QVariant(0)}, eView::Intent::ReportProgress);
1559 }
1560 ft.commit();
1561
1562 // select the new transaction in the ledger
1563 if (!d->m_currentAccount.id().isEmpty())
1564 slotLedgerSelected(d->m_currentAccount.id(), lt.id());
1565 } catch (const MyMoneyException &e) {
1566 KMessageBox::detailedSorry(this, i18n("Unable to duplicate transaction(s)"), QString::fromLatin1(e.what()));
1567 }
1568 // switch off the progress bar
1569 emit selectByVariant(QVariantList {QVariant(-1), QVariant(-1)}, eView::Intent::ReportProgress);
1570 }
1571 }
1572
slotEnterTransaction()1573 void KGlobalLedgerView::slotEnterTransaction()
1574 {
1575 Q_D(KGlobalLedgerView);
1576 // since we jump here via code, we have to make sure to react only
1577 // if the action is enabled
1578 if (pActions[Action::EnterTransaction]->isEnabled()) {
1579 // disable the action while we process it to make sure it's processed only once since
1580 // d->m_transactionEditor->enterTransactions(newId) will run QCoreApplication::processEvents
1581 // we could end up here twice which will cause a crash slotUpdateActions() will enable the action again
1582 pActions[Action::EnterTransaction]->setEnabled(false);
1583 if (d->m_transactionEditor) {
1584 QString accountId = d->m_currentAccount.id();
1585 QString newId;
1586 connect(d->m_transactionEditor.data(), &TransactionEditor::balanceWarning, d->m_balanceWarning.data(), &KBalanceWarning::slotShowMessage);
1587 if (d->m_transactionEditor->enterTransactions(newId)) {
1588 KMyMoneyPayeeCombo* payeeEdit = dynamic_cast<KMyMoneyPayeeCombo*>(d->m_transactionEditor->haveWidget("payee"));
1589 if (payeeEdit && !newId.isEmpty()) {
1590 d->m_lastPayeeEnteredId = payeeEdit->selectedItem();
1591 }
1592 d->deleteTransactionEditor();
1593 }
1594 if (!newId.isEmpty()) {
1595 slotLedgerSelected(accountId, newId);
1596 }
1597 }
1598 updateLedgerActionsInternal();
1599 }
1600 }
1601
slotAcceptTransaction()1602 void KGlobalLedgerView::slotAcceptTransaction()
1603 {
1604 Q_D(KGlobalLedgerView);
1605 KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions;
1606 KMyMoneyRegister::SelectedTransactions::const_iterator it_t;
1607 int cnt = list.count();
1608 int i = 0;
1609 emit selectByVariant(QVariantList {QVariant(0), QVariant(cnt)}, eView::Intent::ReportProgress);
1610 MyMoneyFileTransaction ft;
1611 try {
1612 for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) {
1613 // reload transaction in case it got changed during the course of this loop
1614 MyMoneyTransaction t = MyMoneyFile::instance()->transaction((*it_t).transaction().id());
1615 if (t.isImported()) {
1616 t.setImported(false);
1617 if (!d->m_currentAccount.id().isEmpty()) {
1618 foreach (const auto split, t.splits()) {
1619 if (split.accountId() == d->m_currentAccount.id()) {
1620 if (split.reconcileFlag() == eMyMoney::Split::State::NotReconciled) {
1621 MyMoneySplit s = split;
1622 s.setReconcileFlag(eMyMoney::Split::State::Cleared);
1623 t.modifySplit(s);
1624 }
1625 }
1626 }
1627 }
1628 MyMoneyFile::instance()->modifyTransaction(t);
1629 }
1630 if ((*it_t).split().isMatched()) {
1631 // reload split in case it got changed during the course of this loop
1632 MyMoneySplit s = t.splitById((*it_t).split().id());
1633 TransactionMatcher matcher(d->m_currentAccount);
1634 matcher.accept(t, s);
1635 }
1636 emit selectByVariant(QVariantList {QVariant(i++), QVariant(0)}, eView::Intent::ReportProgress);
1637 }
1638 emit selectByVariant(QVariantList {QVariant(-1), QVariant(-1)}, eView::Intent::ReportProgress);
1639 ft.commit();
1640 } catch (const MyMoneyException &e) {
1641 KMessageBox::detailedSorry(this, i18n("Unable to accept transaction"), QString::fromLatin1(e.what()));
1642 }
1643 }
1644
slotCancelTransaction()1645 void KGlobalLedgerView::slotCancelTransaction()
1646 {
1647 Q_D(KGlobalLedgerView);
1648 // since we jump here via code, we have to make sure to react only
1649 // if the action is enabled
1650 if (pActions[Action::CancelTransaction]->isEnabled()) {
1651 // make sure, we block the enter function
1652 pActions[Action::EnterTransaction]->setEnabled(false);
1653 // qDebug("KMyMoneyApp::slotTransactionsCancel");
1654 d->deleteTransactionEditor();
1655 updateLedgerActions(d->m_selectedTransactions);
1656 emit selectByVariant(QVariantList {QVariant::fromValue(d->m_selectedTransactions)}, eView::Intent::SelectRegisterTransactions);
1657 }
1658 }
1659
slotEditSplits()1660 void KGlobalLedgerView::slotEditSplits()
1661 {
1662 Q_D(KGlobalLedgerView);
1663 // since we jump here via code, we have to make sure to react only
1664 // if the action is enabled
1665 if (pActions[Action::EditSplits]->isEnabled()) {
1666 // as soon as we edit a transaction, we don't remember the last payee entered
1667 d->m_lastPayeeEnteredId.clear();
1668 d->m_transactionEditor = d->startEdit(d->m_selectedTransactions);
1669 updateLedgerActions(d->m_selectedTransactions);
1670 emit selectByVariant(QVariantList {QVariant::fromValue(d->m_selectedTransactions)}, eView::Intent::SelectRegisterTransactions);
1671
1672 if (d->m_transactionEditor) {
1673 KMyMoneyMVCCombo::setSubstringSearchForChildren(this/*d->m_myMoneyView*/, !KMyMoneySettings::stringMatchFromStart());
1674 if (d->m_transactionEditor->slotEditSplits() == QDialog::Accepted) {
1675 MyMoneyFileTransaction ft;
1676 try {
1677 QString id;
1678 connect(d->m_transactionEditor.data(), &TransactionEditor::balanceWarning, d->m_balanceWarning.data(), &KBalanceWarning::slotShowMessage);
1679 d->m_transactionEditor->enterTransactions(id);
1680 ft.commit();
1681 } catch (const MyMoneyException &e) {
1682 KMessageBox::detailedSorry(this, i18n("Unable to modify transaction"), QString::fromLatin1(e.what()));
1683 }
1684 }
1685 }
1686 d->deleteTransactionEditor();
1687 updateLedgerActions(d->m_selectedTransactions);
1688 emit selectByVariant(QVariantList {QVariant::fromValue(d->m_selectedTransactions)}, eView::Intent::SelectRegisterTransactions);
1689 }
1690 }
1691
slotCopyTransactionToClipboard()1692 void KGlobalLedgerView::slotCopyTransactionToClipboard()
1693 {
1694 Q_D(KGlobalLedgerView);
1695
1696 // suppress copy transactions if view not visible
1697 // or in edit mode
1698 if (!isVisible() || d->m_inEditMode)
1699 return;
1700
1701 // format transactions into text
1702 QString txt;
1703 const auto file = MyMoneyFile::instance();
1704 const auto acc = file->account(d->m_lastSelectedAccountID);
1705 const auto currency = file->currency(acc.currencyId());
1706
1707 foreach (const auto& st, d->m_selectedTransactions) {
1708 if (!txt.isEmpty() || (d->m_selectedTransactions.count() > 1)) {
1709 txt += QStringLiteral("----------------------------\n");
1710 }
1711 try {
1712 const auto& s = st.split();
1713 // Date
1714 txt += i18n("Date: %1", st.transaction().postDate().toString(Qt::DefaultLocaleShortDate));
1715 txt += QStringLiteral("\n");
1716 // Payee
1717 QString payee = i18nc("Name for unknown payee", "Unknown");
1718 if (!s.payeeId().isEmpty()) {
1719 payee = file->payee(s.payeeId()).name();
1720 }
1721 txt += i18n("Payee: %1", payee);
1722 txt += QStringLiteral("\n");
1723 // Amount
1724 txt += i18n("Amount: %1", s.value().formatMoney(currency.tradingSymbol(), MyMoneyMoney::denomToPrec(acc.fraction(currency))));
1725 txt += QStringLiteral("\n");
1726 // Memo
1727 txt += i18n("Memo: %1", s.memo());
1728 txt += QStringLiteral("\n");
1729
1730 } catch (MyMoneyException &) {
1731 qDebug() << "Cannot copy transaction" << st.transaction().id() << "to clipboard";
1732 }
1733 }
1734 if (d->m_selectedTransactions.count() > 1) {
1735 txt += QStringLiteral("----------------------------\n");
1736 }
1737
1738 if (!txt.isEmpty()) {
1739 QClipboard *clipboard = QGuiApplication::clipboard();
1740 clipboard->setText(txt);
1741 }
1742 }
1743
slotCopySplits()1744 void KGlobalLedgerView::slotCopySplits()
1745 {
1746 Q_D(KGlobalLedgerView);
1747 const auto file = MyMoneyFile::instance();
1748
1749 if (d->m_selectedTransactions.count() >= 2) {
1750 int singleSplitTransactions = 0;
1751 int multipleSplitTransactions = 0;
1752 KMyMoneyRegister::SelectedTransaction selectedSourceTransaction;
1753 foreach (const auto& st, d->m_selectedTransactions) {
1754 switch (st.transaction().splitCount()) {
1755 case 0:
1756 break;
1757 case 1:
1758 singleSplitTransactions++;
1759 break;
1760 default:
1761 selectedSourceTransaction = st;
1762 multipleSplitTransactions++;
1763 break;
1764 }
1765 }
1766 if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) {
1767 MyMoneyFileTransaction ft;
1768 try {
1769 const auto& sourceTransaction = selectedSourceTransaction.transaction();
1770 const auto& sourceSplit = selectedSourceTransaction.split();
1771 foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) {
1772 auto t = st.transaction();
1773
1774 // don't process the source transaction
1775 if (sourceTransaction.id() == t.id()) {
1776 continue;
1777 }
1778
1779 const auto& baseSplit = st.split();
1780
1781 if (t.splitCount() == 1) {
1782 foreach (const auto& split, sourceTransaction.splits()) {
1783 // Don't copy the source split, as we already have that
1784 // as part of the destination transaction
1785 if (split.id() == sourceSplit.id()) {
1786 continue;
1787 }
1788
1789 MyMoneySplit sp(split);
1790 // clear the ID and reconciliation state
1791 sp.clearId();
1792 sp.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
1793 sp.setReconcileDate(QDate());
1794
1795 // in case it is a simple transaction consisting of two splits,
1796 // we can adjust the share and value part of the second split we
1797 // just created. We need to keep a possible price in mind in case
1798 // of different currencies
1799 if (sourceTransaction.splitCount() == 2) {
1800 sp.setValue(-baseSplit.value());
1801 sp.setShares(-(baseSplit.shares() * baseSplit.price()));
1802 }
1803 t.addSplit(sp);
1804 }
1805 // check if we need to add/update a VAT assignment
1806 file->updateVAT(t);
1807
1808 // and store the modified transaction
1809 file->modifyTransaction(t);
1810 }
1811 }
1812 ft.commit();
1813 } catch (const MyMoneyException &) {
1814 qDebug() << "transactionCopySplits() failed";
1815 }
1816 }
1817 }
1818 }
1819
slotGoToPayee()1820 void KGlobalLedgerView::slotGoToPayee()
1821 {
1822 Q_D(KGlobalLedgerView);
1823 if (!d->m_payeeGoto.isEmpty()) {
1824 try {
1825 QString transactionId;
1826 if (d->m_selectedTransactions.count() == 1) {
1827 transactionId = d->m_selectedTransactions[0].transaction().id();
1828 }
1829 // make sure to pass copies, as d->myMoneyView->slotPayeeSelected() overrides
1830 // d->m_payeeGoto and d->m_currentAccount while calling slotUpdateActions()
1831 QString payeeId = d->m_payeeGoto;
1832 QString accountId = d->m_currentAccount.id();
1833 emit selectByVariant(QVariantList {QVariant(payeeId), QVariant(accountId), QVariant(transactionId)}, eView::Intent::ShowPayee);
1834 // emit openPayeeRequested(payeeId, accountId, transactionId);
1835 } catch (const MyMoneyException &) {
1836 }
1837 }
1838 }
1839
slotGoToAccount()1840 void KGlobalLedgerView::slotGoToAccount()
1841 {
1842 Q_D(KGlobalLedgerView);
1843 if (!d->m_accountGoto.isEmpty()) {
1844 try {
1845 QString transactionId;
1846 if (d->m_selectedTransactions.count() == 1) {
1847 transactionId = d->m_selectedTransactions[0].transaction().id();
1848 }
1849 // make sure to pass a copy, as d->myMoneyView->slotLedgerSelected() overrides
1850 // d->m_accountGoto while calling slotUpdateActions()
1851 slotLedgerSelected(d->m_accountGoto, transactionId);
1852 } catch (const MyMoneyException &) {
1853 }
1854 }
1855 }
1856
slotMatchTransactions()1857 void KGlobalLedgerView::slotMatchTransactions()
1858 {
1859 Q_D(KGlobalLedgerView);
1860 // if the menu action is retrieved it can contain an '&' character for the accelerator causing the comparison to fail if not removed
1861 QString transactionActionText = pActions[Action::MatchTransaction]->text();
1862 transactionActionText.remove('&');
1863 if (transactionActionText == i18nc("Button text for match transaction", "Match"))
1864 d->transactionMatch();
1865 else
1866 d->transactionUnmatch();
1867 }
1868
slotCombineTransactions()1869 void KGlobalLedgerView::slotCombineTransactions()
1870 {
1871 qDebug("slotTransactionCombine() not implemented yet");
1872 }
1873
slotToggleReconciliationFlag()1874 void KGlobalLedgerView::slotToggleReconciliationFlag()
1875 {
1876 Q_D(KGlobalLedgerView);
1877 d->markTransaction(eMyMoney::Split::State::Unknown);
1878 }
1879
slotMarkCleared()1880 void KGlobalLedgerView::slotMarkCleared()
1881 {
1882 Q_D(KGlobalLedgerView);
1883 d->markTransaction(eMyMoney::Split::State::Cleared);
1884 }
1885
slotMarkReconciled()1886 void KGlobalLedgerView::slotMarkReconciled()
1887 {
1888 Q_D(KGlobalLedgerView);
1889 d->markTransaction(eMyMoney::Split::State::Reconciled);
1890 }
1891
slotMarkNotReconciled()1892 void KGlobalLedgerView::slotMarkNotReconciled()
1893 {
1894 Q_D(KGlobalLedgerView);
1895 d->markTransaction(eMyMoney::Split::State::NotReconciled);
1896 }
1897
slotSelectAllTransactions()1898 void KGlobalLedgerView::slotSelectAllTransactions()
1899 {
1900 Q_D(KGlobalLedgerView);
1901 if(d->m_needLoad)
1902 d->init();
1903
1904 d->m_register->clearSelection();
1905 KMyMoneyRegister::RegisterItem* p = d->m_register->firstItem();
1906 while (p) {
1907 KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p);
1908 if (t) {
1909 if (t->isVisible() && t->isSelectable() && !t->isScheduled() && !t->id().isEmpty()) {
1910 t->setSelected(true);
1911 }
1912 }
1913 p = p->nextItem();
1914 }
1915 // this is here only to re-paint the items without selecting anything because the data (including the selection) is not really held in the model right now
1916 d->m_register->selectAll();
1917
1918 // inform everyone else about the selected items
1919 KMyMoneyRegister::SelectedTransactions list(d->m_register);
1920 updateLedgerActions(list);
1921 emit selectByVariant(QVariantList {QVariant::fromValue(list)}, eView::Intent::SelectRegisterTransactions);
1922 }
1923
slotCreateScheduledTransaction()1924 void KGlobalLedgerView::slotCreateScheduledTransaction()
1925 {
1926 Q_D(KGlobalLedgerView);
1927 if (d->m_selectedTransactions.count() == 1) {
1928 // make sure to have the current selected split as first split in the schedule
1929 MyMoneyTransaction t = d->m_selectedTransactions[0].transaction();
1930 MyMoneySplit s = d->m_selectedTransactions[0].split();
1931 QString splitId = s.id();
1932 s.clearId();
1933 s.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
1934 s.setReconcileDate(QDate());
1935 t.removeSplits();
1936 t.addSplit(s);
1937 foreach (const auto split, d->m_selectedTransactions[0].transaction().splits()) {
1938 if (split.id() != splitId) {
1939 MyMoneySplit s0 = split;
1940 s0.clearId();
1941 s0.setReconcileFlag(eMyMoney::Split::State::NotReconciled);
1942 s0.setReconcileDate(QDate());
1943 t.addSplit(s0);
1944 }
1945 }
1946 KEditScheduleDlg::newSchedule(t, eMyMoney::Schedule::Occurrence::Monthly);
1947 }
1948 }
1949
slotAssignNumber()1950 void KGlobalLedgerView::slotAssignNumber()
1951 {
1952 Q_D(KGlobalLedgerView);
1953 if (d->m_transactionEditor)
1954 d->m_transactionEditor->assignNextNumber();
1955 }
1956
slotStartReconciliation()1957 void KGlobalLedgerView::slotStartReconciliation()
1958 {
1959 Q_D(KGlobalLedgerView);
1960
1961 // we cannot reconcile standard accounts
1962 if (!MyMoneyFile::instance()->isStandardAccount(d->m_currentAccount.id())) {
1963 emit selectByObject(d->m_currentAccount, eView::Intent::SynchronizeAccountInLedgersView);
1964 emit selectByObject(d->m_currentAccount, eView::Intent::StartEnteringOverdueScheduledTransactions);
1965 // asynchronous call to KScheduledView::slotEnterOverdueSchedules is made here
1966 // after that all activity should be continued in KGlobalLedgerView::slotContinueReconciliation()
1967 }
1968 }
1969
slotFinishReconciliation()1970 void KGlobalLedgerView::slotFinishReconciliation()
1971 {
1972 Q_D(KGlobalLedgerView);
1973 const auto file = MyMoneyFile::instance();
1974
1975 if (!d->m_reconciliationAccount.id().isEmpty()) {
1976 // retrieve list of all transactions that are not reconciled or cleared
1977 QList<QPair<MyMoneyTransaction, MyMoneySplit> > transactionList;
1978 MyMoneyTransactionFilter filter(d->m_reconciliationAccount.id());
1979 filter.addState((int)eMyMoney::TransactionFilter::State::Cleared);
1980 filter.addState((int)eMyMoney::TransactionFilter::State::NotReconciled);
1981 filter.setDateFilter(QDate(), d->m_endingBalanceDlg->statementDate());
1982 filter.setConsiderCategory(false);
1983 filter.setReportAllSplits(true);
1984 file->transactionList(transactionList, filter);
1985
1986 auto balance = MyMoneyFile::instance()->balance(d->m_reconciliationAccount.id(), d->m_endingBalanceDlg->statementDate());
1987 MyMoneyMoney actBalance, clearedBalance;
1988 actBalance = clearedBalance = balance;
1989
1990 // walk the list of transactions to figure out the balance(s)
1991 for (auto it = transactionList.constBegin(); it != transactionList.constEnd(); ++it) {
1992 if ((*it).second.reconcileFlag() == eMyMoney::Split::State::NotReconciled) {
1993 clearedBalance -= (*it).second.shares();
1994 }
1995 }
1996
1997 if (d->m_endingBalanceDlg->endingBalance() != clearedBalance) {
1998 auto message = i18n("You are about to finish the reconciliation of this account with a difference between your bank statement and the transactions marked as cleared.\n"
1999 "Are you sure you want to finish the reconciliation?");
2000 if (KMessageBox::questionYesNo(this, message, i18n("Confirm end of reconciliation"), KStandardGuiItem::yes(), KStandardGuiItem::no()) == KMessageBox::No)
2001 return;
2002 }
2003
2004 MyMoneyFileTransaction ft;
2005
2006 // refresh object
2007 d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id());
2008
2009 // Turn off reconciliation mode
2010 // Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney());
2011 // slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney());
2012 // d->m_myMoneyView->finishReconciliation(d->m_reconciliationAccount);
2013
2014 // only update the last statement balance here, if we haven't a newer one due
2015 // to download of online statements.
2016 if (d->m_reconciliationAccount.value("lastImportedTransactionDate").isEmpty()
2017 || QDate::fromString(d->m_reconciliationAccount.value("lastImportedTransactionDate"), Qt::ISODate) < d->m_endingBalanceDlg->statementDate()) {
2018 d->m_reconciliationAccount.setValue("lastStatementBalance", d->m_endingBalanceDlg->endingBalance().toString());
2019 // in case we override the last statement balance here, we have to make sure
2020 // that we don't show the online balance anymore, as it might be different
2021 d->m_reconciliationAccount.deletePair("lastImportedTransactionDate");
2022 }
2023 d->m_reconciliationAccount.setLastReconciliationDate(d->m_endingBalanceDlg->statementDate());
2024
2025 // keep a record of this reconciliation
2026 d->m_reconciliationAccount.addReconciliation(d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->endingBalance());
2027
2028 d->m_reconciliationAccount.deletePair("lastReconciledBalance");
2029 d->m_reconciliationAccount.deletePair("statementBalance");
2030 d->m_reconciliationAccount.deletePair("statementDate");
2031
2032 try {
2033 // update the account data
2034 file->modifyAccount(d->m_reconciliationAccount);
2035
2036 /*
2037 // collect the list of cleared splits for this account
2038 filter.clear();
2039 filter.addAccount(d->m_reconciliationAccount.id());
2040 filter.addState(eMyMoney::TransactionFilter::Cleared);
2041 filter.setConsiderCategory(false);
2042 filter.setReportAllSplits(true);
2043 file->transactionList(transactionList, filter);
2044 */
2045
2046 // walk the list of transactions/splits and mark the cleared ones as reconciled
2047
2048 for (auto it = transactionList.begin(); it != transactionList.end(); ++it) {
2049 MyMoneySplit sp = (*it).second;
2050 // skip the ones that are not marked cleared
2051 if (sp.reconcileFlag() != eMyMoney::Split::State::Cleared)
2052 continue;
2053
2054 // always retrieve a fresh copy of the transaction because we
2055 // might have changed it already with another split
2056 MyMoneyTransaction t = file->transaction((*it).first.id());
2057 sp.setReconcileFlag(eMyMoney::Split::State::Reconciled);
2058 sp.setReconcileDate(d->m_endingBalanceDlg->statementDate());
2059 t.modifySplit(sp);
2060
2061 // update the engine ...
2062 file->modifyTransaction(t);
2063
2064 // ... and the list
2065 (*it) = qMakePair(t, sp);
2066 }
2067 ft.commit();
2068
2069 // reload account data from engine as the data might have changed in the meantime
2070 d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id());
2071
2072 /**
2073 * This signal is emitted when an account has been successfully reconciled
2074 * and all transactions are updated in the engine. It can be used by plugins
2075 * to create reconciliation reports.
2076 *
2077 * @param account the account data
2078 * @param date the reconciliation date as provided through the dialog
2079 * @param startingBalance the starting balance as provided through the dialog
2080 * @param endingBalance the ending balance as provided through the dialog
2081 * @param transactionList reference to QList of QPair containing all
2082 * transaction/split pairs processed by the reconciliation.
2083 */
2084 emit selectByVariant(QVariantList {
2085 QVariant::fromValue(d->m_reconciliationAccount),
2086 QVariant::fromValue(d->m_endingBalanceDlg->statementDate()),
2087 QVariant::fromValue(d->m_endingBalanceDlg->previousBalance()),
2088 QVariant::fromValue(d->m_endingBalanceDlg->endingBalance()),
2089 QVariant::fromValue(transactionList)
2090 }, eView::Intent::AccountReconciled);
2091
2092 } catch (const MyMoneyException &) {
2093 qDebug("Unexpected exception when setting cleared to reconcile");
2094 }
2095 // Turn off reconciliation mode
2096 Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney());
2097 slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney());
2098 }
2099 // Turn off reconciliation mode
2100 d->m_reconciliationAccount = MyMoneyAccount();
2101 updateActions(d->m_currentAccount);
2102 updateLedgerActionsInternal();
2103 d->loadView();
2104 // slotUpdateActions();
2105 }
2106
slotPostponeReconciliation()2107 void KGlobalLedgerView::slotPostponeReconciliation()
2108 {
2109 Q_D(KGlobalLedgerView);
2110 MyMoneyFileTransaction ft;
2111 const auto file = MyMoneyFile::instance();
2112
2113 if (!d->m_reconciliationAccount.id().isEmpty()) {
2114 // refresh object
2115 d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id());
2116
2117 // Turn off reconciliation mode
2118 // Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney());
2119 // slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney());
2120 // d->m_myMoneyView->finishReconciliation(d->m_reconciliationAccount);
2121
2122 d->m_reconciliationAccount.setValue("lastReconciledBalance", d->m_endingBalanceDlg->previousBalance().toString());
2123 d->m_reconciliationAccount.setValue("statementBalance", d->m_endingBalanceDlg->endingBalance().toString());
2124 d->m_reconciliationAccount.setValue("statementDate", d->m_endingBalanceDlg->statementDate().toString(Qt::ISODate));
2125
2126 try {
2127 file->modifyAccount(d->m_reconciliationAccount);
2128 ft.commit();
2129 d->m_reconciliationAccount = MyMoneyAccount();
2130 updateActions(d->m_currentAccount);
2131 updateLedgerActionsInternal();
2132 // slotUpdateActions();
2133 } catch (const MyMoneyException &) {
2134 qDebug("Unexpected exception when setting last reconcile info into account");
2135 ft.rollback();
2136 d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id());
2137 }
2138 // Turn off reconciliation mode
2139 Models::instance()->accountsModel()->slotReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney());
2140 slotSetReconcileAccount(MyMoneyAccount(), QDate(), MyMoneyMoney());
2141 d->loadView();
2142 }
2143 }
2144
slotOpenAccount()2145 void KGlobalLedgerView::slotOpenAccount()
2146 {
2147 Q_D(KGlobalLedgerView);
2148 if (!MyMoneyFile::instance()->isStandardAccount(d->m_currentAccount.id()))
2149 slotLedgerSelected(d->m_currentAccount.id(), QString());
2150 }
2151
slotFindTransaction()2152 void KGlobalLedgerView::slotFindTransaction()
2153 {
2154 Q_D(KGlobalLedgerView);
2155 if (!d->m_searchDlg) {
2156 d->m_searchDlg = new KFindTransactionDlg(this);
2157 connect(d->m_searchDlg, &QObject::destroyed, this, &KGlobalLedgerView::slotCloseSearchDialog);
2158 connect(d->m_searchDlg, &KFindTransactionDlg::transactionSelected,
2159 this, &KGlobalLedgerView::slotLedgerSelected);
2160 }
2161 d->m_searchDlg->show();
2162 d->m_searchDlg->raise();
2163 d->m_searchDlg->activateWindow();
2164 }
2165
slotCloseSearchDialog()2166 void KGlobalLedgerView::slotCloseSearchDialog()
2167 {
2168 Q_D(KGlobalLedgerView);
2169 if (d->m_searchDlg)
2170 d->m_searchDlg->deleteLater();
2171 d->m_searchDlg = nullptr;
2172 }
2173
slotStatusMsg(const QString & txt)2174 void KGlobalLedgerView::slotStatusMsg(const QString& txt)
2175 {
2176 emit selectByVariant(QVariantList {QVariant(txt)}, eView::Intent::ReportProgressMessage);
2177 }
2178
slotStatusProgress(int cnt,int base)2179 void KGlobalLedgerView::slotStatusProgress(int cnt, int base)
2180 {
2181 emit selectByVariant(QVariantList {QVariant(cnt), QVariant(base)}, eView::Intent::ReportProgress);
2182 }
2183
slotTransactionsSelected(const KMyMoneyRegister::SelectedTransactions & list)2184 void KGlobalLedgerView::slotTransactionsSelected(const KMyMoneyRegister::SelectedTransactions& list)
2185 {
2186 updateLedgerActions(list);
2187 emit selectByVariant(QVariantList {QVariant::fromValue(list)}, eView::Intent::SelectRegisterTransactions);
2188 }
2189