Warning, file /office/skrooge/skgbasegui/kdateedit.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002   This file is part of libkdepim.
0003 
0004   Copyright (c) 2002 Cornelius Schumacher <schumacher@kde.org>
0005   Copyright (c) 2002 David Jarvie <software@astrojar.org.uk>
0006   Copyright (c) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
0007   Copyright (c) 2004 Tobias Koenig <tokoe@kde.org>
0008 
0009   This library is free software; you can redistribute it and/or
0010   modify it under the terms of the GNU Library General Public
0011   License as published by the Free Software Foundation; either
0012 
0013   This library is distributed in the hope that it will be useful,
0014   Library General Public License for more details.
0015 
0016   You should have received a copy of the GNU Library General Public License
0017   along with this library; see the file COPYING.LIB.  If not, write to
0018   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0019   Boston, MA 02110-1301, USA.
0020 */
0021 
0022 // krazy:excludeall=qclasses as we want to subclass from QComboBox, not KComboBox
0023 
0024 #include "kdateedit.h"
0025 
0026 #include <klocalizedstring.h>
0027 
0028 #include <qabstractitemview.h>
0029 #include <qapplication.h>
0030 #include <qcompleter.h>
0031 #include <qdesktopwidget.h>
0032 #include <qevent.h>
0033 #include <qlineedit.h>
0034 #include <qscreen.h>
0035 #include <qvalidator.h>
0036 
0037 #include "kdatevalidator.h"
0038 
0039 using namespace KPIM;
0040 
0041 KDateEdit::KDateEdit(QWidget* iParent)
0042     : QComboBox(iParent), mReadOnly(false)
0043 {
0044     // need at least one entry for popup to work
0045     setMaxCount(1);
0046     setEditable(true);
0047 
0048     // Check if we can use the QLocale::ShortFormat
0049     mAlternativeDateFormatToUse = QLocale().dateFormat(QLocale::ShortFormat);
0050     if (!mAlternativeDateFormatToUse.contains(QStringLiteral("yyyy"))) {
0051         mAlternativeDateFormatToUse = mAlternativeDateFormatToUse.replace(QStringLiteral("yy"), QStringLiteral("yyyy"));
0052     }
0053 
0054     mDate = QDate::currentDate();
0055     QString today = QLocale().toString(mDate, mAlternativeDateFormatToUse);
0056 
0057     addItem(today);
0058     setCurrentIndex(0);
0059 
0060     connect(lineEdit(), &QLineEdit::returnPressed, this, &KDateEdit::lineEnterPressed);
0061     connect(this, &KDateEdit::editTextChanged, this, &KDateEdit::slotTextChanged);
0062 
0063     mPopup = new KDatePickerPopup(KDatePickerPopup::DatePicker | KDatePickerPopup::Words, QDate::currentDate(), this);
0064     mPopup->hide();
0065     mPopup->installEventFilter(this);
0066 
0067     connect(mPopup, &KDatePickerPopup::dateChanged, this, &KDateEdit::dateSelected);
0068 
0069     // handle keyword entry
0070     setupKeywords();
0071     lineEdit()->installEventFilter(this);
0072 
0073     auto newValidator = new KDateValidator(this);
0074     newValidator->setKeywords(mKeywordMap.keys());
0075     setValidator(newValidator);
0076 
0077     mTextChanged = false;
0078 }
0079 
0080 KDateEdit::~KDateEdit()
0081     = default;
0082 
0083 void KDateEdit::setDate(QDate iDate)
0084 {
0085     assignDate(iDate);
0086     updateView();
0087 }
0088 
0089 QDate KDateEdit::date() const
0090 {
0091     return mDate;
0092 }
0093 
0094 void KDateEdit::setReadOnly(bool readOnly)
0095 {
0096     mReadOnly = readOnly;
0097     lineEdit()->setReadOnly(readOnly);
0098 }
0099 
0100 bool KDateEdit::isReadOnly() const
0101 {
0102     return mReadOnly;
0103 }
0104 
0105 void KDateEdit::showPopup()
0106 {
0107     if (mReadOnly) {
0108         return;
0109     }
0110 
0111     QRect desk = QGuiApplication::primaryScreen()->geometry();
0112 
0113     QPoint popupPoint = mapToGlobal(QPoint(0, 0));
0114 
0115     int dateFrameHeight = mPopup->sizeHint().height();
0116     if (popupPoint.y() + height() + dateFrameHeight > desk.bottom()) {
0117         popupPoint.setY(popupPoint.y() - dateFrameHeight);
0118     } else {
0119         popupPoint.setY(popupPoint.y() + height());
0120     }
0121 
0122     int dateFrameWidth = mPopup->sizeHint().width();
0123     if (popupPoint.x() + dateFrameWidth > desk.right()) {
0124         popupPoint.setX(desk.right() - dateFrameWidth);
0125     }
0126 
0127     if (popupPoint.x() < desk.left()) {
0128         popupPoint.setX(desk.left());
0129     }
0130 
0131     if (popupPoint.y() < desk.top()) {
0132         popupPoint.setY(desk.top());
0133     }
0134 
0135     if (mDate.isValid()) {
0136         mPopup->setDate(mDate);
0137     } else {
0138         mPopup->setDate(QDate::currentDate());
0139     }
0140 
0141     mPopup->popup(popupPoint);
0142 
0143     // The combo box is now shown pressed. Make it show not pressed again
0144     // by causing its (invisible) list box to emit a 'selected' signal.
0145     // First, ensure that the list box contains the date currently displayed.
0146     QDate date2 = parseDate();
0147     assignDate(date2);
0148     updateView();
0149 
0150     // Now, simulate an Enter to unpress it
0151     QAbstractItemView* lb = view();
0152     if (lb != nullptr) {
0153         lb->setCurrentIndex(lb->model()->index(0, 0));
0154         QKeyEvent* keyEvent =
0155             new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier);
0156         QApplication::postEvent(lb, keyEvent);
0157     }
0158 }
0159 
0160 void KDateEdit::dateSelected(QDate iDate)
0161 {
0162     if (assignDate(iDate)) {
0163         updateView();
0164         emit dateChanged(iDate);
0165         emit dateEntered(iDate);
0166 
0167         if (iDate.isValid()) {
0168             mPopup->hide();
0169         }
0170     }
0171 }
0172 
0173 void KDateEdit::lineEnterPressed()
0174 {
0175     bool replaced = false;
0176 
0177     QDate date2 = parseDate(&replaced);
0178 
0179     if (assignDate(date2)) {
0180         if (replaced) {
0181             updateView();
0182         }
0183 
0184         emit dateChanged(date2);
0185         emit dateEntered(date2);
0186     }
0187 }
0188 
0189 QDate KDateEdit::parseDate(bool* replaced) const
0190 {
0191     QString text = currentText();
0192     QDate result;
0193 
0194     if (replaced != nullptr) {
0195         (*replaced) = false;
0196     }
0197 
0198     if (text.isEmpty()) {
0199         result = QDate();
0200     } else if (mKeywordMap.contains(text.toLower())) {
0201         QDate today = QDate::currentDate();
0202         int i = mKeywordMap[ text.toLower()];
0203         if (i == 30) {
0204             today = today.addMonths(1);
0205         } else if (i >= 100) {
0206             /* A day name has been entered. Convert to offset from today.
0207              * This uses some math tricks to figure out the offset in days
0208              * to the next date the given day of the week occurs. There
0209              * are two cases, that the new day is >= the current day, which means
0210              * the new day has not occurred yet or that the new day < the current day,
0211              * which means the new day is already passed (so we need to find the
0212              * day in the next week).
0213              */
0214             i -= 100;
0215             int currentDay = today.dayOfWeek();
0216             if (i >= currentDay) {
0217                 i -= currentDay;
0218             } else {
0219                 i += 7 - currentDay;
0220             }
0221         }
0222 
0223         result = today.addDays(i);
0224         if (replaced != nullptr) {
0225             (*replaced) = true;
0226         }
0227     } else {
0228         result = QLocale().toDate(text, mAlternativeDateFormatToUse);
0229     }
0230 
0231     return result;
0232 }
0233 
0234 void KDateEdit::focusOutEvent(QFocusEvent* e)
0235 {
0236     if (mTextChanged) {
0237         lineEnterPressed();
0238         mTextChanged = false;
0239     }
0240     QComboBox::focusOutEvent(e);
0241 }
0242 
0243 void KDateEdit::keyPressEvent(QKeyEvent* e)
0244 {
0245     QDate date2;
0246 
0247     if (!mReadOnly) {
0248         switch (e->key()) {
0249         case Qt::Key_Up:
0250             date2 = parseDate();
0251             if (!date2.isValid()) {
0252                 break;
0253             }
0254             if ((e->modifiers() & Qt::ControlModifier) != 0u) {  // Warning OSX: The KeypadModifier value will also be set when an arrow key is pressed as the arrow keys are considered part of the keypad.
0255                 date2 = date2.addMonths(1);
0256             } else {
0257                 date2 = date2.addDays(1);
0258             }
0259             break;
0260         case Qt::Key_Down:
0261             date2 = parseDate();
0262             if (!date2.isValid()) {
0263                 break;
0264             }
0265             if ((e->modifiers() & Qt::ControlModifier) != 0u) {  // Warning OSX: The KeypadModifier value will also be set when an arrow key is pressed as the arrow keys are considered part of the keypad.
0266                 date2 = date2.addMonths(-1);
0267             } else {
0268                 date2 = date2.addDays(-1);
0269             }
0270             break;
0271         case Qt::Key_PageUp:
0272             date2 = parseDate();
0273             if (!date2.isValid()) {
0274                 break;
0275             }
0276             date2 = date2.addMonths(1);
0277             break;
0278         case Qt::Key_PageDown:
0279             date2 = parseDate();
0280             if (!date2.isValid()) {
0281                 break;
0282             }
0283             date2 = date2.addMonths(-1);
0284             break;
0285         case Qt::Key_Equal:
0286             date2 = QDate::currentDate();
0287             break;
0288         default: {}
0289         }
0290 
0291         if (date2.isValid() && assignDate(date2)) {
0292             e->accept();
0293             updateView();
0294             emit dateChanged(date2);
0295             emit dateEntered(date2);
0296             return;
0297         }
0298     }
0299 
0300     QComboBox::keyPressEvent(e);
0301 }
0302 
0303 bool KDateEdit::eventFilter(QObject* iObject, QEvent* iEvent)
0304 {
0305     if (iObject == lineEdit()) {
0306         // We only process the focus out event if the text has changed
0307         // since we got focus
0308         if ((iEvent->type() == QEvent::FocusOut) && mTextChanged) {
0309             lineEnterPressed();
0310             mTextChanged = false;
0311         } else if (iEvent->type() == QEvent::KeyPress) {
0312             // Up and down arrow keys step the date
0313             auto* keyEvent = dynamic_cast<QKeyEvent*>(iEvent);
0314             if (keyEvent && (keyEvent && (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter))) {
0315                 lineEnterPressed();
0316                 return true;
0317             }
0318         }
0319     }
0320 
0321     return QComboBox::eventFilter(iObject, iEvent);
0322 }
0323 
0324 void KDateEdit::slotTextChanged(const QString& /*unused*/)
0325 {
0326     QDate date2 = parseDate();
0327 
0328     if (assignDate(date2)) {
0329         emit dateChanged(date2);
0330     }
0331 
0332     mTextChanged = true;
0333 }
0334 
0335 void KDateEdit::setupKeywords()
0336 {
0337     // Create the keyword list. This will be used to match against when the user
0338     // enters information.
0339     mKeywordMap.insert(i18nc("the day after today", "tomorrow"), 1);
0340     mKeywordMap.insert(i18nc("this day", "today"), 0);
0341     mKeywordMap.insert(i18nc("the day before today", "yesterday"), -1);
0342     mKeywordMap.insert(i18nc("the week after this week", "next week"), 7);
0343     mKeywordMap.insert(i18nc("the month after this month", "next month"), 30);
0344 
0345     QString dayName;
0346     for (int i = 1; i <= 7; ++i) {
0347         dayName = QLocale().dayName(i).toLower();
0348         mKeywordMap.insert(dayName, i + 100);
0349     }
0350 
0351     auto comp = new QCompleter(mKeywordMap.keys(), this);
0352     comp->setCaseSensitivity(Qt::CaseInsensitive);
0353     comp->setCompletionMode(QCompleter::InlineCompletion);
0354     setCompleter(comp);
0355 }
0356 
0357 bool KDateEdit::assignDate(QDate iDate)
0358 {
0359     mDate = iDate;
0360     mTextChanged = false;
0361     return true;
0362 }
0363 
0364 void KDateEdit::updateView()
0365 {
0366     QString dateString;
0367     if (mDate.isValid()) {
0368         dateString = QLocale().toString(mDate, mAlternativeDateFormatToUse);
0369     }
0370 
0371     // We do not want to generate a signal here,
0372     // since we explicitly setting the date
0373     bool blocked = signalsBlocked();
0374     blockSignals(true);
0375     removeItem(0);
0376     insertItem(0, dateString);
0377     blockSignals(blocked);
0378 }
0379 
0380