1// Copyright (C) 2017 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 "qquickpageindicator_p.h"
5#include "qquickcontrol_p_p.h"
6
7#include <QtCore/qmath.h>
8#include <QtQuick/private/qquickitem_p.h>
9
10QT_BEGIN_NAMESPACE
11
12/*!
13 \qmltype PageIndicator
14 \inherits Control
15//! \instantiates QQuickPageIndicator
16 \inqmlmodule QtQuick.Controls
17 \since 5.7
18 \ingroup qtquickcontrols-indicators
19 \brief Indicates the currently active page.
20
21 PageIndicator is used to indicate the currently active page
22 in a container of multiple pages. PageIndicator consists of
23 delegate items that present pages.
24
25 \image qtquickcontrols-pageindicator.png
26
27 \code
28 Column {
29 StackLayout {
30 id: stackLayout
31
32 Page {
33 // ...
34 }
35 Page {
36 // ...
37 }
38 Page {
39 // ...
40 }
41 }
42
43 PageIndicator {
44 currentIndex: stackLayout.currentIndex
45 count: stackLayout.count
46 }
47 }
48 \endcode
49
50 \sa SwipeView, {Customizing PageIndicator}, {Indicator Controls}
51*/
52
53class QQuickPageIndicatorPrivate : public QQuickControlPrivate
54{
55 Q_DECLARE_PUBLIC(QQuickPageIndicator)
56
57public:
58 bool handlePress(const QPointF &point, ulong timestamp) override;
59 bool handleMove(const QPointF &point, ulong timestamp) override;
60 bool handleRelease(const QPointF &point, ulong timestamp) override;
61 void handleUngrab() override;
62
63 QQuickItem *itemAt(const QPointF &pos) const;
64 void updatePressed(bool pressed, const QPointF &pos = QPointF());
65 void setContextProperty(QQuickItem *item, const QString &name, const QVariant &value);
66
67 void itemChildAdded(QQuickItem *, QQuickItem *child) override;
68
69 int count = 0;
70 int currentIndex = 0;
71 bool interactive = false;
72 QQmlComponent *delegate = nullptr;
73 QQuickItem *pressedItem = nullptr;
74};
75
76bool QQuickPageIndicatorPrivate::handlePress(const QPointF &point, ulong timestamp)
77{
78 QQuickControlPrivate::handlePress(point, timestamp);
79 if (interactive) {
80 updatePressed(pressed: true, pos: point);
81 return true;
82 }
83 return false;
84}
85
86bool QQuickPageIndicatorPrivate::handleMove(const QPointF &point, ulong timestamp)
87{
88 QQuickControlPrivate::handleMove(point, timestamp);
89 if (interactive) {
90 updatePressed(pressed: true, pos: point);
91 return true;
92 }
93 return false;
94}
95
96bool QQuickPageIndicatorPrivate::handleRelease(const QPointF &point, ulong timestamp)
97{
98 Q_Q(QQuickPageIndicator);
99 QQuickControlPrivate::handleRelease(point, timestamp);
100 if (interactive) {
101 if (pressedItem && contentItem)
102 q->setCurrentIndex(contentItem->childItems().indexOf(t: pressedItem));
103 updatePressed(pressed: false);
104 return true;
105 }
106 return false;
107}
108
109void QQuickPageIndicatorPrivate::handleUngrab()
110{
111 QQuickControlPrivate::handleUngrab();
112 if (interactive)
113 updatePressed(pressed: false);
114}
115
116QQuickItem *QQuickPageIndicatorPrivate::itemAt(const QPointF &pos) const
117{
118 Q_Q(const QQuickPageIndicator);
119 if (!contentItem || !q->contains(point: pos))
120 return nullptr;
121
122 QPointF contentPos = q->mapToItem(item: contentItem, point: pos);
123 QQuickItem *item = contentItem->childAt(x: contentPos.x(), y: contentPos.y());
124 while (item && item->parentItem() != contentItem)
125 item = item->parentItem();
126 if (item && !QQuickItemPrivate::get(item)->isTransparentForPositioner())
127 return item;
128
129 // find the nearest
130 qreal distance = qInf();
131 QQuickItem *nearest = nullptr;
132 const auto childItems = contentItem->childItems();
133 for (QQuickItem *child : childItems) {
134 if (QQuickItemPrivate::get(item: child)->isTransparentForPositioner())
135 continue;
136
137 QPointF center = child->boundingRect().center();
138 QPointF pt = contentItem->mapToItem(item: child, point: contentPos);
139
140 qreal len = QLineF(center, pt).length();
141 if (len < distance) {
142 distance = len;
143 nearest = child;
144 }
145 }
146 return nearest;
147}
148
149void QQuickPageIndicatorPrivate::updatePressed(bool pressed, const QPointF &pos)
150{
151 QQuickItem *prevItem = pressedItem;
152 pressedItem = pressed ? itemAt(pos) : nullptr;
153 if (prevItem != pressedItem) {
154 setContextProperty(item: prevItem, QStringLiteral("pressed"), value: false);
155 setContextProperty(item: pressedItem, QStringLiteral("pressed"), value: pressed);
156 }
157}
158
159void QQuickPageIndicatorPrivate::setContextProperty(QQuickItem *item, const QString &name, const QVariant &value)
160{
161 QQmlContext *context = qmlContext(item);
162 if (context && context->isValid()) {
163 context = context->parentContext();
164 if (context && context->isValid())
165 context->setContextProperty(name, value);
166 }
167}
168
169void QQuickPageIndicatorPrivate::itemChildAdded(QQuickItem *, QQuickItem *child)
170{
171 if (!QQuickItemPrivate::get(item: child)->isTransparentForPositioner())
172 setContextProperty(item: child, QStringLiteral("pressed"), value: false);
173}
174
175QQuickPageIndicator::QQuickPageIndicator(QQuickItem *parent)
176 : QQuickControl(*(new QQuickPageIndicatorPrivate), parent)
177{
178}
179
180QQuickPageIndicator::~QQuickPageIndicator()
181{
182 Q_D(QQuickPageIndicator);
183 if (d->contentItem)
184 QQuickItemPrivate::get(item: d->contentItem)->removeItemChangeListener(d, types: QQuickItemPrivate::Children);
185}
186
187/*!
188 \qmlproperty int QtQuick.Controls::PageIndicator::count
189
190 This property holds the number of pages.
191*/
192int QQuickPageIndicator::count() const
193{
194 Q_D(const QQuickPageIndicator);
195 return d->count;
196}
197
198void QQuickPageIndicator::setCount(int count)
199{
200 Q_D(QQuickPageIndicator);
201 if (d->count == count)
202 return;
203
204 d->count = count;
205 emit countChanged();
206}
207
208/*!
209 \qmlproperty int QtQuick.Controls::PageIndicator::currentIndex
210
211 This property holds the index of the current page.
212*/
213int QQuickPageIndicator::currentIndex() const
214{
215 Q_D(const QQuickPageIndicator);
216 return d->currentIndex;
217}
218
219void QQuickPageIndicator::setCurrentIndex(int index)
220{
221 Q_D(QQuickPageIndicator);
222 if (d->currentIndex == index)
223 return;
224
225 d->currentIndex = index;
226 emit currentIndexChanged();
227}
228
229/*!
230 \qmlproperty bool QtQuick.Controls::PageIndicator::interactive
231
232 This property holds whether the control is interactive. An interactive page indicator
233 reacts to presses and automatically changes the \l {currentIndex}{current index}
234 appropriately.
235
236 \snippet qtquickcontrols-pageindicator-interactive.qml 1
237
238 \note Page indicators are typically quite small (in order to avoid
239 distracting the user from the actual content of the user interface). They
240 can be hard to click, and might not be easily recognized as interactive by
241 the user. For these reasons, they are best used to complement primary
242 methods of navigation (such as \l SwipeView), not replace them.
243
244 The default value is \c false.
245*/
246bool QQuickPageIndicator::isInteractive() const
247{
248 Q_D(const QQuickPageIndicator);
249 return d->interactive;
250}
251
252void QQuickPageIndicator::setInteractive(bool interactive)
253{
254 Q_D(QQuickPageIndicator);
255 if (d->interactive == interactive)
256 return;
257
258 d->interactive = interactive;
259 if (interactive) {
260 setAcceptedMouseButtons(Qt::LeftButton);
261#if QT_CONFIG(quicktemplates2_multitouch)
262 setAcceptTouchEvents(true);
263#endif
264#if QT_CONFIG(cursor)
265 setCursor(Qt::ArrowCursor);
266#endif
267 } else {
268 setAcceptedMouseButtons(Qt::NoButton);
269#if QT_CONFIG(quicktemplates2_multitouch)
270 setAcceptTouchEvents(true);
271#endif
272#if QT_CONFIG(cursor)
273 unsetCursor();
274#endif
275 }
276 emit interactiveChanged();
277}
278
279/*!
280 \qmlproperty Component QtQuick.Controls::PageIndicator::delegate
281
282 This property holds a delegate that presents a page.
283
284 The following properties are available in the context of each delegate:
285 \table
286 \row \li \b index : int \li The index of the item
287 \row \li \b pressed : bool \li Whether the item is pressed
288 \endtable
289*/
290QQmlComponent *QQuickPageIndicator::delegate() const
291{
292 Q_D(const QQuickPageIndicator);
293 return d->delegate;
294}
295
296void QQuickPageIndicator::setDelegate(QQmlComponent *delegate)
297{
298 Q_D(QQuickPageIndicator);
299 if (d->delegate == delegate)
300 return;
301
302 d->delegate = delegate;
303 emit delegateChanged();
304}
305
306void QQuickPageIndicator::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
307{
308 Q_D(QQuickPageIndicator);
309 QQuickControl::contentItemChange(newItem, oldItem);
310 if (oldItem)
311 QQuickItemPrivate::get(item: oldItem)->removeItemChangeListener(d, types: QQuickItemPrivate::Children);
312 if (newItem)
313 QQuickItemPrivate::get(item: newItem)->addItemChangeListener(listener: d, types: QQuickItemPrivate::Children);
314}
315
316#if QT_CONFIG(quicktemplates2_multitouch)
317void QQuickPageIndicator::touchEvent(QTouchEvent *event)
318{
319 Q_D(QQuickPageIndicator);
320 if (d->interactive)
321 QQuickControl::touchEvent(event);
322 else
323 event->ignore(); // QTBUG-61785
324}
325#endif
326
327#if QT_CONFIG(accessibility)
328QAccessible::Role QQuickPageIndicator::accessibleRole() const
329{
330 return QAccessible::Indicator;
331}
332#endif
333
334QT_END_NAMESPACE
335
336#include "moc_qquickpageindicator_p.cpp"
337

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