1 /*
2 * Copyright 2007-2012 Thomas Baumgart <tbaumgart@kde.org>
3 * Copyright 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of
8 * the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "kenterscheduledlg.h"
20
21 // ----------------------------------------------------------------------------
22 // QT Includes
23
24 #include <QTimer>
25 #include <QWidget>
26 #include <QLabel>
27 #include <QPushButton>
28 #include <QIcon>
29 #include <QWindow>
30
31 // ----------------------------------------------------------------------------
32 // KDE Includes
33
34 #include <KMessageBox>
35 #include <KHelpClient>
36 #include <KLocalizedString>
37 #include <KGuiItem>
38 #include <KStandardGuiItem>
39 #include <KSharedConfig>
40 #include <KWindowConfig>
41 #include <KConfigGroup>
42
43 // ----------------------------------------------------------------------------
44 // Project Includes
45
46 #include "ui_kenterscheduledlg.h"
47
48 #include "tabbar.h"
49 #include "mymoneysplit.h"
50 #include "mymoneytransaction.h"
51 #include "mymoneyfile.h"
52 #include "mymoneyaccount.h"
53 #include "mymoneymoney.h"
54 #include "mymoneyschedule.h"
55 #include "register.h"
56 #include "transactionform.h"
57 #include "transaction.h"
58 #include "selectedtransactions.h"
59 #include "transactioneditor.h"
60 #include "kmymoneyutils.h"
61 #include "kmymoneylineedit.h"
62 #include "kmymoneydateinput.h"
63 #include "knewaccountdlg.h"
64 #include "knewinvestmentwizard.h"
65 #include "mymoneyexception.h"
66 #include "icons/icons.h"
67 #include "mymoneyenums.h"
68 #include "dialogenums.h"
69 #include "widgetenums.h"
70
71 using namespace Icons;
72
73 class KEnterScheduleDlgPrivate
74 {
75 Q_DISABLE_COPY(KEnterScheduleDlgPrivate)
76
77 public:
KEnterScheduleDlgPrivate()78 KEnterScheduleDlgPrivate() :
79 ui(new Ui::KEnterScheduleDlg),
80 m_item(nullptr),
81 m_showWarningOnce(true),
82 m_extendedReturnCode(eDialogs::ScheduleResultCode::Cancel)
83 {
84 }
85
~KEnterScheduleDlgPrivate()86 ~KEnterScheduleDlgPrivate()
87 {
88 delete ui;
89 }
90
91 Ui::KEnterScheduleDlg *ui;
92 MyMoneySchedule m_schedule;
93 KMyMoneyRegister::Transaction* m_item;
94 QWidgetList m_tabOrderWidgets;
95 bool m_showWarningOnce;
96 eDialogs::ScheduleResultCode m_extendedReturnCode;
97 };
98
KEnterScheduleDlg(QWidget * parent,const MyMoneySchedule & schedule)99 KEnterScheduleDlg::KEnterScheduleDlg(QWidget *parent, const MyMoneySchedule& schedule) :
100 QDialog(parent),
101 d_ptr(new KEnterScheduleDlgPrivate)
102 {
103 Q_D(KEnterScheduleDlg);
104
105 // restore the last used dialog size
106 KConfigGroup grp = KSharedConfig::openConfig()->group("KEnterScheduleDlg");
107 if (grp.isValid()) {
108 KWindowConfig::restoreWindowSize(windowHandle(), grp);
109 }
110 // let the minimum size be 780x410
111 resize(QSize(780, 410).expandedTo(windowHandle() ? windowHandle()->size() : QSize()));
112
113 // position the dialog centered on the application (for some reason without
114 // a call to winId() the dialog is positioned in the upper left corner of
115 // the screen, but winId() crashes on MS-Windows ...
116 if (parent)
117 move(parent->pos() + QPoint(parent->width()/2, parent->height()/2) - QPoint(width()/2, height()/2));
118
119 d->ui->setupUi(this);
120 d->m_schedule = schedule;
121 d->m_extendedReturnCode = eDialogs::ScheduleResultCode::Enter;
122 d->ui->buttonOk->setIcon(Icons::get(Icon::KeyEnter));
123 d->ui->buttonSkip->setIcon(Icons::get(Icon::SeekForward));
124 KGuiItem::assign(d->ui->buttonCancel, KStandardGuiItem::cancel());
125 KGuiItem::assign(d->ui->buttonHelp, KStandardGuiItem::help());
126 d->ui->buttonIgnore->setHidden(true);
127 d->ui->buttonSkip->setHidden(true);
128
129 // make sure, we have a tabbar with the form
130 KMyMoneyTransactionForm::TabBar* tabbar = d->ui->m_form->getTabBar(d->ui->m_form->parentWidget());
131
132 // we never need to see the register
133 d->ui->m_register->hide();
134
135 // ... setup the form ...
136 d->ui->m_form->setupForm(d->m_schedule.account());
137
138 // ... and the register ...
139 d->ui->m_register->clear();
140
141 // ... now add the transaction to register and form ...
142 MyMoneyTransaction t = transaction();
143 d->m_item = KMyMoneyRegister::Register::transactionFactory(d->ui->m_register, t,
144 d->m_schedule.transaction().splits().isEmpty() ? MyMoneySplit() : d->m_schedule.transaction().splits().front(), 0);
145 d->ui->m_register->selectItem(d->m_item);
146 // show the account row
147 d->m_item->setShowRowInForm(0, true);
148
149 d->ui->m_form->slotSetTransaction(d->m_item);
150
151 // no need to see the tabbar
152 tabbar->hide();
153
154 // setup name and type
155 d->ui->m_scheduleName->setText(d->m_schedule.name());
156 d->ui->m_type->setText(KMyMoneyUtils::scheduleTypeToString(d->m_schedule.type()));
157
158 connect(d->ui->buttonHelp, &QAbstractButton::clicked, this, &KEnterScheduleDlg::slotShowHelp);
159 connect(d->ui->buttonIgnore, &QAbstractButton::clicked, this, &KEnterScheduleDlg::slotIgnore);
160 connect(d->ui->buttonSkip, &QAbstractButton::clicked, this, &KEnterScheduleDlg::slotSkip);
161 }
162
~KEnterScheduleDlg()163 KEnterScheduleDlg::~KEnterScheduleDlg()
164 {
165 Q_D(KEnterScheduleDlg);
166
167 // store the last used dialog size
168 KConfigGroup grp = KSharedConfig::openConfig()->group("KEnterScheduleDlg");
169 if (grp.isValid()) {
170 KWindowConfig::saveWindowSize(windowHandle(), grp);
171 }
172
173 delete d;
174 }
175
resultCode() const176 eDialogs::ScheduleResultCode KEnterScheduleDlg::resultCode() const
177 {
178 Q_D(const KEnterScheduleDlg);
179 if (result() == QDialog::Accepted)
180 return d->m_extendedReturnCode;
181 return eDialogs::ScheduleResultCode::Cancel;
182 }
183
showExtendedKeys(bool visible)184 void KEnterScheduleDlg::showExtendedKeys(bool visible)
185 {
186 Q_D(KEnterScheduleDlg);
187 d->ui->buttonIgnore->setVisible(visible);
188 d->ui->buttonSkip->setVisible(visible);
189 }
190
slotIgnore()191 void KEnterScheduleDlg::slotIgnore()
192 {
193 Q_D(KEnterScheduleDlg);
194 d->m_extendedReturnCode = eDialogs::ScheduleResultCode::Ignore;
195 accept();
196 }
197
slotSkip()198 void KEnterScheduleDlg::slotSkip()
199 {
200 Q_D(KEnterScheduleDlg);
201 d->m_extendedReturnCode = eDialogs::ScheduleResultCode::Skip;
202 accept();
203 }
204
transaction()205 MyMoneyTransaction KEnterScheduleDlg::transaction()
206 {
207 Q_D(KEnterScheduleDlg);
208 auto t = d->m_schedule.transaction();
209
210 try {
211 if (d->m_schedule.type() == eMyMoney::Schedule::Type::LoanPayment) {
212 KMyMoneyUtils::calculateAutoLoan(d->m_schedule, t, QMap<QString, MyMoneyMoney>());
213 }
214 } catch (const MyMoneyException &e) {
215 KMessageBox::detailedError(this, i18n("Unable to load schedule details"), QString::fromLatin1(e.what()));
216 }
217
218 t.clearId();
219 t.setEntryDate(QDate());
220 return t;
221 }
222
date(const QDate & _date) const223 QDate KEnterScheduleDlg::date(const QDate& _date) const
224 {
225 Q_D(const KEnterScheduleDlg);
226 auto date(_date);
227 return d->m_schedule.adjustedDate(date, d->m_schedule.weekendOption());
228 }
229
resizeEvent(QResizeEvent * ev)230 void KEnterScheduleDlg::resizeEvent(QResizeEvent* ev)
231 {
232 Q_UNUSED(ev)
233 Q_D(KEnterScheduleDlg);
234 d->ui->m_register->resize((int)eWidgets::eTransaction::Column::Detail);
235 d->ui->m_form->resize((int)eWidgets::eTransactionForm::Column::Value1);
236 QDialog::resizeEvent(ev);
237 }
238
slotSetupSize()239 void KEnterScheduleDlg::slotSetupSize()
240 {
241 resize(width(), minimumSizeHint().height());
242 }
243
exec()244 int KEnterScheduleDlg::exec()
245 {
246 Q_D(KEnterScheduleDlg);
247 if (d->m_showWarningOnce) {
248 d->m_showWarningOnce = false;
249 KMessageBox::information(parentWidget(), QString("<qt>") + i18n("<p>Please check that all the details in the following dialog are correct and press OK.</p><p>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)</p>") + QString("</qt>"), i18n("Enter scheduled transaction"), "EnterScheduleDlgInfo");
250 }
251
252 // force the initial height to be as small as possible
253 QTimer::singleShot(0, this, SLOT(slotSetupSize()));
254 return QDialog::exec();
255 }
256
startEdit()257 TransactionEditor* KEnterScheduleDlg::startEdit()
258 {
259 Q_D(KEnterScheduleDlg);
260 KMyMoneyRegister::SelectedTransactions list(d->ui->m_register);
261 auto editor = d->m_item->createEditor(d->ui->m_form, list, QDate());
262 if (editor) {
263 editor->setScheduleInfo(d->m_schedule.name());
264 editor->setPaymentMethod(d->m_schedule.paymentType());
265 }
266
267 // check that we use the same transaction commodity in all selected transactions
268 // if not, we need to update this in the editor's list. The user can also bail out
269 // of this operation which means that we have to stop editing here.
270 if (editor) {
271 if (!editor->fixTransactionCommodity(d->m_schedule.account())) {
272 // if the user wants to quit, we need to destroy the editor
273 // and bail out
274 delete editor;
275 editor = 0;
276 }
277 }
278
279 if (editor) {
280 connect(editor, &TransactionEditor::transactionDataSufficient, d->ui->buttonOk, &QWidget::setEnabled);
281 connect(editor, &TransactionEditor::escapePressed, d->ui->buttonCancel, &QAbstractButton::animateClick);
282 connect(editor, &TransactionEditor::returnPressed, d->ui->buttonOk, &QAbstractButton::animateClick);
283
284 connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, editor, &TransactionEditor::slotReloadEditWidgets);
285 // connect(editor, SIGNAL(finishEdit(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotLeaveEditMode(KMyMoneyRegister::SelectedTransactions)));
286
287 // create the widgets, place them in the parent and load them with data
288 // setup tab order
289 d->m_tabOrderWidgets.clear();
290 eWidgets::eRegister::Action action = eWidgets::eRegister::Action::Withdrawal;
291 switch (d->m_schedule.type()) {
292 case eMyMoney::Schedule::Type::Transfer:
293 action = eWidgets::eRegister::Action::Transfer;
294 break;
295 case eMyMoney::Schedule::Type::Deposit:
296 action = eWidgets::eRegister::Action::Deposit;
297 break;
298 case eMyMoney::Schedule::Type::LoanPayment:
299 switch (d->m_schedule.paymentType()) {
300 case eMyMoney::Schedule::PaymentType::DirectDeposit:
301 case eMyMoney::Schedule::PaymentType::ManualDeposit:
302 action = eWidgets::eRegister::Action::Deposit;
303 break;
304 default:
305 break;
306 }
307 break;
308 default:
309 break;
310 }
311 editor->setup(d->m_tabOrderWidgets, d->m_schedule.account(), action);
312
313 MyMoneyTransaction t = d->m_schedule.transaction();
314 QString num = t.splits().first().number();
315 QWidget* w = editor->haveWidget("number");
316 if (d->m_schedule.paymentType() == eMyMoney::Schedule::PaymentType::WriteChecque) {
317 num = KMyMoneyUtils::nextFreeCheckNumber(d->m_schedule.account());
318 d->m_schedule.account().setValue("lastNumberUsed", num);
319 if (w)
320 if (auto numberWidget = dynamic_cast<KMyMoneyLineEdit*>(w))
321 numberWidget->loadText(num);
322 } else {
323 // if it's not a check, then we need to clear
324 // a possibly assigned check number
325 if (w)
326 if (auto numberWidget = dynamic_cast<KMyMoneyLineEdit*>(w))
327 numberWidget->loadText(QString());
328 }
329
330 Q_ASSERT(!d->m_tabOrderWidgets.isEmpty());
331
332 // editor->setup() leaves the tabbar as the last widget in the stack, but we
333 // need it as first here. So we move it around.
334 w = editor->haveWidget("tabbar");
335 if (w) {
336 int idx = d->m_tabOrderWidgets.indexOf(w);
337 if (idx != -1) {
338 d->m_tabOrderWidgets.removeAt(idx);
339 d->m_tabOrderWidgets.push_front(w);
340 }
341 }
342
343 // don't forget our three buttons
344 d->m_tabOrderWidgets.append(d->ui->buttonOk);
345 d->m_tabOrderWidgets.append(d->ui->buttonCancel);
346 d->m_tabOrderWidgets.append(d->ui->buttonHelp);
347
348 for (auto i = 0; i < d->m_tabOrderWidgets.size(); ++i) {
349 w = d->m_tabOrderWidgets.at(i);
350 if (w) {
351 w->installEventFilter(this);
352 w->installEventFilter(editor);
353 }
354 }
355 // Check if the editor has some preference on where to set the focus
356 // If not, set the focus to the first widget in the tab order
357 QWidget* focusWidget = editor->firstWidget();
358 if (!focusWidget)
359 focusWidget = d->m_tabOrderWidgets.first();
360 focusWidget->setFocus();
361
362 // Make sure, we use the adjusted date
363 if (auto dateEdit = dynamic_cast<KMyMoneyDateInput*>(editor->haveWidget("postdate")))
364 dateEdit->setDate(d->m_schedule.adjustedNextDueDate());
365 }
366
367 return editor;
368 }
369
focusNextPrevChild(bool next)370 bool KEnterScheduleDlg::focusNextPrevChild(bool next)
371 {
372 Q_D(KEnterScheduleDlg);
373 auto rc = false;
374
375 auto w = qApp->focusWidget();
376 int currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w);
377 while (w && currentWidgetIndex == -1) {
378 // qDebug("'%s' not in list, use parent", w->className());
379 w = w->parentWidget();
380 currentWidgetIndex = d->m_tabOrderWidgets.indexOf(w);
381 }
382
383 if (currentWidgetIndex != -1) {
384 // if(w) qDebug("tab order is at '%s'", w->className());
385 currentWidgetIndex += next ? 1 : -1;
386 if (currentWidgetIndex < 0)
387 currentWidgetIndex = d->m_tabOrderWidgets.size() - 1;
388 else if (currentWidgetIndex >= d->m_tabOrderWidgets.size())
389 currentWidgetIndex = 0;
390
391 w = d->m_tabOrderWidgets[currentWidgetIndex];
392 // qDebug("currentWidgetIndex = %d, w = %p", currentWidgetIndex, w);
393
394 if (((w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && w->isVisible() && w->isEnabled()) {
395 // qDebug("Selecting '%s' as focus", w->className());
396 w->setFocus(next ? Qt::TabFocusReason: Qt::BacktabFocusReason);
397 rc = true;
398 }
399 }
400 return rc;
401 }
402
slotShowHelp()403 void KEnterScheduleDlg::slotShowHelp()
404 {
405 KHelpClient::invokeHelp("details.schedules.entering");
406 }
407