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 * Some documentation on how this thing works:
20 * A vertical journal is a view that creates delegates
21 * based on a model and layouts them in columns following
22 * a top-left most position rule.
24 * The number of columns is calculated using the width of
25 * the view itself, the columnWidth (i.e. the width of each individual delegate)
26 * and the horizontalSpacing between columns.
28 * All delegates are forced to columnWidth if they don't have it.
30 * The first nColumns items are layouted at row 0 from column 0
31 * to column nColumns-1 in order. After that every new item
32 * is positioned in the column which provides the topmost
33 * position as possible. If more than one column tie in providing
34 * the topmost position the leftmost column will be used.
36 * The implementation is centered around m_columnVisibleItems
37 * that holds a vector of lists. There's a list for each of the
38 * columns the view has. In the list the items of the column are
39 * ordered as they appear topdown in the view. m_indexColumnMap is
40 * used when re-building the list up since given a position
41 * in the middle of the list and the need to create the previous does
42 * not give us enough information to know in which column we have
43 * to position the item so that when we reach the item the view is
44 * correctly layouted at 0 for all the columns
46#include "verticaljournal.h"
48#include <qqmlengine.h>
49#pragma GCC diagnostic push
50#pragma GCC diagnostic ignored "-pedantic"
51#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
52#include <private/qquickvisualdatamodel_p.h>
54#include <private/qqmldelegatemodel_p.h>
57#include <private/qquickitem_p.h>
58#pragma GCC diagnostic pop
60static const qreal bufferRatio = 0.5;
62VerticalJournal::VerticalJournal()
63 : m_delegateModel(nullptr)
64 , m_asyncRequestedIndex(-1)
66 , m_horizontalSpacing(0)
67 , m_verticalSpacing(0)
68 , m_delegateCreationBegin(0)
69 , m_delegateCreationEnd(0)
70 , m_delegateCreationBeginValid(false)
71 , m_delegateCreationEndValid(false)
72 , m_needsRelayout(false)
73 , m_delegateValidated(false)
74 , m_implicitHeightDirty(false)
76 connect(this, SIGNAL(widthChanged()), this, SLOT(relayout()));
77 connect(this, SIGNAL(heightChanged()), this, SLOT(onHeightChanged()));
80QAbstractItemModel *VerticalJournal::model() const
82 return m_delegateModel ? m_delegateModel->model().value<QAbstractItemModel *>() : nullptr;
85void VerticalJournal::setModel(QAbstractItemModel *model)
87 if (model != this->model()) {
88 if (!m_delegateModel) {
89 createDelegateModel();
91#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
92 m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
94 m_delegateModel->setModel(QVariant::fromValue<QAbstractItemModel *>(model));
96 // Cleanup the existing items
97 for (int i = 0; i < m_columnVisibleItems.count(); ++i)
99 QList<ViewItem> &column = m_columnVisibleItems[i];
100 Q_FOREACH(const ViewItem &item, column)
104 m_indexColumnMap.clear();
106 Q_EMIT modelChanged();
111QQmlComponent *VerticalJournal::delegate() const
113 return m_delegateModel ? m_delegateModel->delegate() : nullptr;
116void VerticalJournal::setDelegate(QQmlComponent *delegate)
118 if (delegate != this->delegate()) {
119 if (!m_delegateModel) {
120 createDelegateModel();
123 // Cleanup the existing items
124 for (int i = 0; i < m_columnVisibleItems.count(); ++i)
126 QList<ViewItem> &column = m_columnVisibleItems[i];
127 Q_FOREACH(const ViewItem &item, column)
131 m_indexColumnMap.clear();
133 m_delegateModel->setDelegate(delegate);
135 Q_EMIT delegateChanged();
136 m_delegateValidated = false;
141qreal VerticalJournal::columnWidth() const
143 return m_columnWidth;
146void VerticalJournal::setColumnWidth(qreal columnWidth)
148 if (columnWidth != m_columnWidth) {
149 m_columnWidth = columnWidth;
150 Q_EMIT columnWidthChanged();
152 if (isComponentComplete()) {
153 Q_FOREACH(const auto &column, m_columnVisibleItems) {
154 Q_FOREACH(const ViewItem &item, column) {
155 item.m_item->setWidth(columnWidth);
163qreal VerticalJournal::horizontalSpacing() const
165 return m_horizontalSpacing;
168void VerticalJournal::setHorizontalSpacing(qreal horizontalSpacing)
170 if (horizontalSpacing != m_horizontalSpacing) {
171 m_horizontalSpacing = horizontalSpacing;
172 Q_EMIT horizontalSpacingChanged();
174 if (isComponentComplete()) {
180qreal VerticalJournal::verticalSpacing() const
182 return m_verticalSpacing;
185void VerticalJournal::setVerticalSpacing(qreal verticalSpacing)
187 if (verticalSpacing != m_verticalSpacing) {
188 m_verticalSpacing = verticalSpacing;
189 Q_EMIT verticalSpacingChanged();
191 if (isComponentComplete()) {
197qreal VerticalJournal::delegateCreationBegin() const
199 return m_delegateCreationBegin;
202void VerticalJournal::setDelegateCreationBegin(qreal begin)
204 m_delegateCreationBeginValid = true;
205 if (m_delegateCreationBegin == begin)
207 m_delegateCreationBegin = begin;
208 if (isComponentComplete()) {
211 emit delegateCreationBeginChanged();
214void VerticalJournal::resetDelegateCreationBegin()
216 m_delegateCreationBeginValid = false;
217 if (m_delegateCreationBegin == 0)
219 m_delegateCreationBegin = 0;
220 if (isComponentComplete()) {
223 emit delegateCreationBeginChanged();
226qreal VerticalJournal::delegateCreationEnd() const
228 return m_delegateCreationEnd;
231void VerticalJournal::setDelegateCreationEnd(qreal end)
233 m_delegateCreationEndValid = true;
234 if (m_delegateCreationEnd == end)
236 m_delegateCreationEnd = end;
237 if (isComponentComplete()) {
240 emit delegateCreationEndChanged();
243void VerticalJournal::resetDelegateCreationEnd()
245 m_delegateCreationEndValid = false;
246 if (m_delegateCreationEnd == 0)
248 m_delegateCreationEnd = 0;
249 if (isComponentComplete()) {
252 emit delegateCreationEndChanged();
255void VerticalJournal::createDelegateModel()
257#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
258 m_delegateModel = new QQuickVisualDataModel(qmlContext(this), this);
259 connect(m_delegateModel, SIGNAL(createdItem(int,QQuickItem*)), this, SLOT(itemCreated(int,QQuickItem*)));
261 m_delegateModel = new QQmlDelegateModel(qmlContext(this), this);
262 connect(m_delegateModel, SIGNAL(createdItem(int,QObject*)), this, SLOT(itemCreated(int,QObject*)));
264 if (isComponentComplete())
265 m_delegateModel->componentComplete();
268void VerticalJournal::refill()
270 if (!isComponentComplete()) {
274 const bool delegateRangesValid = m_delegateCreationBeginValid && m_delegateCreationEndValid;
275 const qreal from = delegateRangesValid ? m_delegateCreationBegin : 0;
276 const qreal to = delegateRangesValid ? m_delegateCreationEnd : from + height();
277 const qreal buffer = (to - from) * bufferRatio;
278 const qreal bufferFrom = from - buffer;
279 const qreal bufferTo = to + buffer;
281 bool added = addVisibleItems(from, to, false);
282 bool removed = removeNonVisibleItems(bufferFrom, bufferTo);
283 added |= addVisibleItems(bufferFrom, bufferTo, true);
285 if (added || removed) {
286 m_implicitHeightDirty = true;
290void VerticalJournal::findBottomModelIndexToAdd(int *modelIndex, double *yPos)
295 Q_FOREACH(const auto &column, m_columnVisibleItems) {
296 if (!column.isEmpty()) {
297 const ViewItem &item = column.last();
298 *yPos = qMin(*yPos, static_cast<double>(item.y() + item.height() + m_verticalSpacing));
299 *modelIndex = qMax(*modelIndex, item.m_modelIndex + 1);
306void VerticalJournal::findTopModelIndexToAdd(int *modelIndex, double *yPos)
308 *modelIndex = INT_MAX;
310 int columnToAddTo = -1;
312 // Find the topmost free column
313 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
314 const auto &column = m_columnVisibleItems[i];
315 if (!column.isEmpty()) {
316 const ViewItem &item = column.first();
317 const auto itemTopPos = item.y() - m_verticalSpacing;
318 if (itemTopPos > *yPos) {
320 *modelIndex = item.m_modelIndex - 1;
326 if (*modelIndex > 0) {
327 Q_ASSERT(m_indexColumnMap.contains(*modelIndex));
328 while (m_indexColumnMap[*modelIndex] != columnToAddTo) {
329 // We found out that we have to add to columnToAddTo
330 // and thought that we had to add *modelIndex, but history tells
331 // it is not correct, so find up from *modelIndex until we found the index
332 // that has to end up in columnToAddTo
333 *modelIndex = *modelIndex - 1;
334 Q_ASSERT(m_indexColumnMap.contains(*modelIndex));
339bool VerticalJournal::addVisibleItems(qreal fillFrom, qreal fillTo, bool asynchronous)
344 if (m_delegateModel->count() == 0)
349 findBottomModelIndexToAdd(&modelIndex, &yPos);
350 bool changed = false;
351 while (modelIndex < m_delegateModel->count() && yPos <= fillTo) {
352 if (!createItem(modelIndex, asynchronous))
356 findBottomModelIndexToAdd(&modelIndex, &yPos);
359 findTopModelIndexToAdd(&modelIndex, &yPos);
360 while (modelIndex >= 0 && yPos > fillFrom) {
361 if (!createItem(modelIndex, asynchronous))
365 findTopModelIndexToAdd(&modelIndex, &yPos);
371bool VerticalJournal::removeNonVisibleItems(qreal bufferFrom, qreal bufferTo)
373 bool changed = false;
375 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
376 QList<ViewItem> &column = m_columnVisibleItems[i];
377 while (!column.isEmpty() && column.first().y() + column.first().height() < bufferFrom) {
378 releaseItem(column.takeFirst());
382 while (!column.isEmpty() && column.last().y() > bufferTo) {
383 releaseItem(column.takeLast());
391QQuickItem *VerticalJournal::createItem(int modelIndex, bool asynchronous)
393 if (asynchronous && m_asyncRequestedIndex != -1)
396 m_asyncRequestedIndex = -1;
397#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
398 QQuickItem *item = m_delegateModel->item(modelIndex, asynchronous);
400 QObject* object = m_delegateModel->object(modelIndex, asynchronous);
401 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
404#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
405 m_asyncRequestedIndex = modelIndex;
408 m_delegateModel->release(object);
409 if (!m_delegateValidated) {
410 m_delegateValidated = true;
411 QObject* delegateObj = delegate();
412 qmlInfo(delegateObj ? delegateObj : this) << "Delegate must be of Item type";
415 m_asyncRequestedIndex = modelIndex;
420 if (item->width() != m_columnWidth) {
421 qWarning() << "Item" << modelIndex << "width is not the one that the columnWidth mandates, resetting it";
422 item->setWidth(m_columnWidth);
424 positionItem(modelIndex, item);
429void VerticalJournal::positionItem(int modelIndex, QQuickItem *item)
431 // Check if we add it to the bottom of existing column items
432 qreal columnToAddY = !m_columnVisibleItems[0].isEmpty() ? m_columnVisibleItems[0].last().y() + m_columnVisibleItems[0].last().height() : 0;
433 int columnToAddTo = 0;
434 for (int i = 1; i < m_columnVisibleItems.count(); ++i) {
435 const qreal iY = !m_columnVisibleItems[i].isEmpty() ? m_columnVisibleItems[i].last().y() + m_columnVisibleItems[i].last().height() : 0;
436 if (iY < columnToAddY) {
441 if (m_columnVisibleItems[columnToAddTo].isEmpty() || m_columnVisibleItems[columnToAddTo].last().m_modelIndex < modelIndex) {
442 item->setX(columnToAddTo * m_columnWidth + m_horizontalSpacing * (columnToAddTo + 1));
443 item->setY(columnToAddY + m_verticalSpacing);
445 m_columnVisibleItems[columnToAddTo] << ViewItem(item, modelIndex);
446 m_indexColumnMap[modelIndex] = columnToAddTo;
448 Q_ASSERT(m_indexColumnMap.contains(modelIndex));
449 columnToAddTo = m_indexColumnMap[modelIndex];
450 columnToAddY = m_columnVisibleItems[columnToAddTo].first().y();
452 item->setX(columnToAddTo * m_columnWidth + m_horizontalSpacing * (columnToAddTo + 1));
453 item->setY(columnToAddY - m_verticalSpacing - item->height());
455 m_columnVisibleItems[columnToAddTo].prepend(ViewItem(item, modelIndex));
459void VerticalJournal::releaseItem(const ViewItem &item)
461#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
462 QQuickVisualModel::ReleaseFlags flags = m_delegateModel->release(item.m_item);
463 if (flags & QQuickVisualModel::Destroyed) {
465 QQmlDelegateModel::ReleaseFlags flags = m_delegateModel->release(item.m_item);
466 if (flags & QQmlDelegateModel::Destroyed) {
468 item.m_item->setParentItem(nullptr);
472#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
473void VerticalJournal::itemCreated(int modelIndex, QQuickItem *item)
476void VerticalJournal::itemCreated(int modelIndex, QObject *object)
478 QQuickItem *item = qmlobject_cast<QQuickItem*>(object);
480 qWarning() << "ListViewWithPageHeader::itemCreated got a non item for index" << modelIndex;
484 item->setParentItem(this);
485 if (modelIndex == m_asyncRequestedIndex) {
486 createItem(modelIndex, false);
487 m_implicitHeightDirty = true;
492void VerticalJournal::relayout()
494 m_needsRelayout = true;
498void VerticalJournal::onHeightChanged()
503void VerticalJournal::updatePolish()
508 if (m_needsRelayout) {
509 QList<ViewItem> allItems;
510 Q_FOREACH(const auto &column, m_columnVisibleItems)
515 const int nColumns = qMax(1., floor((double)(width() - m_horizontalSpacing) / (m_columnWidth + m_horizontalSpacing)));
516 m_columnVisibleItems.resize(nColumns);
517 m_indexColumnMap.clear();
518 for (int i = 0; i < nColumns; ++i)
519 m_columnVisibleItems[i].clear();
521 // If the first of allItems doesn't contain index 0 we need to drop them
522 // all since we can't consistently relayout without the first item being there
524 if (!allItems.isEmpty()) {
525 if (allItems.first().m_modelIndex == 0) {
526 Q_FOREACH(const ViewItem &item, allItems)
527 positionItem(item.m_modelIndex, item.m_item);
529 Q_FOREACH(const ViewItem &item, allItems)
534 m_needsRelayout = false;
539 const bool delegateRangesValid = m_delegateCreationBeginValid && m_delegateCreationEndValid;
540 const qreal from = delegateRangesValid ? m_delegateCreationBegin : 0;
541 const qreal to = delegateRangesValid ? m_delegateCreationEnd : from + height();
542 Q_FOREACH(const auto &column, m_columnVisibleItems) {
543 Q_FOREACH(const ViewItem &item, column) {
544 QQuickItemPrivate::get(item.m_item)->setCulled(item.y() + item.height() <= from || item.y() >= to);
548 if (m_implicitHeightDirty) {
549 m_implicitHeightDirty = false;
551 int lastModelIndex = -1;
552 qreal bottomMostY = 0;
553 Q_FOREACH(const auto &column, m_columnVisibleItems) {
554 if (!column.isEmpty()) {
555 const ViewItem &item = column.last();
556 lastModelIndex = qMax(lastModelIndex, item.m_modelIndex);
557 bottomMostY = qMax(bottomMostY, item.y() + item.height() + m_verticalSpacing);
560 if (lastModelIndex >= 0) {
561 const double averageHeight = bottomMostY / (lastModelIndex + 1);
562 setImplicitHeight(bottomMostY + averageHeight * (model()->rowCount() - lastModelIndex - 1));
564 setImplicitHeight(0);
569void VerticalJournal::componentComplete()
572 m_delegateModel->componentComplete();
574 QQuickItem::componentComplete();
576 const int nColumns = ceil((double)(width() - m_horizontalSpacing) / (m_columnWidth + m_horizontalSpacing));
577 m_columnVisibleItems.resize(nColumns);
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