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

0001 /*
0002     SPDX-FileCopyrightText: 2016 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "splitdelegate.h"
0007 
0008 // ----------------------------------------------------------------------------
0009 // QT Includes
0010 
0011 #include <QApplication>
0012 #include <QScrollBar>
0013 #include <QPainter>
0014 #include <QDebug>
0015 
0016 // ----------------------------------------------------------------------------
0017 // KDE Includes
0018 
0019 // ----------------------------------------------------------------------------
0020 // Project Includes
0021 
0022 #include "ledgerview.h"
0023 #include "models.h"
0024 #include "accountsmodel.h"
0025 #include "ledgermodel.h"
0026 #include "splitmodel.h"
0027 #include "newspliteditor.h"
0028 #include "mymoneyaccount.h"
0029 #include "modelenums.h"
0030 
0031 using namespace eLedgerModel;
0032 
0033 QColor SplitDelegate::m_erroneousColor = QColor(Qt::red);
0034 QColor SplitDelegate::m_importedColor = QColor(Qt::yellow);
0035 
0036 class SplitDelegate::Private
0037 {
0038 public:
0039     Private()
0040         : m_editor(0)
0041         , m_editorRow(-1)
0042         , m_showValuesInverted(false)
0043     {}
0044 
0045     NewSplitEditor*               m_editor;
0046     int                           m_editorRow;
0047     bool                          m_showValuesInverted;
0048 };
0049 
0050 
0051 SplitDelegate::SplitDelegate(QObject* parent)
0052     : QStyledItemDelegate(parent)
0053     , d(new Private)
0054 {
0055 }
0056 
0057 SplitDelegate::~SplitDelegate()
0058 {
0059     delete d;
0060 }
0061 
0062 void SplitDelegate::setErroneousColor(const QColor& color)
0063 {
0064     m_erroneousColor = color;
0065 }
0066 
0067 QWidget* SplitDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
0068 {
0069     Q_UNUSED(option);
0070 
0071     if(index.isValid()) {
0072         Q_ASSERT(parent);
0073         LedgerView* view = qobject_cast<LedgerView*>(parent->parentWidget());
0074         Q_ASSERT(view != 0);
0075 
0076         if(view->selectionModel()->selectedRows().count() > 1) {
0077             qDebug() << "Editing multiple splits at once is not yet supported";
0078 
0079             /**
0080              * @todo replace the following three lines with the creation of a special
0081              * editor that can handle multiple splits at once or show a message to the user
0082              * that this is not possible
0083              */
0084             d->m_editor = 0;
0085             SplitDelegate* that = const_cast<SplitDelegate*>(this);
0086             emit that->closeEditor(d->m_editor, NoHint);
0087 
0088         } else {
0089             d->m_editor = new NewSplitEditor(parent, view->accountId());
0090         }
0091 
0092         if(d->m_editor) {
0093             d->m_editorRow = index.row();
0094             connect(d->m_editor, SIGNAL(done()), this, SLOT(endEdit()));
0095             emit sizeHintChanged(index);
0096         }
0097 
0098     } else {
0099         qFatal("SplitDelegate::createEditor(): we should never end up here");
0100     }
0101     return d->m_editor;
0102 }
0103 
0104 int SplitDelegate::editorRow() const
0105 {
0106     return d->m_editorRow;
0107 }
0108 
0109 void SplitDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
0110 {
0111     QStyleOptionViewItem opt = option;
0112     initStyleOption(&opt, index);
0113 
0114     // never change the background of the cell the mouse is hovering over
0115     opt.state &= ~QStyle::State_MouseOver;
0116 
0117     // show the focus only on the detail column
0118     opt.state &= ~QStyle::State_HasFocus;
0119     if(index.column() == (int)Column::Detail) {
0120         QAbstractItemView* view = qobject_cast< QAbstractItemView* >(parent());
0121         if(view) {
0122             if(view->currentIndex().row() == index.row()) {
0123                 opt.state |= QStyle::State_HasFocus;
0124             }
0125         }
0126     }
0127 
0128     painter->save();
0129 
0130     // Background
0131     auto bgOpt = opt;
0132     // if selected, always show as active, so that the
0133     // background does not change when the editor is shown
0134     if (opt.state & QStyle::State_Selected) {
0135         bgOpt.state |= QStyle::State_Active;
0136     }
0137     QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
0138     style->drawPrimitive(QStyle::PE_PanelItemViewItem, &bgOpt, painter, opt.widget);
0139 
0140     // Do not paint text if the edit widget is shown
0141     const LedgerView *view = qobject_cast<const LedgerView *>(opt.widget);
0142     if (view && view->indexWidget(index)) {
0143         painter->restore();
0144         return;
0145     }
0146 
0147     const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
0148     const QRect textArea = QRect(opt.rect.x() + margin, opt.rect.y() + margin, opt.rect.width() - 2 * margin, opt.rect.height() - 2 * margin);
0149 
0150     QStringList lines;
0151     if(index.column() == (int)Column::Detail) {
0152         lines << index.model()->data(index, (int)Role::Account).toString();
0153         lines << index.model()->data(index, (int)Role::SingleLineMemo).toString();
0154         lines.removeAll(QString());
0155     }
0156 
0157     // draw the text items
0158     if(!opt.text.isEmpty() || !lines.isEmpty()) {
0159 
0160         QPalette::ColorGroup cg = (opt.state & QStyle::State_Enabled)
0161                                   ? QPalette::Normal : QPalette::Disabled;
0162 
0163         if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) {
0164             cg = QPalette::Inactive;
0165         }
0166         if (opt.state & QStyle::State_Selected) {
0167             painter->setPen(opt.palette.color(cg, QPalette::HighlightedText));
0168         } else {
0169             painter->setPen(opt.palette.color(cg, QPalette::Text));
0170         }
0171         if (opt.state & QStyle::State_Editing) {
0172             painter->setPen(opt.palette.color(cg, QPalette::Text));
0173             painter->drawRect(textArea.adjusted(0, 0, -1, -1));
0174         }
0175 
0176         // collect data for the various columns
0177         if(index.column() == (int)Column::Detail) {
0178             for(int i = 0; i < lines.count(); ++i) {
0179                 painter->drawText(textArea.adjusted(0, (opt.fontMetrics.lineSpacing() + 5) * i, 0, 0), opt.displayAlignment, lines[i]);
0180             }
0181 
0182         } else {
0183             painter->drawText(textArea, opt.displayAlignment, opt.text);
0184         }
0185     }
0186 
0187     // draw the focus rect
0188     if(opt.state & QStyle::State_HasFocus) {
0189         QStyleOptionFocusRect o;
0190         o.QStyleOption::operator=(opt);
0191         o.rect = style->proxy()->subElementRect(QStyle::SE_ItemViewItemFocusRect, &opt, opt.widget);
0192         o.state |= QStyle::State_KeyboardFocusChange;
0193         o.state |= QStyle::State_Item;
0194 
0195         QPalette::ColorGroup cg = (opt.state & QStyle::State_Enabled)
0196                                   ? QPalette::Normal : QPalette::Disabled;
0197         o.backgroundColor = opt.palette.color(cg, (opt.state & QStyle::State_Selected)
0198                                               ? QPalette::Highlight : QPalette::Window);
0199         style->proxy()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, opt.widget);
0200     }
0201 
0202 #if 0
0203     if((index.column() == LedgerModel::DetailColumn)
0204             && erroneous) {
0205         QPixmap attention;
0206         attention.loadFromData(attentionSign, sizeof(attentionSign), 0, 0);
0207         style->proxy()->drawItemPixmap(painter, option.rect, Qt::AlignRight | Qt::AlignTop, attention);
0208     }
0209 #endif
0210 
0211     painter->restore();
0212 #if 0
0213     const QHeaderView* horizontalHeader = view->horizontalHeader();
0214     const QHeaderView* verticalHeader = view->verticalHeader();
0215     const QWidget* viewport = view->viewport();
0216     const bool showGrid = view->showGrid() && !view->indexWidget(index);
0217     const int gridSize = showGrid ? 1 : 0;
0218     const int gridHint = style->styleHint(QStyle::SH_Table_GridLineColor, &option, view);
0219     const QColor gridColor = static_cast<QRgb>(gridHint);
0220     const QPen gridPen = QPen(gridColor, 0, view->gridStyle());
0221     const bool rightToLeft = view->isRightToLeft();
0222     const int viewportOffset = horizontalHeader->offset();
0223 
0224 
0225     // QStyledItemDelegate::paint(painter, opt, index);
0226 
0227     if(!horizontalHeader->isSectionHidden(LedgerModel::DateColumn)) {
0228         QDate postDate = index.data(LedgerModel::PostDateRole).toDate();
0229         if(postDate.isValid()) {
0230             int ofs = horizontalHeader->sectionViewportPosition(LedgerModel::DateColumn) + viewportOffset;
0231             QRect oRect = opt.rect;
0232             opt.displayAlignment = Qt::AlignLeft | Qt::AlignTop;
0233             opt.rect.setLeft(opt.rect.left()+ofs);
0234             opt.rect.setTop(opt.rect.top()+margin);
0235             opt.rect.setWidth(horizontalHeader->sectionSize(LedgerModel::DateColumn));
0236             opt.text = KGlobal::locale()->formatDate(postDate, QLocale::ShortFormat);
0237             style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget);
0238             opt.rect = oRect;
0239         }
0240     }
0241 
0242     if(!horizontalHeader->isSectionHidden(LedgerModel::DetailColumn)) {
0243         QString payee = index.data(LedgerModel::PayeeRole).toString();
0244         QString counterAccount = index.data(LedgerModel::CounterAccountRole).toString();
0245         QString txt = payee;
0246         if(payee.length() > 0)
0247             txt += '\n';
0248         txt += counterAccount;
0249         int ofs = horizontalHeader->sectionViewportPosition(LedgerModel::DetailColumn) + viewportOffset;
0250         QRect oRect = opt.rect;
0251         opt.displayAlignment = Qt::AlignLeft | Qt::AlignTop;
0252         opt.rect.setLeft(opt.rect.left()+ofs);
0253         opt.rect.setTop(opt.rect.top()+margin);
0254         opt.rect.setWidth(horizontalHeader->sectionSize(LedgerModel::DetailColumn));
0255         opt.text = txt;
0256         style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget);
0257         opt.rect = oRect;
0258 
0259     }
0260 #if 0
0261     opt.features |= QStyleOptionViewItemV2::HasDisplay;
0262     QString txt = QString("%1").arg(index.isValid() ? "true" : "false");
0263     if(index.isValid())
0264         txt += QString(" %1 - %2").arg(index.row()).arg(view->verticalHeader()->sectionViewportPosition(index.row()));
0265     opt.text = displayText(txt, opt.locale);
0266 
0267     style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget);
0268 #endif
0269 
0270     // paint grid
0271     if(showGrid) {
0272         painter->save();
0273         QPen old = painter->pen();
0274         painter->setPen(gridPen);
0275 
0276         // qDebug() << "Paint grid for" << index.row() << "in" << opt.rect;
0277         for(int i=0; i < horizontalHeader->count(); ++i) {
0278             if(!horizontalHeader->isSectionHidden(i)) {
0279                 int ofs = horizontalHeader->sectionViewportPosition(i) + viewportOffset;
0280                 if(!rightToLeft) {
0281                     ofs += horizontalHeader->sectionSize(i) - gridSize;
0282                 }
0283                 if(ofs-viewportOffset < viewport->width()) {
0284                     // I have no idea, why I need to paint the grid for the selected row and the one below
0285                     // but it was the only way to get this working correctly. Otherwise the grid was missing
0286                     // while moving the mouse over the view from bottom to top.
0287                     painter->drawLine(opt.rect.x()+ofs, opt.rect.y(), opt.rect.x()+ofs, opt.rect.height());
0288                     painter->drawLine(opt.rect.x()+ofs, opt.rect.y()+verticalHeader->sectionSize(index.row()), opt.rect.x()+ofs, opt.rect.height());
0289                 }
0290             }
0291         }
0292         painter->setPen(old);
0293         painter->restore();
0294     }
0295 #endif
0296 }
0297 
0298 QSize SplitDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
0299 {
0300     bool fullDisplay = false;
0301     LedgerView* view = qobject_cast<LedgerView*>(parent());
0302     if(view) {
0303         QModelIndex currentIndex = view->currentIndex();
0304         if(currentIndex.isValid()) {
0305             QString currentId = currentIndex.model()->data(currentIndex, (int)Role::TransactionSplitId).toString();
0306             QString myId = index.model()->data(index, (int)Role::TransactionSplitId).toString();
0307             fullDisplay = (currentId == myId);
0308         }
0309     }
0310 
0311     QSize size;
0312     QStyleOptionViewItem opt = option;
0313     if(index.isValid()) {
0314         // check if we are showing the edit widget
0315         const QAbstractItemView *viewWidget = qobject_cast<const QAbstractItemView *>(opt.widget);
0316         if (viewWidget) {
0317             QModelIndex editIndex = viewWidget->model()->index(index.row(), 0);
0318             if(editIndex.isValid()) {
0319                 QWidget* editor = viewWidget->indexWidget(editIndex);
0320                 if(editor) {
0321                     size = editor->minimumSizeHint();
0322                     return size;
0323                 }
0324             }
0325         }
0326     }
0327 
0328     int rows = 1;
0329     if(fullDisplay) {
0330         initStyleOption(&opt, index);
0331         auto payee = index.data((int)Role::PayeeName).toString();
0332         auto account = index.data((int)Role::Account).toString();
0333         auto memo = index.data((int)Role::SingleLineMemo).toString();
0334 
0335         rows = (payee.length() > 0 ? 1 : 0) + (account.length() > 0 ? 1 : 0) + (memo.length() > 0 ? 1 : 0);
0336         // make sure we show at least one row
0337         if(!rows) {
0338             rows = 1;
0339         }
0340     }
0341 
0342     // leave a 5 pixel margin for each row
0343     size = QSize(100, (opt.fontMetrics.lineSpacing() + 5) * rows);
0344     return size;
0345 }
0346 
0347 void SplitDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
0348 {
0349     Q_UNUSED(index);
0350     QStyleOptionViewItem opt = option;
0351     int ofs = 8;
0352     const LedgerView* view = qobject_cast<const LedgerView*>(opt.widget);
0353     if(view) {
0354         if(view->verticalScrollBar()->isVisible()) {
0355             ofs += view->verticalScrollBar()->width();
0356         }
0357     }
0358 
0359     QRect r(opt.rect);
0360     r.setWidth(opt.widget->width() - ofs);
0361     editor->setGeometry(r);
0362     editor->update();
0363 }
0364 
0365 void SplitDelegate::endEdit()
0366 {
0367     if(d->m_editor) {
0368         if(d->m_editor->accepted()) {
0369             emit commitData(d->m_editor);
0370         }
0371         emit closeEditor(d->m_editor, NoHint);
0372         d->m_editorRow = -1;
0373     }
0374 }
0375 
0376 void SplitDelegate::setEditorData(QWidget* editWidget, const QModelIndex& index) const
0377 {
0378     const SplitModel* model = qobject_cast<const SplitModel*>(index.model());
0379     NewSplitEditor* editor = qobject_cast<NewSplitEditor*>(editWidget);
0380 
0381     if(model && editor) {
0382         editor->setShowValuesInverted(d->m_showValuesInverted);
0383         editor->setMemo(model->data(index, (int)Role::Memo).toString());
0384         editor->setAccountId(model->data(index, (int)Role::AccountId).toString());
0385         editor->setAmount(model->data(index, (int)Role::SplitShares).value<MyMoneyMoney>());
0386         editor->setCostCenterId(model->data(index, (int)Role::CostCenterId).toString());
0387         editor->setNumber(model->data(index, (int)Role::Number).toString());
0388     }
0389 }
0390 
0391 void SplitDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
0392 {
0393     NewSplitEditor* splitEditor = qobject_cast< NewSplitEditor* >(editor);
0394     if(splitEditor) {
0395         model->setData(index, splitEditor->number(), (int)Role::Number);
0396         model->setData(index, splitEditor->memo(), (int)Role::Memo);
0397         model->setData(index, splitEditor->accountId(), (int)Role::AccountId);
0398         model->setData(index, splitEditor->costCenterId(), (int)Role::CostCenterId);
0399         model->setData(index, QVariant::fromValue<MyMoneyMoney>(splitEditor->amount()), (int)Role::SplitShares);
0400         model->setData(index, QVariant::fromValue<MyMoneyMoney>(splitEditor->amount()), (int)Role::SplitValue);
0401 
0402         const QString transactionCommodity = model->data(index, (int)Role::TransactionCommodity).toString();
0403         QModelIndex accIndex = Models::instance()->accountsModel()->accountById(splitEditor->accountId());
0404         if(accIndex.isValid()) {
0405             MyMoneyAccount acc = Models::instance()->accountsModel()->data(accIndex, (int)eAccountsModel::Role::Account).value<MyMoneyAccount>();
0406             if(transactionCommodity != acc.currencyId()) {
0407 #if 0
0408                 ///  @todo call KCurrencyConversionDialog and update the model data
0409                 MyMoneyMoney value;
0410                 model->setData(index, QVariant::fromValue<MyMoneyMoney>(value), SplitModel::SplitValueRole);
0411 #endif
0412             }
0413         } else {
0414             qWarning() << "Unable to get account index in SplitDelegate::setModelData";
0415         }
0416 
0417         // the following forces to send a dataChanged signal
0418         model->setData(index, QVariant(), (int)Role::EmitDataChanged);
0419 
0420         // in case this was a new split, we nned to create a new empty one
0421         SplitModel* splitModel = qobject_cast<SplitModel*>(model);
0422         if(splitModel) {
0423             splitModel->addEmptySplitEntry();
0424         }
0425     }
0426 }
0427 
0428 /**
0429  * This eventfilter seems to do nothing but it prevents that selecting a
0430  * different row with the mouse closes the editor
0431  */
0432 bool SplitDelegate::eventFilter(QObject* o, QEvent* event)
0433 {
0434     return QAbstractItemDelegate::eventFilter(o, event);
0435 }
0436 
0437 void SplitDelegate::setShowValuesInverted(bool inverse)
0438 {
0439     d->m_showValuesInverted = inverse;
0440 }
0441 
0442 bool SplitDelegate::showValuesInverted()
0443 {
0444     return d->m_showValuesInverted;
0445 }