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 "qquicktabbar_p.h"
38 #include "qquicktabbutton_p.h"
39 #include "qquickcontainer_p_p.h"
40 
41 QT_BEGIN_NAMESPACE
42 
43 /*!
44     \qmltype TabBar
45     \inherits Container
46 //!     \instantiates QQuickTabBar
47     \inqmlmodule QtQuick.Controls
48     \since 5.7
49     \ingroup qtquickcontrols2-navigation
50     \ingroup qtquickcontrols2-containers
51     \ingroup qtquickcontrols2-focusscopes
52     \brief Allows the user to switch between different views or subtasks.
53 
54     TabBar provides a tab-based navigation model.
55 
56     \image qtquickcontrols2-tabbar-wireframe.png
57 
58     TabBar is populated with TabButton controls, and can be used together with
59     any layout or container control that provides \c currentIndex -property,
60     such as \l StackLayout or \l SwipeView
61 
62     \snippet qtquickcontrols2-tabbar.qml 1
63 
64     As shown above, TabBar is typically populated with a static set of tab buttons
65     that are defined inline as children of the tab bar. It is also possible to
66     \l {Container::addItem()}{add}, \l {Container::insertItem()}{insert},
67     \l {Container::moveItem()}{move}, and \l {Container::removeItem()}{remove}
68     items dynamically at run time. The items can be accessed using
69     \l {Container::}{itemAt()} or \l {Container::}{contentChildren}.
70 
71     \section2 Resizing Tabs
72 
73     By default, TabBar resizes its buttons to fit the width of the control.
74     The available space is distributed equally to each button. The default
75     resizing behavior can be overridden by setting an explicit width for the
76     buttons.
77 
78     The following example illustrates how to keep each tab button at their
79     implicit size instead of being resized to fit the tabbar:
80 
81     \borderedimage qtquickcontrols2-tabbar-explicit.png
82 
83     \snippet qtquickcontrols2-tabbar-explicit.qml 1
84 
85     \section2 Flickable Tabs
86 
87     If the total width of the buttons exceeds the available width of the tab bar,
88     it automatically becomes flickable.
89 
90     \image qtquickcontrols2-tabbar-flickable.png
91 
92     \snippet qtquickcontrols2-tabbar-flickable.qml 1
93 
94     \sa TabButton, {Customizing TabBar}, {Navigation Controls}, {Container Controls},
95         {Focus Management in Qt Quick Controls}
96 */
97 
98 class QQuickTabBarPrivate : public QQuickContainerPrivate
99 {
100     Q_DECLARE_PUBLIC(QQuickTabBar)
101 
102 public:
103     void updateCurrentItem();
104     void updateCurrentIndex();
105     void updateLayout();
106 
107     qreal getContentWidth() const override;
108     qreal getContentHeight() const override;
109 
110     void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override;
111     void itemImplicitWidthChanged(QQuickItem *item) override;
112     void itemImplicitHeightChanged(QQuickItem *item) override;
113 
114     bool updatingLayout = false;
115     QQuickTabBar::Position position = QQuickTabBar::Header;
116 };
117 
118 class QQuickTabBarAttachedPrivate : public QObjectPrivate
119 {
120     Q_DECLARE_PUBLIC(QQuickTabBarAttached)
121 
122 public:
get(QQuickTabBarAttached * attached)123     static QQuickTabBarAttachedPrivate *get(QQuickTabBarAttached *attached)
124     {
125         return attached->d_func();
126     }
127 
128     void update(QQuickTabBar *tabBar, int index);
129 
130     int index = -1;
131     QQuickTabBar *tabBar = nullptr;
132 };
133 
updateCurrentItem()134 void QQuickTabBarPrivate::updateCurrentItem()
135 {
136     QQuickTabButton *button = qobject_cast<QQuickTabButton *>(contentModel->get(currentIndex));
137     if (button)
138         button->setChecked(true);
139 }
140 
updateCurrentIndex()141 void QQuickTabBarPrivate::updateCurrentIndex()
142 {
143     Q_Q(QQuickTabBar);
144     QQuickTabButton *button = qobject_cast<QQuickTabButton *>(q->sender());
145     if (button && button->isChecked())
146         q->setCurrentIndex(contentModel->indexOf(button, nullptr));
147 }
148 
updateLayout()149 void QQuickTabBarPrivate::updateLayout()
150 {
151     Q_Q(QQuickTabBar);
152     const int count = contentModel->count();
153     if (count <= 0 || !contentItem)
154         return;
155 
156     qreal reservedWidth = 0;
157     int resizableCount = 0;
158 
159     QVector<QQuickItem *> allItems;
160     allItems.reserve(count);
161 
162     for (int i = 0; i < count; ++i) {
163         QQuickItem *item = q->itemAt(i);
164         if (item) {
165             QQuickItemPrivate *p = QQuickItemPrivate::get(item);
166             if (!p->widthValid)
167                 ++resizableCount;
168             else
169                 reservedWidth += item->width();
170             allItems += item;
171         }
172     }
173 
174     const qreal totalSpacing = qMax(0, count - 1) * spacing;
175     const qreal itemWidth = (contentItem->width() - reservedWidth - totalSpacing) / qMax(1, resizableCount);
176 
177     updatingLayout = true;
178     for (QQuickItem *item : qAsConst(allItems)) {
179         QQuickItemPrivate *p = QQuickItemPrivate::get(item);
180         if (!p->widthValid) {
181             item->setWidth(itemWidth);
182             p->widthValid = false;
183         }
184         if (!p->heightValid) {
185             item->setHeight(contentHeight);
186             p->heightValid = false;
187         } else {
188             item->setY((contentHeight - item->height()) / 2);
189         }
190     }
191     updatingLayout = false;
192 }
193 
getContentWidth() const194 qreal QQuickTabBarPrivate::getContentWidth() const
195 {
196     Q_Q(const QQuickTabBar);
197     const int count = contentModel->count();
198     qreal totalWidth = qMax(0, count - 1) * spacing;
199     for (int i = 0; i < count; ++i) {
200         QQuickItem *item = q->itemAt(i);
201         if (item) {
202             QQuickItemPrivate *p = QQuickItemPrivate::get(item);
203             if (!p->widthValid)
204                 totalWidth += item->implicitWidth();
205             else
206                 totalWidth += item->width();
207         }
208     }
209     return totalWidth;
210 }
211 
getContentHeight() const212 qreal QQuickTabBarPrivate::getContentHeight() const
213 {
214     Q_Q(const QQuickTabBar);
215     const int count = contentModel->count();
216     qreal maxHeight = 0;
217     for (int i = 0; i < count; ++i) {
218         QQuickItem *item = q->itemAt(i);
219         if (item)
220             maxHeight = qMax(maxHeight, item->implicitHeight());
221     }
222     return maxHeight;
223 }
224 
itemGeometryChanged(QQuickItem * item,QQuickGeometryChange change,const QRectF & diff)225 void QQuickTabBarPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff)
226 {
227     QQuickContainerPrivate::itemGeometryChanged(item, change, diff);
228     if (!updatingLayout) {
229         if (change.sizeChange())
230             updateImplicitContentSize();
231         updateLayout();
232     }
233 }
234 
itemImplicitWidthChanged(QQuickItem * item)235 void QQuickTabBarPrivate::itemImplicitWidthChanged(QQuickItem *item)
236 {
237     QQuickContainerPrivate::itemImplicitWidthChanged(item);
238     if (item != contentItem)
239         updateImplicitContentWidth();
240 }
241 
itemImplicitHeightChanged(QQuickItem * item)242 void QQuickTabBarPrivate::itemImplicitHeightChanged(QQuickItem *item)
243 {
244     QQuickContainerPrivate::itemImplicitHeightChanged(item);
245     if (item != contentItem)
246         updateImplicitContentHeight();
247 }
248 
QQuickTabBar(QQuickItem * parent)249 QQuickTabBar::QQuickTabBar(QQuickItem *parent)
250     : QQuickContainer(*(new QQuickTabBarPrivate), parent)
251 {
252     Q_D(QQuickTabBar);
253     d->changeTypes |= QQuickItemPrivate::Geometry | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight;
254     setFlag(ItemIsFocusScope);
255     QObjectPrivate::connect(this, &QQuickTabBar::currentIndexChanged, d, &QQuickTabBarPrivate::updateCurrentItem);
256 }
257 
258 /*!
259     \qmlproperty enumeration QtQuick.Controls::TabBar::position
260 
261     This property holds the position of the tab bar.
262 
263     \note If the tab bar is assigned as a header or footer of \l ApplicationWindow
264     or \l Page, the appropriate position is set automatically.
265 
266     Possible values:
267     \value TabBar.Header The tab bar is at the top, as a window or page header.
268     \value TabBar.Footer The tab bar is at the bottom, as a window or page footer.
269 
270     The default value is style-specific.
271 
272     \sa ApplicationWindow::header, ApplicationWindow::footer, Page::header, Page::footer
273 */
position() const274 QQuickTabBar::Position QQuickTabBar::position() const
275 {
276     Q_D(const QQuickTabBar);
277     return d->position;
278 }
279 
setPosition(Position position)280 void QQuickTabBar::setPosition(Position position)
281 {
282     Q_D(QQuickTabBar);
283     if (d->position == position)
284         return;
285 
286     d->position = position;
287     emit positionChanged();
288 }
289 
290 /*!
291     \since QtQuick.Controls 2.2 (Qt 5.9)
292     \qmlproperty real QtQuick.Controls::TabBar::contentWidth
293 
294     This property holds the content width. It is used for calculating the total
295     implicit width of the tab bar.
296 
297     \note This property is available in TabBar since QtQuick.Controls 2.2 (Qt 5.9),
298     but it was promoted to the Container base type in QtQuick.Controls 2.5 (Qt 5.12).
299 
300     \sa Container::contentWidth
301 */
302 
303 /*!
304     \since QtQuick.Controls 2.2 (Qt 5.9)
305     \qmlproperty real QtQuick.Controls::TabBar::contentHeight
306 
307     This property holds the content height. It is used for calculating the total
308     implicit height of the tab bar.
309 
310     \note This property is available in TabBar since QtQuick.Controls 2.2 (Qt 5.9),
311     but it was promoted to the Container base type in QtQuick.Controls 2.5 (Qt 5.12).
312 
313     \sa Container::contentHeight
314 */
315 
qmlAttachedProperties(QObject * object)316 QQuickTabBarAttached *QQuickTabBar::qmlAttachedProperties(QObject *object)
317 {
318     return new QQuickTabBarAttached(object);
319 }
320 
updatePolish()321 void QQuickTabBar::updatePolish()
322 {
323     Q_D(QQuickTabBar);
324     QQuickContainer::updatePolish();
325     d->updateLayout();
326 }
327 
componentComplete()328 void QQuickTabBar::componentComplete()
329 {
330     Q_D(QQuickTabBar);
331     QQuickContainer::componentComplete();
332     d->updateCurrentItem();
333     d->updateLayout();
334 }
335 
geometryChanged(const QRectF & newGeometry,const QRectF & oldGeometry)336 void QQuickTabBar::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
337 {
338     Q_D(QQuickTabBar);
339     QQuickContainer::geometryChanged(newGeometry, oldGeometry);
340     d->updateLayout();
341 }
342 
isContent(QQuickItem * item) const343 bool QQuickTabBar::isContent(QQuickItem *item) const
344 {
345     return qobject_cast<QQuickTabButton *>(item);
346 }
347 
itemAdded(int index,QQuickItem * item)348 void QQuickTabBar::itemAdded(int index, QQuickItem *item)
349 {
350     Q_D(QQuickTabBar);
351     Q_UNUSED(index);
352     QQuickItemPrivate::get(item)->setCulled(true); // QTBUG-55129
353     if (QQuickTabButton *button = qobject_cast<QQuickTabButton *>(item))
354         QObjectPrivate::connect(button, &QQuickTabButton::checkedChanged, d, &QQuickTabBarPrivate::updateCurrentIndex);
355     QQuickTabBarAttached *attached = qobject_cast<QQuickTabBarAttached *>(qmlAttachedPropertiesObject<QQuickTabBar>(item));
356     if (attached)
357         QQuickTabBarAttachedPrivate::get(attached)->update(this, index);
358     d->updateImplicitContentSize();
359     if (isComponentComplete())
360         polish();
361 }
362 
itemMoved(int index,QQuickItem * item)363 void QQuickTabBar::itemMoved(int index, QQuickItem *item)
364 {
365     QQuickTabBarAttached *attached = qobject_cast<QQuickTabBarAttached *>(qmlAttachedPropertiesObject<QQuickTabBar>(item));
366     if (attached)
367         QQuickTabBarAttachedPrivate::get(attached)->update(this, index);
368 }
369 
itemRemoved(int index,QQuickItem * item)370 void QQuickTabBar::itemRemoved(int index, QQuickItem *item)
371 {
372     Q_D(QQuickTabBar);
373     Q_UNUSED(index);
374     if (QQuickTabButton *button = qobject_cast<QQuickTabButton *>(item))
375         QObjectPrivate::disconnect(button, &QQuickTabButton::checkedChanged, d, &QQuickTabBarPrivate::updateCurrentIndex);
376     QQuickTabBarAttached *attached = qobject_cast<QQuickTabBarAttached *>(qmlAttachedPropertiesObject<QQuickTabBar>(item));
377     if (attached)
378         QQuickTabBarAttachedPrivate::get(attached)->update(nullptr, -1);
379     d->updateImplicitContentSize();
380     if (isComponentComplete())
381         polish();
382 }
383 
defaultFont() const384 QFont QQuickTabBar::defaultFont() const
385 {
386     return QQuickTheme::font(QQuickTheme::TabBar);
387 }
388 
defaultPalette() const389 QPalette QQuickTabBar::defaultPalette() const
390 {
391     return QQuickTheme::palette(QQuickTheme::TabBar);
392 }
393 
394 #if QT_CONFIG(accessibility)
accessibleRole() const395 QAccessible::Role QQuickTabBar::accessibleRole() const
396 {
397     return QAccessible::PageTabList;
398 }
399 #endif
400 
401 /*!
402     \qmlattachedproperty int QtQuick.Controls::TabBar::index
403     \since QtQuick.Controls 2.3 (Qt 5.10)
404     \readonly
405 
406     This attached property holds the index of each tab button in the TabBar.
407 
408     It is attached to each tab button of the TabBar.
409 */
410 
411 /*!
412     \qmlattachedproperty TabBar QtQuick.Controls::TabBar::tabBar
413     \since QtQuick.Controls 2.3 (Qt 5.10)
414     \readonly
415 
416     This attached property holds the tab bar that manages this tab button.
417 
418     It is attached to each tab button of the TabBar.
419 */
420 
421 /*!
422     \qmlattachedproperty enumeration QtQuick.Controls::TabBar::position
423     \since QtQuick.Controls 2.3 (Qt 5.10)
424     \readonly
425 
426     This attached property holds the position of the tab bar.
427 
428     It is attached to each tab button of the TabBar.
429 
430     Possible values:
431     \value TabBar.Header The tab bar is at the top, as a window or page header.
432     \value TabBar.Footer The tab bar is at the bottom, as a window or page footer.
433 */
434 
update(QQuickTabBar * newTabBar,int newIndex)435 void QQuickTabBarAttachedPrivate::update(QQuickTabBar *newTabBar, int newIndex)
436 {
437     Q_Q(QQuickTabBarAttached);
438     const int oldIndex = index;
439     const QQuickTabBar *oldTabBar = tabBar;
440     const QQuickTabBar::Position oldPos = q->position();
441 
442     index = newIndex;
443     tabBar = newTabBar;
444 
445     if (oldTabBar != newTabBar) {
446         if (oldTabBar)
447             QObject::disconnect(oldTabBar, &QQuickTabBar::positionChanged, q, &QQuickTabBarAttached::positionChanged);
448         if (newTabBar)
449             QObject::connect(newTabBar, &QQuickTabBar::positionChanged, q, &QQuickTabBarAttached::positionChanged);
450         emit q->tabBarChanged();
451     }
452 
453     if (oldIndex != newIndex)
454         emit q->indexChanged();
455     if (oldPos != q->position())
456         emit q->positionChanged();
457 }
458 
QQuickTabBarAttached(QObject * parent)459 QQuickTabBarAttached::QQuickTabBarAttached(QObject *parent)
460     : QObject(*(new QQuickTabBarAttachedPrivate), parent)
461 {
462 }
463 
index() const464 int QQuickTabBarAttached::index() const
465 {
466     Q_D(const QQuickTabBarAttached);
467     return d->index;
468 }
469 
tabBar() const470 QQuickTabBar *QQuickTabBarAttached::tabBar() const
471 {
472     Q_D(const QQuickTabBarAttached);
473     return d->tabBar;
474 }
475 
position() const476 QQuickTabBar::Position QQuickTabBarAttached::position() const
477 {
478     Q_D(const QQuickTabBarAttached);
479     if (!d->tabBar)
480         return QQuickTabBar::Header;
481     return d->tabBar->position();
482 }
483 
484 QT_END_NAMESPACE
485