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 */