File indexing completed on 2024-04-28 03:59:15

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, &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"