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
41QT_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
98class QQuickTabBarPrivate : public QQuickContainerPrivate
99{
100 Q_DECLARE_PUBLIC(QQuickTabBar)
101
102public:
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
118class QQuickTabBarAttachedPrivate : public QObjectPrivate
119{
120 Q_DECLARE_PUBLIC(QQuickTabBarAttached)
121
122public:
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
134void QQuickTabBarPrivate::updateCurrentItem()
135{
136 QQuickTabButton *button = qobject_cast<QQuickTabButton *>(object: contentModel->get(index: currentIndex));
137 if (button)
138 button->setChecked(true);
139}
140
141void QQuickTabBarPrivate::updateCurrentIndex()
142{
143 Q_Q(QQuickTabBar);
144 QQuickTabButton *button = qobject_cast<QQuickTabButton *>(object: q->sender());
145 if (button && button->isChecked())
146 q->setCurrentIndex(contentModel->indexOf(object: button, objectContext: nullptr));
147}
148
149void 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(asize: count);
161
162 for (int i = 0; i < count; ++i) {
163 QQuickItem *item = q->itemAt(index: 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(a: 0, b: count - 1) * spacing;
175 const qreal itemWidth = (contentItem->width() - reservedWidth - totalSpacing) / qMax(a: 1, b: resizableCount);
176
177 updatingLayout = true;
178 for (QQuickItem *item : qAsConst(t&: 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
194qreal QQuickTabBarPrivate::getContentWidth() const
195{
196 Q_Q(const QQuickTabBar);
197 const int count = contentModel->count();
198 qreal totalWidth = qMax(a: 0, b: count - 1) * spacing;
199 for (int i = 0; i < count; ++i) {
200 QQuickItem *item = q->itemAt(index: 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
212qreal 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(index: i);
219 if (item)
220 maxHeight = qMax(a: maxHeight, b: item->implicitHeight());
221 }
222 return maxHeight;
223}
224
225void 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
235void QQuickTabBarPrivate::itemImplicitWidthChanged(QQuickItem *item)
236{
237 QQuickContainerPrivate::itemImplicitWidthChanged(item);
238 if (item != contentItem)
239 updateImplicitContentWidth();
240}
241
242void QQuickTabBarPrivate::itemImplicitHeightChanged(QQuickItem *item)
243{
244 QQuickContainerPrivate::itemImplicitHeightChanged(item);
245 if (item != contentItem)
246 updateImplicitContentHeight();
247}
248
249QQuickTabBar::QQuickTabBar(QQuickItem *parent)
250 : QQuickContainer(*(new QQuickTabBarPrivate), parent)
251{
252 Q_D(QQuickTabBar);
253 d->changeTypes |= QQuickItemPrivate::Geometry | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight;
254 setFlag(flag: ItemIsFocusScope);
255 QObjectPrivate::connect(sender: this, signal: &QQuickTabBar::currentIndexChanged, receiverPrivate: d, slot: &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*/
274QQuickTabBar::Position QQuickTabBar::position() const
275{
276 Q_D(const QQuickTabBar);
277 return d->position;
278}
279
280void 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
316QQuickTabBarAttached *QQuickTabBar::qmlAttachedProperties(QObject *object)
317{
318 return new QQuickTabBarAttached(object);
319}
320
321void QQuickTabBar::updatePolish()
322{
323 Q_D(QQuickTabBar);
324 QQuickContainer::updatePolish();
325 d->updateLayout();
326}
327
328void QQuickTabBar::componentComplete()
329{
330 Q_D(QQuickTabBar);
331 QQuickContainer::componentComplete();
332 d->updateCurrentItem();
333 d->updateLayout();
334}
335
336void QQuickTabBar::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
337{
338 Q_D(QQuickTabBar);
339 QQuickContainer::geometryChanged(newGeometry, oldGeometry);
340 d->updateLayout();
341}
342
343bool QQuickTabBar::isContent(QQuickItem *item) const
344{
345 return qobject_cast<QQuickTabButton *>(object: item);
346}
347
348void 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 *>(object: item))
354 QObjectPrivate::connect(sender: button, signal: &QQuickTabButton::checkedChanged, receiverPrivate: d, slot: &QQuickTabBarPrivate::updateCurrentIndex);
355 QQuickTabBarAttached *attached = qobject_cast<QQuickTabBarAttached *>(object: qmlAttachedPropertiesObject<QQuickTabBar>(obj: item));
356 if (attached)
357 QQuickTabBarAttachedPrivate::get(attached)->update(tabBar: this, index);
358 d->updateImplicitContentSize();
359 if (isComponentComplete())
360 polish();
361}
362
363void QQuickTabBar::itemMoved(int index, QQuickItem *item)
364{
365 QQuickTabBarAttached *attached = qobject_cast<QQuickTabBarAttached *>(object: qmlAttachedPropertiesObject<QQuickTabBar>(obj: item));
366 if (attached)
367 QQuickTabBarAttachedPrivate::get(attached)->update(tabBar: this, index);
368}
369
370void QQuickTabBar::itemRemoved(int index, QQuickItem *item)
371{
372 Q_D(QQuickTabBar);
373 Q_UNUSED(index);
374 if (QQuickTabButton *button = qobject_cast<QQuickTabButton *>(object: item))
375 QObjectPrivate::disconnect(sender: button, signal: &QQuickTabButton::checkedChanged, receiverPrivate: d, slot: &QQuickTabBarPrivate::updateCurrentIndex);
376 QQuickTabBarAttached *attached = qobject_cast<QQuickTabBarAttached *>(object: qmlAttachedPropertiesObject<QQuickTabBar>(obj: item));
377 if (attached)
378 QQuickTabBarAttachedPrivate::get(attached)->update(tabBar: nullptr, index: -1);
379 d->updateImplicitContentSize();
380 if (isComponentComplete())
381 polish();
382}
383
384QFont QQuickTabBar::defaultFont() const
385{
386 return QQuickTheme::font(scope: QQuickTheme::TabBar);
387}
388
389QPalette QQuickTabBar::defaultPalette() const
390{
391 return QQuickTheme::palette(scope: QQuickTheme::TabBar);
392}
393
394#if QT_CONFIG(accessibility)
395QAccessible::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
435void 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(sender: oldTabBar, signal: &QQuickTabBar::positionChanged, receiver: q, slot: &QQuickTabBarAttached::positionChanged);
448 if (newTabBar)
449 QObject::connect(sender: newTabBar, signal: &QQuickTabBar::positionChanged, receiver: q, slot: &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
459QQuickTabBarAttached::QQuickTabBarAttached(QObject *parent)
460 : QObject(*(new QQuickTabBarAttachedPrivate), parent)
461{
462}
463
464int QQuickTabBarAttached::index() const
465{
466 Q_D(const QQuickTabBarAttached);
467 return d->index;
468}
469
470QQuickTabBar *QQuickTabBarAttached::tabBar() const
471{
472 Q_D(const QQuickTabBarAttached);
473 return d->tabBar;
474}
475
476QQuickTabBar::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
484QT_END_NAMESPACE
485

source code of qtquickcontrols2/src/quicktemplates2/qquicktabbar.cpp