/* * Copyright (C) 2013, 2014 Canonical, Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "abstractdashview.h" static const qreal bufferRatio = 0.5; AbstractDashView::AbstractDashView() : m_delegateModel(nullptr) , m_asyncRequestedIndex(-1) , m_columnSpacing(0) , m_rowSpacing(0) , m_displayMarginBeginning(0) , m_displayMarginEnd(0) , m_needsRelayout(false) , m_delegateValidated(false) , m_implicitHeightDirty(false) { connect(this, SIGNAL(widthChanged()), this, SLOT(relayout())); connect(this, SIGNAL(heightChanged()), this, SLOT(onHeightChanged())); } QAbstractItemModel *AbstractDashView::model() const { return m_delegateModel ? m_delegateModel->model().value() : nullptr; } void AbstractDashView::setModel(QAbstractItemModel *model) { if (model != this->model()) { if (!m_delegateModel) { createDelegateModel(); } else { disconnect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool))); } m_delegateModel->setModel(QVariant::fromValue(model)); connect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool))); cleanupExistingItems(); Q_EMIT modelChanged(); polish(); } } QQmlComponent *AbstractDashView::delegate() const { return m_delegateModel ? m_delegateModel->delegate() : nullptr; } void AbstractDashView::setDelegate(QQmlComponent *delegate) { if (delegate != this->delegate()) { if (!m_delegateModel) { createDelegateModel(); } cleanupExistingItems(); m_delegateModel->setDelegate(delegate); Q_EMIT delegateChanged(); m_delegateValidated = false; polish(); } } qreal AbstractDashView::columnSpacing() const { return m_columnSpacing; } void AbstractDashView::setColumnSpacing(qreal columnSpacing) { if (columnSpacing != m_columnSpacing) { m_columnSpacing = columnSpacing; Q_EMIT columnSpacingChanged(); if (isComponentComplete()) { relayout(); } } } qreal AbstractDashView::rowSpacing() const { return m_rowSpacing; } void AbstractDashView::setRowSpacing(qreal rowSpacing) { if (rowSpacing != m_rowSpacing) { m_rowSpacing = rowSpacing; Q_EMIT rowSpacingChanged(); if (isComponentComplete()) { relayout(); } } } qreal AbstractDashView::displayMarginBeginning() const { return m_displayMarginBeginning; } void AbstractDashView::setDisplayMarginBeginning(qreal begin) { if (m_displayMarginBeginning == begin) return; m_displayMarginBeginning = begin; if (isComponentComplete()) { polish(); } emit displayMarginBeginningChanged(); } qreal AbstractDashView::displayMarginEnd() const { return m_displayMarginEnd; } void AbstractDashView::setDisplayMarginEnd(qreal end) { if (m_displayMarginEnd == end) return; m_displayMarginEnd = end; if (isComponentComplete()) { polish(); } emit displayMarginEndChanged(); } void AbstractDashView::createDelegateModel() { m_delegateModel = new QQmlDelegateModel(qmlContext(this), this); connect(m_delegateModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(itemCreated(int,QObject*))); if (isComponentComplete()) m_delegateModel->componentComplete(); } void AbstractDashView::refill() { if (!isComponentComplete() || height() < 0) { return; } const qreal from = -m_displayMarginBeginning; const qreal to = height() + m_displayMarginEnd; const qreal buffer = (to - from) * bufferRatio; const qreal bufferFrom = from - buffer; const qreal bufferTo = to + buffer; bool added = addVisibleItems(from, to, false); bool removed = removeNonVisibleItems(bufferFrom, bufferTo); added |= addVisibleItems(bufferFrom, bufferTo, true); if (added || removed) { m_implicitHeightDirty = true; polish(); } } bool AbstractDashView::addVisibleItems(qreal fillFromY, qreal fillToY, bool asynchronous) { if (fillToY <= fillFromY) return false; if (!delegate()) return false; if (m_delegateModel->count() == 0) return false; int modelIndex; qreal yPos; findBottomModelIndexToAdd(&modelIndex, &yPos); bool changed = false; while (modelIndex < m_delegateModel->count() && yPos <= fillToY) { if (!createItem(modelIndex, asynchronous)) break; changed = true; findBottomModelIndexToAdd(&modelIndex, &yPos); } findTopModelIndexToAdd(&modelIndex, &yPos); while (modelIndex >= 0 && yPos > fillFromY) { if (!createItem(modelIndex, asynchronous)) break; changed = true; findTopModelIndexToAdd(&modelIndex, &yPos); } return changed; } QQuickItem *AbstractDashView::createItem(int modelIndex, bool asynchronous) { if (asynchronous && m_asyncRequestedIndex != -1) return nullptr; m_asyncRequestedIndex = -1; QObject* object = m_delegateModel->object(modelIndex, asynchronous); QQuickItem *item = qmlobject_cast(object); if (!item) { if (object) { m_delegateModel->release(object); if (!m_delegateValidated) { m_delegateValidated = true; QObject* delegateObj = delegate(); qmlInfo(delegateObj ? delegateObj : this) << "Delegate must be of Item type"; } } else { m_asyncRequestedIndex = modelIndex; } return nullptr; } else { addItemToView(modelIndex, item); return item; } } void AbstractDashView::releaseItem(QQuickItem *item) { QQmlDelegateModel::ReleaseFlags flags = m_delegateModel->release(item); if (flags & QQmlDelegateModel::Destroyed) { item->setParentItem(nullptr); } } void AbstractDashView::setImplicitHeightDirty() { m_implicitHeightDirty = true; } void AbstractDashView::itemCreated(int modelIndex, QObject *object) { QQuickItem *item = qmlobject_cast(object); if (!item) { qWarning() << "AbstractDashView::itemCreated got a non item for index" << modelIndex; return; } item->setParentItem(this); // We only need to call createItem if we are here because of an asynchronous generation // otherwise we are in this slot because createItem is creating the item sync // and thus we don't need to call createItem again, nor need to set m_implicitHeightDirty // and call polish because the sync createItem was called from addVisibleItems that // is called from refill that will already do those if an item was added if (modelIndex == m_asyncRequestedIndex) { createItem(modelIndex, false); m_implicitHeightDirty = true; polish(); } } void AbstractDashView::onModelUpdated(const QQmlChangeSet &changeSet, bool reset) { if (reset) { cleanupExistingItems(); } else { processModelRemoves(changeSet.removes()); } polish(); } void AbstractDashView::relayout() { m_needsRelayout = true; polish(); } void AbstractDashView::onHeightChanged() { polish(); } void AbstractDashView::updatePolish() { if (!model()) return; if (m_needsRelayout) { doRelayout(); m_needsRelayout = false; m_implicitHeightDirty = true; } refill(); const qreal from = -m_displayMarginBeginning; const qreal to = height() + m_displayMarginEnd; updateItemCulling(from, to); if (m_implicitHeightDirty) { calculateImplicitHeight(); m_implicitHeightDirty = false; } } void AbstractDashView::componentComplete() { if (m_delegateModel) m_delegateModel->componentComplete(); QQuickItem::componentComplete(); m_needsRelayout = true; polish(); }