File indexing completed on 2025-02-16 13:11:51
0001 /* 0002 SPDX-FileCopyrightText: 2011 John Layt <john@layt.net> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "ktimecombobox.h" 0008 0009 #include <QKeyEvent> 0010 #include <QLineEdit> 0011 #include <QTime> 0012 0013 #include "kmessagebox.h" 0014 0015 class KTimeComboBoxPrivate 0016 { 0017 public: 0018 KTimeComboBoxPrivate(KTimeComboBox *qq); 0019 virtual ~KTimeComboBoxPrivate(); 0020 0021 QTime defaultMinTime(); 0022 QTime defaultMaxTime(); 0023 0024 std::pair<QString, QString> timeFormatToInputMask(const QString &format); 0025 QTime nearestIntervalTime(const QTime &time); 0026 QString formatTime(const QTime &time); 0027 0028 void initTimeWidget(); 0029 void updateTimeWidget(); 0030 0031 // Private slots 0032 void selectTime(int index); 0033 void editTime(const QString &text); 0034 void enterTime(const QTime &time); 0035 void parseTime(); 0036 void warnTime(); 0037 0038 KTimeComboBox *const q; 0039 0040 QTime m_time; 0041 KTimeComboBox::Options m_options; 0042 QTime m_minTime; 0043 QTime m_maxTime; 0044 QString m_minWarnMsg; 0045 QString m_maxWarnMsg; 0046 QString m_nullString; 0047 bool m_warningShown; 0048 QLocale::FormatType m_displayFormat; 0049 int m_timeListInterval; 0050 QList<QTime> m_timeList; 0051 }; 0052 0053 KTimeComboBoxPrivate::KTimeComboBoxPrivate(KTimeComboBox *qq) 0054 : q(qq) 0055 , m_time(QTime(0, 0, 0)) 0056 , m_warningShown(false) 0057 , m_displayFormat(QLocale::ShortFormat) 0058 , m_timeListInterval(15) 0059 { 0060 m_options = KTimeComboBox::EditTime | KTimeComboBox::SelectTime; 0061 m_minTime = defaultMinTime(); 0062 m_maxTime = defaultMaxTime(); 0063 } 0064 0065 KTimeComboBoxPrivate::~KTimeComboBoxPrivate() 0066 { 0067 } 0068 0069 QTime KTimeComboBoxPrivate::defaultMinTime() 0070 { 0071 return QTime(0, 0, 0, 0); 0072 } 0073 0074 QTime KTimeComboBoxPrivate::defaultMaxTime() 0075 { 0076 return QTime(23, 59, 59, 999); 0077 } 0078 0079 std::pair<QString, QString> KTimeComboBoxPrivate::timeFormatToInputMask(const QString &format) 0080 { 0081 const QLocale locale = q->locale(); 0082 0083 QString example = formatTime(QTime(12, 34, 56, 789)); 0084 // Replace time components with edit mask characters. 0085 example.replace(locale.toString(12), QLatin1String("09")); 0086 example.replace(locale.toString(34), QLatin1String("99")); 0087 example.replace(locale.toString(56), QLatin1String("99")); 0088 example.replace(locale.toString(789), QLatin1String("900")); 0089 0090 // See if this time format contains a specifier for 0091 // AM/PM, regardless of case. 0092 int ampmPos = format.indexOf(QLatin1String("AP"), 0, Qt::CaseInsensitive); 0093 0094 if (ampmPos != -1) { 0095 // Get the locale aware am/pm strings 0096 QString am = locale.amText(); 0097 QString pm = locale.pmText(); 0098 0099 // Convert the am/pm strings to the same case 0100 // as the input format. This is necessary to 0101 // provide a correct mask to the line edit. 0102 if (format[ampmPos].isUpper()) { 0103 am = am.toUpper(); 0104 pm = pm.toUpper(); 0105 } else { 0106 am = am.toLower(); 0107 pm = pm.toLower(); 0108 } 0109 0110 int ampmLen = qMax(am.length(), pm.length()); 0111 const QString ampmMask(ampmLen, QLatin1Char('x')); 0112 example.replace(pm, ampmMask); 0113 } 0114 0115 // Build a mask by copying mask characters and escaping the rest. 0116 QString mask; 0117 QString null; 0118 for (const QChar c : example) { 0119 if (c == QLatin1Char('0') || c == QLatin1Char('9') || c == QLatin1Char('x')) { 0120 mask.append(c); 0121 } else { 0122 mask.append(QLatin1Char('\\')); 0123 mask.append(c); 0124 null.append(c); 0125 } 0126 } 0127 0128 return std::make_pair(mask, null); 0129 } 0130 0131 QTime KTimeComboBoxPrivate::nearestIntervalTime(const QTime &time) 0132 { 0133 int i = 0; 0134 while (q->itemData(i).toTime() < time) { 0135 ++i; 0136 } 0137 QTime before = q->itemData(i).toTime(); 0138 QTime after = q->itemData(i + 1).toTime(); 0139 if (before.secsTo(time) <= time.secsTo(after)) { 0140 return before; 0141 } else { 0142 return after; 0143 } 0144 } 0145 0146 QString KTimeComboBoxPrivate::formatTime(const QTime &time) 0147 { 0148 return q->locale().toString(time, m_displayFormat); 0149 } 0150 0151 void KTimeComboBoxPrivate::initTimeWidget() 0152 { 0153 q->blockSignals(true); 0154 q->clear(); 0155 0156 // Set the input mask from the current format 0157 QString mask; 0158 std::tie(mask, m_nullString) = timeFormatToInputMask(q->locale().timeFormat(m_displayFormat)); 0159 q->lineEdit()->setInputMask(mask); 0160 0161 // If EditTime then set the line edit 0162 q->lineEdit()->setReadOnly((m_options & KTimeComboBox::EditTime) != KTimeComboBox::EditTime); 0163 0164 // If SelectTime then make list items visible 0165 if ((m_options & KTimeComboBox::SelectTime) == KTimeComboBox::SelectTime) { 0166 q->setMaxVisibleItems(10); 0167 } else { 0168 q->setMaxVisibleItems(0); 0169 } 0170 0171 // Populate the drop-down time list 0172 // If no time list set the use the time interval 0173 if (m_timeList.isEmpty()) { 0174 QTime startTime = m_minTime; 0175 QTime thisTime(startTime.hour(), 0, 0, 0); 0176 while (thisTime.isValid() && thisTime <= startTime) { 0177 thisTime = thisTime.addSecs(m_timeListInterval * 60); 0178 } 0179 QTime endTime = m_maxTime; 0180 q->addItem(formatTime(startTime), startTime); 0181 while (thisTime.isValid() && thisTime < endTime) { 0182 q->addItem(formatTime(thisTime), thisTime); 0183 QTime newTime = thisTime.addSecs(m_timeListInterval * 60); 0184 if (newTime.isValid() && newTime > thisTime) { 0185 thisTime = newTime; 0186 } else { 0187 thisTime = QTime(); 0188 } 0189 } 0190 q->addItem(formatTime(endTime), endTime); 0191 } else { 0192 for (const QTime &thisTime : std::as_const(m_timeList)) { 0193 if (thisTime.isValid() && thisTime >= m_minTime && thisTime <= m_maxTime) { 0194 q->addItem(formatTime(thisTime), thisTime); 0195 } 0196 } 0197 } 0198 q->blockSignals(false); 0199 } 0200 0201 void KTimeComboBoxPrivate::updateTimeWidget() 0202 { 0203 q->blockSignals(true); 0204 int pos = q->lineEdit()->cursorPosition(); 0205 // Set index before setting text otherwise it overwrites 0206 int i = 0; 0207 if (!m_time.isValid() || m_time < m_minTime) { 0208 i = 0; 0209 } else if (m_time > m_maxTime) { 0210 i = q->count() - 1; 0211 } else { 0212 while (q->itemData(i).toTime() < m_time && i < q->count() - 1) { 0213 ++i; 0214 } 0215 } 0216 q->setCurrentIndex(i); 0217 if (m_time.isValid()) { 0218 q->lineEdit()->setText(formatTime(m_time)); 0219 } else { 0220 q->lineEdit()->setText(QString()); 0221 } 0222 q->lineEdit()->setCursorPosition(pos); 0223 q->blockSignals(false); 0224 } 0225 0226 void KTimeComboBoxPrivate::selectTime(int index) 0227 { 0228 enterTime(q->itemData(index).toTime()); 0229 } 0230 0231 void KTimeComboBoxPrivate::editTime(const QString &text) 0232 { 0233 m_warningShown = false; 0234 Q_EMIT q->timeEdited(q->locale().toTime(text, m_displayFormat)); 0235 } 0236 0237 void KTimeComboBoxPrivate::parseTime() 0238 { 0239 m_time = q->locale().toTime(q->lineEdit()->text(), m_displayFormat); 0240 } 0241 0242 void KTimeComboBoxPrivate::enterTime(const QTime &time) 0243 { 0244 q->setTime(time); 0245 warnTime(); 0246 Q_EMIT q->timeEntered(m_time); 0247 } 0248 0249 void KTimeComboBoxPrivate::warnTime() 0250 { 0251 if (!m_warningShown && !q->isValid() && (m_options & KTimeComboBox::WarnOnInvalid) == KTimeComboBox::WarnOnInvalid) { 0252 QString warnMsg; 0253 if (!m_time.isValid()) { 0254 warnMsg = KTimeComboBox::tr("The time you entered is invalid", "@info"); 0255 } else if (m_time < m_minTime) { 0256 if (m_minWarnMsg.isEmpty()) { 0257 warnMsg = KTimeComboBox::tr("Time cannot be earlier than %1", "@info").arg(formatTime(m_minTime)); 0258 } else { 0259 warnMsg = m_minWarnMsg; 0260 warnMsg.replace(QLatin1String("%1"), formatTime(m_minTime)); 0261 } 0262 } else if (m_time > m_maxTime) { 0263 if (m_maxWarnMsg.isEmpty()) { 0264 warnMsg = KTimeComboBox::tr("Time cannot be later than %1", "@info").arg(formatTime(m_maxTime)); 0265 } else { 0266 warnMsg = m_maxWarnMsg; 0267 warnMsg.replace(QLatin1String("%1"), formatTime(m_maxTime)); 0268 } 0269 } 0270 m_warningShown = true; 0271 KMessageBox::error(q, warnMsg); 0272 } 0273 } 0274 0275 KTimeComboBox::KTimeComboBox(QWidget *parent) 0276 : QComboBox(parent) 0277 , d(new KTimeComboBoxPrivate(this)) 0278 { 0279 setEditable(true); 0280 setInsertPolicy(QComboBox::NoInsert); 0281 setSizeAdjustPolicy(QComboBox::AdjustToContents); 0282 d->initTimeWidget(); 0283 d->updateTimeWidget(); 0284 0285 connect(this, qOverload<int>(&QComboBox::activated), this, [this](int value) { 0286 d->selectTime(value); 0287 }); 0288 connect(this, &QComboBox::editTextChanged, this, [this](const QString &str) { 0289 d->editTime(str); 0290 }); 0291 } 0292 0293 KTimeComboBox::~KTimeComboBox() = default; 0294 0295 QTime KTimeComboBox::time() const 0296 { 0297 d->parseTime(); 0298 return d->m_time; 0299 } 0300 0301 void KTimeComboBox::setTime(const QTime &time) 0302 { 0303 if (time == d->m_time) { 0304 return; 0305 } 0306 0307 if ((d->m_options & KTimeComboBox::ForceTime) == KTimeComboBox::ForceTime) { 0308 assignTime(d->nearestIntervalTime(time)); 0309 } else { 0310 assignTime(time); 0311 } 0312 0313 d->updateTimeWidget(); 0314 Q_EMIT timeChanged(d->m_time); 0315 } 0316 0317 void KTimeComboBox::assignTime(const QTime &time) 0318 { 0319 d->m_time = time; 0320 } 0321 0322 bool KTimeComboBox::isValid() const 0323 { 0324 d->parseTime(); 0325 return d->m_time.isValid() && d->m_time >= d->m_minTime && d->m_time <= d->m_maxTime; 0326 } 0327 0328 bool KTimeComboBox::isNull() const 0329 { 0330 return lineEdit()->text() == d->m_nullString; 0331 } 0332 0333 KTimeComboBox::Options KTimeComboBox::options() const 0334 { 0335 return d->m_options; 0336 } 0337 0338 void KTimeComboBox::setOptions(Options options) 0339 { 0340 if (options != d->m_options) { 0341 d->m_options = options; 0342 d->initTimeWidget(); 0343 d->updateTimeWidget(); 0344 } 0345 } 0346 0347 QTime KTimeComboBox::minimumTime() const 0348 { 0349 return d->m_minTime; 0350 } 0351 0352 void KTimeComboBox::setMinimumTime(const QTime &minTime, const QString &minWarnMsg) 0353 { 0354 setTimeRange(minTime, d->m_maxTime, minWarnMsg, d->m_maxWarnMsg); 0355 } 0356 0357 void KTimeComboBox::resetMinimumTime() 0358 { 0359 setTimeRange(d->defaultMinTime(), d->m_maxTime, QString(), d->m_maxWarnMsg); 0360 } 0361 0362 QTime KTimeComboBox::maximumTime() const 0363 { 0364 return d->m_maxTime; 0365 } 0366 0367 void KTimeComboBox::setMaximumTime(const QTime &maxTime, const QString &maxWarnMsg) 0368 { 0369 setTimeRange(d->m_minTime, maxTime, d->m_minWarnMsg, maxWarnMsg); 0370 } 0371 0372 void KTimeComboBox::resetMaximumTime() 0373 { 0374 setTimeRange(d->m_minTime, d->defaultMaxTime(), d->m_minWarnMsg, QString()); 0375 } 0376 0377 void KTimeComboBox::setTimeRange(const QTime &minTime, const QTime &maxTime, const QString &minWarnMsg, const QString &maxWarnMsg) 0378 { 0379 if (!minTime.isValid() || !maxTime.isValid() || minTime > maxTime) { 0380 return; 0381 } 0382 0383 if (minTime != d->m_minTime || maxTime != d->m_maxTime // 0384 || minWarnMsg != d->m_minWarnMsg || maxWarnMsg != d->m_maxWarnMsg) { 0385 d->m_minTime = minTime; 0386 d->m_maxTime = maxTime; 0387 d->m_minWarnMsg = minWarnMsg; 0388 d->m_maxWarnMsg = maxWarnMsg; 0389 d->initTimeWidget(); 0390 d->updateTimeWidget(); 0391 } 0392 } 0393 0394 void KTimeComboBox::resetTimeRange() 0395 { 0396 setTimeRange(d->defaultMinTime(), d->defaultMaxTime(), QString(), QString()); 0397 } 0398 0399 QLocale::FormatType KTimeComboBox::displayFormat() const 0400 { 0401 return d->m_displayFormat; 0402 } 0403 0404 void KTimeComboBox::setDisplayFormat(QLocale::FormatType format) 0405 { 0406 if (format != d->m_displayFormat) { 0407 d->m_displayFormat = format; 0408 d->initTimeWidget(); 0409 d->updateTimeWidget(); 0410 } 0411 } 0412 0413 int KTimeComboBox::timeListInterval() const 0414 { 0415 return d->m_timeListInterval; 0416 } 0417 0418 void KTimeComboBox::setTimeListInterval(int minutes) 0419 { 0420 if (minutes != d->m_timeListInterval) { 0421 // Must be able to exactly divide the valid time period 0422 int lowMins = (d->m_minTime.hour() * 60) + d->m_minTime.minute(); 0423 int hiMins = (d->m_maxTime.hour() * 60) + d->m_maxTime.minute(); 0424 if (d->m_minTime.minute() == 0 && d->m_maxTime.minute() == 59) { 0425 ++hiMins; 0426 } 0427 if ((hiMins - lowMins) % minutes == 0) { 0428 d->m_timeListInterval = minutes; 0429 d->m_timeList.clear(); 0430 } else { 0431 return; 0432 } 0433 d->initTimeWidget(); 0434 } 0435 } 0436 0437 QList<QTime> KTimeComboBox::timeList() const 0438 { 0439 // Return the drop down list as it is what can be selected currently 0440 QList<QTime> list; 0441 int c = count(); 0442 list.reserve(c); 0443 for (int i = 0; i < c; ++i) { 0444 list.append(itemData(i).toTime()); 0445 } 0446 return list; 0447 } 0448 0449 void KTimeComboBox::setTimeList(QList<QTime> timeList, const QString &minWarnMsg, const QString &maxWarnMsg) 0450 { 0451 if (timeList != d->m_timeList) { 0452 d->m_timeList.clear(); 0453 for (const QTime &time : std::as_const(timeList)) { 0454 if (time.isValid() && !d->m_timeList.contains(time)) { 0455 d->m_timeList.append(time); 0456 } 0457 } 0458 std::sort(d->m_timeList.begin(), d->m_timeList.end()); 0459 // Does the updateTimeWidget call for us 0460 setTimeRange(d->m_timeList.first(), d->m_timeList.last(), minWarnMsg, maxWarnMsg); 0461 } 0462 } 0463 0464 bool KTimeComboBox::eventFilter(QObject *object, QEvent *event) 0465 { 0466 return QComboBox::eventFilter(object, event); 0467 } 0468 0469 void KTimeComboBox::keyPressEvent(QKeyEvent *keyEvent) 0470 { 0471 QTime temp; 0472 switch (keyEvent->key()) { 0473 case Qt::Key_Down: 0474 temp = d->m_time.addSecs(-60); 0475 break; 0476 case Qt::Key_Up: 0477 temp = d->m_time.addSecs(60); 0478 break; 0479 case Qt::Key_PageDown: 0480 temp = d->m_time.addSecs(-3600); 0481 break; 0482 case Qt::Key_PageUp: 0483 temp = d->m_time.addSecs(3600); 0484 break; 0485 default: 0486 QComboBox::keyPressEvent(keyEvent); 0487 return; 0488 } 0489 if (temp.isValid() && temp >= d->m_minTime && temp <= d->m_maxTime) { 0490 d->enterTime(temp); 0491 } 0492 } 0493 0494 void KTimeComboBox::focusOutEvent(QFocusEvent *event) 0495 { 0496 d->parseTime(); 0497 d->warnTime(); 0498 QComboBox::focusOutEvent(event); 0499 } 0500 0501 void KTimeComboBox::showPopup() 0502 { 0503 QComboBox::showPopup(); 0504 } 0505 0506 void KTimeComboBox::hidePopup() 0507 { 0508 QComboBox::hidePopup(); 0509 } 0510 0511 void KTimeComboBox::mousePressEvent(QMouseEvent *event) 0512 { 0513 QComboBox::mousePressEvent(event); 0514 } 0515 0516 void KTimeComboBox::wheelEvent(QWheelEvent *event) 0517 { 0518 QComboBox::wheelEvent(event); 0519 } 0520 0521 void KTimeComboBox::focusInEvent(QFocusEvent *event) 0522 { 0523 QComboBox::focusInEvent(event); 0524 } 0525 0526 void KTimeComboBox::resizeEvent(QResizeEvent *event) 0527 { 0528 QComboBox::resizeEvent(event); 0529 } 0530 0531 #include "moc_ktimecombobox.cpp"