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 "qquickswipeview_p.h"
38 
39 #include <QtQml/qqmlinfo.h>
40 #include <QtQuickTemplates2/private/qquickcontainer_p_p.h>
41 
42 QT_BEGIN_NAMESPACE
43 
44 /*!
45     \qmltype SwipeView
46     \inherits Container
47 //!     \instantiates QQuickSwipeView
48     \inqmlmodule QtQuick.Controls
49     \since 5.7
50     \ingroup qtquickcontrols2-navigation
51     \ingroup qtquickcontrols2-containers
52     \ingroup qtquickcontrols2-focusscopes
53     \brief Enables the user to navigate pages by swiping sideways.
54 
55     SwipeView provides a swipe-based navigation model.
56 
57     \image qtquickcontrols2-swipeview.gif
58 
59     SwipeView is populated with a set of pages. One page is visible at a time.
60     The user can navigate between the pages by swiping sideways. Notice that
61     SwipeView itself is entirely non-visual. It is recommended to combine it
62     with PageIndicator, to give the user a visual clue that there are multiple
63     pages.
64 
65     \snippet qtquickcontrols2-swipeview-indicator.qml 1
66 
67     As shown above, SwipeView is typically populated with a static set of
68     pages that are defined inline as children of the view. It is also possible
69     to \l {Container::addItem()}{add}, \l {Container::insertItem()}{insert},
70     \l {Container::moveItem()}{move}, and \l {Container::removeItem()}{remove}
71     pages dynamically at run time.
72 
73     It is generally not advisable to add excessive amounts of pages to a
74     SwipeView. However, when the amount of pages grows larger, or individual
75     pages are relatively complex, it may be desirable to free up resources by
76     unloading pages that are outside the immediate reach of the user.
77     The following example presents how to use \l Loader to keep a maximum of
78     three pages simultaneously instantiated.
79 
80     \code
81     SwipeView {
82         Repeater {
83             model: 6
84             Loader {
85                 active: SwipeView.isCurrentItem || SwipeView.isNextItem || SwipeView.isPreviousItem
86                 sourceComponent: Text {
87                     text: index
88                     Component.onCompleted: console.log("created:", index)
89                     Component.onDestruction: console.log("destroyed:", index)
90                 }
91             }
92         }
93     }
94     \endcode
95 
96     \note SwipeView takes over the geometry management of items added to the
97           view. Using anchors on the items is not supported, and any \c width
98           or \c height assignment will be overridden by the view. Notice that
99           this only applies to the root of the item. Specifying width and height,
100           or using anchors for its children works as expected.
101 
102     \sa TabBar, PageIndicator, {Customizing SwipeView}, {Navigation Controls}, {Container Controls},
103         {Focus Management in Qt Quick Controls}
104 */
105 
106 class QQuickSwipeViewPrivate : public QQuickContainerPrivate
107 {
108     Q_DECLARE_PUBLIC(QQuickSwipeView)
109 
110 public:
111     void resizeItem(QQuickItem *item);
112     void resizeItems();
113 
114     static QQuickSwipeViewPrivate *get(QQuickSwipeView *view);
115 
116     void itemImplicitWidthChanged(QQuickItem *item) override;
117     void itemImplicitHeightChanged(QQuickItem *item) override;
118 
119     qreal getContentWidth() const override;
120     qreal getContentHeight() const override;
121 
122     bool interactive = true;
123     Qt::Orientation orientation = Qt::Horizontal;
124 };
125 
126 class QQuickSwipeViewAttachedPrivate : public QObjectPrivate
127 {
128     Q_DECLARE_PUBLIC(QQuickSwipeViewAttached)
129 
130 public:
get(QQuickSwipeViewAttached * attached)131     static QQuickSwipeViewAttachedPrivate *get(QQuickSwipeViewAttached *attached)
132     {
133         return attached->d_func();
134     }
135 
136     void update(QQuickSwipeView *newView, int newIndex);
137     void updateCurrentIndex();
138     void setCurrentIndex(int i);
139 
140     QQuickSwipeView *swipeView = nullptr;
141     int index = -1;
142     int currentIndex = -1;
143 };
144 
resizeItems()145 void QQuickSwipeViewPrivate::resizeItems()
146 {
147     Q_Q(QQuickSwipeView);
148     const int count = q->count();
149     for (int i = 0; i < count; ++i) {
150         QQuickItem *item = itemAt(i);
151         if (item) {
152             QQuickAnchors *anchors = QQuickItemPrivate::get(item)->_anchors;
153             // TODO: expose QQuickAnchorLine so we can test for other conflicting anchors
154             if (anchors && (anchors->fill() || anchors->centerIn()) && !item->property("_q_QQuickSwipeView_warned").toBool()) {
155                 qmlWarning(item) << "SwipeView has detected conflicting anchors. Unable to layout the item.";
156                 item->setProperty("_q_QQuickSwipeView_warned", true);
157             }
158 
159             if (orientation == Qt::Horizontal)
160                 item->setY(0);
161             else
162                 item->setX(0);
163             item->setSize(QSizeF(contentItem->width(), contentItem->height()));
164         }
165     }
166 }
167 
get(QQuickSwipeView * view)168 QQuickSwipeViewPrivate *QQuickSwipeViewPrivate::get(QQuickSwipeView *view)
169 {
170     return view->d_func();
171 }
172 
itemImplicitWidthChanged(QQuickItem * item)173 void QQuickSwipeViewPrivate::itemImplicitWidthChanged(QQuickItem *item)
174 {
175     Q_Q(QQuickSwipeView);
176     QQuickContainerPrivate::itemImplicitWidthChanged(item);
177     if (item == q->currentItem())
178         updateImplicitContentWidth();
179 }
180 
itemImplicitHeightChanged(QQuickItem * item)181 void QQuickSwipeViewPrivate::itemImplicitHeightChanged(QQuickItem *item)
182 {
183     Q_Q(QQuickSwipeView);
184     QQuickContainerPrivate::itemImplicitHeightChanged(item);
185     if (item == q->currentItem())
186         updateImplicitContentHeight();
187 }
188 
getContentWidth() const189 qreal QQuickSwipeViewPrivate::getContentWidth() const
190 {
191     Q_Q(const QQuickSwipeView);
192     QQuickItem *currentItem = q->currentItem();
193     return currentItem ? currentItem->implicitWidth() : 0;
194 }
195 
getContentHeight() const196 qreal QQuickSwipeViewPrivate::getContentHeight() const
197 {
198     Q_Q(const QQuickSwipeView);
199     QQuickItem *currentItem = q->currentItem();
200     return currentItem ? currentItem->implicitHeight() : 0;
201 }
202 
QQuickSwipeView(QQuickItem * parent)203 QQuickSwipeView::QQuickSwipeView(QQuickItem *parent)
204     : QQuickContainer(*(new QQuickSwipeViewPrivate), parent)
205 {
206     Q_D(QQuickSwipeView);
207     d->changeTypes |= QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight;
208     setFlag(ItemIsFocusScope);
209     setActiveFocusOnTab(true);
210     QObjectPrivate::connect(this, &QQuickContainer::currentItemChanged, d, &QQuickControlPrivate::updateImplicitContentSize);
211 }
212 
213 /*!
214     \since QtQuick.Controls 2.1 (Qt 5.8)
215     \qmlproperty bool QtQuick.Controls::SwipeView::interactive
216 
217     This property describes whether the user can interact with the SwipeView.
218     The user cannot swipe a view that is not interactive.
219 
220     The default value is \c true.
221 */
isInteractive() const222 bool QQuickSwipeView::isInteractive() const
223 {
224     Q_D(const QQuickSwipeView);
225     return d->interactive;
226 }
227 
setInteractive(bool interactive)228 void QQuickSwipeView::setInteractive(bool interactive)
229 {
230     Q_D(QQuickSwipeView);
231     if (d->interactive == interactive)
232         return;
233 
234     d->interactive = interactive;
235     emit interactiveChanged();
236 }
237 
238 /*!
239     \since QtQuick.Controls 2.2 (Qt 5.9)
240     \qmlproperty enumeration QtQuick.Controls::SwipeView::orientation
241 
242     This property holds the orientation.
243 
244     Possible values:
245     \value Qt.Horizontal Horizontal (default)
246     \value Qt.Vertical Vertical
247 
248     \sa horizontal, vertical
249 */
orientation() const250 Qt::Orientation QQuickSwipeView::orientation() const
251 {
252     Q_D(const QQuickSwipeView);
253     return d->orientation;
254 }
255 
setOrientation(Qt::Orientation orientation)256 void QQuickSwipeView::setOrientation(Qt::Orientation orientation)
257 {
258     Q_D(QQuickSwipeView);
259     if (d->orientation == orientation)
260         return;
261 
262     d->orientation = orientation;
263     if (isComponentComplete())
264         d->resizeItems();
265     emit orientationChanged();
266 }
267 
268 /*!
269     \since QtQuick.Controls 2.3 (Qt 5.10)
270     \qmlproperty bool QtQuick.Controls::SwipeView::horizontal
271     \readonly
272 
273     This property holds whether the swipe view is horizontal.
274 
275     \sa orientation
276 */
isHorizontal() const277 bool QQuickSwipeView::isHorizontal() const
278 {
279     Q_D(const QQuickSwipeView);
280     return d->orientation == Qt::Horizontal;
281 }
282 
283 /*!
284     \since QtQuick.Controls 2.3 (Qt 5.10)
285     \qmlproperty bool QtQuick.Controls::SwipeView::vertical
286     \readonly
287 
288     This property holds whether the swipe view is vertical.
289 
290     \sa orientation
291 */
isVertical() const292 bool QQuickSwipeView::isVertical() const
293 {
294     Q_D(const QQuickSwipeView);
295     return d->orientation == Qt::Vertical;
296 }
297 
qmlAttachedProperties(QObject * object)298 QQuickSwipeViewAttached *QQuickSwipeView::qmlAttachedProperties(QObject *object)
299 {
300     return new QQuickSwipeViewAttached(object);
301 }
302 
geometryChanged(const QRectF & newGeometry,const QRectF & oldGeometry)303 void QQuickSwipeView::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
304 {
305     Q_D(QQuickSwipeView);
306     QQuickContainer::geometryChanged(newGeometry, oldGeometry);
307     d->resizeItems();
308 }
309 
itemAdded(int index,QQuickItem * item)310 void QQuickSwipeView::itemAdded(int index, QQuickItem *item)
311 {
312     Q_D(QQuickSwipeView);
313     QQuickItemPrivate::get(item)->setCulled(true); // QTBUG-51078, QTBUG-51669
314     if (isComponentComplete())
315         item->setSize(QSizeF(d->contentItem->width(), d->contentItem->height()));
316     QQuickSwipeViewAttached *attached = qobject_cast<QQuickSwipeViewAttached *>(qmlAttachedPropertiesObject<QQuickSwipeView>(item));
317     if (attached)
318         QQuickSwipeViewAttachedPrivate::get(attached)->update(this, index);
319 }
320 
itemMoved(int index,QQuickItem * item)321 void QQuickSwipeView::itemMoved(int index, QQuickItem *item)
322 {
323     QQuickSwipeViewAttached *attached = qobject_cast<QQuickSwipeViewAttached *>(qmlAttachedPropertiesObject<QQuickSwipeView>(item));
324     if (attached)
325         QQuickSwipeViewAttachedPrivate::get(attached)->update(this, index);
326 }
327 
itemRemoved(int,QQuickItem * item)328 void QQuickSwipeView::itemRemoved(int, QQuickItem *item)
329 {
330     QQuickSwipeViewAttached *attached = qobject_cast<QQuickSwipeViewAttached *>(qmlAttachedPropertiesObject<QQuickSwipeView>(item));
331     if (attached)
332         QQuickSwipeViewAttachedPrivate::get(attached)->update(nullptr, -1);
333 }
334 
335 #if QT_CONFIG(accessibility)
accessibleRole() const336 QAccessible::Role QQuickSwipeView::accessibleRole() const
337 {
338     return QAccessible::PageTabList;
339 }
340 #endif
341 
342 /*!
343     \qmlattachedproperty int QtQuick.Controls::SwipeView::index
344     \readonly
345 
346     This attached property holds the index of each child item in the SwipeView.
347 
348     It is attached to each child item of the SwipeView.
349 */
350 
351 /*!
352     \qmlattachedproperty bool QtQuick.Controls::SwipeView::isCurrentItem
353     \readonly
354 
355     This attached property is \c true if this child is the current item.
356 
357     It is attached to each child item of the SwipeView.
358 */
359 
360 /*!
361     \qmlattachedproperty bool QtQuick.Controls::SwipeView::isNextItem
362     \since QtQuick.Controls 2.1 (Qt 5.8)
363     \readonly
364 
365     This attached property is \c true if this child is the next item.
366 
367     It is attached to each child item of the SwipeView.
368 */
369 
370 /*!
371     \qmlattachedproperty bool QtQuick.Controls::SwipeView::isPreviousItem
372     \since QtQuick.Controls 2.1 (Qt 5.8)
373     \readonly
374 
375     This attached property is \c true if this child is the previous item.
376 
377     It is attached to each child item of the SwipeView.
378 */
379 
380 /*!
381     \qmlattachedproperty SwipeView QtQuick.Controls::SwipeView::view
382     \readonly
383 
384     This attached property holds the view that manages this child item.
385 
386     It is attached to each child item of the SwipeView.
387 */
388 
updateCurrentIndex()389 void QQuickSwipeViewAttachedPrivate::updateCurrentIndex()
390 {
391     setCurrentIndex(swipeView ? swipeView->currentIndex() : -1);
392 }
393 
setCurrentIndex(int i)394 void QQuickSwipeViewAttachedPrivate::setCurrentIndex(int i)
395 {
396     if (i == currentIndex)
397         return;
398 
399     Q_Q(QQuickSwipeViewAttached);
400     const bool wasCurrent = q->isCurrentItem();
401     const bool wasNext = q->isNextItem();
402     const bool wasPrevious = q->isPreviousItem();
403 
404     currentIndex = i;
405     if (wasCurrent != q->isCurrentItem())
406         emit q->isCurrentItemChanged();
407     if (wasNext != q->isNextItem())
408         emit q->isNextItemChanged();
409     if (wasPrevious != q->isPreviousItem())
410         emit q->isPreviousItemChanged();
411 }
412 
update(QQuickSwipeView * newView,int newIndex)413 void QQuickSwipeViewAttachedPrivate::update(QQuickSwipeView *newView, int newIndex)
414 {
415     Q_Q(QQuickSwipeViewAttached);
416     int oldIndex = index;
417     QQuickSwipeView *oldView = swipeView;
418 
419     index = newIndex;
420     swipeView = newView;
421 
422     if (oldView != newView) {
423         if (oldView) {
424             disconnect(oldView, &QQuickSwipeView::currentIndexChanged,
425                        this, &QQuickSwipeViewAttachedPrivate::updateCurrentIndex);
426         }
427         if (newView) {
428             connect(newView, &QQuickSwipeView::currentIndexChanged,
429                     this, &QQuickSwipeViewAttachedPrivate::updateCurrentIndex);
430         }
431         emit q->viewChanged();
432     }
433     if (oldIndex != newIndex)
434         emit q->indexChanged();
435 
436     updateCurrentIndex();
437 }
438 
QQuickSwipeViewAttached(QObject * parent)439 QQuickSwipeViewAttached::QQuickSwipeViewAttached(QObject *parent)
440     : QObject(*(new QQuickSwipeViewAttachedPrivate), parent)
441 {
442     if (!qobject_cast<QQuickItem *>(parent))
443         qmlWarning(parent) << "SwipeView: attached properties must be accessed from within a child item";
444 }
445 
index() const446 int QQuickSwipeViewAttached::index() const
447 {
448     Q_D(const QQuickSwipeViewAttached);
449     return d->index;
450 }
451 
isCurrentItem() const452 bool QQuickSwipeViewAttached::isCurrentItem() const
453 {
454     Q_D(const QQuickSwipeViewAttached);
455     return d->index != -1 && d->currentIndex != -1 && d->index == d->currentIndex;
456 }
457 
view() const458 QQuickSwipeView *QQuickSwipeViewAttached::view() const
459 {
460     Q_D(const QQuickSwipeViewAttached);
461     return d->swipeView;
462 }
463 
isNextItem() const464 bool QQuickSwipeViewAttached::isNextItem() const
465 {
466     Q_D(const QQuickSwipeViewAttached);
467     return d->index != -1 && d->currentIndex != -1 && d->index == d->currentIndex + 1;
468 }
469 
isPreviousItem() const470 bool QQuickSwipeViewAttached::isPreviousItem() const
471 {
472     Q_D(const QQuickSwipeViewAttached);
473     return d->index != -1 && d->currentIndex != -1 && d->index == d->currentIndex - 1;
474 }
475 
476 QT_END_NAMESPACE
477