diff --git a/kmymoney/dialogs/keditscheduledlg.cpp b/kmymoney/dialogs/keditscheduledlg.cpp index 984351b26..d62180e21 100644 --- a/kmymoney/dialogs/keditscheduledlg.cpp +++ b/kmymoney/dialogs/keditscheduledlg.cpp @@ -1,781 +1,781 @@ /* * Copyright 2007-2018 Thomas Baumgart * Copyright 2017-2018 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "keditscheduledlg.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_keditscheduledlg.h" #include "tabbar.h" #include "mymoneyexception.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneymoney.h" #include "mymoneyschedule.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "register.h" #include "transactionform.h" #include "transaction.h" #include "selectedtransactions.h" #include "transactioneditor.h" #include "kmymoneylineedit.h" #include "kmymoneydateinput.h" #include "kmymoneymvccombo.h" #include "kguiutils.h" #include "kmymoneyutils.h" #include "knewaccountdlg.h" #include "knewinvestmentwizard.h" #include "keditloanwizard.h" #include "kmymoneysettings.h" #include "mymoneyenums.h" #include "widgetenums.h" using namespace eMyMoney; class KEditScheduleDlgPrivate { Q_DISABLE_COPY(KEditScheduleDlgPrivate) Q_DECLARE_PUBLIC(KEditScheduleDlg) public: explicit KEditScheduleDlgPrivate(KEditScheduleDlg *qq) : q_ptr(qq), ui(new Ui::KEditScheduleDlg), m_item(nullptr), m_editor(nullptr), m_requiredFields(nullptr) { } ~KEditScheduleDlgPrivate() { delete ui; } void init() { Q_Q(KEditScheduleDlg); ui->setupUi(q); m_requiredFields = new KMandatoryFieldGroup(q); m_requiredFields->setOkButton(ui->buttonBox->button(QDialogButtonBox::Ok)); // button to be enabled when all fields present // make sure, we have a tabbar with the form // insert it after the horizontal line ui->m_paymentInformationLayout->insertWidget(2, ui->m_form->getTabBar(ui->m_form->parentWidget())); // we never need to see the register ui->m_register->hide(); // ... setup the form ... ui->m_form->setupForm(m_schedule.account()); // ... and the register ... ui->m_register->clear(); // ... now add the transaction to register and form ... auto t = transaction(); if (m_schedule.transaction().splits().isEmpty()) m_item = KMyMoneyRegister::Register::transactionFactory(ui->m_register, t, MyMoneySplit(), 0); else m_item = KMyMoneyRegister::Register::transactionFactory(ui->m_register, t, m_schedule.transaction().splits().isEmpty() ? MyMoneySplit() : m_schedule.transaction().splits().front(), 0); ui->m_register->selectItem(m_item); // show the account row m_item->setShowRowInForm(0, true); ui->m_form->slotSetTransaction(m_item); // setup widget contents ui->m_nameEdit->setText(m_schedule.name()); ui->m_frequencyEdit->setCurrentItem((int)m_schedule.occurrence()); if (ui->m_frequencyEdit->currentItem() == Schedule::Occurrence::Any) ui->m_frequencyEdit->setCurrentItem((int)Schedule::Occurrence::Monthly); q->slotFrequencyChanged((int)ui->m_frequencyEdit->currentItem()); ui->m_frequencyNoEdit->setValue(m_schedule.occurrenceMultiplier()); // load option widgets ui->m_paymentMethodEdit->insertItem(i18n("Direct deposit"), (int)Schedule::PaymentType::DirectDeposit); ui->m_paymentMethodEdit->insertItem(i18n("Manual deposit"), (int)Schedule::PaymentType::ManualDeposit); ui->m_paymentMethodEdit->insertItem(i18n("Direct debit"), (int)Schedule::PaymentType::DirectDebit); ui->m_paymentMethodEdit->insertItem(i18n("Standing order"), (int)Schedule::PaymentType::StandingOrder); ui->m_paymentMethodEdit->insertItem(i18n("Bank transfer"), (int)Schedule::PaymentType::BankTransfer); ui->m_paymentMethodEdit->insertItem(i18n("Write check"), (int)Schedule::PaymentType::WriteChecque); ui->m_paymentMethodEdit->insertItem(i18nc("Other payment method", "Other"), (int)Schedule::PaymentType::Other); auto method = m_schedule.paymentType(); if (method == Schedule::PaymentType::Any) method = Schedule::PaymentType::Other; ui->m_paymentMethodEdit->setCurrentItem((int)method); switch (m_schedule.weekendOption()) { case Schedule::WeekendOption::MoveNothing: ui->m_weekendOptionEdit->setCurrentIndex(0); break; case Schedule::WeekendOption::MoveBefore: ui->m_weekendOptionEdit->setCurrentIndex(1); break; case Schedule::WeekendOption::MoveAfter: ui->m_weekendOptionEdit->setCurrentIndex(2); break; } ui->m_estimateEdit->setChecked(!m_schedule.isFixed()); ui->m_lastDayInMonthEdit->setChecked(m_schedule.lastDayInMonth()); ui->m_autoEnterEdit->setChecked(m_schedule.autoEnter()); ui->m_endSeriesEdit->setChecked(m_schedule.willEnd()); ui->m_endOptionsFrame->setEnabled(m_schedule.willEnd()); if (m_schedule.willEnd()) { ui->m_RemainingEdit->setValue(m_schedule.transactionsRemaining()); ui->m_FinalPaymentEdit->setDate(m_schedule.endDate()); } q->connect(ui->m_RemainingEdit, static_cast(&QSpinBox::valueChanged), q, &KEditScheduleDlg::slotRemainingChanged); q->connect(ui->m_FinalPaymentEdit, &KMyMoneyDateInput::dateChanged, q, &KEditScheduleDlg::slotEndDateChanged); q->connect(ui->m_frequencyEdit, &KMyMoneyGeneralCombo::itemSelected, q, &KEditScheduleDlg::slotFrequencyChanged); q->connect(ui->m_frequencyNoEdit, static_cast(&QSpinBox::valueChanged), q, &KEditScheduleDlg::slotOccurrenceMultiplierChanged); q->connect(ui->buttonBox, &QDialogButtonBox::helpRequested, q, &KEditScheduleDlg::slotShowHelp); q->setModal(true); // force the initial height to be as small as possible QTimer::singleShot(0, q, SLOT(slotSetupSize())); // we just hide the variation field for now and enable the logic // once we have a respective member in the MyMoneySchedule object ui->m_variation->hide(); } /** * Helper method to recalculate and update Transactions Remaining * when other values are changed */ void updateTransactionsRemaining() { auto remain = m_schedule.transactionsRemaining(); if (remain != ui->m_RemainingEdit->value()) { ui->m_RemainingEdit->blockSignals(true); ui->m_RemainingEdit->setValue(remain); ui->m_RemainingEdit->blockSignals(false); } } MyMoneyTransaction transaction() const { auto t = m_schedule.transaction(); if (m_editor) { m_editor->createTransaction(t, m_schedule.transaction(), m_schedule.transaction().splits().isEmpty() ? MyMoneySplit() : m_schedule.transaction().splits().front(), false); } t.clearId(); t.setEntryDate(QDate()); return t; } KEditScheduleDlg *q_ptr; Ui::KEditScheduleDlg *ui; MyMoneySchedule m_schedule; KMyMoneyRegister::Transaction* m_item; QWidgetList m_tabOrderWidgets; TransactionEditor* m_editor; KMandatoryFieldGroup* m_requiredFields; }; KEditScheduleDlg::KEditScheduleDlg(const MyMoneySchedule& schedule, QWidget *parent) : QDialog(parent), d_ptr(new KEditScheduleDlgPrivate(this)) { Q_D(KEditScheduleDlg); d->m_schedule = schedule; d->m_editor = 0; d->init(); } KEditScheduleDlg::~KEditScheduleDlg() { Q_D(KEditScheduleDlg); delete d; } void KEditScheduleDlg::slotSetupSize() { resize(width(), minimumSizeHint().height()); } TransactionEditor* KEditScheduleDlg::startEdit() { Q_D(KEditScheduleDlg); KMyMoneyRegister::SelectedTransactions list(d->ui->m_register); TransactionEditor* editor = d->m_item->createEditor(d->ui->m_form, list, QDate()); // check that we use the same transaction commodity in all selected transactions // if not, we need to update this in the editor's list. The user can also bail out // of this operation which means that we have to stop editing here. if (editor && !d->m_schedule.account().id().isEmpty()) { if (!editor->fixTransactionCommodity(d->m_schedule.account())) { // if the user wants to quit, we need to destroy the editor // and bail out delete editor; editor = 0; } } if (editor) { editor->setScheduleInfo(d->ui->m_nameEdit->text()); connect(editor, &TransactionEditor::transactionDataSufficient, d->ui->buttonBox->button(QDialogButtonBox::Ok), &QWidget::setEnabled); connect(editor, &TransactionEditor::escapePressed, d->ui->buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::animateClick); connect(editor, &TransactionEditor::returnPressed, d->ui->buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::animateClick); connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, editor, &TransactionEditor::slotReloadEditWidgets); // connect(editor, SIGNAL(finishEdit(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotLeaveEditMode(KMyMoneyRegister::SelectedTransactions))); connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, editor, &TransactionEditor::slotReloadEditWidgets); // create the widgets, place them in the parent and load them with data // setup tab order d->m_tabOrderWidgets.clear(); eWidgets::eRegister::Action action = eWidgets::eRegister::Action::Withdrawal; switch (d->m_schedule.type()) { case Schedule::Type::Deposit: action = eWidgets::eRegister::Action::Deposit; break; case Schedule::Type::Bill: action = eWidgets::eRegister::Action::Withdrawal; editor->setPaymentMethod(d->m_schedule.paymentType()); break; case Schedule::Type::Transfer: action = eWidgets::eRegister::Action::Transfer; break; default: // if we end up here, we don't have a known schedule type (yet). in this case, we just glimpse // into the transaction and determine the type. in case we don't have a transaction with splits // we stick with the default action already set up if (d->m_schedule.transaction().splits().count() > 0) { auto isDeposit = false; auto isTransfer = false; auto splits = d->m_schedule.transaction().splits(); foreach (const auto split, splits) { if (split.accountId() == d->m_schedule.account().id()) { isDeposit = !(split.shares().isNegative()); } else { auto acc = MyMoneyFile::instance()->account(split.accountId()); if (acc.isAssetLiability() && d->m_schedule.transaction().splits().count() == 2) { isTransfer = true; } } } if (isTransfer) action = eWidgets::eRegister::Action::Transfer; else if (isDeposit) action = eWidgets::eRegister::Action::Deposit; } break; } editor->setup(d->m_tabOrderWidgets, d->m_schedule.account(), action); // if it's not a check, then we need to clear // a possibly assigned check number if (d->m_schedule.paymentType() != Schedule::PaymentType::WriteChecque) { QWidget* w = editor->haveWidget("number"); if (w) { if (auto numberWidget = dynamic_cast(w)) { numberWidget->loadText(QString()); } } } Q_ASSERT(!d->m_tabOrderWidgets.isEmpty()); d->m_tabOrderWidgets.push_front(d->ui->m_paymentMethodEdit); // editor->setup() leaves the tabbar as the last widget in the stack, but we // need it as first here. So we move it around. QWidget* w = editor->haveWidget("tabbar"); if (w) { int idx = d->m_tabOrderWidgets.indexOf(w); if (idx != -1) { d->m_tabOrderWidgets.removeAt(idx); d->m_tabOrderWidgets.push_front(w); } } // don't forget our three buttons and additional widgets // make sure to use the correct order d->m_tabOrderWidgets.push_front(d->ui->m_frequencyEdit); d->m_tabOrderWidgets.push_front(d->ui->m_frequencyNoEdit); d->m_tabOrderWidgets.push_front(d->ui->m_nameEdit); d->m_tabOrderWidgets.append(d->ui->m_weekendOptionEdit); d->m_tabOrderWidgets.append(d->ui->m_estimateEdit); d->m_tabOrderWidgets.append(d->ui->m_variation); d->m_tabOrderWidgets.append(d->ui->m_lastDayInMonthEdit); d->m_tabOrderWidgets.append(d->ui->m_autoEnterEdit); d->m_tabOrderWidgets.append(d->ui->m_endSeriesEdit); d->m_tabOrderWidgets.append(d->ui->m_RemainingEdit); d->m_tabOrderWidgets.append(d->ui->m_FinalPaymentEdit); d->m_tabOrderWidgets.append(d->ui->buttonBox->button(QDialogButtonBox::Ok)); d->m_tabOrderWidgets.append(d->ui->buttonBox->button(QDialogButtonBox::Cancel)); d->m_tabOrderWidgets.append(d->ui->buttonBox->button(QDialogButtonBox::Help)); for (auto i = 0; i < d->m_tabOrderWidgets.size(); ++i) { w = d->m_tabOrderWidgets.at(i); if (w) { w->installEventFilter(this); w->installEventFilter(editor); } } // connect the postdate modification signal to our update routine if (auto dateEdit = dynamic_cast(editor->haveWidget("postdate"))) connect(dateEdit, &KMyMoneyDateInput::dateChanged, this, &KEditScheduleDlg::slotPostDateChanged); d->ui->m_nameEdit->setFocus(); // add the required fields to the mandatory group d->m_requiredFields->add(d->ui->m_nameEdit); d->m_requiredFields->add(editor->haveWidget("account")); d->m_requiredFields->add(editor->haveWidget("category")); // fix labels if (auto label = dynamic_cast(editor->haveWidget("date-label"))) label->setText(i18n("Next due date")); d->m_editor = editor; slotSetPaymentMethod((int)d->m_schedule.paymentType()); connect(d->ui->m_paymentMethodEdit, &KMyMoneyGeneralCombo::itemSelected, this, &KEditScheduleDlg::slotSetPaymentMethod); connect(editor, &TransactionEditor::operationTypeChanged, this, &KEditScheduleDlg::slotFilterPaymentType); } return editor; } void KEditScheduleDlg::accept() { Q_D(KEditScheduleDlg); // Force the focus to be on the OK button. This will trigger creation // of any unknown objects (payees, categories etc.) d->ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus(); // only accept if the button is really still enabled. We could end // up here, if the user filled all fields, the focus is on the category // field, but the category is not yet existent. When the user presses the // OK button in this context, he will be asked if he wants to create // the category or not. In case he decides no, we end up here with no // category filled in, so we don't run through the final acceptance. if (d->ui->buttonBox->button(QDialogButtonBox::Ok)->isEnabled()) QDialog::accept(); } const MyMoneySchedule& KEditScheduleDlg::schedule() { Q_D(KEditScheduleDlg); if (d->m_editor) { auto t = d->transaction(); if (d->m_schedule.nextDueDate() != t.postDate()) { d->m_schedule.setNextDueDate(t.postDate()); d->m_schedule.setStartDate(t.postDate()); } d->m_schedule.setTransaction(t); d->m_schedule.setName(d->ui->m_nameEdit->text()); d->m_schedule.setFixed(!d->ui->m_estimateEdit->isChecked()); d->m_schedule.setOccurrencePeriod(static_cast(d->ui->m_frequencyEdit->currentItem())); d->m_schedule.setOccurrenceMultiplier(d->ui->m_frequencyNoEdit->value()); switch (d->ui->m_weekendOptionEdit->currentIndex()) { case 0: d->m_schedule.setWeekendOption(Schedule::WeekendOption::MoveNothing); break; case 1: d->m_schedule.setWeekendOption(Schedule::WeekendOption::MoveBefore); break; case 2: d->m_schedule.setWeekendOption(Schedule::WeekendOption::MoveAfter); break; } d->m_schedule.setType(Schedule::Type::Bill); if (auto tabbar = dynamic_cast(d->m_editor->haveWidget("tabbar"))) { switch (static_cast(tabbar->currentIndex())) { case eWidgets::eRegister::Action::Deposit: d->m_schedule.setType(Schedule::Type::Deposit); break; default: case eWidgets::eRegister::Action::Withdrawal: d->m_schedule.setType(Schedule::Type::Bill); break; case eWidgets::eRegister::Action::Transfer: d->m_schedule.setType(Schedule::Type::Transfer); break; } } else { qDebug("No tabbar found in KEditScheduleDlg::schedule(). Defaulting type to BILL"); } if(d->ui->m_lastDayInMonthEdit->isEnabled()) d->m_schedule.setLastDayInMonth(d->ui->m_lastDayInMonthEdit->isChecked()); else d->m_schedule.setLastDayInMonth(false); d->m_schedule.setAutoEnter(d->ui->m_autoEnterEdit->isChecked()); d->m_schedule.setPaymentType(static_cast(d->ui->m_paymentMethodEdit->currentItem())); if (d->ui->m_endSeriesEdit->isEnabled() && d->ui->m_endSeriesEdit->isChecked()) { d->m_schedule.setEndDate(d->ui->m_FinalPaymentEdit->date()); } else { d->m_schedule.setEndDate(QDate()); } } return d->m_schedule; } void KEditScheduleDlg::newSchedule(const MyMoneyTransaction& _t, eMyMoney::Schedule::Occurrence occurrence) { MyMoneySchedule schedule; schedule.setOccurrence(occurrence); // if the schedule is based on an existing transaction, // we take the post date and project it to the next // schedule in a month. if (_t != MyMoneyTransaction()) { MyMoneyTransaction t(_t); schedule.setTransaction(t); if (occurrence != eMyMoney::Schedule::Occurrence::Once) schedule.setNextDueDate(schedule.nextPayment(t.postDate())); } bool committed; do { committed = true; QPointer dlg = new KEditScheduleDlg(schedule, nullptr); QPointer transactionEditor = dlg->startEdit(); KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); if (dlg->exec() == QDialog::Accepted && dlg != 0) { MyMoneyFileTransaction ft; try { schedule = dlg->schedule(); MyMoneyFile::instance()->addSchedule(schedule); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(nullptr, i18n("Unable to add scheduled transaction: %1", QString::fromLatin1(e.what())), i18n("Add scheduled transaction")); committed = false; } } delete transactionEditor; delete dlg; } while(!committed); } void KEditScheduleDlg::editSchedule(const MyMoneySchedule& inputSchedule) { try { auto schedule = MyMoneyFile::instance()->schedule(inputSchedule.id()); KEditScheduleDlg* sched_dlg = nullptr; KEditLoanWizard* loan_wiz = nullptr; switch (schedule.type()) { case eMyMoney::Schedule::Type::Bill: case eMyMoney::Schedule::Type::Deposit: case eMyMoney::Schedule::Type::Transfer: { sched_dlg = new KEditScheduleDlg(schedule, nullptr); QPointer transactionEditor = sched_dlg->startEdit(); if (transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(sched_dlg, !KMyMoneySettings::stringMatchFromStart()); if (sched_dlg->exec() == QDialog::Accepted) { MyMoneyFileTransaction ft; try { MyMoneySchedule sched = sched_dlg->schedule(); // Check whether the new Schedule Date // is at or before the lastPaymentDate // If it is, ask the user whether to clear the // lastPaymentDate const auto& next = sched.nextDueDate(); const auto& last = sched.lastPayment(); if (next.isValid() && last.isValid() && next <= last) { // Entered a date effectively no later // than previous payment. Date would be // updated automatically so we probably // want to clear it. Let's ask the user. if (KMessageBox::questionYesNo(nullptr, i18n("You have entered a scheduled transaction date of %1. Because the scheduled transaction was last paid on %2, KMyMoney will automatically adjust the scheduled transaction date to the next date unless the last payment date is reset. Do you want to reset the last payment date?", QLocale().toString(next, QLocale::ShortFormat), QLocale().toString(last, QLocale::ShortFormat)), i18n("Reset Last Payment Date"), KStandardGuiItem::yes(), KStandardGuiItem::no()) == KMessageBox::Yes) { sched.setLastPayment(QDate()); } } MyMoneyFile::instance()->modifySchedule(sched); // delete the editor before we emit the dataChanged() signal from the // engine. Calling this twice in a row does not hurt. delete transactionEditor; ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(nullptr, i18n("Unable to modify scheduled transaction '%1'", inputSchedule.name()), QString::fromLatin1(e.what())); } } delete transactionEditor; } delete sched_dlg; break; } case eMyMoney::Schedule::Type::LoanPayment: { loan_wiz = new KEditLoanWizard(schedule.account(2)); if (loan_wiz->exec() == QDialog::Accepted) { MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifySchedule(loan_wiz->schedule()); MyMoneyFile::instance()->modifyAccount(loan_wiz->account()); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(nullptr, i18n("Unable to modify scheduled transaction '%1'", inputSchedule.name()), QString::fromLatin1(e.what())); } } delete loan_wiz; break; } case eMyMoney::Schedule::Type::Any: break; } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(nullptr, i18n("Unable to modify scheduled transaction '%1'", inputSchedule.name()), QString::fromLatin1(e.what())); } } bool KEditScheduleDlg::focusNextPrevChild(bool next) { Q_D(KEditScheduleDlg); auto rc = false; auto w = qApp->focusWidget(); auto currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w); while (w && currentWidgetIndex == -1) { // qDebug("'%s' not in list, use parent", qPrintable(w->objectName())); w = w->parentWidget(); currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w); } if (currentWidgetIndex != -1) { do { // if(w) qDebug("tab order is at '%s (%d/%d)'", qPrintable(w->objectName()), currentWidgetIndex, d->m_tabOrderWidgets.size()); currentWidgetIndex += next ? 1 : -1; if (currentWidgetIndex < 0) currentWidgetIndex = d->m_tabOrderWidgets.size() - 1; else if (currentWidgetIndex >= d->m_tabOrderWidgets.size()) currentWidgetIndex = 0; w = d->m_tabOrderWidgets[currentWidgetIndex]; // qDebug("currentWidgetIndex = %d, w = %p", currentWidgetIndex, w); if (((w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && w->isVisible() && w->isEnabled()) { // qDebug("Selecting '%s' as focus", qPrintable(w->objectName())); - w->setFocus(); + w->setFocus(next ? Qt::TabFocusReason: Qt::BacktabFocusReason); rc = true; } } while (rc == false); } return rc; } void KEditScheduleDlg::resizeEvent(QResizeEvent* ev) { Q_D(KEditScheduleDlg); d->ui->m_register->resize((int)eWidgets::eTransaction::Column::Detail); d->ui->m_form->resize((int)eWidgets::eTransactionForm::Column::Value1); QDialog::resizeEvent(ev); } void KEditScheduleDlg::slotRemainingChanged(int value) { Q_D(KEditScheduleDlg); // Make sure the required fields are set if (auto dateEdit = dynamic_cast(d->m_editor->haveWidget("postdate"))) d->m_schedule.setNextDueDate(dateEdit->date()); d->m_schedule.setOccurrencePeriod(static_cast(d->ui->m_frequencyEdit->currentItem())); d->m_schedule.setOccurrenceMultiplier(d->ui->m_frequencyNoEdit->value()); if (d->m_schedule.transactionsRemaining() != value) { d->ui->m_FinalPaymentEdit->blockSignals(true); d->ui->m_FinalPaymentEdit->setDate(d->m_schedule.dateAfter(value)); d->ui->m_FinalPaymentEdit->blockSignals(false); } } void KEditScheduleDlg::slotEndDateChanged(const QDate& date) { Q_D(KEditScheduleDlg); // Make sure the required fields are set if (auto dateEdit = dynamic_cast(d->m_editor->haveWidget("postdate"))) d->m_schedule.setNextDueDate(dateEdit->date()); d->m_schedule.setOccurrencePeriod(static_cast(d->ui->m_frequencyEdit->currentItem())); d->m_schedule.setOccurrenceMultiplier(d->ui->m_frequencyNoEdit->value()); if (d->m_schedule.endDate() != date) { d->m_schedule.setEndDate(date); d->updateTransactionsRemaining(); } } void KEditScheduleDlg::slotPostDateChanged(const QDate& date) { Q_D(KEditScheduleDlg); if (d->m_schedule.nextDueDate() != date) { if (d->ui->m_endOptionsFrame->isEnabled()) { d->m_schedule.setNextDueDate(date); d->m_schedule.setOccurrenceMultiplier(d->ui->m_frequencyNoEdit->value()); d->m_schedule.setOccurrencePeriod(static_cast(d->ui->m_frequencyEdit->currentItem())); d->m_schedule.setEndDate(d->ui->m_FinalPaymentEdit->date()); d->updateTransactionsRemaining(); } } } void KEditScheduleDlg::slotSetPaymentMethod(int item) { Q_D(KEditScheduleDlg); const bool isWriteCheck = item == (int)Schedule::PaymentType::WriteChecque; if (auto numberEdit = dynamic_cast(d->m_editor->haveWidget("number"))) { numberEdit->setVisible(isWriteCheck); // hiding the label does not work, because the label underneath will shine // through. So we either write the label or a blank if (auto label = dynamic_cast(d->m_editor->haveWidget("number-label"))) label->setText(isWriteCheck ? i18n("Number") : QStringLiteral(" ")); } } void KEditScheduleDlg::slotFrequencyChanged(int item) { Q_D(KEditScheduleDlg); d->ui->m_endSeriesEdit->setEnabled(item != (int)Schedule::Occurrence::Once); bool isEndSeries = d->ui->m_endSeriesEdit->isChecked(); if (isEndSeries) d->ui->m_endOptionsFrame->setEnabled(item != (int)Schedule::Occurrence::Once); switch (item) { case (int)Schedule::Occurrence::Daily: case (int)Schedule::Occurrence::Weekly: d->ui->m_frequencyNoEdit->setEnabled(true); d->ui->m_lastDayInMonthEdit->setEnabled(false); break; case (int)Schedule::Occurrence::EveryHalfMonth: case (int)Schedule::Occurrence::Monthly: case (int)Schedule::Occurrence::Yearly: // Supports Frequency Number d->ui->m_frequencyNoEdit->setEnabled(true); d->ui->m_lastDayInMonthEdit->setEnabled(true); break; default: // Multiplier is always 1 d->ui->m_frequencyNoEdit->setEnabled(false); d->ui->m_frequencyNoEdit->setValue(1); d->ui->m_lastDayInMonthEdit->setEnabled(true); break; } if (isEndSeries && (item != (int)Schedule::Occurrence::Once)) { // Changing the frequency changes the number // of remaining transactions if (auto dateEdit = dynamic_cast(d->m_editor->haveWidget("postdate"))) d->m_schedule.setNextDueDate(dateEdit->date()); d->m_schedule.setOccurrenceMultiplier(d->ui->m_frequencyNoEdit->value()); d->m_schedule.setOccurrencePeriod(static_cast(item)); d->m_schedule.setEndDate(d->ui->m_FinalPaymentEdit->date()); d->updateTransactionsRemaining(); } } void KEditScheduleDlg::slotOccurrenceMultiplierChanged(int multiplier) { Q_D(KEditScheduleDlg); // Make sure the required fields are set auto oldOccurrenceMultiplier = d->m_schedule.occurrenceMultiplier(); if (multiplier != oldOccurrenceMultiplier) { if (d->ui->m_endOptionsFrame->isEnabled()) { if (auto dateEdit = dynamic_cast(d->m_editor->haveWidget("postdate"))) d->m_schedule.setNextDueDate(dateEdit->date()); d->m_schedule.setOccurrenceMultiplier(multiplier); d->m_schedule.setOccurrencePeriod(static_cast(d->ui->m_frequencyEdit->currentItem())); d->m_schedule.setEndDate(d->ui->m_FinalPaymentEdit->date()); d->updateTransactionsRemaining(); } } } void KEditScheduleDlg::slotShowHelp() { KHelpClient::invokeHelp("details.schedules.intro"); } void KEditScheduleDlg::slotFilterPaymentType(int index) { Q_D(KEditScheduleDlg); //save selected item to reload if possible auto selectedId = d->ui->m_paymentMethodEdit->itemData(d->ui->m_paymentMethodEdit->currentIndex(), Qt::UserRole).toInt(); //clear and reload the widget with the correct items d->ui->m_paymentMethodEdit->clear(); // load option widgets eWidgets::eRegister::Action action = static_cast(index); if (action != eWidgets::eRegister::Action::Withdrawal) { d->ui->m_paymentMethodEdit->insertItem(i18n("Direct deposit"), (int)Schedule::PaymentType::DirectDeposit); d->ui->m_paymentMethodEdit->insertItem(i18n("Manual deposit"), (int)Schedule::PaymentType::ManualDeposit); } if (action != eWidgets::eRegister::Action::Deposit) { d->ui->m_paymentMethodEdit->insertItem(i18n("Direct debit"), (int)Schedule::PaymentType::DirectDebit); d->ui->m_paymentMethodEdit->insertItem(i18n("Write check"), (int)Schedule::PaymentType::WriteChecque); } d->ui->m_paymentMethodEdit->insertItem(i18n("Standing order"), (int)Schedule::PaymentType::StandingOrder); d->ui->m_paymentMethodEdit->insertItem(i18n("Bank transfer"), (int)Schedule::PaymentType::BankTransfer); d->ui->m_paymentMethodEdit->insertItem(i18nc("Other payment method", "Other"), (int)Schedule::PaymentType::Other); auto newIndex = d->ui->m_paymentMethodEdit->findData(QVariant(selectedId), Qt::UserRole, Qt::MatchExactly); if (newIndex > -1) { d->ui->m_paymentMethodEdit->setCurrentIndex(newIndex); } else { d->ui->m_paymentMethodEdit->setCurrentIndex(0); } } diff --git a/kmymoney/dialogs/kenterscheduledlg.cpp b/kmymoney/dialogs/kenterscheduledlg.cpp index d76059269..10e3a5aff 100644 --- a/kmymoney/dialogs/kenterscheduledlg.cpp +++ b/kmymoney/dialogs/kenterscheduledlg.cpp @@ -1,407 +1,407 @@ /* * Copyright 2007-2012 Thomas Baumgart * Copyright 2017-2018 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kenterscheduledlg.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_kenterscheduledlg.h" #include "tabbar.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneymoney.h" #include "mymoneyschedule.h" #include "register.h" #include "transactionform.h" #include "transaction.h" #include "selectedtransactions.h" #include "transactioneditor.h" #include "kmymoneyutils.h" #include "kmymoneylineedit.h" #include "kmymoneydateinput.h" #include "knewaccountdlg.h" #include "knewinvestmentwizard.h" #include "mymoneyexception.h" #include "icons/icons.h" #include "mymoneyenums.h" #include "dialogenums.h" #include "widgetenums.h" using namespace Icons; class KEnterScheduleDlgPrivate { Q_DISABLE_COPY(KEnterScheduleDlgPrivate) public: KEnterScheduleDlgPrivate() : ui(new Ui::KEnterScheduleDlg), m_item(nullptr), m_showWarningOnce(true), m_extendedReturnCode(eDialogs::ScheduleResultCode::Cancel) { } ~KEnterScheduleDlgPrivate() { delete ui; } Ui::KEnterScheduleDlg *ui; MyMoneySchedule m_schedule; KMyMoneyRegister::Transaction* m_item; QWidgetList m_tabOrderWidgets; bool m_showWarningOnce; eDialogs::ScheduleResultCode m_extendedReturnCode; }; KEnterScheduleDlg::KEnterScheduleDlg(QWidget *parent, const MyMoneySchedule& schedule) : QDialog(parent), d_ptr(new KEnterScheduleDlgPrivate) { Q_D(KEnterScheduleDlg); // restore the last used dialog size KConfigGroup grp = KSharedConfig::openConfig()->group("KEnterScheduleDlg"); if (grp.isValid()) { KWindowConfig::restoreWindowSize(windowHandle(), grp); } // let the minimum size be 780x410 resize(QSize(780, 410).expandedTo(windowHandle() ? windowHandle()->size() : QSize())); // position the dialog centered on the application (for some reason without // a call to winId() the dialog is positioned in the upper left corner of // the screen, but winId() crashes on MS-Windows ... if (parent) move(parent->pos() + QPoint(parent->width()/2, parent->height()/2) - QPoint(width()/2, height()/2)); d->ui->setupUi(this); d->m_schedule = schedule; d->m_extendedReturnCode = eDialogs::ScheduleResultCode::Enter; d->ui->buttonOk->setIcon(Icons::get(Icon::KeyEnter)); d->ui->buttonSkip->setIcon(Icons::get(Icon::MediaSeekForward)); KGuiItem::assign(d->ui->buttonCancel, KStandardGuiItem::cancel()); KGuiItem::assign(d->ui->buttonHelp, KStandardGuiItem::help()); d->ui->buttonIgnore->setHidden(true); d->ui->buttonSkip->setHidden(true); // make sure, we have a tabbar with the form KMyMoneyTransactionForm::TabBar* tabbar = d->ui->m_form->getTabBar(d->ui->m_form->parentWidget()); // we never need to see the register d->ui->m_register->hide(); // ... setup the form ... d->ui->m_form->setupForm(d->m_schedule.account()); // ... and the register ... d->ui->m_register->clear(); // ... now add the transaction to register and form ... MyMoneyTransaction t = transaction(); d->m_item = KMyMoneyRegister::Register::transactionFactory(d->ui->m_register, t, d->m_schedule.transaction().splits().isEmpty() ? MyMoneySplit() : d->m_schedule.transaction().splits().front(), 0); d->ui->m_register->selectItem(d->m_item); // show the account row d->m_item->setShowRowInForm(0, true); d->ui->m_form->slotSetTransaction(d->m_item); // no need to see the tabbar tabbar->hide(); // setup name and type d->ui->m_scheduleName->setText(d->m_schedule.name()); d->ui->m_type->setText(KMyMoneyUtils::scheduleTypeToString(d->m_schedule.type())); connect(d->ui->buttonHelp, &QAbstractButton::clicked, this, &KEnterScheduleDlg::slotShowHelp); connect(d->ui->buttonIgnore, &QAbstractButton::clicked, this, &KEnterScheduleDlg::slotIgnore); connect(d->ui->buttonSkip, &QAbstractButton::clicked, this, &KEnterScheduleDlg::slotSkip); } KEnterScheduleDlg::~KEnterScheduleDlg() { Q_D(KEnterScheduleDlg); // store the last used dialog size KConfigGroup grp = KSharedConfig::openConfig()->group("KEnterScheduleDlg"); if (grp.isValid()) { KWindowConfig::saveWindowSize(windowHandle(), grp); } delete d; } eDialogs::ScheduleResultCode KEnterScheduleDlg::resultCode() const { Q_D(const KEnterScheduleDlg); if (result() == QDialog::Accepted) return d->m_extendedReturnCode; return eDialogs::ScheduleResultCode::Cancel; } void KEnterScheduleDlg::showExtendedKeys(bool visible) { Q_D(KEnterScheduleDlg); d->ui->buttonIgnore->setVisible(visible); d->ui->buttonSkip->setVisible(visible); } void KEnterScheduleDlg::slotIgnore() { Q_D(KEnterScheduleDlg); d->m_extendedReturnCode = eDialogs::ScheduleResultCode::Ignore; accept(); } void KEnterScheduleDlg::slotSkip() { Q_D(KEnterScheduleDlg); d->m_extendedReturnCode = eDialogs::ScheduleResultCode::Skip; accept(); } MyMoneyTransaction KEnterScheduleDlg::transaction() { Q_D(KEnterScheduleDlg); auto t = d->m_schedule.transaction(); try { if (d->m_schedule.type() == eMyMoney::Schedule::Type::LoanPayment) { KMyMoneyUtils::calculateAutoLoan(d->m_schedule, t, QMap()); } } catch (const MyMoneyException &e) { KMessageBox::detailedError(this, i18n("Unable to load schedule details"), QString::fromLatin1(e.what())); } t.clearId(); t.setEntryDate(QDate()); return t; } QDate KEnterScheduleDlg::date(const QDate& _date) const { Q_D(const KEnterScheduleDlg); auto date(_date); return d->m_schedule.adjustedDate(date, d->m_schedule.weekendOption()); } void KEnterScheduleDlg::resizeEvent(QResizeEvent* ev) { Q_UNUSED(ev) Q_D(KEnterScheduleDlg); d->ui->m_register->resize((int)eWidgets::eTransaction::Column::Detail); d->ui->m_form->resize((int)eWidgets::eTransactionForm::Column::Value1); QDialog::resizeEvent(ev); } void KEnterScheduleDlg::slotSetupSize() { resize(width(), minimumSizeHint().height()); } int KEnterScheduleDlg::exec() { Q_D(KEnterScheduleDlg); if (d->m_showWarningOnce) { d->m_showWarningOnce = false; KMessageBox::information(parentWidget(), QString("") + i18n("

Please check that all the details in the following dialog are correct and press OK.

Editable data can be changed and can either be applied to just this occurrence or for all subsequent occurrences for this schedule. (You will be asked what you intend after pressing OK in the following dialog)

") + QString("
"), i18n("Enter scheduled transaction"), "EnterScheduleDlgInfo"); } // force the initial height to be as small as possible QTimer::singleShot(0, this, SLOT(slotSetupSize())); return QDialog::exec(); } TransactionEditor* KEnterScheduleDlg::startEdit() { Q_D(KEnterScheduleDlg); KMyMoneyRegister::SelectedTransactions list(d->ui->m_register); auto editor = d->m_item->createEditor(d->ui->m_form, list, QDate()); if (editor) { editor->setScheduleInfo(d->m_schedule.name()); editor->setPaymentMethod(d->m_schedule.paymentType()); } // check that we use the same transaction commodity in all selected transactions // if not, we need to update this in the editor's list. The user can also bail out // of this operation which means that we have to stop editing here. if (editor) { if (!editor->fixTransactionCommodity(d->m_schedule.account())) { // if the user wants to quit, we need to destroy the editor // and bail out delete editor; editor = 0; } } if (editor) { connect(editor, &TransactionEditor::transactionDataSufficient, d->ui->buttonOk, &QWidget::setEnabled); connect(editor, &TransactionEditor::escapePressed, d->ui->buttonCancel, &QAbstractButton::animateClick); connect(editor, &TransactionEditor::returnPressed, d->ui->buttonOk, &QAbstractButton::animateClick); connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, editor, &TransactionEditor::slotReloadEditWidgets); // connect(editor, SIGNAL(finishEdit(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotLeaveEditMode(KMyMoneyRegister::SelectedTransactions))); connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, editor, &TransactionEditor::slotReloadEditWidgets); // create the widgets, place them in the parent and load them with data // setup tab order d->m_tabOrderWidgets.clear(); eWidgets::eRegister::Action action = eWidgets::eRegister::Action::Withdrawal; switch (d->m_schedule.type()) { case eMyMoney::Schedule::Type::Transfer: action = eWidgets::eRegister::Action::Transfer; break; case eMyMoney::Schedule::Type::Deposit: action = eWidgets::eRegister::Action::Deposit; break; case eMyMoney::Schedule::Type::LoanPayment: switch (d->m_schedule.paymentType()) { case eMyMoney::Schedule::PaymentType::DirectDeposit: case eMyMoney::Schedule::PaymentType::ManualDeposit: action = eWidgets::eRegister::Action::Deposit; break; default: break; } break; default: break; } editor->setup(d->m_tabOrderWidgets, d->m_schedule.account(), action); MyMoneyTransaction t = d->m_schedule.transaction(); QString num = t.splits().first().number(); QWidget* w = editor->haveWidget("number"); if (d->m_schedule.paymentType() == eMyMoney::Schedule::PaymentType::WriteChecque) { num = KMyMoneyUtils::nextFreeCheckNumber(d->m_schedule.account()); d->m_schedule.account().setValue("lastNumberUsed", num); if (w) if (auto numberWidget = dynamic_cast(w)) numberWidget->loadText(num); } else { // if it's not a check, then we need to clear // a possibly assigned check number if (w) if (auto numberWidget = dynamic_cast(w)) numberWidget->loadText(QString()); } Q_ASSERT(!d->m_tabOrderWidgets.isEmpty()); // editor->setup() leaves the tabbar as the last widget in the stack, but we // need it as first here. So we move it around. w = editor->haveWidget("tabbar"); if (w) { int idx = d->m_tabOrderWidgets.indexOf(w); if (idx != -1) { d->m_tabOrderWidgets.removeAt(idx); d->m_tabOrderWidgets.push_front(w); } } // don't forget our three buttons d->m_tabOrderWidgets.append(d->ui->buttonOk); d->m_tabOrderWidgets.append(d->ui->buttonCancel); d->m_tabOrderWidgets.append(d->ui->buttonHelp); for (auto i = 0; i < d->m_tabOrderWidgets.size(); ++i) { w = d->m_tabOrderWidgets.at(i); if (w) { w->installEventFilter(this); w->installEventFilter(editor); } } // Check if the editor has some preference on where to set the focus // If not, set the focus to the first widget in the tab order QWidget* focusWidget = editor->firstWidget(); if (!focusWidget) focusWidget = d->m_tabOrderWidgets.first(); focusWidget->setFocus(); // Make sure, we use the adjusted date if (auto dateEdit = dynamic_cast(editor->haveWidget("postdate"))) dateEdit->setDate(d->m_schedule.adjustedNextDueDate()); } return editor; } bool KEnterScheduleDlg::focusNextPrevChild(bool next) { Q_D(KEnterScheduleDlg); auto rc = false; auto w = qApp->focusWidget(); int currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w); while (w && currentWidgetIndex == -1) { // qDebug("'%s' not in list, use parent", w->className()); w = w->parentWidget(); currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w); } if (currentWidgetIndex != -1) { // if(w) qDebug("tab order is at '%s'", w->className()); currentWidgetIndex += next ? 1 : -1; if (currentWidgetIndex < 0) currentWidgetIndex = d->m_tabOrderWidgets.size() - 1; else if (currentWidgetIndex >= d->m_tabOrderWidgets.size()) currentWidgetIndex = 0; w = d->m_tabOrderWidgets[currentWidgetIndex]; // qDebug("currentWidgetIndex = %d, w = %p", currentWidgetIndex, w); if (((w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && w->isVisible() && w->isEnabled()) { // qDebug("Selecting '%s' as focus", w->className()); - w->setFocus(); + w->setFocus(next ? Qt::TabFocusReason: Qt::BacktabFocusReason); rc = true; } } return rc; } void KEnterScheduleDlg::slotShowHelp() { KHelpClient::invokeHelp("details.schedules.entering"); } diff --git a/kmymoney/dialogs/kmymoneysplittable.cpp b/kmymoney/dialogs/kmymoneysplittable.cpp index 40d825dec..83ae370e1 100644 --- a/kmymoney/dialogs/kmymoneysplittable.cpp +++ b/kmymoney/dialogs/kmymoneysplittable.cpp @@ -1,1137 +1,1137 @@ /* * Copyright 2008-2018 Thomas Baumgart * Copyright 2017-2018 Łukasz Wojniłowicz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "kmymoneysplittable.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneyaccount.h" #include "mymoneyfile.h" #include "mymoneyprice.h" #include "amountedit.h" #include "kmymoneycategory.h" #include "kmymoneyaccountselector.h" #include "kmymoneylineedit.h" #include "mymoneysecurity.h" #include "kmymoneysettings.h" #include "kmymoneymvccombo.h" #include "mymoneytag.h" #include "kmymoneytagcombo.h" #include "ktagcontainer.h" #include "kcurrencycalculator.h" #include "mymoneyutils.h" #include "mymoneytracer.h" #include "mymoneyexception.h" #include "icons.h" #include "mymoneyenums.h" using namespace Icons; class KMyMoneySplitTablePrivate { Q_DISABLE_COPY(KMyMoneySplitTablePrivate) public: KMyMoneySplitTablePrivate() : m_currentRow(0), m_maxRows(0), m_precision(2), m_contextMenu(nullptr), m_contextMenuDelete(nullptr), m_contextMenuDuplicate(nullptr), m_editCategory(0), m_editTag(0), m_editMemo(0), m_editAmount(0) { } ~KMyMoneySplitTablePrivate() { } /// the currently selected row (will be printed as selected) int m_currentRow; /// the number of rows filled with data int m_maxRows; MyMoneyTransaction m_transaction; MyMoneyAccount m_account; MyMoneySplit m_split; MyMoneySplit m_hiddenSplit; /** * This member keeps the precision for the values */ int m_precision; /** * This member keeps a pointer to the context menu */ QMenu* m_contextMenu; /// keeps the QAction of the delete entry in the context menu QAction* m_contextMenuDelete; /// keeps the QAction of the duplicate entry in the context menu QAction* m_contextMenuDuplicate; /** * This member contains a pointer to the input widget for the category. * The widget will be created and destroyed dynamically in createInputWidgets() * and destroyInputWidgets(). */ QPointer m_editCategory; /** * This member contains a pointer to the tag widget for the memo. */ QPointer m_editTag; /** * This member contains a pointer to the input widget for the memo. * The widget will be created and destroyed dynamically in createInputWidgets() * and destroyInputWidgets(). */ QPointer m_editMemo; /** * This member contains a pointer to the input widget for the amount. * The widget will be created and destroyed dynamically in createInputWidgets() * and destroyInputWidgets(). */ QPointer m_editAmount; /** * This member keeps the tab order for the above widgets */ QWidgetList m_tabOrderWidgets; QPointer m_registerButtonFrame; QPointer m_registerEnterButton; QPointer m_registerCancelButton; QMap m_priceInfo; }; KMyMoneySplitTable::KMyMoneySplitTable(QWidget *parent) : QTableWidget(parent), d_ptr(new KMyMoneySplitTablePrivate) { Q_D(KMyMoneySplitTable); // used for custom coloring with the help of the application's stylesheet setObjectName(QLatin1String("splittable")); // setup the transactions table setRowCount(1); setColumnCount(4); QStringList labels; labels << i18n("Category") << i18n("Memo") << i18n("Tag") << i18n("Amount"); setHorizontalHeaderLabels(labels); setSelectionMode(QAbstractItemView::SingleSelection); setSelectionBehavior(QAbstractItemView::SelectRows); int left, top, right, bottom; getContentsMargins(&left, &top, &right, &bottom); setContentsMargins(0, top, right, bottom); setFont(KMyMoneySettings::listCellFontEx()); setAlternatingRowColors(true); verticalHeader()->hide(); horizontalHeader()->setSectionsMovable(false); horizontalHeader()->setFont(KMyMoneySettings::listHeaderFontEx()); KConfigGroup grp = KSharedConfig::openConfig()->group("SplitTable"); QByteArray columns; columns = grp.readEntry("HeaderState", columns); horizontalHeader()->restoreState(columns); horizontalHeader()->setStretchLastSection(true); setShowGrid(KMyMoneySettings::showGrid()); setEditTriggers(QAbstractItemView::NoEditTriggers); // setup the context menu d->m_contextMenu = new QMenu(this); d->m_contextMenu->setTitle(i18n("Split Options")); d->m_contextMenu->setIcon(Icons::get(Icon::ViewFinancialTransfer)); d->m_contextMenu->addAction(Icons::get(Icon::DocumentEdit), i18n("Edit..."), this, SLOT(slotStartEdit())); d->m_contextMenuDuplicate = d->m_contextMenu->addAction(Icons::get(Icon::EditCopy), i18nc("To duplicate a split", "Duplicate"), this, SLOT(slotDuplicateSplit())); d->m_contextMenuDelete = d->m_contextMenu->addAction(Icons::get(Icon::EditDelete), i18n("Delete..."), this, SLOT(slotDeleteSplit())); connect(this, &QAbstractItemView::clicked, this, static_cast(&KMyMoneySplitTable::slotSetFocus)); connect(this, &KMyMoneySplitTable::transactionChanged, this, &KMyMoneySplitTable::slotUpdateData); installEventFilter(this); } KMyMoneySplitTable::~KMyMoneySplitTable() { Q_D(KMyMoneySplitTable); auto grp = KSharedConfig::openConfig()->group("SplitTable"); QByteArray columns = horizontalHeader()->saveState(); grp.writeEntry("HeaderState", columns); grp.sync(); delete d; } int KMyMoneySplitTable::currentRow() const { Q_D(const KMyMoneySplitTable); return d->m_currentRow; } void KMyMoneySplitTable::setup(const QMap& priceInfo, int precision) { Q_D(KMyMoneySplitTable); d->m_priceInfo = priceInfo; d->m_precision = precision; } bool KMyMoneySplitTable::eventFilter(QObject *o, QEvent *e) { Q_D(KMyMoneySplitTable); // MYMONEYTRACER(tracer); QKeyEvent *k = static_cast(e); bool rc = false; int row = currentRow(); int lines = viewport()->height() / rowHeight(0); if (e->type() == QEvent::KeyPress && !isEditMode()) { rc = true; switch (k->key()) { case Qt::Key_Up: if (row) slotSetFocus(model()->index(row - 1, 0)); break; case Qt::Key_Down: if (row < d->m_transaction.splits().count() - 1) slotSetFocus(model()->index(row + 1, 0)); break; case Qt::Key_Home: slotSetFocus(model()->index(0, 0)); break; case Qt::Key_End: slotSetFocus(model()->index(d->m_transaction.splits().count() - 1, 0)); break; case Qt::Key_PageUp: if (lines) { while (lines-- > 0 && row) --row; slotSetFocus(model()->index(row, 0)); } break; case Qt::Key_PageDown: if (row < d->m_transaction.splits().count() - 1) { while (lines-- > 0 && row < d->m_transaction.splits().count() - 1) ++row; slotSetFocus(model()->index(row, 0)); } break; case Qt::Key_Delete: slotDeleteSplit(); break; case Qt::Key_Return: case Qt::Key_Enter: if (row < d->m_transaction.splits().count() - 1 && KMyMoneySettings::enterMovesBetweenFields()) { slotStartEdit(); } else emit returnPressed(); break; case Qt::Key_Escape: emit escapePressed(); break; case Qt::Key_F2: slotStartEdit(); break; default: rc = true; // duplicate split if (Qt::Key_C == k->key() && Qt::ControlModifier == k->modifiers()) { slotDuplicateSplit(); // new split } else if (Qt::Key_Insert == k->key() && Qt::ControlModifier == k->modifiers()) { slotSetFocus(model()->index(d->m_transaction.splits().count() - 1, 0)); slotStartEdit(); } else if (k->text()[ 0 ].isPrint()) { KMyMoneyCategory* cat = createEditWidgets(false); if (cat) { KMyMoneyLineEdit *le = qobject_cast(cat->lineEdit()); if (le) { // make sure, the widget receives the key again // and does not select the text this time le->setText(k->text()); le->end(false); le->deselect(); le->skipSelectAll(true); le->setFocus(); } } } break; } } else if (e->type() == QEvent::KeyPress && isEditMode()) { bool terminate = true; rc = true; switch (k->key()) { // suppress the F2 functionality to start editing in inline edit mode case Qt::Key_F2: // suppress the cursor movement in inline edit mode case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_PageUp: case Qt::Key_PageDown: break; case Qt::Key_Return: case Qt::Key_Enter: // we cannot call the slot directly, as it destroys the caller of // this method :-( So we let the event handler take care of calling // the respective slot using a timeout. For a KLineEdit derived object // it could be, that at this point the user selected a value from // a completion list. In this case, we close the completion list and // do not end editing of the transaction. if (o->inherits("KLineEdit")) { if (auto le = dynamic_cast(o)) { KCompletionBox* box = le->completionBox(false); if (box && box->isVisible()) { terminate = false; le->completionBox(false)->hide(); } } } // in case we have the 'enter moves focus between fields', we need to simulate // a TAB key when the object 'o' points to the category or memo field. if (KMyMoneySettings::enterMovesBetweenFields()) { if (o == d->m_editCategory->lineEdit() || o == d->m_editMemo || o == d->m_editTag) { terminate = false; QKeyEvent evt(e->type(), Qt::Key_Tab, k->modifiers(), QString(), k->isAutoRepeat(), k->count()); QApplication::sendEvent(o, &evt); } } if (terminate) { QTimer::singleShot(0, this, SLOT(slotEndEditKeyboard())); } break; case Qt::Key_Escape: // we cannot call the slot directly, as it destroys the caller of // this method :-( So we let the event handler take care of calling // the respective slot using a timeout. QTimer::singleShot(0, this, SLOT(slotCancelEdit())); break; default: rc = false; break; } } else if (e->type() == QEvent::KeyRelease && !isEditMode()) { // for some reason, we only see a KeyRelease event of the Menu key // here. In other locations (e.g. Register::eventFilter()) we see // a KeyPress event. Strange. (ipwizard - 2008-05-10) switch (k->key()) { case Qt::Key_Menu: // if the very last entry is selected, the delete // operation is not available otherwise it is d->m_contextMenuDelete->setEnabled( row < d->m_transaction.splits().count() - 1); d->m_contextMenuDuplicate->setEnabled( row < d->m_transaction.splits().count() - 1); d->m_contextMenu->exec(QCursor::pos()); rc = true; break; default: break; } } // if the event has not been processed here, forward it to // the base class implementation if it's not a key event if (rc == false) { if (e->type() != QEvent::KeyPress && e->type() != QEvent::KeyRelease) { rc = QTableWidget::eventFilter(o, e); } } return rc; } void KMyMoneySplitTable::slotSetFocus(const QModelIndex& index) { slotSetFocus(index, Qt::LeftButton); } void KMyMoneySplitTable::slotSetFocus(const QModelIndex& index, int button) { Q_D(KMyMoneySplitTable); MYMONEYTRACER(tracer); auto row = index.row(); // adjust row to used area if (row > d->m_transaction.splits().count() - 1) row = d->m_transaction.splits().count() - 1; if (row < 0) row = 0; // make sure the row will be on the screen scrollTo(model()->index(row, 0)); if (isEditMode()) { // in edit mode? if (isEditSplitValid() && KMyMoneySettings::focusChangeIsEnter()) endEdit(false/*keyboard driven*/, false/*set focus to next row*/); else slotCancelEdit(); } if (button == Qt::LeftButton) { // left mouse button if (row != currentRow()) { // setup new current row and update visible selection selectRow(row); slotUpdateData(d->m_transaction); } } else if (button == Qt::RightButton) { // context menu is only available when cursor is on // an existing transaction or the first line after this area if (row == index.row()) { // setup new current row and update visible selection selectRow(row); slotUpdateData(d->m_transaction); // if the very last entry is selected, the delete // operation is not available otherwise it is d->m_contextMenuDelete->setEnabled( row < d->m_transaction.splits().count() - 1); d->m_contextMenuDuplicate->setEnabled( row < d->m_transaction.splits().count() - 1); d->m_contextMenu->exec(QCursor::pos()); } } } void KMyMoneySplitTable::mousePressEvent(QMouseEvent* e) { slotSetFocus(indexAt(e->pos()), e->button()); } /* turn off QTable behaviour */ void KMyMoneySplitTable::mouseReleaseEvent(QMouseEvent* /* e */) { } void KMyMoneySplitTable::mouseDoubleClickEvent(QMouseEvent *e) { Q_D(KMyMoneySplitTable); MYMONEYTRACER(tracer); int col = columnAt(e->pos().x()); slotSetFocus(model()->index(rowAt(e->pos().y()), col), e->button()); createEditWidgets(false); QLineEdit* editWidget = 0; //krazy:exclude=qmethods switch (col) { case 0: editWidget = d->m_editCategory->lineEdit(); break; case 1: editWidget = d->m_editMemo; break; case 2: d->m_editTag->tagCombo()->setFocus(); break; case 3: editWidget = d->m_editAmount; break; default: break; } if (editWidget) { editWidget->setFocus(); editWidget->selectAll(); } } void KMyMoneySplitTable::selectRow(int row) { Q_D(KMyMoneySplitTable); MYMONEYTRACER(tracer); if (row > d->m_maxRows) row = d->m_maxRows; d->m_currentRow = row; QTableWidget::selectRow(row); QList list = getSplits(d->m_transaction); if (row < list.count()) d->m_split = list[row]; else d->m_split = MyMoneySplit(); } void KMyMoneySplitTable::setRowCount(int irows) { QTableWidget::setRowCount(irows); // determine row height according to the edit widgets // we use the category widget as the base QFontMetrics fm(KMyMoneySettings::listCellFontEx()); int height = fm.lineSpacing() + 6; #if 0 // recalculate row height hint KMyMoneyCategory cat; height = qMax(cat.sizeHint().height(), height); #endif verticalHeader()->setUpdatesEnabled(false); for (auto i = 0; i < irows; ++i) verticalHeader()->resizeSection(i, height); verticalHeader()->setUpdatesEnabled(true); } void KMyMoneySplitTable::setTransaction(const MyMoneyTransaction& t, const MyMoneySplit& s, const MyMoneyAccount& acc) { Q_D(KMyMoneySplitTable); MYMONEYTRACER(tracer); d->m_transaction = t; d->m_account = acc; d->m_hiddenSplit = s; selectRow(0); slotUpdateData(d->m_transaction); } MyMoneyTransaction KMyMoneySplitTable::transaction() const { Q_D(const KMyMoneySplitTable); return d->m_transaction; } QList KMyMoneySplitTable::getSplits(const MyMoneyTransaction& t) const { Q_D(const KMyMoneySplitTable); // get list of splits QList list = t.splits(); // and ignore the one that should be hidden QList::Iterator it; for (it = list.begin(); it != list.end(); ++it) { if ((*it).id() == d->m_hiddenSplit.id()) { list.erase(it); break; } } return list; } void KMyMoneySplitTable::slotUpdateData(const MyMoneyTransaction& t) { Q_D(KMyMoneySplitTable); MYMONEYTRACER(tracer); unsigned long numRows = 0; QTableWidgetItem* textItem; QList list = getSplits(t); updateTransactionTableSize(); // fill the part that is used by transactions QList::Iterator it; for (it = list.begin(); it != list.end(); ++it) { QString colText; MyMoneyMoney value = (*it).value(); if (!(*it).accountId().isEmpty()) { try { colText = MyMoneyFile::instance()->accountToCategory((*it).accountId()); } catch (const MyMoneyException &) { qDebug("Unexpected exception in KMyMoneySplitTable::slotUpdateData()"); } } QString amountTxt = value.formatMoney(d->m_account.fraction()); if (value == MyMoneyMoney::autoCalc) { amountTxt = i18n("will be calculated"); } if (colText.isEmpty() && (*it).memo().isEmpty() && value.isZero()) amountTxt.clear(); unsigned width = fontMetrics().width(amountTxt); AmountEdit* valfield = new AmountEdit(); valfield->setMinimumWidth(width); width = valfield->minimumSizeHint().width(); delete valfield; textItem = item(numRows, 0); if (textItem) textItem->setText(colText); else setItem(numRows, 0, new QTableWidgetItem(colText)); textItem = item(numRows, 1); if (textItem) textItem->setText((*it).memo()); else setItem(numRows, 1, new QTableWidgetItem((*it).memo())); QList tl = (*it).tagIdList(); QStringList tagNames; if (!tl.isEmpty()) { for (int i = 0; i < tl.size(); i++) tagNames.append(MyMoneyFile::instance()->tag(tl[i]).name()); } setItem(numRows, 2, new QTableWidgetItem(tagNames.join(", "))); textItem = item(numRows, 3); if (textItem) textItem->setText(amountTxt); else setItem(numRows, 3, new QTableWidgetItem(amountTxt)); item(numRows, 3)->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); ++numRows; } // now clean out the remainder of the table while (numRows < static_cast(rowCount())) { for (auto i = 0 ; i < 4; ++i) { textItem = item(numRows, i); if (textItem) textItem->setText(""); else setItem(numRows, i, new QTableWidgetItem("")); } item(numRows, 3)->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); ++numRows; } } void KMyMoneySplitTable::updateTransactionTableSize() { Q_D(KMyMoneySplitTable); // get current size of transactions table int tableHeight = height(); int splitCount = d->m_transaction.splits().count() - 1; if (splitCount < 0) splitCount = 0; // see if we need some extra lines to fill the current size with the grid int numExtraLines = (tableHeight / rowHeight(0)) - splitCount; if (numExtraLines < 2) numExtraLines = 2; setRowCount(splitCount + numExtraLines); d->m_maxRows = splitCount; } void KMyMoneySplitTable::resizeEvent(QResizeEvent* ev) { QTableWidget::resizeEvent(ev); if (!isEditMode()) { // update the size of the transaction table only if a split is not being edited // otherwise the height of the editors would be altered in an undesired way updateTransactionTableSize(); } } void KMyMoneySplitTable::slotDuplicateSplit() { Q_D(KMyMoneySplitTable); MYMONEYTRACER(tracer); QList list = getSplits(d->m_transaction); if (d->m_currentRow < list.count()) { MyMoneySplit split = list[d->m_currentRow]; split.clearId(); try { d->m_transaction.addSplit(split); emit transactionChanged(d->m_transaction); } catch (const MyMoneyException &e) { qDebug("Cannot duplicate split: %s", e.what()); } } } void KMyMoneySplitTable::slotDeleteSplit() { Q_D(KMyMoneySplitTable); MYMONEYTRACER(tracer); QList list = getSplits(d->m_transaction); if (d->m_currentRow < list.count()) { if (KMessageBox::warningContinueCancel(this, i18n("You are about to delete the selected split. " "Do you really want to continue?"), i18n("KMyMoney") ) == KMessageBox::Continue) { try { d->m_transaction.removeSplit(list[d->m_currentRow]); // if we removed the last split, select the previous if (d->m_currentRow && d->m_currentRow == list.count() - 1) selectRow(d->m_currentRow - 1); else selectRow(d->m_currentRow); emit transactionChanged(d->m_transaction); } catch (const MyMoneyException &e) { qDebug("Cannot remove split: %s", e.what()); } } } } KMyMoneyCategory* KMyMoneySplitTable::slotStartEdit() { MYMONEYTRACER(tracer); return createEditWidgets(true); } void KMyMoneySplitTable::slotEndEdit() { endEdit(false); } void KMyMoneySplitTable::slotEndEditKeyboard() { endEdit(true); } void KMyMoneySplitTable::endEdit(bool keyboardDriven, bool setFocusToNextRow) { Q_D(KMyMoneySplitTable); auto file = MyMoneyFile::instance(); MYMONEYTRACER(tracer); MyMoneySplit s1 = d->m_split; if (!isEditSplitValid()) { KMessageBox::information(this, i18n("You need to assign a category to this split before it can be entered."), i18n("Enter split"), "EnterSplitWithEmptyCategory"); d->m_editCategory->setFocus(); return; } bool needUpdate = false; if (d->m_editCategory->selectedItem() != d->m_split.accountId()) { s1.setAccountId(d->m_editCategory->selectedItem()); needUpdate = true; } if (d->m_editMemo->text() != d->m_split.memo()) { s1.setMemo(d->m_editMemo->text()); needUpdate = true; } if (d->m_editTag->selectedTags() != d->m_split.tagIdList()) { s1.setTagIdList(d->m_editTag->selectedTags()); needUpdate = true; } if (d->m_editAmount->value() != d->m_split.value()) { s1.setValue(d->m_editAmount->value()); needUpdate = true; } if (needUpdate) { if (!s1.value().isZero()) { MyMoneyAccount cat = file->account(s1.accountId()); if (cat.currencyId() != d->m_transaction.commodity()) { MyMoneySecurity fromCurrency, toCurrency; MyMoneyMoney fromValue, toValue; fromCurrency = file->security(d->m_transaction.commodity()); toCurrency = file->security(cat.currencyId()); // determine the fraction required for this category int fract = toCurrency.smallestAccountFraction(); if (cat.accountType() == eMyMoney::Account::Type::Cash) fract = toCurrency.smallestCashFraction(); // display only positive values to the user fromValue = s1.value().abs(); // if we had a price info in the beginning, we use it here if (d->m_priceInfo.find(cat.currencyId()) != d->m_priceInfo.end()) { toValue = (fromValue * d->m_priceInfo[cat.currencyId()]).convert(fract); } // if the shares are still 0, we need to change that if (toValue.isZero()) { const MyMoneyPrice &price = MyMoneyFile::instance()->price(fromCurrency.id(), toCurrency.id()); // if the price is valid calculate the shares. If it is invalid // assume a conversion rate of 1.0 if (price.isValid()) { toValue = (price.rate(toCurrency.id()) * fromValue).convert(fract); } else { toValue = fromValue; } } // now present all that to the user QPointer calc = new KCurrencyCalculator(fromCurrency, toCurrency, fromValue, toValue, d->m_transaction.postDate(), fract, this); if (calc->exec() == QDialog::Rejected) { delete calc; return; } else { s1.setShares((s1.value() * calc->price()).convert(fract)); delete calc; } } else { s1.setShares(s1.value()); } } else s1.setShares(s1.value()); d->m_split = s1; try { if (d->m_split.id().isEmpty()) { d->m_transaction.addSplit(d->m_split); } else { d->m_transaction.modifySplit(d->m_split); } emit transactionChanged(d->m_transaction); } catch (const MyMoneyException &e) { qDebug("Cannot add/modify split: %s", e.what()); } } this->setFocus(); destroyEditWidgets(); if (setFocusToNextRow) { slotSetFocus(model()->index(currentRow() + 1, 0)); } // if we still have more splits, we start editing right away // in case we have selected 'enter moves between fields' if (keyboardDriven && currentRow() < d->m_transaction.splits().count() - 1 && KMyMoneySettings::enterMovesBetweenFields()) { slotStartEdit(); } } void KMyMoneySplitTable::slotCancelEdit() { Q_D(const KMyMoneySplitTable); MYMONEYTRACER(tracer); if (isEditMode()) { /* * Prevent asking to add a new category which happens if the user entered any text * caused by emitting signals in KMyMoneyCombo::focusOutEvent() on focus out event. * (see bug 344409) */ if (d->m_editCategory) d->m_editCategory->lineEdit()->setText(QString()); destroyEditWidgets(); this->setFocus(); } } bool KMyMoneySplitTable::isEditMode() const { Q_D(const KMyMoneySplitTable); // while the edit widgets exist we're in edit mode return d->m_editAmount || d->m_editMemo || d->m_editCategory || d->m_editTag; } bool KMyMoneySplitTable::isEditSplitValid() const { Q_D(const KMyMoneySplitTable); return isEditMode() && !(d->m_editCategory && d->m_editCategory->selectedItem().isEmpty()); } void KMyMoneySplitTable::destroyEditWidgets() { MYMONEYTRACER(tracer); Q_D(KMyMoneySplitTable); emit editFinished(); disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &KMyMoneySplitTable::slotLoadEditWidgets); destroyEditWidget(d->m_currentRow, 0); destroyEditWidget(d->m_currentRow, 1); destroyEditWidget(d->m_currentRow, 2); destroyEditWidget(d->m_currentRow, 3); destroyEditWidget(d->m_currentRow + 1, 0); } void KMyMoneySplitTable::destroyEditWidget(int r, int c) { if (QWidget* cw = cellWidget(r, c)) cw->hide(); removeCellWidget(r, c); } KMyMoneyCategory* KMyMoneySplitTable::createEditWidgets(bool setFocus) { MYMONEYTRACER(tracer); emit editStarted(); Q_D(KMyMoneySplitTable); auto cellFont = KMyMoneySettings::listCellFontEx(); d->m_tabOrderWidgets.clear(); // create the widgets d->m_editAmount = new AmountEdit; d->m_editAmount->setFont(cellFont); d->m_editAmount->setCalculatorButtonVisible(true); d->m_editAmount->setPrecision(d->m_precision); d->m_editCategory = new KMyMoneyCategory(); d->m_editCategory->setPlaceholderText(i18n("Category")); d->m_editCategory->setFont(cellFont); connect(d->m_editCategory, SIGNAL(createItem(QString,QString&)), this, SIGNAL(createCategory(QString,QString&))); connect(d->m_editCategory, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool))); d->m_editMemo = new KMyMoneyLineEdit(0, false, Qt::AlignLeft | Qt::AlignVCenter); d->m_editMemo->setPlaceholderText(i18n("Memo")); d->m_editMemo->setFont(cellFont); d->m_editTag = new KTagContainer; d->m_editTag->tagCombo()->setPlaceholderText(i18n("Tag")); d->m_editTag->tagCombo()->setFont(cellFont); d->m_editTag->loadTags(MyMoneyFile::instance()->tagList()); connect(d->m_editTag->tagCombo(), SIGNAL(createItem(QString,QString&)), this, SIGNAL(createTag(QString,QString&))); connect(d->m_editTag->tagCombo(), SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool))); // create buttons for the mouse users d->m_registerButtonFrame = new QFrame(this); d->m_registerButtonFrame->setContentsMargins(0, 0, 0, 0); d->m_registerButtonFrame->setAutoFillBackground(true); QHBoxLayout* l = new QHBoxLayout(d->m_registerButtonFrame); l->setContentsMargins(0, 0, 0, 0); l->setSpacing(0); d->m_registerEnterButton = new QPushButton(Icons::get(Icon::DialogOK) , QString(), d->m_registerButtonFrame); d->m_registerCancelButton = new QPushButton(Icons::get(Icon::DialogCancel) , QString(), d->m_registerButtonFrame); l->addWidget(d->m_registerEnterButton); l->addWidget(d->m_registerCancelButton); l->addStretch(2); connect(d->m_registerEnterButton.data(), &QAbstractButton::clicked, this, &KMyMoneySplitTable::slotEndEdit); connect(d->m_registerCancelButton.data(), &QAbstractButton::clicked, this, &KMyMoneySplitTable::slotCancelEdit); // setup tab order addToTabOrder(d->m_editCategory); addToTabOrder(d->m_editMemo); addToTabOrder(d->m_editTag); addToTabOrder(d->m_editAmount); addToTabOrder(d->m_registerEnterButton); addToTabOrder(d->m_registerCancelButton); if (!d->m_split.accountId().isEmpty()) { d->m_editCategory->setSelectedItem(d->m_split.accountId()); } else { // check if the transaction is balanced or not. If not, // assign the remainder to the amount. MyMoneyMoney diff; QList list = d->m_transaction.splits(); QList::ConstIterator it_s; for (it_s = list.constBegin(); it_s != list.constEnd(); ++it_s) { if (!(*it_s).accountId().isEmpty()) diff += (*it_s).value(); } d->m_split.setValue(-diff); } QList t = d->m_split.tagIdList(); if (!t.isEmpty()) { for (int i = 0; i < t.size(); i++) d->m_editTag->addTagWidget(t[i]); } d->m_editMemo->loadText(d->m_split.memo()); // don't allow automatically calculated values to be modified if (d->m_split.value() == MyMoneyMoney::autoCalc) { d->m_editAmount->setEnabled(false); d->m_editAmount->setText("will be calculated"); } else d->m_editAmount->setValue(d->m_split.value()); setCellWidget(d->m_currentRow, 0, d->m_editCategory); setCellWidget(d->m_currentRow, 1, d->m_editMemo); setCellWidget(d->m_currentRow, 2, d->m_editTag); setCellWidget(d->m_currentRow, 3, d->m_editAmount); setCellWidget(d->m_currentRow + 1, 0, d->m_registerButtonFrame); // load e.g. the category widget with the account list slotLoadEditWidgets(); connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &KMyMoneySplitTable::slotLoadEditWidgets); foreach (QWidget* w, d->m_tabOrderWidgets) { if (w) { w->installEventFilter(this); } } if (setFocus) { d->m_editCategory->lineEdit()->setFocus(); d->m_editCategory->lineEdit()->selectAll(); } // resize the rows so the added edit widgets would fit appropriately resizeRowsToContents(); return d->m_editCategory; } void KMyMoneySplitTable::slotLoadEditWidgets() { Q_D(KMyMoneySplitTable); // reload category widget auto categoryId = d->m_editCategory->selectedItem(); AccountSet aSet; aSet.addAccountGroup(eMyMoney::Account::Type::Asset); aSet.addAccountGroup(eMyMoney::Account::Type::Liability); aSet.addAccountGroup(eMyMoney::Account::Type::Income); aSet.addAccountGroup(eMyMoney::Account::Type::Expense); if (KMyMoneySettings::expertMode()) aSet.addAccountGroup(eMyMoney::Account::Type::Equity); // remove the accounts with invalid types at this point aSet.removeAccountType(eMyMoney::Account::Type::CertificateDep); aSet.removeAccountType(eMyMoney::Account::Type::Investment); aSet.removeAccountType(eMyMoney::Account::Type::Stock); aSet.removeAccountType(eMyMoney::Account::Type::MoneyMarket); aSet.load(d->m_editCategory->selector()); // if an account is specified then remove it from the widget so that the user // cannot create a transfer with from and to account being the same account if (!d->m_account.id().isEmpty()) d->m_editCategory->selector()->removeItem(d->m_account.id()); if (!categoryId.isEmpty()) d->m_editCategory->setSelectedItem(categoryId); } void KMyMoneySplitTable::addToTabOrder(QWidget* w) { Q_D(KMyMoneySplitTable); if (w) { while (w->focusProxy()) w = w->focusProxy(); d->m_tabOrderWidgets.append(w); } } bool KMyMoneySplitTable::focusNextPrevChild(bool next) { MYMONEYTRACER(tracer); Q_D(KMyMoneySplitTable); auto rc = false; if (isEditMode()) { QWidget *w = 0; w = qApp->focusWidget(); int currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w); while (w && currentWidgetIndex == -1) { // qDebug("'%s' not in list, use parent", w->className()); w = w->parentWidget(); currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w); } if (currentWidgetIndex != -1) { // if(w) qDebug("tab order is at '%s'", w->className()); currentWidgetIndex += next ? 1 : -1; if (currentWidgetIndex < 0) currentWidgetIndex = d->m_tabOrderWidgets.size() - 1; else if (currentWidgetIndex >= d->m_tabOrderWidgets.size()) currentWidgetIndex = 0; w = d->m_tabOrderWidgets[currentWidgetIndex]; if (((w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && w->isVisible() && w->isEnabled()) { // qDebug("Selecting '%s' as focus", w->className()); - w->setFocus(); + w->setFocus(next ? Qt::TabFocusReason: Qt::BacktabFocusReason); rc = true; } } } else rc = QTableWidget::focusNextPrevChild(next); return rc; }