File indexing completed on 2024-05-12 16:44:02

0001 /*
0002     SPDX-FileCopyrightText: 2000-2003 Michael Edwardes <mte@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2001 Felix Rodriguez <frodriguez@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "kmymoneydateinput.h"
0009 #include "kmymoneysettings.h"
0010 
0011 // ----------------------------------------------------------------------------
0012 // QT Includes
0013 
0014 #include <QPoint>
0015 #include <QApplication>
0016 #include <QDesktopWidget>
0017 #include <QTimer>
0018 #include <QLabel>
0019 #include <QKeyEvent>
0020 #include <QEvent>
0021 #include <QDateEdit>
0022 #include <QLineEdit>
0023 #include <QPushButton>
0024 #include <QIcon>
0025 #include <QVBoxLayout>
0026 
0027 // ----------------------------------------------------------------------------
0028 // KDE Includes
0029 
0030 #include <KLocalizedString>
0031 #include <KDatePicker>
0032 
0033 // ----------------------------------------------------------------------------
0034 // Project Includes
0035 
0036 #include "icons.h"
0037 #include "popuppositioner.h"
0038 
0039 using namespace Icons;
0040 
0041 namespace
0042 {
0043 const QDate INVALID_DATE = QDate(1800, 1, 1);
0044 }
0045 
0046 KMyMoney::OldDateEdit::OldDateEdit(const QDate& date, QWidget* parent)
0047     : QDateEdit(date, parent)
0048     , m_initialSection(QDateTimeEdit::DaySection)
0049     , m_initStage(Created)
0050 {
0051 }
0052 
0053 void KMyMoney::OldDateEdit::keyPressEvent(QKeyEvent* k)
0054 {
0055     if ((lineEdit()->text().isEmpty() || lineEdit()->selectedText() == lineEdit()->text()) && QChar(k->key()).isDigit()) {
0056         // the line edit is empty which means that the date was cleared
0057         // or the whole text is selected and a digit character was entered
0058         // (the same meaning as clearing the date) - in this case set the date
0059         // to the current date and let the editor do the actual work
0060         setDate(QDate::currentDate());
0061         setSelectedSection(m_initialSection); // start as when focused in if the date was cleared
0062     }
0063     QDateEdit::keyPressEvent(k);
0064 }
0065 
0066 void KMyMoney::OldDateEdit::focusInEvent(QFocusEvent * event)
0067 {
0068     QDateEdit::focusInEvent(event);
0069     setSelectedSection(m_initialSection);
0070 }
0071 
0072 bool KMyMoney::OldDateEdit::event(QEvent* e)
0073 {
0074     // make sure that we keep the current date setting of a KMyMoneyDateInput object
0075     // across the QDateEdit::event(FocusOutEvent)
0076     bool rc;
0077 
0078     KMyMoneyDateInput* p = dynamic_cast<KMyMoneyDateInput*>(parentWidget());
0079     if (e->type() == QEvent::FocusOut && p) {
0080         QDate d = p->date();
0081         rc = QDateEdit::event(e);
0082         if (d.isValid())
0083             d = p->date();
0084         p->loadDate(d);
0085     } else {
0086         rc = QDateEdit::event(e);
0087     }
0088     switch (m_initStage) {
0089     case Created:
0090         if (e->type() == QEvent::FocusIn) {
0091             m_initStage = GotFocus;
0092         }
0093         break;
0094     case GotFocus:
0095         if (e->type() == QEvent::MouseButtonPress) {
0096             // create a phony corresponding release event
0097             QMouseEvent* mev = static_cast<QMouseEvent*>(e);
0098             QMouseEvent release(QEvent::MouseButtonRelease,
0099                                 mev->localPos(),
0100                                 mev->windowPos(),
0101                                 mev->screenPos(),
0102                                 mev->button(),
0103                                 mev->buttons(),
0104                                 mev->modifiers(),
0105                                 mev->source());
0106             QApplication::sendEvent(this, &release);
0107             m_initStage = FirstMousePress;
0108         }
0109         break;
0110     case FirstMousePress:
0111         break;
0112     }
0113     return rc;
0114 }
0115 
0116 bool KMyMoney::OldDateEdit::focusNextPrevChild(bool next)
0117 {
0118     Q_UNUSED(next)
0119     return true;
0120 }
0121 
0122 struct KMyMoneyDateInput::Private {
0123     KMyMoney::OldDateEdit *m_dateEdit;
0124     KDatePicker *m_datePicker;
0125     QDate m_date;
0126     QDate m_prevDate;
0127     Qt::AlignmentFlag m_qtalignment;
0128     QWidget *m_dateFrame;
0129     QPushButton *m_dateButton;
0130 };
0131 
0132 KMyMoneyDateInput::KMyMoneyDateInput(QWidget *parent, Qt::AlignmentFlag flags)
0133     : QWidget(parent), d(new Private)
0134 {
0135     d->m_qtalignment = flags;
0136     d->m_date = QDate::currentDate();
0137 
0138     QHBoxLayout *dateInputLayout = new QHBoxLayout(this);
0139     dateInputLayout->setSpacing(0);
0140     dateInputLayout->setContentsMargins(0, 0, 0, 0);
0141     d->m_dateEdit = new KMyMoney::OldDateEdit(d->m_date, this);
0142     dateInputLayout->addWidget(d->m_dateEdit, 3);
0143     setFocusProxy(d->m_dateEdit);
0144     d->m_dateEdit->installEventFilter(this); // To get d->m_dateEdit's FocusIn/Out and some KeyPress events
0145 
0146     // we use INVALID_DATE as a special value for multi transaction editing
0147     d->m_dateEdit->setMinimumDate(INVALID_DATE);
0148     d->m_dateEdit->setSpecialValueText(QLatin1String(" "));
0149 
0150     d->m_dateFrame = new QWidget(this);
0151     dateInputLayout->addWidget(d->m_dateFrame);
0152     QVBoxLayout *dateFrameVBoxLayout = new QVBoxLayout(d->m_dateFrame);
0153     dateFrameVBoxLayout->setMargin(0);
0154     dateFrameVBoxLayout->setContentsMargins(0, 0, 0, 0);
0155     d->m_dateFrame->setWindowFlags(Qt::Popup);
0156     d->m_dateFrame->hide();
0157 
0158     d->m_dateEdit->setDisplayFormat(QLocale().dateFormat(QLocale::ShortFormat));
0159     switch(KMyMoneySettings::initialDateFieldCursorPosition()) {
0160     case KMyMoneySettings::Day:
0161         d->m_dateEdit->setInitialSection(QDateTimeEdit::DaySection);
0162         break;
0163     case KMyMoneySettings::Month:
0164         d->m_dateEdit->setInitialSection(QDateTimeEdit::MonthSection);
0165         break;
0166     case KMyMoneySettings::Year:
0167         d->m_dateEdit->setInitialSection(QDateTimeEdit::YearSection);
0168         break;
0169     }
0170 
0171     d->m_datePicker = new KDatePicker(d->m_date, d->m_dateFrame);
0172     dateFrameVBoxLayout->addWidget(d->m_datePicker);
0173     // Let the date picker have a close button (Added in 3.1)
0174     d->m_datePicker->setCloseButton(true);
0175 
0176     // the next line is a try to add an icon to the button
0177     d->m_dateButton = new QPushButton(Icons::get(Icon::CalendarDay), QString(), this);
0178     dateInputLayout->addWidget(d->m_dateButton);
0179 
0180     connect(d->m_dateButton, &QAbstractButton::clicked, this, &KMyMoneyDateInput::toggleDatePicker);
0181     connect(d->m_dateEdit, &QDateTimeEdit::dateChanged, this, &KMyMoneyDateInput::slotDateChosenRef);
0182     connect(d->m_datePicker, &KDatePicker::dateSelected, this, &KMyMoneyDateInput::slotDateChosen);
0183     connect(d->m_datePicker, &KDatePicker::dateEntered, this, &KMyMoneyDateInput::slotDateChosen);
0184     connect(d->m_datePicker, &KDatePicker::dateSelected, d->m_dateFrame, &QWidget::hide);
0185 }
0186 
0187 void KMyMoneyDateInput::markAsBadDate(bool bad, const QColor& color)
0188 {
0189     // the next line knows a bit about the internals of QAbstractSpinBox
0190     QLineEdit* le = d->m_dateEdit->findChild<QLineEdit *>(); //krazy:exclude=qclasses
0191 
0192     if (le) {
0193         QPalette palette = this->palette();
0194         le->setPalette(palette);
0195         if (bad) {
0196             palette.setColor(foregroundRole(), color);
0197             le->setPalette(palette);
0198         }
0199     }
0200 }
0201 
0202 void KMyMoneyDateInput::showEvent(QShowEvent* event)
0203 {
0204     // don't forget the standard behaviour  ;-)
0205     QWidget::showEvent(event);
0206 
0207     // If the widget is shown, the size must be fixed a little later
0208     // to be appropriate. I saw this in some other places and the only
0209     // way to solve this problem is to postpone the setup of the size
0210     // to the time when the widget is on the screen.
0211     QTimer::singleShot(50, this, SLOT(fixSize()));
0212 }
0213 
0214 void KMyMoneyDateInput::fixSize()
0215 {
0216     // According to a hint in the documentation of KDatePicker::sizeHint()
0217     // 28 pixels should be added in each direction to obtain a better
0218     // display of the month button. I decided, (22,14) is good
0219     // enough and save some space on the screen (ipwizard)
0220     d->m_dateFrame->setFixedSize(d->m_datePicker->sizeHint() + QSize(22, 14));
0221 }
0222 
0223 KMyMoneyDateInput::~KMyMoneyDateInput()
0224 {
0225     delete d->m_dateFrame;
0226     delete d;
0227 }
0228 
0229 void KMyMoneyDateInput::toggleDatePicker()
0230 {
0231     if (d->m_dateFrame->isVisible()) {
0232         d->m_dateFrame->hide();
0233     } else {
0234         PopupPositioner pos(d->m_dateButton, d->m_dateFrame, PopupPositioner::BottomRight);
0235         if (d->m_date.isValid() && d->m_date != INVALID_DATE) {
0236             d->m_datePicker->setDate(d->m_date);
0237         } else {
0238             d->m_datePicker->setDate(QDate::currentDate());
0239         }
0240         d->m_dateFrame->show();
0241     }
0242 }
0243 
0244 
0245 /** Overriding QWidget::keyPressEvent
0246   *
0247   * increments/decrements the date upon +/- or Up/Down key input
0248   * sets the date to current date when the 'T' key is pressed
0249   */
0250 void KMyMoneyDateInput::keyPressEvent(QKeyEvent * k)
0251 {
0252     QKeySequence today(i18nc("Enter todays date into date input widget", "T"));
0253 
0254     auto adjustDateSection = [&](int offset) {
0255         switch(d->m_dateEdit->currentSection()) {
0256         case QDateTimeEdit::DaySection:
0257             slotDateChosen(d->m_date.addDays(offset));
0258             break;
0259         case QDateTimeEdit::MonthSection:
0260             slotDateChosen(d->m_date.addMonths(offset));
0261             break;
0262         case QDateTimeEdit::YearSection:
0263             slotDateChosen(d->m_date.addYears(offset));
0264             break;
0265         default:
0266             break;
0267         }
0268     };
0269 
0270     switch (k->key()) {
0271     case Qt::Key_Equal:
0272     case Qt::Key_Plus:
0273         adjustDateSection(1);
0274         k->accept();
0275         break;
0276 
0277     case Qt::Key_Minus:
0278         adjustDateSection(-1);
0279         k->accept();
0280         break;
0281 
0282     default:
0283         if (today == QKeySequence(k->key()) || k->key() == Qt::Key_T) {
0284             slotDateChosen(QDate::currentDate());
0285             k->accept();
0286         }
0287         break;
0288     }
0289     k->ignore(); // signal that the key event was not handled
0290 }
0291 
0292 /**
0293   * This function receives all events that are sent to focusWidget().
0294   * Some KeyPress events are intercepted and passed to keyPressEvent.
0295   * Otherwise they would be consumed by QDateEdit.
0296   */
0297 bool KMyMoneyDateInput::eventFilter(QObject *, QEvent *e)
0298 {
0299     if (e->type() == QEvent::KeyPress) {
0300         if (QKeyEvent *k = dynamic_cast<QKeyEvent*>(e)) {
0301             keyPressEvent(k);
0302             if (k->isAccepted())
0303                 return true; // signal that the key event was handled
0304         }
0305     }
0306 
0307     return false; // Don't filter the event
0308 }
0309 
0310 void KMyMoneyDateInput::slotDateChosenRef(const QDate& date)
0311 {
0312     if (date.isValid()) {
0313         emit dateChanged(date);
0314         d->m_date = date;
0315     }
0316 }
0317 
0318 void KMyMoneyDateInput::slotDateChosen(QDate date)
0319 {
0320     if (date.isValid()) {
0321         // the next line implies a call to slotDateChosenRef() above
0322         d->m_dateEdit->setDate(date);
0323     } else {
0324         d->m_dateEdit->setDate(INVALID_DATE);
0325     }
0326 }
0327 
0328 QDate KMyMoneyDateInput::date() const
0329 {
0330     QDate rc = d->m_dateEdit->date();
0331     if (rc == INVALID_DATE)
0332         rc = QDate();
0333     return rc;
0334 }
0335 
0336 void KMyMoneyDateInput::setDate(QDate date)
0337 {
0338     slotDateChosen(date);
0339 }
0340 
0341 void KMyMoneyDateInput::loadDate(const QDate& date)
0342 {
0343     d->m_date = d->m_prevDate = date;
0344 
0345     blockSignals(true);
0346     slotDateChosen(date);
0347     blockSignals(false);
0348 }
0349 
0350 void KMyMoneyDateInput::resetDate()
0351 {
0352     setDate(d->m_prevDate);
0353 }
0354 
0355 void KMyMoneyDateInput::setMaximumDate(const QDate& max)
0356 {
0357     d->m_dateEdit->setMaximumDate(max);
0358 }
0359 
0360 QWidget* KMyMoneyDateInput::focusWidget() const
0361 {
0362     QWidget* w = d->m_dateEdit;
0363     while (w->focusProxy())
0364         w = w->focusProxy();
0365     return w;
0366 }
0367 /*
0368 void KMyMoneyDateInput::setRange(const QDate & min, const QDate & max)
0369 {
0370   d->m_dateEdit->setDateRange(min, max);
0371 }
0372 */