KOpeningHours

intervalmodel.cpp
1 /*
2  SPDX-FileCopyrightText: 2020 Volker Krause <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "intervalmodel.h"
8 
9 #include <KOpeningHours/Display>
10 #include <KOpeningHours/Interval>
11 
12 #include <QLocale>
13 
14 #include <vector>
15 
16 using namespace KOpeningHours;
17 
18 namespace KOpeningHours {
19 
20 struct DayData {
21  QDate day;
22  std::vector<Interval> intervals;
23 };
24 
25 class IntervalModelPrivate {
26 public:
27  void repopulateModel();
28 
29  OpeningHours oh;
30  std::vector<DayData> m_intervals;
31  QDate beginDt = QDate::currentDate();
32  QDate endDt = QDate::currentDate().addDays(7);
33 };
34 }
35 
36 static void clipIntervalEnd(Interval &i, const QDateTime &endDt)
37 {
38  i.setEnd(i.hasOpenEnd() ? endDt : std::min(i.end(), endDt));
39  if (i.end() == endDt && i.hasOpenEndTime()) { // spans in the next day, so this sub-interval isn't open-ended
40  i.setOpenEndTime(false);
41  }
42 }
43 
44 void IntervalModelPrivate::repopulateModel()
45 {
46  m_intervals.clear();
47  if (endDt < beginDt || oh.error() != OpeningHours::NoError) {
48  return;
49  }
50 
51  QDate dt = beginDt;
52  m_intervals.resize(beginDt.daysTo(endDt));
53  for (auto &dayData : m_intervals) {
54  dayData.day = dt;
55  auto i = oh.interval(QDateTime(dt, {0, 0}));
56 
57  // clip intervals to the current day, makes displaying much easier
58  i.setBegin(i.hasOpenBegin() ? QDateTime(dt, {0, 0}) : std::max(i.begin(), QDateTime(dt, {0, 0})));
59  dt = dt.addDays(1);
60  clipIntervalEnd(i, QDateTime(dt, {0, 0}));
61 
62  dayData.intervals.push_back(i);
63  while (i.isValid() && i.end() < QDateTime(dt, {0, 0})) {
64  i = oh.nextInterval(i);
65  clipIntervalEnd(i, QDateTime(dt, {0, 0}));
66  dayData.intervals.push_back(i);
67  }
68 
69  // fill open end time estimates
70  for (auto it = dayData.intervals.begin(); it != std::prev(dayData.intervals.end()); ++it) {
71  auto nextStartDt = QDateTime(dt, {0, 0});
72  if (!(*it).hasOpenEndTime() || (*it).state() == Interval::Closed) {
73  continue;
74  }
75  for (auto nextIt = std::next(it); nextIt != dayData.intervals.end(); ++nextIt) {
76  if ((*nextIt).state() != Interval::Closed) {
77  nextStartDt = (*nextIt).begin();
78  break;
79  }
80  }
81 
82  auto estimatedEnd = nextStartDt == QDateTime(dt, {0, 0}) ? nextStartDt : (*it).end().addSecs((*it).end().secsTo(nextStartDt) / 2);
83  estimatedEnd = std::min(estimatedEnd, (*it).end().addSecs(4 * 3600));
84  (*it).setEstimatedEnd(estimatedEnd);
85  (*std::next(it)).setBegin(estimatedEnd);
86  }
87  }
88 }
89 
90 IntervalModel::IntervalModel(QObject *parent)
91  : QAbstractListModel(parent)
92  , d(new IntervalModelPrivate)
93 {
94 }
95 
96 IntervalModel::~IntervalModel() = default;
97 
99 {
100  return d->oh;
101 }
102 
103 void IntervalModel::setOpeningHours(const OpeningHours &oh)
104 {
105  d->oh = oh;
106  emit openingHoursChanged();
107 
108  beginResetModel();
109  d->repopulateModel();
110  endResetModel();
111 }
112 
114 {
115  return d->beginDt;
116 }
117 
118 void IntervalModel::setBeginDate(QDate beginDate)
119 {
120  if (d->beginDt == beginDate) {
121  return;
122  }
123 
124  d->beginDt = beginDate;
125  emit beginDateChanged();
126 
127  beginResetModel();
128  d->repopulateModel();
129  endResetModel();
130 }
131 
133 {
134  return d->endDt;
135 }
136 
137 
138 void IntervalModel::setEndDate(QDate endDate)
139 {
140  if (d->endDt == endDate) {
141  return;
142  }
143 
144  d->endDt = endDate;
145  emit endDateChanged();
146 
147  beginResetModel();
148  d->repopulateModel();
149  endResetModel();
150 }
151 
152 int IntervalModel::rowCount(const QModelIndex &parent) const
153 {
154  if (parent.isValid()) {
155  return 0;
156  }
157  return d->m_intervals.size();
158 }
159 
160 QVariant IntervalModel::data(const QModelIndex &index, int role) const
161 {
162  if (!index.isValid()) {
163  return {};
164  }
165 
166  switch (role) {
167  case Qt::DisplayRole:
168  return QLocale().toString(d->m_intervals[index.row()].day, QLocale::ShortFormat);
169  case IntervalsRole:
170  return QVariant::fromValue(d->m_intervals[index.row()].intervals);
171  case DateRole:
172  return d->m_intervals[index.row()].day;
173  case DayBeginTimeRole:
174  return QDateTime(d->m_intervals[index.row()].day, {0, 0});
175  case ShortDayNameRole:
176  return QLocale().standaloneDayName(d->m_intervals[index.row()].day.dayOfWeek(), QLocale::ShortFormat);
177  case IsTodayRole:
178  return d->m_intervals[index.row()].day == QDate::currentDate();
179  }
180 
181  return {};
182 }
183 
184 QHash<int, QByteArray> IntervalModel::roleNames() const
185 {
187  n.insert(IntervalsRole, "intervals");
188  n.insert(DateRole, "date");
189  n.insert(DayBeginTimeRole, "dayBegin");
190  n.insert(ShortDayNameRole, "shortDayName");
191  n.insert(IsTodayRole, "isToday");
192  return n;
193 }
194 
196 {
197  auto d = dt.date();
198  const auto start = QLocale().firstDayOfWeek();
199  if (start < d.dayOfWeek()) {
200  d = d.addDays(start - d.dayOfWeek());
201  } else {
202  d = d.addDays(start - d.dayOfWeek() - 7);
203  }
204  return d;
205 }
206 
208 {
209  return QLocale().toString(QTime(hour, minute), QLocale::NarrowFormat);
210 }
211 
213 {
214  return Display::currentState(d->oh);
215 }
216 
217 #include "moc_intervalmodel.cpp"
QDateTime addSecs(qint64 s) const const
DisplayRole
QVariant fromValue(const T &value)
Qt::DayOfWeek firstDayOfWeek() const const
@ IntervalsRole
All intervals in the current row.
Definition: intervalmodel.h:52
Q_SCRIPTABLE Q_NOREPLY void start()
@ NoError
there is no error, the expression is valid and can be used
Definition: openinghours.h:132
KOpeningHours::OpeningHours openingHours
The opening hours expression shown in this model.
Definition: intervalmodel.h:28
virtual QHash< int, QByteArray > roleNames() const const
@ DayBeginTimeRole
Same as DateRole, but as a date/time object.
Definition: intervalmodel.h:54
Q_INVOKABLE QDate beginOfWeek(const QDateTime &dt) const
Returns the day the week containing dt begins, based on the current locale.
QString toString(qlonglong i) const const
QDate currentDate()
QDate addDays(qint64 ndays) const const
@ ShortDayNameRole
Localized short day name for the current row.
Definition: intervalmodel.h:55
bool isValid() const
Default constructed empty/invalid interval.
Definition: interval.cpp:60
bool isValid() const const
@ DateRole
The date represented by the current row.
Definition: intervalmodel.h:53
QString currentState
Description of the current status as a translated human-readable string.
Definition: intervalmodel.h:37
int row() const const
An OSM opening hours specification.
Definition: openinghours.h:32
KOPENINGHOURS_EXPORT QString currentState(const OpeningHours &oh)
Localized description of the current opening state, and upcoming transitions.
Definition: display.cpp:17
QDate date() const const
Q_INVOKABLE QString formatTimeColumnHeader(int hour, int minute) const
Localized formatting for time column headers.
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
QString standaloneDayName(int day, QLocale::FormatType type) const const
OSM opening hours parsing and evaluation.
Definition: display.h:14
QObject * parent() const const
A time interval for which an opening hours expression has been evaluated.
Definition: interval.h:24
@ IsTodayRole
true if the row represents the current day.
Definition: intervalmodel.h:56
QDate beginDate
Begin of the date range to show in this model.
Definition: intervalmodel.h:30
QDate endDate
End of the date range to show in this model.
Definition: intervalmodel.h:32
int day() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sat Jun 25 2022 06:02:55 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.