File indexing completed on 2024-05-12 16:42:10
0001 /* 0002 SPDX-FileCopyrightText: 2002 Michael Edwardes <mte@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2002-2011 Thomas Baumgart <tbaumgart@kde.org> 0004 SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "ksplittransactiondlg.h" 0009 0010 // ---------------------------------------------------------------------------- 0011 // QT Includes 0012 0013 #include <QPushButton> 0014 #include <QLabel> 0015 #include <QTimer> 0016 #include <QRadioButton> 0017 #include <QList> 0018 #include <QIcon> 0019 #include <QDialogButtonBox> 0020 #include <QPointer> 0021 0022 // ---------------------------------------------------------------------------- 0023 // KDE Includes 0024 0025 #include <KConfig> 0026 #include <KMessageBox> 0027 #include <KSharedConfig> 0028 #include <KConfigGroup> 0029 #include <KLocalizedString> 0030 0031 // ---------------------------------------------------------------------------- 0032 // Project Includes 0033 0034 #include "ui_ksplittransactiondlg.h" 0035 #include "ui_ksplitcorrectiondlg.h" 0036 0037 #include "kmymoneyutils.h" 0038 #include "mymoneyfile.h" 0039 #include "kmymoneysplittable.h" 0040 #include "mymoneymoney.h" 0041 #include "mymoneyexception.h" 0042 #include "mymoneyaccount.h" 0043 #include "mymoneysecurity.h" 0044 #include "mymoneysplit.h" 0045 #include "mymoneytransaction.h" 0046 #include "icons/icons.h" 0047 0048 using namespace Icons; 0049 0050 KSplitCorrectionDlg::KSplitCorrectionDlg(QWidget *parent) : 0051 QDialog(parent), 0052 ui(new Ui::KSplitCorrectionDlg) 0053 { 0054 ui->setupUi(this); 0055 } 0056 0057 KSplitCorrectionDlg::~KSplitCorrectionDlg() 0058 { 0059 delete ui; 0060 } 0061 0062 class KSplitTransactionDlgPrivate 0063 { 0064 Q_DISABLE_COPY(KSplitTransactionDlgPrivate) 0065 Q_DECLARE_PUBLIC(KSplitTransactionDlg) 0066 0067 public: 0068 explicit KSplitTransactionDlgPrivate(KSplitTransactionDlg* qq) 0069 : q_ptr(qq) 0070 , ui(new Ui::KSplitTransactionDlg) 0071 , m_buttonBox(nullptr) 0072 , m_precision(2) 0073 , m_amountValid(false) 0074 , m_isDeposit(false) 0075 , m_readOnly(false) 0076 { 0077 } 0078 0079 ~KSplitTransactionDlgPrivate() 0080 { 0081 delete ui; 0082 } 0083 0084 void init(const MyMoneyTransaction& t, const QMap<QString, MyMoneyMoney>& priceInfo) 0085 { 0086 Q_Q(KSplitTransactionDlg); 0087 ui->setupUi(q); 0088 q->setModal(true); 0089 0090 auto okButton = ui->buttonBox->button(QDialogButtonBox::Ok); 0091 okButton->setDefault(true); 0092 okButton->setShortcut(Qt::CTRL | Qt::Key_Return); 0093 auto user1Button = new QPushButton; 0094 ui->buttonBox->addButton(user1Button, QDialogButtonBox::ActionRole); 0095 auto user2Button = new QPushButton; 0096 ui->buttonBox->addButton(user2Button, QDialogButtonBox::ActionRole); 0097 auto user3Button = new QPushButton; 0098 ui->buttonBox->addButton(user3Button, QDialogButtonBox::ActionRole); 0099 0100 //set custom buttons 0101 //clearAll button 0102 user1Button->setText(i18n("Clear &All")); 0103 user1Button->setToolTip(i18n("Clear all splits")); 0104 user1Button->setWhatsThis(i18n("Use this to clear all splits of this transaction")); 0105 user1Button->setIcon(Icons::get(Icon::EditClear)); 0106 0107 //clearZero button 0108 user2Button->setText(i18n("Clear &Zero")); 0109 user2Button->setToolTip(i18n("Removes all splits that have a value of zero")); 0110 user2Button->setIcon(Icons::get(Icon::EditClear)); 0111 0112 //merge button 0113 user3Button->setText(i18n("&Merge")); 0114 user3Button->setToolTip(i18n("Merges splits with the same category to one split")); 0115 user3Button->setWhatsThis(i18n("In case you have multiple split entries to the same category and you like to keep them as a single split")); 0116 0117 // make finish the default 0118 ui->buttonBox->button(QDialogButtonBox::Cancel)->setDefault(true); 0119 0120 // setup the focus 0121 ui->buttonBox->button(QDialogButtonBox::Cancel)->setFocusPolicy(Qt::NoFocus); 0122 okButton->setFocusPolicy(Qt::NoFocus); 0123 user1Button->setFocusPolicy(Qt::NoFocus); 0124 0125 // q->connect signals with slots 0126 q->connect(ui->transactionsTable, &KMyMoneySplitTable::transactionChanged, 0127 q, &KSplitTransactionDlg::slotSetTransaction); 0128 q->connect(ui->transactionsTable, &KMyMoneySplitTable::createCategory, q, &KSplitTransactionDlg::slotCreateCategory); 0129 q->connect(ui->transactionsTable, &KMyMoneySplitTable::createTag, q, &KSplitTransactionDlg::slotCreateTag); 0130 q->connect(ui->transactionsTable, &KMyMoneySplitTable::objectCreation, q, &KSplitTransactionDlg::objectCreation); 0131 0132 q->connect(ui->transactionsTable, &KMyMoneySplitTable::returnPressed, q, &KSplitTransactionDlg::accept); 0133 q->connect(ui->transactionsTable, &KMyMoneySplitTable::escapePressed, q, &KSplitTransactionDlg::reject); 0134 q->connect(ui->transactionsTable, &KMyMoneySplitTable::editStarted, q, &KSplitTransactionDlg::slotEditStarted); 0135 q->connect(ui->transactionsTable, &KMyMoneySplitTable::editFinished, q, &KSplitTransactionDlg::slotUpdateButtons); 0136 0137 q->connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, q, &KSplitTransactionDlg::reject); 0138 q->connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, q, &KSplitTransactionDlg::accept); 0139 q->connect(user1Button, &QAbstractButton::clicked, q, &KSplitTransactionDlg::slotClearAllSplits); 0140 q->connect(user3Button, &QAbstractButton::clicked, q, &KSplitTransactionDlg::slotMergeSplits); 0141 q->connect(user2Button, &QAbstractButton::clicked, q, &KSplitTransactionDlg::slotClearUnusedSplits); 0142 0143 // setup the precision 0144 try { 0145 auto currency = MyMoneyFile::instance()->currency(t.commodity()); 0146 m_precision = MyMoneyMoney::denomToPrec(m_account.fraction(currency)); 0147 } catch (const MyMoneyException &) { 0148 } 0149 0150 q->slotSetTransaction(t); 0151 0152 // pass on those vars 0153 ui->transactionsTable->setup(priceInfo, m_precision); 0154 0155 QSize size(q->width(), q->height()); 0156 KConfigGroup grp = KSharedConfig::openConfig()->group("SplitTransactionEditor"); 0157 size = grp.readEntry("Geometry", size); 0158 size.setHeight(size.height() - 1); 0159 q->resize(size.expandedTo(q->minimumSizeHint())); 0160 0161 // Trick: it seems, that the initial sizing of the dialog does 0162 // not work correctly. At least, the columns do not get displayed 0163 // correct. Reason: the return value of ui->transactionsTable->visibleWidth() 0164 // is incorrect. If the widget is visible, resizing works correctly. 0165 // So, we let the dialog show up and resize it then. It's not really 0166 // clean, but the only way I got the damned thing working. 0167 QTimer::singleShot(10, q, SLOT(initSize())); 0168 0169 } 0170 0171 /** 0172 * This method updates the display of the sums below the register 0173 */ 0174 void updateSums() 0175 { 0176 Q_Q(KSplitTransactionDlg); 0177 MyMoneyMoney splits(q->splitsValue()); 0178 0179 if (m_amountValid == false) { 0180 m_split.setValue(-splits); 0181 m_transaction.modifySplit(m_split); 0182 } 0183 0184 ui->splitSum->setText("<b>" + splits.formatMoney(QString(), m_precision) + ' '); 0185 ui->splitUnassigned->setText("<b>" + q->diffAmount().formatMoney(QString(), m_precision) + ' '); 0186 ui->transactionAmount->setText("<b>" + (-m_split.value()).formatMoney(QString(), m_precision) + ' '); 0187 } 0188 0189 KSplitTransactionDlg* q_ptr; 0190 Ui::KSplitTransactionDlg* ui; 0191 QDialogButtonBox* m_buttonBox; 0192 /** 0193 * This member keeps a copy of the current selected transaction 0194 */ 0195 MyMoneyTransaction m_transaction; 0196 0197 /** 0198 * This member keeps a copy of the currently selected account 0199 */ 0200 MyMoneyAccount m_account; 0201 0202 /** 0203 * This member keeps a copy of the currently selected split 0204 */ 0205 MyMoneySplit m_split; 0206 0207 /** 0208 * This member keeps the precision for the values 0209 */ 0210 int m_precision; 0211 0212 /** 0213 * flag that shows that the amount specified in the constructor 0214 * should be used as fix value (true) or if it can be changed (false) 0215 */ 0216 bool m_amountValid; 0217 0218 /** 0219 * This member keeps track if the current transaction is of type 0220 * deposit (true) or withdrawal (false). 0221 */ 0222 bool m_isDeposit; 0223 0224 /** 0225 * This member keeps track of the readonly mode 0226 */ 0227 bool m_readOnly; 0228 0229 /** 0230 * This member keeps the amount that will be assigned to all the 0231 * splits that are marked 'will be calculated'. 0232 */ 0233 MyMoneyMoney m_calculatedValue; 0234 }; 0235 0236 KSplitTransactionDlg::KSplitTransactionDlg(const MyMoneyTransaction& t, 0237 const MyMoneySplit& s, 0238 const MyMoneyAccount& acc, 0239 const bool amountValid, 0240 const bool deposit, 0241 const MyMoneyMoney& calculatedValue, 0242 const QMap<QString, MyMoneyMoney>& priceInfo, 0243 QWidget* parent) : 0244 QDialog(parent), 0245 d_ptr(new KSplitTransactionDlgPrivate(this)) 0246 { 0247 Q_D(KSplitTransactionDlg); 0248 d->ui->buttonBox = nullptr; 0249 d->m_account = acc; 0250 d->m_split = s; 0251 d->m_precision = 2; 0252 d->m_amountValid = amountValid; 0253 d->m_isDeposit = deposit; 0254 d->m_calculatedValue = calculatedValue; 0255 d->init(t, priceInfo); 0256 } 0257 0258 KSplitTransactionDlg::~KSplitTransactionDlg() 0259 { 0260 Q_D(KSplitTransactionDlg); 0261 auto grp = KSharedConfig::openConfig()->group("SplitTransactionEditor"); 0262 grp.writeEntry("Geometry", size()); 0263 delete d; 0264 } 0265 0266 int KSplitTransactionDlg::exec() 0267 { 0268 Q_D(KSplitTransactionDlg); 0269 // for deposits, we invert the sign of all splits. 0270 // don't forget to revert when we're done ;-) 0271 if (d->m_isDeposit) { 0272 for (auto i = 0; i < d->m_transaction.splits().count(); ++i) { 0273 MyMoneySplit split = d->m_transaction.splits()[i]; 0274 split.setValue(-split.value()); 0275 split.setShares(-split.shares()); 0276 d->m_transaction.modifySplit(split); 0277 } 0278 } 0279 0280 int rc; 0281 do { 0282 d->ui->transactionsTable->setFocus(); 0283 0284 // initialize the display 0285 d->ui->transactionsTable->setTransaction(d->m_transaction, d->m_split, d->m_account); 0286 d->updateSums(); 0287 0288 rc = QDialog::exec(); 0289 0290 if (rc == Accepted) { 0291 if (!diffAmount().isZero()) { 0292 QPointer<KSplitCorrectionDlg> corrDlg = new KSplitCorrectionDlg(this); 0293 connect(corrDlg->ui->buttonBox, &QDialogButtonBox::accepted, corrDlg.data(), &QDialog::accept); 0294 connect(corrDlg->ui->buttonBox, &QDialogButtonBox::rejected, corrDlg.data(), &QDialog::reject); 0295 corrDlg->ui->buttonGroup->setId(corrDlg->ui->continueBtn, 0); 0296 corrDlg->ui->buttonGroup->setId(corrDlg->ui->changeBtn, 1); 0297 corrDlg->ui->buttonGroup->setId(corrDlg->ui->distributeBtn, 2); 0298 corrDlg->ui->buttonGroup->setId(corrDlg->ui->leaveBtn, 3); 0299 0300 MyMoneySplit split = d->m_transaction.splits()[0]; 0301 QString total = (-split.value()).formatMoney(QString(), d->m_precision); 0302 QString sums = splitsValue().formatMoney(QString(), d->m_precision); 0303 QString diff = diffAmount().formatMoney(QString(), d->m_precision); 0304 0305 // now modify the text items of the dialog to contain the correct values 0306 QString q = i18n("The total amount of this transaction is %1 while " 0307 "the sum of the splits is %2. The remaining %3 are " 0308 "unassigned.", total, sums, diff); 0309 corrDlg->ui->explanation->setText(q); 0310 0311 q = i18n("Change &total amount of transaction to %1.", sums); 0312 corrDlg->ui->changeBtn->setText(q); 0313 0314 q = i18n("&Distribute difference of %1 among all splits.", diff); 0315 corrDlg->ui->distributeBtn->setText(q); 0316 // FIXME remove the following line once distribution among 0317 // all splits is implemented 0318 corrDlg->ui->distributeBtn->hide(); 0319 0320 0321 // if we have only two splits left, we don't allow leaving sth. unassigned. 0322 if (d->m_transaction.splitCount() < 3) { 0323 q = i18n("&Leave total amount of transaction at %1.", total); 0324 } else { 0325 q = i18n("&Leave %1 unassigned.", diff); 0326 } 0327 corrDlg->ui->leaveBtn->setText(q); 0328 0329 if ((rc = corrDlg->exec()) == Accepted) { 0330 switch (corrDlg->ui->buttonGroup->checkedId()) { 0331 case 0: // continue to edit 0332 rc = Rejected; 0333 break; 0334 0335 case 1: // modify total 0336 split.setValue(-splitsValue()); 0337 split.setShares(-splitsValue()); 0338 d->m_transaction.modifySplit(split); 0339 break; 0340 0341 case 2: // distribute difference 0342 qDebug("distribution of difference not yet supported in KSplitTransactionDlg::slotFinishClicked()"); 0343 break; 0344 0345 case 3: // leave unassigned 0346 break; 0347 } 0348 } 0349 delete corrDlg; 0350 } 0351 } else 0352 break; 0353 0354 } while (rc != Accepted); 0355 0356 // for deposits, we inverted the sign of all splits. 0357 // now we revert it back, so that things are left correct 0358 if (d->m_isDeposit) { 0359 for (auto i = 0; i < d->m_transaction.splits().count(); ++i) { 0360 auto split = d->m_transaction.splits()[i]; 0361 split.setValue(-split.value()); 0362 split.setShares(-split.shares()); 0363 d->m_transaction.modifySplit(split); 0364 } 0365 } 0366 0367 return rc; 0368 } 0369 0370 void KSplitTransactionDlg::initSize() 0371 { 0372 QDialog::resize(width(), height() + 1); 0373 } 0374 0375 void KSplitTransactionDlg::accept() 0376 { 0377 Q_D(KSplitTransactionDlg); 0378 d->ui->transactionsTable->slotCancelEdit(); 0379 QDialog::accept(); 0380 } 0381 0382 void KSplitTransactionDlg::reject() 0383 { 0384 Q_D(KSplitTransactionDlg); 0385 // cancel any edit activity in the split register 0386 d->ui->transactionsTable->slotCancelEdit(); 0387 QDialog::reject(); 0388 } 0389 0390 void KSplitTransactionDlg::slotClearAllSplits() 0391 { 0392 Q_D(KSplitTransactionDlg); 0393 int answer; 0394 answer = KMessageBox::warningContinueCancel(this, 0395 i18n("You are about to delete all splits of this transaction. " 0396 "Do you really want to continue?"), 0397 i18n("KMyMoney")); 0398 0399 if (answer == KMessageBox::Continue) { 0400 d->ui->transactionsTable->slotCancelEdit(); 0401 QList<MyMoneySplit> list = d->ui->transactionsTable->getSplits(d->m_transaction); 0402 QList<MyMoneySplit>::ConstIterator it; 0403 0404 // clear all but the one referencing the account 0405 for (it = list.constBegin(); it != list.constEnd(); ++it) { 0406 d->m_transaction.removeSplit(*it); 0407 } 0408 0409 d->ui->transactionsTable->setTransaction(d->m_transaction, d->m_split, d->m_account); 0410 slotSetTransaction(d->m_transaction); 0411 } 0412 } 0413 0414 void KSplitTransactionDlg::slotClearUnusedSplits() 0415 { 0416 Q_D(KSplitTransactionDlg); 0417 QList<MyMoneySplit> list = d->ui->transactionsTable->getSplits(d->m_transaction); 0418 QList<MyMoneySplit>::ConstIterator it; 0419 0420 try { 0421 // remove all splits that don't have a value assigned 0422 for (it = list.constBegin(); it != list.constEnd(); ++it) { 0423 if ((*it).shares().isZero()) { 0424 d->m_transaction.removeSplit(*it); 0425 } 0426 } 0427 0428 d->ui->transactionsTable->setTransaction(d->m_transaction, d->m_split, d->m_account); 0429 slotSetTransaction(d->m_transaction); 0430 } catch (const MyMoneyException &) { 0431 } 0432 } 0433 0434 void KSplitTransactionDlg::slotMergeSplits() 0435 { 0436 Q_D(KSplitTransactionDlg); 0437 0438 try { 0439 // collect all splits, merge them if needed and remove from transaction 0440 QList<MyMoneySplit> splits; 0441 foreach (const auto lsplit, d->ui->transactionsTable->getSplits(d->m_transaction)) { 0442 auto found = false; 0443 for (auto& split : splits) { 0444 if (split.accountId() == lsplit.accountId() 0445 && split.memo().isEmpty() && lsplit.memo().isEmpty()) { 0446 split.setShares(lsplit.shares() + split.shares()); 0447 split.setValue(lsplit.value() + split.value()); 0448 found = true; 0449 break; 0450 } 0451 } 0452 if (!found) 0453 splits << lsplit; 0454 0455 d->m_transaction.removeSplit(lsplit); 0456 } 0457 0458 // now add them back to the transaction 0459 for (auto& split : splits) { 0460 split.clearId(); 0461 d->m_transaction.addSplit(split); 0462 } 0463 0464 d->ui->transactionsTable->setTransaction(d->m_transaction, d->m_split, d->m_account); 0465 slotSetTransaction(d->m_transaction); 0466 } catch (const MyMoneyException &) { 0467 } 0468 } 0469 0470 void KSplitTransactionDlg::slotSetTransaction(const MyMoneyTransaction& t) 0471 { 0472 Q_D(KSplitTransactionDlg); 0473 d->m_transaction = t; 0474 slotUpdateButtons(); 0475 d->updateSums(); 0476 } 0477 0478 void KSplitTransactionDlg::slotUpdateButtons() 0479 { 0480 Q_D(KSplitTransactionDlg); 0481 QList<MyMoneySplit> list = d->ui->transactionsTable->getSplits(d->m_transaction); 0482 // check if we can merge splits or not, have zero splits or not 0483 QMap<QString, int> splits; 0484 bool haveZeroSplit = false; 0485 for (QList<MyMoneySplit>::const_iterator it = list.constBegin(); it != list.constEnd(); ++it) { 0486 splits[(*it).accountId()]++; 0487 if (((*it).id() != d->m_split.id()) && ((*it).shares().isZero())) 0488 haveZeroSplit = true; 0489 } 0490 QMap<QString, int>::const_iterator it_s; 0491 for (it_s = splits.constBegin(); it_s != splits.constEnd(); ++it_s) { 0492 if ((*it_s) > 1) 0493 break; 0494 } 0495 d->ui->buttonBox->buttons().at(4)->setEnabled(it_s != splits.constEnd()); 0496 d->ui->buttonBox->buttons().at(3)->setEnabled(haveZeroSplit); 0497 0498 // manage readonly mode 0499 if (d->m_readOnly) { 0500 d->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); 0501 d->ui->buttonBox->buttons().at(2)->setEnabled(false); // clear all splits 0502 d->ui->buttonBox->buttons().at(3)->setEnabled(false); // clear zero splits 0503 d->ui->buttonBox->buttons().at(4)->setEnabled(false); // merge splits 0504 } 0505 } 0506 0507 void KSplitTransactionDlg::slotEditStarted() 0508 { 0509 Q_D(KSplitTransactionDlg); 0510 d->ui->buttonBox->buttons().at(4)->setEnabled(false); 0511 d->ui->buttonBox->buttons().at(3)->setEnabled(false); 0512 } 0513 0514 MyMoneyMoney KSplitTransactionDlg::splitsValue() 0515 { 0516 Q_D(KSplitTransactionDlg); 0517 MyMoneyMoney splitsValue(d->m_calculatedValue); 0518 QList<MyMoneySplit> list = d->ui->transactionsTable->getSplits(d->m_transaction); 0519 QList<MyMoneySplit>::ConstIterator it; 0520 0521 // calculate the current sum of all split parts 0522 for (it = list.constBegin(); it != list.constEnd(); ++it) { 0523 if ((*it).value() != MyMoneyMoney::autoCalc) 0524 splitsValue += (*it).value(); 0525 } 0526 0527 return splitsValue; 0528 } 0529 0530 MyMoneyTransaction KSplitTransactionDlg::transaction() const 0531 { 0532 Q_D(const KSplitTransactionDlg); 0533 return d->m_transaction; 0534 } 0535 0536 MyMoneyMoney KSplitTransactionDlg::diffAmount() 0537 { 0538 Q_D(KSplitTransactionDlg); 0539 MyMoneyMoney diff; 0540 0541 // if there is an amount specified in the transaction, we need to calculate the 0542 // difference, otherwise we display the difference as 0 and display the same sum. 0543 if (d->m_amountValid) { 0544 MyMoneySplit split = d->m_transaction.splits()[0]; 0545 0546 diff = -(splitsValue() + split.value()); 0547 } 0548 return diff; 0549 } 0550 0551 void KSplitTransactionDlg::slotCreateCategory(const QString& name, QString& id) 0552 { 0553 Q_D(KSplitTransactionDlg); 0554 MyMoneyAccount acc, parent; 0555 acc.setName(name); 0556 0557 if (d->m_isDeposit) 0558 parent = MyMoneyFile::instance()->income(); 0559 else 0560 parent = MyMoneyFile::instance()->expense(); 0561 0562 // TODO extract possible first part of a hierarchy and check if it is one 0563 // of our top categories. If so, remove it and select the parent 0564 // according to this information. 0565 0566 emit createCategory(acc, parent); 0567 0568 // return id 0569 id = acc.id(); 0570 } 0571 0572 void KSplitTransactionDlg::slotCreateTag(const QString& txt, QString& id) 0573 { 0574 KMyMoneyUtils::newTag(txt, id); 0575 emit createTag(txt, id); 0576 } 0577 0578 void KSplitTransactionDlg::setReadOnlyMode(bool readOnly) 0579 { 0580 Q_D(KSplitTransactionDlg); 0581 d->m_readOnly = readOnly; 0582 d->ui->transactionsTable->setReadOnlyMode(readOnly); 0583 slotUpdateButtons(); 0584 }