File indexing completed on 2024-04-28 15:32:00

0001 /*
0002     SPDX-FileCopyrightText: 2011 John Layt <john@layt.net>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "kdatecombobox.h"
0008 
0009 #include "common_helpers_p.h"
0010 #include "kdatepickerpopup.h"
0011 #include "kdaterangecontrol_p.h"
0012 
0013 #include <QAbstractItemView>
0014 #include <QApplication>
0015 #include <QDate>
0016 #include <QKeyEvent>
0017 #include <QLineEdit>
0018 #include <QMenu>
0019 #include <QScreen>
0020 #include <QVector>
0021 #include <QWidgetAction>
0022 
0023 #include "kdatepicker.h"
0024 #include "kmessagebox.h"
0025 
0026 class KDateComboBoxPrivate : public KDateRangeControlPrivate
0027 {
0028 public:
0029     KDateComboBoxPrivate(KDateComboBox *qq);
0030 
0031     // TODO: Find a way to get that from QLocale
0032 #if 0
0033     QDate defaultMinDate();
0034     QDate defaultMaxDate();
0035 #endif
0036 
0037     QString dateFormat(QLocale::FormatType format);
0038     QString formatDate(const QDate &date);
0039 
0040     void initDateWidget();
0041     void updateDateWidget();
0042     void setDateRange(const QDate &minDate, const QDate &maxDate, const QString &minWarnMsg, const QString &maxWarnMsg);
0043     using KDateRangeControlPrivate::setDateRange;
0044 
0045     void editDate(const QString &text);
0046     void enterDate(const QDate &date);
0047     void parseDate();
0048     void warnDate();
0049 
0050     KDateComboBox *const q;
0051     KDatePickerPopup *m_dateMenu;
0052 
0053     QDate m_date;
0054     KDateComboBox::Options m_options;
0055     QString m_minWarnMsg;
0056     QString m_maxWarnMsg;
0057     bool m_warningShown;
0058     bool m_edited; // and dateChanged not yet emitted
0059     QLocale::FormatType m_displayFormat;
0060 };
0061 
0062 KDateComboBoxPrivate::KDateComboBoxPrivate(KDateComboBox *qq)
0063     : q(qq)
0064     , m_dateMenu(new KDatePickerPopup(KDatePickerPopup::DatePicker | KDatePickerPopup::Words, QDate::currentDate(), qq))
0065     , m_warningShown(false)
0066     , m_edited(false)
0067     , m_displayFormat(QLocale::ShortFormat)
0068 {
0069     m_options = KDateComboBox::EditDate | KDateComboBox::SelectDate | KDateComboBox::DatePicker | KDateComboBox::DateKeywords;
0070     m_date = QDate::currentDate();
0071     // m_minDate = defaultMinDate();
0072     // m_maxDate = defaultMaxDate();
0073 }
0074 
0075 #if 0
0076 QDate KDateComboBoxPrivate::defaultMinDate()
0077 {
0078     return m_date.calendar()->earliestValidDate();
0079 }
0080 
0081 QDate KDateComboBoxPrivate::defaultMaxDate()
0082 {
0083     return m_date.calendar()->latestValidDate();
0084 }
0085 #endif
0086 
0087 QString KDateComboBoxPrivate::dateFormat(QLocale::FormatType format)
0088 {
0089     return dateFormatWith4DigitYear(q->locale(), format);
0090 }
0091 
0092 QString KDateComboBoxPrivate::formatDate(const QDate &date)
0093 {
0094     return q->locale().toString(date, dateFormat(m_displayFormat));
0095 }
0096 
0097 void KDateComboBoxPrivate::initDateWidget()
0098 {
0099     q->blockSignals(true);
0100     q->clear();
0101 
0102     // If EditTime then set the line edit
0103     q->lineEdit()->setReadOnly((m_options & KDateComboBox::EditDate) != KDateComboBox::EditDate);
0104 
0105     // If SelectDate then make list items visible
0106     if ((m_options & KDateComboBox::SelectDate) == KDateComboBox::SelectDate //
0107         || (m_options & KDateComboBox::DatePicker) == KDateComboBox::DatePicker //
0108         || (m_options & KDateComboBox::DatePicker) == KDateComboBox::DateKeywords) {
0109         q->setMaxVisibleItems(1);
0110     } else {
0111         q->setMaxVisibleItems(0);
0112     }
0113 
0114     q->setSizeAdjustPolicy(QComboBox::AdjustToContents);
0115     q->addItem(formatDate(m_date));
0116     q->setCurrentIndex(0);
0117     q->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow);
0118     q->blockSignals(false);
0119 
0120     KDatePickerPopup::Modes modes;
0121     if (m_options & KDateComboBox::DatePicker) {
0122         modes |= KDatePickerPopup::DatePicker;
0123     }
0124     if (m_options & KDateComboBox::DateKeywords) {
0125         modes |= KDatePickerPopup::Words;
0126     }
0127     m_dateMenu->setModes(modes);
0128 }
0129 
0130 void KDateComboBoxPrivate::updateDateWidget()
0131 {
0132     q->blockSignals(true);
0133     m_dateMenu->setDate(m_date);
0134     int pos = q->lineEdit()->cursorPosition();
0135     q->setItemText(0, formatDate(m_date));
0136     q->lineEdit()->setText(formatDate(m_date));
0137     q->lineEdit()->setCursorPosition(pos);
0138     q->blockSignals(false);
0139 }
0140 
0141 void KDateComboBoxPrivate::setDateRange(const QDate &minDate, const QDate &maxDate, const QString &minWarnMsg, const QString &maxWarnMsg)
0142 {
0143     if (!setDateRange(minDate, maxDate)) {
0144         return;
0145     }
0146 
0147     m_dateMenu->setDateRange(minDate, maxDate);
0148     m_minWarnMsg = minWarnMsg;
0149     m_maxWarnMsg = maxWarnMsg;
0150 }
0151 
0152 void KDateComboBoxPrivate::editDate(const QString &text)
0153 {
0154     m_warningShown = false;
0155     m_date = q->locale().toDate(text, dateFormat(m_displayFormat));
0156     m_edited = true;
0157     Q_EMIT q->dateEdited(m_date);
0158 }
0159 
0160 void KDateComboBoxPrivate::parseDate()
0161 {
0162     m_date = q->locale().toDate(q->lineEdit()->text(), dateFormat(m_displayFormat));
0163 }
0164 
0165 void KDateComboBoxPrivate::enterDate(const QDate &date)
0166 {
0167     q->setDate(date);
0168     // Re-add the combo box item in order to retain the correct widget width
0169     q->blockSignals(true);
0170     q->clear();
0171     q->setSizeAdjustPolicy(QComboBox::AdjustToContents);
0172     q->addItem(formatDate(m_date));
0173     q->setCurrentIndex(0);
0174     q->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow);
0175     q->blockSignals(false);
0176 
0177     m_dateMenu->hide();
0178     warnDate();
0179     Q_EMIT q->dateEntered(m_date);
0180 }
0181 
0182 void KDateComboBoxPrivate::warnDate()
0183 {
0184     if (!m_warningShown && !q->isValid() && (m_options & KDateComboBox::WarnOnInvalid) == KDateComboBox::WarnOnInvalid) {
0185         QString warnMsg;
0186         if (!m_date.isValid()) {
0187             warnMsg = KDateComboBox::tr("The date you entered is invalid", "@info");
0188         } else if (m_minDate.isValid() && m_date < m_minDate) {
0189             if (m_minWarnMsg.isEmpty()) {
0190                 warnMsg = KDateComboBox::tr("Date cannot be earlier than %1", "@info").arg(formatDate(m_minDate));
0191             } else {
0192                 warnMsg = m_minWarnMsg;
0193                 warnMsg.replace(QLatin1String("%1"), formatDate(m_minDate));
0194             }
0195         } else if (m_maxDate.isValid() && m_date > m_maxDate) {
0196             if (m_maxWarnMsg.isEmpty()) {
0197                 warnMsg = KDateComboBox::tr("Date cannot be later than %1", "@info").arg(formatDate(m_maxDate));
0198             } else {
0199                 warnMsg = m_maxWarnMsg;
0200                 warnMsg.replace(QLatin1String("%1"), formatDate(m_maxDate));
0201             }
0202         }
0203         m_warningShown = true;
0204         KMessageBox::error(q, warnMsg);
0205     }
0206 }
0207 
0208 KDateComboBox::KDateComboBox(QWidget *parent)
0209     : QComboBox(parent)
0210     , d(new KDateComboBoxPrivate(this))
0211 {
0212     setEditable(true);
0213     setMaxVisibleItems(1);
0214     setInsertPolicy(QComboBox::NoInsert);
0215     d->initDateWidget();
0216     d->updateDateWidget();
0217 
0218     connect(d->m_dateMenu, &KDatePickerPopup::dateChanged, this, [this](QDate date) {
0219         if (d->isInDateRange(date)) {
0220             d->enterDate(date);
0221         }
0222     });
0223 
0224     connect(this, &QComboBox::editTextChanged, this, [this](const QString &text) {
0225         d->editDate(text);
0226     });
0227 
0228     connect(lineEdit(), &QLineEdit::returnPressed, this, [this]() {
0229         if (d->m_edited) {
0230             d->enterDate(date());
0231             Q_EMIT dateChanged(date());
0232         }
0233     });
0234 }
0235 
0236 KDateComboBox::~KDateComboBox() = default;
0237 
0238 QDate KDateComboBox::date() const
0239 {
0240     d->parseDate();
0241     return d->m_date;
0242 }
0243 
0244 void KDateComboBox::setDate(const QDate &date)
0245 {
0246     if (date == d->m_date) {
0247         return;
0248     }
0249 
0250     d->m_edited = false;
0251     assignDate(date);
0252     d->updateDateWidget();
0253     Q_EMIT dateChanged(d->m_date);
0254 }
0255 
0256 void KDateComboBox::assignDate(const QDate &date)
0257 {
0258     d->m_date = date;
0259 }
0260 
0261 bool KDateComboBox::isValid() const
0262 {
0263     d->parseDate();
0264     return d->isInDateRange(d->m_date);
0265 }
0266 
0267 bool KDateComboBox::isNull() const
0268 {
0269     return lineEdit()->text().isEmpty();
0270 }
0271 
0272 KDateComboBox::Options KDateComboBox::options() const
0273 {
0274     return d->m_options;
0275 }
0276 
0277 void KDateComboBox::setOptions(Options options)
0278 {
0279     if (options != d->m_options) {
0280         d->m_options = options;
0281         d->initDateWidget();
0282         d->updateDateWidget();
0283     }
0284 }
0285 
0286 QDate KDateComboBox::minimumDate() const
0287 {
0288     return d->m_minDate;
0289 }
0290 
0291 void KDateComboBox::setMinimumDate(const QDate &minDate, const QString &minWarnMsg)
0292 {
0293     if (minDate.isValid()) {
0294         d->setDateRange(minDate, d->m_maxDate, minWarnMsg, d->m_maxWarnMsg);
0295     }
0296 }
0297 
0298 void KDateComboBox::resetMinimumDate()
0299 {
0300     d->setDateRange(QDate(), d->m_maxDate, QString(), d->m_maxWarnMsg);
0301 }
0302 
0303 QDate KDateComboBox::maximumDate() const
0304 {
0305     return d->m_maxDate;
0306 }
0307 
0308 void KDateComboBox::setMaximumDate(const QDate &maxDate, const QString &maxWarnMsg)
0309 {
0310     if (maxDate.isValid()) {
0311         d->setDateRange(d->m_minDate, maxDate, d->m_minWarnMsg, maxWarnMsg);
0312     }
0313 }
0314 
0315 void KDateComboBox::resetMaximumDate()
0316 {
0317     d->setDateRange(d->m_minDate, QDate(), d->m_minWarnMsg, QString());
0318 }
0319 
0320 void KDateComboBox::setDateRange(const QDate &minDate, const QDate &maxDate, const QString &minWarnMsg, const QString &maxWarnMsg)
0321 {
0322     if (minDate.isValid() && maxDate.isValid()) {
0323         d->setDateRange(minDate, maxDate, minWarnMsg, maxWarnMsg);
0324     }
0325 }
0326 
0327 void KDateComboBox::resetDateRange()
0328 {
0329     d->setDateRange(QDate(), QDate(), QString(), QString());
0330 }
0331 
0332 QLocale::FormatType KDateComboBox::displayFormat() const
0333 {
0334     return d->m_displayFormat;
0335 }
0336 
0337 void KDateComboBox::setDisplayFormat(QLocale::FormatType format)
0338 {
0339     if (format != d->m_displayFormat) {
0340         d->m_displayFormat = format;
0341         d->initDateWidget();
0342         d->updateDateWidget();
0343     }
0344 }
0345 
0346 QMap<QDate, QString> KDateComboBox::dateMap() const
0347 {
0348     return d->m_dateMenu->dateMap();
0349 }
0350 
0351 void KDateComboBox::setDateMap(QMap<QDate, QString> dateMap)
0352 {
0353     d->m_dateMenu->setDateMap(dateMap);
0354 }
0355 
0356 bool KDateComboBox::eventFilter(QObject *object, QEvent *event)
0357 {
0358     return QComboBox::eventFilter(object, event);
0359 }
0360 
0361 void KDateComboBox::keyPressEvent(QKeyEvent *keyEvent)
0362 {
0363     QDate temp;
0364     switch (keyEvent->key()) {
0365     case Qt::Key_Down:
0366         temp = d->m_date.addDays(-1);
0367         break;
0368     case Qt::Key_Up:
0369         temp = d->m_date.addDays(1);
0370         break;
0371     case Qt::Key_PageDown:
0372         temp = d->m_date.addMonths(-1);
0373         break;
0374     case Qt::Key_PageUp:
0375         temp = d->m_date.addMonths(1);
0376         break;
0377     default:
0378         QComboBox::keyPressEvent(keyEvent);
0379         return;
0380     }
0381     if (d->isInDateRange(temp)) {
0382         d->enterDate(temp);
0383     }
0384 }
0385 
0386 void KDateComboBox::focusOutEvent(QFocusEvent *event)
0387 {
0388     d->parseDate();
0389     d->warnDate();
0390     if (d->m_edited) {
0391         d->m_edited = false;
0392         Q_EMIT dateChanged(d->m_date);
0393     }
0394     QComboBox::focusOutEvent(event);
0395 }
0396 
0397 void KDateComboBox::showPopup()
0398 {
0399     if (!isEditable() || !d->m_dateMenu //
0400         || (d->m_options & KDateComboBox::SelectDate) != KDateComboBox::SelectDate) {
0401         return;
0402     }
0403 
0404     d->m_dateMenu->setDate(d->m_date);
0405 
0406     const QRect desk = screen()->geometry();
0407 
0408     QPoint popupPoint = mapToGlobal(QPoint(0, 0));
0409 
0410     const int dateFrameHeight = d->m_dateMenu->sizeHint().height();
0411     if (popupPoint.y() + height() + dateFrameHeight > desk.bottom()) {
0412         popupPoint.setY(popupPoint.y() - dateFrameHeight);
0413     } else {
0414         popupPoint.setY(popupPoint.y() + height());
0415     }
0416 
0417     const int dateFrameWidth = d->m_dateMenu->sizeHint().width();
0418     if (popupPoint.x() + dateFrameWidth > desk.right()) {
0419         popupPoint.setX(desk.right() - dateFrameWidth);
0420     }
0421 
0422     if (popupPoint.x() < desk.left()) {
0423         popupPoint.setX(desk.left());
0424     }
0425 
0426     if (popupPoint.y() < desk.top()) {
0427         popupPoint.setY(desk.top());
0428     }
0429 
0430     d->m_dateMenu->popup(popupPoint);
0431 }
0432 
0433 void KDateComboBox::hidePopup()
0434 {
0435     QComboBox::hidePopup();
0436 }
0437 
0438 void KDateComboBox::mousePressEvent(QMouseEvent *event)
0439 {
0440     QComboBox::mousePressEvent(event);
0441 }
0442 
0443 void KDateComboBox::wheelEvent(QWheelEvent *event)
0444 {
0445     QDate temp;
0446     if (event->angleDelta().y() < 0) {
0447         temp = d->m_date.addDays(-1);
0448     } else {
0449         temp = d->m_date.addDays(1);
0450     }
0451     if (d->isInDateRange(temp)) {
0452         d->enterDate(temp);
0453     }
0454 }
0455 
0456 void KDateComboBox::focusInEvent(QFocusEvent *event)
0457 {
0458     QComboBox::focusInEvent(event);
0459 }
0460 
0461 void KDateComboBox::resizeEvent(QResizeEvent *event)
0462 {
0463     QComboBox::resizeEvent(event);
0464 }
0465 
0466 #include "moc_kdatecombobox.cpp"