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