1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt Quick Controls module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qquickcalendarmodel_p.h" |
41 | |
42 | namespace { |
43 | static const int daysInAWeek = 7; |
44 | |
45 | /* |
46 | Not the number of weeks per month, but the number of weeks that are |
47 | shown on a typical calendar. |
48 | */ |
49 | static const int weeksOnACalendarMonth = 6; |
50 | |
51 | /* |
52 | The amount of days to populate the calendar with. |
53 | */ |
54 | static const int daysOnACalendarMonth = daysInAWeek * weeksOnACalendarMonth; |
55 | } |
56 | |
57 | QT_BEGIN_NAMESPACE |
58 | |
59 | /*! |
60 | QQuickCalendarModel provides a model for the Calendar control. |
61 | It is responsible for populating itself with dates based on a given month |
62 | and year. |
63 | |
64 | The model stores a list of dates whose indices map directly to the Calendar. |
65 | For example, the model would store the following dates when any day in |
66 | January 2015 is selected on the Calendar: |
67 | |
68 | [30/12/2014][31/12/2014][01/01/2015]...[31/01/2015][01/02/2015]...[09/02/2015] |
69 | |
70 | The Calendar would then display the dates in the same order within a grid: |
71 | |
72 | January 2015 |
73 | |
74 | [30][31][01][02][03][04][05] |
75 | [06][07][08][09][10][11][12] |
76 | [13][14][15][16][17][18][19] |
77 | [20][21][22][23][24][25][26] |
78 | [27][28][29][30][31][01][02] |
79 | [03][04][05][06][07][08][09] |
80 | */ |
81 | |
82 | QQuickCalendarModel1::QQuickCalendarModel1(QObject *parent) : |
83 | QAbstractListModel(parent) |
84 | { |
85 | } |
86 | |
87 | /*! |
88 | The date that determines which dates are stored. |
89 | |
90 | We store all of the days in the month of visibleDate, as well as any days |
91 | in the previous or following month if there is enough space. |
92 | */ |
93 | QDate QQuickCalendarModel1::visibleDate() const |
94 | { |
95 | return mVisibleDate; |
96 | } |
97 | |
98 | /*! |
99 | Sets the visible date to \a visibleDate. |
100 | |
101 | If \a visibleDate is a valid date and is different than the previously |
102 | visible date, the visible date is changed and |
103 | populateFromVisibleDate() called. |
104 | */ |
105 | void QQuickCalendarModel1::setVisibleDate(const QDate &date) |
106 | { |
107 | if (date != mVisibleDate && date.isValid()) { |
108 | const QDate previousDate = mVisibleDate; |
109 | mVisibleDate = date; |
110 | populateFromVisibleDate(previousDate); |
111 | emit visibleDateChanged(visibleDate: date); |
112 | } |
113 | } |
114 | |
115 | /*! |
116 | The locale set on the Calendar. |
117 | |
118 | This affects which dates are stored for visibleDate(). For example, if |
119 | the locale is en_US, the first day of the week is Sunday. Therefore, if |
120 | visibleDate() is some day in January 2014, there will be three days |
121 | displayed before the 1st of January: |
122 | |
123 | |
124 | January 2014 |
125 | |
126 | [SO][MO][TU][WE][TH][FR][SA] |
127 | [29][30][31][01][02][03][04] |
128 | ... |
129 | |
130 | If the locale is then changed to en_GB (with the same visibleDate()), |
131 | there will be 2 days before the 1st of January, because Monday is the |
132 | first day of the week for that locale: |
133 | |
134 | January 2014 |
135 | |
136 | [MO][TU][WE][TH][FR][SA][SO] |
137 | [30][31][01][02][03][04][05] |
138 | ... |
139 | */ |
140 | QLocale QQuickCalendarModel1::locale() const |
141 | { |
142 | return mLocale; |
143 | } |
144 | |
145 | /*! |
146 | Sets the locale to \a locale. |
147 | */ |
148 | void QQuickCalendarModel1::setLocale(const QLocale &locale) |
149 | { |
150 | if (locale != mLocale) { |
151 | Qt::DayOfWeek previousFirstDayOfWeek = mLocale.firstDayOfWeek(); |
152 | mLocale = locale; |
153 | emit localeChanged(locale: mLocale); |
154 | if (mLocale.firstDayOfWeek() != previousFirstDayOfWeek) { |
155 | // We don't have a previousDate, so just use our current one... |
156 | // it's ignored anyway, since we're forcing the repopulation. |
157 | populateFromVisibleDate(previousDate: mVisibleDate, force: true); |
158 | } |
159 | } |
160 | } |
161 | |
162 | QVariant QQuickCalendarModel1::data(const QModelIndex &index, int role) const |
163 | { |
164 | if (role == DateRole) |
165 | return QDateTime(mVisibleDates.at(i: index.row()), QTime(12, 0)); |
166 | return QVariant(); |
167 | } |
168 | |
169 | int QQuickCalendarModel1::rowCount(const QModelIndex &) const |
170 | { |
171 | return mVisibleDates.isEmpty() ? 0 : weeksOnACalendarMonth * daysInAWeek; |
172 | } |
173 | |
174 | QHash<int, QByteArray> QQuickCalendarModel1::roleNames() const |
175 | { |
176 | QHash<int, QByteArray> names; |
177 | names[DateRole] = QByteArrayLiteral("date" ); |
178 | return names; |
179 | } |
180 | |
181 | /*! |
182 | Returns the date at \a index, or an invalid date if \a index is invalid. |
183 | */ |
184 | QDateTime QQuickCalendarModel1::dateAt(int index) const |
185 | { |
186 | return index >= 0 && index < mVisibleDates.size() ? QDateTime(mVisibleDates.at(i: index), QTime(12, 0)) : QDateTime(); |
187 | } |
188 | |
189 | /*! |
190 | Returns the index for \a date, or -1 if \a date is outside of our range. |
191 | */ |
192 | int QQuickCalendarModel1::indexAt(const QDate &date) |
193 | { |
194 | if (mVisibleDates.size() == 0 || date < mFirstVisibleDate || date > mLastVisibleDate) |
195 | return -1; |
196 | |
197 | // The index of the visible date will be the days from the |
198 | // previous month that we had to display before it, plus the |
199 | // day of the visible date itself. |
200 | return qMax(a: qint64(0), b: mFirstVisibleDate.daysTo(date)); |
201 | } |
202 | |
203 | /*! |
204 | Returns the week number for the first day of the week corresponding to \a row, |
205 | or -1 if \a row is outside of our range. |
206 | */ |
207 | int QQuickCalendarModel1::weekNumberAt(int row) const |
208 | { |
209 | const int index = row * daysInAWeek; |
210 | const QDate date = dateAt(index).date(); |
211 | if (date.isValid()) |
212 | return date.weekNumber(); |
213 | return -1; |
214 | } |
215 | |
216 | /*! |
217 | Called before visibleDateChanged() is emitted. |
218 | |
219 | This function is called even when just the day has changed, in which case |
220 | it does nothing. |
221 | |
222 | The \a force parameter is used when the locale has changed; the month |
223 | shown doesn't change, but the days displayed do. |
224 | The \a previousDate parameter is ignored when \a force is true. |
225 | */ |
226 | void QQuickCalendarModel1::populateFromVisibleDate(const QDate &previousDate, bool force) |
227 | { |
228 | // We don't need to populate if the year and month haven't changed. |
229 | if (!force && mVisibleDate.year() == previousDate.year() && mVisibleDate.month() == previousDate.month()) |
230 | return; |
231 | |
232 | // Since our model is of a fixed size, we fill it once and assign values each time |
233 | // the month changes, instead of clearing and appending each time. |
234 | bool isEmpty = mVisibleDates.isEmpty(); |
235 | if (isEmpty) { |
236 | beginResetModel(); |
237 | mVisibleDates.fill(from: QDate(), asize: daysOnACalendarMonth); |
238 | } |
239 | |
240 | // The actual first (1st) day of the month. |
241 | QDate firstDayOfMonthDate(mVisibleDate.year(), mVisibleDate.month(), 1); |
242 | int difference = ((firstDayOfMonthDate.dayOfWeek() - mLocale.firstDayOfWeek()) + 7) % 7; |
243 | // The first day to display should never be the 1st of the month, as we want some days from |
244 | // the previous month to be visible. |
245 | if (difference == 0) |
246 | difference += daysInAWeek; |
247 | QDate firstDateToDisplay = firstDayOfMonthDate.addDays(days: -difference); |
248 | for (int i = 0; i < daysOnACalendarMonth; ++i) |
249 | mVisibleDates[i] = firstDateToDisplay.addDays(days: i); |
250 | |
251 | mFirstVisibleDate = mVisibleDates.at(i: 0); |
252 | mLastVisibleDate = mVisibleDates.at(i: mVisibleDates.size() - 1); |
253 | |
254 | if (!isEmpty) { |
255 | emit dataChanged(topLeft: index(row: 0, column: 0), bottomRight: index(row: rowCount() - 1, column: 0)); |
256 | } else { |
257 | endResetModel(); |
258 | emit countChanged(count: rowCount()); |
259 | } |
260 | } |
261 | |
262 | QT_END_NAMESPACE |
263 | |