Unity 8
abstractdashview.cpp
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  */
16 
17 #include "abstractdashview.h"
18 
19 #include <private/qquickitem_p.h>
20 
21 AbstractDashView::AbstractDashView()
22  : m_delegateModel(nullptr)
23  , m_asyncRequestedIndex(-1)
24  , m_columnSpacing(0)
25  , m_rowSpacing(0)
26  , m_buffer(320) // Same value used in qquickitemview.cpp in Qt 5.4
27  , m_displayMarginBeginning(0)
28  , m_displayMarginEnd(0)
29  , m_needsRelayout(false)
30  , m_delegateValidated(false)
31  , m_implicitHeightDirty(false)
32 {
33  connect(this, &AbstractDashView::widthChanged, this, &AbstractDashView::relayout);
34  connect(this, &AbstractDashView::heightChanged, this, &AbstractDashView::onHeightChanged);
35 }
36 
37 QAbstractItemModel *AbstractDashView::model() const
38 {
39  return m_delegateModel ? m_delegateModel->model().value<QAbstractItemModel *>() : nullptr;
40 }
41 
42 void AbstractDashView::setModel(QAbstractItemModel *model)
43 {
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);
52 
53  cleanupExistingItems();
54 
55  Q_EMIT modelChanged();
56  polish();
57  }
58 }
59 
60 QQmlComponent *AbstractDashView::delegate() const
61 {
62  return m_delegateModel ? m_delegateModel->delegate() : nullptr;
63 }
64 
65 void AbstractDashView::setDelegate(QQmlComponent *delegate)
66 {
67  if (delegate != this->delegate()) {
68  if (!m_delegateModel) {
69  createDelegateModel();
70  }
71 
72  cleanupExistingItems();
73 
74  m_delegateModel->setDelegate(delegate);
75 
76  Q_EMIT delegateChanged();
77  m_delegateValidated = false;
78  polish();
79  }
80 }
81 
82 qreal AbstractDashView::columnSpacing() const
83 {
84  return m_columnSpacing;
85 }
86 
87 void AbstractDashView::setColumnSpacing(qreal columnSpacing)
88 {
89  if (columnSpacing != m_columnSpacing) {
90  m_columnSpacing = columnSpacing;
91  Q_EMIT columnSpacingChanged();
92 
93  if (isComponentComplete()) {
94  relayout();
95  }
96  }
97 }
98 
99 qreal AbstractDashView::rowSpacing() const
100 {
101  return m_rowSpacing;
102 }
103 
104 void AbstractDashView::setRowSpacing(qreal rowSpacing)
105 {
106  if (rowSpacing != m_rowSpacing) {
107  m_rowSpacing = rowSpacing;
108  Q_EMIT rowSpacingChanged();
109 
110  if (isComponentComplete()) {
111  relayout();
112  }
113  }
114 }
115 
116 int AbstractDashView::cacheBuffer() const
117 {
118  return m_buffer;
119 }
120 
121 void AbstractDashView::setCacheBuffer(int buffer)
122 {
123  if (buffer < 0) {
124  qmlInfo(this) << "Cannot set a negative cache buffer";
125  return;
126  }
127 
128  if (m_buffer != buffer) {
129  m_buffer = buffer;
130  if (isComponentComplete()) {
131  polish();
132  }
133  emit cacheBufferChanged();
134  }
135 }
136 
137 qreal AbstractDashView::displayMarginBeginning() const
138 {
139  return m_displayMarginBeginning;
140 }
141 
142 void AbstractDashView::setDisplayMarginBeginning(qreal begin)
143 {
144  if (m_displayMarginBeginning == begin)
145  return;
146  m_displayMarginBeginning = begin;
147  if (isComponentComplete()) {
148  polish();
149  }
150  emit displayMarginBeginningChanged();
151 }
152 
153 qreal AbstractDashView::displayMarginEnd() const
154 {
155  return m_displayMarginEnd;
156 }
157 
158 void AbstractDashView::setDisplayMarginEnd(qreal end)
159 {
160  if (m_displayMarginEnd == end)
161  return;
162  m_displayMarginEnd = end;
163  if (isComponentComplete()) {
164  polish();
165  }
166  emit displayMarginEndChanged();
167 }
168 
169 void AbstractDashView::createDelegateModel()
170 {
171  m_delegateModel = new QQmlDelegateModel(qmlContext(this), this);
172  connect(m_delegateModel, &QQmlDelegateModel::createdItem, this, &AbstractDashView::itemCreated);
173  if (isComponentComplete())
174  m_delegateModel->componentComplete();
175 }
176 
177 void AbstractDashView::refill()
178 {
179  if (!isComponentComplete() || height() < 0) {
180  return;
181  }
182 
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;
187 
188  bool added = addVisibleItems(from, to, false);
189  bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
190  added |= addVisibleItems(bufferFrom, bufferTo, true);
191 
192  if (added || removed) {
193  m_implicitHeightDirty = true;
194  polish();
195  }
196 }
197 
198 bool AbstractDashView::addVisibleItems(qreal fillFromY, qreal fillToY, bool asynchronous)
199 {
200  if (fillToY <= fillFromY)
201  return false;
202 
203  if (!delegate())
204  return false;
205 
206  if (m_delegateModel->count() == 0)
207  return false;
208 
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;
216 
217  changed = true;
218  findBottomModelIndexToAdd(&modelIndex, &yPos);
219  }
220 
221  findTopModelIndexToAdd(&modelIndex, &yPos);
222  while (modelIndex >= 0 && yPos > fillFromY) {
223  if (!createItem(modelIndex, asynchronous))
224  break;
225 
226  changed = true;
227  findTopModelIndexToAdd(&modelIndex, &yPos);
228  }
229 
230  return changed;
231 }
232 
233 QQuickItem *AbstractDashView::createItem(int modelIndex, bool asynchronous)
234 {
235  if (asynchronous && m_asyncRequestedIndex != -1)
236  return nullptr;
237 
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  }
258 }
259 
260 void AbstractDashView::releaseItem(QQuickItem *item)
261 {
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  }
267 }
268 
269 void AbstractDashView::setImplicitHeightDirty()
270 {
271  m_implicitHeightDirty = true;
272 }
273 
274 void AbstractDashView::itemCreated(int modelIndex, QObject *object)
275 {
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);
282 
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  }
293 }
294 
295 void AbstractDashView::onModelUpdated(const QQmlChangeSet &changeSet, bool reset)
296 {
297  if (reset) {
298  cleanupExistingItems();
299  } else {
300  processModelRemoves(changeSet.removes());
301 
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();
312 }
313 
314 
315 void AbstractDashView::relayout()
316 {
317  m_needsRelayout = true;
318  polish();
319 }
320 
321 void AbstractDashView::onHeightChanged()
322 {
323  polish();
324 }
325 
326 void AbstractDashView::updatePolish()
327 {
328  if (!model())
329  return;
330 
331  if (m_needsRelayout) {
332  doRelayout();
333  m_needsRelayout = false;
334  m_implicitHeightDirty = true;
335  }
336 
337  refill();
338 
339  const qreal from = -m_displayMarginBeginning;
340  const qreal to = height() + m_displayMarginEnd;
341  updateItemCulling(from, to);
342 
343  if (m_implicitHeightDirty) {
344  calculateImplicitHeight();
345  m_implicitHeightDirty = false;
346  }
347 }
348 
349 void AbstractDashView::componentComplete()
350 {
351  if (m_delegateModel)
352  m_delegateModel->componentComplete();
353 
354  QQuickItem::componentComplete();
355 
356  m_needsRelayout = true;
357 
358  polish();
359 }