1/*
2 SPDX-FileCopyrightText: 2011 John Layt <john@layt.net>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "ktimecombobox.h"
8
9#include <QKeyEvent>
10#include <QLineEdit>
11#include <QTime>
12
13#include "kmessagebox.h"
14
15class KTimeComboBoxPrivate
16{
17public:
18 KTimeComboBoxPrivate(KTimeComboBox *qq);
19 virtual ~KTimeComboBoxPrivate();
20
21 QTime defaultMinTime();
22 QTime defaultMaxTime();
23
24 std::pair<QString, QString> timeFormatToInputMask(const QString &format);
25 QTime nearestIntervalTime(const QTime &time);
26 QString formatTime(const QTime &time);
27
28 void initTimeWidget();
29 void updateTimeWidget();
30
31 // Private slots
32 void selectTime(int index);
33 void editTime(const QString &text);
34 void enterTime(const QTime &time);
35 void parseTime();
36 void warnTime();
37
38 KTimeComboBox *const q;
39
40 QTime m_time;
41 KTimeComboBox::Options m_options;
42 QTime m_minTime;
43 QTime m_maxTime;
44 QString m_minWarnMsg;
45 QString m_maxWarnMsg;
46 QString m_nullString;
47 bool m_warningShown;
48 QLocale::FormatType m_displayFormat;
49 int m_timeListInterval;
50 QList<QTime> m_timeList;
51};
52
53KTimeComboBoxPrivate::KTimeComboBoxPrivate(KTimeComboBox *qq)
54 : q(qq)
55 , m_time(QTime(0, 0, 0))
56 , m_warningShown(false)
57 , m_displayFormat(QLocale::ShortFormat)
58 , m_timeListInterval(15)
59{
60 m_options = KTimeComboBox::EditTime | KTimeComboBox::SelectTime;
61 m_minTime = defaultMinTime();
62 m_maxTime = defaultMaxTime();
63}
64
65KTimeComboBoxPrivate::~KTimeComboBoxPrivate()
66{
67}
68
69QTime KTimeComboBoxPrivate::defaultMinTime()
70{
71 return QTime(0, 0, 0, 0);
72}
73
74QTime KTimeComboBoxPrivate::defaultMaxTime()
75{
76 return QTime(23, 59, 59, 999);
77}
78
79std::pair<QString, QString> KTimeComboBoxPrivate::timeFormatToInputMask(const QString &format)
80{
81 const QLocale locale = q->locale();
82
83 QString example = formatTime(time: QTime(12, 34, 56, 789));
84 // Replace time components with edit mask characters.
85 example.replace(before: locale.toString(i: 12), after: QLatin1String("09"));
86 example.replace(before: locale.toString(i: 34), after: QLatin1String("99"));
87 example.replace(before: locale.toString(i: 56), after: QLatin1String("99"));
88 example.replace(before: locale.toString(i: 789), after: QLatin1String("900"));
89
90 // See if this time format contains a specifier for
91 // AM/PM, regardless of case.
92 int ampmPos = format.indexOf(s: QLatin1String("AP"), from: 0, cs: Qt::CaseInsensitive);
93
94 if (ampmPos != -1) {
95 // Get the locale aware am/pm strings
96 QString am = locale.amText();
97 QString pm = locale.pmText();
98
99 int ampmLen = qMax(a: am.length(), b: pm.length());
100 const QString ampmMask(ampmLen, QLatin1Char('x'));
101 example.replace(before: pm, after: ampmMask, cs: Qt::CaseInsensitive);
102 }
103
104 // Build a mask by copying mask characters and escaping the rest.
105 QString mask;
106 QString null;
107 for (const QChar c : example) {
108 if (c == QLatin1Char('0') || c == QLatin1Char('9') || c == QLatin1Char('x')) {
109 mask.append(c);
110 } else {
111 mask.append(c: QLatin1Char('\\'));
112 mask.append(c);
113 null.append(c);
114 }
115 }
116
117 return std::make_pair(x&: mask, y&: null);
118}
119
120QTime KTimeComboBoxPrivate::nearestIntervalTime(const QTime &time)
121{
122 int i = 0;
123 while (q->itemData(index: i).toTime() < time) {
124 ++i;
125 }
126 QTime before = q->itemData(index: i).toTime();
127 QTime after = q->itemData(index: i + 1).toTime();
128 if (before.secsTo(t: time) <= time.secsTo(t: after)) {
129 return before;
130 } else {
131 return after;
132 }
133}
134
135QString KTimeComboBoxPrivate::formatTime(const QTime &time)
136{
137 return q->locale().toString(time, format: m_displayFormat);
138}
139
140void KTimeComboBoxPrivate::initTimeWidget()
141{
142 q->blockSignals(b: true);
143 q->clear();
144
145 // Set the input mask from the current format
146 QString mask;
147 std::tie(args&: mask, args&: m_nullString) = timeFormatToInputMask(format: q->locale().timeFormat(format: m_displayFormat));
148 q->lineEdit()->setInputMask(mask);
149
150 // If EditTime then set the line edit
151 q->lineEdit()->setReadOnly((m_options & KTimeComboBox::EditTime) != KTimeComboBox::EditTime);
152
153 // If SelectTime then make list items visible
154 if ((m_options & KTimeComboBox::SelectTime) == KTimeComboBox::SelectTime) {
155 q->setMaxVisibleItems(10);
156 } else {
157 q->setMaxVisibleItems(0);
158 }
159
160 // Populate the drop-down time list
161 // If no time list set the use the time interval
162 if (m_timeList.isEmpty()) {
163 QTime startTime = m_minTime;
164 QTime thisTime(startTime.hour(), 0, 0, 0);
165 while (thisTime.isValid() && thisTime <= startTime) {
166 thisTime = thisTime.addSecs(secs: m_timeListInterval * 60);
167 }
168 QTime endTime = m_maxTime;
169 q->addItem(atext: formatTime(time: startTime), auserData: startTime);
170 while (thisTime.isValid() && thisTime < endTime) {
171 q->addItem(atext: formatTime(time: thisTime), auserData: thisTime);
172 QTime newTime = thisTime.addSecs(secs: m_timeListInterval * 60);
173 if (newTime.isValid() && newTime > thisTime) {
174 thisTime = newTime;
175 } else {
176 thisTime = QTime();
177 }
178 }
179 q->addItem(atext: formatTime(time: endTime), auserData: endTime);
180 } else {
181 for (const QTime &thisTime : std::as_const(t&: m_timeList)) {
182 if (thisTime.isValid() && thisTime >= m_minTime && thisTime <= m_maxTime) {
183 q->addItem(atext: formatTime(time: thisTime), auserData: thisTime);
184 }
185 }
186 }
187 q->blockSignals(b: false);
188}
189
190void KTimeComboBoxPrivate::updateTimeWidget()
191{
192 q->blockSignals(b: true);
193 int pos = q->lineEdit()->cursorPosition();
194 // Set index before setting text otherwise it overwrites
195 int i = 0;
196 if (!m_time.isValid() || m_time < m_minTime) {
197 i = 0;
198 } else if (m_time > m_maxTime) {
199 i = q->count() - 1;
200 } else {
201 while (q->itemData(index: i).toTime() < m_time && i < q->count() - 1) {
202 ++i;
203 }
204 }
205 q->setCurrentIndex(i);
206 if (m_time.isValid()) {
207 q->lineEdit()->setText(formatTime(time: m_time));
208 } else {
209 q->lineEdit()->setText(QString());
210 }
211 q->lineEdit()->setCursorPosition(pos);
212 q->blockSignals(b: false);
213}
214
215void KTimeComboBoxPrivate::selectTime(int index)
216{
217 enterTime(time: q->itemData(index).toTime());
218}
219
220void KTimeComboBoxPrivate::editTime(const QString &text)
221{
222 m_warningShown = false;
223 Q_EMIT q->timeEdited(time: q->locale().toTime(string: text, m_displayFormat));
224}
225
226void KTimeComboBoxPrivate::parseTime()
227{
228 m_time = q->locale().toTime(string: q->lineEdit()->text(), m_displayFormat);
229}
230
231void KTimeComboBoxPrivate::enterTime(const QTime &time)
232{
233 q->setTime(time);
234 warnTime();
235 Q_EMIT q->timeEntered(time: m_time);
236}
237
238void KTimeComboBoxPrivate::warnTime()
239{
240 if (!m_warningShown && !q->isValid() && (m_options & KTimeComboBox::WarnOnInvalid) == KTimeComboBox::WarnOnInvalid) {
241 QString warnMsg;
242 if (!m_time.isValid()) {
243 warnMsg = KTimeComboBox::tr(s: "The time you entered is invalid", c: "@info");
244 } else if (m_time < m_minTime) {
245 if (m_minWarnMsg.isEmpty()) {
246 warnMsg = KTimeComboBox::tr(s: "Time cannot be earlier than %1", c: "@info").arg(a: formatTime(time: m_minTime));
247 } else {
248 warnMsg = m_minWarnMsg;
249 warnMsg.replace(before: QLatin1String("%1"), after: formatTime(time: m_minTime));
250 }
251 } else if (m_time > m_maxTime) {
252 if (m_maxWarnMsg.isEmpty()) {
253 warnMsg = KTimeComboBox::tr(s: "Time cannot be later than %1", c: "@info").arg(a: formatTime(time: m_maxTime));
254 } else {
255 warnMsg = m_maxWarnMsg;
256 warnMsg.replace(before: QLatin1String("%1"), after: formatTime(time: m_maxTime));
257 }
258 }
259 m_warningShown = true;
260 KMessageBox::error(parent: q, text: warnMsg);
261 }
262}
263
264KTimeComboBox::KTimeComboBox(QWidget *parent)
265 : QComboBox(parent)
266 , d(new KTimeComboBoxPrivate(this))
267{
268 setEditable(true);
269 setInsertPolicy(QComboBox::NoInsert);
270 setSizeAdjustPolicy(QComboBox::AdjustToContents);
271 d->initTimeWidget();
272 d->updateTimeWidget();
273
274 connect(sender: this, signal: &QComboBox::activated, context: this, slot: [this](int value) {
275 d->selectTime(index: value);
276 });
277 connect(sender: this, signal: &QComboBox::editTextChanged, context: this, slot: [this](const QString &str) {
278 d->editTime(text: str);
279 });
280}
281
282KTimeComboBox::~KTimeComboBox() = default;
283
284QTime KTimeComboBox::time() const
285{
286 d->parseTime();
287 return d->m_time;
288}
289
290void KTimeComboBox::setTime(const QTime &time)
291{
292 if (time == d->m_time) {
293 return;
294 }
295
296 if ((d->m_options & KTimeComboBox::ForceTime) == KTimeComboBox::ForceTime) {
297 assignTime(time: d->nearestIntervalTime(time));
298 } else {
299 assignTime(time);
300 }
301
302 d->updateTimeWidget();
303 Q_EMIT timeChanged(time: d->m_time);
304}
305
306void KTimeComboBox::assignTime(const QTime &time)
307{
308 d->m_time = time;
309}
310
311bool KTimeComboBox::isValid() const
312{
313 d->parseTime();
314 return d->m_time.isValid() && d->m_time >= d->m_minTime && d->m_time <= d->m_maxTime;
315}
316
317bool KTimeComboBox::isNull() const
318{
319 return lineEdit()->text() == d->m_nullString;
320}
321
322KTimeComboBox::Options KTimeComboBox::options() const
323{
324 return d->m_options;
325}
326
327void KTimeComboBox::setOptions(Options options)
328{
329 if (options != d->m_options) {
330 d->m_options = options;
331 d->initTimeWidget();
332 d->updateTimeWidget();
333 }
334}
335
336QTime KTimeComboBox::minimumTime() const
337{
338 return d->m_minTime;
339}
340
341void KTimeComboBox::setMinimumTime(const QTime &minTime, const QString &minWarnMsg)
342{
343 setTimeRange(minTime, maxTime: d->m_maxTime, minWarnMsg, maxWarnMsg: d->m_maxWarnMsg);
344}
345
346void KTimeComboBox::resetMinimumTime()
347{
348 setTimeRange(minTime: d->defaultMinTime(), maxTime: d->m_maxTime, minWarnMsg: QString(), maxWarnMsg: d->m_maxWarnMsg);
349}
350
351QTime KTimeComboBox::maximumTime() const
352{
353 return d->m_maxTime;
354}
355
356void KTimeComboBox::setMaximumTime(const QTime &maxTime, const QString &maxWarnMsg)
357{
358 setTimeRange(minTime: d->m_minTime, maxTime, minWarnMsg: d->m_minWarnMsg, maxWarnMsg);
359}
360
361void KTimeComboBox::resetMaximumTime()
362{
363 setTimeRange(minTime: d->m_minTime, maxTime: d->defaultMaxTime(), minWarnMsg: d->m_minWarnMsg, maxWarnMsg: QString());
364}
365
366void KTimeComboBox::setTimeRange(const QTime &minTime, const QTime &maxTime, const QString &minWarnMsg, const QString &maxWarnMsg)
367{
368 if (!minTime.isValid() || !maxTime.isValid() || minTime > maxTime) {
369 return;
370 }
371
372 if (minTime != d->m_minTime || maxTime != d->m_maxTime //
373 || minWarnMsg != d->m_minWarnMsg || maxWarnMsg != d->m_maxWarnMsg) {
374 d->m_minTime = minTime;
375 d->m_maxTime = maxTime;
376 d->m_minWarnMsg = minWarnMsg;
377 d->m_maxWarnMsg = maxWarnMsg;
378 d->initTimeWidget();
379 d->updateTimeWidget();
380 }
381}
382
383void KTimeComboBox::resetTimeRange()
384{
385 setTimeRange(minTime: d->defaultMinTime(), maxTime: d->defaultMaxTime(), minWarnMsg: QString(), maxWarnMsg: QString());
386}
387
388QLocale::FormatType KTimeComboBox::displayFormat() const
389{
390 return d->m_displayFormat;
391}
392
393void KTimeComboBox::setDisplayFormat(QLocale::FormatType format)
394{
395 if (format != d->m_displayFormat) {
396 d->m_displayFormat = format;
397 d->initTimeWidget();
398 d->updateTimeWidget();
399 }
400}
401
402int KTimeComboBox::timeListInterval() const
403{
404 return d->m_timeListInterval;
405}
406
407void KTimeComboBox::setTimeListInterval(int minutes)
408{
409 if (minutes != d->m_timeListInterval) {
410 // Must be able to exactly divide the valid time period
411 int lowMins = (d->m_minTime.hour() * 60) + d->m_minTime.minute();
412 int hiMins = (d->m_maxTime.hour() * 60) + d->m_maxTime.minute();
413 if (d->m_minTime.minute() == 0 && d->m_maxTime.minute() == 59) {
414 ++hiMins;
415 }
416 if ((hiMins - lowMins) % minutes == 0) {
417 d->m_timeListInterval = minutes;
418 d->m_timeList.clear();
419 } else {
420 return;
421 }
422 d->initTimeWidget();
423 }
424}
425
426QList<QTime> KTimeComboBox::timeList() const
427{
428 // Return the drop down list as it is what can be selected currently
429 QList<QTime> list;
430 int c = count();
431 list.reserve(asize: c);
432 for (int i = 0; i < c; ++i) {
433 list.append(t: itemData(index: i).toTime());
434 }
435 return list;
436}
437
438void KTimeComboBox::setTimeList(QList<QTime> timeList, const QString &minWarnMsg, const QString &maxWarnMsg)
439{
440 if (timeList != d->m_timeList) {
441 d->m_timeList.clear();
442 for (const QTime &time : std::as_const(t&: timeList)) {
443 if (time.isValid() && !d->m_timeList.contains(t: time)) {
444 d->m_timeList.append(t: time);
445 }
446 }
447 std::sort(first: d->m_timeList.begin(), last: d->m_timeList.end());
448 // Does the updateTimeWidget call for us
449 setTimeRange(minTime: d->m_timeList.first(), maxTime: d->m_timeList.last(), minWarnMsg, maxWarnMsg);
450 }
451}
452
453bool KTimeComboBox::eventFilter(QObject *object, QEvent *event)
454{
455 return QComboBox::eventFilter(watched: object, event);
456}
457
458void KTimeComboBox::keyPressEvent(QKeyEvent *keyEvent)
459{
460 QTime temp;
461 switch (keyEvent->key()) {
462 case Qt::Key_Down:
463 temp = d->m_time.addSecs(secs: -60);
464 break;
465 case Qt::Key_Up:
466 temp = d->m_time.addSecs(secs: 60);
467 break;
468 case Qt::Key_PageDown:
469 temp = d->m_time.addSecs(secs: -3600);
470 break;
471 case Qt::Key_PageUp:
472 temp = d->m_time.addSecs(secs: 3600);
473 break;
474 default:
475 QComboBox::keyPressEvent(e: keyEvent);
476 return;
477 }
478 if (temp.isValid() && temp >= d->m_minTime && temp <= d->m_maxTime) {
479 d->enterTime(time: temp);
480 }
481}
482
483void KTimeComboBox::focusOutEvent(QFocusEvent *event)
484{
485 d->parseTime();
486 d->warnTime();
487 QComboBox::focusOutEvent(e: event);
488}
489
490void KTimeComboBox::showPopup()
491{
492 QComboBox::showPopup();
493}
494
495void KTimeComboBox::hidePopup()
496{
497 QComboBox::hidePopup();
498}
499
500void KTimeComboBox::mousePressEvent(QMouseEvent *event)
501{
502 QComboBox::mousePressEvent(e: event);
503}
504
505void KTimeComboBox::wheelEvent(QWheelEvent *event)
506{
507 QComboBox::wheelEvent(e: event);
508}
509
510void KTimeComboBox::focusInEvent(QFocusEvent *event)
511{
512 QComboBox::focusInEvent(e: event);
513}
514
515void KTimeComboBox::resizeEvent(QResizeEvent *event)
516{
517 QComboBox::resizeEvent(e: event);
518}
519
520#include "moc_ktimecombobox.cpp"
521

source code of kwidgetsaddons/src/ktimecombobox.cpp