File indexing completed on 2024-04-21 04:40:44

0001 /*
0002     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "intervalmodel.h"
0008 
0009 #include <KOpeningHours/Display>
0010 #include <KOpeningHours/Interval>
0011 
0012 #include <QLocale>
0013 
0014 #include <vector>
0015 
0016 using namespace KOpeningHours;
0017 
0018 namespace KOpeningHours {
0019 
0020 struct DayData {
0021     QDate day;
0022     std::vector<Interval> intervals;
0023 };
0024 
0025 class IntervalModelPrivate {
0026 public:
0027     void repopulateModel();
0028 
0029     OpeningHours oh;
0030     std::vector<DayData> m_intervals;
0031     QDate beginDt = QDate::currentDate();
0032     QDate endDt = QDate::currentDate().addDays(7);
0033 };
0034 }
0035 
0036 static void clipIntervalEnd(Interval &i, const QDateTime &endDt)
0037 {
0038     i.setEnd(i.hasOpenEnd() ? endDt : std::min(i.end(), endDt));
0039     if (i.end() == endDt && i.hasOpenEndTime()) { // spans in the next day, so this sub-interval isn't open-ended
0040         i.setOpenEndTime(false);
0041     }
0042 }
0043 
0044 void IntervalModelPrivate::repopulateModel()
0045 {
0046     m_intervals.clear();
0047     if (endDt < beginDt || oh.error() != OpeningHours::NoError) {
0048         return;
0049     }
0050 
0051     QDate dt = beginDt;
0052     m_intervals.resize(beginDt.daysTo(endDt));
0053     for (auto &dayData : m_intervals) {
0054         dayData.day = dt;
0055         auto i = oh.interval(QDateTime(dt, {0, 0}));
0056 
0057         // clip intervals to the current day, makes displaying much easier
0058         i.setBegin(i.hasOpenBegin() ? QDateTime(dt, {0, 0}) : std::max(i.begin(), QDateTime(dt, {0, 0})));
0059         dt = dt.addDays(1);
0060         clipIntervalEnd(i, QDateTime(dt, {0, 0}));
0061 
0062         dayData.intervals.push_back(i);
0063         while (i.isValid() && i.end() < QDateTime(dt, {0, 0})) {
0064             i = oh.nextInterval(i);
0065             clipIntervalEnd(i, QDateTime(dt, {0, 0}));
0066             dayData.intervals.push_back(i);
0067         }
0068 
0069         // fill open end time estimates
0070         for (auto it = dayData.intervals.begin(); it != std::prev(dayData.intervals.end()); ++it) {
0071             auto nextStartDt = QDateTime(dt, {0, 0});
0072             if (!(*it).hasOpenEndTime() || (*it).state() == Interval::Closed) {
0073                 continue;
0074             }
0075             for (auto nextIt = std::next(it); nextIt != dayData.intervals.end(); ++nextIt) {
0076                 if ((*nextIt).state() != Interval::Closed) {
0077                     nextStartDt = (*nextIt).begin();
0078                     break;
0079                 }
0080             }
0081 
0082             auto estimatedEnd = nextStartDt == QDateTime(dt, {0, 0}) ? nextStartDt : (*it).end().addSecs((*it).end().secsTo(nextStartDt) / 2);
0083             estimatedEnd = std::min(estimatedEnd, (*it).end().addSecs(4 * 3600));
0084             (*it).setEstimatedEnd(estimatedEnd);
0085             (*std::next(it)).setBegin(estimatedEnd);
0086         }
0087     }
0088 }
0089 
0090 IntervalModel::IntervalModel(QObject *parent)
0091     : QAbstractListModel(parent)
0092     , d(new IntervalModelPrivate)
0093 {
0094 }
0095 
0096 IntervalModel::~IntervalModel() = default;
0097 
0098 OpeningHours IntervalModel::openingHours() const
0099 {
0100     return d->oh;
0101 }
0102 
0103 void IntervalModel::setOpeningHours(const OpeningHours &oh)
0104 {
0105     d->oh = oh;
0106     emit openingHoursChanged();
0107 
0108     beginResetModel();
0109     d->repopulateModel();
0110     endResetModel();
0111 }
0112 
0113 QDate IntervalModel::beginDate() const
0114 {
0115     return d->beginDt;
0116 }
0117 
0118 void IntervalModel::setBeginDate(QDate beginDate)
0119 {
0120     if (d->beginDt == beginDate) {
0121         return;
0122     }
0123 
0124     d->beginDt = beginDate;
0125     emit beginDateChanged();
0126 
0127     beginResetModel();
0128     d->repopulateModel();
0129     endResetModel();
0130 }
0131 
0132 QDate IntervalModel::endDate() const
0133 {
0134     return d->endDt;
0135 }
0136 
0137 
0138 void IntervalModel::setEndDate(QDate endDate)
0139 {
0140     if (d->endDt == endDate) {
0141         return;
0142     }
0143 
0144     d->endDt = endDate;
0145     emit endDateChanged();
0146 
0147     beginResetModel();
0148     d->repopulateModel();
0149     endResetModel();
0150 }
0151 
0152 int IntervalModel::rowCount(const QModelIndex &parent) const
0153 {
0154     if (parent.isValid()) {
0155         return 0;
0156     }
0157     return d->m_intervals.size();
0158 }
0159 
0160 QVariant IntervalModel::data(const QModelIndex &index, int role) const
0161 {
0162     if (!index.isValid()) {
0163         return {};
0164     }
0165 
0166     switch (role) {
0167         case Qt::DisplayRole:
0168             return QLocale().toString(d->m_intervals[index.row()].day, QLocale::ShortFormat);
0169         case IntervalsRole:
0170             return QVariant::fromValue(d->m_intervals[index.row()].intervals);
0171         case DateRole:
0172             return d->m_intervals[index.row()].day;
0173         case DayBeginTimeRole:
0174             return QDateTime(d->m_intervals[index.row()].day, {0, 0});
0175         case ShortDayNameRole:
0176              return QLocale().standaloneDayName(d->m_intervals[index.row()].day.dayOfWeek(), QLocale::ShortFormat);
0177         case IsTodayRole:
0178             return d->m_intervals[index.row()].day == QDate::currentDate();
0179     }
0180 
0181     return {};
0182 }
0183 
0184 QHash<int, QByteArray> IntervalModel::roleNames() const
0185 {
0186     auto n = QAbstractListModel::roleNames();
0187     n.insert(IntervalsRole, "intervals");
0188     n.insert(DateRole, "date");
0189     n.insert(DayBeginTimeRole, "dayBegin");
0190     n.insert(ShortDayNameRole, "shortDayName");
0191     n.insert(IsTodayRole, "isToday");
0192     return n;
0193 }
0194 
0195 QDate IntervalModel::beginOfWeek(const QDateTime& dt) const
0196 {
0197     auto d = dt.date();
0198     const auto start = QLocale().firstDayOfWeek();
0199     if (start < d.dayOfWeek()) {
0200         d = d.addDays(start - d.dayOfWeek());
0201     } else {
0202         d = d.addDays(start - d.dayOfWeek() - 7);
0203     }
0204     return d;
0205 }
0206 
0207 QString IntervalModel::formatTimeColumnHeader(int hour, int minute) const
0208 {
0209     return QLocale().toString(QTime(hour, minute), QLocale::NarrowFormat);
0210 }
0211 
0212 QString IntervalModel::currentState() const
0213 {
0214     return Display::currentState(d->oh);
0215 }
0216 
0217 #include "moc_intervalmodel.cpp"