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