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 }