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