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 | void 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::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 | |
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 | void 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 | |
208 | KDateComboBox::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 | |
236 | KDateComboBox::~KDateComboBox() = default; |
237 | |
238 | QDate KDateComboBox::date() const |
239 | { |
240 | d->parseDate(); |
241 | return d->m_date; |
242 | } |
243 | |
244 | void 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 | |
256 | void KDateComboBox::assignDate(const QDate &date) |
257 | { |
258 | d->m_date = date; |
259 | } |
260 | |
261 | bool KDateComboBox::isValid() const |
262 | { |
263 | d->parseDate(); |
264 | return d->isInDateRange(date: d->m_date); |
265 | } |
266 | |
267 | bool KDateComboBox::isNull() const |
268 | { |
269 | return lineEdit()->text().isEmpty(); |
270 | } |
271 | |
272 | KDateComboBox::Options KDateComboBox::options() const |
273 | { |
274 | return d->m_options; |
275 | } |
276 | |
277 | void 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 | |
286 | QDate KDateComboBox::minimumDate() const |
287 | { |
288 | return d->m_minDate; |
289 | } |
290 | |
291 | void 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 | |
298 | void KDateComboBox::resetMinimumDate() |
299 | { |
300 | d->setDateRange(minDate: QDate(), maxDate: d->m_maxDate, minWarnMsg: QString(), maxWarnMsg: d->m_maxWarnMsg); |
301 | } |
302 | |
303 | QDate KDateComboBox::maximumDate() const |
304 | { |
305 | return d->m_maxDate; |
306 | } |
307 | |
308 | void 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 | |
315 | void KDateComboBox::resetMaximumDate() |
316 | { |
317 | d->setDateRange(minDate: d->m_minDate, maxDate: QDate(), minWarnMsg: d->m_minWarnMsg, maxWarnMsg: QString()); |
318 | } |
319 | |
320 | void 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 | |
327 | void KDateComboBox::resetDateRange() |
328 | { |
329 | d->setDateRange(minDate: QDate(), maxDate: QDate(), minWarnMsg: QString(), maxWarnMsg: QString()); |
330 | } |
331 | |
332 | QLocale::FormatType KDateComboBox::displayFormat() const |
333 | { |
334 | return d->m_displayFormat; |
335 | } |
336 | |
337 | void 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 | |
346 | QMap<QDate, QString> KDateComboBox::dateMap() const |
347 | { |
348 | return d->m_dateMenu->dateMap(); |
349 | } |
350 | |
351 | void KDateComboBox::setDateMap(QMap<QDate, QString> dateMap) |
352 | { |
353 | d->m_dateMenu->setDateMap(dateMap); |
354 | } |
355 | |
356 | bool KDateComboBox::eventFilter(QObject *object, QEvent *event) |
357 | { |
358 | return QComboBox::eventFilter(watched: object, event); |
359 | } |
360 | |
361 | void 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 | |
386 | void 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 | |
397 | void KDateComboBox::() |
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 = 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 | |
433 | void KDateComboBox::() |
434 | { |
435 | QComboBox::hidePopup(); |
436 | } |
437 | |
438 | void KDateComboBox::mousePressEvent(QMouseEvent *event) |
439 | { |
440 | QComboBox::mousePressEvent(e: event); |
441 | } |
442 | |
443 | void 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 | |
456 | void KDateComboBox::focusInEvent(QFocusEvent *event) |
457 | { |
458 | QComboBox::focusInEvent(e: event); |
459 | } |
460 | |
461 | void KDateComboBox::resizeEvent(QResizeEvent *event) |
462 | { |
463 | QComboBox::resizeEvent(e: event); |
464 | } |
465 | |
466 | #include "moc_kdatecombobox.cpp" |
467 | |