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 | |
8 | QT_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 | |
67 | class QQuickTabBarPrivate : public QQuickContainerPrivate |
68 | { |
69 | public: |
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 | |
92 | class QQuickTabBarAttachedPrivate : public QObjectPrivate |
93 | { |
94 | Q_DECLARE_PUBLIC(QQuickTabBarAttached) |
95 | |
96 | public: |
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 | |
108 | void QQuickTabBarPrivate::updateCurrentItem() |
109 | { |
110 | QQuickTabButton *button = qobject_cast<QQuickTabButton *>(object: contentModel->get(index: currentIndex)); |
111 | if (button) |
112 | button->setChecked(true); |
113 | } |
114 | |
115 | void 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 | |
123 | void 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 | |
168 | qreal 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 | |
186 | qreal 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 | |
199 | void 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 | |
209 | void QQuickTabBarPrivate::itemImplicitWidthChanged(QQuickItem *item) |
210 | { |
211 | QQuickContainerPrivate::itemImplicitWidthChanged(item); |
212 | if (item != contentItem) |
213 | updateImplicitContentWidth(); |
214 | } |
215 | |
216 | void QQuickTabBarPrivate::itemImplicitHeightChanged(QQuickItem *item) |
217 | { |
218 | QQuickContainerPrivate::itemImplicitHeightChanged(item); |
219 | if (item != contentItem) |
220 | updateImplicitContentHeight(); |
221 | } |
222 | |
223 | QQuickTabBar::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 | */ |
248 | QQuickTabBar::Position QQuickTabBar::position() const |
249 | { |
250 | Q_D(const QQuickTabBar); |
251 | return d->position; |
252 | } |
253 | |
254 | void 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 | |
290 | QQuickTabBarAttached *QQuickTabBar::qmlAttachedProperties(QObject *object) |
291 | { |
292 | return new QQuickTabBarAttached(object); |
293 | } |
294 | |
295 | void QQuickTabBar::updatePolish() |
296 | { |
297 | Q_D(QQuickTabBar); |
298 | QQuickContainer::updatePolish(); |
299 | d->updateLayout(); |
300 | } |
301 | |
302 | void QQuickTabBar::componentComplete() |
303 | { |
304 | Q_D(QQuickTabBar); |
305 | QQuickContainer::componentComplete(); |
306 | d->updateCurrentItem(); |
307 | d->updateLayout(); |
308 | } |
309 | |
310 | void QQuickTabBar::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) |
311 | { |
312 | Q_D(QQuickTabBar); |
313 | QQuickContainer::geometryChange(newGeometry, oldGeometry); |
314 | d->updateLayout(); |
315 | } |
316 | |
317 | bool QQuickTabBar::isContent(QQuickItem *item) const |
318 | { |
319 | return qobject_cast<QQuickTabButton *>(object: item); |
320 | } |
321 | |
322 | void 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 | |
337 | void 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 | |
344 | void 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) |
359 | void 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 | |
378 | QFont QQuickTabBar::defaultFont() const |
379 | { |
380 | return QQuickTheme::font(scope: QQuickTheme::TabBar); |
381 | } |
382 | |
383 | #if QT_CONFIG(accessibility) |
384 | QAccessible::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 | |
424 | void 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 | |
448 | QQuickTabBarAttached::QQuickTabBarAttached(QObject *parent) |
449 | : QObject(*(new QQuickTabBarAttachedPrivate), parent) |
450 | { |
451 | } |
452 | |
453 | int QQuickTabBarAttached::index() const |
454 | { |
455 | Q_D(const QQuickTabBarAttached); |
456 | return d->index; |
457 | } |
458 | |
459 | QQuickTabBar *QQuickTabBarAttached::tabBar() const |
460 | { |
461 | Q_D(const QQuickTabBarAttached); |
462 | return d->tabBar; |
463 | } |
464 | |
465 | QQuickTabBar::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 | |
473 | QT_END_NAMESPACE |
474 | |
475 | #include "moc_qquicktabbar_p.cpp" |
476 | |