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

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 <QScrollBar>
0013 #include <QPainter>
0014 #include <QDebug>
0015 #include <QDate>
0016 #include <QSortFilterProxyModel>
0017 
0018 // ----------------------------------------------------------------------------
0019 // KDE Includes
0020 
0021 #include <KLocalizedString>
0022 #include <KColorScheme>
0023 
0024 // ----------------------------------------------------------------------------
0025 // Project Includes
0026 
0027 #include "mymoneyfile.h"
0028 #include "ledgerview.h"
0029 #include "journalmodel.h"
0030 #include "schedulesjournalmodel.h"
0031 #include "accountsmodel.h"
0032 #include "payeesmodel.h"
0033 #include "newtransactioneditor.h"
0034 #include "investtransactioneditor.h"
0035 #include "mymoneyutils.h"
0036 #include "mymoneysecurity.h"
0037 
0038 // clang-format off
0039 static unsigned char attentionSign[] = {
0040     0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
0041     0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
0042     0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14,
0043     0x08, 0x06, 0x00, 0x00, 0x00, 0x8D, 0x89, 0x1D,
0044     0x0D, 0x00, 0x00, 0x00, 0x04, 0x73, 0x42, 0x49,
0045     0x54, 0x08, 0x08, 0x08, 0x08, 0x7C, 0x08, 0x64,
0046     0x88, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58,
0047     0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72,
0048     0x65, 0x00, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x6E,
0049     0x6B, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2E, 0x6F,
0050     0x72, 0x67, 0x9B, 0xEE, 0x3C, 0x1A, 0x00, 0x00,
0051     0x02, 0x05, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8D,
0052     0xAD, 0x95, 0xBF, 0x4B, 0x5B, 0x51, 0x14, 0xC7,
0053     0x3F, 0x2F, 0xBC, 0x97, 0x97, 0x97, 0x97, 0x77,
0054     0xF3, 0xF2, 0x1C, 0xA4, 0x54, 0x6B, 0x70, 0x10,
0055     0x44, 0x70, 0x2A, 0x91, 0x2E, 0x52, 0x02, 0x55,
0056     0x8A, 0xB5, 0xA3, 0xAB, 0x38, 0x08, 0x66, 0xCC,
0057     0xEE, 0xE0, 0xE2, 0x20, 0xB8, 0x38, 0xB8, 0xB8,
0058     0xF8, 0x1F, 0x38, 0x29, 0xA5, 0x29, 0x74, 0x90,
0059     0x0E, 0x0D, 0x0E, 0x22, 0x1D, 0x44, 0xA8, 0xD0,
0060     0xD4, 0xB4, 0x58, 0x4B, 0x09, 0xF9, 0xF1, 0x4A,
0061     0x3B, 0xD4, 0xD3, 0xE1, 0x55, 0xD3, 0x34, 0xAF,
0062     0x49, 0x6C, 0x3D, 0xF0, 0x85, 0x7B, 0xCF, 0xFD,
0063     0x9E, 0xEF, 0x3D, 0xE7, 0xFE, 0xD4, 0x44, 0x84,
0064     0xDB, 0xB4, 0x48, 0x2F, 0xA4, 0x94, 0xAB, 0xE5,
0065     0x52, 0xAE, 0x96, 0xEB, 0x49, 0x51, 0x44, 0x3A,
0066     0x02, 0x18, 0x88, 0xC7, 0xF1, 0xE3, 0x71, 0x7C,
0067     0x60, 0xA0, 0x1B, 0xBF, 0x6B, 0x86, 0x49, 0xC5,
0068     0x46, 0x3E, 0x47, 0x34, 0x9F, 0x23, 0x9A, 0x54,
0069     0x6C, 0xFC, 0x57, 0x86, 0x40, 0xC6, 0x4B, 0xE1,
0070     0x37, 0xCA, 0x48, 0xA3, 0x8C, 0x78, 0x29, 0x7C,
0071     0x20, 0xD3, 0x31, 0xA6, 0xD3, 0xA0, 0x52, 0x1C,
0072     0x6D, 0x6F, 0x72, 0xD9, 0x28, 0x23, 0xFE, 0x07,
0073     0x64, 0x7B, 0x93, 0x4B, 0xA5, 0x38, 0xFA, 0x27,
0074     0x41, 0x60, 0x6E, 0x74, 0x84, 0x7A, 0xE5, 0x1D,
0075     0x92, 0x54, 0x88, 0xE7, 0x22, 0xD5, 0x12, 0x32,
0076     0x3A, 0x42, 0x1D, 0x98, 0xBB, 0x91, 0x20, 0x60,
0077     0xDA, 0x36, 0x17, 0xFB, 0x7B, 0xC8, 0xC1, 0x4B,
0078     0x04, 0x02, 0xBC, 0x7E, 0x81, 0xEC, 0xEF, 0x21,
0079     0xB6, 0xCD, 0x05, 0x60, 0xF6, 0x2C, 0x68, 0x9A,
0080     0x2C, 0xCF, 0x4C, 0xE1, 0x4B, 0x05, 0x39, 0x3F,
0081     0x69, 0x0A, 0xBE, 0x7F, 0x83, 0x48, 0x05, 0x99,
0082     0x99, 0xC2, 0x37, 0x4D, 0x96, 0x7B, 0x12, 0x04,
0083     0xFA, 0x2D, 0x8B, 0xC6, 0xE9, 0x61, 0x10, 0x2C,
0084     0x15, 0xC4, 0x8A, 0x21, 0x86, 0x8E, 0xFC, 0xF8,
0085     0x12, 0xF4, 0x4F, 0x0F, 0x11, 0xCB, 0xA2, 0x01,
0086     0xF4, 0x77, 0x3D, 0x36, 0x4E, 0x82, 0xF5, 0xA5,
0087     0x05, 0x8C, 0xE1, 0x74, 0xD3, 0x37, 0x34, 0x18,
0088     0x20, 0xF2, 0x8B, 0x3D, 0x9C, 0x86, 0xA5, 0x05,
0089     0x0C, 0x27, 0xC1, 0x7A, 0xC7, 0x63, 0x03, 0x8C,
0090     0x2B, 0x07, 0xBF, 0x5A, 0x6A, 0x66, 0x27, 0x15,
0091     0x64, 0x3A, 0x8B, 0x3C, 0x7A, 0xD8, 0xEA, 0xAB,
0092     0x96, 0x10, 0xE5, 0xE0, 0x03, 0xE3, 0x7F, 0xCD,
0093     0x50, 0x39, 0x6C, 0xAD, 0xAD, 0x10, 0x53, 0xAA,
0094     0x75, 0xD2, 0xF4, 0xBD, 0x00, 0x2D, 0x5C, 0x05,
0095     0x6B, 0x2B, 0xC4, 0x94, 0xC3, 0xD6, 0xEF, 0xFE,
0096     0x6B, 0x41, 0x4D, 0xD3, 0x66, 0xFB, 0x3C, 0xC6,
0097     0x16, 0xE7, 0xDB, 0x97, 0x61, 0xE2, 0x3E, 0x3C,
0098     0xC8, 0xB4, 0x15, 0xC7, 0xE2, 0x3C, 0x91, 0x3E,
0099     0x8F, 0x31, 0x4D, 0xD3, 0x66, 0x5B, 0x4A, 0x06,
0100     0x8C, 0x84, 0xCD, 0x59, 0x61, 0xA7, 0xB5, 0xAC,
0101     0x2B, 0x9C, 0x1C, 0x04, 0x08, 0x1B, 0x2B, 0xEC,
0102     0x20, 0x09, 0x9B, 0x33, 0xC0, 0xB8, 0xDE, 0x65,
0103     0x43, 0x27, 0x9F, 0x9D, 0xA4, 0x1E, 0x16, 0xF0,
0104     0xF9, 0x6D, 0xB0, 0xC3, 0x86, 0x1E, 0xB4, 0xC3,
0105     0x38, 0xD9, 0x49, 0xEA, 0x86, 0x4E, 0xFE, 0xEA,
0106     0x29, 0xF4, 0x2C, 0x8B, 0xDA, 0x71, 0x31, 0x9C,
0107     0xFC, 0xF5, 0x23, 0x32, 0x34, 0x88, 0xDC, 0xBD,
0108     0x13, 0x5C, 0xBF, 0x30, 0xCE, 0x71, 0x11, 0xB1,
0109     0x2C, 0x6A, 0x80, 0xA7, 0xDB, 0x36, 0xAB, 0x4F,
0110     0xA6, 0x89, 0xBA, 0x49, 0x38, 0xFF, 0xD4, 0xBE,
0111     0x4E, 0x00, 0xAF, 0x9E, 0x81, 0x08, 0xD4, 0xEA,
0112     0x01, 0xFE, 0x34, 0x37, 0x09, 0x4F, 0x1F, 0x13,
0113     0xDD, 0x7D, 0xCE, 0xAA, 0x96, 0x72, 0x29, 0x7C,
0114     0xFB, 0xCE, 0x44, 0xB8, 0xD4, 0xCD, 0x2C, 0x66,
0115     0x52, 0xD4, 0x6E, 0xFB, 0x0B, 0xF8, 0x09, 0x63,
0116     0x63, 0x31, 0xE4, 0x85, 0x76, 0x2E, 0x0E, 0x00,
0117     0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
0118     0x42, 0x60, 0x82
0119 };
0120 // clang-format on
0121 
0122 QColor JournalDelegate::m_erroneousColor = QColor(Qt::red);
0123 QColor JournalDelegate::m_importedColor = QColor(Qt::yellow);
0124 QColor JournalDelegate::m_separatorColor = QColor(0xff, 0xf2, 0x9b);
0125 
0126 
0127 
0128 
0129 class JournalDelegate::Private
0130 {
0131 public:
0132     Private()
0133         : m_editor(nullptr)
0134         , m_view(nullptr)
0135         , m_editorRow(-1)
0136         , m_singleLineRole(eMyMoney::Model::SplitPayeeRole)
0137         , m_lineHeight(-1)
0138         , m_margin(2)
0139 
0140     {}
0141 
0142     ~Private()
0143     {
0144     }
0145 
0146     QStringList displayString(const QModelIndex& index, const QStyleOptionViewItem& opt)
0147     {
0148         QStringList lines;
0149         if(index.column() == JournalModel::Column::Detail) {
0150             if (index.data(eMyMoney::Model::TransactionIsInvestmentRole).toBool()) {
0151                 if(opt.state & QStyle::State_Selected) {
0152                     lines << index.data(eMyMoney::Model::SplitActivityRole).toString();
0153                     lines << index.data(eMyMoney::Model::TransactionBrokerageAccountRole).toString();
0154                     lines << index.data(eMyMoney::Model::TransactionInterestCategoryRole).toString();
0155                     lines << index.data(eMyMoney::Model::TransactionFeesCategoryRole).toString();
0156                     lines << index.data(eMyMoney::Model::SplitSingleLineMemoRole).toString();
0157                 } else {
0158                     lines << index.data(eMyMoney::Model::SplitActivityRole).toString();
0159                 }
0160 
0161             } else {
0162                 lines << index.data(m_singleLineRole).toString();
0163                 if(opt.state & QStyle::State_Selected) {
0164                     lines.clear();
0165                     lines << index.data(eMyMoney::Model::Roles::SplitPayeeRole).toString();
0166                     lines << index.data(eMyMoney::Model::Roles::TransactionCounterAccountRole).toString();
0167                     lines << index.data(eMyMoney::Model::Roles::SplitSingleLineMemoRole).toString();
0168 
0169                 } else {
0170                     if(lines.at(0).isEmpty()) {
0171                         lines.clear();
0172                         lines << index.data(eMyMoney::Model::Roles::SplitSingleLineMemoRole).toString();
0173                     }
0174                     if(lines.at(0).isEmpty()) {
0175                         lines << index.data(eMyMoney::Model::Roles::TransactionCounterAccountRole).toString();
0176                     }
0177                 }
0178             }
0179             lines.removeAll(QString());
0180 
0181         } else if(index.column() == JournalModel::Column::Quantity) {
0182             if (index.data(eMyMoney::Model::TransactionIsInvestmentRole).toBool()) {
0183                 lines << opt.text;
0184                 if(opt.state & QStyle::State_Selected) {
0185                     // we have to pay attention here as later on empty items will be removed
0186                     // from the lines all together. Since we use the column detail as label
0187                     // we have to make that we are not off. Therefor, if the detail column
0188                     // is filled, we add a simple blank here instead of an empty line.
0189                     // The first line is always present, so we make sure it is not empty in this column.
0190                     if (lines[0].isEmpty())
0191                         lines[0] = QStringLiteral(" ");
0192                     lines << (index.data(eMyMoney::Model::TransactionBrokerageAccountRole).toString().isEmpty() ? QString() : QStringLiteral(" "));
0193 
0194                     MyMoneySecurity currency = MyMoneyFile::instance()->currency(index.data(eMyMoney::Model::TransactionCommodityRole).toString());
0195 
0196                     if (index.data(eMyMoney::Model::TransactionInterestSplitPresentRole).toBool()) {
0197                         const auto value = index.data(eMyMoney::Model::TransactionInterestValueRole).value<MyMoneyMoney>();
0198                         lines << (index.data(eMyMoney::Model::TransactionInterestCategoryRole).toString().isEmpty() ? QString() : MyMoneyUtils::formatMoney(value.abs(), currency));
0199                     }
0200 
0201                     if (index.data(eMyMoney::Model::TransactionFeeSplitPresentRole).toBool()) {
0202                         const auto value = index.data(eMyMoney::Model::TransactionFeesValueRole).value<MyMoneyMoney>();
0203                         lines << (index.data(eMyMoney::Model::TransactionFeesCategoryRole).toString().isEmpty() ? QString() : MyMoneyUtils::formatMoney(value.abs(), currency));
0204                     }
0205                 } else {
0206                     lines << opt.text;
0207                 }
0208             }
0209             lines.removeAll(QString());
0210 
0211         } else {
0212             lines << opt.text;
0213         }
0214         return lines;
0215     }
0216 
0217     TransactionEditorBase*        m_editor;
0218     LedgerView*                   m_view;
0219     int                           m_editorRow;
0220     eMyMoney::Model::Roles        m_singleLineRole;
0221     int                           m_lineHeight;
0222     int                           m_margin;
0223     int                           m_editorWidthOfs;
0224 };
0225 
0226 
0227 JournalDelegate::JournalDelegate(LedgerView* parent)
0228     : KMMStyledItemDelegate(parent)
0229     , d(new Private)
0230 {
0231     d->m_view = parent;
0232 }
0233 
0234 JournalDelegate::~JournalDelegate()
0235 {
0236     delete d;
0237 }
0238 
0239 void JournalDelegate::setErroneousColor(const QColor& color)
0240 {
0241     m_erroneousColor = color;
0242 }
0243 
0244 void JournalDelegate::setSingleLineRole(eMyMoney::Model::Roles role)
0245 {
0246     d->m_singleLineRole = role;
0247 }
0248 
0249 QWidget* JournalDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
0250 {
0251     Q_UNUSED(option);
0252 
0253     if(index.isValid()) {
0254         if(d->m_view->selectionModel()->selectedRows().count() > 1) {
0255             qDebug() << "Editing multiple transactions at once is not yet supported";
0256 
0257             /**
0258              * @todo replace the following three lines with the creation of a special
0259              * editor that can handle multiple transactions at once
0260              */
0261             d->m_editor = nullptr;
0262             JournalDelegate* that = const_cast<JournalDelegate*>(this);
0263             emit that->closeEditor(d->m_editor, NoHint);
0264 
0265         } else {
0266             auto accountId = index.data(eMyMoney::Model::SplitAccountIdRole).toString();
0267             if (accountId.isEmpty() || (accountId == MyMoneyFile::instance()->journalModel()->fakeId())) {
0268                 accountId = d->m_view->accountId();
0269             }
0270             if (!accountId.isEmpty()) {
0271                 // now determine which editor to use. In case we have no transaction (yet)
0272                 // we use the account type
0273                 if (index.data(eMyMoney::Model::JournalTransactionIdRole).toString().isEmpty()) {
0274                     const auto acc = MyMoneyFile::instance()->accountsModel()->itemById(accountId);
0275                     if (acc.accountType() == eMyMoney::Account::Type::Investment) {
0276                         d->m_editor = new InvestTransactionEditor(parent, accountId);
0277                     } else {
0278                         d->m_editor = new NewTransactionEditor(parent, accountId);
0279                     }
0280                 } else {
0281                     if (index.data(eMyMoney::Model::TransactionIsInvestmentRole).toBool()) {
0282                         // in case of an investment transaction we need to use
0283                         // the parent account of the security account and pass
0284                         // it to the editor.
0285                         accountId = index.data(eMyMoney::Model::TransactionInvestmentAccountIdRole).toString();
0286                         d->m_editor = new InvestTransactionEditor(parent, accountId);
0287                     } else {
0288                         d->m_editor = new NewTransactionEditor(parent, accountId);
0289                     }
0290                 }
0291                 d->m_editorWidthOfs = 8;
0292                 if(d->m_view) {
0293                     if(d->m_view->verticalScrollBar()->isVisible()) {
0294                         d->m_editorWidthOfs += d->m_view->verticalScrollBar()->width();
0295                     }
0296                 }
0297 
0298             } else {
0299                 qDebug() << "Unable to determine account for editing";
0300 
0301                 d->m_editor = nullptr;
0302                 JournalDelegate* that = const_cast<JournalDelegate*>(this);
0303                 emit that->closeEditor(d->m_editor, NoHint);
0304             }
0305         }
0306 
0307         if(d->m_editor) {
0308             d->m_editorRow = index.row();
0309             connect(d->m_editor, &TransactionEditorBase::done, this, &JournalDelegate::endEdit);
0310             JournalDelegate* that = const_cast<JournalDelegate*>(this);
0311             emit that->sizeHintChanged(index);
0312         }
0313 
0314     } else {
0315         qFatal("JournalDelegate::createEditor(): we should never end up here");
0316     }
0317     return d->m_editor;
0318 }
0319 
0320 int JournalDelegate::editorRow() const
0321 {
0322     return d->m_editorRow;
0323 }
0324 
0325 void JournalDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
0326 {
0327     QStyleOptionViewItem opt = option;
0328     initStyleOption(&opt, index);
0329 
0330     // never change the background of the cell the mouse is hovering over
0331     opt.state &= ~QStyle::State_MouseOver;
0332 
0333     // show the focus only on the detail column
0334     opt.state &= ~QStyle::State_HasFocus;
0335 
0336     // if selected, always show as active, so that the
0337     // background does not change when the editor is shown
0338     if (opt.state & QStyle::State_Selected) {
0339         opt.state |= QStyle::State_Active;
0340     }
0341 
0342     painter->save();
0343 
0344     QAbstractItemView* view = qobject_cast< QAbstractItemView* >(parent());
0345     const bool editWidgetIsVisible = d->m_view && d->m_view->indexWidget(index);
0346 
0347     // Background
0348     QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
0349     const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin);
0350     const int lineHeight = opt.fontMetrics.lineSpacing() + 2;
0351 
0352     style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);
0353 
0354     QPalette::ColorGroup cg;
0355 
0356     // Do not paint text if the edit widget is shown
0357     if (!editWidgetIsVisible) {
0358         bool isOverdue = false;
0359         if(view && (index.column() == JournalModel::Column::Detail)) {
0360             if(view->currentIndex().row() == index.row()) {
0361                 opt.state |= QStyle::State_HasFocus;
0362             }
0363         }
0364         const QRect textArea = QRect(opt.rect.x() + margin, opt.rect.y() + margin, opt.rect.width() - 2 * margin, opt.rect.height() - 2 * margin);
0365         const bool selected = opt.state & QStyle::State_Selected;
0366 
0367         QStringList lines = d->displayString(index, opt);
0368 
0369         const bool erroneous = index.data(eMyMoney::Model::Roles::TransactionErroneousRole).toBool();
0370 
0371         // draw the text items
0372         if(!opt.text.isEmpty() || !lines.isEmpty()) {
0373 
0374             // check if it is a scheduled transaction and display it as inactive
0375             if (MyMoneyFile::baseModel()->baseModel(index) == MyMoneyFile::instance()->schedulesJournalModel()) {
0376                 opt.state &= ~QStyle::State_Enabled;
0377                 isOverdue = index.data(eMyMoney::Model::ScheduleIsOverdueRole).toBool();
0378             }
0379             cg = (opt.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled;
0380 
0381             if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) {
0382                 cg = QPalette::Inactive;
0383             }
0384             if (selected) {
0385                 // always use the normal palette since the background is also in normal
0386                 painter->setPen(opt.palette.color(QPalette::ColorGroup(QPalette::Normal), QPalette::HighlightedText));
0387 
0388             } else if (erroneous) {
0389                 painter->setPen(m_erroneousColor);
0390 
0391             } else {
0392                 painter->setPen(opt.palette.color(cg, QPalette::Text));
0393             }
0394 
0395             if (opt.state & QStyle::State_Editing) {
0396                 painter->setPen(opt.palette.color(cg, QPalette::Text));
0397                 painter->drawRect(textArea.adjusted(0, 0, -1, -1));
0398             }
0399 
0400             // collect data for the various columns
0401             for(int i = 0; i < lines.count(); ++i) {
0402                 painter->drawText(textArea.adjusted(0, lineHeight * i, 0, 0), opt.displayAlignment, lines[i]);
0403             }
0404         }
0405 
0406         // draw the focus rect
0407         if(opt.state & QStyle::State_HasFocus) {
0408             QStyleOptionFocusRect o;
0409             o.QStyleOption::operator=(opt);
0410             o.rect = style->proxy()->subElementRect(QStyle::SE_ItemViewItemFocusRect, &opt, opt.widget);
0411             o.state |= QStyle::State_KeyboardFocusChange;
0412             o.state |= QStyle::State_Item;
0413 
0414             cg = (opt.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled;
0415             o.backgroundColor = opt.palette.color(cg, (opt.state & QStyle::State_Selected)
0416                                                   ? QPalette::Highlight : QPalette::Window);
0417             style->proxy()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, opt.widget);
0418         }
0419 
0420         // draw the attention mark
0421         if((index.column() == JournalModel::Column::Detail)
0422                 && (erroneous || isOverdue)) {
0423             QPixmap attention;
0424             attention.loadFromData(attentionSign, sizeof(attentionSign), 0, 0);
0425             style->proxy()->drawItemPixmap(painter, option.rect, Qt::AlignRight | Qt::AlignTop, attention);
0426         }
0427     }
0428 
0429     painter->restore();
0430 }
0431 
0432 QSize JournalDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
0433 {
0434     // get parameters only once per update to speed things up
0435     if (d->m_lineHeight == -1) {
0436         QStyleOptionViewItem opt = option;
0437         initStyleOption(&opt, index);
0438         QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
0439         d->m_margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin);
0440         d->m_lineHeight = opt.fontMetrics.lineSpacing();
0441     }
0442     int rows = 1;
0443 
0444     if(index.isValid()) {
0445         // check if we are showing the edit widget
0446         // const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(opt.widget);
0447         if (d->m_view) {
0448             QModelIndex editIndex = d->m_view->model()->index(index.row(), 0);
0449             if(editIndex.isValid()) {
0450                 QWidget* editor = d->m_view->indexWidget(editIndex);
0451                 if(editor) {
0452                     return editor->minimumSizeHint();
0453                 }
0454             }
0455         }
0456     }
0457 
0458     QSize size(10, d->m_lineHeight + 2 * d->m_margin);
0459 
0460     if(option.state & QStyle::State_Selected) {
0461         rows = d->displayString(index, option).count();
0462 
0463         // make sure we show at least one row
0464         if(!rows) {
0465             rows = 1;
0466         }
0467         // leave a few pixels as margin for each space between rows
0468         size.setHeight((size.height() * rows) - (d->m_margin * (rows - 1)));
0469     }
0470     return size;
0471 }
0472 
0473 void JournalDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
0474 {
0475     Q_UNUSED(index);
0476 
0477     QRect r(option.rect);
0478     // respect the vertical scrollbar if visible
0479     if (option.widget
0480             && d->m_view
0481             && d->m_view->verticalScrollBar()->isVisible() ) {
0482         r.setWidth(option.widget->width() - d->m_view->verticalScrollBar()->width());
0483     }
0484     editor->setGeometry(r);
0485     editor->update();
0486 }
0487 
0488 void JournalDelegate::endEdit()
0489 {
0490     if(d->m_editor) {
0491         if(d->m_editor->accepted()) {
0492             emit commitData(d->m_editor);
0493         }
0494         emit closeEditor(d->m_editor, NoHint);
0495         d->m_editorRow = -1;
0496         d->m_editor = nullptr;
0497     }
0498 }
0499 
0500 /**
0501  * This eventfilter seems to do nothing but it prevents that selecting a
0502  * different row with the mouse closes the editor
0503  */
0504 bool JournalDelegate::eventFilter(QObject* o, QEvent* event)
0505 {
0506     return QAbstractItemDelegate::eventFilter(o, event);
0507 }
0508 
0509 void JournalDelegate::setEditorData(QWidget* editWidget, const QModelIndex& index) const
0510 {
0511     auto* editor = qobject_cast<TransactionEditorBase*>(editWidget);
0512     if(editor) {
0513         editor->loadTransaction(index);
0514     }
0515 }
0516 
0517 void JournalDelegate::setModelData(QWidget* editWidget, QAbstractItemModel* model, const QModelIndex& index) const
0518 {
0519     Q_UNUSED(model)
0520     Q_UNUSED(index)
0521 
0522     auto* editor = qobject_cast<TransactionEditorBase*>(editWidget);
0523     if(editor) {
0524         // saving the transaction may move the selected transaction(s) around
0525         // we keep the transaction IDs here and take care of them when we return
0526         const auto selection = d->m_view->selectedTransactions();
0527 
0528         editor->saveTransaction();
0529 
0530         d->m_view->setSelectedTransactions(selection);
0531     }
0532 }