on 175e752 help sign in
unity8/plugins/Dash/abstractdashview.cpp
359 lines 9.6 kB history raw
1/*
2 * Copyright (C) 2013, 2014 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
17#include "abstractdashview.h"
19#include <private/qquickitem_p.h>
21AbstractDashView::AbstractDashView()
22 : m_delegateModel(nullptr)
23 , m_asyncRequestedIndex(-1)
24 , m_columnSpacing(0)
25 , m_rowSpacing(0)
26 , m_buffer(320) // Same value as QML_VIEW_DEFAULTCACHEBUFFER in qquickitemview.cpp (Qt 5.6)
27 , m_displayMarginBeginning(0)
28 , m_displayMarginEnd(0)
29 , m_needsRelayout(false)
30 , m_delegateValidated(false)
31 , m_implicitHeightDirty(false)
33 connect(this, &AbstractDashView::widthChanged, this, &AbstractDashView::relayout);
34 connect(this, &AbstractDashView::heightChanged, this, &AbstractDashView::onHeightChanged);
37QAbstractItemModel *AbstractDashView::model() const
39 return m_delegateModel ? m_delegateModel->model().value<QAbstractItemModel *>() : nullptr;
42void AbstractDashView::setModel(QAbstractItemModel *model)
44 if (model != this->model()) {
45 if (!m_delegateModel) {
46 createDelegateModel();
47 } else {
48 disconnect(m_delegateModel, &QQmlDelegateModel::modelUpdated, this, &AbstractDashView::onModelUpdated);
49 }
50 m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
51 connect(m_delegateModel, &QQmlDelegateModel::modelUpdated, this, &AbstractDashView::onModelUpdated);
53 cleanupExistingItems();
55 Q_EMIT modelChanged();
56 polish();
57 }
60QQmlComponent *AbstractDashView::delegate() const
62 return m_delegateModel ? m_delegateModel->delegate() : nullptr;
65void AbstractDashView::setDelegate(QQmlComponent *delegate)
67 if (delegate != this->delegate()) {
68 if (!m_delegateModel) {
69 createDelegateModel();
70 }
72 cleanupExistingItems();
74 m_delegateModel->setDelegate(delegate);
76 Q_EMIT delegateChanged();
77 m_delegateValidated = false;
78 polish();
79 }
82qreal AbstractDashView::columnSpacing() const
84 return m_columnSpacing;
87void AbstractDashView::setColumnSpacing(qreal columnSpacing)
89 if (columnSpacing != m_columnSpacing) {
90 m_columnSpacing = columnSpacing;
91 Q_EMIT columnSpacingChanged();
93 if (isComponentComplete()) {
94 relayout();
95 }
96 }
99qreal AbstractDashView::rowSpacing() const
101 return m_rowSpacing;
104void AbstractDashView::setRowSpacing(qreal rowSpacing)
106 if (rowSpacing != m_rowSpacing) {
107 m_rowSpacing = rowSpacing;
108 Q_EMIT rowSpacingChanged();
110 if (isComponentComplete()) {
111 relayout();
112 }
113 }
116int AbstractDashView::cacheBuffer() const
118 return m_buffer;
121void AbstractDashView::setCacheBuffer(int buffer)
123 if (buffer < 0) {
124 qmlInfo(this) << "Cannot set a negative cache buffer";
125 return;
126 }
128 if (m_buffer != buffer) {
129 m_buffer = buffer;
130 if (isComponentComplete()) {
131 polish();
132 }
133 emit cacheBufferChanged();
134 }
137qreal AbstractDashView::displayMarginBeginning() const
139 return m_displayMarginBeginning;
142void AbstractDashView::setDisplayMarginBeginning(qreal begin)
144 if (m_displayMarginBeginning == begin)
145 return;
146 m_displayMarginBeginning = begin;
147 if (isComponentComplete()) {
148 polish();
149 }
150 emit displayMarginBeginningChanged();
153qreal AbstractDashView::displayMarginEnd() const
155 return m_displayMarginEnd;
158void AbstractDashView::setDisplayMarginEnd(qreal end)
160 if (m_displayMarginEnd == end)
161 return;
162 m_displayMarginEnd = end;
163 if (isComponentComplete()) {
164 polish();
165 }
166 emit displayMarginEndChanged();
169void AbstractDashView::createDelegateModel()
171 m_delegateModel = new QQmlDelegateModel(qmlContext(this), this);
172 connect(m_delegateModel, &QQmlDelegateModel::createdItem, this, &AbstractDashView::itemCreated);
173 if (isComponentComplete())
174 m_delegateModel->componentComplete();
177void AbstractDashView::refill()
179 if (!isComponentComplete() || height() < 0) {
180 return;
181 }
183 const qreal from = -m_displayMarginBeginning;
184 const qreal to = height() + m_displayMarginEnd;
185 const qreal bufferFrom = from - m_buffer;
186 const qreal bufferTo = to + m_buffer;
188 bool added = addVisibleItems(from, to, false);
189 bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
190 added |= addVisibleItems(bufferFrom, bufferTo, true);
192 if (added || removed) {
193 m_implicitHeightDirty = true;
194 polish();
195 }
198bool AbstractDashView::addVisibleItems(qreal fillFromY, qreal fillToY, bool asynchronous)
200 if (fillToY <= fillFromY)
201 return false;
203 if (!delegate())
204 return false;
206 if (m_delegateModel->count() == 0)
207 return false;
209 int modelIndex;
210 qreal yPos;
211 findBottomModelIndexToAdd(&modelIndex, &yPos);
212 bool changed = false;
213 while (modelIndex < m_delegateModel->count() && yPos <= fillToY) {
214 if (!createItem(modelIndex, asynchronous))
215 break;
217 changed = true;
218 findBottomModelIndexToAdd(&modelIndex, &yPos);
219 }
221 findTopModelIndexToAdd(&modelIndex, &yPos);
222 while (modelIndex >= 0 && yPos > fillFromY) {
223 if (!createItem(modelIndex, asynchronous))
224 break;
226 changed = true;
227 findTopModelIndexToAdd(&modelIndex, &yPos);
228 }
230 return changed;
233QQuickItem *AbstractDashView::createItem(int modelIndex, bool asynchronous)
235 if (asynchronous && m_asyncRequestedIndex != -1)
236 return nullptr;
238 m_asyncRequestedIndex = -1;
239 QObject* object = m_delegateModel->object(modelIndex, asynchronous);
240 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
241 if (!item) {
242 if (object) {
243 m_delegateModel->release(object);
244 if (!m_delegateValidated) {
245 m_delegateValidated = true;
246 QObject* delegateObj = delegate();
247 qmlInfo(delegateObj ? delegateObj : this) << "Delegate must be of Item type";
248 }
249 } else {
250 m_asyncRequestedIndex = modelIndex;
251 }
252 return nullptr;
253 } else {
254 QQuickItemPrivate::get(item)->addItemChangeListener(this, QQuickItemPrivate::Geometry);
255 addItemToView(modelIndex, item);
256 return item;
257 }
260void AbstractDashView::releaseItem(QQuickItem *item)
262 QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Geometry);
263 QQmlDelegateModel::ReleaseFlags flags = m_delegateModel->release(item);
264 if (flags & QQmlDelegateModel::Destroyed) {
265 item->setParentItem(nullptr);
266 }
269void AbstractDashView::setImplicitHeightDirty()
271 m_implicitHeightDirty = true;
274void AbstractDashView::itemCreated(int modelIndex, QObject *object)
276 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
277 if (!item) {
278 qWarning() << "AbstractDashView::itemCreated got a non item for index" << modelIndex;
279 return;
280 }
281 item->setParentItem(this);
283 // We only need to call createItem if we are here because of an asynchronous generation
284 // otherwise we are in this slot because createItem is creating the item sync
285 // and thus we don't need to call createItem again, nor need to set m_implicitHeightDirty
286 // and call polish because the sync createItem was called from addVisibleItems that
287 // is called from refill that will already do those if an item was added
288 if (modelIndex == m_asyncRequestedIndex) {
289 createItem(modelIndex, false);
290 m_implicitHeightDirty = true;
291 polish();
292 }
295void AbstractDashView::onModelUpdated(const QQmlChangeSet &changeSet, bool reset)
297 if (reset) {
298 cleanupExistingItems();
299 } else {
300 processModelRemoves(changeSet.removes());
302 // The current AbstractDashViews do not support insertions that are not at the end
303 // so reset if that happens
304 Q_FOREACH(const QQmlChangeSet::Change insert, changeSet.inserts()) {
305 if (insert.index < m_delegateModel->count() - 1) {
306 cleanupExistingItems();
307 break;
308 }
309 }
310 }
311 polish();
315void AbstractDashView::relayout()
317 m_needsRelayout = true;
318 polish();
321void AbstractDashView::onHeightChanged()
323 polish();
326void AbstractDashView::updatePolish()
328 if (!model())
329 return;
331 if (m_needsRelayout) {
332 doRelayout();
333 m_needsRelayout = false;
334 m_implicitHeightDirty = true;
335 }
337 refill();
339 const qreal from = -m_displayMarginBeginning;
340 const qreal to = height() + m_displayMarginEnd;
341 updateItemCulling(from, to);
343 if (m_implicitHeightDirty) {
344 calculateImplicitHeight();
345 m_implicitHeightDirty = false;
346 }
349void AbstractDashView::componentComplete()
351 if (m_delegateModel)
352 m_delegateModel->componentComplete();
354 QQuickItem::componentComplete();
356 m_needsRelayout = true;
358 polish();