File indexing completed on 2024-05-12 16:43:52

0001 /*
0002     SPDX-FileCopyrightText: 2015 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "ledgerdelegate.h"
0007 
0008 // ----------------------------------------------------------------------------
0009 // QT Includes
0010 
0011 #include <QApplication>
0012 #include <QScrollBar>
0013 #include <QPainter>
0014 #include <QDebug>
0015 #include <QDate>
0016 
0017 // ----------------------------------------------------------------------------
0018 // KDE Includes
0019 
0020 #include <KLocalizedString>
0021 #include <KColorScheme>
0022 
0023 // ----------------------------------------------------------------------------
0024 // Project Includes
0025 
0026 #include "ledgerview.h"
0027 #include "ledgermodel.h"
0028 #include "newtransactioneditor.h"
0029 
0030 static unsigned char attentionSign[] = {
0031     0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
0032     0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
0033     0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14,
0034     0x08, 0x06, 0x00, 0x00, 0x00, 0x8D, 0x89, 0x1D,
0035     0x0D, 0x00, 0x00, 0x00, 0x04, 0x73, 0x42, 0x49,
0036     0x54, 0x08, 0x08, 0x08, 0x08, 0x7C, 0x08, 0x64,
0037     0x88, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58,
0038     0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72,
0039     0x65, 0x00, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x6E,
0040     0x6B, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2E, 0x6F,
0041     0x72, 0x67, 0x9B, 0xEE, 0x3C, 0x1A, 0x00, 0x00,
0042     0x02, 0x05, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8D,
0043     0xAD, 0x95, 0xBF, 0x4B, 0x5B, 0x51, 0x14, 0xC7,
0044     0x3F, 0x2F, 0xBC, 0x97, 0x97, 0x97, 0x97, 0x77,
0045     0xF3, 0xF2, 0x1C, 0xA4, 0x54, 0x6B, 0x70, 0x10,
0046     0x44, 0x70, 0x2A, 0x91, 0x2E, 0x52, 0x02, 0x55,
0047     0x8A, 0xB5, 0xA3, 0xAB, 0x38, 0x08, 0x66, 0xCC,
0048     0xEE, 0xE0, 0xE2, 0x20, 0xB8, 0x38, 0xB8, 0xB8,
0049     0xF8, 0x1F, 0x38, 0x29, 0xA5, 0x29, 0x74, 0x90,
0050     0x0E, 0x0D, 0x0E, 0x22, 0x1D, 0x44, 0xA8, 0xD0,
0051     0xD4, 0xB4, 0x58, 0x4B, 0x09, 0xF9, 0xF1, 0x4A,
0052     0x3B, 0xD4, 0xD3, 0xE1, 0x55, 0xD3, 0x34, 0xAF,
0053     0x49, 0x6C, 0x3D, 0xF0, 0x85, 0x7B, 0xCF, 0xFD,
0054     0x9E, 0xEF, 0x3D, 0xE7, 0xFE, 0xD4, 0x44, 0x84,
0055     0xDB, 0xB4, 0x48, 0x2F, 0xA4, 0x94, 0xAB, 0xE5,
0056     0x52, 0xAE, 0x96, 0xEB, 0x49, 0x51, 0x44, 0x3A,
0057     0x02, 0x18, 0x88, 0xC7, 0xF1, 0xE3, 0x71, 0x7C,
0058     0x60, 0xA0, 0x1B, 0xBF, 0x6B, 0x86, 0x49, 0xC5,
0059     0x46, 0x3E, 0x47, 0x34, 0x9F, 0x23, 0x9A, 0x54,
0060     0x6C, 0xFC, 0x57, 0x86, 0x40, 0xC6, 0x4B, 0xE1,
0061     0x37, 0xCA, 0x48, 0xA3, 0x8C, 0x78, 0x29, 0x7C,
0062     0x20, 0xD3, 0x31, 0xA6, 0xD3, 0xA0, 0x52, 0x1C,
0063     0x6D, 0x6F, 0x72, 0xD9, 0x28, 0x23, 0xFE, 0x07,
0064     0x64, 0x7B, 0x93, 0x4B, 0xA5, 0x38, 0xFA, 0x27,
0065     0x41, 0x60, 0x6E, 0x74, 0x84, 0x7A, 0xE5, 0x1D,
0066     0x92, 0x54, 0x88, 0xE7, 0x22, 0xD5, 0x12, 0x32,
0067     0x3A, 0x42, 0x1D, 0x98, 0xBB, 0x91, 0x20, 0x60,
0068     0xDA, 0x36, 0x17, 0xFB, 0x7B, 0xC8, 0xC1, 0x4B,
0069     0x04, 0x02, 0xBC, 0x7E, 0x81, 0xEC, 0xEF, 0x21,
0070     0xB6, 0xCD, 0x05, 0x60, 0xF6, 0x2C, 0x68, 0x9A,
0071     0x2C, 0xCF, 0x4C, 0xE1, 0x4B, 0x05, 0x39, 0x3F,
0072     0x69, 0x0A, 0xBE, 0x7F, 0x83, 0x48, 0x05, 0x99,
0073     0x99, 0xC2, 0x37, 0x4D, 0x96, 0x7B, 0x12, 0x04,
0074     0xFA, 0x2D, 0x8B, 0xC6, 0xE9, 0x61, 0x10, 0x2C,
0075     0x15, 0xC4, 0x8A, 0x21, 0x86, 0x8E, 0xFC, 0xF8,
0076     0x12, 0xF4, 0x4F, 0x0F, 0x11, 0xCB, 0xA2, 0x01,
0077     0xF4, 0x77, 0x3D, 0x36, 0x4E, 0x82, 0xF5, 0xA5,
0078     0x05, 0x8C, 0xE1, 0x74, 0xD3, 0x37, 0x34, 0x18,
0079     0x20, 0xF2, 0x8B, 0x3D, 0x9C, 0x86, 0xA5, 0x05,
0080     0x0C, 0x27, 0xC1, 0x7A, 0xC7, 0x63, 0x03, 0x8C,
0081     0x2B, 0x07, 0xBF, 0x5A, 0x6A, 0x66, 0x27, 0x15,
0082     0x64, 0x3A, 0x8B, 0x3C, 0x7A, 0xD8, 0xEA, 0xAB,
0083     0x96, 0x10, 0xE5, 0xE0, 0x03, 0xE3, 0x7F, 0xCD,
0084     0x50, 0x39, 0x6C, 0xAD, 0xAD, 0x10, 0x53, 0xAA,
0085     0x75, 0xD2, 0xF4, 0xBD, 0x00, 0x2D, 0x5C, 0x05,
0086     0x6B, 0x2B, 0xC4, 0x94, 0xC3, 0xD6, 0xEF, 0xFE,
0087     0x6B, 0x41, 0x4D, 0xD3, 0x66, 0xFB, 0x3C, 0xC6,
0088     0x16, 0xE7, 0xDB, 0x97, 0x61, 0xE2, 0x3E, 0x3C,
0089     0xC8, 0xB4, 0x15, 0xC7, 0xE2, 0x3C, 0x91, 0x3E,
0090     0x8F, 0x31, 0x4D, 0xD3, 0x66, 0x5B, 0x4A, 0x06,
0091     0x8C, 0x84, 0xCD, 0x59, 0x61, 0xA7, 0xB5, 0xAC,
0092     0x2B, 0x9C, 0x1C, 0x04, 0x08, 0x1B, 0x2B, 0xEC,
0093     0x20, 0x09, 0x9B, 0x33, 0xC0, 0xB8, 0xDE, 0x65,
0094     0x43, 0x27, 0x9F, 0x9D, 0xA4, 0x1E, 0x16, 0xF0,
0095     0xF9, 0x6D, 0xB0, 0xC3, 0x86, 0x1E, 0xB4, 0xC3,
0096     0x38, 0xD9, 0x49, 0xEA, 0x86, 0x4E, 0xFE, 0xEA,
0097     0x29, 0xF4, 0x2C, 0x8B, 0xDA, 0x71, 0x31, 0x9C,
0098     0xFC, 0xF5, 0x23, 0x32, 0x34, 0x88, 0xDC, 0xBD,
0099     0x13, 0x5C, 0xBF, 0x30, 0xCE, 0x71, 0x11, 0xB1,
0100     0x2C, 0x6A, 0x80, 0xA7, 0xDB, 0x36, 0xAB, 0x4F,
0101     0xA6, 0x89, 0xBA, 0x49, 0x38, 0xFF, 0xD4, 0xBE,
0102     0x4E, 0x00, 0xAF, 0x9E, 0x81, 0x08, 0xD4, 0xEA,
0103     0x01, 0xFE, 0x34, 0x37, 0x09, 0x4F, 0x1F, 0x13,
0104     0xDD, 0x7D, 0xCE, 0xAA, 0x96, 0x72, 0x29, 0x7C,
0105     0xFB, 0xCE, 0x44, 0xB8, 0xD4, 0xCD, 0x2C, 0x66,
0106     0x52, 0xD4, 0x6E, 0xFB, 0x0B, 0xF8, 0x09, 0x63,
0107     0x63, 0x31, 0xE4, 0x85, 0x76, 0x2E, 0x0E, 0x00,
0108     0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
0109     0x42, 0x60, 0x82
0110 };
0111 
0112 QColor LedgerDelegate::m_erroneousColor = QColor(Qt::red);
0113 QColor LedgerDelegate::m_importedColor = QColor(Qt::yellow);
0114 QColor LedgerDelegate::m_separatorColor = QColor(0xff, 0xf2, 0x9b);
0115 
0116 
0117 class LedgerSeparatorDate : public LedgerSeparator
0118 {
0119 public:
0120     LedgerSeparatorDate(eLedgerModel::Role role);
0121     virtual ~LedgerSeparatorDate() {}
0122 
0123     virtual bool rowHasSeparator(const QModelIndex& index) const override;
0124     virtual QString separatorText(const QModelIndex& index) const override;
0125     virtual void adjustBackgroundScheme(QPalette& palette, const QModelIndex& index) const override;
0126 
0127 protected:
0128     QString getEntry(const QModelIndex& index, const QModelIndex& nextIndex) const;
0129     QMap<QDate, QString>      m_entries;
0130 };
0131 
0132 class LedgerSeparatorOnlineBalance : public LedgerSeparatorDate
0133 {
0134 public:
0135     LedgerSeparatorOnlineBalance(eLedgerModel::Role role);
0136     virtual ~LedgerSeparatorOnlineBalance() {}
0137 
0138     virtual bool rowHasSeparator(const QModelIndex& index) const override;
0139     virtual QString separatorText(const QModelIndex& index) const override;
0140     virtual void adjustBackgroundScheme(QPalette& palette, const QModelIndex& index) const override;
0141 
0142     void setSeparatorData(const QDate& date, const MyMoneyMoney& amount, int fraction);
0143 
0144 private:
0145     QString m_balanceAmount;
0146 };
0147 
0148 
0149 
0150 QDate LedgerSeparator::firstFiscalDate;
0151 bool  LedgerSeparator::showFiscalDate = true;
0152 bool  LedgerSeparator::showFancyDate = true;
0153 
0154 
0155 void LedgerSeparator::setFirstFiscalDate(int firstMonth, int firstDay)
0156 {
0157     firstFiscalDate = QDate(QDate::currentDate().year(), firstMonth, firstDay);
0158     if (QDate::currentDate() < firstFiscalDate)
0159         firstFiscalDate = firstFiscalDate.addYears(-1);
0160 }
0161 
0162 QModelIndex LedgerSeparator::nextIndex(const QModelIndex& index) const
0163 {
0164     const int nextRow = index.row() + 1;
0165     if (index.isValid() && (nextRow < index.model()->rowCount(QModelIndex()))) {
0166         const QAbstractItemModel* model = index.model();
0167         return model->index(nextRow, 0, QModelIndex());
0168     }
0169     return QModelIndex();
0170 }
0171 
0172 LedgerSeparatorDate::LedgerSeparatorDate(eLedgerModel::Role role)
0173     : LedgerSeparator(role)
0174 {
0175     const QDate today = QDate::currentDate();
0176     const QDate thisMonth(today.year(), today.month(), 1);
0177     const QDate lastMonth = thisMonth.addMonths(-1);
0178     const QDate yesterday = today.addDays(-1);
0179     // a = QDate::dayOfWeek()         todays weekday (1 = Monday, 7 = Sunday)
0180     // b = QLocale().firstDayOfWeek() first day of week (1 = Monday, 7 = Sunday)
0181     int weekStartOfs = today.dayOfWeek() - QLocale().firstDayOfWeek();
0182     if (weekStartOfs < 0) {
0183         weekStartOfs = 7 + weekStartOfs;
0184     }
0185     const QDate thisWeek = today.addDays(-weekStartOfs);
0186     const QDate lastWeek = thisWeek.addDays(-7);
0187     const QDate thisYear(today.year(), 1, 1);
0188 
0189     m_entries[thisYear] = i18n("This year");
0190     m_entries[lastMonth] = i18n("Last month");
0191     m_entries[thisMonth] = i18n("This month");
0192     m_entries[lastWeek] = i18n("Last week");
0193     m_entries[thisWeek] = i18n("This week");
0194     m_entries[yesterday] = i18n("Yesterday");
0195     m_entries[today] = i18n("Today");
0196     m_entries[today.addDays(1)] = i18n("Future transactions");
0197     m_entries[thisWeek.addDays(7)] = i18n("Next week");
0198     m_entries[thisMonth.addMonths(1)] = i18n("Next month");
0199     if (showFiscalDate && firstFiscalDate.isValid()) {
0200         m_entries[firstFiscalDate] = i18n("Current fiscal year");
0201         m_entries[firstFiscalDate.addYears(-1)] = i18n("Previous fiscal year");
0202         m_entries[firstFiscalDate.addYears(1)] = i18n("Next fiscal year");
0203     }
0204 }
0205 
0206 
0207 QString LedgerSeparatorDate::getEntry(const QModelIndex& index, const QModelIndex& nextIndex) const
0208 {
0209     Q_ASSERT(index.isValid());
0210     Q_ASSERT(nextIndex.isValid());
0211     Q_ASSERT(index.model() == nextIndex.model());
0212 
0213     const QAbstractItemModel* model = index.model();
0214     QString rc;
0215     if(!m_entries.isEmpty()) {
0216         if (model->data(index, (int)m_role).toDate() != model->data(nextIndex, (int)m_role).toDate()) {
0217             const QDate key = model->data(index, (int)m_role).toDate();
0218             const QDate endKey = model->data(nextIndex, (int)m_role).toDate();
0219             QMap<QDate, QString>::const_iterator it = m_entries.upperBound(key);
0220             while((it != m_entries.cend()) && (it.key() <= endKey)) {
0221                 rc = *it;
0222                 ++it;
0223             }
0224         }
0225     }
0226     return rc;
0227 }
0228 
0229 bool LedgerSeparatorDate::rowHasSeparator(const QModelIndex& index) const
0230 {
0231     bool rc = false;
0232     if(!m_entries.isEmpty()) {
0233         QModelIndex nextIdx = nextIndex(index);
0234         if(nextIdx.isValid() ) {
0235             const QString id = nextIdx.model()->data(nextIdx, (int)eLedgerModel::Role::TransactionSplitId).toString();
0236             // For a new transaction the id is completely empty, for a split view the transaction
0237             // part is filled but the split id is empty and the string ends with a dash
0238             // and we never draw a separator in front of that row
0239             if(!id.isEmpty() && !id.endsWith('-')) {
0240                 rc = !getEntry(index, nextIdx).isEmpty();
0241             }
0242         }
0243     }
0244     return rc;
0245 }
0246 
0247 QString LedgerSeparatorDate::separatorText(const QModelIndex& index) const
0248 {
0249     QModelIndex nextIdx = nextIndex(index);
0250     if(nextIdx.isValid()) {
0251         return getEntry(index, nextIdx);
0252     }
0253     return QString();
0254 }
0255 
0256 void LedgerSeparatorDate::adjustBackgroundScheme(QPalette& palette, const QModelIndex& index) const
0257 {
0258     Q_UNUSED(index);
0259     KColorScheme::adjustBackground(palette, KColorScheme::ActiveBackground, QPalette::Base, KColorScheme::Button, KSharedConfigPtr());
0260 }
0261 
0262 
0263 
0264 LedgerSeparatorOnlineBalance::LedgerSeparatorOnlineBalance(eLedgerModel::Role role)
0265     : LedgerSeparatorDate(role)
0266 {
0267     // we don't need the standard values
0268     m_entries.clear();
0269 }
0270 
0271 void LedgerSeparatorOnlineBalance::setSeparatorData(const QDate& date, const MyMoneyMoney& amount, int fraction)
0272 {
0273     m_entries.clear();
0274     if (date.isValid()) {
0275         m_balanceAmount = amount.formatMoney(fraction);
0276         m_entries[date] = i18n("Online statement balance: %1", m_balanceAmount);
0277     }
0278 }
0279 
0280 bool LedgerSeparatorOnlineBalance::rowHasSeparator(const QModelIndex& index) const
0281 {
0282     bool rc = false;
0283     if(!m_entries.isEmpty()) {
0284         QModelIndex nextIdx = nextIndex(index);
0285         const QAbstractItemModel* model = index.model();
0286         const QDate date = model->data(index, (int)m_role).toDate();
0287         // only a real transaction can have an online balance separator
0288         if(model->data(index, (int) eLedgerModel::Role::ScheduleId).toString().isEmpty()) {
0289             // if this is not the last entry and not a schedule?
0290             if(nextIdx.isValid()) {
0291                 // index points to the last entry of a date
0292                 rc = (date != model->data(nextIdx, (int)m_role).toDate());
0293                 if (!rc) {
0294                     // in case it's the same date, we need to check if this is the last real transaction
0295                     // and the next one is a scheduled transaction
0296                     if(!model->data(nextIdx, (int) eLedgerModel::Role::ScheduleId).toString().isEmpty() ) {
0297                         rc = (date <= m_entries.firstKey());
0298                     }
0299                 } else {
0300                     // check if this the spot for the online balance data
0301                     rc &= ((date <= m_entries.firstKey())
0302                            && (model->data(nextIdx, (int)m_role).toDate() > m_entries.firstKey()));
0303                 }
0304             } else {
0305                 rc = (date <= m_entries.firstKey());
0306             }
0307         }
0308     }
0309     return rc;
0310 }
0311 
0312 QString LedgerSeparatorOnlineBalance::separatorText(const QModelIndex& index) const
0313 {
0314     if(rowHasSeparator(index)) {
0315         return m_entries.first();
0316     }
0317     return QString();
0318 }
0319 
0320 void LedgerSeparatorOnlineBalance::adjustBackgroundScheme(QPalette& palette, const QModelIndex& index) const
0321 {
0322     const QAbstractItemModel* model = index.model();
0323     QModelIndex amountIndex = model->index(index.row(), (int)eLedgerModel::Column::Balance);
0324     QString amount = model->data(amountIndex).toString();
0325     KColorScheme::BackgroundRole role = KColorScheme::PositiveBackground;
0326 
0327     if (!m_entries.isEmpty()) {
0328         if(amount != m_balanceAmount) {
0329             role = KColorScheme::NegativeBackground;
0330         }
0331     }
0332     KColorScheme::adjustBackground(palette, role, QPalette::Base, KColorScheme::Button, KSharedConfigPtr());
0333 }
0334 
0335 
0336 
0337 
0338 class LedgerDelegate::Private
0339 {
0340 public:
0341     Private()
0342         : m_editor(0)
0343         , m_view(0)
0344         , m_editorRow(-1)
0345         , m_separator(0)
0346         , m_onlineBalanceSeparator(0)
0347     {}
0348 
0349     ~Private()
0350     {
0351         delete m_separator;
0352     }
0353 
0354     inline bool displaySeparator(const QModelIndex& index) const
0355     {
0356         return m_separator && m_separator->rowHasSeparator(index);
0357     }
0358 
0359     inline bool displayOnlineBalanceSeparator(const QModelIndex& index) const
0360     {
0361         return m_onlineBalanceSeparator && m_onlineBalanceSeparator->rowHasSeparator(index);
0362     }
0363 
0364     NewTransactionEditor*         m_editor;
0365     LedgerView*                   m_view;
0366     int                           m_editorRow;
0367     LedgerSeparator*              m_separator;
0368     LedgerSeparatorOnlineBalance* m_onlineBalanceSeparator;
0369 };
0370 
0371 
0372 LedgerDelegate::LedgerDelegate(LedgerView* parent)
0373     : QStyledItemDelegate(parent)
0374     , d(new Private)
0375 {
0376     d->m_view = parent;
0377 }
0378 
0379 LedgerDelegate::~LedgerDelegate()
0380 {
0381     delete d;
0382 }
0383 
0384 void LedgerDelegate::setSortRole(eLedgerModel::Role role)
0385 {
0386     delete d->m_separator;
0387     delete d->m_onlineBalanceSeparator;
0388     d->m_separator = 0;
0389     d->m_onlineBalanceSeparator = 0;
0390 
0391     switch(role) {
0392     case eLedgerModel::Role::PostDate:
0393         d->m_separator = new LedgerSeparatorDate(role);
0394         d->m_onlineBalanceSeparator = new LedgerSeparatorOnlineBalance(role);
0395         break;
0396     default:
0397         qDebug() << "LedgerDelegate::setSortRole role" << (int)role << "not implemented";
0398         break;
0399     }
0400 }
0401 
0402 void LedgerDelegate::setErroneousColor(const QColor& color)
0403 {
0404     m_erroneousColor = color;
0405 }
0406 
0407 void LedgerDelegate::setOnlineBalance(const QDate& date, const MyMoneyMoney& amount, int fraction)
0408 {
0409     if(d->m_onlineBalanceSeparator) {
0410         d->m_onlineBalanceSeparator->setSeparatorData(date, amount, fraction);
0411     }
0412 }
0413 
0414 QWidget* LedgerDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
0415 {
0416     Q_UNUSED(option);
0417 
0418     if(index.isValid()) {
0419         if(d->m_view->selectionModel()->selectedRows().count() > 1) {
0420             qDebug() << "Editing multiple transactions at once is not yet supported";
0421 
0422             /**
0423              * @todo replace the following three lines with the creation of a special
0424              * editor that can handle multiple transactions at once
0425              */
0426             d->m_editor = 0;
0427             LedgerDelegate* that = const_cast<LedgerDelegate*>(this);
0428             emit that->closeEditor(d->m_editor, NoHint);
0429 
0430         } else {
0431             d->m_editor = new NewTransactionEditor(parent, d->m_view->accountId());
0432         }
0433 
0434         if(d->m_editor) {
0435             d->m_editorRow = index.row();
0436             connect(d->m_editor, SIGNAL(done()), this, SLOT(endEdit()));
0437             emit sizeHintChanged(index);
0438         }
0439 
0440     } else {
0441         qFatal("LedgerDelegate::createEditor(): we should never end up here");
0442     }
0443     return d->m_editor;
0444 }
0445 
0446 int LedgerDelegate::editorRow() const
0447 {
0448     return d->m_editorRow;
0449 }
0450 
0451 void LedgerDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
0452 {
0453     QStyleOptionViewItem opt = option;
0454     initStyleOption(&opt, index);
0455 
0456     // never change the background of the cell the mouse is hovering over
0457     opt.state &= ~QStyle::State_MouseOver;
0458 
0459     // show the focus only on the detail column
0460     opt.state &= ~QStyle::State_HasFocus;
0461 
0462     // if selected, always show as active, so that the
0463     // background does not change when the editor is shown
0464     if (opt.state & QStyle::State_Selected) {
0465         opt.state |= QStyle::State_Active;
0466     }
0467 
0468     painter->save();
0469 
0470     QAbstractItemView* view = qobject_cast< QAbstractItemView* >(parent());
0471     const bool editWidgetIsVisible = d->m_view && d->m_view->indexWidget(index);
0472     const bool rowHasSeparator = d->displaySeparator(index);
0473     const bool rowHasOnlineBalance = d->displayOnlineBalanceSeparator(index);
0474 
0475     // Background
0476     QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
0477     const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin);
0478     const int lineHeight = opt.fontMetrics.lineSpacing() + 2;
0479 
0480     if (rowHasSeparator) {
0481         // don't draw over the separator space
0482         opt.rect.setHeight(opt.rect.height() - lineHeight );
0483     }
0484     if (rowHasOnlineBalance) {
0485         // don't draw over the online balance space
0486         opt.rect.setHeight(opt.rect.height() - lineHeight );
0487     }
0488 
0489     style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);
0490 
0491     QPalette::ColorGroup cg;
0492 
0493     // Do not paint text if the edit widget is shown
0494     if (!editWidgetIsVisible) {
0495         if(view && (index.column() == (int)eLedgerModel::Column::Detail)) {
0496             if(view->currentIndex().row() == index.row()) {
0497                 opt.state |= QStyle::State_HasFocus;
0498             }
0499         }
0500         const QRect textArea = QRect(opt.rect.x() + margin, opt.rect.y() + margin, opt.rect.width() - 2 * margin, opt.rect.height() - 2 * margin);
0501         const bool selected = opt.state & QStyle::State_Selected;
0502 
0503         QStringList lines;
0504         if(index.column() == (int)eLedgerModel::Column::Detail) {
0505             lines << index.model()->data(index, (int)eLedgerModel::Role::PayeeName).toString();
0506             if(selected) {
0507                 lines << index.model()->data(index, (int)eLedgerModel::Role::CounterAccount).toString();
0508                 lines << index.model()->data(index, (int)eLedgerModel::Role::SingleLineMemo).toString();
0509 
0510             } else {
0511                 if(lines.at(0).isEmpty()) {
0512                     lines.clear();
0513                     lines << index.model()->data(index, (int)eLedgerModel::Role::SingleLineMemo).toString();
0514                 }
0515                 if(lines.at(0).isEmpty()) {
0516                     lines << index.model()->data(index, (int)eLedgerModel::Role::CounterAccount).toString();
0517                 }
0518             }
0519             lines.removeAll(QString());
0520         }
0521 
0522         const bool erroneous = index.model()->data(index, (int)eLedgerModel::Role::Erroneous).toBool();
0523 
0524         // draw the text items
0525         if(!opt.text.isEmpty() || !lines.isEmpty()) {
0526 
0527             // check if it is a scheduled transaction and display it as inactive
0528             if(!index.model()->data(index, (int)eLedgerModel::Role::ScheduleId).toString().isEmpty()) {
0529                 opt.state &= ~QStyle::State_Enabled;
0530             }
0531 
0532             cg = (opt.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled;
0533 
0534             if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) {
0535                 cg = QPalette::Inactive;
0536             }
0537             if (selected) {
0538                 // always use the normal palette since the background is also in normal
0539                 painter->setPen(opt.palette.color(QPalette::ColorGroup(QPalette::Normal), QPalette::HighlightedText));
0540 
0541             } else if (erroneous) {
0542                 painter->setPen(m_erroneousColor);
0543 
0544             } else {
0545                 painter->setPen(opt.palette.color(cg, QPalette::Text));
0546             }
0547 
0548             if (opt.state & QStyle::State_Editing) {
0549                 painter->setPen(opt.palette.color(cg, QPalette::Text));
0550                 painter->drawRect(textArea.adjusted(0, 0, -1, -1));
0551             }
0552 
0553             // collect data for the various columns
0554             if(index.column() == (int)eLedgerModel::Column::Detail) {
0555                 for(int i = 0; i < lines.count(); ++i) {
0556                     painter->drawText(textArea.adjusted(0, lineHeight * i, 0, 0), opt.displayAlignment, lines[i]);
0557                 }
0558 
0559             } else {
0560                 painter->drawText(textArea, opt.displayAlignment, opt.text);
0561             }
0562         }
0563 
0564         // draw the focus rect
0565         if(opt.state & QStyle::State_HasFocus) {
0566             QStyleOptionFocusRect o;
0567             o.QStyleOption::operator=(opt);
0568             o.rect = style->proxy()->subElementRect(QStyle::SE_ItemViewItemFocusRect, &opt, opt.widget);
0569             o.state |= QStyle::State_KeyboardFocusChange;
0570             o.state |= QStyle::State_Item;
0571 
0572             cg = (opt.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled;
0573             o.backgroundColor = opt.palette.color(cg, (opt.state & QStyle::State_Selected)
0574                                                   ? QPalette::Highlight : QPalette::Window);
0575             style->proxy()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, opt.widget);
0576         }
0577 
0578         // draw the attention mark
0579         if((index.column() == (int)eLedgerModel::Column::Detail)
0580                 && erroneous) {
0581             QPixmap attention;
0582             attention.loadFromData(attentionSign, sizeof(attentionSign), 0, 0);
0583             style->proxy()->drawItemPixmap(painter, option.rect, Qt::AlignRight | Qt::AlignTop, attention);
0584         }
0585     }
0586 
0587     // draw a separator if any
0588     if (rowHasOnlineBalance) {
0589         opt.rect.setY(opt.rect.y() + opt.rect.height());
0590         opt.rect.setHeight(lineHeight);
0591         d->m_onlineBalanceSeparator->adjustBackgroundScheme(opt.palette, index);
0592         opt.backgroundBrush = opt.palette.base();
0593 
0594         // never draw it as selected but always enabled
0595         opt.state &= ~QStyle::State_Selected;
0596         style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);
0597 
0598         // when the editor is shown, the row has only a single column
0599         // so we need to paint the separator if we get here in this casee
0600         bool needPaint = editWidgetIsVisible;
0601 
0602         if(!needPaint && (index.column() == (int)eLedgerModel::Column::Detail)) {
0603             needPaint = true;
0604             // adjust the rect to cover all columns
0605             if(view && view->viewport()) {
0606                 opt.rect.setX(0);
0607                 opt.rect.setWidth(view->viewport()->width());
0608             }
0609         }
0610 
0611         if(needPaint) {
0612             painter->setPen(opt.palette.color(QPalette::Normal, QPalette::Text));
0613             painter->drawText(opt.rect, Qt::AlignCenter, d->m_onlineBalanceSeparator->separatorText(index));
0614         }
0615     }
0616 
0617     if (rowHasSeparator) {
0618         opt.rect.setY(opt.rect.y() + opt.rect.height());
0619         opt.rect.setHeight(lineHeight);
0620         d->m_separator->adjustBackgroundScheme(opt.palette, index);
0621         opt.backgroundBrush = opt.palette.base();
0622 
0623         // never draw it as selected but always enabled
0624         opt.state &= ~QStyle::State_Selected;
0625         style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);
0626 
0627         // when the editor is shown, the row has only a single column
0628         // so we need to paint the separator if we get here in this casee
0629         bool needPaint = editWidgetIsVisible;
0630 
0631         if(!needPaint && (index.column() == (int)eLedgerModel::Column::Detail)) {
0632             needPaint = true;
0633             // adjust the rect to cover all columns
0634             if(view && view->viewport()) {
0635                 opt.rect.setX(0);
0636                 opt.rect.setWidth(view->viewport()->width());
0637             }
0638         }
0639 
0640         if(needPaint) {
0641             painter->setPen(opt.palette.foreground().color());
0642             painter->drawText(opt.rect, Qt::AlignCenter, d->m_separator->separatorText(index));
0643         }
0644     }
0645     painter->restore();
0646 }
0647 
0648 QSize LedgerDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
0649 {
0650     bool fullDisplay = false;
0651     if(d->m_view) {
0652         QModelIndex currentIndex = d->m_view->currentIndex();
0653         if(currentIndex.isValid()) {
0654             QString currentId = currentIndex.model()->data(currentIndex, (int)eLedgerModel::Role::TransactionSplitId).toString();
0655             QString myId = index.model()->data(index, (int)eLedgerModel::Role::TransactionSplitId).toString();
0656             fullDisplay = (currentId == myId);
0657         }
0658     }
0659 
0660     QSize size;
0661     QStyleOptionViewItem opt = option;
0662     int rows = 1;
0663     initStyleOption(&opt, index);
0664 
0665     QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
0666     const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin);
0667     const int lineHeight = opt.fontMetrics.lineSpacing();
0668 
0669     if(index.isValid()) {
0670         // check if we are showing the edit widget
0671         // const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(opt.widget);
0672         if (d->m_view) {
0673             QModelIndex editIndex = d->m_view->model()->index(index.row(), 0);
0674             if(editIndex.isValid()) {
0675                 QWidget* editor = d->m_view->indexWidget(editIndex);
0676                 if(editor) {
0677                     size = editor->minimumSizeHint();
0678                     if(d->displaySeparator(index)) {
0679                         // don't draw over the separator space
0680                         size += QSize(0, lineHeight + margin);
0681                     }
0682                     if(d->displayOnlineBalanceSeparator(index)) {
0683                         // don't draw over the separator space
0684                         size += QSize(0, lineHeight + margin);
0685                     }
0686                     return size;
0687                 }
0688             }
0689         }
0690     }
0691 
0692     size = QSize(100, lineHeight + 2*margin);
0693 
0694     if(fullDisplay) {
0695         auto payee = index.data((int)eLedgerModel::Role::PayeeName).toString();
0696         auto counterAccount = index.data((int)eLedgerModel::Role::CounterAccount).toString();
0697         auto memo = index.data((int)eLedgerModel::Role::SingleLineMemo).toString();
0698 
0699         rows = (payee.length() > 0 ? 1 : 0) + (counterAccount.length() > 0 ? 1 : 0) + (memo.length() > 0 ? 1 : 0);
0700         // make sure we show at least one row
0701         if(!rows) {
0702             rows = 1;
0703         }
0704         // leave a few pixels as margin for each space between rows
0705         size.setHeight((size.height() * rows) - (margin * (rows - 1)));
0706 
0707     }
0708 
0709     if (d->m_separator && d->m_separator->rowHasSeparator(index)) {
0710         size.setHeight(size.height() + lineHeight + margin);
0711     }
0712     if (d->m_onlineBalanceSeparator && d->m_onlineBalanceSeparator->rowHasSeparator(index)) {
0713         size.setHeight(size.height() + lineHeight + margin);
0714     }
0715     return size;
0716 }
0717 
0718 void LedgerDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
0719 {
0720     Q_UNUSED(index);
0721 
0722     QStyle *style = option.widget ? option.widget->style() : QApplication::style();
0723     const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin);
0724     const int lineHeight = option.fontMetrics.lineSpacing();
0725 
0726     int ofs = 8;
0727     if(d->m_view) {
0728         if(d->m_view->verticalScrollBar()->isVisible()) {
0729             ofs += d->m_view->verticalScrollBar()->width();
0730         }
0731     }
0732 
0733     QRect r(option.rect);
0734     if (option.widget)
0735         r.setWidth(option.widget->width() - ofs);
0736 
0737     if(d->displaySeparator(index)) {
0738         // consider the separator space
0739         r.setHeight(r.height() - lineHeight - margin);
0740     }
0741     if(d->displayOnlineBalanceSeparator(index)) {
0742         // consider the separator space
0743         r.setHeight(r.height() - lineHeight - margin);
0744     }
0745     editor->setGeometry(r);
0746     editor->update();
0747 }
0748 
0749 void LedgerDelegate::endEdit()
0750 {
0751     if(d->m_editor) {
0752         if(d->m_editor->accepted()) {
0753             emit commitData(d->m_editor);
0754         }
0755         emit closeEditor(d->m_editor, NoHint);
0756         d->m_editorRow = -1;
0757     }
0758 }
0759 
0760 /**
0761  * This eventfilter seems to do nothing but it prevents that selecting a
0762  * different row with the mouse closes the editor
0763  */
0764 bool LedgerDelegate::eventFilter(QObject* o, QEvent* event)
0765 {
0766     return QAbstractItemDelegate::eventFilter(o, event);
0767 }
0768 
0769 void LedgerDelegate::setEditorData(QWidget* editWidget, const QModelIndex& index) const
0770 {
0771     NewTransactionEditor* editor = qobject_cast<NewTransactionEditor*>(editWidget);
0772     if(editor) {
0773         editor->loadTransaction(index.model()->data(index, (int)eLedgerModel::Role::TransactionSplitId).toString());
0774     }
0775 }
0776 
0777 void LedgerDelegate::setModelData(QWidget* editWidget, QAbstractItemModel* model, const QModelIndex& index) const
0778 {
0779     Q_UNUSED(model)
0780     Q_UNUSED(index)
0781 
0782     NewTransactionEditor* editor = qobject_cast<NewTransactionEditor*>(editWidget);
0783     if(editor) {
0784         editor->saveTransaction();
0785     }
0786 }