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"