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//! \instantiates 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.png
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 Q_Q(const QQuickTabBar);
171 const int count = contentModel->count();
172 qreal totalWidth = qMax(a: 0, b: count - 1) * spacing;
173 for (int i = 0; i < count; ++i) {
174 QQuickItem *item = q->itemAt(index: i);
175 if (item) {
176 QQuickItemPrivate *p = QQuickItemPrivate::get(item);
177 if (!p->widthValid())
178 totalWidth += item->implicitWidth();
179 else
180 totalWidth += item->width();
181 }
182 }
183 return totalWidth;
184}
185
186qreal QQuickTabBarPrivate::getContentHeight() const
187{
188 Q_Q(const QQuickTabBar);
189 const int count = contentModel->count();
190 qreal maxHeight = 0;
191 for (int i = 0; i < count; ++i) {
192 QQuickItem *item = q->itemAt(index: i);
193 if (item)
194 maxHeight = qMax(a: maxHeight, b: item->implicitHeight());
195 }
196 return maxHeight;
197}
198
199void QQuickTabBarPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff)
200{
201 QQuickContainerPrivate::itemGeometryChanged(item, change, diff);
202 if (!updatingLayout) {
203 if (change.sizeChange())
204 updateImplicitContentSize();
205 updateLayout();
206 }
207}
208
209void QQuickTabBarPrivate::itemImplicitWidthChanged(QQuickItem *item)
210{
211 QQuickContainerPrivate::itemImplicitWidthChanged(item);
212 if (item != contentItem)
213 updateImplicitContentWidth();
214}
215
216void QQuickTabBarPrivate::itemImplicitHeightChanged(QQuickItem *item)
217{
218 QQuickContainerPrivate::itemImplicitHeightChanged(item);
219 if (item != contentItem)
220 updateImplicitContentHeight();
221}
222
223QQuickTabBar::QQuickTabBar(QQuickItem *parent)
224 : QQuickContainer(*(new QQuickTabBarPrivate), parent)
225{
226 Q_D(QQuickTabBar);
227 d->changeTypes |= QQuickItemPrivate::Geometry | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight;
228 setFlag(flag: ItemIsFocusScope);
229 QObjectPrivate::connect(sender: this, signal: &QQuickTabBar::currentIndexChanged, receiverPrivate: d, slot: &QQuickTabBarPrivate::updateCurrentItem);
230}
231
232/*!
233 \qmlproperty enumeration QtQuick.Controls::TabBar::position
234
235 This property holds the position of the tab bar.
236
237 \note If the tab bar is assigned as a header or footer of \l ApplicationWindow
238 or \l Page, the appropriate position is set automatically.
239
240 Possible values:
241 \value TabBar.Header The tab bar is at the top, as a window or page header.
242 \value TabBar.Footer The tab bar is at the bottom, as a window or page footer.
243
244 The default value is style-specific.
245
246 \sa ApplicationWindow::header, ApplicationWindow::footer, Page::header, Page::footer
247*/
248QQuickTabBar::Position QQuickTabBar::position() const
249{
250 Q_D(const QQuickTabBar);
251 return d->position;
252}
253
254void QQuickTabBar::setPosition(Position position)
255{
256 Q_D(QQuickTabBar);
257 if (d->position == position)
258 return;
259
260 d->position = position;
261 emit positionChanged();
262}
263
264/*!
265 \since QtQuick.Controls 2.2 (Qt 5.9)
266 \qmlproperty real QtQuick.Controls::TabBar::contentWidth
267
268 This property holds the content width. It is used for calculating the total
269 implicit width of the tab bar.
270
271 \note This property is available in TabBar since QtQuick.Controls 2.2 (Qt 5.9),
272 but it was promoted to the Container base type in QtQuick.Controls 2.5 (Qt 5.12).
273
274 \sa Container::contentWidth
275*/
276
277/*!
278 \since QtQuick.Controls 2.2 (Qt 5.9)
279 \qmlproperty real QtQuick.Controls::TabBar::contentHeight
280
281 This property holds the content height. It is used for calculating the total
282 implicit height of the tab bar.
283
284 \note This property is available in TabBar since QtQuick.Controls 2.2 (Qt 5.9),
285 but it was promoted to the Container base type in QtQuick.Controls 2.5 (Qt 5.12).
286
287 \sa Container::contentHeight
288*/
289
290QQuickTabBarAttached *QQuickTabBar::qmlAttachedProperties(QObject *object)
291{
292 return new QQuickTabBarAttached(object);
293}
294
295void QQuickTabBar::updatePolish()
296{
297 Q_D(QQuickTabBar);
298 QQuickContainer::updatePolish();
299 d->updateLayout();
300}
301
302void QQuickTabBar::componentComplete()
303{
304 Q_D(QQuickTabBar);
305 QQuickContainer::componentComplete();
306 d->updateCurrentItem();
307 d->updateLayout();
308}
309
310void QQuickTabBar::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
311{
312 Q_D(QQuickTabBar);
313 QQuickContainer::geometryChange(newGeometry, oldGeometry);
314 d->updateLayout();
315}
316
317bool QQuickTabBar::isContent(QQuickItem *item) const
318{
319 return qobject_cast<QQuickTabButton *>(object: item);
320}
321
322void QQuickTabBar::itemAdded(int index, QQuickItem *item)
323{
324 Q_D(QQuickTabBar);
325 Q_UNUSED(index);
326 QQuickItemPrivate::get(item)->setCulled(true); // QTBUG-55129
327 if (QQuickTabButton *button = qobject_cast<QQuickTabButton *>(object: item))
328 QObjectPrivate::connect(sender: button, signal: &QQuickTabButton::checkedChanged, receiverPrivate: d, slot: &QQuickTabBarPrivate::updateCurrentIndex);
329 QQuickTabBarAttached *attached = qobject_cast<QQuickTabBarAttached *>(object: qmlAttachedPropertiesObject<QQuickTabBar>(obj: item));
330 if (attached)
331 QQuickTabBarAttachedPrivate::get(attached)->update(tabBar: this, index);
332 d->updateImplicitContentSize();
333 if (isComponentComplete())
334 polish();
335}
336
337void QQuickTabBar::itemMoved(int index, QQuickItem *item)
338{
339 QQuickTabBarAttached *attached = qobject_cast<QQuickTabBarAttached *>(object: qmlAttachedPropertiesObject<QQuickTabBar>(obj: item));
340 if (attached)
341 QQuickTabBarAttachedPrivate::get(attached)->update(tabBar: this, index);
342}
343
344void QQuickTabBar::itemRemoved(int index, QQuickItem *item)
345{
346 Q_D(QQuickTabBar);
347 Q_UNUSED(index);
348 if (QQuickTabButton *button = qobject_cast<QQuickTabButton *>(object: item))
349 QObjectPrivate::disconnect(sender: button, signal: &QQuickTabButton::checkedChanged, receiverPrivate: d, slot: &QQuickTabBarPrivate::updateCurrentIndex);
350 QQuickTabBarAttached *attached = qobject_cast<QQuickTabBarAttached *>(object: qmlAttachedPropertiesObject<QQuickTabBar>(obj: item));
351 if (attached)
352 QQuickTabBarAttachedPrivate::get(attached)->update(tabBar: nullptr, index: -1);
353 d->updateImplicitContentSize();
354 if (isComponentComplete())
355 polish();
356}
357
358#if QT_CONFIG(wheelevent)
359void QQuickTabBar::wheelEvent(QWheelEvent *event)
360{
361 Q_D(QQuickTabBar);
362 QQuickContainer::wheelEvent(event);
363 if (d->wheelEnabled) {
364 d->accumulatedAngleDelta += event->angleDelta();
365 int xSteps = d->accumulatedAngleDelta.x() / QWheelEvent::DefaultDeltasPerStep;
366 int ySteps = d->accumulatedAngleDelta.y() / QWheelEvent::DefaultDeltasPerStep;
367 if (xSteps > 0 || ySteps > 0) {
368 decrementCurrentIndex();
369 d->accumulatedAngleDelta = QPoint();
370 } else if (xSteps < 0 || ySteps < 0) {
371 incrementCurrentIndex();
372 d->accumulatedAngleDelta = QPoint();
373 }
374 }
375}
376#endif
377
378QFont QQuickTabBar::defaultFont() const
379{
380 return QQuickTheme::font(scope: QQuickTheme::TabBar);
381}
382
383#if QT_CONFIG(accessibility)
384QAccessible::Role QQuickTabBar::accessibleRole() const
385{
386 return QAccessible::PageTabList;
387}
388#endif
389
390/*!
391 \qmlattachedproperty int QtQuick.Controls::TabBar::index
392 \since QtQuick.Controls 2.3 (Qt 5.10)
393 \readonly
394
395 This attached property holds the index of each tab button in the TabBar.
396
397 It is attached to each tab button of the TabBar.
398*/
399
400/*!
401 \qmlattachedproperty TabBar QtQuick.Controls::TabBar::tabBar
402 \since QtQuick.Controls 2.3 (Qt 5.10)
403 \readonly
404
405 This attached property holds the tab bar that manages this tab button.
406
407 It is attached to each tab button of the TabBar.
408*/
409
410/*!
411 \qmlattachedproperty enumeration QtQuick.Controls::TabBar::position
412 \since QtQuick.Controls 2.3 (Qt 5.10)
413 \readonly
414
415 This attached property holds the position of the tab bar.
416
417 It is attached to each tab button of the TabBar.
418
419 Possible values:
420 \value TabBar.Header The tab bar is at the top, as a window or page header.
421 \value TabBar.Footer The tab bar is at the bottom, as a window or page footer.
422*/
423
424void QQuickTabBarAttachedPrivate::update(QQuickTabBar *newTabBar, int newIndex)
425{
426 Q_Q(QQuickTabBarAttached);
427 const int oldIndex = index;
428 const QQuickTabBar *oldTabBar = tabBar;
429 const QQuickTabBar::Position oldPos = q->position();
430
431 index = newIndex;
432 tabBar = newTabBar;
433
434 if (oldTabBar != newTabBar) {
435 if (oldTabBar)
436 QObject::disconnect(sender: oldTabBar, signal: &QQuickTabBar::positionChanged, receiver: q, slot: &QQuickTabBarAttached::positionChanged);
437 if (newTabBar)
438 QObject::connect(sender: newTabBar, signal: &QQuickTabBar::positionChanged, context: q, slot: &QQuickTabBarAttached::positionChanged);
439 emit q->tabBarChanged();
440 }
441
442 if (oldIndex != newIndex)
443 emit q->indexChanged();
444 if (oldPos != q->position())
445 emit q->positionChanged();
446}
447
448QQuickTabBarAttached::QQuickTabBarAttached(QObject *parent)
449 : QObject(*(new QQuickTabBarAttachedPrivate), parent)
450{
451}
452
453int QQuickTabBarAttached::index() const
454{
455 Q_D(const QQuickTabBarAttached);
456 return d->index;
457}
458
459QQuickTabBar *QQuickTabBarAttached::tabBar() const
460{
461 Q_D(const QQuickTabBarAttached);
462 return d->tabBar;
463}
464
465QQuickTabBar::Position QQuickTabBarAttached::position() const
466{
467 Q_D(const QQuickTabBarAttached);
468 if (!d->tabBar)
469 return QQuickTabBar::Header;
470 return d->tabBar->position();
471}
472
473QT_END_NAMESPACE
474
475#include "moc_qquicktabbar_p.cpp"
476

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