1/*
2 SPDX-FileCopyrightText: 2011 John Layt <john@layt.net>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "kdatecombobox.h"
8
9#include "common_helpers_p.h"
10#include "kdatepickerpopup.h"
11#include "kdaterangecontrol_p.h"
12
13#include <QAbstractItemView>
14#include <QApplication>
15#include <QDate>
16#include <QKeyEvent>
17#include <QLineEdit>
18#include <QList>
19#include <QMenu>
20#include <QScreen>
21#include <QWidgetAction>
22
23#include "kdatepicker.h"
24#include "kmessagebox.h"
25
26class KDateComboBoxPrivate : public KDateRangeControlPrivate
27{
28public:
29 KDateComboBoxPrivate(KDateComboBox *qq);
30
31 // TODO: Find a way to get that from QLocale
32#if 0
33 QDate defaultMinDate();
34 QDate defaultMaxDate();
35#endif
36
37 QString dateFormat(QLocale::FormatType format);
38 QString formatDate(const QDate &date);
39
40 void initDateWidget();
41 void updateDateWidget();
42 void setDateRange(const QDate &minDate, const QDate &maxDate, const QString &minWarnMsg, const QString &maxWarnMsg);
43 using KDateRangeControlPrivate::setDateRange;
44
45 void editDate(const QString &text);
46 void enterDate(const QDate &date);
47 void parseDate();
48 bool warnDate();
49
50 KDateComboBox *const q;
51 KDatePickerPopup *m_dateMenu;
52
53 QDate m_date;
54 KDateComboBox::Options m_options;
55 QString m_minWarnMsg;
56 QString m_maxWarnMsg;
57 bool m_warningShown;
58 bool m_edited; // and dateChanged not yet emitted
59 QLocale::FormatType m_displayFormat;
60};
61
62KDateComboBoxPrivate::KDateComboBoxPrivate(KDateComboBox *qq)
63 : q(qq)
64 , m_dateMenu(new KDatePickerPopup(KDatePickerPopup::DatePicker | KDatePickerPopup::Words, QDate::currentDate(), qq))
65 , m_warningShown(false)
66 , m_edited(false)
67 , m_displayFormat(QLocale::ShortFormat)
68{
69 m_options = KDateComboBox::EditDate | KDateComboBox::SelectDate | KDateComboBox::DatePicker | KDateComboBox::DateKeywords;
70 m_date = QDate::currentDate();
71 // m_minDate = defaultMinDate();
72 // m_maxDate = defaultMaxDate();
73}
74
75#if 0
76QDate KDateComboBoxPrivate::defaultMinDate()
77{
78 return m_date.calendar()->earliestValidDate();
79}
80
81QDate KDateComboBoxPrivate::defaultMaxDate()
82{
83 return m_date.calendar()->latestValidDate();
84}
85#endif
86
87QString KDateComboBoxPrivate::dateFormat(QLocale::FormatType format)
88{
89 return dateFormatWith4DigitYear(locale: q->locale(), format);
90}
91
92QString KDateComboBoxPrivate::formatDate(const QDate &date)
93{
94 return q->locale().toString(date, format: dateFormat(format: m_displayFormat));
95}
96
97void KDateComboBoxPrivate::initDateWidget()
98{
99 q->blockSignals(b: true);
100 q->clear();
101
102 // If EditTime then set the line edit
103 q->lineEdit()->setReadOnly((m_options & KDateComboBox::EditDate) != KDateComboBox::EditDate);
104
105 // If SelectDate then make list items visible
106 if ((m_options & KDateComboBox::SelectDate) == KDateComboBox::SelectDate //
107 || (m_options & KDateComboBox::DatePicker) == KDateComboBox::DatePicker //
108 || (m_options & KDateComboBox::DateKeywords) == KDateComboBox::DateKeywords) {
109 q->setMaxVisibleItems(1);
110 } else {
111 q->setMaxVisibleItems(0);
112 }
113
114 q->setSizeAdjustPolicy(QComboBox::AdjustToContents);
115 q->addItem(atext: formatDate(date: m_date));
116 q->setCurrentIndex(0);
117 q->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow);
118 q->blockSignals(b: false);
119
120 KDatePickerPopup::Modes modes;
121 if (m_options & KDateComboBox::DatePicker) {
122 modes |= KDatePickerPopup::DatePicker;
123 }
124 if (m_options & KDateComboBox::DateKeywords) {
125 modes |= KDatePickerPopup::Words;
126 }
127 m_dateMenu->setModes(modes);
128}
129
130void KDateComboBoxPrivate::updateDateWidget()
131{
132 q->blockSignals(b: true);
133 m_dateMenu->setDate(m_date);
134 int pos = q->lineEdit()->cursorPosition();
135 q->setItemText(index: 0, text: formatDate(date: m_date));
136 q->lineEdit()->setText(formatDate(date: m_date));
137 q->lineEdit()->setCursorPosition(pos);
138 q->blockSignals(b: false);
139}
140
141void KDateComboBoxPrivate::setDateRange(const QDate &minDate, const QDate &maxDate, const QString &minWarnMsg, const QString &maxWarnMsg)
142{
143 if (!setDateRange(minDate, maxDate)) {
144 return;
145 }
146
147 m_dateMenu->setDateRange(minDate, maxDate);
148 m_minWarnMsg = minWarnMsg;
149 m_maxWarnMsg = maxWarnMsg;
150}
151
152void KDateComboBoxPrivate::editDate(const QString &text)
153{
154 m_warningShown = false;
155 m_date = q->locale().toDate(string: text, format: dateFormat(format: m_displayFormat));
156 m_edited = true;
157 Q_EMIT q->dateEdited(date: m_date);
158}
159
160void KDateComboBoxPrivate::parseDate()
161{
162 m_date = q->locale().toDate(string: q->lineEdit()->text(), format: dateFormat(format: m_displayFormat));
163}
164
165void KDateComboBoxPrivate::enterDate(const QDate &date)
166{
167 q->setDate(date);
168 // Re-add the combo box item in order to retain the correct widget width
169 q->blockSignals(b: true);
170 q->clear();
171 q->setSizeAdjustPolicy(QComboBox::AdjustToContents);
172 q->addItem(atext: formatDate(date: m_date));
173 q->setCurrentIndex(0);
174 q->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow);
175 q->blockSignals(b: false);
176
177 m_dateMenu->hide();
178 warnDate();
179 Q_EMIT q->dateEntered(date: m_date);
180}
181
182bool KDateComboBoxPrivate::warnDate()
183{
184 if (!m_warningShown && !q->isValid() && (m_options & KDateComboBox::WarnOnInvalid) == KDateComboBox::WarnOnInvalid) {
185 QString warnMsg;
186 if (!m_date.isValid()) {
187 warnMsg = KDateComboBox::tr(s: "The date you entered is invalid", c: "@info");
188 } else if (m_minDate.isValid() && m_date < m_minDate) {
189 if (m_minWarnMsg.isEmpty()) {
190 warnMsg = KDateComboBox::tr(s: "Date cannot be earlier than %1", c: "@info").arg(a: formatDate(date: m_minDate));
191 } else {
192 warnMsg = m_minWarnMsg;
193 warnMsg.replace(before: QLatin1String("%1"), after: formatDate(date: m_minDate));
194 }
195 } else if (m_maxDate.isValid() && m_date > m_maxDate) {
196 if (m_maxWarnMsg.isEmpty()) {
197 warnMsg = KDateComboBox::tr(s: "Date cannot be later than %1", c: "@info").arg(a: formatDate(date: m_maxDate));
198 } else {
199 warnMsg = m_maxWarnMsg;
200 warnMsg.replace(before: QLatin1String("%1"), after: formatDate(date: m_maxDate));
201 }
202 }
203 m_warningShown = true;
204 KMessageBox::error(parent: q, text: warnMsg);
205 return true;
206 }
207 return false;
208}
209
210KDateComboBox::KDateComboBox(QWidget *parent)
211 : QComboBox(parent)
212 , d(new KDateComboBoxPrivate(this))
213{
214 setEditable(true);
215 setMaxVisibleItems(1);
216 setInsertPolicy(QComboBox::NoInsert);
217 d->initDateWidget();
218 d->updateDateWidget();
219
220 connect(sender: d->m_dateMenu, signal: &KDatePickerPopup::dateChanged, context: this, slot: [this](QDate date) {
221 if (d->isInDateRange(date)) {
222 d->enterDate(date);
223 }
224 });
225
226 connect(sender: this, signal: &QComboBox::editTextChanged, context: this, slot: [this](const QString &text) {
227 d->editDate(text);
228 });
229
230 connect(sender: lineEdit(), signal: &QLineEdit::returnPressed, context: this, slot: [this]() {
231 if (d->m_edited) {
232 d->enterDate(date: date());
233 Q_EMIT dateChanged(date: date());
234 }
235 });
236}
237
238KDateComboBox::~KDateComboBox() = default;
239
240QDate KDateComboBox::date() const
241{
242 d->parseDate();
243 return d->m_date;
244}
245
246void KDateComboBox::setDate(const QDate &date)
247{
248 if (date == d->m_date) {
249 return;
250 }
251
252 d->m_edited = false;
253 assignDate(date);
254 d->updateDateWidget();
255 Q_EMIT dateChanged(date: d->m_date);
256}
257
258void KDateComboBox::assignDate(const QDate &date)
259{
260 d->m_date = date;
261}
262
263bool KDateComboBox::isValid() const
264{
265 d->parseDate();
266 return d->isInDateRange(date: d->m_date);
267}
268
269bool KDateComboBox::isNull() const
270{
271 return lineEdit()->text().isEmpty();
272}
273
274KDateComboBox::Options KDateComboBox::options() const
275{
276 return d->m_options;
277}
278
279void KDateComboBox::setOptions(Options options)
280{
281 if (options != d->m_options) {
282 d->m_options = options;
283 d->initDateWidget();
284 d->updateDateWidget();
285 }
286}
287
288QDate KDateComboBox::minimumDate() const
289{
290 return d->m_minDate;
291}
292
293void KDateComboBox::setMinimumDate(const QDate &minDate, const QString &minWarnMsg)
294{
295 if (minDate.isValid()) {
296 d->setDateRange(minDate, maxDate: d->m_maxDate, minWarnMsg, maxWarnMsg: d->m_maxWarnMsg);
297 }
298}
299
300void KDateComboBox::resetMinimumDate()
301{
302 d->setDateRange(minDate: QDate(), maxDate: d->m_maxDate, minWarnMsg: QString(), maxWarnMsg: d->m_maxWarnMsg);
303}
304
305QDate KDateComboBox::maximumDate() const
306{
307 return d->m_maxDate;
308}
309
310void KDateComboBox::setMaximumDate(const QDate &maxDate, const QString &maxWarnMsg)
311{
312 if (maxDate.isValid()) {
313 d->setDateRange(minDate: d->m_minDate, maxDate, minWarnMsg: d->m_minWarnMsg, maxWarnMsg);
314 }
315}
316
317void KDateComboBox::resetMaximumDate()
318{
319 d->setDateRange(minDate: d->m_minDate, maxDate: QDate(), minWarnMsg: d->m_minWarnMsg, maxWarnMsg: QString());
320}
321
322void KDateComboBox::setDateRange(const QDate &minDate, const QDate &maxDate, const QString &minWarnMsg, const QString &maxWarnMsg)
323{
324 if (minDate.isValid() && maxDate.isValid()) {
325 d->setDateRange(minDate, maxDate, minWarnMsg, maxWarnMsg);
326 }
327}
328
329void KDateComboBox::resetDateRange()
330{
331 d->setDateRange(minDate: QDate(), maxDate: QDate(), minWarnMsg: QString(), maxWarnMsg: QString());
332}
333
334QLocale::FormatType KDateComboBox::displayFormat() const
335{
336 return d->m_displayFormat;
337}
338
339void KDateComboBox::setDisplayFormat(QLocale::FormatType format)
340{
341 if (format != d->m_displayFormat) {
342 d->m_displayFormat = format;
343 d->initDateWidget();
344 d->updateDateWidget();
345 }
346}
347
348QMap<QDate, QString> KDateComboBox::dateMap() const
349{
350 return d->m_dateMenu->dateMap();
351}
352
353void KDateComboBox::setDateMap(QMap<QDate, QString> dateMap)
354{
355 d->m_dateMenu->setDateMap(dateMap);
356}
357
358bool KDateComboBox::eventFilter(QObject *object, QEvent *event)
359{
360 return QComboBox::eventFilter(watched: object, event);
361}
362
363void KDateComboBox::keyPressEvent(QKeyEvent *keyEvent)
364{
365 QDate temp;
366 switch (keyEvent->key()) {
367 case Qt::Key_Down:
368 temp = d->m_date.addDays(days: -1);
369 break;
370 case Qt::Key_Up:
371 temp = d->m_date.addDays(days: 1);
372 break;
373 case Qt::Key_PageDown:
374 temp = d->m_date.addMonths(months: -1);
375 break;
376 case Qt::Key_PageUp:
377 temp = d->m_date.addMonths(months: 1);
378 break;
379 default:
380 QComboBox::keyPressEvent(e: keyEvent);
381 return;
382 }
383 if (d->isInDateRange(date: temp)) {
384 d->enterDate(date: temp);
385 }
386}
387
388void KDateComboBox::focusOutEvent(QFocusEvent *event)
389{
390 d->parseDate();
391 d->warnDate();
392 if (d->m_edited) {
393 d->m_edited = false;
394 Q_EMIT dateEntered(date: d->m_date);
395 Q_EMIT dateChanged(date: d->m_date);
396 }
397 QComboBox::focusOutEvent(e: event);
398}
399
400void KDateComboBox::showPopup()
401{
402 if (d->warnDate()) {
403 return;
404 }
405 if (!isEditable() || !d->m_dateMenu //
406 || (d->m_options & KDateComboBox::SelectDate) != KDateComboBox::SelectDate) {
407 return;
408 }
409
410 d->m_dateMenu->setDate(d->m_date);
411
412 const QRect desk = screen()->geometry();
413
414 QPoint popupPoint = mapToGlobal(QPoint(0, 0));
415
416 const int dateFrameHeight = d->m_dateMenu->sizeHint().height();
417 if (popupPoint.y() + height() + dateFrameHeight > desk.bottom()) {
418 popupPoint.setY(popupPoint.y() - dateFrameHeight);
419 } else {
420 popupPoint.setY(popupPoint.y() + height());
421 }
422
423 const int dateFrameWidth = d->m_dateMenu->sizeHint().width();
424 if (popupPoint.x() + dateFrameWidth > desk.right()) {
425 popupPoint.setX(desk.right() - dateFrameWidth);
426 }
427
428 if (popupPoint.x() < desk.left()) {
429 popupPoint.setX(desk.left());
430 }
431
432 if (popupPoint.y() < desk.top()) {
433 popupPoint.setY(desk.top());
434 }
435
436 d->m_dateMenu->popup(pos: popupPoint);
437}
438
439void KDateComboBox::hidePopup()
440{
441 QComboBox::hidePopup();
442}
443
444void KDateComboBox::mousePressEvent(QMouseEvent *event)
445{
446 QComboBox::mousePressEvent(e: event);
447}
448
449void KDateComboBox::wheelEvent(QWheelEvent *event)
450{
451 QDate temp;
452 if (event->angleDelta().y() < 0) {
453 temp = d->m_date.addDays(days: -1);
454 } else {
455 temp = d->m_date.addDays(days: 1);
456 }
457 if (d->isInDateRange(date: temp)) {
458 d->enterDate(date: temp);
459 }
460}
461
462void KDateComboBox::focusInEvent(QFocusEvent *event)
463{
464 QComboBox::focusInEvent(e: event);
465}
466
467void KDateComboBox::resizeEvent(QResizeEvent *event)
468{
469 QComboBox::resizeEvent(e: event);
470}
471
472#include "moc_kdatecombobox.cpp"
473

source code of kwidgetsaddons/src/kdatecombobox.cpp