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

source code of qtdeclarative/src/quicktemplates/qquicktabbar.cpp