Unity 8
 All Classes Functions Properties
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 static const qreal bufferRatio = 0.5;
20 
21 AbstractDashView::AbstractDashView()
22  : m_delegateModel(nullptr)
23  , m_asyncRequestedIndex(-1)
24  , m_columnSpacing(0)
25  , m_rowSpacing(0)
26  , m_delegateCreationBegin(0)
27  , m_delegateCreationEnd(0)
28  , m_delegateCreationBeginValid(false)
29  , m_delegateCreationEndValid(false)
30  , m_needsRelayout(false)
31  , m_delegateValidated(false)
32  , m_implicitHeightDirty(false)
33 {
34  connect(this, SIGNAL(widthChanged()), this, SLOT(relayout()));
35  connect(this, SIGNAL(heightChanged()), this, SLOT(onHeightChanged()));
36 }
37 
38 QAbstractItemModel *AbstractDashView::model() const
39 {
40  return m_delegateModel ? m_delegateModel->model().value<QAbstractItemModel *>() : nullptr;
41 }
42 
43 void AbstractDashView::setModel(QAbstractItemModel *model)
44 {
45  if (model != this->model()) {
46  if (!m_delegateModel) {
47  createDelegateModel();
48  } else {
49 #if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
50  disconnect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
51  }
52  m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
53  connect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
54 #else
55  disconnect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool)));
56  }
57  m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
58  connect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool)));
59 #endif
60 
61  cleanupExistingItems();
62 
63  Q_EMIT modelChanged();
64  polish();
65  }
66 }
67 
68 QQmlComponent *AbstractDashView::delegate() const
69 {
70  return m_delegateModel ? m_delegateModel->delegate() : nullptr;
71 }
72 
73 void AbstractDashView::setDelegate(QQmlComponent *delegate)
74 {
75  if (delegate != this->delegate()) {
76  if (!m_delegateModel) {
77  createDelegateModel();
78  }
79 
80  cleanupExistingItems();
81 
82  m_delegateModel->setDelegate(delegate);
83 
84  Q_EMIT delegateChanged();
85  m_delegateValidated = false;
86  polish();
87  }
88 }
89 
90 qreal AbstractDashView::columnSpacing() const
91 {
92  return m_columnSpacing;
93 }
94 
95 void AbstractDashView::setColumnSpacing(qreal columnSpacing)
96 {
97  if (columnSpacing != m_columnSpacing) {
98  m_columnSpacing = columnSpacing;
99  Q_EMIT columnSpacingChanged();
100 
101  if (isComponentComplete()) {
102  relayout();
103  }
104  }
105 }
106 
107 qreal AbstractDashView::rowSpacing() const
108 {
109  return m_rowSpacing;
110 }
111 
112 void AbstractDashView::setRowSpacing(qreal rowSpacing)
113 {
114  if (rowSpacing != m_rowSpacing) {
115  m_rowSpacing = rowSpacing;
116  Q_EMIT rowSpacingChanged();
117 
118  if (isComponentComplete()) {
119  relayout();
120  }
121  }
122 }
123 
124 qreal AbstractDashView::delegateCreationBegin() const
125 {
126  return m_delegateCreationBegin;
127 }
128 
129 void AbstractDashView::setDelegateCreationBegin(qreal begin)
130 {
131  m_delegateCreationBeginValid = true;
132  if (m_delegateCreationBegin == begin)
133  return;
134  m_delegateCreationBegin = begin;
135  if (isComponentComplete()) {
136  polish();
137  }
138  emit delegateCreationBeginChanged();
139 }
140 
141 void AbstractDashView::resetDelegateCreationBegin()
142 {
143  m_delegateCreationBeginValid = false;
144  if (m_delegateCreationBegin == 0)
145  return;
146  m_delegateCreationBegin = 0;
147  if (isComponentComplete()) {
148  polish();
149  }
150  emit delegateCreationBeginChanged();
151 }
152 
153 qreal AbstractDashView::delegateCreationEnd() const
154 {
155  return m_delegateCreationEnd;
156 }
157 
158 void AbstractDashView::setDelegateCreationEnd(qreal end)
159 {
160  m_delegateCreationEndValid = true;
161  if (m_delegateCreationEnd == end)
162  return;
163  m_delegateCreationEnd = end;
164  if (isComponentComplete()) {
165  polish();
166  }
167  emit delegateCreationEndChanged();
168 }
169 
170 void AbstractDashView::resetDelegateCreationEnd()
171 {
172  m_delegateCreationEndValid = false;
173  if (m_delegateCreationEnd == 0)
174  return;
175  m_delegateCreationEnd = 0;
176  if (isComponentComplete()) {
177  polish();
178  }
179  emit delegateCreationEndChanged();
180 }
181 
182 void AbstractDashView::createDelegateModel()
183 {
184 #if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
185  m_delegateModel = new QQuickVisualDataModel(qmlContext(this), this);
186  connect(m_delegateModel, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(itemCreated(int,QQuickItem*)));
187 #else
188  m_delegateModel = new QQmlDelegateModel(qmlContext(this), this);
189  connect(m_delegateModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(itemCreated(int,QObject*)));
190 #endif
191  if (isComponentComplete())
192  m_delegateModel->componentComplete();
193 }
194 
195 void AbstractDashView::refill()
196 {
197  if (!isComponentComplete() || height() < 0) {
198  return;
199  }
200 
201  const bool delegateRangesValid = m_delegateCreationBeginValid && m_delegateCreationEndValid;
202  const qreal from = delegateRangesValid ? m_delegateCreationBegin : 0;
203  const qreal to = delegateRangesValid ? m_delegateCreationEnd : from + height();
204  const qreal buffer = (to - from) * bufferRatio;
205  const qreal bufferFrom = from - buffer;
206  const qreal bufferTo = to + buffer;
207 
208  bool added = addVisibleItems(from, to, false);
209  bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
210  added |= addVisibleItems(bufferFrom, bufferTo, true);
211 
212  if (added || removed) {
213  m_implicitHeightDirty = true;
214  polish();
215  }
216 }
217 
218 bool AbstractDashView::addVisibleItems(qreal fillFromY, qreal fillToY, bool asynchronous)
219 {
220  if (!delegate())
221  return false;
222 
223  if (m_delegateModel->count() == 0)
224  return false;
225 
226  int modelIndex;
227  qreal yPos;
228  findBottomModelIndexToAdd(&modelIndex, &yPos);
229  bool changed = false;
230  while (modelIndex < m_delegateModel->count() && yPos <= fillToY) {
231  if (!createItem(modelIndex, asynchronous))
232  break;
233 
234  changed = true;
235  findBottomModelIndexToAdd(&modelIndex, &yPos);
236  }
237 
238  findTopModelIndexToAdd(&modelIndex, &yPos);
239  while (modelIndex >= 0 && yPos > fillFromY) {
240  if (!createItem(modelIndex, asynchronous))
241  break;
242 
243  changed = true;
244  findTopModelIndexToAdd(&modelIndex, &yPos);
245  }
246 
247  return changed;
248 }
249 
250 QQuickItem *AbstractDashView::createItem(int modelIndex, bool asynchronous)
251 {
252  if (asynchronous && m_asyncRequestedIndex != -1)
253  return nullptr;
254 
255  m_asyncRequestedIndex = -1;
256 #if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
257  QQuickItem *item = m_delegateModel->item(modelIndex, asynchronous);
258 #else
259  QObject* object = m_delegateModel->object(modelIndex, asynchronous);
260  QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
261 #endif
262  if (!item) {
263 #if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
264  m_asyncRequestedIndex = modelIndex;
265 #else
266  if (object) {
267  m_delegateModel->release(object);
268  if (!m_delegateValidated) {
269  m_delegateValidated = true;
270  QObject* delegateObj = delegate();
271  qmlInfo(delegateObj ? delegateObj : this) << "Delegate must be of Item type";
272  }
273  } else {
274  m_asyncRequestedIndex = modelIndex;
275  }
276 #endif
277  return nullptr;
278  } else {
279  addItemToView(modelIndex, item);
280  return item;
281  }
282 }
283 
284 void AbstractDashView::releaseItem(QQuickItem *item)
285 {
286 #if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
287  QQuickVisualModel::ReleaseFlags flags = m_delegateModel->release(item);
288  if (flags & QQuickVisualModel::Destroyed) {
289 #else
290  QQmlDelegateModel::ReleaseFlags flags = m_delegateModel->release(item);
291  if (flags & QQmlDelegateModel::Destroyed) {
292 #endif
293  item->setParentItem(nullptr);
294  }
295 }
296 
297 void AbstractDashView::setImplicitHeightDirty()
298 {
299  m_implicitHeightDirty = true;
300 }
301 
302 #if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
303 void AbstractDashView::itemCreated(int modelIndex, QQuickItem *item)
304 {
305 #else
306 void AbstractDashView::itemCreated(int modelIndex, QObject *object)
307 {
308  QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
309  if (!item) {
310  qWarning() << "AbstractDashView::itemCreated got a non item for index" << modelIndex;
311  return;
312  }
313 #endif
314  item->setParentItem(this);
315 
316  // We only need to call createItem if we are here because of an asynchronous generation
317  // otherwise we are in this slot because createItem is creating the item sync
318  // and thus we don't need to call createItem again, nor need to set m_implicitHeightDirty
319  // and call polish because the sync createItem was called from addVisibleItems that
320  // is called from refill that will already do those if an item was added
321  if (modelIndex == m_asyncRequestedIndex) {
322  createItem(modelIndex, false);
323  m_implicitHeightDirty = true;
324  polish();
325  }
326 }
327 
328 #if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
329 void AbstractDashView::onModelUpdated(const QQuickChangeSet &changeSet, bool reset)
330 #else
331 void AbstractDashView::onModelUpdated(const QQmlChangeSet &changeSet, bool reset)
332 #endif
333 {
334  if (reset) {
335  cleanupExistingItems();
336  } else {
337  processModelRemoves(changeSet.removes());
338  }
339  polish();
340 }
341 
342 
343 void AbstractDashView::relayout()
344 {
345  m_needsRelayout = true;
346  polish();
347 }
348 
349 void AbstractDashView::onHeightChanged()
350 {
351  polish();
352 }
353 
354 void AbstractDashView::updatePolish()
355 {
356  if (!model())
357  return;
358 
359  if (m_needsRelayout) {
360  doRelayout();
361  m_needsRelayout = false;
362  m_implicitHeightDirty = true;
363  }
364 
365  refill();
366 
367  const bool delegateRangesValid = m_delegateCreationBeginValid && m_delegateCreationEndValid;
368  const qreal from = delegateRangesValid ? m_delegateCreationBegin : 0;
369  const qreal to = delegateRangesValid ? m_delegateCreationEnd : from + height();
370  updateItemCulling(from, to);
371 
372  if (m_implicitHeightDirty) {
373  calculateImplicitHeight();
374  m_implicitHeightDirty = false;
375  }
376 }
377 
378 void AbstractDashView::componentComplete()
379 {
380  if (m_delegateModel)
381  m_delegateModel->componentComplete();
382 
383  QQuickItem::componentComplete();
384 
385  m_needsRelayout = true;
386 
387  polish();
388 }