File indexing completed on 2024-05-12 05:07:38

0001 /*
0002     SPDX-FileCopyrightText: 2021 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "keditscheduledlg.h"
0007 
0008 // ----------------------------------------------------------------------------
0009 // QT Includes
0010 
0011 #include <QAction>
0012 #include <QCheckBox>
0013 #include <QDebug>
0014 #include <QKeyEvent>
0015 #include <QLabel>
0016 #include <QList>
0017 #include <QPushButton>
0018 
0019 // ----------------------------------------------------------------------------
0020 // KDE Includes
0021 
0022 #include <KHelpClient>
0023 #include <KLineEdit>
0024 #include <KLocalizedString>
0025 #include <KMessageBox>
0026 #include <KStandardGuiItem>
0027 
0028 // ----------------------------------------------------------------------------
0029 // Project Includes
0030 
0031 #include "accountsmodel.h"
0032 #include "comboboxmodels.h"
0033 #include "delegateproxy.h"
0034 #include "journaldelegate.h"
0035 #include "keditloanwizard.h"
0036 #include "kguiutils.h"
0037 #include "kmymoneyaccountcombo.h"
0038 #include "kmymoneydateedit.h"
0039 #include "kmymoneylineedit.h"
0040 #include "kmymoneymvccombo.h"
0041 #include "kmymoneysettings.h"
0042 #include "kmymoneyutils.h"
0043 #include "knewaccountdlg.h"
0044 #include "knewinvestmentwizard.h"
0045 #include "menuenums.h"
0046 #include "mymoneyaccount.h"
0047 #include "mymoneyenums.h"
0048 #include "mymoneyexception.h"
0049 #include "mymoneyfile.h"
0050 #include "mymoneymoney.h"
0051 #include "mymoneyschedule.h"
0052 #include "mymoneysplit.h"
0053 #include "mymoneytransaction.h"
0054 #include "mymoneyutils.h"
0055 #include "newtransactioneditor.h"
0056 #include "securitiesmodel.h"
0057 #include "widgetenums.h"
0058 #include "widgethintframe.h"
0059 
0060 #include "kmmyesno.h"
0061 
0062 #include "ui_keditscheduledlg.h"
0063 
0064 using namespace eMyMoney;
0065 
0066 class KEditScheduleDlgPrivate : public QObject
0067 {
0068     Q_OBJECT
0069 
0070     Q_DISABLE_COPY(KEditScheduleDlgPrivate)
0071     Q_DECLARE_PUBLIC(KEditScheduleDlg)
0072 
0073 public:
0074     explicit KEditScheduleDlgPrivate(KEditScheduleDlg* qq)
0075         : q_ptr(qq)
0076         , ui(new Ui::KEditScheduleDlg)
0077         , tabOrderUi(nullptr)
0078         , m_frameCollection(nullptr)
0079         , m_accountCombo(nullptr)
0080         , m_categoryCombo(nullptr)
0081         , transactionEditor(nullptr)
0082     {
0083     }
0084 
0085     ~KEditScheduleDlgPrivate()
0086     {
0087         delete ui;
0088         delete tabOrderUi;
0089     }
0090 
0091     void init()
0092     {
0093         Q_Q(KEditScheduleDlg);
0094         ui->setupUi(q);
0095         ui->keepMultiCurrencyAmount->setEnabled(false);
0096 
0097         transactionEditor = ui->transactionEditor;
0098         transactionEditor->setShowAccountCombo(true);
0099 
0100         transactionEditor->setShowButtons(false);
0101         transactionEditor->layout()->setContentsMargins(0, 0, 0, 0);
0102 
0103         m_frameCollection = new WidgetHintFrameCollection(q);
0104         m_frameCollection->addWidget(ui->buttonBox->button(QDialogButtonBox::Ok));
0105         m_frameCollection->addFrame(new WidgetHintFrame(ui->nameEdit));
0106         connect(ui->nameEdit, &KLineEdit::textChanged, this, [&]() {
0107             updateState();
0108         });
0109 
0110         // also forward the state of the editor's widgets
0111         m_frameCollection->chainFrameCollection(transactionEditor->widgetHintFrameCollection());
0112 
0113         transactionEditor->setShowNumberWidget(true);
0114 
0115         // we don't need the focus frame here
0116         delete transactionEditor->focusFrame();
0117     }
0118 
0119     void updateState()
0120     {
0121         WidgetHintFrame::hide(ui->nameEdit);
0122         WidgetHintFrame::hide(m_accountCombo);
0123         WidgetHintFrame::hide(m_categoryCombo);
0124         ui->keepMultiCurrencyAmount->setDisabled(true);
0125 
0126         if (ui->nameEdit->text().isEmpty()) {
0127             WidgetHintFrame::show(ui->nameEdit, i18nc("@info:tooltip", "The name is a required field for a schedule"));
0128         }
0129         if (m_accountCombo && m_accountCombo->getSelected().isEmpty()) {
0130             WidgetHintFrame::show(m_accountCombo, i18nc("@info:tooltip", "The account is a required field for a schedule"));
0131         }
0132         // if we have a combo, it is empty and can be modified (no multi split transaction)
0133         if (m_categoryCombo && m_categoryCombo->getSelected().isEmpty() && !m_categoryCombo->lineEdit()->isReadOnly()) {
0134             WidgetHintFrame::show(m_categoryCombo, i18nc("@info:tooltip", "The category is a required field for a schedule"));
0135         }
0136 
0137         if (m_accountCombo && m_categoryCombo) {
0138             if (!(m_accountCombo->getSelected().isEmpty() || m_categoryCombo->getSelected().isEmpty() || m_categoryCombo->lineEdit()->isReadOnly())) {
0139                 const auto file = MyMoneyFile::instance();
0140                 try {
0141                     const auto account = file->account(m_accountCombo->getSelected());
0142                     const auto category = file->account(m_categoryCombo->getSelected());
0143                     ui->keepMultiCurrencyAmount->setEnabled(account.currencyId() != category.currencyId());
0144                 } catch (MyMoneyException&) {
0145                     ;
0146                 }
0147             }
0148         }
0149     }
0150 
0151     void loadWidgets()
0152     {
0153         Q_Q(KEditScheduleDlg);
0154         m_accountCombo = transactionEditor->findChild<KMyMoneyAccountCombo*>(QLatin1String("accountCombo"));
0155         if (m_accountCombo) {
0156             m_frameCollection->addFrame(new WidgetHintFrame(m_accountCombo));
0157             connect(m_accountCombo, &KMyMoneyAccountCombo::accountSelected, this, [&]() {
0158                 updateState();
0159             });
0160         }
0161         const auto account = m_schedule.account();
0162         if (!account.id().isEmpty()) {
0163             m_accountCombo->setSelected(account.id());
0164         }
0165 
0166         m_categoryCombo = transactionEditor->findChild<KMyMoneyAccountCombo*>(QLatin1String("categoryCombo"));
0167         if (m_categoryCombo) {
0168             m_frameCollection->addFrame(new WidgetHintFrame(m_categoryCombo));
0169             connect(transactionEditor, &NewTransactionEditor::categorySelectionChanged, this, [&]() {
0170                 updateState();
0171             });
0172         }
0173 
0174         transactionEditor->loadSchedule(m_schedule);
0175 
0176         // setup widget contents
0177         ui->nameEdit->setText(m_schedule.name());
0178 
0179         ui->frequencyEdit->setModel(&m_frequencyModel);
0180         ui->frequencyEdit->setCurrentIndex(m_frequencyModel.indexByOccurrence(m_schedule.occurrence()).row());
0181 
0182         if (ui->frequencyEdit->currentData(eMyMoney::Model::ScheduleFrequencyRole).value<eMyMoney::Schedule::Occurrence>() == Schedule::Occurrence::Any)
0183             ui->frequencyEdit->setCurrentIndex(m_frequencyModel.indexByOccurrence(Schedule::Occurrence::Monthly).row());
0184         // q->slotFrequencyChanged((int)ui->m_frequencyEdit->currentItem());
0185         ui->frequencyNoEdit->setValue(m_schedule.occurrenceMultiplier());
0186 
0187         // load option widgets
0188         ui->paymentMethodCombo->setModel(&m_paymentMethodModel);
0189 
0190         auto method = m_schedule.paymentType();
0191         if (method == Schedule::PaymentType::Any)
0192             method = Schedule::PaymentType::Other;
0193         ui->paymentMethodCombo->setCurrentIndex(m_paymentMethodModel.indexByPaymentMethod(method).row());
0194 
0195         switch (m_schedule.weekendOption()) {
0196         case Schedule::WeekendOption::MoveNothing:
0197             ui->weekendOptionCombo->setCurrentIndex(0);
0198             break;
0199         case Schedule::WeekendOption::MoveBefore:
0200             ui->weekendOptionCombo->setCurrentIndex(1);
0201             break;
0202         case Schedule::WeekendOption::MoveAfter:
0203             ui->weekendOptionCombo->setCurrentIndex(2);
0204             break;
0205         }
0206         ui->estimateOption->setChecked(!m_schedule.isFixed());
0207         ui->lastDayInMonthOption->setChecked(m_schedule.lastDayInMonth());
0208         ui->autoEnterOption->setChecked(m_schedule.autoEnter());
0209         ui->endSeriesOption->setChecked(m_schedule.willEnd());
0210 
0211         ui->endOptionsFrame->setEnabled(m_schedule.willEnd());
0212         if (m_schedule.willEnd()) {
0213             ui->remainingEdit->setValue(m_schedule.transactionsRemaining());
0214             ui->finalPaymentDateEdit->setDate(m_schedule.endDate());
0215         }
0216 
0217         ui->keepMultiCurrencyAmount->setChecked(m_schedule.keepMultiCurrencyAmount());
0218 
0219         q->setModal(true);
0220 
0221         // we just hide the variation field for now and enable the logic
0222         // once we have a respective member in the MyMoneySchedule object
0223         ui->variationEdit->hide();
0224     }
0225 
0226     void setupTabOrder()
0227     {
0228         Q_Q(KEditScheduleDlg);
0229 
0230         const auto defaultTabOrder = QStringList{
0231             QLatin1String("nameEdit"),
0232             QLatin1String("frequencyNoEdit"),
0233             QLatin1String("frequencyEdit"),
0234             QLatin1String("paymentMethodCombo"),
0235             // the std transaction editor (see also newtransactioneditor.cpp
0236             QLatin1String("accountCombo"),
0237             QLatin1String("dateEdit"),
0238             QLatin1String("creditDebitEdit"),
0239             QLatin1String("payeeEdit"),
0240             QLatin1String("numberEdit"),
0241             QLatin1String("categoryCombo"),
0242             QLatin1String("costCenterCombo"),
0243             QLatin1String("tagContainer"),
0244             QLatin1String("statusCombo"),
0245             QLatin1String("memoEdit"),
0246             // the schedule options
0247             QLatin1String("weekendOptionCombo"),
0248             QLatin1String("estimateOption"),
0249             QLatin1String("variationEdit"),
0250             QLatin1String("lastDayInMonthOption"),
0251             QLatin1String("autoEnterOption"),
0252             QLatin1String("endSeriesOption"),
0253             QLatin1String("remainingEdit"),
0254             QLatin1String("finalPaymentDateEdit"),
0255             QLatin1String("buttonBox"),
0256         };
0257         q->setProperty("kmm_defaulttaborder", defaultTabOrder);
0258         q->setProperty("kmm_currenttaborder", KMyMoneyUtils::tabOrder(QLatin1String("scheduleTransactionEditor"), defaultTabOrder));
0259         // let the transaction editor part use the same tab order information
0260         transactionEditor->setProperty("kmm_currenttaborder", KMyMoneyUtils::tabOrder(QLatin1String("scheduleTransactionEditor"), defaultTabOrder));
0261 
0262         KMyMoneyUtils::setupTabOrder(q, q->property("kmm_currenttaborder").toStringList());
0263     }
0264 
0265     /**
0266      * Helper method to recalculate and update Transactions Remaining
0267      * when other values are changed
0268      */
0269     void updateTransactionsRemaining()
0270     {
0271         auto remain = m_schedule.transactionsRemaining();
0272         if (remain != ui->remainingEdit->value()) {
0273             QSignalBlocker blocked(ui->remainingEdit);
0274             ui->remainingEdit->setValue(remain);
0275         }
0276     }
0277 
0278     MyMoneyTransaction transaction() const
0279     {
0280         auto t = m_schedule.transaction();
0281 
0282         if (transactionEditor) {
0283             t = transactionEditor->transaction();
0284         }
0285 
0286         t.clearId();
0287         t.setEntryDate(QDate());
0288         return t;
0289     }
0290 
0291     void setScheduleOccurrencePeriod()
0292     {
0293         const auto row = ui->frequencyEdit->currentIndex();
0294         const auto idx = ui->frequencyEdit->model()->index(row, 0);
0295         const auto occurrence = idx.data(eMyMoney::Model::ScheduleFrequencyRole).value<Schedule::Occurrence>();
0296         m_schedule.setOccurrencePeriod(occurrence);
0297     }
0298 
0299     KEditScheduleDlg* q_ptr;
0300     Ui::KEditScheduleDlg* ui;
0301     Ui::KEditScheduleDlg* tabOrderUi;
0302     WidgetHintFrameCollection* m_frameCollection;
0303     KMyMoneyAccountCombo* m_accountCombo;
0304     KMyMoneyAccountCombo* m_categoryCombo;
0305     NewTransactionEditor* transactionEditor;
0306     MyMoneySchedule m_schedule;
0307     QWidgetList m_tabOrderWidgets;
0308     OccurrencesModel m_frequencyModel;
0309     PaymentMethodModel m_paymentMethodModel;
0310 };
0311 
0312 KEditScheduleDlg::KEditScheduleDlg(const MyMoneySchedule& schedule, QWidget* parent)
0313     : QDialog(parent)
0314     , d_ptr(new KEditScheduleDlgPrivate(this))
0315 {
0316     Q_D(KEditScheduleDlg);
0317     d->m_schedule = schedule;
0318     d->init();
0319     setSizeGripEnabled(true);
0320 
0321     connect(d->ui->remainingEdit, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [&](int value) {
0322         Q_D(KEditScheduleDlg);
0323         // Make sure the required fields are set
0324         d->m_schedule.setNextDueDate(d->transactionEditor->postDate());
0325         d->setScheduleOccurrencePeriod();
0326         d->m_schedule.setOccurrenceMultiplier(d->ui->frequencyNoEdit->value());
0327 
0328         QSignalBlocker blocked(d->ui->finalPaymentDateEdit);
0329         d->ui->finalPaymentDateEdit->setDate(d->m_schedule.dateAfter(value));
0330     });
0331 
0332     connect(d->ui->finalPaymentDateEdit, &KMyMoneyDateEdit::dateChanged, this, [&](const QDate& date) {
0333         Q_D(KEditScheduleDlg);
0334         // Make sure the required fields are set
0335         d->m_schedule.setNextDueDate(d->transactionEditor->postDate());
0336         d->setScheduleOccurrencePeriod();
0337         d->m_schedule.setOccurrenceMultiplier(d->ui->frequencyNoEdit->value());
0338 
0339         d->m_schedule.setEndDate(date);
0340         d->updateTransactionsRemaining();
0341     });
0342 
0343     connect(d->ui->frequencyEdit, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [&](int idx) {
0344         Q_D(KEditScheduleDlg);
0345         const auto model = d->ui->frequencyEdit->model();
0346         const auto paymentType = model->index(idx, 0).data(eMyMoney::Model::ScheduleFrequencyRole).value<eMyMoney::Schedule::Occurrence>();
0347 
0348         d->ui->endSeriesOption->setEnabled(paymentType != Schedule::Occurrence::Once);
0349         bool isEndSeries = d->ui->endSeriesOption->isChecked();
0350         if (isEndSeries)
0351             d->ui->endOptionsFrame->setEnabled(paymentType != Schedule::Occurrence::Once);
0352         switch (paymentType) {
0353         case Schedule::Occurrence::Daily:
0354         case Schedule::Occurrence::Weekly:
0355             d->ui->frequencyNoEdit->setEnabled(true);
0356             d->ui->lastDayInMonthOption->setEnabled(false);
0357             break;
0358 
0359         case Schedule::Occurrence::EveryHalfMonth:
0360         case Schedule::Occurrence::Monthly:
0361         case Schedule::Occurrence::Yearly:
0362             // Supports Frequency Number
0363             d->ui->frequencyNoEdit->setEnabled(true);
0364             d->ui->lastDayInMonthOption->setEnabled(true);
0365             break;
0366 
0367         default:
0368             // Multiplier is always 1
0369             d->ui->frequencyNoEdit->setEnabled(false);
0370             d->ui->frequencyNoEdit->setValue(1);
0371             d->ui->lastDayInMonthOption->setEnabled(true);
0372             break;
0373         }
0374         if (isEndSeries && (paymentType != Schedule::Occurrence::Once)) {
0375             // Changing the frequency changes the number
0376             // of remaining transactions
0377             d->m_schedule.setNextDueDate(d->transactionEditor->postDate());
0378             d->m_schedule.setOccurrenceMultiplier(d->ui->frequencyNoEdit->value());
0379             d->m_schedule.setOccurrencePeriod(paymentType);
0380             d->m_schedule.setEndDate(d->ui->finalPaymentDateEdit->date());
0381             d->updateTransactionsRemaining();
0382         }
0383     });
0384 
0385     connect(d->ui->frequencyNoEdit, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [&](int multiplier) {
0386         Q_D(KEditScheduleDlg);
0387         // Make sure the required fields are set
0388         auto oldOccurrenceMultiplier = d->m_schedule.occurrenceMultiplier();
0389         if (multiplier != oldOccurrenceMultiplier) {
0390             if (d->ui->endOptionsFrame->isEnabled()) {
0391                 d->m_schedule.setNextDueDate(d->transactionEditor->postDate());
0392                 d->m_schedule.setOccurrenceMultiplier(multiplier);
0393                 d->setScheduleOccurrencePeriod();
0394                 d->m_schedule.setEndDate(d->ui->finalPaymentDateEdit->date());
0395                 d->updateTransactionsRemaining();
0396             }
0397         }
0398     });
0399 
0400     connect(d->transactionEditor, &NewTransactionEditor::postDateChanged, this, [&](const QDate& date) {
0401         Q_D(KEditScheduleDlg);
0402         if (d->m_schedule.nextDueDate() != date) {
0403             if (d->ui->endOptionsFrame->isEnabled()) {
0404                 d->m_schedule.setNextDueDate(date);
0405                 d->m_schedule.setStartDate(date);
0406                 d->m_schedule.setOccurrenceMultiplier(d->ui->frequencyNoEdit->value());
0407 
0408                 d->setScheduleOccurrencePeriod();
0409 
0410                 d->m_schedule.setEndDate(d->ui->finalPaymentDateEdit->date());
0411                 d->updateTransactionsRemaining();
0412             }
0413         }
0414     });
0415 
0416     connect(d->ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
0417     connect(d->ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
0418     connect(d->ui->buttonBox, &QDialogButtonBox::helpRequested, this, [&]() {
0419         KHelpClient::invokeHelp("details.schedules.intro");
0420     });
0421 
0422     d->loadWidgets();
0423 
0424     const auto dateEdit = d->transactionEditor->findChild<QWidget*>("dateEdit");
0425     if (dateEdit) {
0426         connect(d->ui->lastDayInMonthOption, &QCheckBox::stateChanged, dateEdit, &QWidget::setDisabled);
0427         dateEdit->setDisabled(d->ui->lastDayInMonthOption->isChecked());
0428     }
0429 
0430     // update the widget hint frames
0431     d->updateState();
0432 
0433     // we delay setting up the tab order until we drop back to the event loop.
0434     // this will make sure that all widgets are visible and the tab order logic
0435     // works properly
0436     QMetaObject::invokeMethod(
0437         this,
0438         [&]() {
0439             Q_D(KEditScheduleDlg);
0440             d->setupTabOrder();
0441             // set focus to first tab field once we return to event loop
0442             const auto tabOrder = property("kmm_currenttaborder").toStringList();
0443             if (!tabOrder.isEmpty()) {
0444                 const auto focusWidget = findChild<QWidget*>(tabOrder.first());
0445                 if (focusWidget) {
0446                     QMetaObject::invokeMethod(focusWidget, "setFocus", Qt::QueuedConnection);
0447                 }
0448             }
0449         },
0450         Qt::QueuedConnection);
0451 }
0452 
0453 KEditScheduleDlg::~KEditScheduleDlg()
0454 {
0455     Q_D(KEditScheduleDlg);
0456     delete d;
0457 }
0458 
0459 void KEditScheduleDlg::accept()
0460 {
0461     Q_D(KEditScheduleDlg);
0462     // Force the focus to be on the OK button. This will trigger creation
0463     // of any unknown objects (payees, categories etc.)
0464     d->ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus();
0465 
0466     // only accept if the button is really still enabled. We could end
0467     // up here, if the user filled all fields, the focus is on the category
0468     // field, but the category is not yet existent. When the user presses the
0469     // OK button in this context, he will be asked if he wants to create
0470     // the category or not. In case he decides no, we end up here with no
0471     // category filled in, so we don't run through the final acceptance.
0472     if (d->ui->buttonBox->button(QDialogButtonBox::Ok)->isEnabled())
0473         QDialog::accept();
0474 }
0475 
0476 const MyMoneySchedule& KEditScheduleDlg::schedule()
0477 {
0478     Q_D(KEditScheduleDlg);
0479     auto t = d->transaction();
0480     if (d->m_schedule.nextDueDate() != t.postDate()) {
0481         d->m_schedule.setNextDueDate(t.postDate());
0482         d->m_schedule.setStartDate(t.postDate());
0483     }
0484     d->m_schedule.setTransaction(t);
0485     d->m_schedule.setName(d->ui->nameEdit->text());
0486     d->m_schedule.setFixed(!d->ui->estimateOption->isChecked());
0487 
0488     auto model = d->ui->frequencyEdit->model();
0489     const auto frequency =
0490         model->index(d->ui->frequencyEdit->currentIndex(), 0).data(eMyMoney::Model::ScheduleFrequencyRole).value<eMyMoney::Schedule::Occurrence>();
0491     d->m_schedule.setOccurrencePeriod(frequency);
0492     d->m_schedule.setOccurrenceMultiplier(d->ui->frequencyNoEdit->value());
0493 
0494     switch (d->ui->weekendOptionCombo->currentIndex()) {
0495     case 0:
0496         d->m_schedule.setWeekendOption(Schedule::WeekendOption::MoveNothing);
0497         break;
0498     case 1:
0499         d->m_schedule.setWeekendOption(Schedule::WeekendOption::MoveBefore);
0500         break;
0501     case 2:
0502         d->m_schedule.setWeekendOption(Schedule::WeekendOption::MoveAfter);
0503         break;
0504     }
0505 
0506     d->m_schedule.setType(Schedule::Type::Bill);
0507 
0508     const auto amount = d->transactionEditor->transactionAmount();
0509     if (d->m_schedule.transaction().splitCount() == 2) {
0510         const auto splits = d->m_schedule.transaction().splits();
0511         bool isTransfer = true;
0512         for (const auto& split : splits) {
0513             const auto idx = MyMoneyFile::instance()->accountsModel()->indexById(split.accountId());
0514             if (idx.data(eMyMoney::Model::AccountIsAssetLiabilityRole).toBool() == false) {
0515                 isTransfer = false;
0516                 break;
0517             }
0518         }
0519         if (isTransfer) {
0520             d->m_schedule.setType(Schedule::Type::Transfer);
0521         }
0522     }
0523 
0524     if (d->m_schedule.type() != Schedule::Type::Transfer) {
0525         if (!amount.isNegative()) {
0526             d->m_schedule.setType(Schedule::Type::Deposit);
0527         }
0528     }
0529 
0530     if (d->ui->lastDayInMonthOption->isEnabled())
0531         d->m_schedule.setLastDayInMonth(d->ui->lastDayInMonthOption->isChecked());
0532     else
0533         d->m_schedule.setLastDayInMonth(false);
0534     d->m_schedule.setAutoEnter(d->ui->autoEnterOption->isChecked());
0535 
0536     model = d->ui->paymentMethodCombo->model();
0537     const auto paymentType =
0538         model->index(d->ui->paymentMethodCombo->currentIndex(), 0).data(eMyMoney::Model::SchedulePaymentTypeRole).value<eMyMoney::Schedule::PaymentType>();
0539     d->m_schedule.setPaymentType(paymentType);
0540     if (d->ui->endSeriesOption->isEnabled() && d->ui->endSeriesOption->isChecked()) {
0541         d->m_schedule.setEndDate(d->ui->finalPaymentDateEdit->date());
0542     } else {
0543         d->m_schedule.setEndDate(QDate());
0544     }
0545 
0546     d->m_schedule.setKeepMultiCurrencyAmount(d->ui->keepMultiCurrencyAmount->isEnabled() && d->ui->keepMultiCurrencyAmount->isChecked());
0547     return d->m_schedule;
0548 }
0549 
0550 void KEditScheduleDlg::newSchedule(const MyMoneyTransaction& _t, eMyMoney::Schedule::Occurrence occurrence)
0551 {
0552     MyMoneySchedule schedule;
0553     schedule.setOccurrence(occurrence);
0554 
0555     // if the schedule is based on an existing transaction,
0556     // we take the post date and project it to the next
0557     // schedule in a month.
0558     if (_t != MyMoneyTransaction()) {
0559         MyMoneyTransaction t(_t);
0560         schedule.setTransaction(t);
0561         if (occurrence != eMyMoney::Schedule::Occurrence::Once)
0562             schedule.setNextDueDate(schedule.nextPayment(t.postDate()));
0563     }
0564 
0565     bool committed;
0566     do {
0567         committed = true;
0568         QPointer<KEditScheduleDlg> dlg = new KEditScheduleDlg(schedule, nullptr);
0569         KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart());
0570         if (dlg->exec() == QDialog::Accepted && dlg != 0) {
0571             MyMoneyFileTransaction ft;
0572             try {
0573                 schedule = dlg->schedule();
0574                 MyMoneyFile::instance()->addSchedule(schedule);
0575                 ft.commit();
0576 
0577             } catch (const MyMoneyException& e) {
0578                 KMessageBox::error(nullptr, i18n("Unable to add scheduled transaction: %1", QString::fromLatin1(e.what())), i18n("Add scheduled transaction"));
0579                 committed = false;
0580             }
0581         }
0582         // delete transactionEditor;
0583         delete dlg;
0584     } while (!committed);
0585 }
0586 
0587 void KEditScheduleDlg::editSchedule(const MyMoneySchedule& inputSchedule)
0588 {
0589     try {
0590         auto schedule = MyMoneyFile::instance()->schedule(inputSchedule.id());
0591 
0592         switch (schedule.type()) {
0593         case eMyMoney::Schedule::Type::Bill:
0594         case eMyMoney::Schedule::Type::Deposit:
0595         case eMyMoney::Schedule::Type::Transfer: {
0596             QScopedPointer<KEditScheduleDlg> dlg(new KEditScheduleDlg(schedule, nullptr));
0597             KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg.data(), !KMyMoneySettings::stringMatchFromStart());
0598             if (dlg->exec() == QDialog::Accepted && dlg != 0) {
0599                 MyMoneyFileTransaction ft;
0600                 try {
0601                     MyMoneySchedule sched = dlg->schedule();
0602                     // Check whether the new Schedule Date
0603                     // is at or before the lastPaymentDate
0604                     // If it is, ask the user whether to clear the
0605                     // lastPaymentDate
0606                     const auto& next = sched.nextDueDate();
0607                     const auto& last = sched.lastPayment();
0608                     if (next.isValid() && last.isValid() && next <= last) {
0609                         // Entered a date effectively no later
0610                         // than previous payment.  Date would be
0611                         // updated automatically so we probably
0612                         // want to clear it.  Let's ask the user.
0613                         QString questionText = i18n(
0614                             "<qt>You have entered a scheduled transaction date of <b>%1</b>.  Because the scheduled transaction was last paid on <b>%2</b>, "
0615                             "KMyMoney will automatically adjust the scheduled transaction date to the next date unless the last payment date is reset.  Do you "
0616                             "want to reset the last payment date?</qt>",
0617                             MyMoneyUtils::formatDate(next),
0618                             MyMoneyUtils::formatDate(last));
0619                         if (KMessageBox::questionTwoActions(nullptr, questionText, i18n("Reset Last Payment Date"), KMMYesNo::yes(), KMMYesNo::no())
0620                             == KMessageBox::PrimaryAction) {
0621                             sched.setLastPayment(QDate());
0622                         }
0623                     }
0624                     MyMoneyFile::instance()->modifySchedule(sched);
0625                     ft.commit();
0626                 } catch (const MyMoneyException& e) {
0627                     KMessageBox::detailedError(nullptr,
0628                                                i18n("Unable to modify scheduled transaction '%1'", inputSchedule.name()),
0629                                                QString::fromLatin1(e.what()));
0630                 }
0631             }
0632             break;
0633         }
0634         case eMyMoney::Schedule::Type::LoanPayment: {
0635             QScopedPointer<KEditLoanWizard> dlg(new KEditLoanWizard(schedule.account(2)));
0636             if (dlg->exec() == QDialog::Accepted) {
0637                 MyMoneyFileTransaction ft;
0638                 try {
0639                     MyMoneyFile::instance()->modifySchedule(dlg->schedule());
0640                     MyMoneyFile::instance()->modifyAccount(dlg->account());
0641                     ft.commit();
0642                 } catch (const MyMoneyException& e) {
0643                     KMessageBox::detailedError(nullptr,
0644                                                i18n("Unable to modify scheduled transaction '%1'", inputSchedule.name()),
0645                                                QString::fromLatin1(e.what()));
0646                 }
0647             }
0648             break;
0649         }
0650         case eMyMoney::Schedule::Type::Any:
0651             break;
0652         }
0653     } catch (const MyMoneyException& e) {
0654         KMessageBox::detailedError(nullptr, i18n("Unable to modify scheduled transaction '%1'", inputSchedule.name()), QString::fromLatin1(e.what()));
0655     }
0656 }
0657 
0658 void KEditScheduleDlg::keyPressEvent(QKeyEvent* event)
0659 {
0660     const auto keySeq = QKeySequence(event->modifiers() | event->key());
0661 
0662     if (keySeq.matches(pActions[eMenu::Action::EditTabOrder]->shortcut())) {
0663         QPointer<TabOrderDialog> tabOrderDialog = new TabOrderDialog(this);
0664         auto tabOrderWidget = static_cast<TabOrderEditorInterface*>(qt_metacast("TabOrderEditorInterface"));
0665         if (tabOrderWidget) {
0666             tabOrderDialog->setTarget(tabOrderWidget);
0667 
0668             // the account combo is created invisible by the TransactionEditor ctor. Since we
0669             // need it in the schedule dialog editor, we make sure to see it before we start
0670             // to setup the tab order and modify it. At the same time, we need to hide the
0671             // additional buttons provided by the TransactionEditor.
0672             auto w = tabOrderDialog->findChild<QWidget*>("accountCombo");
0673             if (w) {
0674                 w->setVisible(true);
0675             }
0676             const QStringList buttonNames{QLatin1String("enterButton"), QLatin1String("cancelButton")};
0677             for (const auto& widgetName : buttonNames) {
0678                 w = tabOrderDialog->findChild<QWidget*>(widgetName);
0679                 if (w) {
0680                     w->setVisible(false);
0681                 }
0682             }
0683 
0684             auto tabOrder = property("kmm_defaulttaborder").toStringList();
0685             tabOrderDialog->setDefaultTabOrder(tabOrder);
0686             tabOrder = property("kmm_currenttaborder").toStringList();
0687             tabOrderDialog->setTabOrder(tabOrder);
0688 
0689             if ((tabOrderDialog->exec() == QDialog::Accepted) && tabOrderDialog) {
0690                 Q_D(KEditScheduleDlg);
0691                 tabOrderWidget->storeTabOrder(tabOrderDialog->tabOrder());
0692                 d->setupTabOrder();
0693             }
0694         }
0695         tabOrderDialog->deleteLater();
0696     }
0697 }
0698 
0699 void KEditScheduleDlg::setupUi(QWidget* parent)
0700 {
0701     Q_D(KEditScheduleDlg);
0702     if (d->tabOrderUi == nullptr) {
0703         d->tabOrderUi = new Ui::KEditScheduleDlg;
0704     }
0705     d->tabOrderUi->setupUi(parent);
0706 }
0707 
0708 void KEditScheduleDlg::storeTabOrder(const QStringList& tabOrder)
0709 {
0710     KMyMoneyUtils::storeTabOrder(QLatin1String("scheduleTransactionEditor"), tabOrder);
0711 }
0712 
0713 bool KEditScheduleDlg::focusNextPrevChild(bool next)
0714 {
0715     auto rc = KMyMoneyUtils::tabFocusHelper(this, next);
0716 
0717     if (rc == false) {
0718         // In case we're going backward from the buttonbox at the bottom,
0719         // the default focusNextPrevChild() implementation sets the focus
0720         // to the account combo box instead of the last option. We catch
0721         // this here and set the focus accordingly.
0722         const auto pushButton = focusWidget();
0723         const auto dialogButtonBox = focusWidget()->parentWidget();
0724         if (pushButton->qt_metacast("QPushButton") && dialogButtonBox->qt_metacast("QDialogButtonBox") && (!next)) {
0725             if (dialogButtonBox->nextInFocusChain() == pushButton) {
0726                 const auto widgetName = focusWidget()->parentWidget()->objectName();
0727                 const QStringList tabOrder(property("kmm_currenttaborder").toStringList());
0728                 auto idx = tabOrder.indexOf(widgetName);
0729                 if (idx >= 0) {
0730                     QWidget* w = nullptr;
0731                     auto previousTabWidget = [&]() {
0732                         --idx;
0733                         if (idx < 0) {
0734                             idx = tabOrder.count() - 1;
0735                         }
0736                         w = findChild<QWidget*>(tabOrder.at(idx));
0737                     };
0738                     previousTabWidget();
0739                     while (w && !w->isEnabled()) {
0740                         previousTabWidget();
0741                     }
0742                     if (w) {
0743                         w->setFocus();
0744                         return true;
0745                     }
0746                 }
0747             }
0748         }
0749         rc = QWidget::focusNextPrevChild(next);
0750     }
0751     return rc;
0752 }
0753 
0754 #include "keditscheduledlg.moc"