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

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 "register.h"
0008 
0009 #include <typeinfo>
0010 
0011 // ----------------------------------------------------------------------------
0012 // QT Includes
0013 
0014 #include <QString>
0015 #include <QToolTip>
0016 #include <QMouseEvent>
0017 #include <QList>
0018 #include <QKeyEvent>
0019 #include <QEvent>
0020 #include <QFrame>
0021 #include <QHeaderView>
0022 #include <QApplication>
0023 #include <QPushButton>
0024 #include <QTimer>
0025 
0026 // ----------------------------------------------------------------------------
0027 // KDE Includes
0028 
0029 #include <KLocalizedString>
0030 
0031 // ----------------------------------------------------------------------------
0032 // Project Includes
0033 
0034 #include "mymoneysplit.h"
0035 #include "mymoneytransaction.h"
0036 #include "mymoneyexception.h"
0037 #include "mymoneyaccount.h"
0038 #include "stdtransactiondownloaded.h"
0039 #include "stdtransactionmatched.h"
0040 #include "selectedtransactions.h"
0041 #include "scheduledtransaction.h"
0042 #include "kmymoneysettings.h"
0043 #include "mymoneymoney.h"
0044 #include "mymoneyfile.h"
0045 #include "groupmarkers.h"
0046 #include "fancydategroupmarkers.h"
0047 #include "registeritemdelegate.h"
0048 #include "itemptrvector.h"
0049 #include "mymoneyenums.h"
0050 #include "widgetenums.h"
0051 
0052 using namespace KMyMoneyRegister;
0053 using namespace eWidgets;
0054 using namespace eMyMoney;
0055 
0056 namespace KMyMoneyRegister
0057 {
0058 class RegisterPrivate
0059 {
0060 public:
0061     RegisterPrivate() :
0062         m_selectAnchor(nullptr),
0063         m_focusItem(nullptr),
0064         m_ensureVisibleItem(nullptr),
0065         m_firstItem(nullptr),
0066         m_lastItem(nullptr),
0067         m_firstErroneous(nullptr),
0068         m_lastErroneous(nullptr),
0069         m_rowHeightHint(0),
0070         m_ledgerLensForced(false),
0071         m_selectionMode(QTableWidget::MultiSelection),
0072         m_needResize(true),
0073         m_listsDirty(false),
0074         m_ignoreNextButtonRelease(false),
0075         m_needInitialColumnResize(false),
0076         m_usedWithEditor(false),
0077         m_mouseButton(Qt::MouseButtons(Qt::NoButton)),
0078         m_modifiers(Qt::KeyboardModifiers(Qt::NoModifier)),
0079         m_lastCol(eTransaction::Column::Account),
0080         m_detailsColumnType(eRegister::DetailColumn::PayeeFirst)
0081     {
0082     }
0083 
0084     ~RegisterPrivate()
0085     {
0086     }
0087 
0088     ItemPtrVector                m_items;
0089     QVector<RegisterItem*>       m_itemIndex;
0090     RegisterItem*                m_selectAnchor;
0091     RegisterItem*                m_focusItem;
0092     RegisterItem*                m_ensureVisibleItem;
0093     RegisterItem*                m_firstItem;
0094     RegisterItem*                m_lastItem;
0095     RegisterItem*                m_firstErroneous;
0096     RegisterItem*                m_lastErroneous;
0097 
0098     int                          m_rowHeightHint;
0099 
0100     MyMoneyAccount               m_account;
0101 
0102     bool                         m_ledgerLensForced;
0103     QAbstractItemView::SelectionMode m_selectionMode;
0104 
0105     bool                         m_needResize;
0106     bool                         m_listsDirty;
0107     bool                         m_ignoreNextButtonRelease;
0108     bool                         m_needInitialColumnResize;
0109     bool                         m_usedWithEditor;
0110     Qt::MouseButtons             m_mouseButton;
0111     Qt::KeyboardModifiers        m_modifiers;
0112     eTransaction::Column         m_lastCol;
0113     QList<SortField>             m_sortOrder;
0114     QRect                        m_lastRepaintRect;
0115     eRegister::DetailColumn      m_detailsColumnType;
0116 
0117 };
0118 
0119 Register::Register(QWidget *parent) :
0120     TransactionEditorContainer(parent),
0121     d_ptr(new RegisterPrivate)
0122 {
0123     // used for custom coloring with the help of the application's stylesheet
0124     setObjectName(QLatin1String("register"));
0125     setItemDelegate(new RegisterItemDelegate(this));
0126 
0127     setEditTriggers(QAbstractItemView::NoEditTriggers);
0128     setColumnCount((int)eTransaction::Column::LastColumn);
0129     setSelectionBehavior(QAbstractItemView::SelectRows);
0130     setAcceptDrops(true);
0131     setShowGrid(false);
0132     setContextMenuPolicy(Qt::DefaultContextMenu);
0133 
0134     setHorizontalHeaderItem((int)eTransaction::Column::Number, new QTableWidgetItem());
0135     setHorizontalHeaderItem((int)eTransaction::Column::Date, new QTableWidgetItem());
0136     setHorizontalHeaderItem((int)eTransaction::Column::Account, new QTableWidgetItem());
0137     setHorizontalHeaderItem((int)eTransaction::Column::Security, new QTableWidgetItem());
0138     setHorizontalHeaderItem((int)eTransaction::Column::Detail, new QTableWidgetItem());
0139     setHorizontalHeaderItem((int)eTransaction::Column::ReconcileFlag, new QTableWidgetItem());
0140     setHorizontalHeaderItem((int)eTransaction::Column::Payment, new QTableWidgetItem());
0141     setHorizontalHeaderItem((int)eTransaction::Column::Deposit, new QTableWidgetItem());
0142     setHorizontalHeaderItem((int)eTransaction::Column::Quantity, new QTableWidgetItem());
0143     setHorizontalHeaderItem((int)eTransaction::Column::Price, new QTableWidgetItem());
0144     setHorizontalHeaderItem((int)eTransaction::Column::Value, new QTableWidgetItem());
0145     setHorizontalHeaderItem((int)eTransaction::Column::Balance, new QTableWidgetItem());
0146 
0147     // keep the following list in sync with KMyMoneyRegister::Column in transaction.h
0148     horizontalHeaderItem((int)eTransaction::Column::Number)->setText(i18nc("Cheque Number", "No."));
0149     horizontalHeaderItem((int)eTransaction::Column::Date)->setText(i18n("Date"));
0150     horizontalHeaderItem((int)eTransaction::Column::Account)->setText(i18n("Account"));
0151     horizontalHeaderItem((int)eTransaction::Column::Security)->setText(i18n("Security"));
0152     horizontalHeaderItem((int)eTransaction::Column::Detail)->setText(i18n("Details"));
0153     horizontalHeaderItem((int)eTransaction::Column::ReconcileFlag)->setText(i18n("C"));
0154     horizontalHeaderItem((int)eTransaction::Column::Payment)->setText(i18n("Payment"));
0155     horizontalHeaderItem((int)eTransaction::Column::Deposit)->setText(i18n("Deposit"));
0156     horizontalHeaderItem((int)eTransaction::Column::Quantity)->setText(i18n("Quantity"));
0157     horizontalHeaderItem((int)eTransaction::Column::Price)->setText(i18n("Price"));
0158     horizontalHeaderItem((int)eTransaction::Column::Value)->setText(i18n("Value"));
0159     horizontalHeaderItem((int)eTransaction::Column::Balance)->setText(i18n("Balance"));
0160 
0161     verticalHeader()->hide();
0162 
0163     horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed);
0164     horizontalHeader()->setSortIndicatorShown(false);
0165     horizontalHeader()->setSectionsMovable(false);
0166     horizontalHeader()->setSectionsClickable(false);
0167     horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
0168 
0169     connect(this, &QTableWidget::cellClicked, this, static_cast<void (Register::*)(int, int)>(&Register::selectItem));
0170     connect(this, &QTableWidget::cellDoubleClicked, this, &Register::slotDoubleClicked);
0171 }
0172 
0173 Register::~Register()
0174 {
0175     Q_D(Register);
0176     clear();
0177     delete d;
0178 }
0179 
0180 bool Register::eventFilter(QObject* o, QEvent* e)
0181 {
0182     if (o == this && e->type() == QEvent::KeyPress) {
0183         auto ke = dynamic_cast<QKeyEvent*>(e);
0184         if (ke && ke->key() == Qt::Key_Menu) {
0185             emit openContextMenu();
0186             return true;
0187         }
0188     }
0189     return QTableWidget::eventFilter(o, e);
0190 }
0191 
0192 void Register::setupRegister(const MyMoneyAccount& account, const QList<eTransaction::Column>& cols)
0193 {
0194     Q_D(Register);
0195     d->m_account = account;
0196     setUpdatesEnabled(false);
0197 
0198     for (auto i = 0; i < (int)eTransaction::Column::LastColumn; ++i)
0199         hideColumn(i);
0200 
0201     d->m_needInitialColumnResize = true;
0202 
0203     d->m_lastCol = static_cast<eTransaction::Column>(0);
0204     QList<eTransaction::Column>::const_iterator it_c;
0205     for (it_c = cols.begin(); it_c != cols.end(); ++it_c) {
0206         if ((*it_c) > eTransaction::Column::LastColumn)
0207             continue;
0208         showColumn((int)*it_c);
0209         if (*it_c > d->m_lastCol)
0210             d->m_lastCol = *it_c;
0211     }
0212 
0213     setUpdatesEnabled(true);
0214 }
0215 
0216 void Register::setupRegister(const MyMoneyAccount& account, bool showAccountColumn)
0217 {
0218     Q_D(Register);
0219     d->m_account = account;
0220     setUpdatesEnabled(false);
0221 
0222     for (auto i = 0; i < (int)eTransaction::Column::LastColumn; ++i)
0223         hideColumn(i);
0224 
0225     horizontalHeaderItem((int)eTransaction::Column::Payment)->setText(i18nc("Payment made from account", "Payment"));
0226     horizontalHeaderItem((int)eTransaction::Column::Deposit)->setText(i18nc("Deposit into account", "Deposit"));
0227 
0228     if (account.id().isEmpty()) {
0229         setUpdatesEnabled(true);
0230         return;
0231     }
0232 
0233     d->m_needInitialColumnResize = true;
0234 
0235     // turn on standard columns
0236     showColumn((int)eTransaction::Column::Date);
0237     showColumn((int)eTransaction::Column::Detail);
0238     showColumn((int)eTransaction::Column::ReconcileFlag);
0239 
0240     // balance
0241     switch (account.accountType()) {
0242     case Account::Type::Stock:
0243         break;
0244     default:
0245         showColumn((int)eTransaction::Column::Balance);
0246         break;
0247     }
0248 
0249     // Number column
0250     switch (account.accountType()) {
0251     case Account::Type::Savings:
0252     case Account::Type::Cash:
0253     case Account::Type::Loan:
0254     case Account::Type::AssetLoan:
0255     case Account::Type::Asset:
0256     case Account::Type::Liability:
0257     case Account::Type::Equity:
0258         if (KMyMoneySettings::alwaysShowNrField())
0259             showColumn((int)eTransaction::Column::Number);
0260         break;
0261 
0262     case Account::Type::Checkings:
0263     case Account::Type::CreditCard:
0264         showColumn((int)eTransaction::Column::Number);
0265         break;
0266 
0267     default:
0268         hideColumn((int)eTransaction::Column::Number);
0269         break;
0270     }
0271 
0272     switch (account.accountType()) {
0273     case Account::Type::Income:
0274     case Account::Type::Expense:
0275         showAccountColumn = true;
0276         break;
0277     default:
0278         break;
0279     }
0280 
0281     if (showAccountColumn)
0282         showColumn((int)eTransaction::Column::Account);
0283 
0284     // Security, activity, payment, deposit, amount, price and value column
0285     switch (account.accountType()) {
0286     default:
0287         showColumn((int)eTransaction::Column::Payment);
0288         showColumn((int)eTransaction::Column::Deposit);
0289         break;
0290 
0291     case Account::Type::Investment:
0292         showColumn((int)eTransaction::Column::Security);
0293         showColumn((int)eTransaction::Column::Quantity);
0294         showColumn((int)eTransaction::Column::Price);
0295         showColumn((int)eTransaction::Column::Value);
0296         break;
0297     }
0298 
0299     // headings
0300     switch (account.accountType()) {
0301     case Account::Type::CreditCard:
0302         horizontalHeaderItem((int)eTransaction::Column::Payment)->setText(i18nc("Payment made with credit card", "Charge"));
0303         horizontalHeaderItem((int)eTransaction::Column::Deposit)->setText(i18nc("Payment towards credit card", "Payment"));
0304         break;
0305     case Account::Type::Asset:
0306     case Account::Type::AssetLoan:
0307         horizontalHeaderItem((int)eTransaction::Column::Payment)->setText(i18nc("Decrease of asset/liability value", "Decrease"));
0308         horizontalHeaderItem((int)eTransaction::Column::Deposit)->setText(i18nc("Increase of asset/liability value", "Increase"));
0309         break;
0310     case Account::Type::Liability:
0311     case Account::Type::Loan:
0312         horizontalHeaderItem((int)eTransaction::Column::Payment)->setText(i18nc("Increase of asset/liability value", "Increase"));
0313         horizontalHeaderItem((int)eTransaction::Column::Deposit)->setText(i18nc("Decrease of asset/liability value", "Decrease"));
0314         break;
0315     case Account::Type::Income:
0316     case Account::Type::Expense:
0317         horizontalHeaderItem((int)eTransaction::Column::Payment)->setText(i18n("Income"));
0318         horizontalHeaderItem((int)eTransaction::Column::Deposit)->setText(i18n("Expense"));
0319         break;
0320 
0321     default:
0322         break;
0323     }
0324 
0325     d->m_lastCol = eTransaction::Column::Balance;
0326 
0327     setUpdatesEnabled(true);
0328 }
0329 
0330 bool Register::focusNextPrevChild(bool next)
0331 {
0332     return QFrame::focusNextPrevChild(next);
0333 }
0334 
0335 void Register::setSortOrder(const QString& order)
0336 {
0337     Q_D(Register);
0338     const QStringList orderList = order.split(',', QString::SkipEmptyParts);
0339     QStringList::const_iterator it;
0340     d->m_sortOrder.clear();
0341     for (it = orderList.constBegin(); it != orderList.constEnd(); ++it) {
0342         d->m_sortOrder << static_cast<SortField>((*it).toInt());
0343     }
0344 }
0345 
0346 const QList<SortField>& Register::sortOrder() const
0347 {
0348     Q_D(const Register);
0349     return d->m_sortOrder;
0350 }
0351 
0352 void Register::sortItems()
0353 {
0354     Q_D(Register);
0355     if (d->m_items.count() == 0)
0356         return;
0357 
0358     // sort the array of pointers to the transactions
0359     d->m_items.sort();
0360 
0361     // update the next/prev item chains
0362     RegisterItem* prev = 0;
0363     RegisterItem* item;
0364     d->m_firstItem = d->m_lastItem = 0;
0365     for (QVector<RegisterItem*>::size_type i = 0; i < d->m_items.size(); ++i) {
0366         item = d->m_items[i];
0367         if (!item)
0368             continue;
0369 
0370         if (!d->m_firstItem)
0371             d->m_firstItem = item;
0372         d->m_lastItem = item;
0373         if (prev)
0374             prev->setNextItem(item);
0375         item->setPrevItem(prev);
0376         item->setNextItem(0);
0377         prev = item;
0378     }
0379 
0380     // update the balance visibility settings
0381     item = d->m_lastItem;
0382     bool showBalance = true;
0383     while (item) {
0384         auto t = dynamic_cast<Transaction*>(item);
0385         if (t) {
0386             t->setShowBalance(showBalance);
0387             if (!t->isVisible()) {
0388                 showBalance = false;
0389             }
0390         }
0391         item = item->prevItem();
0392     }
0393 
0394     // force update of the item index (row to item array)
0395     d->m_listsDirty = true;
0396 }
0397 
0398 eTransaction::Column Register::lastCol() const
0399 {
0400     Q_D(const Register);
0401     return d->m_lastCol;
0402 }
0403 
0404 SortField Register::primarySortKey() const
0405 {
0406     Q_D(const Register);
0407     if (!d->m_sortOrder.isEmpty())
0408         return static_cast<SortField>(d->m_sortOrder.first());
0409     return SortField::Unknown;
0410 }
0411 
0412 
0413 void Register::clear()
0414 {
0415     Q_D(Register);
0416     d->m_firstErroneous = d->m_lastErroneous = 0;
0417     d->m_ensureVisibleItem = 0;
0418 
0419     d->m_items.clear();
0420 
0421     RegisterItem* p;
0422     while ((p = firstItem()) != 0) {
0423         delete p;
0424     }
0425 
0426     d->m_firstItem = d->m_lastItem = 0;
0427 
0428     d->m_listsDirty = true;
0429     d->m_selectAnchor = 0;
0430     d->m_focusItem = 0;
0431 
0432 #ifndef KMM_DESIGNER
0433     // recalculate row height hint
0434     QFontMetrics fm(KMyMoneySettings::listCellFontEx());
0435     d->m_rowHeightHint = fm.lineSpacing() + 6;
0436 #endif
0437 
0438     d->m_needInitialColumnResize = true;
0439     d->m_needResize = true;
0440     updateRegister(true);
0441 }
0442 
0443 void Register::insertItemAfter(RegisterItem*p, RegisterItem* prev)
0444 {
0445     Q_D(Register);
0446     RegisterItem* next = 0;
0447     if (!prev)
0448         prev = lastItem();
0449 
0450     if (prev) {
0451         next = prev->nextItem();
0452         prev->setNextItem(p);
0453     }
0454     if (next)
0455         next->setPrevItem(p);
0456 
0457     p->setPrevItem(prev);
0458     p->setNextItem(next);
0459 
0460     if (!d->m_firstItem)
0461         d->m_firstItem = p;
0462     if (!d->m_lastItem)
0463         d->m_lastItem = p;
0464 
0465     if (prev == d->m_lastItem)
0466         d->m_lastItem = p;
0467 
0468     d->m_listsDirty = true;
0469     d->m_needResize = true;
0470 }
0471 
0472 void Register::addItem(RegisterItem* p)
0473 {
0474     Q_D(Register);
0475     RegisterItem* q = lastItem();
0476     if (q)
0477         q->setNextItem(p);
0478     p->setPrevItem(q);
0479     p->setNextItem(0);
0480 
0481     d->m_items.append(p);
0482 
0483     if (!d->m_firstItem)
0484         d->m_firstItem = p;
0485     d->m_lastItem = p;
0486     d->m_listsDirty = true;
0487     d->m_needResize = true;
0488 }
0489 
0490 void Register::removeItem(RegisterItem* p)
0491 {
0492     Q_D(Register);
0493     // remove item from list
0494     if (p->prevItem())
0495         p->prevItem()->setNextItem(p->nextItem());
0496     if (p->nextItem())
0497         p->nextItem()->setPrevItem(p->prevItem());
0498 
0499     // update first and last pointer if required
0500     if (p == d->m_firstItem)
0501         d->m_firstItem = p->nextItem();
0502     if (p == d->m_lastItem)
0503         d->m_lastItem = p->prevItem();
0504 
0505     // make sure we don't do it twice
0506     p->setNextItem(0);
0507     p->setPrevItem(0);
0508 
0509     // remove it from the m_items array
0510     int i = d->m_items.indexOf(p);
0511     if (-1 != i) {
0512         d->m_items[i] = 0;
0513     }
0514     d->m_listsDirty = true;
0515     d->m_needResize = true;
0516 }
0517 
0518 RegisterItem* Register::firstItem() const
0519 {
0520     Q_D(const Register);
0521     return d->m_firstItem;
0522 }
0523 
0524 RegisterItem* Register::nextItem(RegisterItem* item) const
0525 {
0526     return item->nextItem();
0527 }
0528 
0529 RegisterItem* Register::lastItem() const
0530 {
0531     Q_D(const Register);
0532     return d->m_lastItem;
0533 }
0534 
0535 void Register::setupItemIndex(int rowCount)
0536 {
0537     Q_D(Register);
0538     // setup index array
0539     d->m_itemIndex.clear();
0540     d->m_itemIndex.reserve(rowCount);
0541 
0542     // fill index array
0543     rowCount = 0;
0544     RegisterItem* prev = 0;
0545     d->m_firstItem = d->m_lastItem = 0;
0546     for (QVector<RegisterItem*>::size_type i = 0; i < d->m_items.size(); ++i) {
0547         RegisterItem* item = d->m_items[i];
0548         if (!item)
0549             continue;
0550         if (!d->m_firstItem)
0551             d->m_firstItem = item;
0552         d->m_lastItem = item;
0553         if (prev)
0554             prev->setNextItem(item);
0555         item->setPrevItem(prev);
0556         item->setNextItem(0);
0557         prev = item;
0558         for (int j = item->numRowsRegister(); j; --j) {
0559             d->m_itemIndex.push_back(item);
0560         }
0561     }
0562 }
0563 
0564 void Register::updateAlternate() const
0565 {
0566     Q_D(const Register);
0567     bool alternate = false;
0568     for (QVector<RegisterItem*>::size_type i = 0; i < d->m_items.size(); ++i) {
0569         RegisterItem* item = d->m_items[i];
0570         if (!item)
0571             continue;
0572         if (item->isVisible()) {
0573             item->setAlternate(alternate);
0574             alternate ^= true;
0575         }
0576     }
0577 }
0578 
0579 void Register::suppressAdjacentMarkers()
0580 {
0581     bool lastWasGroupMarker = false;
0582     KMyMoneyRegister::RegisterItem* p = lastItem();
0583     auto t = dynamic_cast<KMyMoneyRegister::Transaction*>(p);
0584     if (t && t->transaction().id().isEmpty()) {
0585         lastWasGroupMarker = true;
0586         p = p->prevItem();
0587     }
0588     while (p) {
0589         auto m = dynamic_cast<KMyMoneyRegister::GroupMarker*>(p);
0590         if (m) {
0591             // make adjacent group marker invisible except those that show statement information
0592             if (lastWasGroupMarker && (dynamic_cast<KMyMoneyRegister::StatementGroupMarker*>(m) == 0)) {
0593                 m->setVisible(false);
0594             }
0595             lastWasGroupMarker = true;
0596         } else if (p->isVisible())
0597             lastWasGroupMarker = false;
0598         p = p->prevItem();
0599     }
0600 }
0601 
0602 void Register::updateRegister(bool forceUpdateRowHeight)
0603 {
0604     Q_D(Register);
0605     if (d->m_listsDirty || forceUpdateRowHeight) {
0606         // don't get in here recursively
0607         d->m_listsDirty = false;
0608 
0609         int rowCount = 0;
0610         // determine the number of rows we need to display all items
0611         // while going through the list, check for erroneous transactions
0612         for (QVector<RegisterItem*>::size_type i = 0; i < d->m_items.size(); ++i) {
0613             RegisterItem* item = d->m_items[i];
0614             if (!item)
0615                 continue;
0616             item->setStartRow(rowCount);
0617             item->setNeedResize();
0618             rowCount += item->numRowsRegister();
0619 
0620             if (item->isErroneous()) {
0621                 if (!d->m_firstErroneous)
0622                     d->m_firstErroneous = item;
0623                 d->m_lastErroneous = item;
0624             }
0625         }
0626 
0627         updateAlternate();
0628 
0629         // create item index
0630         setupItemIndex(rowCount);
0631 
0632         bool needUpdateHeaders = (QTableWidget::rowCount() != rowCount) | forceUpdateRowHeight;
0633 
0634         // setup QTable.  Make sure to suppress screen updates for now
0635         setRowCount(rowCount);
0636 
0637         // if we need to update the headers, we do it now for all rows
0638         // again we make sure to suppress screen updates
0639         if (needUpdateHeaders) {
0640             for (auto i = 0; i < rowCount; ++i) {
0641                 RegisterItem* item = itemAtRow(i);
0642                 if (item->isVisible()) {
0643                     showRow(i);
0644                 } else {
0645                     hideRow(i);
0646                 }
0647                 verticalHeader()->resizeSection(i, item->rowHeightHint());
0648             }
0649             verticalHeader()->setUpdatesEnabled(true);
0650         }
0651 
0652         // force resizeing of the columns if necessary
0653         if (d->m_needInitialColumnResize) {
0654             QTimer::singleShot(0, this, SLOT(resize()));
0655             d->m_needInitialColumnResize = false;
0656         } else {
0657             update();
0658 
0659             // if the number of rows changed, we might need to resize the register
0660             // to make sure we reflect the current visibility of the scrollbars.
0661             if (needUpdateHeaders)
0662                 QTimer::singleShot(0, this, SLOT(resize()));
0663         }
0664     }
0665 }
0666 
0667 int Register::rowHeightHint() const
0668 {
0669     Q_D(const Register);
0670     if (!d->m_rowHeightHint) {
0671         qDebug("Register::rowHeightHint(): m_rowHeightHint is zero!!");
0672     }
0673     return d->m_rowHeightHint;
0674 }
0675 
0676 void Register::focusInEvent(QFocusEvent* ev)
0677 {
0678     Q_D(const Register);
0679     QTableWidget::focusInEvent(ev);
0680     if (d->m_focusItem) {
0681         d->m_focusItem->setFocus(true, false);
0682     }
0683 }
0684 
0685 bool Register::event(QEvent* event)
0686 {
0687     if (event->type() == QEvent::ToolTip) {
0688         QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
0689 
0690         // get the row, if it's the header, then we're done
0691         // otherwise, adjust the row to be 0 based.
0692         int row = rowAt(helpEvent->y());
0693         if (!row)
0694             return true;
0695         --row;
0696 
0697         int col = columnAt(helpEvent->x());
0698         RegisterItem* item = itemAtRow(row);
0699         if (!item)
0700             return true;
0701 
0702         row = row - item->startRow();
0703 
0704         QString msg;
0705         QRect rect;
0706         if (!item->maybeTip(helpEvent->pos(), row, col, rect, msg))
0707             return true;
0708 
0709         if (!msg.isEmpty()) {
0710             QToolTip::showText(helpEvent->globalPos(), msg);
0711         } else {
0712             QToolTip::hideText();
0713             event->ignore();
0714         }
0715         return true;
0716     }
0717     return TransactionEditorContainer::event(event);
0718 }
0719 
0720 void Register::focusOutEvent(QFocusEvent* ev)
0721 {
0722     Q_D(Register);
0723     if (d->m_focusItem) {
0724         d->m_focusItem->setFocus(false, false);
0725     }
0726     QTableWidget::focusOutEvent(ev);
0727 }
0728 
0729 void Register::resizeEvent(QResizeEvent* ev)
0730 {
0731     TransactionEditorContainer::resizeEvent(ev);
0732     resize((int)eTransaction::Column::Detail, true);
0733 }
0734 
0735 void Register::resize()
0736 {
0737     resize((int)eTransaction::Column::Detail);
0738 }
0739 
0740 void Register::resize(int col, bool force)
0741 {
0742     Q_D(Register);
0743     if (!d->m_needResize && !force)
0744         return;
0745 
0746     d->m_needResize = false;
0747 
0748     // resize the register
0749     int w = viewport()->width();
0750 
0751     // TODO I was playing a bit with manual ledger resizing but could not get
0752     // a good solution. I just leave the code around, so that maybe others
0753     // pick it up again.  So far, it's not clear to me where to store the
0754     // size of the sections:
0755     //
0756     // a) with the account (as it is done now)
0757     // b) with the application for the specific account type
0758     // c) ????
0759     //
0760     // Ideas are welcome (ipwizard: 2007-07-19)
0761     // Note: currently there's no way to switch back to automatic
0762     // column sizing once the manual sizing option has been saved
0763 #if 0
0764     if (m_account.value("kmm-ledger-column-width").isEmpty()) {
0765 #endif
0766 
0767         // check which space we need
0768         if (columnWidth((int)eTransaction::Column::Number))
0769             adjustColumn((int)eTransaction::Column::Number);
0770         if (columnWidth((int)eTransaction::Column::Account))
0771             adjustColumn((int)eTransaction::Column::Account);
0772         if (columnWidth((int)eTransaction::Column::Payment))
0773             adjustColumn((int)eTransaction::Column::Payment);
0774         if (columnWidth((int)eTransaction::Column::Deposit))
0775             adjustColumn((int)eTransaction::Column::Deposit);
0776         if (columnWidth((int)eTransaction::Column::Quantity))
0777             adjustColumn((int)eTransaction::Column::Quantity);
0778         if (columnWidth((int)eTransaction::Column::Balance))
0779             adjustColumn((int)eTransaction::Column::Balance);
0780         if (columnWidth((int)eTransaction::Column::Price))
0781             adjustColumn((int)eTransaction::Column::Price);
0782         if (columnWidth((int)eTransaction::Column::Value))
0783             adjustColumn((int)eTransaction::Column::Value);
0784 
0785         // make amount columns all the same size
0786         // only extend the entry columns to make sure they fit
0787         // the widget
0788         int dwidth = 0;
0789         int ewidth = 0;
0790         if (ewidth < columnWidth((int)eTransaction::Column::Payment))
0791             ewidth = columnWidth((int)eTransaction::Column::Payment);
0792         if (ewidth < columnWidth((int)eTransaction::Column::Deposit))
0793             ewidth = columnWidth((int)eTransaction::Column::Deposit);
0794         if (ewidth < columnWidth((int)eTransaction::Column::Quantity))
0795             ewidth = columnWidth((int)eTransaction::Column::Quantity);
0796         if (dwidth < columnWidth((int)eTransaction::Column::Balance))
0797             dwidth = columnWidth((int)eTransaction::Column::Balance);
0798         if (ewidth < columnWidth((int)eTransaction::Column::Price))
0799             ewidth = columnWidth((int)eTransaction::Column::Price);
0800         if (dwidth < columnWidth((int)eTransaction::Column::Value))
0801             dwidth = columnWidth((int)eTransaction::Column::Value);
0802         int swidth = columnWidth((int)eTransaction::Column::Security);
0803         if (swidth > 0) {
0804             adjustColumn((int)eTransaction::Column::Security);
0805             swidth = columnWidth((int)eTransaction::Column::Security);
0806         }
0807 
0808         adjustColumn((int)eTransaction::Column::Date);
0809 
0810 #ifndef KMM_DESIGNER
0811         // Resize the date and money fields to either
0812         // a) the size required by the input widget if no transaction form is shown and the register is used with an editor
0813         // b) the adjusted value for the input widget if the transaction form is visible or an editor is not used
0814         if (d->m_usedWithEditor && !KMyMoneySettings::transactionForm()) {
0815             QPushButton *pushButton = new QPushButton;
0816             const int pushButtonSpacing = pushButton->sizeHint().width() + 5;
0817             setColumnWidth((int)eTransaction::Column::Date, columnWidth((int)eTransaction::Column::Date) + pushButtonSpacing + 4/* space for the spinbox arrows */);
0818             ewidth += pushButtonSpacing;
0819 
0820             if (swidth > 0) {
0821                 // extend the security width to make space for the selector arrow
0822                 swidth = columnWidth((int)eTransaction::Column::Security) + 40;
0823             }
0824             delete pushButton;
0825         }
0826 #endif
0827 
0828         if (columnWidth((int)eTransaction::Column::Payment))
0829             setColumnWidth((int)eTransaction::Column::Payment, ewidth);
0830         if (columnWidth((int)eTransaction::Column::Deposit))
0831             setColumnWidth((int)eTransaction::Column::Deposit, ewidth);
0832         if (columnWidth((int)eTransaction::Column::Quantity))
0833             setColumnWidth((int)eTransaction::Column::Quantity, ewidth);
0834         if (columnWidth((int)eTransaction::Column::Balance))
0835             setColumnWidth((int)eTransaction::Column::Balance, dwidth);
0836         if (columnWidth((int)eTransaction::Column::Price))
0837             setColumnWidth((int)eTransaction::Column::Price, ewidth);
0838         if (columnWidth((int)eTransaction::Column::Value))
0839             setColumnWidth((int)eTransaction::Column::Value, dwidth);
0840 
0841         if (columnWidth((int)eTransaction::Column::ReconcileFlag))
0842             setColumnWidth((int)eTransaction::Column::ReconcileFlag, 20);
0843 
0844         if (swidth > 0)
0845             setColumnWidth((int)eTransaction::Column::Security, swidth);
0846 #if 0
0847         // see comment above
0848     } else {
0849         QStringList colSizes = QStringList::split(",", m_account.value("kmm-ledger-column-width"), true);
0850         for (int i; i < colSizes.count(); ++i) {
0851             int colWidth = colSizes[i].toInt();
0852             if (colWidth == 0)
0853                 continue;
0854             setColumnWidth(i, w * colWidth / 100);
0855         }
0856     }
0857 #endif
0858 
0859     for (auto i = 0; i < columnCount(); ++i) {
0860         if (i == col)
0861             continue;
0862 
0863         w -= columnWidth(i);
0864     }
0865     setColumnWidth(col, w);
0866 }
0867 
0868 void Register::forceUpdateLists()
0869 {
0870     Q_D(Register);
0871     d->m_listsDirty = true;
0872 }
0873 
0874 int Register::minimumColumnWidth(int col)
0875 {
0876     Q_D(Register);
0877     QHeaderView *topHeader = horizontalHeader();
0878     int w = topHeader->fontMetrics().width(horizontalHeaderItem(col) ? horizontalHeaderItem(col)->text() : QString()) + 10;
0879     w = qMax(w, 20);
0880 #ifdef KMM_DESIGNER
0881     return w;
0882 #else
0883     int maxWidth = 0;
0884     int minWidth = 0;
0885     QFontMetrics cellFontMetrics(KMyMoneySettings::listCellFontEx());
0886     switch (col) {
0887     case (int)eTransaction::Column::Date:
0888         minWidth = cellFontMetrics.width(QLocale().toString(QDate(6999, 12, 29), QLocale::ShortFormat) + "  ");
0889         break;
0890     default:
0891         break;
0892     }
0893 
0894     // scan through the transactions
0895     for (auto i = 0; i < d->m_items.size(); ++i) {
0896         RegisterItem* const item = d->m_items[i];
0897         if (!item)
0898             continue;
0899         auto t = dynamic_cast<Transaction*>(item);
0900         if (t) {
0901             int nw = 0;
0902             try {
0903                 nw = t->registerColWidth(col, cellFontMetrics);
0904             } catch (const MyMoneyException &) {
0905                 // This should only be reached if the data in the file disappeared
0906                 // from under us, such as when the account was deleted from a
0907                 // different view, then this view is restored. In this case, new
0908                 // data is about to be loaded into the view anyway, so just remove
0909                 // the item from the register and swallow the exception.
0910                 //qDebug("%s", e.what());
0911                 removeItem(t);
0912             }
0913             w = qMax(w, nw);
0914             if (maxWidth) {
0915                 if (w > maxWidth) {
0916                     w = maxWidth;
0917                     break;
0918                 }
0919             }
0920             if (minWidth && (w < minWidth)) {
0921                 w = minWidth;
0922             }
0923         }
0924     }
0925 
0926     return w;
0927 #endif
0928 }
0929 
0930 void Register::adjustColumn(int col)
0931 {
0932     setColumnWidth(col, minimumColumnWidth(col));
0933 }
0934 
0935 void Register::clearSelection()
0936 {
0937     unselectItems();
0938     TransactionEditorContainer::clearSelection();
0939 }
0940 
0941 void Register::doSelectItems(int from, int to, bool selected)
0942 {
0943     Q_D(Register);
0944     int start, end;
0945     // make sure start is smaller than end
0946     if (from <= to) {
0947         start = from;
0948         end = to;
0949     } else {
0950         start = to;
0951         end = from;
0952     }
0953     // make sure we stay in bounds
0954     if (start < 0)
0955         start = 0;
0956     if ((end <= -1) || (end > (d->m_items.size() - 1)))
0957         end = d->m_items.size() - 1;
0958 
0959     RegisterItem* firstItem;
0960     RegisterItem* lastItem;
0961     firstItem = lastItem = 0;
0962     for (int i = start; i <= end; ++i) {
0963         RegisterItem* const item = d->m_items[i];
0964         if (item) {
0965             if (selected != item->isSelected()) {
0966                 if (!firstItem)
0967                     firstItem = item;
0968                 item->setSelected(selected);
0969                 lastItem = item;
0970             }
0971         }
0972     }
0973 }
0974 
0975 RegisterItem* Register::itemAtRow(int row) const
0976 {
0977     Q_D(const Register);
0978     if (row >= 0 && row < d->m_itemIndex.size()) {
0979         return d->m_itemIndex[row];
0980     }
0981     return 0;
0982 }
0983 
0984 int Register::rowToIndex(int row) const
0985 {
0986     Q_D(const Register);
0987     for (auto i = 0; i < d->m_items.size(); ++i) {
0988         RegisterItem* const item = d->m_items[i];
0989         if (!item)
0990             continue;
0991         if (row >= item->startRow() && row < (item->startRow() + item->numRowsRegister()))
0992             return i;
0993     }
0994     return -1;
0995 }
0996 
0997 void Register::selectedTransactions(SelectedTransactions& list) const
0998 {
0999     Q_D(const Register);
1000     if (d->m_focusItem && d->m_focusItem->isSelected() && d->m_focusItem->isVisible()) {
1001         auto t = dynamic_cast<Transaction*>(d->m_focusItem);
1002         if (t) {
1003             QString id;
1004             if (t->isScheduled())
1005                 id = t->transaction().id();
1006             SelectedTransaction s(t->transaction(), t->split(), id);
1007             list << s;
1008         }
1009     }
1010 
1011     for (auto i = 0; i < d->m_items.size(); ++i) {
1012         RegisterItem* const item = d->m_items[i];
1013         // make sure, we don't include the focus item twice
1014         if (item == d->m_focusItem)
1015             continue;
1016         if (item && item->isSelected() && item->isVisible()) {
1017             auto t = dynamic_cast<Transaction*>(item);
1018             if (t) {
1019                 QString id;
1020                 if (t->isScheduled())
1021                     id = t->transaction().id();
1022                 SelectedTransaction s(t->transaction(), t->split(), id);
1023                 list << s;
1024             }
1025         }
1026     }
1027 }
1028 
1029 QList<RegisterItem*> Register::selectedItems() const
1030 {
1031     Q_D(const Register);
1032     QList<RegisterItem*> list;
1033 
1034     RegisterItem* item = d->m_firstItem;
1035     while (item) {
1036         if (item && item->isSelected() && item->isVisible()) {
1037             list << item;
1038         }
1039         item = item->nextItem();
1040     }
1041     return list;
1042 }
1043 
1044 int Register::selectedItemsCount() const
1045 {
1046     Q_D(const Register);
1047     auto cnt = 0;
1048     RegisterItem* item = d->m_firstItem;
1049     while (item) {
1050         if (item->isSelected() && item->isVisible())
1051             ++cnt;
1052         item = item->nextItem();
1053     }
1054     return cnt;
1055 }
1056 
1057 void Register::mouseReleaseEvent(QMouseEvent *e)
1058 {
1059     Q_D(Register);
1060     if (e->button() == Qt::RightButton) {
1061         // see the comment in Register::contextMenuEvent
1062         // on Linux we never get here but on Windows this
1063         // event is fired before the contextMenuEvent which
1064         // causes the loss of the multiple selection; to avoid
1065         // this just ignore the event and act like on Linux
1066         return;
1067     }
1068     if (d->m_ignoreNextButtonRelease) {
1069         d->m_ignoreNextButtonRelease = false;
1070         return;
1071     }
1072     d->m_mouseButton = e->button();
1073     d->m_modifiers = QApplication::keyboardModifiers();
1074     QTableWidget::mouseReleaseEvent(e);
1075 }
1076 
1077 void Register::contextMenuEvent(QContextMenuEvent *e)
1078 {
1079     Q_D(Register);
1080     if (e->reason() == QContextMenuEvent::Mouse) {
1081         // since mouse release event is not called, we need
1082         // to reset the mouse button and the modifiers here
1083         d->m_mouseButton = Qt::NoButton;
1084         d->m_modifiers = Qt::NoModifier;
1085 
1086         // if a selected item is clicked don't change the selection
1087         RegisterItem* item = itemAtRow(rowAt(e->y()));
1088         if (item && !item->isSelected())
1089             selectItem(rowAt(e->y()), columnAt(e->x()));
1090     }
1091     openContextMenu();
1092 }
1093 
1094 void Register::unselectItems(int from, int to)
1095 {
1096     doSelectItems(from, to, false);
1097 }
1098 
1099 void Register::selectItems(int from, int to)
1100 {
1101     doSelectItems(from, to, true);
1102 }
1103 
1104 void Register::selectItem(int row, int col)
1105 {
1106     Q_D(Register);
1107     if (row >= 0 && row < d->m_itemIndex.size()) {
1108         RegisterItem* item = d->m_itemIndex[row];
1109 
1110         // don't support selecting when the item has an editor
1111         // or the item itself is not selectable
1112         if (item->hasEditorOpen() || !item->isSelectable()) {
1113             d->m_mouseButton = Qt::NoButton;
1114             return;
1115         }
1116         QString id = item->id();
1117         selectItem(item);
1118         // selectItem() might have changed the pointers, so we
1119         // need to reconstruct it here
1120         item = itemById(id);
1121         auto t = dynamic_cast<Transaction*>(item);
1122         if (t) {
1123             if (!id.isEmpty()) {
1124                 if (t && col == (int)eTransaction::Column::ReconcileFlag && selectedItemsCount() == 1 && !t->isScheduled())
1125                     emit reconcileStateColumnClicked(t);
1126             } else {
1127                 emit emptyItemSelected();
1128             }
1129         }
1130     }
1131 }
1132 
1133 void Register::setAnchorItem(RegisterItem* anchorItem)
1134 {
1135     Q_D(Register);
1136     d->m_selectAnchor = anchorItem;
1137 }
1138 
1139 bool Register::setFocusItem(RegisterItem* focusItem)
1140 {
1141     Q_D(Register);
1142     if (focusItem && focusItem->canHaveFocus()) {
1143         if (d->m_focusItem) {
1144             d->m_focusItem->setFocus(false);
1145         }
1146         auto item = dynamic_cast<Transaction*>(focusItem);
1147         if (d->m_focusItem != focusItem && item) {
1148             emit focusChanged(item);
1149         }
1150 
1151         d->m_focusItem = focusItem;
1152         d->m_focusItem->setFocus(true);
1153         if (d->m_listsDirty)
1154             updateRegister(KMyMoneySettings::ledgerLens() | !KMyMoneySettings::transactionForm());
1155         ensureItemVisible(d->m_focusItem);
1156         return true;
1157     } else
1158         return false;
1159 }
1160 
1161 bool Register::setFocusToTop()
1162 {
1163     Q_D(Register);
1164     RegisterItem* rgItem = d->m_firstItem;
1165     while (rgItem) {
1166         if (setFocusItem(rgItem))
1167             return true;
1168         rgItem = rgItem->nextItem();
1169     }
1170     return false;
1171 }
1172 
1173 void Register::selectItem(RegisterItem* item, bool dontChangeSelections)
1174 {
1175     Q_D(Register);
1176     if (!item)
1177         return;
1178 
1179     Qt::MouseButtons buttonState = d->m_mouseButton;
1180     Qt::KeyboardModifiers modifiers = d->m_modifiers;
1181     d->m_mouseButton = Qt::NoButton;
1182     d->m_modifiers = Qt::NoModifier;
1183 
1184     if (d->m_selectionMode == NoSelection)
1185         return;
1186 
1187     if (item->isSelectable()) {
1188         QString id = item->id();
1189         QList<RegisterItem*> itemList = selectedItems();
1190         bool okToSelect = true;
1191         auto cnt = itemList.count();
1192         auto scheduledTransactionSelected = false;
1193         if (cnt > 0) {
1194             auto& r = *(itemList.front());
1195             scheduledTransactionSelected = (typeid(r) == typeid(StdTransactionScheduled));
1196         }
1197         if (buttonState & Qt::LeftButton) {
1198             if (!(modifiers & (Qt::ShiftModifier | Qt::ControlModifier))
1199                     || (d->m_selectAnchor == 0)) {
1200                 if ((cnt != 1) || ((cnt == 1) && !item->isSelected())) {
1201                     emit aboutToSelectItem(item, okToSelect);
1202                     if (okToSelect) {
1203                         // pointer 'item' might have changed. reconstruct it.
1204                         item = itemById(id);
1205                         unselectItems();
1206                         item->setSelected(true);
1207                         setFocusItem(item);
1208                     }
1209                 }
1210                 if (okToSelect)
1211                     d->m_selectAnchor = item;
1212             }
1213 
1214             if (d->m_selectionMode == MultiSelection) {
1215                 switch (modifiers & (Qt::ShiftModifier | Qt::ControlModifier)) {
1216                 case Qt::ControlModifier:
1217                     if (scheduledTransactionSelected || typeid(*item) == typeid(StdTransactionScheduled))
1218                         okToSelect = false;
1219                     // toggle selection state of current item
1220                     emit aboutToSelectItem(item, okToSelect);
1221                     if (okToSelect) {
1222                         // pointer 'item' might have changed. reconstruct it.
1223                         item = itemById(id);
1224                         item->setSelected(!item->isSelected());
1225                         setFocusItem(item);
1226                     }
1227                     break;
1228 
1229                 case Qt::ShiftModifier:
1230                     if (scheduledTransactionSelected || typeid(*item) == typeid(StdTransactionScheduled))
1231                         okToSelect = false;
1232                     emit aboutToSelectItem(item, okToSelect);
1233                     if (okToSelect) {
1234                         // pointer 'item' might have changed. reconstruct it.
1235                         item = itemById(id);
1236                         unselectItems();
1237                         if (d->m_selectAnchor)
1238                             selectItems(rowToIndex(d->m_selectAnchor->startRow()), rowToIndex(item->startRow()));
1239                         setFocusItem(item);
1240                     }
1241                     break;
1242                 }
1243             }
1244         } else {
1245             // we get here when called by application logic
1246             emit aboutToSelectItem(item, okToSelect);
1247             if (okToSelect) {
1248                 // pointer 'item' might have changed. reconstruct it.
1249                 item = itemById(id);
1250                 if (!dontChangeSelections)
1251                     unselectItems();
1252                 item->setSelected(true);
1253                 setFocusItem(item);
1254                 d->m_selectAnchor = item;
1255             }
1256         }
1257         if (okToSelect) {
1258             SelectedTransactions list(this);
1259             emit transactionsSelected(list);
1260         }
1261     }
1262 }
1263 
1264 void Register::ensureFocusItemVisible()
1265 {
1266     Q_D(Register);
1267     ensureItemVisible(d->m_focusItem);
1268 }
1269 
1270 void Register::ensureItemVisible(RegisterItem* item)
1271 {
1272     Q_D(Register);
1273     if (!item)
1274         return;
1275 
1276     d->m_ensureVisibleItem = item;
1277     QTimer::singleShot(0, this, SLOT(slotEnsureItemVisible()));
1278 }
1279 
1280 void Register::slotDoubleClicked(int row, int)
1281 {
1282     Q_D(Register);
1283     if (row >= 0 && row < d->m_itemIndex.size()) {
1284         RegisterItem* p = d->m_itemIndex[row];
1285         if (p->isSelectable()) {
1286             d->m_ignoreNextButtonRelease = true;
1287             // double click to start editing only works if the focus
1288             // item is among the selected ones
1289             if (!focusItem()) {
1290                 setFocusItem(p);
1291                 if (d->m_selectionMode != NoSelection)
1292                     p->setSelected(true);
1293             }
1294 
1295             if (d->m_focusItem->isSelected()) {
1296                 // don't emit the signal right away but wait until
1297                 // we come back to the Qt main loop
1298                 QTimer::singleShot(0, this, SIGNAL(editTransaction()));
1299             }
1300         }
1301     }
1302 }
1303 
1304 void Register::slotEnsureItemVisible()
1305 {
1306     Q_D(Register);
1307     // if clear() has been called since the timer was
1308     // started, we just ignore the call
1309     if (!d->m_ensureVisibleItem)
1310         return;
1311 
1312     // make sure to catch latest changes
1313     setUpdatesEnabled(false);
1314     updateRegister();
1315     setUpdatesEnabled(true);
1316     // since the item will be made visible at the top of the viewport make the bottom index visible first to make the whole item visible
1317     scrollTo(model()->index(d->m_ensureVisibleItem->startRow() + d->m_ensureVisibleItem->numRowsRegister() - 1, (int)eTransaction::Column::Detail));
1318     scrollTo(model()->index(d->m_ensureVisibleItem->startRow(), (int)eTransaction::Column::Detail));
1319 }
1320 
1321 QString Register::text(int /*row*/, int /*col*/) const
1322 {
1323     return QString("a");
1324 }
1325 
1326 QWidget* Register::createEditor(int /*row*/, int /*col*/, bool /*initFromCell*/) const
1327 {
1328     return 0;
1329 }
1330 
1331 void Register::setCellContentFromEditor(int /*row*/, int /*col*/)
1332 {
1333 }
1334 
1335 void Register::endEdit()
1336 {
1337     Q_D(Register);
1338     d->m_ignoreNextButtonRelease = false;
1339 }
1340 
1341 RegisterItem* Register::focusItem() const
1342 {
1343     Q_D(const Register);
1344     return d->m_focusItem;
1345 }
1346 
1347 RegisterItem* Register::anchorItem() const
1348 {
1349     Q_D(const Register);
1350     return d->m_selectAnchor;
1351 }
1352 
1353 void Register::arrangeEditWidgets(QMap<QString, QWidget*>& editWidgets, KMyMoneyRegister::Transaction* t)
1354 {
1355     t->arrangeWidgetsInRegister(editWidgets);
1356     ensureItemVisible(t);
1357     // updateContents();
1358 }
1359 
1360 void Register::tabOrder(QWidgetList& tabOrderWidgets, KMyMoneyRegister::Transaction* t) const
1361 {
1362     t->tabOrderInRegister(tabOrderWidgets);
1363 }
1364 
1365 void Register::removeEditWidgets(QMap<QString, QWidget*>& editWidgets)
1366 {
1367     // remove pointers from map
1368     QMap<QString, QWidget*>::iterator it;
1369     for (it = editWidgets.begin(); it != editWidgets.end();) {
1370         if ((*it)->parentWidget() == this) {
1371             editWidgets.erase(it);
1372             it = editWidgets.begin();
1373         } else
1374             ++it;
1375     }
1376 
1377     // now delete the widgets
1378     if (auto t = dynamic_cast<KMyMoneyRegister::Transaction*>(focusItem())) {
1379         for (int row = t->startRow(); row < t->startRow() + t->numRowsRegister(true); ++row) {
1380             for (int col = 0; col < columnCount(); ++col) {
1381                 if (cellWidget(row, col)) {
1382                     cellWidget(row, col)->hide();
1383                     setCellWidget(row, col, 0);
1384                 }
1385             }
1386             // make sure to reduce the possibly size to what it was before editing started
1387             setRowHeight(row, t->rowHeightHint());
1388         }
1389     }
1390 }
1391 
1392 RegisterItem* Register::itemById(const QString& id) const
1393 {
1394     Q_D(const Register);
1395     if (id.isEmpty())
1396         return d->m_lastItem;
1397 
1398     for (QVector<RegisterItem*>::size_type i = 0; i < d->m_items.size(); ++i) {
1399         RegisterItem* item = d->m_items[i];
1400         if (!item)
1401             continue;
1402         if (item->id() == id)
1403             return item;
1404     }
1405     return 0;
1406 }
1407 
1408 void Register::handleItemChange(RegisterItem* old, bool shift, bool control)
1409 {
1410     Q_D(Register);
1411     if (d->m_selectionMode == MultiSelection) {
1412         if (shift) {
1413             selectRange(d->m_selectAnchor ? d->m_selectAnchor : old,
1414                         d->m_focusItem, false, true, (d->m_selectAnchor && !control) ? true : false);
1415         } else if (!control) {
1416             selectItem(d->m_focusItem, false);
1417         }
1418     }
1419 }
1420 
1421 void Register::selectRange(RegisterItem* from, RegisterItem* to, bool invert, bool includeFirst, bool clearSel)
1422 {
1423     if (!from || !to)
1424         return;
1425     if (from == to && !includeFirst)
1426         return;
1427     bool swap = false;
1428     if (to == from->prevItem())
1429         swap = true;
1430 
1431     RegisterItem* item;
1432     if (!swap && from != to && from != to->prevItem()) {
1433         bool found = false;
1434         for (item = from; item; item = item->nextItem()) {
1435             if (item == to) {
1436                 found = true;
1437                 break;
1438             }
1439         }
1440         if (!found)
1441             swap = true;
1442     }
1443 
1444     if (swap) {
1445         item = from;
1446         from = to;
1447         to = item;
1448         if (!includeFirst)
1449             to = to->prevItem();
1450 
1451     } else if (!includeFirst) {
1452         from = from->nextItem();
1453     }
1454 
1455     if (clearSel) {
1456         for (item = firstItem(); item; item = item->nextItem()) {
1457             if (item->isSelected() && item->isVisible()) {
1458                 item->setSelected(false);
1459             }
1460         }
1461     }
1462 
1463     for (item = from; item; item = item->nextItem()) {
1464         if (item->isSelectable()) {
1465             if (!invert) {
1466                 if (!item->isSelected() && item->isVisible()) {
1467                     item->setSelected(true);
1468                 }
1469             } else {
1470                 bool sel = !item->isSelected();
1471                 if ((item->isSelected() != sel) && item->isVisible()) {
1472                     item->setSelected(sel);
1473                 }
1474             }
1475         }
1476         if (item == to)
1477             break;
1478     }
1479 }
1480 
1481 void Register::scrollPage(int key, Qt::KeyboardModifiers modifiers)
1482 {
1483     Q_D(Register);
1484     RegisterItem* oldFocusItem = d->m_focusItem;
1485 
1486     // make sure we have a focus item
1487     if (!d->m_focusItem)
1488         setFocusItem(d->m_firstItem);
1489     if (!d->m_focusItem && d->m_firstItem)
1490         setFocusItem(d->m_firstItem->nextItem());
1491     if (!d->m_focusItem)
1492         return;
1493 
1494     RegisterItem* item = d->m_focusItem;
1495     int height = 0;
1496 
1497     bool hitTopOrBottom = false;
1498     switch (key) {
1499     case Qt::Key_PageUp:
1500         while (height < viewport()->height() && item->prevItem() && !hitTopOrBottom) {
1501             do {
1502                 item = item->prevItem();
1503                 if (item->isVisible())
1504                     height += item->rowHeightHint();
1505             } while ((!item->isSelectable() || !item->isVisible()) && item->prevItem());
1506             hitTopOrBottom = (item->prevItem() == nullptr);
1507             while ((!item->isSelectable() || !item->isVisible()) && item->nextItem()) {
1508                 item = item->nextItem();
1509             }
1510         }
1511         break;
1512     case Qt::Key_PageDown:
1513         while (height < viewport()->height() && item->nextItem() && !hitTopOrBottom) {
1514             do {
1515                 if (item->isVisible())
1516                     height += item->rowHeightHint();
1517                 item = item->nextItem();
1518             } while ((!item->isSelectable() || !item->isVisible()) && item->nextItem());
1519             hitTopOrBottom = (item->nextItem() == nullptr);
1520             while ((!item->isSelectable() || !item->isVisible()) && item->prevItem()) {
1521                 item = item->prevItem();
1522             }
1523         }
1524         break;
1525 
1526     case Qt::Key_Up:
1527         if (item->prevItem()) {
1528             do {
1529                 item = item->prevItem();
1530             } while ((!item->isSelectable() || !item->isVisible()) && item->prevItem());
1531         }
1532         break;
1533 
1534     case Qt::Key_Down:
1535         if (item->nextItem()) {
1536             do {
1537                 item = item->nextItem();
1538             } while ((!item->isSelectable() || !item->isVisible()) && item->nextItem());
1539         }
1540         break;
1541 
1542     case Qt::Key_Home:
1543         item = d->m_firstItem;
1544         while ((!item->isSelectable() || !item->isVisible()) && item->nextItem())
1545             item = item->nextItem();
1546         break;
1547 
1548     case Qt::Key_End:
1549         item = d->m_lastItem;
1550         while ((!item->isSelectable() || !item->isVisible()) && item->prevItem())
1551             item = item->prevItem();
1552         break;
1553     }
1554 
1555     // make sure to avoid selecting a possible empty transaction at the end
1556     auto t = dynamic_cast<Transaction*>(item);
1557     if (t && t->transaction().id().isEmpty()) {
1558         if (t->prevItem()) {
1559             item = t->prevItem();
1560         }
1561     }
1562 
1563     if (!(modifiers & Qt::ShiftModifier) || !d->m_selectAnchor)
1564         d->m_selectAnchor = item;
1565 
1566     setFocusItem(item);
1567 
1568     if (item->isSelectable()) {
1569         handleItemChange(oldFocusItem, modifiers & Qt::ShiftModifier, modifiers & Qt::ControlModifier);
1570         // tell the world about the changes in selection
1571         SelectedTransactions list(this);
1572         emit transactionsSelected(list);
1573     }
1574 
1575     if (d->m_focusItem && !d->m_focusItem->isSelected() && d->m_selectionMode == SingleSelection)
1576         selectItem(item);
1577 
1578 }
1579 
1580 void Register::keyPressEvent(QKeyEvent* ev)
1581 {
1582     Q_D(Register);
1583     switch (ev->key()) {
1584     case Qt::Key_Space:
1585         if (d->m_selectionMode != NoSelection) {
1586             // get the state out of the event ...
1587             d->m_modifiers = ev->modifiers();
1588             // ... and pretend that we have pressed the left mouse button ;)
1589             d->m_mouseButton = Qt::LeftButton;
1590             selectItem(d->m_focusItem);
1591         }
1592         break;
1593 
1594     case Qt::Key_PageUp:
1595     case Qt::Key_PageDown:
1596     case Qt::Key_Home:
1597     case Qt::Key_End:
1598     case Qt::Key_Down:
1599     case Qt::Key_Up:
1600         scrollPage(ev->key(), ev->modifiers());
1601         break;
1602     case Qt::Key_Enter:
1603     case Qt::Key_Return:
1604         // don't emit the signal right away but wait until
1605         // we come back to the Qt main loop
1606         QTimer::singleShot(0, this, SIGNAL(editTransaction()));
1607         break;
1608 
1609     default:
1610         QTableWidget::keyPressEvent(ev);
1611         break;
1612     }
1613 }
1614 
1615 Transaction* Register::transactionFactory(Register *parent, const MyMoneyTransaction& transaction, const MyMoneySplit& split, int uniqueId)
1616 {
1617     Transaction* t = 0;
1618     MyMoneySplit s = split;
1619 
1620     if (parent->account() == MyMoneyAccount()) {
1621         t = new KMyMoneyRegister::StdTransaction(parent, transaction, s, uniqueId);
1622         return t;
1623     }
1624 
1625     switch (parent->account().accountType()) {
1626     case Account::Type::Checkings:
1627     case Account::Type::Savings:
1628     case Account::Type::Cash:
1629     case Account::Type::CreditCard:
1630     case Account::Type::Loan:
1631     case Account::Type::Asset:
1632     case Account::Type::Liability:
1633     case Account::Type::Currency:
1634     case Account::Type::Income:
1635     case Account::Type::Expense:
1636     case Account::Type::AssetLoan:
1637     case Account::Type::Equity:
1638         if (s.accountId().isEmpty())
1639             s.setAccountId(parent->account().id());
1640         if (s.isMatched())
1641             t = new KMyMoneyRegister::StdTransactionMatched(parent, transaction, s, uniqueId);
1642         else if (transaction.isImported())
1643             t = new KMyMoneyRegister::StdTransactionDownloaded(parent, transaction, s, uniqueId);
1644         else
1645             t = new KMyMoneyRegister::StdTransaction(parent, transaction, s, uniqueId);
1646         break;
1647 
1648     case Account::Type::Investment:
1649         if (s.isMatched())
1650             t = new KMyMoneyRegister::InvestTransaction/* Matched */(parent, transaction, s, uniqueId);
1651         else if (transaction.isImported())
1652             t = new KMyMoneyRegister::InvestTransactionDownloaded(parent, transaction, s, uniqueId);
1653         else
1654             t = new KMyMoneyRegister::InvestTransaction(parent, transaction, s, uniqueId);
1655         break;
1656 
1657     case Account::Type::CertificateDep:
1658     case Account::Type::MoneyMarket:
1659     case Account::Type::Stock:
1660     default:
1661         qDebug("Register::transactionFactory: invalid accountTypeE %d", (int)parent->account().accountType());
1662         break;
1663     }
1664     return t;
1665 }
1666 
1667 const MyMoneyAccount& Register::account() const
1668 {
1669     Q_D(const Register);
1670     return d->m_account;
1671 }
1672 
1673 void Register::addGroupMarkers()
1674 {
1675     Q_D(Register);
1676     QMap<QString, int> list;
1677     QMap<QString, int>::const_iterator it;
1678     KMyMoneyRegister::RegisterItem* p = firstItem();
1679     KMyMoneyRegister::Transaction* t;
1680     QString name;
1681     QDate today;
1682     QDate yesterday, thisWeek, lastWeek;
1683     QDate thisMonth, lastMonth;
1684     QDate thisYear;
1685     int weekStartOfs;
1686 
1687     switch (primarySortKey()) {
1688     case SortField::PostDate:
1689     case SortField::EntryDate:
1690         today = QDate::currentDate();
1691         thisMonth.setDate(today.year(), today.month(), 1);
1692         lastMonth = thisMonth.addMonths(-1);
1693         yesterday = today.addDays(-1);
1694         // a = QDate::dayOfWeek()         todays weekday (1 = Monday, 7 = Sunday)
1695         // b = QLocale().firstDayOfWeek() first day of week (1 = Monday, 7 = Sunday)
1696         weekStartOfs = today.dayOfWeek() - QLocale().firstDayOfWeek();
1697         if (weekStartOfs < 0) {
1698             weekStartOfs = 7 + weekStartOfs;
1699         }
1700         thisWeek = today.addDays(-weekStartOfs);
1701         lastWeek = thisWeek.addDays(-7);
1702         thisYear.setDate(today.year(), 1, 1);
1703         if (KMyMoneySettings::startDate().date() != QDate(1900, 1, 1))
1704             new KMyMoneyRegister::FancyDateGroupMarker(this, KMyMoneySettings::startDate().date(), i18n("Prior transactions possibly filtered"));
1705 
1706         if (d->m_account.lastReconciliationDate().isValid())
1707             new KMyMoneyRegister::StatementGroupMarker(this, eRegister::CashFlowDirection::Deposit, d->m_account.lastReconciliationDate(), i18n("Last reconciliation"));
1708 
1709         if (KMyMoneySettings::showReconciledBalances()) {
1710             foreach(const QDate &date, d->m_account.reconciliationHistory().keys()) {
1711                 QString txt = i18n("Reconciled Balance: %1", d->m_account.reconciliationHistory()[date].formatMoney(d->m_account.fraction()));
1712                 new KMyMoneyRegister::StatementGroupMarker(this, eRegister::CashFlowDirection::Deposit, date, txt);
1713             }
1714         }
1715 
1716         if (KMyMoneySettings::showFancyMarker()) {
1717             if (!d->m_account.value("lastImportedTransactionDate").isEmpty()
1718                     && !d->m_account.value("lastStatementBalance").isEmpty()) {
1719                 MyMoneyMoney balance(d->m_account.value("lastStatementBalance"));
1720                 if (d->m_account.accountGroup() == Account::Type::Liability)
1721                     balance = -balance;
1722                 auto txt = i18n("Online Statement Balance: %1", balance.formatMoney(d->m_account.fraction()));
1723 
1724                 KMyMoneyRegister::StatementGroupMarker *pGroupMarker = new KMyMoneyRegister::StatementGroupMarker(this, eRegister::CashFlowDirection::Deposit, QDate::fromString(d->m_account.value("lastImportedTransactionDate"), Qt::ISODate), txt);
1725 
1726                 pGroupMarker->setErroneous(!MyMoneyFile::instance()->hasMatchingOnlineBalance(d->m_account));
1727             }
1728 
1729             new KMyMoneyRegister::FancyDateGroupMarker(this, thisYear, i18n("This year"));
1730             new KMyMoneyRegister::FancyDateGroupMarker(this, lastMonth, i18n("Last month"));
1731             new KMyMoneyRegister::FancyDateGroupMarker(this, thisMonth, i18n("This month"));
1732             new KMyMoneyRegister::FancyDateGroupMarker(this, lastWeek, i18n("Last week"));
1733             new KMyMoneyRegister::FancyDateGroupMarker(this, thisWeek, i18n("This week"));
1734             new KMyMoneyRegister::FancyDateGroupMarker(this, yesterday, i18n("Yesterday"));
1735             new KMyMoneyRegister::FancyDateGroupMarker(this, today, i18n("Today"));
1736             new KMyMoneyRegister::FancyDateGroupMarker(this, today.addDays(1), i18n("Future transactions"));
1737             new KMyMoneyRegister::FancyDateGroupMarker(this, thisWeek.addDays(7), i18n("Next week"));
1738             new KMyMoneyRegister::FancyDateGroupMarker(this, thisMonth.addMonths(1), i18n("Next month"));
1739 
1740         } else {
1741             new KMyMoneyRegister::SimpleDateGroupMarker(this, today.addDays(1), i18n("Future transactions"));
1742         }
1743         if (KMyMoneySettings::showFiscalMarker()) {
1744             QDate currentFiscalYear = KMyMoneySettings::firstFiscalDate();
1745             new KMyMoneyRegister::FiscalYearGroupMarker(this, currentFiscalYear, i18n("Current fiscal year"));
1746             new KMyMoneyRegister::FiscalYearGroupMarker(this, currentFiscalYear.addYears(-1), i18n("Previous fiscal year"));
1747             new KMyMoneyRegister::FiscalYearGroupMarker(this, currentFiscalYear.addYears(1), i18n("Next fiscal year"));
1748         }
1749         break;
1750 
1751     case SortField::Type:
1752         if (KMyMoneySettings::showFancyMarker()) {
1753             new KMyMoneyRegister::TypeGroupMarker(this, eRegister::CashFlowDirection::Deposit, d->m_account.accountType());
1754             new KMyMoneyRegister::TypeGroupMarker(this, eRegister::CashFlowDirection::Payment, d->m_account.accountType());
1755         }
1756         break;
1757 
1758     case SortField::ReconcileState:
1759         if (KMyMoneySettings::showFancyMarker()) {
1760             new KMyMoneyRegister::ReconcileGroupMarker(this, eMyMoney::Split::State::NotReconciled);
1761             new KMyMoneyRegister::ReconcileGroupMarker(this, eMyMoney::Split::State::Cleared);
1762             new KMyMoneyRegister::ReconcileGroupMarker(this, eMyMoney::Split::State::Reconciled);
1763             new KMyMoneyRegister::ReconcileGroupMarker(this, eMyMoney::Split::State::Frozen);
1764         }
1765         break;
1766 
1767     case SortField::Payee:
1768         if (KMyMoneySettings::showFancyMarker()) {
1769             while (p) {
1770                 if ((t = dynamic_cast<KMyMoneyRegister::Transaction*>(p)))
1771                     list[t->sortPayee()] = 1;
1772                 p = p->nextItem();
1773             }
1774             for (it = list.constBegin(); it != list.constEnd(); ++it) {
1775                 name = it.key();
1776                 if (name.isEmpty()) {
1777                     name = i18nc("Unknown payee", "Unknown");
1778                 }
1779                 new KMyMoneyRegister::PayeeGroupMarker(this, name);
1780             }
1781         }
1782         break;
1783 
1784     case SortField::Category:
1785         if (KMyMoneySettings::showFancyMarker()) {
1786             while (p) {
1787                 if ((t = dynamic_cast<KMyMoneyRegister::Transaction*>(p)))
1788                     list[t->sortCategory()] = 1;
1789                 p = p->nextItem();
1790             }
1791             for (it = list.constBegin(); it != list.constEnd(); ++it) {
1792                 name = it.key();
1793                 if (name.isEmpty()) {
1794                     name = i18nc("Unknown category", "Unknown");
1795                 }
1796                 new KMyMoneyRegister::CategoryGroupMarker(this, name);
1797             }
1798         }
1799         break;
1800 
1801     case SortField::Security:
1802         if (KMyMoneySettings::showFancyMarker()) {
1803             while (p) {
1804                 if ((t = dynamic_cast<KMyMoneyRegister::InvestTransaction*>(p)))
1805                     list[t->sortSecurity()] = 1;
1806                 p = p->nextItem();
1807             }
1808             for (it = list.constBegin(); it != list.constEnd(); ++it) {
1809                 name = it.key();
1810                 if (name.isEmpty()) {
1811                     name = i18nc("Unknown security", "Unknown");
1812                 }
1813                 new KMyMoneyRegister::CategoryGroupMarker(this, name);
1814             }
1815         }
1816         break;
1817 
1818     default: // no markers supported
1819         break;
1820     }
1821 }
1822 
1823 void Register::removeUnwantedGroupMarkers()
1824 {
1825     // remove all trailing group markers except statement markers
1826     KMyMoneyRegister::RegisterItem* q;
1827     KMyMoneyRegister::RegisterItem* p = lastItem();
1828     while (p) {
1829         q = p;
1830         if (dynamic_cast<KMyMoneyRegister::Transaction*>(p)
1831                 || dynamic_cast<KMyMoneyRegister::StatementGroupMarker*>(p))
1832             break;
1833 
1834         p = p->prevItem();
1835         delete q;
1836     }
1837 
1838     // remove all adjacent group markers
1839     bool lastWasGroupMarker = false;
1840     p = lastItem();
1841     while (p) {
1842         q = p;
1843         auto m = dynamic_cast<KMyMoneyRegister::GroupMarker*>(p);
1844         p = p->prevItem();
1845         if (m) {
1846             m->markVisible(true);
1847             // make adjacent group marker invisible except those that show statement information
1848             if (lastWasGroupMarker && (dynamic_cast<KMyMoneyRegister::StatementGroupMarker*>(m) == 0)) {
1849                 m->markVisible(false);
1850             }
1851             lastWasGroupMarker = true;
1852         } else if (q->isVisible())
1853             lastWasGroupMarker = false;
1854     }
1855 }
1856 
1857 void Register::setLedgerLensForced(bool forced)
1858 {
1859     Q_D(Register);
1860     d->m_ledgerLensForced = forced;
1861 }
1862 
1863 bool Register::ledgerLens() const
1864 {
1865     Q_D(const Register);
1866     return d->m_ledgerLensForced;
1867 }
1868 
1869 void Register::setSelectionMode(SelectionMode mode)
1870 {
1871     Q_D(Register);
1872     d->m_selectionMode = mode;
1873 }
1874 
1875 void Register::setUsedWithEditor(bool value)
1876 {
1877     Q_D(Register);
1878     d->m_usedWithEditor = value;
1879 }
1880 
1881 eRegister::DetailColumn Register::getDetailsColumnType() const
1882 {
1883     Q_D(const Register);
1884     return d->m_detailsColumnType;
1885 }
1886 
1887 void Register::setDetailsColumnType(eRegister::DetailColumn detailsColumnType)
1888 {
1889     Q_D(Register);
1890     d->m_detailsColumnType = detailsColumnType;
1891 }
1892 }