on 7227143 help sign in
unity8/plugins/DashViews/verticaljournal.cpp
536 lines 16 kB history raw
2#include "verticaljournal.h"
4#include <qqmlengine.h>
5#pragma GCC diagnostic push
6#pragma GCC diagnostic ignored "-pedantic"
7#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
8#include <private/qquickvisualdatamodel_p.h>
9#else
10#include <private/qqmldelegatemodel_p.h>
11#include <qqmlinfo.h>
12#endif
13#include <private/qquickitem_p.h>
14#pragma GCC diagnostic pop
16static const qreal bufferRatio = 0.5;
18VerticalJournal::VerticalJournal()
19 : m_delegateModel(nullptr)
20 , m_asyncRequestedIndex(-1)
21 , m_columnWidth(0)
22 , m_horizontalSpacing(0)
23 , m_verticalSpacing(0)
24 , m_delegateCreationBegin(0)
25 , m_delegateCreationEnd(0)
26 , m_delegateCreationBeginValid(false)
27 , m_delegateCreationEndValid(false)
28 , m_needsRelayout(false)
29 , m_delegateValidated(false)
30 , m_implicitHeightDirty(false)
32 connect(this, SIGNAL(widthChanged()), this, SLOT(relayout()));
33 connect(this, SIGNAL(heightChanged()), this, SLOT(onHeightChanged()));
36QAbstractItemModel *VerticalJournal::model() const
38 return m_delegateModel ? m_delegateModel->model().value<QAbstractItemModel *>() : nullptr;
41void VerticalJournal::setModel(QAbstractItemModel *model)
43 if (model != this->model()) {
44 if (!m_delegateModel) {
45 createDelegateModel();
46 }
47#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
48 m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
49#else
50 m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
51#endif
52 // Cleanup the existing items
53 for (int i = 0; i < m_columnVisibleItems.count(); ++i)
54 {
55 QList<ViewItem> &column = m_columnVisibleItems[i];
56 Q_FOREACH(const ViewItem &item, column)
57 releaseItem(item);
58 column.clear();
59 }
60 m_indexColumnMap.clear();
62 Q_EMIT modelChanged();
63 polish();
64 }
67QQmlComponent *VerticalJournal::delegate() const
69 return m_delegateModel ? m_delegateModel->delegate() : nullptr;
72void VerticalJournal::setDelegate(QQmlComponent *delegate)
74 if (delegate != this->delegate()) {
75 if (!m_delegateModel) {
76 createDelegateModel();
77 }
79 // Cleanup the existing items
80 for (int i = 0; i < m_columnVisibleItems.count(); ++i)
81 {
82 QList<ViewItem> &column = m_columnVisibleItems[i];
83 Q_FOREACH(const ViewItem &item, column)
84 releaseItem(item);
85 column.clear();
86 }
87 m_indexColumnMap.clear();
89 m_delegateModel->setDelegate(delegate);
91 Q_EMIT delegateChanged();
92 m_delegateValidated = false;
93 polish();
94 }
97qreal VerticalJournal::columnWidth() const
99 return m_columnWidth;
102void VerticalJournal::setColumnWidth(qreal columnWidth)
104 if (columnWidth != m_columnWidth) {
105 m_columnWidth = columnWidth;
106 Q_EMIT columnWidthChanged();
108 if (isComponentComplete()) {
109 Q_FOREACH(const auto &column, m_columnVisibleItems) {
110 Q_FOREACH(const ViewItem &item, column) {
111 item.m_item->setWidth(columnWidth);
112 }
113 }
114 relayout();
115 }
116 }
119qreal VerticalJournal::horizontalSpacing() const
121 return m_horizontalSpacing;
124void VerticalJournal::setHorizontalSpacing(qreal horizontalSpacing)
126 if (horizontalSpacing != m_horizontalSpacing) {
127 m_horizontalSpacing = horizontalSpacing;
128 Q_EMIT horizontalSpacingChanged();
130 if (isComponentComplete()) {
131 relayout();
132 }
133 }
136qreal VerticalJournal::verticalSpacing() const
138 return m_verticalSpacing;
141void VerticalJournal::setVerticalSpacing(qreal verticalSpacing)
143 if (verticalSpacing != m_verticalSpacing) {
144 m_verticalSpacing = verticalSpacing;
145 Q_EMIT verticalSpacingChanged();
147 if (isComponentComplete()) {
148 relayout();
149 }
150 }
153qreal VerticalJournal::delegateCreationBegin() const
155 return m_delegateCreationBegin;
158void VerticalJournal::setDelegateCreationBegin(qreal begin)
160 m_delegateCreationBeginValid = true;
161 if (m_delegateCreationBegin == begin)
162 return;
163 m_delegateCreationBegin = begin;
164 if (isComponentComplete()) {
165 polish();
166 }
167 emit delegateCreationBeginChanged();
170void VerticalJournal::resetDelegateCreationBegin()
172 m_delegateCreationBeginValid = false;
173 if (m_delegateCreationBegin == 0)
174 return;
175 m_delegateCreationBegin = 0;
176 if (isComponentComplete()) {
177 polish();
178 }
179 emit delegateCreationBeginChanged();
182qreal VerticalJournal::delegateCreationEnd() const
184 return m_delegateCreationEnd;
187void VerticalJournal::setDelegateCreationEnd(qreal end)
189 m_delegateCreationEndValid = true;
190 if (m_delegateCreationEnd == end)
191 return;
192 m_delegateCreationEnd = end;
193 if (isComponentComplete()) {
194 polish();
195 }
196 emit delegateCreationEndChanged();
199void VerticalJournal::resetDelegateCreationEnd()
201 m_delegateCreationEndValid = false;
202 if (m_delegateCreationEnd == 0)
203 return;
204 m_delegateCreationEnd = 0;
205 if (isComponentComplete()) {
206 polish();
207 }
208 emit delegateCreationEndChanged();
211void VerticalJournal::createDelegateModel()
213#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
214 m_delegateModel = new QQuickVisualDataModel(qmlContext(this), this);
215 connect(m_delegateModel, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(itemCreated(int,QQuickItem*)));
216#else
217 m_delegateModel = new QQmlDelegateModel(qmlContext(this), this);
218 connect(m_delegateModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(itemCreated(int,QObject*)));
219#endif
220 if (isComponentComplete())
221 m_delegateModel->componentComplete();
224void VerticalJournal::refill()
226 if (!isComponentComplete()) {
227 return;
228 }
230 const bool delegateRangesValid = m_delegateCreationBeginValid && m_delegateCreationEndValid;
231 const qreal from = delegateRangesValid ? m_delegateCreationBegin : 0;
232 const qreal to = delegateRangesValid ? m_delegateCreationEnd : from + height();
233 const qreal buffer = (to - from) * bufferRatio;
234 const qreal bufferFrom = from - buffer;
235 const qreal bufferTo = to + buffer;
237 bool added = addVisibleItems(from, to, false);
238 bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
239 added |= addVisibleItems(bufferFrom, bufferTo, true);
241 if (added || removed) {
242 m_implicitHeightDirty = true;
243 }
246void VerticalJournal::findBottomModelIndexToAdd(int *modelIndex, double *yPos)
248 *modelIndex = 0;
249 *yPos = INT_MAX;
251 Q_FOREACH(const auto &column, m_columnVisibleItems) {
252 if (!column.isEmpty()) {
253 const ViewItem &item = column.last();
254 *yPos = qMin(*yPos, item.y() + item.height() + m_verticalSpacing);
255 *modelIndex = qMax(*modelIndex, item.m_modelIndex + 1);
256 } else {
257 *yPos = 0;
258 }
259 }
262void VerticalJournal::findTopModelIndexToAdd(int *modelIndex, double *yPos)
264 *modelIndex = INT_MAX;
265 *yPos = INT_MIN;
266 int columnToAddTo = -1;
268 // Find the topmost free column
269 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
270 const auto &column = m_columnVisibleItems[i];
271 if (!column.isEmpty()) {
272 const ViewItem &item = column.first();
273 const auto itemTopPos = item.y() - m_verticalSpacing;
274 if (itemTopPos > *yPos) {
275 *yPos = itemTopPos;
276 *modelIndex = item.m_modelIndex - 1;
277 columnToAddTo = i;
278 }
279 }
280 }
282 if (*modelIndex > 0) {
283 Q_ASSERT(m_indexColumnMap.contains(*modelIndex));
284 while (m_indexColumnMap[*modelIndex] != columnToAddTo) {
285 // We found out that we have to add to columnToAddTo
286 // and thought that we had to add *modelIndex, but history tells
287 // it is not correct, so find up from *modelIndex until we found the index
288 // that has to end up in columnToAddTo
289 *modelIndex = *modelIndex - 1;
290 Q_ASSERT(m_indexColumnMap.contains(*modelIndex));
291 }
292 }
295bool VerticalJournal::addVisibleItems(qreal fillFrom, qreal fillTo, bool asynchronous)
297 if (!delegate())
298 return false;
300 if (m_delegateModel->count() == 0)
301 return false;
303 int modelIndex;
304 double yPos;
305 findBottomModelIndexToAdd(&modelIndex, &yPos);
306 bool changed = false;
307 while (modelIndex < m_delegateModel->count() && yPos <= fillTo) {
308 if (!createItem(modelIndex, asynchronous))
309 break;
311 changed = true;
312 findBottomModelIndexToAdd(&modelIndex, &yPos);
313 }
315 findTopModelIndexToAdd(&modelIndex, &yPos);
316 while (modelIndex >= 0 && yPos > fillFrom) {
317 if (!createItem(modelIndex, asynchronous))
318 break;
320 changed = true;
321 findTopModelIndexToAdd(&modelIndex, &yPos);
322 }
324 return changed;
327bool VerticalJournal::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo)
329 bool changed = false;
331 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
332 QList<ViewItem> &column = m_columnVisibleItems[i];
333 while (!column.isEmpty() && column.first().y() + column.first().height() < bufferFrom) {
334 releaseItem(column.takeFirst());
335 changed = true;
336 }
338 while (!column.isEmpty() && column.last().y() > bufferTo) {
339 releaseItem(column.takeLast());
340 changed = true;
341 }
342 }
344 return changed;
347QQuickItem *VerticalJournal::createItem(int modelIndex, bool asynchronous)
349 if (asynchronous && m_asyncRequestedIndex != -1)
350 return nullptr;
352 m_asyncRequestedIndex = -1;
353#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
354 QQuickItem *item = m_delegateModel->item(modelIndex, asynchronous);
355#else
356 QObject* object = m_delegateModel->object(modelIndex, asynchronous);
357 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
358#endif
359 if (!item) {
360#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
361 m_asyncRequestedIndex = modelIndex;
362#else
363 if (object) {
364 m_delegateModel->release(object);
365 if (!m_delegateValidated) {
366 m_delegateValidated = true;
367 QObject* delegateObj = delegate();
368 qmlInfo(delegateObj ? delegateObj : this) << "Delegate must be of Item type";
369 }
370 } else {
371 m_asyncRequestedIndex = modelIndex;
372 }
373#endif
374 return nullptr;
375 } else {
376 if (item->width() != m_columnWidth) {
377 qWarning() << "Item" << modelIndex << "width is not the one that the columnWidth mandates, resetting it";
378 item->setWidth(m_columnWidth);
379 }
380 positionItem(modelIndex, item);
381 return item;
382 }
385void VerticalJournal::positionItem(int modelIndex, QQuickItem *item)
387 // Check if we add it to the bottom of existing column items
388 qreal columnToAddY = !m_columnVisibleItems[0].isEmpty() ? m_columnVisibleItems[0].last().y() + m_columnVisibleItems[0].last().height() : 0;
389 int columnToAddTo = 0;
390 for (int i = 1; i < m_columnVisibleItems.count(); ++i) {
391 const qreal iY = !m_columnVisibleItems[i].isEmpty() ? m_columnVisibleItems[i].last().y() + m_columnVisibleItems[i].last().height() : 0;
392 if (iY < columnToAddY) {
393 columnToAddTo = i;
394 columnToAddY = iY;
395 }
396 }
397 if (m_columnVisibleItems[columnToAddTo].isEmpty() || m_columnVisibleItems[columnToAddTo].last().m_modelIndex < modelIndex) {
398 item->setX(columnToAddTo * m_columnWidth + m_horizontalSpacing * (columnToAddTo + 1));
399 item->setY(columnToAddY + m_verticalSpacing);
401 m_columnVisibleItems[columnToAddTo] << ViewItem(item, modelIndex);
402 m_indexColumnMap[modelIndex] = columnToAddTo;
403 } else {
404 Q_ASSERT(m_indexColumnMap.contains(modelIndex));
405 columnToAddTo = m_indexColumnMap[modelIndex];
406 columnToAddY = m_columnVisibleItems[columnToAddTo].first().y();
408 item->setX(columnToAddTo * m_columnWidth + m_horizontalSpacing * (columnToAddTo + 1));
409 item->setY(columnToAddY - m_verticalSpacing - item->height());
411 m_columnVisibleItems[columnToAddTo].prepend(ViewItem(item, modelIndex));
412 }
415void VerticalJournal::releaseItem(const ViewItem &item)
417#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
418 QQuickVisualModel::ReleaseFlags flags = m_delegateModel->release(item.m_item);
419 if (flags & QQuickVisualModel::Destroyed) {
420#else
421 QQmlDelegateModel::ReleaseFlags flags = m_delegateModel->release(item.m_item);
422 if (flags & QQmlDelegateModel::Destroyed) {
423#endif
424 item.m_item->setParentItem(nullptr);
425 }
428#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
429void VerticalJournal::itemCreated(int modelIndex, QQuickItem *item)
431#else
432void VerticalJournal::itemCreated(int modelIndex, QObject *object)
434 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
435 if (!item) {
436 qWarning() << "ListViewWithPageHeader::itemCreated got a non item for index" << modelIndex;
437 return;
438 }
439#endif
440 item->setParentItem(this);
441 if (modelIndex == m_asyncRequestedIndex) {
442 createItem(modelIndex, false);
443 m_implicitHeightDirty = true;
444 polish();
445 }
448void VerticalJournal::relayout()
450 m_needsRelayout = true;
451 polish();
454void VerticalJournal::onHeightChanged()
456 polish();
459void VerticalJournal::updatePolish()
461 if (!model())
462 return;
464 if (m_needsRelayout) {
465 QList<ViewItem> allItems;
466 Q_FOREACH(const auto &column, m_columnVisibleItems)
467 allItems << column;
469 qSort(allItems);
471 const int nColumns = qMax(1., floor((double)(width() - m_horizontalSpacing) / (m_columnWidth + m_horizontalSpacing)));
472 m_columnVisibleItems.resize(nColumns);
473 m_indexColumnMap.clear();
474 for (int i = 0; i < nColumns; ++i)
475 m_columnVisibleItems[i].clear();
477 // If the first of allItems doesn't contain index 0 we need to drop them
478 // all since we can't consistently relayout without the first item being there
480 if (!allItems.isEmpty()) {
481 if (allItems.first().m_modelIndex == 0) {
482 Q_FOREACH(const ViewItem &item, allItems)
483 positionItem(item.m_modelIndex, item.m_item);
484 } else {
485 Q_FOREACH(const ViewItem &item, allItems)
486 releaseItem(item);
487 }
488 }
490 m_needsRelayout = false;
491 }
493 refill();
495 const bool delegateRangesValid = m_delegateCreationBeginValid && m_delegateCreationEndValid;
496 const qreal from = delegateRangesValid ? m_delegateCreationBegin : 0;
497 const qreal to = delegateRangesValid ? m_delegateCreationEnd : from + height();
498 Q_FOREACH(const auto &column, m_columnVisibleItems) {
499 Q_FOREACH(const ViewItem &item, column) {
500 QQuickItemPrivate::get(item.m_item)->setCulled(item.y() + item.height() <= from || item.y() >= to);
501 }
502 }
504 if (m_implicitHeightDirty) {
505 m_implicitHeightDirty = false;
507 int lastModelIndex = -1;
508 qreal bottomMostY = 0;
509 Q_FOREACH(const auto &column, m_columnVisibleItems) {
510 if (!column.isEmpty()) {
511 const ViewItem &item = column.last();
512 lastModelIndex = qMax(lastModelIndex, item.m_modelIndex);
513 bottomMostY = qMax(bottomMostY, item.y() + item.height() + m_verticalSpacing);
514 }
515 }
516 if (lastModelIndex >= 0) {
517 const double averageHeight = bottomMostY / (lastModelIndex + 1);
518 setImplicitHeight(bottomMostY + averageHeight * (model()->rowCount() - lastModelIndex - 1));
519 } else {
520 setImplicitHeight(0);
521 }
522 }
525void VerticalJournal::componentComplete()
527 if (m_delegateModel)
528 m_delegateModel->componentComplete();
530 QQuickItem::componentComplete();
532 const int nColumns = ceil((double)(width() - m_horizontalSpacing) / (m_columnWidth + m_horizontalSpacing));
533 m_columnVisibleItems.resize(nColumns);
535 polish();