1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qquickmonthmodel_p.h"
5
6#include <QtCore/private/qabstractitemmodel_p.h>
7#include <QtCore/qloggingcategory.h>
8#include <QtCore/qtimezone.h>
9
10namespace {
11 static const int daysInAWeek = 7;
12 static const int weeksOnACalendarMonth = 6;
13 static const int daysOnACalendarMonth = daysInAWeek * weeksOnACalendarMonth;
14}
15
16QT_BEGIN_NAMESPACE
17
18Q_STATIC_LOGGING_CATEGORY(lcMonthModel, "qt.quick.controls.monthmodel")
19
20class QQuickMonthModelPrivate : public QAbstractItemModelPrivate
21{
22 Q_DECLARE_PUBLIC(QQuickMonthModel)
23
24public:
25 QQuickMonthModelPrivate() : dates(daysOnACalendarMonth)
26 {
27 today = QDate::currentDate();
28 month = today.month();
29 year = today.year();
30 }
31
32 bool populate(int month, int year, const QLocale &locale, bool force = false);
33
34 int month;
35 int year;
36 QString title;
37 QLocale locale;
38 QVector<QDateTime> dates;
39 QDate today;
40};
41
42bool QQuickMonthModelPrivate::populate(int m, int y, const QLocale &l, bool force)
43{
44 Q_Q(QQuickMonthModel);
45 if (!force && m == month && y == year && l.firstDayOfWeek() == locale.firstDayOfWeek())
46 return false;
47
48 // The actual first (1st) day of the month.
49 const QDate firstDayOfMonthDate = QDate(y, m, 1);
50 // QDate is converted to local time when converted to a JavaScript Date,
51 // so if we stored our dates as QDates, it's possible that the date provided
52 // to delegates will be wrong in certain timezones:
53 // e.g. 00:00 UTC converted to UTC-8 is 16:00 the day before.
54 // To account for this, we store our dates as local QDateTimes.
55 const QDateTime firstDayOfMonthDateTime = firstDayOfMonthDate.startOfDay();
56 int difference = ((firstDayOfMonthDate.dayOfWeek() - l.firstDayOfWeek()) + 7) % 7;
57 // The first day to display should never be the 1st of the month, as we want some days from
58 // the previous month to be visible.
59 if (difference == 0)
60 difference += 7;
61 const QDateTime firstDateToDisplay = firstDayOfMonthDateTime.addDays(days: -difference);
62
63 today = QDate::currentDate();
64 for (int i = 0; i < daysOnACalendarMonth; ++i)
65 dates[i] = firstDateToDisplay.addDays(days: i);
66
67 q->setTitle(l.standaloneMonthName(m) + QStringLiteral(" ") + QString::number(y));
68
69 qCDebug(lcMonthModel) << "populated model for month" << m << "year" << y << "locale" << locale
70 << "firstDayOfMonthDateTime" << firstDayOfMonthDateTime;
71
72 return true;
73}
74
75QQuickMonthModel::QQuickMonthModel(QObject *parent) :
76 QAbstractListModel(*(new QQuickMonthModelPrivate), parent)
77{
78 Q_D(QQuickMonthModel);
79 d->populate(m: d->month, y: d->year, l: d->locale, force: true);
80}
81
82int QQuickMonthModel::month() const
83{
84 Q_D(const QQuickMonthModel);
85 return d->month;
86}
87
88void QQuickMonthModel::setMonth(int month)
89{
90 Q_D(QQuickMonthModel);
91 if (d->month != month) {
92 if (d->populate(m: month, y: d->year, l: d->locale))
93 emit dataChanged(topLeft: index(row: 0, column: 0), bottomRight: index(row: daysOnACalendarMonth - 1, column: 0));
94 d->month = month;
95 emit monthChanged();
96 }
97}
98
99int QQuickMonthModel::year() const
100{
101 Q_D(const QQuickMonthModel);
102 return d->year;
103}
104
105void QQuickMonthModel::setYear(int year)
106{
107 Q_D(QQuickMonthModel);
108 if (d->year != year) {
109 if (d->populate(m: d->month, y: year, l: d->locale))
110 emit dataChanged(topLeft: index(row: 0, column: 0), bottomRight: index(row: daysOnACalendarMonth - 1, column: 0));
111 d->year = year;
112 emit yearChanged();
113 }
114}
115
116QLocale QQuickMonthModel::locale() const
117{
118 Q_D(const QQuickMonthModel);
119 return d->locale;
120}
121
122void QQuickMonthModel::setLocale(const QLocale &locale)
123{
124 Q_D(QQuickMonthModel);
125 if (d->locale != locale) {
126 if (d->populate(m: d->month, y: d->year, l: locale))
127 emit dataChanged(topLeft: index(row: 0, column: 0), bottomRight: index(row: daysOnACalendarMonth - 1, column: 0));
128 d->locale = locale;
129 emit localeChanged();
130 }
131}
132
133QString QQuickMonthModel::title() const
134{
135 Q_D(const QQuickMonthModel);
136 return d->title;
137}
138
139void QQuickMonthModel::setTitle(const QString &title)
140{
141 Q_D(QQuickMonthModel);
142 if (d->title != title) {
143 d->title = title;
144 emit titleChanged();
145 }
146}
147
148QDateTime QQuickMonthModel::dateAt(int index) const
149{
150 Q_D(const QQuickMonthModel);
151 return d->dates.value(i: index);
152}
153
154int QQuickMonthModel::indexOf(QDateTime date) const
155{
156 Q_D(const QQuickMonthModel);
157 if (date < d->dates.first() || date > d->dates.last())
158 return -1;
159 return qMax(a: qint64(0), b: d->dates.first().daysTo(date));
160}
161
162QVariant QQuickMonthModel::data(const QModelIndex &index, int role) const
163{
164 Q_D(const QQuickMonthModel);
165 if (index.isValid() && index.row() < daysOnACalendarMonth) {
166 const QDateTime dateTime = d->dates.at(i: index.row());
167 // As mentioned in populate, we store dates whose time is adjusted
168 // by the timezone offset, so we need to convert back to local time
169 // to get the correct date if the conversion to JavaScript's Date
170 // isn't being done for us.
171 const QDate date = d->dates.at(i: index.row()).toLocalTime().date();
172 switch (role) {
173 case DateRole:
174 return dateTime;
175 case DayRole:
176 return date.day();
177 case TodayRole:
178 return date == d->today;
179 case WeekNumberRole:
180 return date.weekNumber();
181 case MonthRole:
182 return date.month() - 1;
183 case YearRole:
184 return date.year();
185 default:
186 break;
187 }
188 }
189 return QVariant();
190}
191
192int QQuickMonthModel::rowCount(const QModelIndex &parent) const
193{
194 if (parent.isValid())
195 return 0;
196 return daysOnACalendarMonth;
197}
198
199QHash<int, QByteArray> QQuickMonthModel::roleNames() const
200{
201 QHash<int, QByteArray> roles;
202 roles[DateRole] = QByteArrayLiteral("date");
203 roles[DayRole] = QByteArrayLiteral("day");
204 roles[TodayRole] = QByteArrayLiteral("today");
205 roles[WeekNumberRole] = QByteArrayLiteral("weekNumber");
206 roles[MonthRole] = QByteArrayLiteral("month");
207 roles[YearRole] = QByteArrayLiteral("year");
208 return roles;
209}
210
211QT_END_NAMESPACE
212
213#include "moc_qquickmonthmodel_p.cpp"
214

source code of qtdeclarative/src/quicktemplates/qquickmonthmodel.cpp