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 | |
10 | QT_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 | |
53 | class QQuickPageIndicatorPrivate : public QQuickControlPrivate |
54 | { |
55 | Q_DECLARE_PUBLIC(QQuickPageIndicator) |
56 | |
57 | public: |
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 | |
76 | bool 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 | |
86 | bool 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 | |
96 | bool 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 | |
109 | void QQuickPageIndicatorPrivate::handleUngrab() |
110 | { |
111 | QQuickControlPrivate::handleUngrab(); |
112 | if (interactive) |
113 | updatePressed(pressed: false); |
114 | } |
115 | |
116 | QQuickItem *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 | |
149 | void 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 | |
159 | void 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 | |
169 | void QQuickPageIndicatorPrivate::itemChildAdded(QQuickItem *, QQuickItem *child) |
170 | { |
171 | if (!QQuickItemPrivate::get(item: child)->isTransparentForPositioner()) |
172 | setContextProperty(item: child, QStringLiteral("pressed" ), value: false); |
173 | } |
174 | |
175 | QQuickPageIndicator::QQuickPageIndicator(QQuickItem *parent) |
176 | : QQuickControl(*(new QQuickPageIndicatorPrivate), parent) |
177 | { |
178 | } |
179 | |
180 | QQuickPageIndicator::~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 | */ |
192 | int QQuickPageIndicator::count() const |
193 | { |
194 | Q_D(const QQuickPageIndicator); |
195 | return d->count; |
196 | } |
197 | |
198 | void 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 | */ |
213 | int QQuickPageIndicator::currentIndex() const |
214 | { |
215 | Q_D(const QQuickPageIndicator); |
216 | return d->currentIndex; |
217 | } |
218 | |
219 | void 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 | */ |
246 | bool QQuickPageIndicator::isInteractive() const |
247 | { |
248 | Q_D(const QQuickPageIndicator); |
249 | return d->interactive; |
250 | } |
251 | |
252 | void 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 | */ |
290 | QQmlComponent *QQuickPageIndicator::delegate() const |
291 | { |
292 | Q_D(const QQuickPageIndicator); |
293 | return d->delegate; |
294 | } |
295 | |
296 | void 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 | |
306 | void 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) |
317 | void 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) |
328 | QAccessible::Role QQuickPageIndicator::accessibleRole() const |
329 | { |
330 | return QAccessible::Indicator; |
331 | } |
332 | #endif |
333 | |
334 | QT_END_NAMESPACE |
335 | |
336 | #include "moc_qquickpageindicator_p.cpp" |
337 | |