File indexing completed on 2024-05-12 16:44:09

0001 /*
0002     SPDX-FileCopyrightText: 2006-2018 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "transaction.h"
0008 #include "transaction_p.h"
0009 
0010 // ----------------------------------------------------------------------------
0011 // QT Includes
0012 
0013 #include <QString>
0014 #include <QPainter>
0015 #include <QWidget>
0016 #include <QList>
0017 #include <QPixmap>
0018 #include <QHeaderView>
0019 #include <QApplication>
0020 #include <QTextDocument>
0021 #include <QAbstractTextDocumentLayout>
0022 
0023 // ----------------------------------------------------------------------------
0024 // KDE Includes
0025 
0026 #include <KLocalizedString>
0027 
0028 // ----------------------------------------------------------------------------
0029 // Project Includes
0030 
0031 #include "mymoneyutils.h"
0032 #include "mymoneytransaction.h"
0033 #include "mymoneysplit.h"
0034 #include "mymoneyfile.h"
0035 #include "mymoneysecurity.h"
0036 #include "mymoneypayee.h"
0037 #include "mymoneytag.h"
0038 #include "register.h"
0039 #include "kmymoneycategory.h"
0040 #include "kmymoneydateinput.h"
0041 #include "transactionform.h"
0042 #include "kmymoneyutils.h"
0043 #include "registerfilter.h"
0044 #include "tabbar.h"
0045 
0046 #include "kmymoneysettings.h"
0047 #include "widgetenums.h"
0048 #include "mymoneyenums.h"
0049 
0050 using namespace eWidgets;
0051 using namespace KMyMoneyRegister;
0052 using namespace KMyMoneyTransactionForm;
0053 
0054 // clang-format off
0055 static unsigned char attentionSign[] = {
0056     0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
0057     0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
0058     0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14,
0059     0x08, 0x06, 0x00, 0x00, 0x00, 0x8D, 0x89, 0x1D,
0060     0x0D, 0x00, 0x00, 0x00, 0x04, 0x73, 0x42, 0x49,
0061     0x54, 0x08, 0x08, 0x08, 0x08, 0x7C, 0x08, 0x64,
0062     0x88, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58,
0063     0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72,
0064     0x65, 0x00, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x6E,
0065     0x6B, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2E, 0x6F,
0066     0x72, 0x67, 0x9B, 0xEE, 0x3C, 0x1A, 0x00, 0x00,
0067     0x02, 0x05, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8D,
0068     0xAD, 0x95, 0xBF, 0x4B, 0x5B, 0x51, 0x14, 0xC7,
0069     0x3F, 0x2F, 0xBC, 0x97, 0x97, 0x97, 0x97, 0x77,
0070     0xF3, 0xF2, 0x1C, 0xA4, 0x54, 0x6B, 0x70, 0x10,
0071     0x44, 0x70, 0x2A, 0x91, 0x2E, 0x52, 0x02, 0x55,
0072     0x8A, 0xB5, 0xA3, 0xAB, 0x38, 0x08, 0x66, 0xCC,
0073     0xEE, 0xE0, 0xE2, 0x20, 0xB8, 0x38, 0xB8, 0xB8,
0074     0xF8, 0x1F, 0x38, 0x29, 0xA5, 0x29, 0x74, 0x90,
0075     0x0E, 0x0D, 0x0E, 0x22, 0x1D, 0x44, 0xA8, 0xD0,
0076     0xD4, 0xB4, 0x58, 0x4B, 0x09, 0xF9, 0xF1, 0x4A,
0077     0x3B, 0xD4, 0xD3, 0xE1, 0x55, 0xD3, 0x34, 0xAF,
0078     0x49, 0x6C, 0x3D, 0xF0, 0x85, 0x7B, 0xCF, 0xFD,
0079     0x9E, 0xEF, 0x3D, 0xE7, 0xFE, 0xD4, 0x44, 0x84,
0080     0xDB, 0xB4, 0x48, 0x2F, 0xA4, 0x94, 0xAB, 0xE5,
0081     0x52, 0xAE, 0x96, 0xEB, 0x49, 0x51, 0x44, 0x3A,
0082     0x02, 0x18, 0x88, 0xC7, 0xF1, 0xE3, 0x71, 0x7C,
0083     0x60, 0xA0, 0x1B, 0xBF, 0x6B, 0x86, 0x49, 0xC5,
0084     0x46, 0x3E, 0x47, 0x34, 0x9F, 0x23, 0x9A, 0x54,
0085     0x6C, 0xFC, 0x57, 0x86, 0x40, 0xC6, 0x4B, 0xE1,
0086     0x37, 0xCA, 0x48, 0xA3, 0x8C, 0x78, 0x29, 0x7C,
0087     0x20, 0xD3, 0x31, 0xA6, 0xD3, 0xA0, 0x52, 0x1C,
0088     0x6D, 0x6F, 0x72, 0xD9, 0x28, 0x23, 0xFE, 0x07,
0089     0x64, 0x7B, 0x93, 0x4B, 0xA5, 0x38, 0xFA, 0x27,
0090     0x41, 0x60, 0x6E, 0x74, 0x84, 0x7A, 0xE5, 0x1D,
0091     0x92, 0x54, 0x88, 0xE7, 0x22, 0xD5, 0x12, 0x32,
0092     0x3A, 0x42, 0x1D, 0x98, 0xBB, 0x91, 0x20, 0x60,
0093     0xDA, 0x36, 0x17, 0xFB, 0x7B, 0xC8, 0xC1, 0x4B,
0094     0x04, 0x02, 0xBC, 0x7E, 0x81, 0xEC, 0xEF, 0x21,
0095     0xB6, 0xCD, 0x05, 0x60, 0xF6, 0x2C, 0x68, 0x9A,
0096     0x2C, 0xCF, 0x4C, 0xE1, 0x4B, 0x05, 0x39, 0x3F,
0097     0x69, 0x0A, 0xBE, 0x7F, 0x83, 0x48, 0x05, 0x99,
0098     0x99, 0xC2, 0x37, 0x4D, 0x96, 0x7B, 0x12, 0x04,
0099     0xFA, 0x2D, 0x8B, 0xC6, 0xE9, 0x61, 0x10, 0x2C,
0100     0x15, 0xC4, 0x8A, 0x21, 0x86, 0x8E, 0xFC, 0xF8,
0101     0x12, 0xF4, 0x4F, 0x0F, 0x11, 0xCB, 0xA2, 0x01,
0102     0xF4, 0x77, 0x3D, 0x36, 0x4E, 0x82, 0xF5, 0xA5,
0103     0x05, 0x8C, 0xE1, 0x74, 0xD3, 0x37, 0x34, 0x18,
0104     0x20, 0xF2, 0x8B, 0x3D, 0x9C, 0x86, 0xA5, 0x05,
0105     0x0C, 0x27, 0xC1, 0x7A, 0xC7, 0x63, 0x03, 0x8C,
0106     0x2B, 0x07, 0xBF, 0x5A, 0x6A, 0x66, 0x27, 0x15,
0107     0x64, 0x3A, 0x8B, 0x3C, 0x7A, 0xD8, 0xEA, 0xAB,
0108     0x96, 0x10, 0xE5, 0xE0, 0x03, 0xE3, 0x7F, 0xCD,
0109     0x50, 0x39, 0x6C, 0xAD, 0xAD, 0x10, 0x53, 0xAA,
0110     0x75, 0xD2, 0xF4, 0xBD, 0x00, 0x2D, 0x5C, 0x05,
0111     0x6B, 0x2B, 0xC4, 0x94, 0xC3, 0xD6, 0xEF, 0xFE,
0112     0x6B, 0x41, 0x4D, 0xD3, 0x66, 0xFB, 0x3C, 0xC6,
0113     0x16, 0xE7, 0xDB, 0x97, 0x61, 0xE2, 0x3E, 0x3C,
0114     0xC8, 0xB4, 0x15, 0xC7, 0xE2, 0x3C, 0x91, 0x3E,
0115     0x8F, 0x31, 0x4D, 0xD3, 0x66, 0x5B, 0x4A, 0x06,
0116     0x8C, 0x84, 0xCD, 0x59, 0x61, 0xA7, 0xB5, 0xAC,
0117     0x2B, 0x9C, 0x1C, 0x04, 0x08, 0x1B, 0x2B, 0xEC,
0118     0x20, 0x09, 0x9B, 0x33, 0xC0, 0xB8, 0xDE, 0x65,
0119     0x43, 0x27, 0x9F, 0x9D, 0xA4, 0x1E, 0x16, 0xF0,
0120     0xF9, 0x6D, 0xB0, 0xC3, 0x86, 0x1E, 0xB4, 0xC3,
0121     0x38, 0xD9, 0x49, 0xEA, 0x86, 0x4E, 0xFE, 0xEA,
0122     0x29, 0xF4, 0x2C, 0x8B, 0xDA, 0x71, 0x31, 0x9C,
0123     0xFC, 0xF5, 0x23, 0x32, 0x34, 0x88, 0xDC, 0xBD,
0124     0x13, 0x5C, 0xBF, 0x30, 0xCE, 0x71, 0x11, 0xB1,
0125     0x2C, 0x6A, 0x80, 0xA7, 0xDB, 0x36, 0xAB, 0x4F,
0126     0xA6, 0x89, 0xBA, 0x49, 0x38, 0xFF, 0xD4, 0xBE,
0127     0x4E, 0x00, 0xAF, 0x9E, 0x81, 0x08, 0xD4, 0xEA,
0128     0x01, 0xFE, 0x34, 0x37, 0x09, 0x4F, 0x1F, 0x13,
0129     0xDD, 0x7D, 0xCE, 0xAA, 0x96, 0x72, 0x29, 0x7C,
0130     0xFB, 0xCE, 0x44, 0xB8, 0xD4, 0xCD, 0x2C, 0x66,
0131     0x52, 0xD4, 0x6E, 0xFB, 0x0B, 0xF8, 0x09, 0x63,
0132     0x63, 0x31, 0xE4, 0x85, 0x76, 0x2E, 0x0E, 0x00,
0133     0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
0134     0x42, 0x60, 0x82
0135 };
0136 // clang-format on
0137 
0138 Transaction::Transaction(Register *parent, const MyMoneyTransaction& transaction, const MyMoneySplit& split, int uniqueId) :
0139     RegisterItem(*new TransactionPrivate, parent)
0140 {
0141     Q_D(Transaction);
0142     d->m_transaction = transaction;
0143     d->m_split = split;
0144     d->m_form = nullptr;
0145     d->m_uniqueId = d->m_transaction.id();
0146     d->init(uniqueId);
0147 }
0148 
0149 Transaction::Transaction(TransactionPrivate &dd, Register* parent, const MyMoneyTransaction& transaction, const MyMoneySplit& split, int uniqueId) :
0150     RegisterItem(dd, parent)
0151 {
0152     Q_D(Transaction);
0153     d->m_form = nullptr;
0154     d->m_transaction = transaction;
0155     d->m_split = split;
0156     d->m_uniqueId = d->m_transaction.id();
0157     d->init(uniqueId);
0158 }
0159 
0160 Transaction::~Transaction()
0161 {
0162 }
0163 
0164 const char* Transaction::className()
0165 {
0166     return "Transaction";
0167 }
0168 
0169 bool Transaction::isSelectable() const
0170 {
0171     return true;
0172 }
0173 
0174 bool Transaction::isSelected() const
0175 {
0176     Q_D(const Transaction);
0177     return d->m_selected;
0178 }
0179 
0180 void Transaction::setSelected(bool selected)
0181 {
0182     Q_D(Transaction);
0183     if (!selected || (selected && isVisible()))
0184         d->m_selected = selected;
0185 }
0186 
0187 bool Transaction::canHaveFocus() const
0188 {
0189     return true;
0190 }
0191 
0192 bool Transaction::hasFocus() const
0193 {
0194     Q_D(const Transaction);
0195     return d->m_focus;
0196 }
0197 
0198 bool Transaction::hasEditorOpen() const
0199 {
0200     Q_D(const Transaction);
0201     return d->m_inEdit;
0202 }
0203 
0204 bool Transaction::isScheduled() const
0205 {
0206     return false;
0207 }
0208 
0209 void Transaction::setFocus(bool focus, bool updateLens)
0210 {
0211     Q_D(Transaction);
0212     if (focus != d->m_focus) {
0213         d->m_focus = focus;
0214     }
0215     if (updateLens) {
0216         if (KMyMoneySettings::ledgerLens()
0217                 || !KMyMoneySettings::transactionForm()
0218                 || KMyMoneySettings::showRegisterDetailed()
0219                 || d->m_parent->ledgerLens()) {
0220             if (focus)
0221                 setNumRowsRegister(numRowsRegister(true));
0222             else
0223                 setNumRowsRegister(numRowsRegister(KMyMoneySettings::showRegisterDetailed()));
0224         }
0225     }
0226 }
0227 
0228 bool Transaction::isErroneous() const
0229 {
0230     Q_D(const Transaction);
0231     return d->m_erroneous;
0232 }
0233 
0234 QDate Transaction::sortPostDate() const
0235 {
0236     Q_D(const Transaction);
0237     return d->m_transaction.postDate();
0238 }
0239 
0240 int Transaction::sortSamePostDate() const
0241 {
0242     return 2;
0243 }
0244 
0245 QDate Transaction::sortEntryDate() const
0246 {
0247     Q_D(const Transaction);
0248     return d->m_transaction.entryDate();
0249 }
0250 
0251 const QString& Transaction::sortPayee() const
0252 {
0253     Q_D(const Transaction);
0254     return d->m_payee;
0255 }
0256 
0257 const QList<QString>& Transaction::sortTagList() const
0258 {
0259     Q_D(const Transaction);
0260     return d->m_tagList;
0261 }
0262 
0263 MyMoneyMoney Transaction::sortValue() const
0264 {
0265     Q_D(const Transaction);
0266     return d->m_split.shares();
0267 }
0268 
0269 QString Transaction::sortNumber() const
0270 {
0271     Q_D(const Transaction);
0272     return d->m_split.number();
0273 }
0274 
0275 const QString& Transaction::sortEntryOrder() const
0276 {
0277     Q_D(const Transaction);
0278     return d->m_uniqueId;
0279 }
0280 
0281 eRegister::CashFlowDirection Transaction::sortType() const
0282 {
0283     Q_D(const Transaction);
0284     return d->m_split.shares().isNegative() ? eRegister::CashFlowDirection::Payment : eRegister::CashFlowDirection::Deposit;
0285 }
0286 
0287 const QString& Transaction::sortCategory() const
0288 {
0289     Q_D(const Transaction);
0290     return d->m_category;
0291 }
0292 
0293 eMyMoney::Split::State Transaction::sortReconcileState() const
0294 {
0295     Q_D(const Transaction);
0296     return d->m_split.reconcileFlag();
0297 }
0298 
0299 QString Transaction::id() const
0300 {
0301     Q_D(const Transaction);
0302     return d->m_uniqueId;
0303 }
0304 
0305 const MyMoneyTransaction& Transaction::transaction() const
0306 {
0307     Q_D(const Transaction);
0308     return d->m_transaction;
0309 }
0310 
0311 const MyMoneySplit& Transaction::split() const
0312 {
0313     Q_D(const Transaction);
0314     return d->m_split;
0315 }
0316 
0317 void Transaction::setBalance(const MyMoneyMoney& balance)
0318 {
0319     Q_D(Transaction);
0320     d->m_balance = balance;
0321 }
0322 
0323 const MyMoneyMoney& Transaction::balance() const
0324 {
0325     Q_D(const Transaction);
0326     return d->m_balance;
0327 }
0328 
0329 bool Transaction::paintRegisterCellSetup(QPainter *painter, QStyleOptionViewItem &option, const QModelIndex &index)
0330 {
0331     Q_D(Transaction);
0332     Q_UNUSED(painter)
0333 
0334     if (d->m_reducedIntensity) {
0335         option.palette.setColor(QPalette::Text, option.palette.color(QPalette::Disabled, QPalette::Text));
0336     }
0337 
0338     if (d->m_selected) {
0339         option.state |= QStyle::State_Selected;
0340     } else {
0341         option.state &= ~QStyle::State_Selected;
0342     }
0343 
0344     if (d->m_focus) {
0345         option.state |= QStyle::State_HasFocus;
0346     } else {
0347         option.state &= ~QStyle::State_HasFocus;
0348     }
0349 
0350     if (option.widget && option.widget->hasFocus()) {
0351         option.palette.setCurrentColorGroup(QPalette::Active);
0352     } else {
0353         option.palette.setCurrentColorGroup(QPalette::Inactive);
0354     }
0355 
0356     if (index.column() == 0) {
0357         option.viewItemPosition = QStyleOptionViewItem::Beginning;
0358     } else if (index.column() == (int)eTransaction::Column::LastColumn - 1) {
0359         option.viewItemPosition = QStyleOptionViewItem::End;
0360     } else {
0361         option.viewItemPosition = QStyleOptionViewItem::Middle;
0362     }
0363 
0364     // do we need to switch to the error color?
0365     if (d->m_erroneous) {
0366         option.palette.setColor(QPalette::Text, KMyMoneySettings::schemeColor(SchemeColor::TransactionErroneous));
0367     }
0368 
0369     // do we need to switch to the negative balance color?
0370     if (index.column() == (int)eTransaction::Column::Balance) {
0371         bool showNegative = d->m_balance.isNegative();
0372         if (d->m_account.accountGroup() == eMyMoney::Account::Type::Liability && !d->m_balance.isZero())
0373             showNegative = !showNegative;
0374         if (showNegative)
0375             option.palette.setColor(QPalette::Text, KMyMoneySettings::schemeColor(SchemeColor::TransactionErroneous));
0376     }
0377     return true;
0378 }
0379 
0380 void Transaction::registerCellText(QString& txt, int row, int col)
0381 {
0382     Qt::Alignment align;
0383     registerCellText(txt, align, row, col, 0);
0384 }
0385 
0386 void Transaction::paintRegisterCell(QPainter *painter, QStyleOptionViewItem &option, const QModelIndex &index)
0387 {
0388     Q_D(Transaction);
0389     painter->save();
0390     if (paintRegisterCellSetup(painter, option, index)) {
0391         const QStyle *style = option.widget ? option.widget->style() : QApplication::style();
0392         const QWidget* widget = option.widget;
0393 
0394         // clear the mouse over state before painting the background
0395         option.state &= ~QStyle::State_MouseOver;
0396         // the background
0397         if (option.state & QStyle::State_Selected || option.state & QStyle::State_HasFocus) {
0398             // if this is not the first row of the transaction paint the previous rows
0399             // since the selection background is painted from the first row of the transaction
0400             if (index.row() > startRow()) {
0401                 QStyleOptionViewItem optionSibling = option;
0402                 QModelIndex previousRowItem = index.sibling(index.row() - 1, index.column());
0403                 optionSibling.rect = d->m_parent->visualRect(previousRowItem);
0404                 paintRegisterCell(painter, optionSibling, previousRowItem);
0405             }
0406             // paint the selection background only from the first row on to the last row at once
0407             if (index.row() == startRow()) {
0408                 QRect old = option.rect;
0409                 int extraHeight = 0;
0410                 if (d->m_inRegisterEdit) {
0411                     // since, when editing a transaction inside the register (without the transaction form),
0412                     // row heights can have various sizes (the memo row is larger than the rest) we have
0413                     // to iterate over all the items of the transaction to compute the size of the selection rectangle
0414                     // of course we start with the item after this one because it's size is already in the rectangle
0415                     for (int i = startRow() + 1; i < startRow() + numRowsRegister(); ++i) {
0416                         extraHeight += d->m_parent->visualRect(index.sibling(i, index.column())).height();
0417                     }
0418                 } else {
0419                     // we are not editing in the register so all rows have the same sizes just compute the extra height
0420                     extraHeight = (numRowsRegister() - 1) * option.rect.height();
0421                 }
0422                 option.rect.setBottom(option.rect.bottom() + extraHeight);
0423                 style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, widget);
0424                 if (d->m_focus && index.column() == (int)eTransaction::Column::Detail) {
0425                     option.state |= QStyle::State_HasFocus;
0426                     style->drawPrimitive(QStyle::PE_FrameFocusRect, &option, painter, widget);
0427                 }
0428                 option.rect = old;
0429             }
0430         } else {
0431             if (d->m_alternate) {
0432                 painter->fillRect(option.rect, option.palette.alternateBase());
0433             } else {
0434                 painter->fillRect(option.rect, option.palette.base());
0435             }
0436         }
0437 
0438         // the text
0439         // construct the text for the cell
0440         QString txt;
0441         option.displayAlignment = Qt::AlignVCenter;
0442         if (!d->m_transaction.id().isEmpty() && !d->m_inRegisterEdit) {
0443             registerCellText(txt, option.displayAlignment, index.row() - startRow(), index.column(), painter);
0444         }
0445 
0446         if (Qt::mightBeRichText(txt)) {
0447             QTextDocument document;
0448             // this should set the alignment of the html but it does not work so htmls will be left aligned
0449             document.setDefaultTextOption(QTextOption(option.displayAlignment));
0450             document.setDocumentMargin(2);
0451             document.setHtml(txt);
0452             painter->translate(option.rect.topLeft());
0453             QAbstractTextDocumentLayout::PaintContext ctx;
0454             ctx.palette = option.palette;
0455             // Highlighting text if item is selected
0456             if (d->m_selected)
0457                 ctx.palette.setColor(QPalette::Text, option.palette.color(QPalette::HighlightedText));
0458             document.documentLayout()->draw(painter, ctx);
0459             painter->translate(-option.rect.topLeft());
0460         } else {
0461             // draw plain text properly aligned
0462             style->drawItemText(painter, option.rect.adjusted(2, 0, -2, 0), option.displayAlignment, option.palette, true, txt, d->m_selected ? QPalette::HighlightedText : QPalette::Text);
0463         }
0464 
0465         // draw the grid if it's needed
0466         if (KMyMoneySettings::showGrid()) {
0467             const int gridHint = style->styleHint(QStyle::SH_Table_GridLineColor, &option, widget);
0468             const QPen gridPen = QPen(QColor(static_cast<QRgb>(gridHint)), 0);
0469             QPen old = painter->pen();
0470             painter->setPen(gridPen);
0471             if (index.row() == startRow())
0472                 painter->drawLine(option.rect.topLeft(), option.rect.topRight());
0473             painter->drawLine(option.rect.topLeft(), option.rect.bottomLeft());
0474             painter->setPen(old);
0475         }
0476 
0477         // possible icons
0478         if (index.row() == startRow() && index.column() == (int)eTransaction::Column::Detail) {
0479             if (d->m_erroneous) {
0480                 QPixmap attention;
0481                 attention.loadFromData(attentionSign, sizeof(attentionSign), 0, 0);
0482                 style->drawItemPixmap(painter, option.rect, Qt::AlignRight | Qt::AlignVCenter, attention);
0483             }
0484         }
0485     }
0486     painter->restore();
0487 }
0488 
0489 bool Transaction::formCellText(QString& /* txt */, Qt::Alignment& /* align */, int /* row */, int /* col */, QPainter* /* painter */)
0490 {
0491     return false;
0492 }
0493 
0494 void Transaction::registerCellText(QString& /* txt */, Qt::Alignment& /* align */, int /* row */, int /* col */, QPainter* /* painter */)
0495 {
0496 }
0497 
0498 int Transaction::registerColWidth(int /* col */, const QFontMetrics& /* cellFontMetrics */)
0499 {
0500     return 0;
0501 }
0502 
0503 int Transaction::formRowHeight(int /*row*/)
0504 {
0505     Q_D(Transaction);
0506     if (d->m_formRowHeight < 0) {
0507         d->m_formRowHeight = formRowHeight();
0508     }
0509     return d->m_formRowHeight;
0510 }
0511 
0512 int Transaction::formRowHeight() const
0513 {
0514     Q_D(const Transaction);
0515     if (d->m_formRowHeight < 0) {
0516         // determine the height of the objects in the table
0517         KMyMoneyDateInput dateInput;
0518         KMyMoneyCategory category(true, nullptr);
0519 
0520         return qMax(dateInput.sizeHint().height(), category.sizeHint().height());
0521     }
0522     return d->m_formRowHeight;
0523 }
0524 
0525 void Transaction::setupForm(TransactionForm* form)
0526 {
0527     Q_D(Transaction);
0528     d->m_form = form;
0529     form->verticalHeader()->setUpdatesEnabled(false);
0530     form->horizontalHeader()->setUpdatesEnabled(false);
0531 
0532     form->setRowCount(numRowsForm());
0533     form->setColumnCount(numColsForm());
0534 
0535     // Force all cells to have some text (so that paintCell is called for each cell)
0536     for (int r = 0; r < numRowsForm(); ++r) {
0537         for (int c = 0; c < numColsForm(); ++c) {
0538             if (r == 0 && form->columnWidth(c) == 0) {
0539                 form->setColumnWidth(c, 10);
0540             }
0541         }
0542     }
0543     form->horizontalHeader()->setUpdatesEnabled(true);
0544     form->verticalHeader()->setUpdatesEnabled(true);
0545 
0546     loadTab(form);
0547 }
0548 
0549 void Transaction::paintFormCell(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
0550 {
0551     Q_D(Transaction);
0552     if (!d->m_form)
0553         return;
0554 
0555     QRect cellRect = option.rect;
0556 
0557     QRect textRect(cellRect);
0558     textRect.setWidth(textRect.width() - 2);
0559     textRect.setHeight(textRect.height() - 2);
0560 
0561     painter->setPen(option.palette.text().color());
0562 
0563     QString txt;
0564     Qt::Alignment align = Qt::AlignVCenter;
0565     bool editField = formCellText(txt, align, index.row(), index.column(), painter);
0566 
0567     // if we have an editable field and don't currently edit the transaction
0568     // show the background in a different color
0569     if (editField && !d->m_inEdit) {
0570         painter->fillRect(textRect, option.palette.alternateBase());
0571     }
0572 
0573     if (!d->m_inEdit)
0574         painter->drawText(textRect, align, txt);
0575 }
0576 
0577 void Transaction::setupPalette(const QPalette& palette, QMap<QString, QWidget*>& editWidgets)
0578 {
0579     QMap<QString, QWidget*>::iterator it_w;
0580     for (it_w = editWidgets.begin(); it_w != editWidgets.end(); ++it_w) {
0581         if (*it_w) {
0582             (*it_w)->setPalette(palette);
0583         }
0584     }
0585 }
0586 
0587 void Transaction::setupFormPalette(QMap<QString, QWidget*>& editWidgets)
0588 {
0589     Q_D(Transaction);
0590     QPalette palette = d->m_parent->palette();
0591     palette.setColor(QPalette::Active, QPalette::Base, palette.color(QPalette::Active, QPalette::Base));
0592     setupPalette(palette, editWidgets);
0593 }
0594 
0595 void Transaction::setupRegisterPalette(QMap<QString, QWidget*>& editWidgets)
0596 {
0597     Q_D(Transaction);
0598     // make sure, we're using the right palette
0599     QPalette palette = d->m_parent->palette();
0600 
0601     // use the highlight color as background
0602     palette.setColor(QPalette::Active, QPalette::Background, palette.color(QPalette::Active, QPalette::Highlight));
0603 
0604     setupPalette(palette, editWidgets);
0605 }
0606 
0607 QWidget* Transaction::focusWidget(QWidget* w) const
0608 {
0609     if (w) {
0610         while (w->focusProxy())
0611             w = w->focusProxy();
0612     }
0613     return w;
0614 }
0615 
0616 void Transaction::arrangeWidget(QTableWidget* tbl, int row, int col, QWidget* w) const
0617 {
0618     if (w) {
0619         tbl->setCellWidget(row, col, w);
0620         // remove the widget from the QTable's eventFilter so that all
0621         // events will be directed to the edit widget
0622         w->removeEventFilter(tbl);
0623     }
0624 }
0625 
0626 bool Transaction::haveNumberField() const
0627 {
0628     Q_D(const Transaction);
0629     auto rc = true;
0630     switch (d->m_account.accountType()) {
0631     case eMyMoney::Account::Type::Savings:
0632     case eMyMoney::Account::Type::Cash:
0633     case eMyMoney::Account::Type::Loan:
0634     case eMyMoney::Account::Type::AssetLoan:
0635     case eMyMoney::Account::Type::Asset:
0636     case eMyMoney::Account::Type::Liability:
0637     case eMyMoney::Account::Type::Equity:
0638         rc = KMyMoneySettings::alwaysShowNrField();
0639         break;
0640 
0641     case eMyMoney::Account::Type::Checkings:
0642     case eMyMoney::Account::Type::CreditCard:
0643     // the next case is used for the editor when the account
0644     // is unknown (eg. when creating new schedules)
0645     case eMyMoney::Account::Type::Unknown:
0646         break;
0647 
0648     default:
0649         rc = false;
0650         break;
0651     }
0652     return rc;
0653 }
0654 
0655 bool Transaction::maybeTip(const QPoint& cpos, int row, int col, QRect& r, QString& msg)
0656 {
0657     Q_D(Transaction);
0658     if (col != (int)eTransaction::Column::Detail)
0659         return false;
0660 
0661     if (!d->m_erroneous && d->m_transaction.splitCount() < 3)
0662         return false;
0663 
0664     // check for detail column in row 0 of the transaction for a possible
0665     // exclamation mark. m_startRow is based 0, whereas the row to obtain
0666     // the modelindex is based 1, so we need to add one here
0667     r = d->m_parent->visualRect(d->m_parent->model()->index(d->m_startRow + 1, col));
0668     r.setBottom(r.bottom() + (numRowsRegister() - 1)*r.height());
0669     if (r.contains(cpos) && d->m_erroneous) {
0670         if (d->m_transaction.splits().count() < 2) {
0671             msg = QString("<qt>%1</qt>").arg(i18n("Transaction is missing a category assignment."));
0672         } else {
0673             const auto sec = MyMoneyFile::instance()->security(d->m_account.currencyId());
0674             msg = QString("<qt>%1</qt>").arg(i18n("The transaction has a missing assignment of <b>%1</b>.", MyMoneyUtils::formatMoney(d->m_transaction.splitSum().abs(), d->m_account, sec)));
0675         }
0676         return true;
0677     }
0678 
0679     // check if the mouse cursor is located on row 1 of the transaction
0680     // and display the details of a split transaction if it is one
0681     if (row == 1 && r.contains(cpos) && d->m_transaction.splitCount() > 2) {
0682         auto file = MyMoneyFile::instance();
0683         QString txt;
0684         const auto sec = file->security(d->m_transaction.commodity());
0685         MyMoneyMoney factor(1, 1);
0686         if (!d->m_split.value().isNegative())
0687             factor = -factor;
0688 
0689         foreach (const auto split, d->m_transaction.splits()) {
0690             if (split == d->m_split)
0691                 continue;
0692             const MyMoneyAccount& acc = file->account(split.accountId());
0693             auto category = file->accountToCategory(acc.id());
0694             auto amount = MyMoneyUtils::formatMoney((split.value() * factor), acc, sec);
0695 
0696             txt += QString("<tr><td><nobr>%1</nobr></td><td align=right><nobr>%2</nobr></td></tr>").arg(category, amount);
0697         }
0698         msg = QString("<table>%1</table>").arg(txt);
0699         return true;
0700     }
0701 
0702     return false;
0703 }
0704 
0705 QString Transaction::reconcileState(bool text) const
0706 {
0707     Q_D(const Transaction);
0708     auto txt = KMyMoneyUtils::reconcileStateToString(d->m_split.reconcileFlag(), text);
0709 
0710     if ((text == true) //
0711             && (txt == i18nc("Unknown reconciliation state", "Unknown")) //
0712             && (d->m_transaction.id().isEmpty()))
0713         txt.clear();
0714     return txt;
0715 }
0716 
0717 void Transaction::startEditMode()
0718 {
0719     Q_D(Transaction);
0720     d->m_inEdit = true;
0721 
0722     // hide the original tabbar since the edit tabbar will be added
0723     if (auto form = dynamic_cast<KMyMoneyTransactionForm::TransactionForm*>(d->m_form))
0724         form->getTabBar()->setVisible(false);
0725 
0726     // only update the number of lines displayed if we edit inside the register
0727     if (d->m_inRegisterEdit)
0728         setNumRowsRegister(numRowsRegister(true));
0729 }
0730 
0731 int Transaction::numRowsRegister() const
0732 {
0733     return RegisterItem::numRowsRegister();
0734 }
0735 
0736 void Transaction::leaveEditMode()
0737 {
0738     Q_D(Transaction);
0739     // show the original tabbar since the edit tabbar was removed
0740     if (auto form = dynamic_cast<KMyMoneyTransactionForm::TransactionForm*>(d->m_form))
0741         form->getTabBar()->setVisible(true);
0742 
0743     // make sure we reset the row height of all the transaction's rows because it could have been changed during edit
0744     if (d->m_parent) {
0745         for (auto i = 0; i < numRowsRegister(); ++i)
0746             d->m_parent->setRowHeight(d->m_startRow + i, d->m_parent->rowHeightHint());
0747     }
0748 
0749     d->m_inEdit = false;
0750     d->m_inRegisterEdit = false;
0751     setFocus(hasFocus(), true);
0752 }
0753 
0754 void Transaction::singleLineMemo(QString& txt, const MyMoneySplit& split) const
0755 {
0756     txt = split.memo();
0757     // remove empty lines
0758     txt.replace("\n\n", "\n");
0759     // replace '\n' with ", "
0760     txt.replace('\n', ", ");
0761 }
0762 
0763 int Transaction::rowHeightHint() const
0764 {
0765     Q_D(const Transaction);
0766     return d->m_inEdit ? formRowHeight() : RegisterItem::rowHeightHint();
0767 }
0768 
0769 bool Transaction::matches(const RegisterFilter& filter) const
0770 {
0771     Q_D(const Transaction);
0772     // check if the state matches
0773     if (!transaction().id().isEmpty()) {
0774         switch (filter.state) {
0775         default:
0776             break;
0777         case eRegister::ItemState::Imported:
0778             if (!transaction().isImported())
0779                 return false;
0780             break;
0781         case eRegister::ItemState::Matched:
0782             if (!split().isMatched())
0783                 return false;
0784             break;
0785         case eRegister::ItemState::Erroneous:
0786             if (transaction().splitSum().isZero())
0787                 return false;
0788             break;
0789         case eRegister::ItemState::NotMarked:
0790             if (split().reconcileFlag() != eMyMoney::Split::State::NotReconciled)
0791                 return false;
0792             break;
0793         case eRegister::ItemState::NotReconciled:
0794             if (split().reconcileFlag() != eMyMoney::Split::State::NotReconciled
0795                     && split().reconcileFlag() != eMyMoney::Split::State::Cleared)
0796                 return false;
0797             break;
0798         case eRegister::ItemState::Cleared:
0799             if (split().reconcileFlag() != eMyMoney::Split::State::Cleared)
0800                 return false;
0801             break;
0802         case eRegister::ItemState::Scheduled:
0803             if (!isScheduled())
0804                 return false;
0805             break;
0806         }
0807     }
0808 
0809     // check if the text matches
0810     if (filter.text.isEmpty() || d->m_transaction.splitCount() == 0)
0811         return true;
0812 
0813     auto file = MyMoneyFile::instance();
0814 
0815     foreach (const auto split, d->m_transaction.splits()) {
0816         // check if the text is contained in one of the fields
0817         // memo, number, payee, tag, account
0818         if (split.memo().contains(filter.text, Qt::CaseInsensitive) //
0819                 || split.number().contains(filter.text, Qt::CaseInsensitive))
0820             return true;
0821 
0822         if (!split.payeeId().isEmpty()) {
0823             const MyMoneyPayee& payee = file->payee(split.payeeId());
0824             if (payee.name().contains(filter.text, Qt::CaseInsensitive))
0825                 return true;
0826         }
0827         if (!split.tagIdList().isEmpty()) {
0828             const QList<QString>& t = split.tagIdList();
0829             for (auto i = 0; i < t.count(); ++i) {
0830                 if ((file->tag(t[i])).name().contains(filter.text, Qt::CaseInsensitive))
0831                     return true;
0832             }
0833         }
0834         const MyMoneyAccount& acc = file->account(split.accountId());
0835         // search for account hierarchy
0836         if (filter.text.contains(MyMoneyFile::AccountSeparator)) {
0837             QStringList names;
0838             MyMoneyAccount current = acc;
0839             QString accountId;
0840             do {
0841                 names.prepend(current.name());
0842                 accountId = current.parentAccountId();
0843                 current = file->account(accountId);
0844             } while (current.accountType() != eMyMoney::Account::Type::Unknown && !MyMoneyFile::instance()->isStandardAccount(accountId));
0845             if (names.size() > 1 && names.join(MyMoneyFile::AccountSeparator).contains(filter.text, Qt::CaseInsensitive))
0846                 return true;
0847         }
0848 
0849         if (acc.name().contains(filter.text, Qt::CaseInsensitive))
0850             return true;
0851 
0852         QString s(filter.text);
0853         s.replace(MyMoneyMoney::thousandSeparator(), QChar());
0854         if (!s.isEmpty()) {
0855             // check if any of the value field matches if a value has been entered
0856             QString r = split.value().formatMoney(d->m_account.fraction(), false);
0857             if (r.contains(s, Qt::CaseInsensitive))
0858                 return true;
0859             const MyMoneyAccount& splitAcc = file->account(split.accountId());
0860             r = split.shares().formatMoney(splitAcc.fraction(), false);
0861             if (r.contains(s, Qt::CaseInsensitive))
0862                 return true;
0863         }
0864     }
0865 
0866     return false;
0867 }
0868 
0869 void Transaction::setShowBalance(bool showBalance)
0870 {
0871     Q_D(Transaction);
0872     d->m_showBalance = showBalance;
0873 }
0874 
0875 bool Transaction::showRowInForm(int row) const
0876 {
0877     Q_UNUSED(row) return true;
0878 }
0879 
0880 void Transaction::setShowRowInForm(int row, bool show)
0881 {
0882     Q_UNUSED(row);
0883     Q_UNUSED(show)
0884 }
0885 
0886 void Transaction::setReducedIntensity(bool reduced)
0887 {
0888     Q_D(Transaction);
0889     d->m_reducedIntensity = reduced;
0890 }
0891 
0892 void Transaction::setVisible(bool visible)
0893 {
0894     Q_D(Transaction);
0895     if (visible != isVisible()) {
0896         RegisterItem::setVisible(visible);
0897         RegisterItem* p;
0898         Transaction* t;
0899         if (!visible) {
0900             // if we are hidden, we need to inform all previous transactions
0901             // about it so that they don't show the balance
0902             p = prevItem();
0903             while (p) {
0904                 if ((t = dynamic_cast<Transaction*>(p))) {
0905                     if (!t->d_func()->m_showBalance)
0906                         break;
0907                     t->d_func()->m_showBalance = false;
0908                 }
0909                 p = p->prevItem();
0910             }
0911         } else {
0912             // if we are shown, we need to check if the next transaction
0913             // is visible and change the display of the balance
0914             p = this;
0915             do {
0916                 p = p->nextItem();
0917                 t = dynamic_cast<Transaction*>(p);
0918             } while (!t && p);
0919 
0920             // if the next transaction is visible or I am the last one
0921             if ((t && t->d_func()->m_showBalance) || !t) {
0922                 d->m_showBalance = true;
0923                 p = prevItem();
0924                 while (p && p->isVisible()) {
0925                     if ((t = dynamic_cast<Transaction*>(p))) {
0926                         if (t->d_func()->m_showBalance)
0927                             break;
0928                         t->d_func()->m_showBalance = true;
0929                     }
0930                     p = p->prevItem();
0931                 }
0932             }
0933         }
0934     }
0935 }
0936