KWidgetsAddons

ktimecombobox.cpp
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{
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(QTime(12, 34, 56, 789));
84 // Replace time components with edit mask characters.
85 example.replace(locale.toString(12), QLatin1String("09"));
86 example.replace(locale.toString(34), QLatin1String("99"));
87 example.replace(locale.toString(56), QLatin1String("99"));
88 example.replace(locale.toString(789), QLatin1String("900"));
89
90 // See if this time format contains a specifier for
91 // AM/PM, regardless of case.
92 int ampmPos = format.indexOf(QLatin1String("AP"), 0, 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(am.length(), pm.length());
100 const QString ampmMask(ampmLen, QLatin1Char('x'));
101 example.replace(pm, ampmMask, 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(QLatin1Char('\\'));
112 mask.append(c);
113 null.append(c);
114 }
115 }
116
117 return std::make_pair(mask, null);
118}
119
120QTime KTimeComboBoxPrivate::nearestIntervalTime(const QTime &time)
121{
122 int i = 0;
123 while (q->itemData(i).toTime() < time) {
124 ++i;
125 }
126 QTime before = q->itemData(i).toTime();
127 QTime after = q->itemData(i + 1).toTime();
128 if (before.secsTo(time) <= time.secsTo(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, m_displayFormat);
138}
139
140void KTimeComboBoxPrivate::initTimeWidget()
141{
142 q->blockSignals(true);
143 q->clear();
144
145 // Set the input mask from the current format
146 QString mask;
147 std::tie(mask, m_nullString) = timeFormatToInputMask(q->locale().timeFormat(m_displayFormat));
148 q->lineEdit()->setInputMask(mask);
149
150 // If EditTime then set the line edit
152
153 // If SelectTime then make list items visible
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(m_timeListInterval * 60);
167 }
168 QTime endTime = m_maxTime;
169 q->addItem(formatTime(startTime), startTime);
170 while (thisTime.isValid() && thisTime < endTime) {
171 q->addItem(formatTime(thisTime), thisTime);
172 QTime newTime = thisTime.addSecs(m_timeListInterval * 60);
173 if (newTime.isValid() && newTime > thisTime) {
174 thisTime = newTime;
175 } else {
176 thisTime = QTime();
177 }
178 }
179 q->addItem(formatTime(endTime), endTime);
180 } else {
181 for (const QTime &thisTime : std::as_const(m_timeList)) {
182 if (thisTime.isValid() && thisTime >= m_minTime && thisTime <= m_maxTime) {
183 q->addItem(formatTime(thisTime), thisTime);
184 }
185 }
186 }
187 q->blockSignals(false);
188}
189
190void KTimeComboBoxPrivate::updateTimeWidget()
191{
192 q->blockSignals(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(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(m_time));
208 } else {
209 q->lineEdit()->setText(QString());
210 }
211 q->lineEdit()->setCursorPosition(pos);
212 q->blockSignals(false);
213}
214
215void KTimeComboBoxPrivate::selectTime(int index)
216{
217 enterTime(q->itemData(index).toTime());
218}
219
220void KTimeComboBoxPrivate::editTime(const QString &text)
221{
222 m_warningShown = false;
223 Q_EMIT q->timeEdited(q->locale().toTime(text, m_displayFormat));
224}
225
226void KTimeComboBoxPrivate::parseTime()
227{
228 m_time = q->locale().toTime(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(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("The time you entered is invalid", "@info");
244 } else if (m_time < m_minTime) {
245 if (m_minWarnMsg.isEmpty()) {
246 warnMsg = KTimeComboBox::tr("Time cannot be earlier than %1", "@info").arg(formatTime(m_minTime));
247 } else {
248 warnMsg = m_minWarnMsg;
249 warnMsg.replace(QLatin1String("%1"), formatTime(m_minTime));
250 }
251 } else if (m_time > m_maxTime) {
252 if (m_maxWarnMsg.isEmpty()) {
253 warnMsg = KTimeComboBox::tr("Time cannot be later than %1", "@info").arg(formatTime(m_maxTime));
254 } else {
255 warnMsg = m_maxWarnMsg;
256 warnMsg.replace(QLatin1String("%1"), formatTime(m_maxTime));
257 }
258 }
259 m_warningShown = true;
260 KMessageBox::error(q, warnMsg);
261 }
262}
263
265 : QComboBox(parent)
266 , d(new KTimeComboBoxPrivate(this))
267{
268 setEditable(true);
271 d->initTimeWidget();
272 d->updateTimeWidget();
273
274 connect(this, &QComboBox::activated, this, [this](int value) {
275 d->selectTime(value);
276 });
277 connect(this, &QComboBox::editTextChanged, this, [this](const QString &str) {
278 d->editTime(str);
279 });
280}
281
283
284QTime KTimeComboBox::time() const
285{
286 d->parseTime();
287 return d->m_time;
288}
289
291{
292 if (time == d->m_time) {
293 return;
294 }
295
296 if ((d->m_options & KTimeComboBox::ForceTime) == KTimeComboBox::ForceTime) {
297 assignTime(d->nearestIntervalTime(time));
298 } else {
299 assignTime(time);
300 }
301
302 d->updateTimeWidget();
303 Q_EMIT timeChanged(d->m_time);
304}
305
307{
308 d->m_time = time;
309}
310
312{
313 d->parseTime();
314 return d->m_time.isValid() && d->m_time >= d->m_minTime && d->m_time <= d->m_maxTime;
315}
316
318{
319 return lineEdit()->text() == d->m_nullString;
320}
321
322KTimeComboBox::Options KTimeComboBox::options() const
323{
324 return d->m_options;
325}
326
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, d->m_maxTime, minWarnMsg, d->m_maxWarnMsg);
344}
345
347{
348 setTimeRange(d->defaultMinTime(), d->m_maxTime, QString(), 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(d->m_minTime, maxTime, d->m_minWarnMsg, maxWarnMsg);
359}
360
362{
363 setTimeRange(d->m_minTime, d->defaultMaxTime(), d->m_minWarnMsg, 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
384{
385 setTimeRange(d->defaultMinTime(), d->defaultMaxTime(), QString(), QString());
386}
387
389{
390 return d->m_displayFormat;
391}
392
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
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
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(c);
432 for (int i = 0; i < c; ++i) {
433 list.append(itemData(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(timeList)) {
443 if (time.isValid() && !d->m_timeList.contains(time)) {
444 d->m_timeList.append(time);
445 }
446 }
447 std::sort(d->m_timeList.begin(), d->m_timeList.end());
448 // Does the updateTimeWidget call for us
449 setTimeRange(d->m_timeList.first(), d->m_timeList.last(), minWarnMsg, maxWarnMsg);
450 }
451}
452
453bool KTimeComboBox::eventFilter(QObject *object, QEvent *event)
454{
455 return QComboBox::eventFilter(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(-60);
464 break;
465 case Qt::Key_Up:
466 temp = d->m_time.addSecs(60);
467 break;
468 case Qt::Key_PageDown:
469 temp = d->m_time.addSecs(-3600);
470 break;
471 case Qt::Key_PageUp:
472 temp = d->m_time.addSecs(3600);
473 break;
474 default:
475 QComboBox::keyPressEvent(keyEvent);
476 return;
477 }
478 if (temp.isValid() && temp >= d->m_minTime && temp <= d->m_maxTime) {
479 d->enterTime(temp);
480 }
481}
482
483void KTimeComboBox::focusOutEvent(QFocusEvent *event)
484{
485 d->parseTime();
486 d->warnTime();
488}
489
490void KTimeComboBox::showPopup()
491{
493}
494
495void KTimeComboBox::hidePopup()
496{
498}
499
500void KTimeComboBox::mousePressEvent(QMouseEvent *event)
501{
503}
504
505void KTimeComboBox::wheelEvent(QWheelEvent *event)
506{
508}
509
510void KTimeComboBox::focusInEvent(QFocusEvent *event)
511{
513}
514
515void KTimeComboBox::resizeEvent(QResizeEvent *event)
516{
518}
519
520#include "moc_ktimecombobox.cpp"
A combobox for times.
KTimeComboBox(QWidget *parent=nullptr)
Create a new KTimeComboBox widget.
~KTimeComboBox() override
Destroy the widget.
void setTimeListInterval(int minutes)
Set the interval between times able to be selected from the drop-down.
@ ForceTime
Any set or entered time will be forced to one of the drop-down times.
@ SelectTime
Allow the user to select the time from a drop-down menu.
@ WarnOnInvalid
Show a warning box on focus out if the user enters an invalid time.
@ EditTime
Allow the user to manually edit the time in the combo line edit.
QLocale::FormatType displayFormat() const
Return the currently set time format.
void resetMinimumTime()
Reset the minimum time to the default of 00:00:00.000.
void setMaximumTime(const QTime &maxTime, const QString &maxWarnMsg=QString())
Set the maximum allowed time.
void resetMaximumTime()
Reset the maximum time to the default of 23:59:59.999.
virtual void assignTime(const QTime &time)
Assign the time for the widget.
bool isNull() const
Return if the current user input is null.
void resetTimeRange()
Reset the minimum and maximum time to the default values.
void setOptions(Options options)
Set the new widget options.
void setTimeRange(const QTime &minTime, const QTime &maxTime, const QString &minWarnMsg=QString(), const QString &maxWarnMsg=QString())
Set the minimum and maximum time range.
void timeEntered(const QTime &time)
Signal if the time has been manually entered or selected by the user.
void setDisplayFormat(QLocale::FormatType format)
Sets the time format to display.
void setTime(const QTime &time)
Set the currently selected time.
QList< QTime > timeList() const
Return the list of times able to be selected in the drop-down.
bool isValid() const
Return if the current user input is valid.
void setTimeList(QList< QTime > timeList, const QString &minWarnMsg=QString(), const QString &maxWarnMsg=QString())
Set the list of times able to be selected from the drop-down.
void setMinimumTime(const QTime &minTime, const QString &minWarnMsg=QString())
Set the minimum allowed time.
void timeChanged(const QTime &time)
Signal if the time has been changed either manually by the user or programmatically.
void timeEdited(const QTime &time)
Signal if the time is being manually edited by the user.
void error(QWidget *parent, const QString &text, const QString &title, Options options)
Display an "Error" dialog.
void activated(int index)
void addItem(const QIcon &icon, const QString &text, const QVariant &userData)
void clear()
void setCurrentIndex(int index)
void editTextChanged(const QString &text)
void setEditable(bool editable)
virtual bool event(QEvent *event) override
virtual void focusInEvent(QFocusEvent *e) override
virtual void focusOutEvent(QFocusEvent *e) override
virtual void hidePopup()
void setInsertPolicy(InsertPolicy policy)
QVariant itemData(int index, int role) const const
virtual void keyPressEvent(QKeyEvent *e) override
QLineEdit * lineEdit() const const
void setMaxVisibleItems(int maxItems)
virtual void mousePressEvent(QMouseEvent *e) override
virtual void resizeEvent(QResizeEvent *e) override
virtual void showPopup()
void setSizeAdjustPolicy(SizeAdjustPolicy policy)
virtual void wheelEvent(QWheelEvent *e) override
void setInputMask(const QString &inputMask)
void setReadOnly(bool)
void setText(const QString &)
void append(QList< T > &&value)
bool isEmpty() const const
void reserve(qsizetype size)
QString amText() const const
QString pmText() const const
QString toString(QDate date, FormatType format) const const
Q_EMITQ_EMIT
bool blockSignals(bool block)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual bool eventFilter(QObject *watched, QEvent *event)
QString tr(const char *sourceText, const char *disambiguation, int n)
QString & append(QChar ch)
QString arg(Args &&... args) const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
CaseInsensitive
Key_Down
void keyEvent(KeyAction action, QWidget *widget, Qt::Key key, Qt::KeyboardModifiers modifier, int delay)
QTime addSecs(int s) const const
int hour() const const
bool isValid(int h, int m, int s, int ms)
int secsTo(QTime t) const const
QTime toTime() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 25 2024 12:00:45 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.