File indexing completed on 2024-05-19 05:08:27

0001 /*
0002     SPDX-FileCopyrightText: 2016-2019 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 "splitview.h"
0023 #include "accountsmodel.h"
0024 #include "splitmodel.h"
0025 #include "newspliteditor.h"
0026 #include "mymoneyaccount.h"
0027 #include "mymoneysecurity.h"
0028 #include "mymoneyfile.h"
0029 
0030 QColor SplitDelegate::m_erroneousColor = QColor(Qt::red);
0031 QColor SplitDelegate::m_importedColor = QColor(Qt::yellow);
0032 
0033 class SplitDelegate::Private
0034 {
0035 public:
0036     Private()
0037         : m_editor(nullptr)
0038         , m_editorRow(-1)
0039         , m_showValuesInverted(false)
0040         , m_readOnly(false)
0041     {}
0042 
0043     NewSplitEditor* m_editor;
0044     int m_editorRow;
0045     bool m_showValuesInverted;
0046     bool m_readOnly;
0047     MyMoneySecurity m_commodity;
0048     QString m_transactionPayeeId;
0049 };
0050 
0051 
0052 SplitDelegate::SplitDelegate(QObject* parent)
0053     : QStyledItemDelegate(parent)
0054     , d(new Private)
0055 {
0056 }
0057 
0058 SplitDelegate::~SplitDelegate()
0059 {
0060     delete d;
0061 }
0062 
0063 void SplitDelegate::setErroneousColor(const QColor& color)
0064 {
0065     m_erroneousColor = color;
0066 }
0067 
0068 void SplitDelegate::setCommodity(const MyMoneySecurity& commodity)
0069 {
0070     d->m_commodity = commodity;
0071 }
0072 
0073 void SplitDelegate::setTransactionPayeeId(const QString& id)
0074 {
0075     d->m_transactionPayeeId = id;
0076 }
0077 
0078 QWidget* SplitDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
0079 {
0080     Q_UNUSED(option);
0081 
0082     if(index.isValid()) {
0083         Q_ASSERT(parent);
0084         auto view = qobject_cast<SplitView*>(parent->parentWidget());
0085         Q_ASSERT(view != 0);
0086 
0087         if(view->selectionModel()->selectedRows().count() > 1) {
0088             qDebug() << "Editing multiple splits at once is not yet supported";
0089 
0090             /**
0091              * @todo replace the following three lines with the creation of a special
0092              * editor that can handle multiple splits at once or show a message to the user
0093              * that this is not possible
0094              */
0095             d->m_editor = 0;
0096             SplitDelegate* that = const_cast<SplitDelegate*>(this);
0097             Q_EMIT that->closeEditor(d->m_editor, NoHint);
0098 
0099         } else {
0100             QString accountId = index.data(eMyMoney::Model::SplitAccountIdRole).toString();
0101             d->m_editor = new NewSplitEditor(parent, d->m_commodity, accountId);
0102         }
0103 
0104         if(d->m_editor) {
0105             d->m_editorRow = index.row();
0106             connect(d->m_editor, &NewSplitEditor::done, this, &SplitDelegate::endEdit);
0107             Q_EMIT const_cast<SplitDelegate*>(this)->sizeHintChanged(index);
0108 
0109             d->m_editor->setAmountPlaceHolderText(index.model());
0110 
0111             // propagate read-only mode
0112             d->m_editor->setReadOnly(d->m_readOnly);
0113         }
0114 
0115     } else {
0116         qFatal("SplitDelegate::createEditor(): we should never end up here");
0117     }
0118     return d->m_editor;
0119 }
0120 
0121 int SplitDelegate::editorRow() const
0122 {
0123     return d->m_editorRow;
0124 }
0125 
0126 void SplitDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
0127 {
0128     QStyleOptionViewItem opt = option;
0129     initStyleOption(&opt, index);
0130 
0131     // never change the background of the cell the mouse is hovering over
0132     opt.state &= ~QStyle::State_MouseOver;
0133 
0134     // show the focus only on the detail column
0135     opt.state &= ~QStyle::State_HasFocus;
0136     if(index.column() == SplitModel::Column::Memo) {
0137         QAbstractItemView* view = qobject_cast< QAbstractItemView* >(parent());
0138         if(view) {
0139             if(view->currentIndex().row() == index.row()) {
0140                 opt.state |= QStyle::State_HasFocus;
0141             }
0142         }
0143     }
0144 
0145     painter->save();
0146 
0147     // Background
0148     auto bgOpt = opt;
0149     // if selected, always show as active, so that the
0150     // background does not change when the editor is shown
0151     if (opt.state & QStyle::State_Selected) {
0152         bgOpt.state |= QStyle::State_Active;
0153     }
0154     QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
0155     style->drawPrimitive(QStyle::PE_PanelItemViewItem, &bgOpt, painter, opt.widget);
0156 
0157     // Do not paint text if the edit widget is shown
0158     const auto view = qobject_cast<const SplitView *>(opt.widget);
0159     if (view && view->indexWidget(index)) {
0160         painter->restore();
0161         return;
0162     }
0163 
0164     const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
0165     const QRect textArea = QRect(opt.rect.x() + margin, opt.rect.y() + margin, opt.rect.width() - 2 * margin, opt.rect.height() - 2 * margin);
0166 
0167     QStringList lines;
0168     if(index.column() == SplitModel::Column::Memo) {
0169         const auto payeeId = index.data(eMyMoney::Model::SplitPayeeIdRole).toString();
0170         if (!payeeId.isEmpty()) {
0171             if (payeeId != d->m_transactionPayeeId) {
0172                 lines << index.data(eMyMoney::Model::SplitPayeeRole).toString();
0173             }
0174         }
0175         lines << index.data(eMyMoney::Model::SplitSingleLineMemoRole).toString();
0176         lines.removeAll(QString());
0177     }
0178 
0179     // draw the text items
0180     if(!opt.text.isEmpty() || !lines.isEmpty()) {
0181 
0182         QPalette::ColorGroup cg = (opt.state & QStyle::State_Enabled)
0183                                   ? QPalette::Normal : QPalette::Disabled;
0184 
0185         if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) {
0186             cg = QPalette::Inactive;
0187         }
0188         if (opt.state & QStyle::State_Selected) {
0189             painter->setPen(opt.palette.color(cg, QPalette::HighlightedText));
0190         } else {
0191             painter->setPen(opt.palette.color(cg, QPalette::Text));
0192         }
0193         if (opt.state & QStyle::State_Editing) {
0194             painter->setPen(opt.palette.color(cg, QPalette::Text));
0195             painter->drawRect(textArea.adjusted(0, 0, -1, -1));
0196         }
0197 
0198         // collect data for the various columns
0199         if(index.column() == SplitModel::Column::Memo) {
0200             for(int i = 0; i < lines.count(); ++i) {
0201                 painter->drawText(textArea.adjusted(0, (opt.fontMetrics.lineSpacing() + 5) * i, 0, 0), opt.displayAlignment, lines[i]);
0202             }
0203 
0204         } else {
0205             painter->drawText(textArea, opt.displayAlignment, opt.text);
0206         }
0207     }
0208 
0209     // draw the focus rect
0210     if(opt.state & QStyle::State_HasFocus) {
0211         QStyleOptionFocusRect o;
0212         o.QStyleOption::operator=(opt);
0213         o.rect = style->proxy()->subElementRect(QStyle::SE_ItemViewItemFocusRect, &opt, opt.widget);
0214         o.state |= QStyle::State_KeyboardFocusChange;
0215         o.state |= QStyle::State_Item;
0216 
0217         QPalette::ColorGroup cg = (opt.state & QStyle::State_Enabled)
0218                                   ? QPalette::Normal : QPalette::Disabled;
0219         o.backgroundColor = opt.palette.color(cg, (opt.state & QStyle::State_Selected)
0220                                               ? QPalette::Highlight : QPalette::Window);
0221         style->proxy()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, opt.widget);
0222     }
0223 
0224     painter->restore();
0225 }
0226 
0227 QSize SplitDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
0228 {
0229     bool fullDisplay = false;
0230     auto view = qobject_cast<SplitView*>(parent());
0231     if(view) {
0232         const auto currentIndex = view->currentIndex();
0233         if(currentIndex.isValid()) {
0234             const auto currentId = currentIndex.model()->data(currentIndex, eMyMoney::Model::IdRole).toString();
0235             const auto myId = index.model()->data(index, eMyMoney::Model::IdRole).toString();
0236             fullDisplay = (currentId == myId);
0237         }
0238     }
0239 
0240     QSize size;
0241     QStyleOptionViewItem opt = option;
0242     if(index.isValid()) {
0243         // check if we are showing the edit widget
0244         const auto* viewWidget = qobject_cast<const QAbstractItemView*>(opt.widget);
0245         if (viewWidget) {
0246             const auto editIndex = viewWidget->model()->index(index.row(), 0);
0247             if(editIndex.isValid()) {
0248                 QWidget* editor = viewWidget->indexWidget(editIndex);
0249                 if(editor) {
0250                     size = editor->minimumSizeHint();
0251                     return size;
0252                 }
0253             }
0254         }
0255     }
0256 
0257     int rows = 1;
0258     if(fullDisplay) {
0259         initStyleOption(&opt, index);
0260         rows = 0;
0261         const auto payeeId = index.data(eMyMoney::Model::SplitPayeeIdRole).toString();
0262         if (!payeeId.isEmpty() && (payeeId != d->m_transactionPayeeId)) {
0263             ++rows;
0264         }
0265         if (!index.data(eMyMoney::Model::SplitMemoRole).toString().isEmpty()) {
0266             ++rows;
0267         }
0268 
0269         // make sure we show at least one row
0270         if(!rows) {
0271             rows = 1;
0272         }
0273     }
0274 
0275     // leave a 5 pixel margin for each row
0276     size = QSize(100, (opt.fontMetrics.lineSpacing() + 5) * rows);
0277     return size;
0278 }
0279 
0280 void SplitDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
0281 {
0282     Q_UNUSED(index);
0283 
0284     QStyleOptionViewItem opt = option;
0285     const auto view = qobject_cast<const SplitView*>(opt.widget);
0286 
0287     QRect r(opt.rect);
0288     if(view && view->verticalScrollBar()->isVisible()) {
0289         r.setWidth(opt.widget->width() - view->verticalScrollBar()->width());
0290     }
0291 
0292     editor->setGeometry(r);
0293     editor->update();
0294 }
0295 
0296 void SplitDelegate::endEdit()
0297 {
0298     if(d->m_editor) {
0299         if(d->m_editor->accepted()) {
0300             Q_EMIT commitData(d->m_editor);
0301         }
0302         Q_EMIT closeEditor(d->m_editor, NoHint);
0303         d->m_editorRow = -1;
0304     }
0305 }
0306 
0307 void SplitDelegate::setEditorData(QWidget* editWidget, const QModelIndex& index) const
0308 {
0309     const SplitModel* model = qobject_cast<const SplitModel*>(index.model());
0310     NewSplitEditor* editor = qobject_cast<NewSplitEditor*>(editWidget);
0311 
0312     if(model && editor) {
0313         editor->startLoadingSplit();
0314         editor->setShowValuesInverted(d->m_showValuesInverted);
0315         editor->setMemo(index.data(eMyMoney::Model::SplitMemoRole).toString());
0316         editor->setAccountId(index.data(eMyMoney::Model::SplitAccountIdRole).toString());
0317         editor->setValue(-(index.data(eMyMoney::Model::SplitValueRole).value<MyMoneyMoney>()));
0318         editor->setShares(-(index.data(eMyMoney::Model::SplitSharesRole).value<MyMoneyMoney>()));
0319         editor->setCostCenterId(index.data(eMyMoney::Model::SplitCostCenterIdRole).toString());
0320         editor->setNumber(index.data(eMyMoney::Model::SplitNumberRole).toString());
0321         editor->setPayeeId(index.data(eMyMoney::Model::SplitPayeeIdRole).toString());
0322         editor->setTagIdList(index.data(eMyMoney::Model::SplitTagIdRole).toStringList());
0323         editor->finishLoadingSplit();
0324     }
0325 }
0326 
0327 void SplitDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
0328 {
0329     NewSplitEditor* splitEditor = qobject_cast< NewSplitEditor* >(editor);
0330     if(splitEditor) {
0331         // prevent update signals
0332         QSignalBlocker block(model);
0333         model->setData(index, splitEditor->number(), eMyMoney::Model::SplitNumberRole);
0334         model->setData(index, splitEditor->memo(), eMyMoney::Model::SplitMemoRole);
0335         model->setData(index, splitEditor->accountId(), eMyMoney::Model::SplitAccountIdRole);
0336         model->setData(index, splitEditor->costCenterId(), eMyMoney::Model::SplitCostCenterIdRole);
0337         model->setData(index, splitEditor->payeeId(), eMyMoney::Model::SplitPayeeIdRole);
0338         model->setData(index, QVariant::fromValue<QStringList>(splitEditor->tagIdList()), eMyMoney::Model::SplitTagIdRole);
0339         model->setData(index, QVariant::fromValue<MyMoneyMoney>(-splitEditor->shares()), eMyMoney::Model::SplitSharesRole);
0340         // send out the dataChanged signal with the next (last) setData()
0341         block.unblock();
0342         model->setData(index, QVariant::fromValue<MyMoneyMoney>(-splitEditor->value()), eMyMoney::Model::SplitValueRole);
0343 
0344         // in case this was a new split, we need to create a new empty one
0345         SplitModel* splitModel = qobject_cast<SplitModel*>(model);
0346         if(splitModel) {
0347             splitModel->appendEmptySplit();
0348         }
0349     }
0350 }
0351 
0352 /**
0353  * This eventfilter seems to do nothing but it prevents that selecting a
0354  * different row with the mouse closes the editor
0355  */
0356 bool SplitDelegate::eventFilter(QObject* o, QEvent* event)
0357 {
0358     return QAbstractItemDelegate::eventFilter(o, event);
0359 }
0360 
0361 void SplitDelegate::setShowValuesInverted(bool inverse)
0362 {
0363     d->m_showValuesInverted = inverse;
0364 }
0365 
0366 bool SplitDelegate::showValuesInverted()
0367 {
0368     return d->m_showValuesInverted;
0369 }
0370 
0371 void SplitDelegate::setReadOnlyMode(bool readOnly)
0372 {
0373     d->m_readOnly = readOnly;
0374 }