File indexing completed on 2024-05-12 16:43:55

0001 /*
0002     SPDX-FileCopyrightText: 2015 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "splitdialog.h"
0007 
0008 // ----------------------------------------------------------------------------
0009 // QT Includes
0010 
0011 #include <QDebug>
0012 #include <QHeaderView>
0013 #include <QPointer>
0014 
0015 // ----------------------------------------------------------------------------
0016 // KDE Includes
0017 
0018 #include <KLocalizedString>
0019 
0020 // ----------------------------------------------------------------------------
0021 // Project Includes
0022 
0023 #include "ui_splitdialog.h"
0024 #include "mymoneyaccount.h"
0025 #include "splitdelegate.h"
0026 #include "newtransactioneditor.h"
0027 #include "splitadjustdialog.h"
0028 #include "modelenums.h"
0029 #include "icons/icons.h"
0030 
0031 using namespace Icons;
0032 
0033 class SplitDialog::Private
0034 {
0035 public:
0036     Private(SplitDialog* p)
0037         : parent(p)
0038         , ui(new Ui_SplitDialog)
0039         , splitDelegate(nullptr)
0040         , transactionEditor(nullptr)
0041     {
0042     }
0043 
0044     ~Private()
0045     {
0046         delete ui;
0047     }
0048 
0049     void deleteSplits(QModelIndexList indexList);
0050 
0051     SplitDialog*                parent;
0052     Ui_SplitDialog*             ui;
0053     SplitDelegate*              splitDelegate;
0054 
0055     /**
0056      * The account in which this split editor was opened
0057      */
0058     MyMoneyAccount              account;
0059 
0060     /**
0061      * The parent transaction editor which opened the split editor
0062      */
0063     NewTransactionEditor*       transactionEditor;
0064 
0065     MyMoneyMoney                transactionTotal;
0066     MyMoneyMoney                splitsTotal;
0067 };
0068 
0069 static const int SumRow = 0;
0070 static const int DiffRow = 1;
0071 static const int AmountRow = 2;
0072 static const int HeaderCol = 0;
0073 static const int ValueCol = 1;
0074 
0075 
0076 void SplitDialog::Private::deleteSplits(QModelIndexList indexList)
0077 {
0078     if (indexList.isEmpty()) {
0079         return;
0080     }
0081 
0082     // remove from the end so that the row information stays
0083     // consistent and is not changed due to index changes
0084     QMap<int, int> sortedList;
0085     foreach(auto index, indexList) {
0086         sortedList[index.row()] = index.row();
0087     }
0088 
0089     QMap<int, int>::const_iterator it = sortedList.constEnd();
0090     do {
0091         --it;
0092         ui->splitView->model()->removeRow(*it);
0093     } while(it != sortedList.constBegin());
0094 }
0095 
0096 
0097 SplitDialog::SplitDialog(const MyMoneyAccount& account, const MyMoneyMoney& amount, NewTransactionEditor* parent, Qt::WindowFlags f)
0098     : QDialog(parent, f)
0099     , d(new Private(this))
0100 {
0101     d->transactionEditor = parent;
0102     d->account = account;
0103     d->transactionTotal = amount;
0104     d->ui->setupUi(this);
0105 
0106     d->ui->splitView->setColumnHidden((int)eLedgerModel::Column::Date, true);
0107     d->ui->splitView->setColumnHidden((int)eLedgerModel::Column::Number, true);
0108     d->ui->splitView->setColumnHidden((int)eLedgerModel::Column::Security, true);
0109     d->ui->splitView->setColumnHidden((int)eLedgerModel::Column::Reconciliation, true);
0110     d->ui->splitView->setColumnHidden((int)eLedgerModel::Column::Payment, false);
0111     d->ui->splitView->setColumnHidden((int)eLedgerModel::Column::Deposit, false);
0112     d->ui->splitView->setColumnHidden((int)eLedgerModel::Column::Quantity, true);
0113     d->ui->splitView->setColumnHidden((int)eLedgerModel::Column::Price, true);
0114     d->ui->splitView->setColumnHidden((int)eLedgerModel::Column::Amount, true);
0115     d->ui->splitView->setColumnHidden((int)eLedgerModel::Column::Value, true);
0116     d->ui->splitView->setColumnHidden((int)eLedgerModel::Column::Balance, true);
0117     d->ui->splitView->setSelectionMode(QAbstractItemView::ExtendedSelection);
0118     d->ui->splitView->setSelectionBehavior(QAbstractItemView::SelectRows);
0119 
0120     // setup the item delegate
0121     d->splitDelegate = new SplitDelegate(d->ui->splitView);
0122     d->ui->splitView->setItemDelegate(d->splitDelegate);
0123 
0124     d->ui->okButton->setIcon(Icons::get(Icon::DialogOK));
0125     d->ui->cancelButton->setIcon(Icons::get(Icon::DialogCancel));
0126 
0127     // setup some connections
0128     connect(d->ui->splitView, &LedgerView::aboutToStartEdit, this, &SplitDialog::disableButtons);
0129     connect(d->ui->splitView, &LedgerView::aboutToFinishEdit, this, &SplitDialog::enableButtons);
0130 
0131     connect(d->ui->deleteAllButton, &QAbstractButton::pressed, this, &SplitDialog::deleteAllSplits);
0132     connect(d->ui->deleteButton, &QAbstractButton::pressed, this, &SplitDialog::deleteSelectedSplits);
0133     connect(d->ui->deleteZeroButton, &QAbstractButton::pressed, this, &SplitDialog::deleteZeroSplits);
0134     connect(d->ui->mergeButton, &QAbstractButton::pressed, this, &SplitDialog::mergeSplits);
0135     connect(d->ui->newSplitButton, &QAbstractButton::pressed, this, &SplitDialog::newSplit);
0136 
0137     // finish polishing the widgets
0138     QMetaObject::invokeMethod(this, "adjustSummary", Qt::QueuedConnection);
0139 }
0140 
0141 SplitDialog::~SplitDialog()
0142 {
0143 }
0144 
0145 int SplitDialog::exec()
0146 {
0147     if(!d->ui->splitView->model()) {
0148         qWarning() << "SplitDialog::exec() executed without a model. Use setModel() before calling exec().";
0149         return QDialog::Rejected;
0150     }
0151     return QDialog::exec();
0152 }
0153 
0154 void SplitDialog::accept()
0155 {
0156     adjustSummary();
0157     bool accept = true;
0158     if(d->transactionTotal != d->splitsTotal) {
0159         QPointer<SplitAdjustDialog> dlg = new SplitAdjustDialog(this);
0160         dlg->setValues(d->ui->summaryView->item(AmountRow, ValueCol)->data(Qt::DisplayRole).toString(),
0161                        d->ui->summaryView->item(SumRow, ValueCol)->data(Qt::DisplayRole).toString(),
0162                        d->ui->summaryView->item(DiffRow, ValueCol)->data(Qt::DisplayRole).toString(),
0163                        d->ui->splitView->model()->rowCount());
0164         accept = false;
0165         if(dlg->exec() == QDialog::Accepted && dlg) {
0166             switch(dlg->selectedOption()) {
0167             case SplitAdjustDialog::SplitAdjustContinue:
0168                 break;
0169             case SplitAdjustDialog::SplitAdjustChange:
0170                 d->transactionTotal = d->splitsTotal;
0171                 accept = true;
0172                 break;
0173             case SplitAdjustDialog::SplitAdjustDistribute:
0174                 qWarning() << "SplitDialog::accept needs to implement the case SplitAdjustDialog::SplitAdjustDistribute";
0175                 accept = true;
0176                 break;
0177             case SplitAdjustDialog::SplitAdjustLeaveAsIs:
0178                 accept = true;
0179                 break;
0180             }
0181         }
0182         delete dlg;
0183         updateButtonState();
0184     }
0185     if(accept)
0186         QDialog::accept();
0187 }
0188 
0189 void SplitDialog::enableButtons()
0190 {
0191     d->ui->buttonContainer->setEnabled(true);
0192 }
0193 
0194 void SplitDialog::disableButtons()
0195 {
0196     d->ui->buttonContainer->setEnabled(false);
0197 }
0198 
0199 void SplitDialog::setModel(QAbstractItemModel* model)
0200 {
0201     d->ui->splitView->setModel(model);
0202 
0203     if(model->rowCount() > 0) {
0204         QModelIndex index = model->index(0, 0);
0205         d->ui->splitView->setCurrentIndex(index);
0206     }
0207 
0208     adjustSummary();
0209 
0210     // force an update of the summary if data changes in the model
0211     connect(model, &QAbstractItemModel::dataChanged, this, &SplitDialog::adjustSummary);
0212     connect(d->ui->splitView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &SplitDialog::selectionChanged);
0213 }
0214 
0215 void SplitDialog::adjustSummary()
0216 {
0217     d->splitsTotal = 0;
0218     for(int row = 0; row < d->ui->splitView->model()->rowCount(); ++row) {
0219         QModelIndex index = d->ui->splitView->model()->index(row, 0);
0220         if(index.isValid()) {
0221             d->splitsTotal += d->ui->splitView->model()->data(index, (int)eLedgerModel::Role::SplitValue).value<MyMoneyMoney>();
0222         }
0223     }
0224     QString formattedValue = d->splitsTotal.formatMoney(d->account.fraction());
0225     d->ui->summaryView->item(SumRow, ValueCol)->setData(Qt::DisplayRole, formattedValue);
0226 
0227     if(d->transactionEditor) {
0228         d->transactionTotal = d->transactionEditor->transactionAmount();
0229         formattedValue = d->transactionTotal.formatMoney(d->account.fraction());
0230         d->ui->summaryView->item(AmountRow, ValueCol)->setData(Qt::DisplayRole, formattedValue);
0231         if((d->transactionTotal - d->splitsTotal).isNegative()) {
0232             d->ui->summaryView->item(DiffRow, HeaderCol)->setData(Qt::DisplayRole, i18nc("Split editor summary", "Assigned too much"));
0233         } else {
0234             d->ui->summaryView->item(DiffRow, HeaderCol)->setData(Qt::DisplayRole, i18nc("Split editor summary", "Unassigned"));
0235         }
0236         formattedValue = (d->transactionTotal - d->splitsTotal).abs().formatMoney(d->account.fraction());
0237         d->ui->summaryView->item(DiffRow, ValueCol)->setData(Qt::DisplayRole, formattedValue);
0238     } else {
0239         d->ui->summaryView->item(SumRow, ValueCol)->setData(Qt::DisplayRole, QString());
0240         d->ui->summaryView->item(AmountRow, ValueCol)->setData(Qt::DisplayRole, QString());
0241     }
0242 
0243     adjustSummaryWidth();
0244     updateButtonState();
0245 }
0246 
0247 void SplitDialog::resizeEvent(QResizeEvent* ev)
0248 {
0249     QDialog::resizeEvent(ev);
0250     adjustSummaryWidth();
0251 }
0252 
0253 void SplitDialog::adjustSummaryWidth()
0254 {
0255     d->ui->summaryView->resizeColumnToContents(1);
0256     d->ui->summaryView->horizontalHeader()->resizeSection(0, d->ui->summaryView->width() - d->ui->summaryView->horizontalHeader()->sectionSize(1) - 10);
0257 }
0258 
0259 void SplitDialog::newSplit()
0260 {
0261     // creating a new split is easy, because we simply
0262     // need to select the last entry in the view. If we
0263     // are on this row already with the editor closed things
0264     // are a bit more complicated.
0265     QModelIndex index = d->ui->splitView->currentIndex();
0266     if(index.isValid()) {
0267         int row = index.row();
0268         if(row != d->ui->splitView->model()->rowCount()-1) {
0269             d->ui->splitView->selectRow(d->ui->splitView->model()->rowCount()-1);
0270         } else {
0271             d->ui->splitView->edit(index);
0272         }
0273     } else {
0274         d->ui->splitView->selectRow(d->ui->splitView->model()->rowCount()-1);
0275     }
0276 }
0277 
0278 MyMoneyMoney SplitDialog::transactionAmount() const
0279 {
0280     return d->transactionTotal;
0281 }
0282 
0283 void SplitDialog::selectionChanged()
0284 {
0285     updateButtonState();
0286 }
0287 
0288 void SplitDialog::updateButtonState()
0289 {
0290     d->ui->deleteButton->setEnabled(false);
0291     d->ui->deleteAllButton->setEnabled(false);
0292     d->ui->mergeButton->setEnabled(false);
0293     d->ui->deleteZeroButton->setEnabled(false);
0294 
0295     if(d->ui->splitView->selectionModel()->selectedRows().count() >= 1) {
0296         d->ui->deleteButton->setEnabled(true);
0297     }
0298 
0299     if(d->ui->splitView->model()->rowCount() > 2) {
0300         d->ui->deleteAllButton->setEnabled(true);
0301     }
0302 
0303     QAbstractItemModel* model = d->ui->splitView->model();
0304     QSet<QString> accountIDs;
0305     for(int row = 0; row < model->rowCount(); ++row) {
0306         const QModelIndex index = model->index(row,0);
0307         // don't check the empty line at the end
0308         if(model->data(index, (int)eLedgerModel::Role::SplitId).toString().isEmpty())
0309             continue;
0310 
0311         const QString accountID = model->data(index, (int)eLedgerModel::Role::AccountId).toString();
0312         const MyMoneyMoney value = model->data(index, (int)eLedgerModel::Role::SplitShares).value<MyMoneyMoney>();
0313         if(accountIDs.contains(accountID)) {
0314             d->ui->mergeButton->setEnabled(true);
0315         }
0316         if(value.isZero()) {
0317             d->ui->deleteZeroButton->setEnabled(true);
0318         }
0319     }
0320 }
0321 
0322 void SplitDialog::deleteSelectedSplits()
0323 {
0324     d->deleteSplits(d->ui->splitView->selectionModel()->selectedRows());
0325     adjustSummary();
0326 }
0327 
0328 void SplitDialog::deleteAllSplits()
0329 {
0330     QAbstractItemModel* model = d->ui->splitView->model();
0331     QModelIndexList list = model->match(model->index(0,0),
0332                                         (int)eLedgerModel::Role::SplitId,
0333                                         QLatin1String(".+"),
0334                                         -1,
0335                                         Qt::MatchRegExp
0336                                        );
0337     d->deleteSplits(list);
0338     adjustSummary();
0339 }
0340 
0341 void SplitDialog::deleteZeroSplits()
0342 {
0343     QAbstractItemModel* model = d->ui->splitView->model();
0344     QModelIndexList list = model->match(model->index(0,0),
0345                                         (int)eLedgerModel::Role::SplitId,
0346                                         QLatin1String(".+"),
0347                                         -1,
0348                                         Qt::MatchRegExp
0349                                        );
0350     for(int idx = 0; idx < list.count();) {
0351         QModelIndex index = list.at(idx);
0352         if(!model->data(index, (int)eLedgerModel::Role::SplitShares).value<MyMoneyMoney>().isZero()) {
0353             list.removeAt(idx);
0354         } else {
0355             ++idx;
0356         }
0357     }
0358     d->deleteSplits(list);
0359     adjustSummary();
0360 }
0361 
0362 void SplitDialog::mergeSplits()
0363 {
0364     qDebug() << "Merge splits not yet implemented.";
0365     adjustSummary();
0366 }