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