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

0001 /*
0002     SPDX-FileCopyrightText: 2015-2019 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "journaldelegate.h"
0007 
0008 // ----------------------------------------------------------------------------
0009 // QT Includes
0010 
0011 #include <QApplication>
0012 #include <QDate>
0013 #include <QDebug>
0014 #include <QHeaderView>
0015 #include <QPainter>
0016 #include <QScrollBar>
0017 #include <QSortFilterProxyModel>
0018 
0019 // ----------------------------------------------------------------------------
0020 // KDE Includes
0021 
0022 #include <KColorScheme>
0023 #include <KLocalizedString>
0024 #include <KMessageBox>
0025 
0026 // ----------------------------------------------------------------------------
0027 // Project Includes
0028 
0029 #include "accountsmodel.h"
0030 #include "icons.h"
0031 #include "investtransactioneditor.h"
0032 #include "journalmodel.h"
0033 #include "ledgerview.h"
0034 #include "ledgerviewsettings.h"
0035 #include "multitransactioneditor.h"
0036 #include "mymoneyfile.h"
0037 #include "mymoneysecurity.h"
0038 #include "mymoneyutils.h"
0039 #include "newtransactioneditor.h"
0040 #include "schedulesjournalmodel.h"
0041 
0042 QColor JournalDelegate::m_erroneousColor = QColor(Qt::red);
0043 
0044 struct displayProperties {
0045     int italicStartLine;
0046     QStringList lines;
0047 };
0048 
0049 class JournalDelegate::Private
0050 {
0051 public:
0052     Private()
0053         : m_editor(nullptr)
0054         , m_view(nullptr)
0055         , m_editorRow(-1)
0056         , m_editorCol(-1)
0057         , m_singleLineRole(eMyMoney::Model::SplitPayeeRole)
0058         , m_lineHeight(-1)
0059         , m_margin(2)
0060         , m_editorWidthOfs(0)
0061         , m_showPayeeInDetailColumn(true)
0062         , m_accountType(eMyMoney::Account::Type::Unknown)
0063     {}
0064 
0065     ~Private()
0066     {
0067     }
0068 
0069     bool isInvestmentView()
0070     {
0071         if (m_accountType == eMyMoney::Account::Type::Unknown) {
0072             const auto accountId = m_view->accountId();
0073             const auto acc = MyMoneyFile::instance()->accountsModel()->itemById(accountId);
0074             if (!acc.id().isEmpty()) {
0075                 m_accountType = acc.accountType();
0076             }
0077         }
0078         return (m_accountType == eMyMoney::Account::Type::Investment);
0079     }
0080 
0081     displayProperties displayMatchedString(const QModelIndex& index, const QStyleOptionViewItem& opt)
0082     {
0083         Q_UNUSED(opt)
0084         displayProperties rc;
0085         rc.italicStartLine = -1;
0086         if (index.data(eMyMoney::Model::JournalSplitIsMatchedRole).toBool()) {
0087             if (index.column() == JournalModel::Column::Detail) {
0088                 const auto memo = index.data(eMyMoney::Model::MatchedSplitMemoRole).toString();
0089                 rc.lines << (memo.isEmpty() ? i18nc("@info placeholder for memo of matched transaction if empty", "Empty memo") : memo);
0090                 return rc;
0091             }
0092         }
0093         return rc;
0094     }
0095 
0096     displayProperties displayString(const QModelIndex& index, const QStyleOptionViewItem& opt)
0097     {
0098         displayProperties rc;
0099         rc.italicStartLine = -1;
0100 
0101         const auto showAllSplits = LedgerViewSettings::instance()->showAllSplits();
0102 
0103         if(index.column() == JournalModel::Column::Detail) {
0104             const auto showDetails = LedgerViewSettings::instance()->showTransactionDetails();
0105             const auto showLedgerLens = LedgerViewSettings::instance()->showLedgerLens();
0106             const auto havePayeeColumn = !m_view->isColumnHidden(JournalModel::Payee);
0107 
0108             if (index.data(eMyMoney::Model::TransactionIsInvestmentRole).toBool() && isInvestmentView()) {
0109                 if (((opt.state & QStyle::State_Selected) && (showLedgerLens)) || showDetails || showAllSplits) {
0110                     rc.lines << index.data(eMyMoney::Model::SplitActivityRole).toString();
0111                     rc.lines << index.data(eMyMoney::Model::TransactionBrokerageAccountRole).toString();
0112                     rc.lines << index.data(eMyMoney::Model::TransactionInterestCategoryRole).toString();
0113                     rc.lines << index.data(eMyMoney::Model::TransactionFeesCategoryRole).toString();
0114                     rc.lines << index.data(eMyMoney::Model::SplitSingleLineMemoRole).toString();
0115                 } else {
0116                     rc.lines << index.data(eMyMoney::Model::SplitActivityRole).toString();
0117                 }
0118 
0119             } else {
0120                 rc.italicStartLine = 1;
0121                 // make sure to not duplicate the payee information in the detail column
0122                 if (havePayeeColumn && (m_singleLineRole == eMyMoney::Model::SplitPayeeRole)) {
0123                     rc.lines << index.data(eMyMoney::Model::TransactionCounterAccountRole).toString();
0124                 } else {
0125                     rc.lines << index.data(m_singleLineRole).toString();
0126                 }
0127                 if (showAllSplits && isMultiSplitDisplay(index)) {
0128                     rc.italicStartLine = 0;
0129                     rc.lines.clear();
0130                     if (!havePayeeColumn) {
0131                         const auto payee = index.data(eMyMoney::Model::SplitPayeeRole).toString();
0132                         if (!payee.isEmpty()) {
0133                             rc.lines << payee;
0134                             ++rc.italicStartLine;
0135                         }
0136                     }
0137                     const auto memo = index.data(eMyMoney::Model::Roles::SplitSingleLineMemoRole).toString();
0138                     if (!memo.isEmpty()) {
0139                         rc.lines << memo;
0140                         ++rc.italicStartLine;
0141                     }
0142                     // make sure to show at least one line even if we have no payee or memo in the split
0143                     if (rc.italicStartLine == 0) {
0144                         rc.lines << QStringLiteral(" ");
0145                     }
0146 
0147                     const auto rowIndeces =
0148                         MyMoneyFile::instance()->journalModel()->indexesByTransactionId(index.data(eMyMoney::Model::JournalTransactionIdRole).toString());
0149                     const auto rowCount = rowIndeces.count();
0150                     const auto splitId = index.data(eMyMoney::Model::IdRole).toString();
0151                     for (int row = 0; row < rowCount; ++row) {
0152                         const auto rowIndex = rowIndeces[row];
0153                         if (rowIndex.data(eMyMoney::Model::IdRole) != splitId) {
0154                             // don't include the split if the value is zero
0155                             if (!rowIndex.data(eMyMoney::Model::SplitSharesRole).value<MyMoneyMoney>().isZero()) {
0156                                 const auto accountId = rowIndex.data(eMyMoney::Model::Roles::JournalSplitAccountIdRole).toString();
0157                                 const auto accountIdx = MyMoneyFile::instance()->accountsModel()->indexById(accountId);
0158                                 const auto account = accountIdx.data(eMyMoney::Model::Roles::AccountFullNameRole).toString();
0159                                 const auto splitMemo = rowIndex.data(eMyMoney::Model::Roles::SplitSingleLineMemoRole).toString();
0160                                 QString txt;
0161                                 QString sep;
0162                                 if (!account.isEmpty()) {
0163                                     txt = account;
0164                                     sep = QStringLiteral(", ");
0165                                 }
0166                                 if (!splitMemo.isEmpty()) {
0167                                     txt += sep + splitMemo;
0168                                 }
0169                                 rc.lines << txt;
0170                             }
0171                         }
0172                     }
0173                 } else if (((opt.state & QStyle::State_Selected) && (showLedgerLens)) || showDetails || showAllSplits) {
0174                     rc.lines.clear();
0175                     if (!havePayeeColumn && m_showPayeeInDetailColumn) {
0176                         rc.lines << index.data(eMyMoney::Model::Roles::SplitPayeeRole).toString();
0177                     }
0178                     rc.lines << index.data(eMyMoney::Model::Roles::TransactionCounterAccountRole).toString();
0179                     rc.lines << index.data(eMyMoney::Model::Roles::SplitSingleLineMemoRole).toString();
0180 
0181                 } else {
0182                     if (rc.lines.at(0).isEmpty()) {
0183                         rc.lines.clear();
0184                         rc.lines << index.data(eMyMoney::Model::Roles::SplitSingleLineMemoRole).toString();
0185                     }
0186                     if (rc.lines.at(0).isEmpty()) {
0187                         rc.lines << index.data(eMyMoney::Model::Roles::TransactionCounterAccountRole).toString();
0188                     }
0189                 }
0190             }
0191             rc.lines.removeAll(QString());
0192 
0193         } else if(index.column() == JournalModel::Column::Quantity) {
0194             if (index.data(eMyMoney::Model::TransactionIsInvestmentRole).toBool()) {
0195                 const auto showDetails = LedgerViewSettings::instance()->showTransactionDetails();
0196                 const auto showLedgerLens = LedgerViewSettings::instance()->showLedgerLens();
0197                 rc.lines << opt.text;
0198                 if (((opt.state & QStyle::State_Selected) && (showLedgerLens)) || showDetails) {
0199                     // we have to pay attention here as later on empty items will be removed
0200                     // from the lines all together. Since we use the detail column as label
0201                     // we have to make sure that we are not off. Therefore, if the detail column
0202                     // is filled, we add a simple blank here instead of an empty line.
0203                     // The first line is always present, so we make sure it is not empty in this column.
0204                     if (rc.lines[0].isEmpty())
0205                         rc.lines[0] = QStringLiteral(" ");
0206                     rc.lines << (index.data(eMyMoney::Model::TransactionBrokerageAccountRole).toString().isEmpty() ? QString() : QStringLiteral(" "));
0207 
0208                     MyMoneySecurity currency = MyMoneyFile::instance()->currency(index.data(eMyMoney::Model::TransactionCommodityRole).toString());
0209 
0210                     if (index.data(eMyMoney::Model::TransactionInterestSplitPresentRole).toBool()) {
0211                         const auto value = index.data(eMyMoney::Model::TransactionInterestValueRole).value<MyMoneyMoney>();
0212                         rc.lines << (index.data(eMyMoney::Model::TransactionInterestCategoryRole).toString().isEmpty()
0213                                          ? QString()
0214                                          : MyMoneyUtils::formatMoney(-value, currency));
0215                     }
0216 
0217                     if (index.data(eMyMoney::Model::TransactionFeeSplitPresentRole).toBool()) {
0218                         const auto value = index.data(eMyMoney::Model::TransactionFeesValueRole).value<MyMoneyMoney>();
0219                         rc.lines << (index.data(eMyMoney::Model::TransactionFeesCategoryRole).toString().isEmpty()
0220                                          ? QString()
0221                                          : MyMoneyUtils::formatMoney(-value, currency));
0222                     }
0223                 } else {
0224                     rc.lines << opt.text;
0225                 }
0226             }
0227             rc.lines.removeAll(QString());
0228 
0229         } else if (index.column() == JournalModel::Column::Deposit) {
0230             const auto havePayeeColumn = !m_view->isColumnHidden(JournalModel::Payee);
0231             rc.lines << opt.text;
0232             if (showAllSplits && isMultiSplitDisplay(index)) {
0233                 const auto payee = index.data(eMyMoney::Model::SplitPayeeRole).toString();
0234                 if (!havePayeeColumn && !payee.isEmpty() && !index.data(eMyMoney::Model::Roles::SplitSingleLineMemoRole).toString().isEmpty()) {
0235                     rc.lines << QStringLiteral(" ");
0236                 }
0237                 rc.italicStartLine = 1;
0238                 rc.lines << displaySplitValues(index, JournalModel::Column::Payment);
0239             }
0240 
0241         } else if (index.column() == JournalModel::Column::Payment) {
0242             const auto havePayeeColumn = !m_view->isColumnHidden(JournalModel::Payee);
0243             rc.lines << opt.text;
0244             if (showAllSplits && isMultiSplitDisplay(index)) {
0245                 const auto payee = index.data(eMyMoney::Model::SplitPayeeRole).toString();
0246                 if (!havePayeeColumn && !payee.isEmpty() && !index.data(eMyMoney::Model::Roles::SplitSingleLineMemoRole).toString().isEmpty()) {
0247                     rc.lines << QStringLiteral(" ");
0248                 }
0249                 rc.italicStartLine = 1;
0250                 rc.lines << displaySplitValues(index, JournalModel::Column::Deposit);
0251             }
0252 
0253         } else {
0254             rc.lines << opt.text;
0255         }
0256         return rc;
0257     }
0258 
0259     QStringList displaySplitValues(const QModelIndex& index, JournalModel::Column column)
0260     {
0261         QStringList lines;
0262         const auto rowIndeces =
0263             MyMoneyFile::instance()->journalModel()->indexesByTransactionId(index.data(eMyMoney::Model::Roles::JournalTransactionIdRole).toString());
0264         const auto rowCount = rowIndeces.count();
0265         const auto splitId = index.data(eMyMoney::Model::IdRole).toString();
0266         for (int row = 0; row < rowCount; ++row) {
0267             const auto rowIndex = rowIndeces[row];
0268             if (rowIndex.data(eMyMoney::Model::IdRole) != splitId) {
0269                 // don't include the split if the value is zero
0270                 if (!rowIndex.data(eMyMoney::Model::SplitSharesRole).value<MyMoneyMoney>().isZero()) {
0271                     const auto columnIndex = rowIndex.model()->index(rowIndex.row(), column, rowIndex.parent());
0272                     const auto txt = columnIndex.data().toString();
0273                     lines << (!txt.isEmpty() ? txt : QStringLiteral(" "));
0274                 }
0275             }
0276         }
0277         return lines;
0278     }
0279 
0280     inline bool isMultiSplitDisplay(const QModelIndex& index)
0281     {
0282         return index.data(eMyMoney::Model::TransactionValuableSplitCountRole).toInt() > 2;
0283     }
0284 
0285     TransactionEditorBase* m_editor;
0286     LedgerView* m_view;
0287     int m_editorRow;
0288     int m_editorCol;
0289     eMyMoney::Model::Roles m_singleLineRole;
0290     int m_lineHeight;
0291     int m_margin;
0292     int m_editorWidthOfs;
0293     bool m_showPayeeInDetailColumn;
0294     eMyMoney::Account::Type m_accountType;
0295 };
0296 
0297 
0298 JournalDelegate::JournalDelegate(LedgerView* parent)
0299     : KMMStyledItemDelegate(parent)
0300     , d(new Private)
0301 {
0302     d->m_view = parent;
0303 }
0304 
0305 JournalDelegate::~JournalDelegate()
0306 {
0307     delete d;
0308 }
0309 
0310 void JournalDelegate::setErroneousColor(const QColor& color)
0311 {
0312     m_erroneousColor = color;
0313 }
0314 
0315 void JournalDelegate::setSingleLineRole(eMyMoney::Model::Roles role)
0316 {
0317     d->m_singleLineRole = role;
0318 }
0319 
0320 void JournalDelegate::setShowPayeeInDetailColumn(bool show)
0321 {
0322     d->m_showPayeeInDetailColumn = show;
0323 }
0324 
0325 QWidget* JournalDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
0326 {
0327     Q_UNUSED(option);
0328     QString errorMessage;
0329 
0330     if(index.isValid()) {
0331         d->m_editor = nullptr;
0332         if(d->m_view->selectionModel()->selectedRows().count() > 1) {
0333             auto accountId = d->m_view->accountId();
0334             if (!accountId.isEmpty()) {
0335                 const auto acc = MyMoneyFile::instance()->accountsModel()->itemById(accountId);
0336                 if (acc.accountType() == eMyMoney::Account::Type::Investment) {
0337                     d->m_editor = nullptr;
0338                 } else {
0339                     d->m_editor = new MultiTransactionEditor(parent, accountId);
0340                 }
0341             } else {
0342                 errorMessage =
0343                     i18nc("@info Editing multiple transactions", "The current implementation cannot modify multiple transactions in different accounts.");
0344                 // Message that multiple edit is only available in ledger (within a single account)
0345             }
0346 
0347         } else {
0348             auto accountId = index.data(eMyMoney::Model::SplitAccountIdRole).toString();
0349             if (accountId.isEmpty() || (accountId == MyMoneyFile::instance()->journalModel()->fakeId())) {
0350                 accountId = d->m_view->accountId();
0351             }
0352             if (!accountId.isEmpty()) {
0353                 // now determine which editor to use. In case we have no transaction (yet)
0354                 // we use the account type
0355                 if (index.data(eMyMoney::Model::JournalTransactionIdRole).toString().isEmpty()) {
0356                     const auto acc = MyMoneyFile::instance()->accountsModel()->itemById(accountId);
0357                     if (acc.accountType() == eMyMoney::Account::Type::Investment) {
0358                         d->m_editor = new InvestTransactionEditor(parent, accountId);
0359                     } else {
0360                         d->m_editor = new NewTransactionEditor(parent, accountId);
0361                     }
0362                 } else {
0363                     if (index.data(eMyMoney::Model::TransactionIsInvestmentRole).toBool()) {
0364                         // in case of an investment transaction we need to use
0365                         // the parent account of the security account and pass
0366                         // it to the editor.
0367                         accountId = index.data(eMyMoney::Model::TransactionInvestmentAccountIdRole).toString();
0368                         d->m_editor = new InvestTransactionEditor(parent, accountId);
0369                     } else {
0370                         d->m_editor = new NewTransactionEditor(parent, accountId);
0371                     }
0372                 }
0373             }
0374         }
0375 
0376         // in case we have an editor, we check that it can perform the action
0377         if (d->m_editor) {
0378             if (d->m_editor->setSelectedJournalEntryIds(d->m_view->selectedJournalEntryIds())) {
0379                 d->m_editor->setAmountPlaceHolderText(index.model());
0380                 d->m_editorWidthOfs = 8;
0381                 if(d->m_view) {
0382                     if(d->m_view->verticalScrollBar()->isVisible()) {
0383                         d->m_editorWidthOfs += d->m_view->verticalScrollBar()->width();
0384                     }
0385                 }
0386             } else {
0387                 // if not get error message and display it and delete the editor again
0388                 /// @todo add error message handling here
0389                 errorMessage = d->m_editor->errorMessage();
0390                 delete d->m_editor;
0391                 d->m_editor = nullptr;
0392             }
0393         }
0394 
0395         // if we still have an editor here,
0396         if(d->m_editor) {
0397             d->m_editorRow = index.row();
0398             d->m_editorCol = index.column();
0399             connect(d->m_editor, &TransactionEditorBase::done, this, &JournalDelegate::endEdit);
0400             JournalDelegate* that = const_cast<JournalDelegate*>(this);
0401             Q_EMIT that->sizeHintChanged(index);
0402 
0403             // check if we need to open editor in read-only mode
0404             const auto journalEntryId = index.data(eMyMoney::Model::IdRole).toString();
0405             const auto warnLevel = MyMoneyUtils::transactionWarnLevel(journalEntryId);
0406             d->m_editor->setReadOnly(warnLevel >= OneSplitFrozen);
0407 
0408         } else {
0409             if (!errorMessage.isEmpty()) {
0410                 KMessageBox::information(0, errorMessage);
0411             }
0412             JournalDelegate* that = const_cast<JournalDelegate*>(this);
0413             Q_EMIT that->closeEditor(d->m_editor, NoHint);
0414         }
0415 
0416     } else {
0417         qFatal("JournalDelegate::createEditor(): we should never end up here");
0418     }
0419     return d->m_editor;
0420 }
0421 
0422 int JournalDelegate::editorRow() const
0423 {
0424     return d->m_editorRow;
0425 }
0426 
0427 void JournalDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
0428 {
0429     QStyleOptionViewItem opt = option;
0430     initStyleOption(&opt, index);
0431 
0432     // never change the background of the cell the mouse is hovering over
0433     opt.state &= ~QStyle::State_MouseOver;
0434 
0435     // show the focus only on the detail column
0436     opt.state &= ~QStyle::State_HasFocus;
0437 
0438     QAbstractItemView* view = qobject_cast<QAbstractItemView*>(parent());
0439     const auto editCol = (d->m_view) ? d->m_view->horizontalHeader()->logicalIndexAt(0) : 0;
0440     const auto editIndex = index.model()->index(index.row(), d->m_editorCol, index.parent());
0441     const auto editWidget = (d->m_view) ? d->m_view->indexWidget(editIndex) : nullptr;
0442 
0443     // if selected, always show as active, so that the
0444     // background does not change when the editor is shown
0445     if (opt.state & QStyle::State_Selected && (editWidget == nullptr)) {
0446         opt.state |= QStyle::State_Active;
0447     } else {
0448         opt.state &= ~QStyle::State_Active;
0449     }
0450 
0451     // if the widget has a different size than what we can paint on
0452     // then we adjust the size of the widget so that the focus frame
0453     // can be painted correctly using a WidgetHintFrame. The editor
0454     // only uses the first column across the whole width
0455     if (editWidget && (index.column() == editCol)) {
0456         auto size = editWidget->size();
0457         if (size.width() != opt.rect.size().width()) {
0458             editWidget->resize(size);
0459         }
0460     }
0461 
0462     painter->save();
0463 
0464     // Background
0465     QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
0466     const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin);
0467     const int lineHeight = opt.fontMetrics.lineSpacing() + 2;
0468 
0469     style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, editWidget ? editWidget : opt.widget);
0470 
0471     QPalette::ColorGroup cg;
0472 
0473     // Do not paint text if the edit widget is shown
0474     if (editWidget == nullptr) {
0475         if(view && (index.column() == JournalModel::Column::Detail)) {
0476             if(view->currentIndex().row() == index.row()) {
0477                 opt.state |= QStyle::State_HasFocus;
0478             }
0479         }
0480         const QRect textArea = QRect(opt.rect.x() + margin, opt.rect.y() + margin, opt.rect.width() - 2 * margin, opt.rect.height() - 2 * margin);
0481         const bool selected = opt.state & QStyle::State_Selected;
0482 
0483         const auto displayProperties = d->displayString(index, opt);
0484         const auto matchedDisplayProperties = d->displayMatchedString(index, opt);
0485 
0486         const int lineCount = displayProperties.lines.count();
0487         const int matchedLineCount = matchedDisplayProperties.lines.count();
0488 
0489         const bool erroneous = index.data(eMyMoney::Model::Roles::TransactionErroneousRole).toBool();
0490 
0491         // draw the text items
0492         if (!opt.text.isEmpty() || (lineCount > 0) || (matchedLineCount > 0)) {
0493             // check if it is a scheduled transaction and display it as inactive
0494             if (MyMoneyFile::baseModel()->baseModel(index) == MyMoneyFile::instance()->schedulesJournalModel()) {
0495                 opt.state &= ~QStyle::State_Enabled;
0496             }
0497             cg = (opt.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled;
0498 
0499             if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) {
0500                 cg = QPalette::Inactive;
0501             }
0502             if (selected) {
0503                 // always use the normal palette since the background is also in normal
0504                 painter->setPen(opt.palette.color(QPalette::ColorGroup(QPalette::Normal), QPalette::HighlightedText));
0505 
0506             } else if (erroneous) {
0507                 painter->setPen(m_erroneousColor);
0508 
0509             } else {
0510                 painter->setPen(opt.palette.color(cg, QPalette::Text));
0511             }
0512 
0513             if (opt.state & QStyle::State_Editing) {
0514                 painter->setPen(opt.palette.color(cg, QPalette::Text));
0515                 painter->drawRect(textArea.adjusted(0, 0, -1, -1));
0516             }
0517 
0518             painter->save();
0519             // collect data for the various columns
0520             for (int i = 0; i < lineCount; ++i) {
0521                 if (i == displayProperties.italicStartLine && LedgerViewSettings::instance()->showAllSplits() && d->isMultiSplitDisplay(index)) {
0522                     auto font = painter->font();
0523                     font.setItalic(true);
0524                     font.setPointSize(font.pointSize() - 2);
0525                     painter->setFont(font);
0526                 }
0527                 painter->drawText(textArea.adjusted(0, lineHeight * i, 0, 0), opt.displayAlignment, displayProperties.lines[i]);
0528             }
0529             painter->restore();
0530 
0531             if (matchedLineCount > 0) {
0532                 painter->drawText(textArea.adjusted(0, lineHeight * lineCount, 0, 0), opt.displayAlignment, matchedDisplayProperties.lines[0]);
0533                 // possibly draw horizontal line as separator
0534                 if (lineCount > 0) {
0535                     const auto yOffset(lineHeight * lineCount);
0536                     if (yOffset < opt.rect.height()) {
0537                         painter->drawLine(opt.rect.x(), opt.rect.y() + yOffset, opt.rect.x() + opt.rect.width(), opt.rect.y() + yOffset);
0538                     }
0539                 }
0540             }
0541         }
0542 
0543         // draw the focus rect
0544         if(opt.state & QStyle::State_HasFocus) {
0545             QStyleOptionFocusRect o;
0546             o.QStyleOption::operator=(opt);
0547             o.rect = style->proxy()->subElementRect(QStyle::SE_ItemViewItemFocusRect, &opt, opt.widget);
0548             o.state |= QStyle::State_KeyboardFocusChange;
0549             o.state |= QStyle::State_Item;
0550 
0551             cg = (opt.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled;
0552             o.backgroundColor = opt.palette.color(cg, (opt.state & QStyle::State_Selected)
0553                                                   ? QPalette::Highlight : QPalette::Window);
0554             style->proxy()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, opt.widget);
0555         }
0556 
0557         // take care of icons on the transaction
0558         if (index.column() == JournalModel::Column::Detail) {
0559             QRect iconArea = QRect(opt.rect.x() + margin, opt.rect.y(), opt.rect.width() - 2 * margin, opt.rect.height());
0560             const auto iconHeight = d->m_lineHeight + 2 * d->m_margin;
0561 
0562             // draw the icons
0563             const auto statusRoles = d->m_view->statusRoles(index);
0564             for (int i = 0; i < statusRoles.count(); ++i) {
0565                 QIcon icon;
0566                 switch (statusRoles[i]) {
0567                 case eMyMoney::Model::TransactionErroneousRole:
0568                 case eMyMoney::Model::ScheduleIsOverdueRole:
0569                     icon = style->proxy()->standardIcon(QStyle::SP_MessageBoxWarning, &option, option.widget);
0570                     break;
0571                 case eMyMoney::Model::TransactionIsImportedRole:
0572                     icon = Icons::get(Icons::Icon::TransactionStateImported);
0573                     break;
0574                 case eMyMoney::Model::JournalSplitIsMatchedRole:
0575                     icon = Icons::get(Icons::Icon::Link);
0576                     break;
0577                 default:
0578                     break;
0579                 }
0580                 if (!icon.isNull()) {
0581                     const auto pixmap = icon.pixmap(iconHeight, iconHeight, QIcon::Active, QIcon::On);
0582                     style->proxy()->drawItemPixmap(painter, iconArea, Qt::AlignRight | Qt::AlignTop, pixmap);
0583                     iconArea.setRight(iconArea.right() - (pixmap.width() + margin));
0584                 }
0585             }
0586         }
0587     }
0588 
0589     painter->restore();
0590 }
0591 
0592 void JournalDelegate::resetLineHeight()
0593 {
0594     d->m_lineHeight = -1;
0595 }
0596 
0597 QSize JournalDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
0598 {
0599     // get parameters only once per update to speed things up
0600     if (d->m_lineHeight == -1) {
0601         QStyleOptionViewItem opt = option;
0602         initStyleOption(&opt, index);
0603         QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
0604         d->m_margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin);
0605         d->m_lineHeight = opt.fontMetrics.lineSpacing();
0606     }
0607 
0608     if(index.isValid()) {
0609         // check if we are showing the edit widget
0610         // const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(opt.widget);
0611         if (d->m_view) {
0612             QModelIndex editIndex = d->m_view->model()->index(index.row(), d->m_editorCol);
0613             if(editIndex.isValid()) {
0614                 QWidget* editor = d->m_view->indexWidget(editIndex);
0615                 if(editor) {
0616                     return editor->minimumSizeHint();
0617                 }
0618             }
0619         }
0620     }
0621 
0622     QSize size(10, d->m_lineHeight + 2 * d->m_margin);
0623 
0624     const auto settings = LedgerViewSettings::instance();
0625     if (((option.state & QStyle::State_Selected) && (settings->showLedgerLens())) || settings->showTransactionDetails()) {
0626         auto rows = index.data(eMyMoney::Model::JournalSplitMaxLinesCountRole).toInt();
0627         if (rows == 0) {
0628             // Scan certain rows which may show multiple lines in a table row
0629             QSet<int> columns = {JournalModel::Column::Detail, JournalModel::Column::Deposit, JournalModel::Column::Payment};
0630             for (const auto& column : qAsConst(columns)) {
0631                 const auto idx = index.model()->index(index.row(), column);
0632                 const auto rowCount = d->displayString(idx, option).lines.count() + d->displayMatchedString(idx, option).lines.count();
0633                 if (rowCount > rows) {
0634                     rows = rowCount;
0635                 }
0636             }
0637 
0638             // make sure we show at least one row
0639             if (!rows) {
0640                 rows = 1;
0641             }
0642             // and cache the value in the model
0643             auto model = const_cast<QAbstractItemModel*>(index.model());
0644             model->setData(index, rows, eMyMoney::Model::JournalSplitMaxLinesCountRole);
0645         }
0646         // leave a few pixels as margin for each space between rows
0647         size.setHeight((size.height() * rows) - (d->m_margin * (rows - 1)));
0648     }
0649     return size;
0650 }
0651 
0652 void JournalDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
0653 {
0654     Q_UNUSED(index);
0655 
0656     QRect r(option.rect);
0657     // respect the vertical scrollbar if visible
0658     if (option.widget && d->m_view) {
0659         const auto ofs = d->m_view->verticalScrollBar()->isVisible() ? d->m_view->verticalScrollBar()->width() : 0;
0660         r.setWidth(option.widget->width() - ofs);
0661     }
0662     editor->setGeometry(r);
0663     editor->update();
0664 }
0665 
0666 void JournalDelegate::endEdit()
0667 {
0668     if(d->m_editor) {
0669         if(d->m_editor->accepted()) {
0670             Q_EMIT commitData(d->m_editor);
0671         }
0672         Q_EMIT closeEditor(d->m_editor, NoHint);
0673         d->m_editorRow = -1;
0674         d->m_editorCol = -1;
0675         delete d->m_editor;
0676         d->m_editor = nullptr;
0677     }
0678 }
0679 
0680 /**
0681  * This eventfilter seems to do nothing but it prevents that selecting a
0682  * different row with the mouse closes the editor
0683  */
0684 bool JournalDelegate::eventFilter(QObject* o, QEvent* event)
0685 {
0686     return QAbstractItemDelegate::eventFilter(o, event);
0687 }
0688 
0689 void JournalDelegate::setEditorData(QWidget* editWidget, const QModelIndex& index) const
0690 {
0691     auto* editor = qobject_cast<TransactionEditorBase*>(editWidget);
0692     if(editor) {
0693         editor->loadTransaction(index);
0694     }
0695 }
0696 
0697 void JournalDelegate::setModelData(QWidget* editWidget, QAbstractItemModel* model, const QModelIndex& index) const
0698 {
0699     Q_UNUSED(model)
0700     Q_UNUSED(index)
0701 
0702     auto* editor = qobject_cast<TransactionEditorBase*>(editWidget);
0703     if(editor) {
0704         // the editor may adjust the selection in case it changes when
0705         // it moves the selected transaction(s) around due to a date change.
0706         // therefore, we reselect when we return from saving.
0707         const auto selection = editor->saveTransaction(d->m_view->selectedJournalEntryIds());
0708         QMetaObject::invokeMethod(d->m_view, "setSelectedJournalEntries", Qt::QueuedConnection, Q_ARG(QStringList, selection));
0709     }
0710 }
0711 
0712 void JournalDelegate::setAccountType(eMyMoney::Account::Type accountType)
0713 {
0714     d->m_accountType = accountType;
0715 }