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

source code of kwidgetsaddons/src/ktimecombobox.cpp