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: |
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 | |
134 | void QQuickTabBarPrivate::updateCurrentItem() |
135 | { |
136 | QQuickTabButton *button = qobject_cast<QQuickTabButton *>(object: contentModel->get(index: currentIndex)); |
137 | if (button) |
138 | button->setChecked(true); |
139 | } |
140 | |
141 | void 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 | |
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(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 | |
194 | qreal 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 | |
212 | 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(index: i); |
219 | if (item) |
220 | maxHeight = qMax(a: maxHeight, b: item->implicitHeight()); |
221 | } |
222 | return maxHeight; |
223 | } |
224 | |
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 | |
235 | void QQuickTabBarPrivate::itemImplicitWidthChanged(QQuickItem *item) |
236 | { |
237 | QQuickContainerPrivate::itemImplicitWidthChanged(item); |
238 | if (item != contentItem) |
239 | updateImplicitContentWidth(); |
240 | } |
241 | |
242 | void QQuickTabBarPrivate::itemImplicitHeightChanged(QQuickItem *item) |
243 | { |
244 | QQuickContainerPrivate::itemImplicitHeightChanged(item); |
245 | if (item != contentItem) |
246 | updateImplicitContentHeight(); |
247 | } |
248 | |
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(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 | */ |
274 | QQuickTabBar::Position QQuickTabBar::position() const |
275 | { |
276 | Q_D(const QQuickTabBar); |
277 | return d->position; |
278 | } |
279 | |
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 | |
316 | QQuickTabBarAttached *QQuickTabBar::qmlAttachedProperties(QObject *object) |
317 | { |
318 | return new QQuickTabBarAttached(object); |
319 | } |
320 | |
321 | void QQuickTabBar::updatePolish() |
322 | { |
323 | Q_D(QQuickTabBar); |
324 | QQuickContainer::updatePolish(); |
325 | d->updateLayout(); |
326 | } |
327 | |
328 | void QQuickTabBar::componentComplete() |
329 | { |
330 | Q_D(QQuickTabBar); |
331 | QQuickContainer::componentComplete(); |
332 | d->updateCurrentItem(); |
333 | d->updateLayout(); |
334 | } |
335 | |
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 | |
343 | bool QQuickTabBar::isContent(QQuickItem *item) const |
344 | { |
345 | return qobject_cast<QQuickTabButton *>(object: item); |
346 | } |
347 | |
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 *>(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 | |
363 | void 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 | |
370 | void 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 | |
384 | QFont QQuickTabBar::defaultFont() const |
385 | { |
386 | return QQuickTheme::font(scope: QQuickTheme::TabBar); |
387 | } |
388 | |
389 | QPalette QQuickTabBar::defaultPalette() const |
390 | { |
391 | return QQuickTheme::palette(scope: QQuickTheme::TabBar); |
392 | } |
393 | |
394 | #if QT_CONFIG(accessibility) |
395 | 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 | |
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(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 | |
459 | QQuickTabBarAttached::QQuickTabBarAttached(QObject *parent) |
460 | : QObject(*(new QQuickTabBarAttachedPrivate), parent) |
461 | { |
462 | } |
463 | |
464 | int QQuickTabBarAttached::index() const |
465 | { |
466 | Q_D(const QQuickTabBarAttached); |
467 | return d->index; |
468 | } |
469 | |
470 | QQuickTabBar *QQuickTabBarAttached::tabBar() const |
471 | { |
472 | Q_D(const QQuickTabBarAttached); |
473 | return d->tabBar; |
474 | } |
475 | |
476 | 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 | |