| 1 | // Copyright (C) 2020 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 "qquickcontainer_p.h" |
| 5 | #include "qquickcontainer_p_p.h" |
| 6 | |
| 7 | #include <QtQuick/private/qquickflickable_p.h> |
| 8 | #include <QtQuick/private/qquickitemview_p.h> |
| 9 | |
| 10 | QT_BEGIN_NAMESPACE |
| 11 | |
| 12 | /*! |
| 13 | \qmltype Container |
| 14 | \inherits Control |
| 15 | //! \nativetype QQuickContainer |
| 16 | \inqmlmodule QtQuick.Controls |
| 17 | \since 5.7 |
| 18 | \ingroup qtquickcontrols-containers |
| 19 | \brief Abstract base type providing functionality common to containers. |
| 20 | |
| 21 | Container is the base type of container-like user interface controls that |
| 22 | allow dynamic insertion and removal of items. |
| 23 | |
| 24 | \section2 Using Containers |
| 25 | |
| 26 | Typically, items are statically declared as children of Container, but it |
| 27 | is also possible to \l {addItem}{add}, \l {insertItem}{insert}, |
| 28 | \l {moveItem}{move} and \l {removeItem}{remove} items dynamically. The |
| 29 | items in a container can be accessed using \l itemAt() or |
| 30 | \l contentChildren. |
| 31 | |
| 32 | Most containers have the concept of a "current" item. The current item is |
| 33 | specified via the \l currentIndex property, and can be accessed using the |
| 34 | read-only \l currentItem property. |
| 35 | |
| 36 | The following example illustrates dynamic insertion of items to a \l TabBar, |
| 37 | which is one of the concrete implementations of Container. |
| 38 | |
| 39 | \code |
| 40 | Row { |
| 41 | TabBar { |
| 42 | id: tabBar |
| 43 | |
| 44 | currentIndex: 0 |
| 45 | width: parent.width - addButton.width |
| 46 | |
| 47 | TabButton { text: "TabButton" } |
| 48 | } |
| 49 | |
| 50 | Component { |
| 51 | id: tabButton |
| 52 | TabButton { text: "TabButton" } |
| 53 | } |
| 54 | |
| 55 | Button { |
| 56 | id: addButton |
| 57 | text: "+" |
| 58 | flat: true |
| 59 | onClicked: { |
| 60 | tabBar.addItem(tabButton.createObject(tabBar)) |
| 61 | console.log("added:", tabBar.itemAt(tabBar.count - 1)) |
| 62 | } |
| 63 | } |
| 64 | } |
| 65 | \endcode |
| 66 | |
| 67 | \section2 Managing the Current Index |
| 68 | |
| 69 | When using multiple containers, such as \l TabBar and \l SwipeView, together, |
| 70 | their \l currentIndex properties can be bound to each other to keep them in |
| 71 | sync. When the user interacts with either container, its current index changes |
| 72 | automatically propagate to the other container. |
| 73 | |
| 74 | Notice, however, that assigning a \c currentIndex value in JavaScript removes |
| 75 | the respective binding. In order to retain the bindings, use the following |
| 76 | methods to alter the current index: |
| 77 | |
| 78 | \list |
| 79 | \li \l incrementCurrentIndex() |
| 80 | \li \l decrementCurrentIndex() |
| 81 | \li \l setCurrentIndex() |
| 82 | \endlist |
| 83 | |
| 84 | \code |
| 85 | TabBar { |
| 86 | id: tabBar |
| 87 | currentIndex: swipeView.currentIndex |
| 88 | } |
| 89 | |
| 90 | SwipeView { |
| 91 | id: swipeView |
| 92 | currentIndex: tabBar.currentIndex |
| 93 | } |
| 94 | |
| 95 | Button { |
| 96 | text: qsTr("Home") |
| 97 | onClicked: swipeView.setCurrentIndex(0) |
| 98 | enabled: swipeView.currentIndex != 0 |
| 99 | } |
| 100 | |
| 101 | Button { |
| 102 | text: qsTr("Previous") |
| 103 | onClicked: swipeView.decrementCurrentIndex() |
| 104 | enabled: swipeView.currentIndex > 0 |
| 105 | } |
| 106 | |
| 107 | Button { |
| 108 | text: qsTr("Next") |
| 109 | onClicked: swipeView.incrementCurrentIndex() |
| 110 | enabled: swipeView.currentIndex < swipeView.count - 1 |
| 111 | } |
| 112 | \endcode |
| 113 | |
| 114 | |
| 115 | \section2 Implementing Containers |
| 116 | |
| 117 | Container does not provide any default visualization. It is used to implement |
| 118 | such containers as \l SwipeView and \l TabBar. When implementing a custom |
| 119 | container, the most important part of the API is \l contentModel, which provides |
| 120 | the contained items in a way that it can be used as an object model for item |
| 121 | views and repeaters. |
| 122 | |
| 123 | \code |
| 124 | Container { |
| 125 | id: container |
| 126 | |
| 127 | contentItem: ListView { |
| 128 | model: container.contentModel |
| 129 | snapMode: ListView.SnapOneItem |
| 130 | orientation: ListView.Horizontal |
| 131 | } |
| 132 | |
| 133 | Text { |
| 134 | text: "Page 1" |
| 135 | width: container.width |
| 136 | height: container.height |
| 137 | } |
| 138 | |
| 139 | Text { |
| 140 | text: "Page 2" |
| 141 | width: container.width |
| 142 | height: container.height |
| 143 | } |
| 144 | } |
| 145 | \endcode |
| 146 | |
| 147 | Notice how the sizes of the page items are set by hand. This is because the |
| 148 | example uses a plain Container, which does not make any assumptions on the |
| 149 | visual layout. It is typically not necessary to specify sizes for items in |
| 150 | concrete Container implementations, such as \l SwipeView and \l TabBar. |
| 151 | |
| 152 | \sa {Container Controls} |
| 153 | */ |
| 154 | |
| 155 | static QQuickItem *effectiveContentItem(QQuickItem *item) |
| 156 | { |
| 157 | QQuickFlickable *flickable = qobject_cast<QQuickFlickable *>(object: item); |
| 158 | if (flickable) |
| 159 | return flickable->contentItem(); |
| 160 | return item; |
| 161 | } |
| 162 | |
| 163 | void QQuickContainerPrivate::init() |
| 164 | { |
| 165 | Q_Q(QQuickContainer); |
| 166 | contentModel = new QQmlObjectModel(q); |
| 167 | QObject::connect(sender: contentModel, signal: &QQmlObjectModel::countChanged, context: q, slot: &QQuickContainer::countChanged); |
| 168 | QObject::connect(sender: contentModel, signal: &QQmlObjectModel::childrenChanged, context: q, slot: &QQuickContainer::contentChildrenChanged); |
| 169 | connect(sender: q, signal: &QQuickControl::implicitContentWidthChanged, receiverPrivate: this, slot: &QQuickContainerPrivate::updateContentWidth); |
| 170 | connect(sender: q, signal: &QQuickControl::implicitContentHeightChanged, receiverPrivate: this, slot: &QQuickContainerPrivate::updateContentHeight); |
| 171 | setSizePolicy(horizontalPolicy: QLayoutPolicy::Preferred, verticalPolicy: QLayoutPolicy::Preferred); |
| 172 | } |
| 173 | |
| 174 | void QQuickContainerPrivate::cleanup() |
| 175 | { |
| 176 | Q_Q(QQuickContainer); |
| 177 | // ensure correct destruction order (QTBUG-46798) |
| 178 | const int count = contentModel->count(); |
| 179 | for (int i = 0; i < count; ++i) { |
| 180 | QQuickItem *item = itemAt(index: i); |
| 181 | if (item) |
| 182 | QQuickItemPrivate::get(item)->removeItemChangeListener(this, types: changeTypes); |
| 183 | } |
| 184 | |
| 185 | if (contentItem) { |
| 186 | QQuickItem *focusItem = QQuickItemPrivate::get(item: contentItem)->subFocusItem; |
| 187 | if (focusItem && window) |
| 188 | QQuickWindowPrivate::get(c: window)->clearFocusInScope(scope: contentItem, item: focusItem, reason: Qt::OtherFocusReason); |
| 189 | |
| 190 | q->contentItemChange(newItem: nullptr, oldItem: contentItem); |
| 191 | QQuickControlPrivate::hideOldItem(item: contentItem); |
| 192 | } |
| 193 | |
| 194 | QObject::disconnect(sender: contentModel, signal: &QQmlObjectModel::countChanged, receiver: q, slot: &QQuickContainer::countChanged); |
| 195 | QObject::disconnect(sender: contentModel, signal: &QQmlObjectModel::childrenChanged, receiver: q, slot: &QQuickContainer::contentChildrenChanged); |
| 196 | delete contentModel; |
| 197 | contentModel = nullptr; |
| 198 | } |
| 199 | |
| 200 | QQuickItem *QQuickContainerPrivate::itemAt(int index) const |
| 201 | { |
| 202 | return qobject_cast<QQuickItem *>(o: contentModel->get(index)); |
| 203 | } |
| 204 | |
| 205 | void QQuickContainerPrivate::insertItem(int index, QQuickItem *item) |
| 206 | { |
| 207 | Q_Q(QQuickContainer); |
| 208 | if (!q->isContent(item)) |
| 209 | return; |
| 210 | contentData.append(t: item); |
| 211 | |
| 212 | updatingCurrent = true; |
| 213 | |
| 214 | item->setParentItem(effectiveContentItem(item: q->contentItem())); |
| 215 | maybeCullItem(item); |
| 216 | QQuickItemPrivate::get(item)->addItemChangeListener(listener: this, types: changeTypes); |
| 217 | contentModel->insert(index, object: item); |
| 218 | |
| 219 | q->itemAdded(index, item); |
| 220 | |
| 221 | int count = contentModel->count(); |
| 222 | for (int i = index + 1; i < count; ++i) |
| 223 | q->itemMoved(index: i, item: itemAt(index: i)); |
| 224 | |
| 225 | if (count == 1 && currentIndex == -1) |
| 226 | q->setCurrentIndex(index); |
| 227 | |
| 228 | updatingCurrent = false; |
| 229 | } |
| 230 | |
| 231 | void QQuickContainerPrivate::moveItem(int from, int to, QQuickItem *item) |
| 232 | { |
| 233 | Q_Q(QQuickContainer); |
| 234 | int oldCurrent = currentIndex; |
| 235 | contentModel->move(from, to); |
| 236 | |
| 237 | updatingCurrent = true; |
| 238 | |
| 239 | q->itemMoved(index: to, item); |
| 240 | |
| 241 | if (from < to) { |
| 242 | for (int i = from; i < to; ++i) |
| 243 | q->itemMoved(index: i, item: itemAt(index: i)); |
| 244 | } else { |
| 245 | for (int i = from; i > to; --i) |
| 246 | q->itemMoved(index: i, item: itemAt(index: i)); |
| 247 | } |
| 248 | |
| 249 | if (from == oldCurrent) |
| 250 | q->setCurrentIndex(to); |
| 251 | else if (from < oldCurrent && to >= oldCurrent) |
| 252 | q->setCurrentIndex(oldCurrent - 1); |
| 253 | else if (from > oldCurrent && to <= oldCurrent) |
| 254 | q->setCurrentIndex(oldCurrent + 1); |
| 255 | |
| 256 | updatingCurrent = false; |
| 257 | } |
| 258 | |
| 259 | void QQuickContainerPrivate::removeItem(int index, QQuickItem *item) |
| 260 | { |
| 261 | Q_Q(QQuickContainer); |
| 262 | const bool item_inDestructor = QQuickItemPrivate::get(item)->inDestructor; |
| 263 | if (!item_inDestructor && !q->isContent(item)) |
| 264 | return; |
| 265 | contentData.removeOne(t: item); |
| 266 | |
| 267 | updatingCurrent = true; |
| 268 | |
| 269 | int count = contentModel->count(); |
| 270 | bool currentChanged = false; |
| 271 | if (index == currentIndex && (index != 0 || count == 1)) { |
| 272 | q->setCurrentIndex(currentIndex - 1); |
| 273 | } else if (index < currentIndex) { |
| 274 | --currentIndex; |
| 275 | currentChanged = true; |
| 276 | } |
| 277 | |
| 278 | if (!item_inDestructor) { |
| 279 | // already handled by ~QQuickItem |
| 280 | QQuickItemPrivate::get(item)->removeItemChangeListener(this, types: changeTypes); |
| 281 | item->setParentItem(nullptr); |
| 282 | } |
| 283 | contentModel->remove(index); |
| 284 | --count; |
| 285 | |
| 286 | q->itemRemoved(index, item); |
| 287 | |
| 288 | for (int i = index; i < count; ++i) |
| 289 | q->itemMoved(index: i, item: itemAt(index: i)); |
| 290 | |
| 291 | if (currentChanged) |
| 292 | emit q->currentIndexChanged(); |
| 293 | |
| 294 | updatingCurrent = false; |
| 295 | } |
| 296 | |
| 297 | void QQuickContainerPrivate::reorderItems() |
| 298 | { |
| 299 | Q_Q(QQuickContainer); |
| 300 | if (!contentItem) |
| 301 | return; |
| 302 | |
| 303 | QList<QQuickItem *> siblings = effectiveContentItem(item: contentItem)->childItems(); |
| 304 | |
| 305 | int to = 0; |
| 306 | for (int i = 0; i < siblings.size(); ++i) { |
| 307 | QQuickItem* sibling = siblings.at(i); |
| 308 | if (QQuickItemPrivate::get(item: sibling)->isTransparentForPositioner()) |
| 309 | continue; |
| 310 | int index = contentModel->indexOf(object: sibling, objectContext: nullptr); |
| 311 | q->moveItem(from: index, to: to++); |
| 312 | } |
| 313 | } |
| 314 | |
| 315 | void QQuickContainerPrivate::maybeCullItem(QQuickItem *item) |
| 316 | { |
| 317 | if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) |
| 318 | return; |
| 319 | |
| 320 | // Items like Repeater don't control the visibility of the items they create, |
| 321 | // so we can't expected them to uncull items added dynamically. As mentioned |
| 322 | // below, Repeater _does_ uncull items added to it, but unlike e.g. ListView, |
| 323 | // it shouldn't care if its size becomes zero and so it shouldn't manage |
| 324 | // the culled state of items in the same way. |
| 325 | if (!qobject_cast<QQuickItemView *>(object: contentItem)) |
| 326 | return; |
| 327 | |
| 328 | // Only cull items if the contentItem has a zero size; otherwise let the |
| 329 | // contentItem manage it. |
| 330 | const bool hasZeroSize = qFuzzyIsNull(d: width) && qFuzzyIsNull(d: height); |
| 331 | if (!hasZeroSize) |
| 332 | return; |
| 333 | |
| 334 | QQuickItemPrivate::get(item)->setCulled(true); |
| 335 | } |
| 336 | |
| 337 | void QQuickContainerPrivate::maybeCullItems() |
| 338 | { |
| 339 | if (!contentItem) |
| 340 | return; |
| 341 | |
| 342 | const QList<QQuickItem *> childItems = effectiveContentItem(item: contentItem)->childItems(); |
| 343 | for (auto &childItem : childItems) |
| 344 | maybeCullItem(item: childItem); |
| 345 | } |
| 346 | |
| 347 | void QQuickContainerPrivate::_q_currentIndexChanged() |
| 348 | { |
| 349 | Q_Q(QQuickContainer); |
| 350 | if (!updatingCurrent) |
| 351 | q->setCurrentIndex(contentItem ? contentItem->property(name: "currentIndex" ).toInt() : -1); |
| 352 | } |
| 353 | |
| 354 | void QQuickContainerPrivate::itemChildAdded(QQuickItem *, QQuickItem *child) |
| 355 | { |
| 356 | // add dynamically reparented items (eg. by a Repeater) |
| 357 | if (!QQuickItemPrivate::get(item: child)->isTransparentForPositioner() && !contentData.contains(t: child)) |
| 358 | insertItem(index: contentModel->count(), item: child); |
| 359 | } |
| 360 | |
| 361 | void QQuickContainerPrivate::itemParentChanged(QQuickItem *item, QQuickItem *parent) |
| 362 | { |
| 363 | // remove dynamically unparented items (eg. by a Repeater) |
| 364 | if (!parent) |
| 365 | removeItem(index: contentModel->indexOf(object: item, objectContext: nullptr), item); |
| 366 | } |
| 367 | |
| 368 | void QQuickContainerPrivate::itemSiblingOrderChanged(QQuickItem *) |
| 369 | { |
| 370 | if (!componentComplete) |
| 371 | return; |
| 372 | |
| 373 | // reorder the restacked items (eg. by a Repeater) |
| 374 | reorderItems(); |
| 375 | } |
| 376 | |
| 377 | void QQuickContainerPrivate::itemDestroyed(QQuickItem *item) |
| 378 | { |
| 379 | int index = contentModel->indexOf(object: item, objectContext: nullptr); |
| 380 | if (index != -1) |
| 381 | removeItem(index, item); |
| 382 | else |
| 383 | QQuickControlPrivate::itemDestroyed(item); |
| 384 | } |
| 385 | |
| 386 | void QQuickContainerPrivate::contentData_append(QQmlListProperty<QObject> *prop, QObject *obj) |
| 387 | { |
| 388 | QQuickContainer *q = static_cast<QQuickContainer *>(prop->object); |
| 389 | QQuickContainerPrivate *p = QQuickContainerPrivate::get(container: q); |
| 390 | QQuickItem *item = qobject_cast<QQuickItem *>(o: obj); |
| 391 | if (item) { |
| 392 | if (QQuickItemPrivate::get(item)->isTransparentForPositioner()) |
| 393 | item->setParentItem(effectiveContentItem(item: q->contentItem())); |
| 394 | else if (p->contentModel->indexOf(object: item, objectContext: nullptr) == -1) |
| 395 | q->addItem(item); |
| 396 | } else { |
| 397 | p->contentData.append(t: obj); |
| 398 | } |
| 399 | } |
| 400 | |
| 401 | qsizetype QQuickContainerPrivate::contentData_count(QQmlListProperty<QObject> *prop) |
| 402 | { |
| 403 | QQuickContainer *q = static_cast<QQuickContainer *>(prop->object); |
| 404 | return QQuickContainerPrivate::get(container: q)->contentData.size(); |
| 405 | } |
| 406 | |
| 407 | QObject *QQuickContainerPrivate::contentData_at(QQmlListProperty<QObject> *prop, qsizetype index) |
| 408 | { |
| 409 | QQuickContainer *q = static_cast<QQuickContainer *>(prop->object); |
| 410 | return QQuickContainerPrivate::get(container: q)->contentData.value(i: index); |
| 411 | } |
| 412 | |
| 413 | void QQuickContainerPrivate::contentData_clear(QQmlListProperty<QObject> *prop) |
| 414 | { |
| 415 | QQuickContainer *q = static_cast<QQuickContainer *>(prop->object); |
| 416 | return QQuickContainerPrivate::get(container: q)->contentData.clear(); |
| 417 | } |
| 418 | |
| 419 | void QQuickContainerPrivate::contentChildren_append(QQmlListProperty<QQuickItem> *prop, QQuickItem *item) |
| 420 | { |
| 421 | QQuickContainer *q = static_cast<QQuickContainer *>(prop->object); |
| 422 | q->addItem(item); |
| 423 | } |
| 424 | |
| 425 | qsizetype QQuickContainerPrivate::contentChildren_count(QQmlListProperty<QQuickItem> *prop) |
| 426 | { |
| 427 | QQuickContainer *q = static_cast<QQuickContainer *>(prop->object); |
| 428 | return QQuickContainerPrivate::get(container: q)->contentModel->count(); |
| 429 | } |
| 430 | |
| 431 | QQuickItem *QQuickContainerPrivate::contentChildren_at(QQmlListProperty<QQuickItem> *prop, qsizetype index) |
| 432 | { |
| 433 | QQuickContainer *q = static_cast<QQuickContainer *>(prop->object); |
| 434 | return q->itemAt(index); |
| 435 | } |
| 436 | |
| 437 | void QQuickContainerPrivate::contentChildren_clear(QQmlListProperty<QQuickItem> *prop) |
| 438 | { |
| 439 | QQuickContainer *q = static_cast<QQuickContainer *>(prop->object); |
| 440 | return QQuickContainerPrivate::get(container: q)->contentModel->clear(); |
| 441 | } |
| 442 | |
| 443 | void QQuickContainerPrivate::updateContentWidth() |
| 444 | { |
| 445 | Q_Q(QQuickContainer); |
| 446 | if (hasContentWidth || qFuzzyCompare(p1: contentWidth, p2: implicitContentWidth) || !contentModel) |
| 447 | return; |
| 448 | |
| 449 | contentWidth = implicitContentWidth; |
| 450 | emit q->contentWidthChanged(); |
| 451 | } |
| 452 | |
| 453 | void QQuickContainerPrivate::updateContentHeight() |
| 454 | { |
| 455 | Q_Q(QQuickContainer); |
| 456 | if (hasContentHeight || qFuzzyCompare(p1: contentHeight, p2: implicitContentHeight) || !contentModel) |
| 457 | return; |
| 458 | |
| 459 | contentHeight = implicitContentHeight; |
| 460 | emit q->contentHeightChanged(); |
| 461 | } |
| 462 | |
| 463 | QQuickContainer::QQuickContainer(QQuickItem *parent) |
| 464 | : QQuickControl(*(new QQuickContainerPrivate), parent) |
| 465 | { |
| 466 | Q_D(QQuickContainer); |
| 467 | d->init(); |
| 468 | } |
| 469 | |
| 470 | QQuickContainer::QQuickContainer(QQuickContainerPrivate &dd, QQuickItem *parent) |
| 471 | : QQuickControl(dd, parent) |
| 472 | { |
| 473 | Q_D(QQuickContainer); |
| 474 | d->init(); |
| 475 | } |
| 476 | |
| 477 | QQuickContainer::~QQuickContainer() |
| 478 | { |
| 479 | Q_D(QQuickContainer); |
| 480 | d->cleanup(); |
| 481 | } |
| 482 | |
| 483 | /*! |
| 484 | \qmlproperty int QtQuick.Controls::Container::count |
| 485 | \readonly |
| 486 | |
| 487 | This property holds the number of items. |
| 488 | */ |
| 489 | int QQuickContainer::count() const |
| 490 | { |
| 491 | Q_D(const QQuickContainer); |
| 492 | return d->contentModel->count(); |
| 493 | } |
| 494 | |
| 495 | /*! |
| 496 | \qmlmethod Item QtQuick.Controls::Container::itemAt(int index) |
| 497 | |
| 498 | Returns the item at \a index, or \c null if it does not exist. |
| 499 | */ |
| 500 | QQuickItem *QQuickContainer::itemAt(int index) const |
| 501 | { |
| 502 | Q_D(const QQuickContainer); |
| 503 | return d->itemAt(index); |
| 504 | } |
| 505 | |
| 506 | /*! |
| 507 | \qmlmethod void QtQuick.Controls::Container::addItem(Item item) |
| 508 | |
| 509 | Adds an \a item. |
| 510 | */ |
| 511 | void QQuickContainer::addItem(QQuickItem *item) |
| 512 | { |
| 513 | Q_D(QQuickContainer); |
| 514 | insertItem(index: d->contentModel->count(), item); |
| 515 | } |
| 516 | |
| 517 | /*! |
| 518 | \qmlmethod void QtQuick.Controls::Container::insertItem(int index, Item item) |
| 519 | |
| 520 | Inserts an \a item at \a index. |
| 521 | */ |
| 522 | void QQuickContainer::insertItem(int index, QQuickItem *item) |
| 523 | { |
| 524 | Q_D(QQuickContainer); |
| 525 | if (!item) |
| 526 | return; |
| 527 | const int count = d->contentModel->count(); |
| 528 | if (index < 0 || index > count) |
| 529 | index = count; |
| 530 | |
| 531 | int oldIndex = d->contentModel->indexOf(object: item, objectContext: nullptr); |
| 532 | if (oldIndex != -1) { |
| 533 | if (oldIndex < index) |
| 534 | --index; |
| 535 | if (oldIndex != index) |
| 536 | d->moveItem(from: oldIndex, to: index, item); |
| 537 | } else { |
| 538 | d->insertItem(index, item); |
| 539 | } |
| 540 | } |
| 541 | |
| 542 | /*! |
| 543 | \qmlmethod void QtQuick.Controls::Container::moveItem(int from, int to) |
| 544 | |
| 545 | Moves an item \a from one index \a to another. |
| 546 | */ |
| 547 | void QQuickContainer::moveItem(int from, int to) |
| 548 | { |
| 549 | Q_D(QQuickContainer); |
| 550 | const int count = d->contentModel->count(); |
| 551 | if (from < 0 || from > count - 1) |
| 552 | return; |
| 553 | if (to < 0 || to > count - 1) |
| 554 | to = count - 1; |
| 555 | |
| 556 | if (from != to) |
| 557 | d->moveItem(from, to, item: d->itemAt(index: from)); |
| 558 | } |
| 559 | |
| 560 | /*! |
| 561 | \since QtQuick.Controls 2.3 (Qt 5.10) |
| 562 | \qmlmethod void QtQuick.Controls::Container::removeItem(Item item) |
| 563 | |
| 564 | Removes and destroys the specified \a item. |
| 565 | */ |
| 566 | void QQuickContainer::removeItem(QQuickItem *item) |
| 567 | { |
| 568 | Q_D(QQuickContainer); |
| 569 | if (!item) |
| 570 | return; |
| 571 | |
| 572 | const int index = d->contentModel->indexOf(object: item, objectContext: nullptr); |
| 573 | if (index == -1) |
| 574 | return; |
| 575 | |
| 576 | d->removeItem(index, item); |
| 577 | item->deleteLater(); |
| 578 | } |
| 579 | |
| 580 | /*! |
| 581 | \since QtQuick.Controls 2.3 (Qt 5.10) |
| 582 | \qmlmethod Item QtQuick.Controls::Container::takeItem(int index) |
| 583 | |
| 584 | Removes and returns the item at \a index. |
| 585 | |
| 586 | \note The ownership of the item is transferred to the caller. |
| 587 | */ |
| 588 | QQuickItem *QQuickContainer::takeItem(int index) |
| 589 | { |
| 590 | Q_D(QQuickContainer); |
| 591 | const int count = d->contentModel->count(); |
| 592 | if (index < 0 || index >= count) |
| 593 | return nullptr; |
| 594 | |
| 595 | QQuickItem *item = itemAt(index); |
| 596 | if (item) |
| 597 | d->removeItem(index, item); |
| 598 | return item; |
| 599 | } |
| 600 | |
| 601 | /*! |
| 602 | \qmlproperty model QtQuick.Controls::Container::contentModel |
| 603 | \readonly |
| 604 | |
| 605 | This property holds the content model of items. |
| 606 | |
| 607 | The content model is provided for visualization purposes. It can be assigned |
| 608 | as a model to a content item that presents the contents of the container. |
| 609 | |
| 610 | \code |
| 611 | Container { |
| 612 | id: container |
| 613 | contentItem: ListView { |
| 614 | model: container.contentModel |
| 615 | } |
| 616 | } |
| 617 | \endcode |
| 618 | |
| 619 | \sa contentData, contentChildren |
| 620 | */ |
| 621 | QVariant QQuickContainer::contentModel() const |
| 622 | { |
| 623 | Q_D(const QQuickContainer); |
| 624 | return QVariant::fromValue(value: d->contentModel); |
| 625 | } |
| 626 | |
| 627 | /*! |
| 628 | \qmlproperty list<QtObject> QtQuick.Controls::Container::contentData |
| 629 | \qmldefault |
| 630 | |
| 631 | This property holds the list of content data. |
| 632 | |
| 633 | The list contains all objects that have been declared in QML as children |
| 634 | of the container, and also items that have been dynamically added or |
| 635 | inserted using the \l addItem() and \l insertItem() methods, respectively. |
| 636 | |
| 637 | \note Unlike \c contentChildren, \c contentData does include non-visual QML |
| 638 | objects. It is not re-ordered when items are inserted or moved. |
| 639 | |
| 640 | \sa Item::data, contentChildren |
| 641 | */ |
| 642 | QQmlListProperty<QObject> QQuickContainer::contentData() |
| 643 | { |
| 644 | Q_D(QQuickContainer); |
| 645 | if (!d->contentItem) |
| 646 | d->executeContentItem(); |
| 647 | return QQmlListProperty<QObject>(this, nullptr, |
| 648 | QQuickContainerPrivate::contentData_append, |
| 649 | QQuickContainerPrivate::contentData_count, |
| 650 | QQuickContainerPrivate::contentData_at, |
| 651 | QQuickContainerPrivate::contentData_clear); |
| 652 | } |
| 653 | |
| 654 | /*! |
| 655 | \qmlproperty list<Item> QtQuick.Controls::Container::contentChildren |
| 656 | |
| 657 | This property holds the list of content children. |
| 658 | |
| 659 | The list contains all items that have been declared in QML as children |
| 660 | of the container, and also items that have been dynamically added or |
| 661 | inserted using the \l addItem() and \l insertItem() methods, respectively. |
| 662 | |
| 663 | \note Unlike \c contentData, \c contentChildren does not include non-visual |
| 664 | QML objects. It is re-ordered when items are inserted or moved. |
| 665 | |
| 666 | \sa Item::children, contentData |
| 667 | */ |
| 668 | QQmlListProperty<QQuickItem> QQuickContainer::contentChildren() |
| 669 | { |
| 670 | return QQmlListProperty<QQuickItem>(this, nullptr, |
| 671 | QQuickContainerPrivate::contentChildren_append, |
| 672 | QQuickContainerPrivate::contentChildren_count, |
| 673 | QQuickContainerPrivate::contentChildren_at, |
| 674 | QQuickContainerPrivate::contentChildren_clear); |
| 675 | } |
| 676 | |
| 677 | /*! |
| 678 | \qmlproperty int QtQuick.Controls::Container::currentIndex |
| 679 | |
| 680 | This property holds the index of the current item. |
| 681 | |
| 682 | \sa currentItem, {Managing the Current Index} |
| 683 | */ |
| 684 | int QQuickContainer::currentIndex() const |
| 685 | { |
| 686 | Q_D(const QQuickContainer); |
| 687 | return d->currentIndex; |
| 688 | } |
| 689 | |
| 690 | /*! |
| 691 | \qmlmethod void QtQuick.Controls::Container::setCurrentIndex(int index) |
| 692 | |
| 693 | Sets the current \a index of the container. |
| 694 | |
| 695 | This method can be called to set a specific current index without breaking |
| 696 | existing \c currentIndex bindings. |
| 697 | |
| 698 | \sa currentIndex, {Managing the Current Index} |
| 699 | */ |
| 700 | void QQuickContainer::setCurrentIndex(int index) |
| 701 | { |
| 702 | Q_D(QQuickContainer); |
| 703 | if (d->currentIndex == index) |
| 704 | return; |
| 705 | |
| 706 | d->currentIndex = index; |
| 707 | emit currentIndexChanged(); |
| 708 | emit currentItemChanged(); |
| 709 | } |
| 710 | |
| 711 | /*! |
| 712 | \qmlmethod void QtQuick.Controls::Container::incrementCurrentIndex() |
| 713 | \since QtQuick.Controls 2.1 (Qt 5.8) |
| 714 | |
| 715 | Increments the current index of the container. |
| 716 | |
| 717 | This method can be called to alter the current index without breaking |
| 718 | existing \c currentIndex bindings. |
| 719 | |
| 720 | \sa currentIndex, {Managing the Current Index} |
| 721 | */ |
| 722 | void QQuickContainer::incrementCurrentIndex() |
| 723 | { |
| 724 | Q_D(QQuickContainer); |
| 725 | if (d->currentIndex < count() - 1) |
| 726 | setCurrentIndex(d->currentIndex + 1); |
| 727 | } |
| 728 | |
| 729 | /*! |
| 730 | \qmlmethod void QtQuick.Controls::Container::decrementCurrentIndex() |
| 731 | \since QtQuick.Controls 2.1 (Qt 5.8) |
| 732 | |
| 733 | Decrements the current index of the container. |
| 734 | |
| 735 | This method can be called to alter the current index without breaking |
| 736 | existing \c currentIndex bindings. |
| 737 | |
| 738 | \sa currentIndex, {Managing the Current Index} |
| 739 | */ |
| 740 | void QQuickContainer::decrementCurrentIndex() |
| 741 | { |
| 742 | Q_D(QQuickContainer); |
| 743 | if (d->currentIndex > 0) |
| 744 | setCurrentIndex(d->currentIndex - 1); |
| 745 | } |
| 746 | |
| 747 | /*! |
| 748 | \qmlproperty Item QtQuick.Controls::Container::currentItem |
| 749 | \readonly |
| 750 | |
| 751 | This property holds the current item. |
| 752 | |
| 753 | \sa currentIndex |
| 754 | */ |
| 755 | QQuickItem *QQuickContainer::currentItem() const |
| 756 | { |
| 757 | Q_D(const QQuickContainer); |
| 758 | return itemAt(index: d->currentIndex); |
| 759 | } |
| 760 | |
| 761 | /*! |
| 762 | \since QtQuick.Controls 2.5 (Qt 5.12) |
| 763 | \qmlproperty real QtQuick.Controls::Container::contentWidth |
| 764 | |
| 765 | This property holds the content width. It is used for calculating the total |
| 766 | implicit width of the container. |
| 767 | |
| 768 | Unless explicitly overridden, the content width is automatically calculated |
| 769 | based on the implicit width of the items in the container. |
| 770 | |
| 771 | \sa contentHeight |
| 772 | */ |
| 773 | qreal QQuickContainer::contentWidth() const |
| 774 | { |
| 775 | Q_D(const QQuickContainer); |
| 776 | return d->contentWidth; |
| 777 | } |
| 778 | |
| 779 | void QQuickContainer::setContentWidth(qreal width) |
| 780 | { |
| 781 | Q_D(QQuickContainer); |
| 782 | d->hasContentWidth = true; |
| 783 | if (qFuzzyCompare(p1: d->contentWidth, p2: width)) |
| 784 | return; |
| 785 | |
| 786 | d->contentWidth = width; |
| 787 | d->resizeContent(); |
| 788 | emit contentWidthChanged(); |
| 789 | } |
| 790 | |
| 791 | void QQuickContainer::resetContentWidth() |
| 792 | { |
| 793 | Q_D(QQuickContainer); |
| 794 | if (!d->hasContentWidth) |
| 795 | return; |
| 796 | |
| 797 | d->hasContentWidth = false; |
| 798 | d->updateContentWidth(); |
| 799 | } |
| 800 | |
| 801 | /*! |
| 802 | \since QtQuick.Controls 2.5 (Qt 5.12) |
| 803 | \qmlproperty real QtQuick.Controls::Container::contentHeight |
| 804 | |
| 805 | This property holds the content height. It is used for calculating the total |
| 806 | implicit height of the container. |
| 807 | |
| 808 | Unless explicitly overridden, the content height is automatically calculated |
| 809 | based on the implicit height of the items in the container. |
| 810 | |
| 811 | \sa contentWidth |
| 812 | */ |
| 813 | qreal QQuickContainer::contentHeight() const |
| 814 | { |
| 815 | Q_D(const QQuickContainer); |
| 816 | return d->contentHeight; |
| 817 | } |
| 818 | |
| 819 | void QQuickContainer::setContentHeight(qreal height) |
| 820 | { |
| 821 | Q_D(QQuickContainer); |
| 822 | d->hasContentHeight = true; |
| 823 | if (qFuzzyCompare(p1: d->contentHeight, p2: height)) |
| 824 | return; |
| 825 | |
| 826 | d->contentHeight = height; |
| 827 | d->resizeContent(); |
| 828 | emit contentHeightChanged(); |
| 829 | } |
| 830 | |
| 831 | void QQuickContainer::resetContentHeight() |
| 832 | { |
| 833 | Q_D(QQuickContainer); |
| 834 | if (!d->hasContentHeight) |
| 835 | return; |
| 836 | |
| 837 | d->hasContentHeight = false; |
| 838 | d->updateContentHeight(); |
| 839 | } |
| 840 | |
| 841 | void QQuickContainer::componentComplete() |
| 842 | { |
| 843 | Q_D(QQuickContainer); |
| 844 | QQuickControl::componentComplete(); |
| 845 | d->reorderItems(); |
| 846 | d->maybeCullItems(); |
| 847 | } |
| 848 | |
| 849 | void QQuickContainer::itemChange(ItemChange change, const ItemChangeData &data) |
| 850 | { |
| 851 | Q_D(QQuickContainer); |
| 852 | QQuickControl::itemChange(change, value: data); |
| 853 | if (change == QQuickItem::ItemChildAddedChange && isComponentComplete() && data.item != d->background && data.item != d->contentItem) { |
| 854 | if (!QQuickItemPrivate::get(item: data.item)->isTransparentForPositioner() && d->contentModel->indexOf(object: data.item, objectContext: nullptr) == -1) |
| 855 | addItem(item: data.item); |
| 856 | } |
| 857 | } |
| 858 | |
| 859 | void QQuickContainer::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) |
| 860 | { |
| 861 | Q_D(QQuickContainer); |
| 862 | QQuickControl::contentItemChange(newItem, oldItem); |
| 863 | |
| 864 | static const int slotIndex = metaObject()->indexOfSlot(slot: "_q_currentIndexChanged()" ); |
| 865 | |
| 866 | if (oldItem) { |
| 867 | QQuickItemPrivate::get(item: oldItem)->removeItemChangeListener(d, types: QQuickItemPrivate::Children | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight); |
| 868 | QQuickItem *oldContentItem = effectiveContentItem(item: oldItem); |
| 869 | if (oldContentItem != oldItem) |
| 870 | QQuickItemPrivate::get(item: oldContentItem)->removeItemChangeListener(d, types: QQuickItemPrivate::Children); |
| 871 | |
| 872 | int signalIndex = oldItem->metaObject()->indexOfSignal(signal: "currentIndexChanged()" ); |
| 873 | if (signalIndex != -1) |
| 874 | QMetaObject::disconnect(sender: oldItem, signal_index: signalIndex, receiver: this, method_index: slotIndex); |
| 875 | } |
| 876 | |
| 877 | if (newItem) { |
| 878 | QQuickItemPrivate::get(item: newItem)->addItemChangeListener(listener: d, types: QQuickItemPrivate::Children | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight); |
| 879 | QQuickItem *newContentItem = effectiveContentItem(item: newItem); |
| 880 | if (newContentItem != newItem) |
| 881 | QQuickItemPrivate::get(item: newContentItem)->addItemChangeListener(listener: d, types: QQuickItemPrivate::Children); |
| 882 | |
| 883 | int signalIndex = newItem->metaObject()->indexOfSignal(signal: "currentIndexChanged()" ); |
| 884 | if (signalIndex != -1) |
| 885 | QMetaObject::connect(sender: newItem, signal_index: signalIndex, receiver: this, method_index: slotIndex); |
| 886 | } |
| 887 | } |
| 888 | |
| 889 | bool QQuickContainer::isContent(QQuickItem *item) const |
| 890 | { |
| 891 | // If the item has a QML context associated to it (it was created in QML), |
| 892 | // we add it to the content model. Otherwise, it's probably the default |
| 893 | // highlight item that is always created by the item views, which we need |
| 894 | // to exclude. |
| 895 | // |
| 896 | // TODO: Find a better way to identify/exclude the highlight item... |
| 897 | return qmlContext(item); |
| 898 | } |
| 899 | |
| 900 | void QQuickContainer::itemAdded(int index, QQuickItem *item) |
| 901 | { |
| 902 | Q_UNUSED(index); |
| 903 | Q_UNUSED(item); |
| 904 | } |
| 905 | |
| 906 | void QQuickContainer::itemMoved(int index, QQuickItem *item) |
| 907 | { |
| 908 | Q_UNUSED(index); |
| 909 | Q_UNUSED(item); |
| 910 | } |
| 911 | |
| 912 | void QQuickContainer::itemRemoved(int index, QQuickItem *item) |
| 913 | { |
| 914 | Q_UNUSED(index); |
| 915 | Q_UNUSED(item); |
| 916 | } |
| 917 | |
| 918 | QT_END_NAMESPACE |
| 919 | |
| 920 | #include "moc_qquickcontainer_p.cpp" |
| 921 | |