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