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#include "qquickmonthgrid_p.h"
4#include "qquickmonthmodel_p.h"
5
6#include <QtGui/qstylehints.h>
7#include <QtGui/qguiapplication.h>
8#include <QtQuickTemplates2/private/qquickcontrol_p_p.h>
9#include <QtQml/qqmlinfo.h>
10
11QT_BEGIN_NAMESPACE
12
13/*!
14 \qmltype MonthGrid
15 \inherits Control
16//! \nativetype QQuickMonthGrid
17 \inqmlmodule QtQuick.Controls
18 \brief A grid of days for a calendar month.
19
20 MonthGrid presents a calendar month in a grid. The contents are
21 calculated for a given \l month and \l year, using the specified
22 \l {Control::locale}{locale}.
23
24 \image qtquickcontrols-monthgrid.webp
25 \snippet qtquickcontrols-monthgrid.qml 1
26
27 MonthGrid can be used as a standalone control, but it is most often
28 used in conjunction with DayOfWeekRow and WeekNumberColumn. Regardless
29 of the use case, positioning of the grid is left to the user.
30
31 \image qtquickcontrols-monthgrid-layout.webp
32 \snippet qtquickcontrols-monthgrid-layout.qml 1
33
34 The visual appearance of MonthGrid can be changed by
35 implementing a \l {delegate}{custom delegate}.
36
37 When viewing any given month, MonthGrid shows days from the previous and
38 next month. This means it always shows six rows, even when first or last
39 row is entirely within an adjacent month.
40
41 \section1 Localizing days
42
43 To localize days, use \l {Locale::toString()}{Locale.toString()}.
44 For example, to display day numbers in an Arabic locale:
45
46 \snippet qtquickcontrols-monthgrid-localization.qml 1
47
48 \sa DayOfWeekRow, WeekNumberColumn, CalendarModel,
49 {Qt Quick Controls - Event Calendar}
50*/
51
52/*!
53 \qmlsignal QtQuick.Controls::MonthGrid::pressed(date date)
54
55 This signal is emitted when \a date is pressed.
56*/
57
58/*!
59 \qmlsignal QtQuick.Controls::MonthGrid::released(date date)
60
61 This signal is emitted when \a date is released.
62*/
63
64/*!
65 \qmlsignal QtQuick.Controls::MonthGrid::clicked(date date)
66
67 This signal is emitted when \a date is clicked.
68*/
69
70/*!
71 \qmlsignal QtQuick.Controls::MonthGrid::pressAndHold(date date)
72
73 This signal is emitted when \a date is pressed and held down.
74*/
75
76class QQuickMonthGridPrivate : public QQuickControlPrivate
77{
78 Q_DECLARE_PUBLIC(QQuickMonthGrid)
79
80public:
81 QQuickMonthGridPrivate() : pressTimer(0), pressedItem(nullptr), model(nullptr), delegate(nullptr) { }
82
83 void resizeItems();
84
85 QQuickItem *cellAt(const QPointF &pos) const;
86 QDateTime dateOf(QQuickItem *cell) const;
87
88 void updatePress(const QPointF &pos);
89 void clearPress(bool clicked);
90
91 bool handlePress(const QPointF &point, ulong timestamp) override;
92 bool handleMove(const QPointF &point, ulong timestamp) override;
93 bool handleRelease(const QPointF &point, ulong timestamp) override;
94 void handleUngrab() override;
95
96 QString title;
97 QVariant source;
98
99 // Only the date matters, but we have to store it as QDateTime for compatibility
100 // with Date: QTBUG-72208. See QQuickMonthModelPrivate::populate for more info.
101 QDateTime pressedDate;
102 int pressTimer;
103 QQuickItem *pressedItem;
104 QQuickMonthModel *model;
105 QQmlComponent *delegate;
106};
107
108void QQuickMonthGridPrivate::resizeItems()
109{
110 if (!contentItem)
111 return;
112
113 QSizeF itemSize;
114 itemSize.setWidth((contentItem->width() - 6 * spacing) / 7);
115 itemSize.setHeight((contentItem->height() - 5 * spacing) / 6);
116
117 const auto childItems = contentItem->childItems();
118 for (QQuickItem *item : childItems) {
119 if (!QQuickItemPrivate::get(item)->isTransparentForPositioner())
120 item->setSize(itemSize);
121 }
122}
123
124QQuickItem *QQuickMonthGridPrivate::cellAt(const QPointF &pos) const
125{
126 Q_Q(const QQuickMonthGrid);
127 if (contentItem) {
128 QPointF mapped = q->mapToItem(item: contentItem, point: pos);
129 return contentItem->childAt(x: mapped.x(), y: mapped.y());
130 }
131 return nullptr;
132}
133
134QDateTime QQuickMonthGridPrivate::dateOf(QQuickItem *cell) const
135{
136 if (contentItem)
137 return model->dateAt(index: contentItem->childItems().indexOf(t: cell));
138 return {};
139}
140
141void QQuickMonthGridPrivate::updatePress(const QPointF &pos)
142{
143 Q_Q(QQuickMonthGrid);
144 clearPress(clicked: false);
145 pressedItem = cellAt(pos);
146 pressedDate = dateOf(cell: pressedItem);
147 if (pressedDate.isValid())
148 emit q->pressed(date: pressedDate);
149}
150
151void QQuickMonthGridPrivate::clearPress(bool clicked)
152{
153 Q_Q(QQuickMonthGrid);
154 if (pressedDate.isValid()) {
155 emit q->released(date: pressedDate);
156 if (clicked)
157 emit q->clicked(date: pressedDate);
158 }
159 pressedDate = {};
160 pressedItem = nullptr;
161}
162
163bool QQuickMonthGridPrivate::handlePress(const QPointF &point, ulong timestamp)
164{
165 Q_Q(QQuickMonthGrid);
166 QQuickControlPrivate::handlePress(point, timestamp);
167 updatePress(pos: point);
168 if (pressedDate.isValid())
169 pressTimer = q->startTimer(qGuiApp->styleHints()->mousePressAndHoldInterval());
170 return true;
171}
172
173bool QQuickMonthGridPrivate::handleMove(const QPointF &point, ulong timestamp)
174{
175 QQuickControlPrivate::handleMove(point, timestamp);
176 updatePress(pos: point);
177 return true;
178}
179
180bool QQuickMonthGridPrivate::handleRelease(const QPointF &point, ulong timestamp)
181{
182 QQuickControlPrivate::handleRelease(point, timestamp);
183 clearPress(clicked: true);
184 return true;
185}
186
187void QQuickMonthGridPrivate::handleUngrab()
188{
189 QQuickControlPrivate::handleUngrab();
190 clearPress(clicked: false);
191}
192
193QQuickMonthGrid::QQuickMonthGrid(QQuickItem *parent) :
194 QQuickControl(*(new QQuickMonthGridPrivate), parent)
195{
196 Q_D(QQuickMonthGrid);
197 setFlag(flag: ItemIsFocusScope);
198 setActiveFocusOnTab(true);
199 setAcceptedMouseButtons(Qt::LeftButton);
200#if QT_CONFIG(cursor)
201 setCursor(Qt::ArrowCursor);
202#endif
203
204 d->model = new QQuickMonthModel(this);
205 d->source = QVariant::fromValue(value: d->model);
206 connect(sender: d->model, signal: &QQuickMonthModel::monthChanged, context: this, slot: &QQuickMonthGrid::monthChanged);
207 connect(sender: d->model, signal: &QQuickMonthModel::yearChanged, context: this, slot: &QQuickMonthGrid::yearChanged);
208 connect(sender: d->model, signal: &QQuickMonthModel::titleChanged, context: this, slot: &QQuickMonthGrid::titleChanged);
209}
210
211/*!
212 \qmlproperty int QtQuick.Controls::MonthGrid::month
213
214 This property holds the number of the month. The default value is the
215 current month.
216
217 \include zero-based-months.qdocinc
218
219 \sa Calendar
220*/
221int QQuickMonthGrid::month() const
222{
223 Q_D(const QQuickMonthGrid);
224 return d->model->month() - 1;
225}
226
227void QQuickMonthGrid::setMonth(int month)
228{
229 Q_D(QQuickMonthGrid);
230 if (month < 0 || month > 11) {
231 qmlWarning(me: this) << "month " << month << " is out of range [0...11]";
232 return;
233 }
234 d->model->setMonth(month + 1);
235}
236
237/*!
238 \qmlproperty int QtQuick.Controls::MonthGrid::year
239
240 This property holds the number of the year.
241
242 The value must be in the range from \c -271820 to \c 275759. The default
243 value is the current year.
244*/
245int QQuickMonthGrid::year() const
246{
247 Q_D(const QQuickMonthGrid);
248 return d->model->year();
249}
250
251void QQuickMonthGrid::setYear(int year)
252{
253 Q_D(QQuickMonthGrid);
254 if (year < -271820 || year > 275759) {
255 qmlWarning(me: this) << "year " << year << " is out of range [-271820...275759]";
256 return;
257 }
258 d->model->setYear(year);
259}
260
261/*!
262 \internal
263 \qmlproperty model QtQuick.Controls::MonthGrid::source
264
265 This property holds the source model that is used as a data model
266 for the internal content column.
267*/
268QVariant QQuickMonthGrid::source() const
269{
270 Q_D(const QQuickMonthGrid);
271 return d->source;
272}
273
274void QQuickMonthGrid::setSource(const QVariant &source)
275{
276 Q_D(QQuickMonthGrid);
277 if (d->source != source) {
278 d->source = source;
279 emit sourceChanged();
280 }
281}
282
283/*!
284 \qmlproperty string QtQuick.Controls::MonthGrid::title
285
286 This property holds a title for the calendar.
287
288 This property is provided for convenience. MonthGrid itself does
289 not visualize the title. The default value consists of the month name,
290 formatted using \l {Control::locale}{locale}, and the year number.
291*/
292QString QQuickMonthGrid::title() const
293{
294 Q_D(const QQuickMonthGrid);
295 if (d->title.isNull())
296 return d->model->title();
297 return d->title;
298}
299
300void QQuickMonthGrid::setTitle(const QString &title)
301{
302 Q_D(QQuickMonthGrid);
303 if (d->title != title) {
304 d->title = title;
305 emit titleChanged();
306 }
307}
308
309/*!
310 \qmlproperty Component QtQuick.Controls::MonthGrid::delegate
311
312 This property holds the item delegate that visualizes each day.
313
314 In addition to the \c index property, a list of model data roles
315 are available in the context of each delegate:
316 \table
317 \row \li \b model.date : date \li The date of the cell
318 \row \li \b model.day : int \li The number of the day
319 \row \li \b model.today : bool \li Whether the delegate represents today
320 \row \li \b model.weekNumber : int \li The week number
321 \row \li \b model.month : int \li The number of the month
322 \row \li \b model.year : int \li The number of the year
323 \endtable
324
325 The following snippet presents the default implementation of the item
326 delegate. It can be used as a starting point for implementing custom
327 delegates.
328
329 \snippet basic/MonthGrid.qml delegate
330*/
331QQmlComponent *QQuickMonthGrid::delegate() const
332{
333 Q_D(const QQuickMonthGrid);
334 return d->delegate;
335}
336
337void QQuickMonthGrid::setDelegate(QQmlComponent *delegate)
338{
339 Q_D(QQuickMonthGrid);
340 if (d->delegate != delegate) {
341 d->delegate = delegate;
342 emit delegateChanged();
343 }
344}
345
346void QQuickMonthGrid::componentComplete()
347{
348 Q_D(QQuickMonthGrid);
349 QQuickControl::componentComplete();
350 d->resizeItems();
351}
352
353void QQuickMonthGrid::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
354{
355 Q_D(QQuickMonthGrid);
356 QQuickControl::geometryChange(newGeometry, oldGeometry);
357 if (isComponentComplete())
358 d->resizeItems();
359}
360
361void QQuickMonthGrid::localeChange(const QLocale &newLocale, const QLocale &oldLocale)
362{
363 Q_D(QQuickMonthGrid);
364 QQuickControl::localeChange(newLocale, oldLocale);
365 d->model->setLocale(newLocale);
366}
367
368void QQuickMonthGrid::paddingChange(const QMarginsF &newPadding, const QMarginsF &oldPadding)
369{
370 Q_D(QQuickMonthGrid);
371 QQuickControl::paddingChange(newPadding, oldPadding);
372 if (isComponentComplete())
373 d->resizeItems();
374}
375
376void QQuickMonthGrid::updatePolish()
377{
378 Q_D(QQuickMonthGrid);
379 QQuickControl::updatePolish();
380 d->resizeItems();
381}
382
383void QQuickMonthGrid::timerEvent(QTimerEvent *event)
384{
385 Q_D(QQuickMonthGrid);
386 if (event->timerId() == d->pressTimer) {
387 if (d->pressedDate.isValid())
388 emit pressAndHold(date: d->pressedDate);
389 killTimer(id: d->pressTimer);
390 }
391}
392
393QT_END_NAMESPACE
394
395#include "moc_qquickmonthgrid_p.cpp"
396

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