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 | |
26 | class KDateComboBoxPrivate : public KDateRangeControlPrivate |
27 | { |
28 | public: |
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 *; |
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 | |
62 | KDateComboBoxPrivate::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 |
76 | QDate KDateComboBoxPrivate::defaultMinDate() |
77 | { |
78 | return m_date.calendar()->earliestValidDate(); |
79 | } |
80 | |
81 | QDate KDateComboBoxPrivate::defaultMaxDate() |
82 | { |
83 | return m_date.calendar()->latestValidDate(); |
84 | } |
85 | #endif |
86 | |
87 | QString KDateComboBoxPrivate::dateFormat(QLocale::FormatType format) |
88 | { |
89 | return dateFormatWith4DigitYear(locale: q->locale(), format); |
90 | } |
91 | |
92 | QString KDateComboBoxPrivate::formatDate(const QDate &date) |
93 | { |
94 | return q->locale().toString(date, format: dateFormat(format: m_displayFormat)); |
95 | } |
96 | |
97 | void 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 | |
130 | void 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 | |
141 | void 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 | |
152 | void 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 | |
160 | void KDateComboBoxPrivate::parseDate() |
161 | { |
162 | m_date = q->locale().toDate(string: q->lineEdit()->text(), format: dateFormat(format: m_displayFormat)); |
163 | } |
164 | |
165 | void 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 | |
182 | bool 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 | |
210 | KDateComboBox::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 | |
238 | KDateComboBox::~KDateComboBox() = default; |
239 | |
240 | QDate KDateComboBox::date() const |
241 | { |
242 | d->parseDate(); |
243 | return d->m_date; |
244 | } |
245 | |
246 | void 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 | |
258 | void KDateComboBox::assignDate(const QDate &date) |
259 | { |
260 | d->m_date = date; |
261 | } |
262 | |
263 | bool KDateComboBox::isValid() const |
264 | { |
265 | d->parseDate(); |
266 | return d->isInDateRange(date: d->m_date); |
267 | } |
268 | |
269 | bool KDateComboBox::isNull() const |
270 | { |
271 | return lineEdit()->text().isEmpty(); |
272 | } |
273 | |
274 | KDateComboBox::Options KDateComboBox::options() const |
275 | { |
276 | return d->m_options; |
277 | } |
278 | |
279 | void 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 | |
288 | QDate KDateComboBox::minimumDate() const |
289 | { |
290 | return d->m_minDate; |
291 | } |
292 | |
293 | void 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 | |
300 | void KDateComboBox::resetMinimumDate() |
301 | { |
302 | d->setDateRange(minDate: QDate(), maxDate: d->m_maxDate, minWarnMsg: QString(), maxWarnMsg: d->m_maxWarnMsg); |
303 | } |
304 | |
305 | QDate KDateComboBox::maximumDate() const |
306 | { |
307 | return d->m_maxDate; |
308 | } |
309 | |
310 | void 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 | |
317 | void KDateComboBox::resetMaximumDate() |
318 | { |
319 | d->setDateRange(minDate: d->m_minDate, maxDate: QDate(), minWarnMsg: d->m_minWarnMsg, maxWarnMsg: QString()); |
320 | } |
321 | |
322 | void 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 | |
329 | void KDateComboBox::resetDateRange() |
330 | { |
331 | d->setDateRange(minDate: QDate(), maxDate: QDate(), minWarnMsg: QString(), maxWarnMsg: QString()); |
332 | } |
333 | |
334 | QLocale::FormatType KDateComboBox::displayFormat() const |
335 | { |
336 | return d->m_displayFormat; |
337 | } |
338 | |
339 | void 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 | |
348 | QMap<QDate, QString> KDateComboBox::dateMap() const |
349 | { |
350 | return d->m_dateMenu->dateMap(); |
351 | } |
352 | |
353 | void KDateComboBox::setDateMap(QMap<QDate, QString> dateMap) |
354 | { |
355 | d->m_dateMenu->setDateMap(dateMap); |
356 | } |
357 | |
358 | bool KDateComboBox::eventFilter(QObject *object, QEvent *event) |
359 | { |
360 | return QComboBox::eventFilter(watched: object, event); |
361 | } |
362 | |
363 | void 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 | |
388 | void 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 | |
400 | void KDateComboBox::() |
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 = 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 | |
439 | void KDateComboBox::() |
440 | { |
441 | QComboBox::hidePopup(); |
442 | } |
443 | |
444 | void KDateComboBox::mousePressEvent(QMouseEvent *event) |
445 | { |
446 | QComboBox::mousePressEvent(e: event); |
447 | } |
448 | |
449 | void 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 | |
462 | void KDateComboBox::focusInEvent(QFocusEvent *event) |
463 | { |
464 | QComboBox::focusInEvent(e: event); |
465 | } |
466 | |
467 | void KDateComboBox::resizeEvent(QResizeEvent *event) |
468 | { |
469 | QComboBox::resizeEvent(e: event); |
470 | } |
471 | |
472 | #include "moc_kdatecombobox.cpp" |
473 | |