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