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 void 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::DatePicker) == 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
182void 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 }
206}
207
208KDateComboBox::KDateComboBox(QWidget *parent)
209 : QComboBox(parent)
210 , d(new KDateComboBoxPrivate(this))
211{
212 setEditable(true);
213 setMaxVisibleItems(1);
214 setInsertPolicy(QComboBox::NoInsert);
215 d->initDateWidget();
216 d->updateDateWidget();
217
218 connect(sender: d->m_dateMenu, signal: &KDatePickerPopup::dateChanged, context: this, slot: [this](QDate date) {
219 if (d->isInDateRange(date)) {
220 d->enterDate(date);
221 }
222 });
223
224 connect(sender: this, signal: &QComboBox::editTextChanged, context: this, slot: [this](const QString &text) {
225 d->editDate(text);
226 });
227
228 connect(sender: lineEdit(), signal: &QLineEdit::returnPressed, context: this, slot: [this]() {
229 if (d->m_edited) {
230 d->enterDate(date: date());
231 Q_EMIT dateChanged(date: date());
232 }
233 });
234}
235
236KDateComboBox::~KDateComboBox() = default;
237
238QDate KDateComboBox::date() const
239{
240 d->parseDate();
241 return d->m_date;
242}
243
244void KDateComboBox::setDate(const QDate &date)
245{
246 if (date == d->m_date) {
247 return;
248 }
249
250 d->m_edited = false;
251 assignDate(date);
252 d->updateDateWidget();
253 Q_EMIT dateChanged(date: d->m_date);
254}
255
256void KDateComboBox::assignDate(const QDate &date)
257{
258 d->m_date = date;
259}
260
261bool KDateComboBox::isValid() const
262{
263 d->parseDate();
264 return d->isInDateRange(date: d->m_date);
265}
266
267bool KDateComboBox::isNull() const
268{
269 return lineEdit()->text().isEmpty();
270}
271
272KDateComboBox::Options KDateComboBox::options() const
273{
274 return d->m_options;
275}
276
277void KDateComboBox::setOptions(Options options)
278{
279 if (options != d->m_options) {
280 d->m_options = options;
281 d->initDateWidget();
282 d->updateDateWidget();
283 }
284}
285
286QDate KDateComboBox::minimumDate() const
287{
288 return d->m_minDate;
289}
290
291void KDateComboBox::setMinimumDate(const QDate &minDate, const QString &minWarnMsg)
292{
293 if (minDate.isValid()) {
294 d->setDateRange(minDate, maxDate: d->m_maxDate, minWarnMsg, maxWarnMsg: d->m_maxWarnMsg);
295 }
296}
297
298void KDateComboBox::resetMinimumDate()
299{
300 d->setDateRange(minDate: QDate(), maxDate: d->m_maxDate, minWarnMsg: QString(), maxWarnMsg: d->m_maxWarnMsg);
301}
302
303QDate KDateComboBox::maximumDate() const
304{
305 return d->m_maxDate;
306}
307
308void KDateComboBox::setMaximumDate(const QDate &maxDate, const QString &maxWarnMsg)
309{
310 if (maxDate.isValid()) {
311 d->setDateRange(minDate: d->m_minDate, maxDate, minWarnMsg: d->m_minWarnMsg, maxWarnMsg);
312 }
313}
314
315void KDateComboBox::resetMaximumDate()
316{
317 d->setDateRange(minDate: d->m_minDate, maxDate: QDate(), minWarnMsg: d->m_minWarnMsg, maxWarnMsg: QString());
318}
319
320void KDateComboBox::setDateRange(const QDate &minDate, const QDate &maxDate, const QString &minWarnMsg, const QString &maxWarnMsg)
321{
322 if (minDate.isValid() && maxDate.isValid()) {
323 d->setDateRange(minDate, maxDate, minWarnMsg, maxWarnMsg);
324 }
325}
326
327void KDateComboBox::resetDateRange()
328{
329 d->setDateRange(minDate: QDate(), maxDate: QDate(), minWarnMsg: QString(), maxWarnMsg: QString());
330}
331
332QLocale::FormatType KDateComboBox::displayFormat() const
333{
334 return d->m_displayFormat;
335}
336
337void KDateComboBox::setDisplayFormat(QLocale::FormatType format)
338{
339 if (format != d->m_displayFormat) {
340 d->m_displayFormat = format;
341 d->initDateWidget();
342 d->updateDateWidget();
343 }
344}
345
346QMap<QDate, QString> KDateComboBox::dateMap() const
347{
348 return d->m_dateMenu->dateMap();
349}
350
351void KDateComboBox::setDateMap(QMap<QDate, QString> dateMap)
352{
353 d->m_dateMenu->setDateMap(dateMap);
354}
355
356bool KDateComboBox::eventFilter(QObject *object, QEvent *event)
357{
358 return QComboBox::eventFilter(watched: object, event);
359}
360
361void KDateComboBox::keyPressEvent(QKeyEvent *keyEvent)
362{
363 QDate temp;
364 switch (keyEvent->key()) {
365 case Qt::Key_Down:
366 temp = d->m_date.addDays(days: -1);
367 break;
368 case Qt::Key_Up:
369 temp = d->m_date.addDays(days: 1);
370 break;
371 case Qt::Key_PageDown:
372 temp = d->m_date.addMonths(months: -1);
373 break;
374 case Qt::Key_PageUp:
375 temp = d->m_date.addMonths(months: 1);
376 break;
377 default:
378 QComboBox::keyPressEvent(e: keyEvent);
379 return;
380 }
381 if (d->isInDateRange(date: temp)) {
382 d->enterDate(date: temp);
383 }
384}
385
386void KDateComboBox::focusOutEvent(QFocusEvent *event)
387{
388 d->parseDate();
389 d->warnDate();
390 if (d->m_edited) {
391 d->m_edited = false;
392 Q_EMIT dateChanged(date: d->m_date);
393 }
394 QComboBox::focusOutEvent(e: event);
395}
396
397void KDateComboBox::showPopup()
398{
399 if (!isEditable() || !d->m_dateMenu //
400 || (d->m_options & KDateComboBox::SelectDate) != KDateComboBox::SelectDate) {
401 return;
402 }
403
404 d->m_dateMenu->setDate(d->m_date);
405
406 const QRect desk = screen()->geometry();
407
408 QPoint popupPoint = mapToGlobal(QPoint(0, 0));
409
410 const int dateFrameHeight = d->m_dateMenu->sizeHint().height();
411 if (popupPoint.y() + height() + dateFrameHeight > desk.bottom()) {
412 popupPoint.setY(popupPoint.y() - dateFrameHeight);
413 } else {
414 popupPoint.setY(popupPoint.y() + height());
415 }
416
417 const int dateFrameWidth = d->m_dateMenu->sizeHint().width();
418 if (popupPoint.x() + dateFrameWidth > desk.right()) {
419 popupPoint.setX(desk.right() - dateFrameWidth);
420 }
421
422 if (popupPoint.x() < desk.left()) {
423 popupPoint.setX(desk.left());
424 }
425
426 if (popupPoint.y() < desk.top()) {
427 popupPoint.setY(desk.top());
428 }
429
430 d->m_dateMenu->popup(pos: popupPoint);
431}
432
433void KDateComboBox::hidePopup()
434{
435 QComboBox::hidePopup();
436}
437
438void KDateComboBox::mousePressEvent(QMouseEvent *event)
439{
440 QComboBox::mousePressEvent(e: event);
441}
442
443void KDateComboBox::wheelEvent(QWheelEvent *event)
444{
445 QDate temp;
446 if (event->angleDelta().y() < 0) {
447 temp = d->m_date.addDays(days: -1);
448 } else {
449 temp = d->m_date.addDays(days: 1);
450 }
451 if (d->isInDateRange(date: temp)) {
452 d->enterDate(date: temp);
453 }
454}
455
456void KDateComboBox::focusInEvent(QFocusEvent *event)
457{
458 QComboBox::focusInEvent(e: event);
459}
460
461void KDateComboBox::resizeEvent(QResizeEvent *event)
462{
463 QComboBox::resizeEvent(e: event);
464}
465
466#include "moc_kdatecombobox.cpp"
467

source code of kwidgetsaddons/src/kdatecombobox.cpp