2 * Copyright (C) 2013 Canonical, Ltd.
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.
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.
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/>.
18 * The implementation is centered around m_columnVisibleItems
19 * that holds a vector of lists. There's a list for each of the
20 * columns the view has. In the list the items of the column are
21 * ordered as they appear topdown in the view. m_indexColumnMap is
22 * used when re-building the list up since given a position
23 * in the middle of the list and the need to create the previous does
24 * not give us enough information to know in which column we have
25 * to position the item so that when we reach the item the view is
26 * correctly layouted at 0 for all the columns
28#include "verticaljournal.h"
30#include <qqmlengine.h>
31#pragma GCC diagnostic push
32#pragma GCC diagnostic ignored "-pedantic"
33#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
34#include <private/qquickvisualdatamodel_p.h>
36#include <private/qqmldelegatemodel_p.h>
39#include <private/qquickitem_p.h>
40#pragma GCC diagnostic pop
42static const qreal bufferRatio = 0.5;
44VerticalJournal::VerticalJournal()
45 : m_delegateModel(nullptr)
46 , m_asyncRequestedIndex(-1)
50 , m_delegateCreationBegin(0)
51 , m_delegateCreationEnd(0)
52 , m_delegateCreationBeginValid(false)
53 , m_delegateCreationEndValid(false)
54 , m_needsRelayout(false)
55 , m_delegateValidated(false)
56 , m_implicitHeightDirty(false)
58 connect(this, SIGNAL(widthChanged()), this, SLOT(relayout()));
59 connect(this, SIGNAL(heightChanged()), this, SLOT(onHeightChanged()));
62QAbstractItemModel *VerticalJournal::model() const
64 return m_delegateModel ? m_delegateModel->model().value<QAbstractItemModel *>() : nullptr;
67void VerticalJournal::setModel(QAbstractItemModel *model)
69 if (model != this->model()) {
70 if (!m_delegateModel) {
71 createDelegateModel();
73#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
74 disconnect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
76 m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
77 connect(m_delegateModel, SIGNAL(modelUpdated(QQuickChangeSet,bool)), this, SLOT(onModelUpdated(QQuickChangeSet,bool)));
79 disconnect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool)));
81 m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
82 connect(m_delegateModel, SIGNAL(modelUpdated(QQmlChangeSet,bool)), this, SLOT(onModelUpdated(QQmlChangeSet,bool)));
85 cleanupExistingItems();
87 Q_EMIT modelChanged();
92QQmlComponent *VerticalJournal::delegate() const
94 return m_delegateModel ? m_delegateModel->delegate() : nullptr;
97void VerticalJournal::setDelegate(QQmlComponent *delegate)
99 if (delegate != this->delegate()) {
100 if (!m_delegateModel) {
101 createDelegateModel();
104 cleanupExistingItems();
106 m_delegateModel->setDelegate(delegate);
108 Q_EMIT delegateChanged();
109 m_delegateValidated = false;
114qreal VerticalJournal::columnWidth() const
116 return m_columnWidth;
119void VerticalJournal::setColumnWidth(qreal columnWidth)
121 if (columnWidth != m_columnWidth) {
122 m_columnWidth = columnWidth;
123 Q_EMIT columnWidthChanged();
125 if (isComponentComplete()) {
126 Q_FOREACH(const auto &column, m_columnVisibleItems) {
127 Q_FOREACH(const ViewItem &item, column) {
128 item.m_item->setWidth(columnWidth);
136qreal VerticalJournal::columnSpacing() const
138 return m_columnSpacing;
141void VerticalJournal::setColumnSpacing(qreal columnSpacing)
143 if (columnSpacing != m_columnSpacing) {
144 m_columnSpacing = columnSpacing;
145 Q_EMIT columnSpacingChanged();
147 if (isComponentComplete()) {
153qreal VerticalJournal::rowSpacing() const
158void VerticalJournal::setRowSpacing(qreal rowSpacing)
160 if (rowSpacing != m_rowSpacing) {
161 m_rowSpacing = rowSpacing;
162 Q_EMIT rowSpacingChanged();
164 if (isComponentComplete()) {
170qreal VerticalJournal::delegateCreationBegin() const
172 return m_delegateCreationBegin;
175void VerticalJournal::setDelegateCreationBegin(qreal begin)
177 m_delegateCreationBeginValid = true;
178 if (m_delegateCreationBegin == begin)
180 m_delegateCreationBegin = begin;
181 if (isComponentComplete()) {
184 emit delegateCreationBeginChanged();
187void VerticalJournal::resetDelegateCreationBegin()
189 m_delegateCreationBeginValid = false;
190 if (m_delegateCreationBegin == 0)
192 m_delegateCreationBegin = 0;
193 if (isComponentComplete()) {
196 emit delegateCreationBeginChanged();
199qreal VerticalJournal::delegateCreationEnd() const
201 return m_delegateCreationEnd;
204void VerticalJournal::setDelegateCreationEnd(qreal end)
206 m_delegateCreationEndValid = true;
207 if (m_delegateCreationEnd == end)
209 m_delegateCreationEnd = end;
210 if (isComponentComplete()) {
213 emit delegateCreationEndChanged();
216void VerticalJournal::resetDelegateCreationEnd()
218 m_delegateCreationEndValid = false;
219 if (m_delegateCreationEnd == 0)
221 m_delegateCreationEnd = 0;
222 if (isComponentComplete()) {
225 emit delegateCreationEndChanged();
228void VerticalJournal::createDelegateModel()
230#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
231 m_delegateModel = new QQuickVisualDataModel(qmlContext(this), this);
232 connect(m_delegateModel, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(itemCreated(int,QQuickItem*)));
234 m_delegateModel = new QQmlDelegateModel(qmlContext(this), this);
235 connect(m_delegateModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(itemCreated(int,QObject*)));
237 if (isComponentComplete())
238 m_delegateModel->componentComplete();
241void VerticalJournal::refill()
243 if (!isComponentComplete()) {
247 const bool delegateRangesValid = m_delegateCreationBeginValid && m_delegateCreationEndValid;
248 const qreal from = delegateRangesValid ? m_delegateCreationBegin : 0;
249 const qreal to = delegateRangesValid ? m_delegateCreationEnd : from + height();
250 const qreal buffer = (to - from) * bufferRatio;
251 const qreal bufferFrom = from - buffer;
252 const qreal bufferTo = to + buffer;
254 bool added = addVisibleItems(from, to, false);
255 bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
256 added |= addVisibleItems(bufferFrom, bufferTo, true);
258 if (added || removed) {
259 m_implicitHeightDirty = true;
263void VerticalJournal::findBottomModelIndexToAdd(int *modelIndex, qreal *yPos)
266 *yPos = std::numeric_limits<qreal>::max();
268 Q_FOREACH(const auto &column, m_columnVisibleItems) {
269 if (!column.isEmpty()) {
270 const ViewItem &item = column.last();
271 *yPos = qMin(*yPos, item.y() + item.height() + m_rowSpacing);
272 *modelIndex = qMax(*modelIndex, item.m_modelIndex + 1);
279void VerticalJournal::findTopModelIndexToAdd(int *modelIndex, qreal *yPos)
281 *modelIndex = INT_MAX;
282 *yPos = std::numeric_limits<qreal>::lowest();
283 int columnToAddTo = -1;
285 // Find the topmost free column
286 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
287 const auto &column = m_columnVisibleItems[i];
288 if (!column.isEmpty()) {
289 const ViewItem &item = column.first();
290 const auto itemTopPos = item.y() - m_rowSpacing;
291 if (itemTopPos > *yPos) {
293 *modelIndex = item.m_modelIndex - 1;
299 if (*modelIndex > 0) {
300 Q_ASSERT(m_indexColumnMap.contains(*modelIndex));
301 while (m_indexColumnMap[*modelIndex] != columnToAddTo) {
302 // We found out that we have to add to columnToAddTo
303 // and thought that we had to add *modelIndex, but history tells
304 // it is not correct, so find up from *modelIndex until we found the index
305 // that has to end up in columnToAddTo
306 *modelIndex = *modelIndex - 1;
307 Q_ASSERT(m_indexColumnMap.contains(*modelIndex));
312bool VerticalJournal::addVisibleItems(qreal fillFrom, qreal fillTo, bool asynchronous)
317 if (m_delegateModel->count() == 0)
322 findBottomModelIndexToAdd(&modelIndex, &yPos);
323 bool changed = false;
324 while (modelIndex < m_delegateModel->count() && yPos <= fillTo) {
325 if (!createItem(modelIndex, asynchronous))
329 findBottomModelIndexToAdd(&modelIndex, &yPos);
332 findTopModelIndexToAdd(&modelIndex, &yPos);
333 while (modelIndex >= 0 && yPos > fillFrom) {
334 if (!createItem(modelIndex, asynchronous))
338 findTopModelIndexToAdd(&modelIndex, &yPos);
344bool VerticalJournal::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo)
346 bool changed = false;
348 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
349 QList<ViewItem> &column = m_columnVisibleItems[i];
350 while (!column.isEmpty() && column.first().y() + column.first().height() < bufferFrom) {
351 releaseItem(column.takeFirst());
355 while (!column.isEmpty() && column.last().y() > bufferTo) {
356 releaseItem(column.takeLast());
364QQuickItem *VerticalJournal::createItem(int modelIndex, bool asynchronous)
366 if (asynchronous && m_asyncRequestedIndex != -1)
369 m_asyncRequestedIndex = -1;
370#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
371 QQuickItem *item = m_delegateModel->item(modelIndex, asynchronous);
373 QObject* object = m_delegateModel->object(modelIndex, asynchronous);
374 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
377#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
378 m_asyncRequestedIndex = modelIndex;
381 m_delegateModel->release(object);
382 if (!m_delegateValidated) {
383 m_delegateValidated = true;
384 QObject* delegateObj = delegate();
385 qmlInfo(delegateObj ? delegateObj : this) << "Delegate must be of Item type";
388 m_asyncRequestedIndex = modelIndex;
393 if (item->width() != m_columnWidth) {
394 qWarning() << "Item" << modelIndex << "width is not the one that the columnWidth mandates, resetting it";
395 item->setWidth(m_columnWidth);
397 positionItem(modelIndex, item);
402void VerticalJournal::positionItem(int modelIndex, QQuickItem *item)
404 // Check if we add it to the bottom of existing column items
405 const QList<ViewItem> &firstColumn = m_columnVisibleItems[0];
406 qreal columnToAddY = !firstColumn.isEmpty() ? firstColumn.last().y() + firstColumn.last().height() : -m_rowSpacing;
407 int columnToAddTo = 0;
408 for (int i = 1; i < m_columnVisibleItems.count(); ++i) {
409 const QList<ViewItem> &column = m_columnVisibleItems[i];
410 const qreal iY = !column.isEmpty() ? column.last().y() + column.last().height() : -m_rowSpacing;
411 if (iY < columnToAddY) {
417 const QList<ViewItem> &columnToAdd = m_columnVisibleItems[columnToAddTo];
418 if (columnToAdd.isEmpty() || columnToAdd.last().m_modelIndex < modelIndex) {
419 item->setX(columnToAddTo * (m_columnWidth + m_columnSpacing));
420 item->setY(columnToAddY + m_rowSpacing);
422 m_columnVisibleItems[columnToAddTo] << ViewItem(item, modelIndex);
423 m_indexColumnMap[modelIndex] = columnToAddTo;
425 Q_ASSERT(m_indexColumnMap.contains(modelIndex));
426 columnToAddTo = m_indexColumnMap[modelIndex];
427 columnToAddY = m_columnVisibleItems[columnToAddTo].first().y();
429 item->setX(columnToAddTo * (m_columnWidth + m_columnSpacing));
430 item->setY(columnToAddY - m_rowSpacing - item->height());
432 m_columnVisibleItems[columnToAddTo].prepend(ViewItem(item, modelIndex));
436void VerticalJournal::cleanupExistingItems()
438 // Cleanup the existing items
439 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
440 QList<ViewItem> &column = m_columnVisibleItems[i];
441 Q_FOREACH(const ViewItem &item, column)
445 m_indexColumnMap.clear();
448void VerticalJournal::releaseItem(const ViewItem &item)
450#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
451 QQuickVisualModel::ReleaseFlags flags = m_delegateModel->release(item.m_item);
452 if (flags & QQuickVisualModel::Destroyed) {
454 QQmlDelegateModel::ReleaseFlags flags = m_delegateModel->release(item.m_item);
455 if (flags & QQmlDelegateModel::Destroyed) {
457 item.m_item->setParentItem(nullptr);
461void VerticalJournal::calculateImplicitHeight()
463 m_implicitHeightDirty = false;
465 int lastModelIndex = -1;
466 qreal bottomMostY = 0;
467 Q_FOREACH(const auto &column, m_columnVisibleItems) {
468 if (!column.isEmpty()) {
469 const ViewItem &item = column.last();
470 lastModelIndex = qMax(lastModelIndex, item.m_modelIndex);
471 bottomMostY = qMax(bottomMostY, item.y() + item.height());
474 if (lastModelIndex >= 0) {
475 const double averageHeight = bottomMostY / (lastModelIndex + 1);
476 setImplicitHeight(bottomMostY + averageHeight * (model()->rowCount() - lastModelIndex - 1));
478 setImplicitHeight(0);
482#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
483void VerticalJournal::itemCreated(int modelIndex, QQuickItem *item)
486void VerticalJournal::itemCreated(int modelIndex, QObject *object)
488 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
490 qWarning() << "VerticalJournal::itemCreated got a non item for index" << modelIndex;
494 item->setParentItem(this);
495 if (modelIndex == m_asyncRequestedIndex) {
496 createItem(modelIndex, false);
497 m_implicitHeightDirty = true;
502#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
503void VerticalJournal::onModelUpdated(const QQuickChangeSet &changeSet, bool reset)
505void VerticalJournal::onModelUpdated(const QQmlChangeSet &changeSet, bool reset)
509 cleanupExistingItems();
511#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
512 Q_FOREACH(const QQuickChangeSet::Remove &remove, changeSet.removes()) {
514 Q_FOREACH(const QQmlChangeSet::Remove &remove, changeSet.removes()) {
516 for (int i = remove.count - 1; i >= 0; --i) {
517 const int indexToRemove = remove.index + i;
518 // Since we only support removing from the end, indexToRemove
519 // must refer to the last item of one of the columns.
521 for (int i = 0; !found && i < m_columnVisibleItems.count(); ++i) {
522 QList<ViewItem> &column = m_columnVisibleItems[i];
523 if (!column.isEmpty() && column.last().m_modelIndex == indexToRemove) {
524 releaseItem(column.takeLast());
536void VerticalJournal::relayout()
538 m_needsRelayout = true;
542void VerticalJournal::onHeightChanged()
547void VerticalJournal::updatePolish()
552 if (m_needsRelayout) {
553 m_implicitHeightDirty = true;
555 QList<ViewItem> allItems;
556 Q_FOREACH(const auto &column, m_columnVisibleItems)
561 const int nColumns = qMax(1., floor((double)(width() + m_columnSpacing) / (m_columnWidth + m_columnSpacing)));
562 m_columnVisibleItems.resize(nColumns);
563 m_indexColumnMap.clear();
564 for (int i = 0; i < nColumns; ++i)
565 m_columnVisibleItems[i].clear();
567 // If the first of allItems doesn't contain index 0 we need to drop them
568 // all since we can't consistently relayout without the first item being there
570 if (!allItems.isEmpty()) {
571 if (allItems.first().m_modelIndex == 0) {
572 Q_FOREACH(const ViewItem &item, allItems)
573 positionItem(item.m_modelIndex, item.m_item);
575 Q_FOREACH(const ViewItem &item, allItems)
580 m_needsRelayout = false;
585 const bool delegateRangesValid = m_delegateCreationBeginValid && m_delegateCreationEndValid;
586 const qreal from = delegateRangesValid ? m_delegateCreationBegin : 0;
587 const qreal to = delegateRangesValid ? m_delegateCreationEnd : from + height();
588 Q_FOREACH(const auto &column, m_columnVisibleItems) {
589 Q_FOREACH(const ViewItem &item, column) {
590 QQuickItemPrivate::get(item.m_item)->setCulled(item.y() + item.height() <= from || item.y() >= to);
594 if (m_implicitHeightDirty) {
595 calculateImplicitHeight();
599void VerticalJournal::componentComplete()
602 m_delegateModel->componentComplete();
604 QQuickItem::componentComplete();
606 m_needsRelayout = true;
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list