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