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 // 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(am.length(), pm.length());
111 const QString ampmMask(ampmLen, QLatin1Char('x'));
112 example.replace(pm, 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(QLatin1Char('\\'));
123 mask.append(c);
124 null.append(c);
125 }
126 }
127
128 return std::make_pair(mask, null);
129}
130
131QTime KTimeComboBoxPrivate::nearestIntervalTime(const QTime &time)
132{
133 int i = 0;
134 while (q->itemData(i).toTime() < time) {
135 ++i;
136 }
137 QTime before = q->itemData(i).toTime();
138 QTime after = q->itemData(i + 1).toTime();
139 if (before.secsTo(time) <= time.secsTo(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, m_displayFormat);
149}
150
151void KTimeComboBoxPrivate::initTimeWidget()
152{
153 q->blockSignals(true);
154 q->clear();
155
156 // Set the input mask from the current format
157 QString mask;
158 std::tie(mask, m_nullString) = timeFormatToInputMask(q->locale().timeFormat(m_displayFormat));
159 q->lineEdit()->setInputMask(mask);
160
161 // If EditTime then set the line edit
163
164 // If SelectTime then make list items visible
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(m_timeListInterval * 60);
178 }
179 QTime endTime = m_maxTime;
180 q->addItem(formatTime(startTime), startTime);
181 while (thisTime.isValid() && thisTime < endTime) {
182 q->addItem(formatTime(thisTime), thisTime);
183 QTime newTime = thisTime.addSecs(m_timeListInterval * 60);
184 if (newTime.isValid() && newTime > thisTime) {
185 thisTime = newTime;
186 } else {
187 thisTime = QTime();
188 }
189 }
190 q->addItem(formatTime(endTime), endTime);
191 } else {
192 for (const QTime &thisTime : std::as_const(m_timeList)) {
193 if (thisTime.isValid() && thisTime >= m_minTime && thisTime <= m_maxTime) {
194 q->addItem(formatTime(thisTime), thisTime);
195 }
196 }
197 }
198 q->blockSignals(false);
199}
200
201void KTimeComboBoxPrivate::updateTimeWidget()
202{
203 q->blockSignals(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(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(m_time));
219 } else {
220 q->lineEdit()->setText(QString());
221 }
222 q->lineEdit()->setCursorPosition(pos);
223 q->blockSignals(false);
224}
225
226void KTimeComboBoxPrivate::selectTime(int index)
227{
228 enterTime(q->itemData(index).toTime());
229}
230
231void KTimeComboBoxPrivate::editTime(const QString &text)
232{
233 m_warningShown = false;
234 Q_EMIT q->timeEdited(q->locale().toTime(text, m_displayFormat));
235}
236
237void KTimeComboBoxPrivate::parseTime()
238{
239 m_time = q->locale().toTime(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(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("The time you entered is invalid", "@info");
255 } else if (m_time < m_minTime) {
256 if (m_minWarnMsg.isEmpty()) {
257 warnMsg = KTimeComboBox::tr("Time cannot be earlier than %1", "@info").arg(formatTime(m_minTime));
258 } else {
259 warnMsg = m_minWarnMsg;
260 warnMsg.replace(QLatin1String("%1"), formatTime(m_minTime));
261 }
262 } else if (m_time > m_maxTime) {
263 if (m_maxWarnMsg.isEmpty()) {
264 warnMsg = KTimeComboBox::tr("Time cannot be later than %1", "@info").arg(formatTime(m_maxTime));
265 } else {
266 warnMsg = m_maxWarnMsg;
267 warnMsg.replace(QLatin1String("%1"), formatTime(m_maxTime));
268 }
269 }
270 m_warningShown = true;
271 KMessageBox::error(q, warnMsg);
272 }
273}
274
276 : QComboBox(parent)
277 , d(new KTimeComboBoxPrivate(this))
278{
279 setEditable(true);
282 d->initTimeWidget();
283 d->updateTimeWidget();
284
285 connect(this, &QComboBox::activated, this, [this](int value) {
286 d->selectTime(value);
287 });
288 connect(this, &QComboBox::editTextChanged, this, [this](const QString &str) {
289 d->editTime(str);
290 });
291}
292
294
295QTime KTimeComboBox::time() const
296{
297 d->parseTime();
298 return d->m_time;
299}
300
302{
303 if (time == d->m_time) {
304 return;
305 }
306
307 if ((d->m_options & KTimeComboBox::ForceTime) == KTimeComboBox::ForceTime) {
308 assignTime(d->nearestIntervalTime(time));
309 } else {
310 assignTime(time);
311 }
312
313 d->updateTimeWidget();
314 Q_EMIT timeChanged(d->m_time);
315}
316
318{
319 d->m_time = time;
320}
321
323{
324 d->parseTime();
325 return d->m_time.isValid() && d->m_time >= d->m_minTime && d->m_time <= d->m_maxTime;
326}
327
329{
330 return lineEdit()->text() == d->m_nullString;
331}
332
333KTimeComboBox::Options KTimeComboBox::options() const
334{
335 return d->m_options;
336}
337
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, d->m_maxTime, minWarnMsg, d->m_maxWarnMsg);
355}
356
358{
359 setTimeRange(d->defaultMinTime(), d->m_maxTime, QString(), 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(d->m_minTime, maxTime, d->m_minWarnMsg, maxWarnMsg);
370}
371
373{
374 setTimeRange(d->m_minTime, d->defaultMaxTime(), d->m_minWarnMsg, 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
395{
396 setTimeRange(d->defaultMinTime(), d->defaultMaxTime(), QString(), QString());
397}
398
400{
401 return d->m_displayFormat;
402}
403
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
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
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(c);
443 for (int i = 0; i < c; ++i) {
444 list.append(itemData(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(timeList)) {
454 if (time.isValid() && !d->m_timeList.contains(time)) {
455 d->m_timeList.append(time);
456 }
457 }
458 std::sort(d->m_timeList.begin(), d->m_timeList.end());
459 // Does the updateTimeWidget call for us
460 setTimeRange(d->m_timeList.first(), d->m_timeList.last(), minWarnMsg, maxWarnMsg);
461 }
462}
463
464bool KTimeComboBox::eventFilter(QObject *object, QEvent *event)
465{
466 return QComboBox::eventFilter(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(-60);
475 break;
476 case Qt::Key_Up:
477 temp = d->m_time.addSecs(60);
478 break;
479 case Qt::Key_PageDown:
480 temp = d->m_time.addSecs(-3600);
481 break;
482 case Qt::Key_PageUp:
483 temp = d->m_time.addSecs(3600);
484 break;
485 default:
486 QComboBox::keyPressEvent(keyEvent);
487 return;
488 }
489 if (temp.isValid() && temp >= d->m_minTime && temp <= d->m_maxTime) {
490 d->enterTime(temp);
491 }
492}
493
494void KTimeComboBox::focusOutEvent(QFocusEvent *event)
495{
496 d->parseTime();
497 d->warnTime();
499}
500
501void KTimeComboBox::showPopup()
502{
504}
505
506void KTimeComboBox::hidePopup()
507{
509}
510
511void KTimeComboBox::mousePressEvent(QMouseEvent *event)
512{
514}
515
516void KTimeComboBox::wheelEvent(QWheelEvent *event)
517{
519}
520
521void KTimeComboBox::focusInEvent(QFocusEvent *event)
522{
524}
525
526void KTimeComboBox::resizeEvent(QResizeEvent *event)
527{
529}
530
531#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)
T qobject_cast(QObject *object)
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)
QString toLower() const const
QString toUpper() const const
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 May 3 2024 11:45:27 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.