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