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 }