on a57ff95 help sign in
unity8/plugins/DashViews/verticaljournal.cpp
280 lines 10 kB history raw
1/*
2 * Copyright (C) 2013 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 */
17/*
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
27 */
28#include "verticaljournal.h"
30#pragma GCC diagnostic push
31#pragma GCC diagnostic ignored "-pedantic"
32#include <private/qquickitem_p.h>
33#pragma GCC diagnostic pop
35static const qreal bufferRatio = 0.5;
37VerticalJournal::VerticalJournal()
38 : m_columnWidth(0)
42qreal VerticalJournal::columnWidth() const
44 return m_columnWidth;
47void VerticalJournal::setColumnWidth(qreal columnWidth)
49 if (columnWidth != m_columnWidth) {
50 m_columnWidth = columnWidth;
51 Q_EMIT columnWidthChanged();
53 if (isComponentComplete()) {
54 Q_FOREACH(const auto &column, m_columnVisibleItems) {
55 Q_FOREACH(const ViewItem &item, column) {
56 item.m_item->setWidth(columnWidth);
57 }
58 }
59 relayout();
60 }
61 }
64void VerticalJournal::findBottomModelIndexToAdd(int *modelIndex, qreal *yPos)
66 *modelIndex = 0;
67 *yPos = std::numeric_limits<qreal>::max();
69 Q_FOREACH(const auto &column, m_columnVisibleItems) {
70 if (!column.isEmpty()) {
71 const ViewItem &item = column.last();
72 *yPos = qMin(*yPos, item.y() + item.height() + rowSpacing());
73 *modelIndex = qMax(*modelIndex, item.m_modelIndex + 1);
74 } else {
75 *yPos = 0;
76 }
77 }
80void VerticalJournal::findTopModelIndexToAdd(int *modelIndex, qreal *yPos)
82 *modelIndex = INT_MAX;
83 *yPos = std::numeric_limits<qreal>::lowest();
84 int columnToAddTo = -1;
86 // Find the topmost free column
87 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
88 const auto &column = m_columnVisibleItems[i];
89 if (!column.isEmpty()) {
90 const ViewItem &item = column.first();
91 const auto itemTopPos = item.y() - rowSpacing();
92 if (itemTopPos > *yPos) {
93 *yPos = itemTopPos;
94 *modelIndex = item.m_modelIndex - 1;
95 columnToAddTo = i;
96 }
97 }
98 }
100 if (*modelIndex > 0) {
101 Q_ASSERT(m_indexColumnMap.contains(*modelIndex));
102 while (m_indexColumnMap[*modelIndex] != columnToAddTo) {
103 // We found out that we have to add to columnToAddTo
104 // and thought that we had to add *modelIndex, but history tells
105 // it is not correct, so find up from *modelIndex until we found the index
106 // that has to end up in columnToAddTo
107 *modelIndex = *modelIndex - 1;
108 Q_ASSERT(m_indexColumnMap.contains(*modelIndex));
109 }
110 }
113bool VerticalJournal::removeNonVisibleItems(qreal bufferFromY, qreal bufferToY)
115 bool changed = false;
117 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
118 QList<ViewItem> &column = m_columnVisibleItems[i];
119 while (!column.isEmpty() && column.first().y() + column.first().height() < bufferFromY) {
120 releaseItem(column.takeFirst().m_item);
121 changed = true;
122 }
124 while (!column.isEmpty() && column.last().y() > bufferToY) {
125 releaseItem(column.takeLast().m_item);
126 changed = true;
127 }
128 }
130 return changed;
133void VerticalJournal::positionItem(int modelIndex, QQuickItem *item)
135 if (item->width() != m_columnWidth) {
136 qWarning() << "Item" << modelIndex << "width is not the one that the columnWidth mandates, resetting it";
137 item->setWidth(m_columnWidth);
138 }
140 // Check if we add it to the bottom of existing column items
141 const QList<ViewItem> &firstColumn = m_columnVisibleItems[0];
142 qreal columnToAddY = !firstColumn.isEmpty() ? firstColumn.last().y() + firstColumn.last().height() : -rowSpacing();
143 int columnToAddTo = 0;
144 for (int i = 1; i < m_columnVisibleItems.count(); ++i) {
145 const QList<ViewItem> &column = m_columnVisibleItems[i];
146 const qreal iY = !column.isEmpty() ? column.last().y() + column.last().height() : -rowSpacing();
147 if (iY < columnToAddY) {
148 columnToAddTo = i;
149 columnToAddY = iY;
150 }
151 }
153 const QList<ViewItem> &columnToAdd = m_columnVisibleItems[columnToAddTo];
154 if (columnToAdd.isEmpty() || columnToAdd.last().m_modelIndex < modelIndex) {
155 item->setX(columnToAddTo * (m_columnWidth + columnSpacing()));
156 item->setY(columnToAddY + rowSpacing());
158 m_columnVisibleItems[columnToAddTo] << ViewItem(item, modelIndex);
159 m_indexColumnMap[modelIndex] = columnToAddTo;
160 } else {
161 Q_ASSERT(m_indexColumnMap.contains(modelIndex));
162 columnToAddTo = m_indexColumnMap[modelIndex];
163 columnToAddY = m_columnVisibleItems[columnToAddTo].first().y();
165 item->setX(columnToAddTo * (m_columnWidth + columnSpacing()));
166 item->setY(columnToAddY - rowSpacing() - item->height());
168 m_columnVisibleItems[columnToAddTo].prepend(ViewItem(item, modelIndex));
169 }
172void VerticalJournal::cleanupExistingItems()
174 // Cleanup the existing items
175 for (int i = 0; i < m_columnVisibleItems.count(); ++i) {
176 QList<ViewItem> &column = m_columnVisibleItems[i];
177 Q_FOREACH(const ViewItem &item, column)
178 releaseItem(item.m_item);
179 column.clear();
180 }
181 m_indexColumnMap.clear();
184void VerticalJournal::calculateImplicitHeight()
186 int lastModelIndex = -1;
187 qreal bottomMostY = 0;
188 Q_FOREACH(const auto &column, m_columnVisibleItems) {
189 if (!column.isEmpty()) {
190 const ViewItem &item = column.last();
191 lastModelIndex = qMax(lastModelIndex, item.m_modelIndex);
192 bottomMostY = qMax(bottomMostY, item.y() + item.height());
193 }
194 }
195 if (lastModelIndex >= 0) {
196 const double averageHeight = bottomMostY / (lastModelIndex + 1);
197 setImplicitHeight(bottomMostY + averageHeight * (model()->rowCount() - lastModelIndex - 1));
198 } else {
199 setImplicitHeight(0);
200 }
203void VerticalJournal::doRelayout()
205 QList<ViewItem> allItems;
206 Q_FOREACH(const auto &column, m_columnVisibleItems)
207 allItems << column;
209 qSort(allItems);
211 const int nColumns = qMax(1., floor((double)(width() + columnSpacing()) / (m_columnWidth + columnSpacing())));
212 m_columnVisibleItems.resize(nColumns);
213 m_indexColumnMap.clear();
214 for (int i = 0; i < nColumns; ++i)
215 m_columnVisibleItems[i].clear();
217 // If the first of allItems doesn't contain index 0 we need to drop them
218 // all since we can't consistently relayout without the first item being there
220 if (!allItems.isEmpty()) {
221 if (allItems.first().m_modelIndex == 0) {
222 Q_FOREACH(const ViewItem &item, allItems)
223 positionItem(item.m_modelIndex, item.m_item);
224 } else {
225 Q_FOREACH(const ViewItem &item, allItems)
226 releaseItem(item.m_item);
227 }
228 }
231void VerticalJournal::updateItemCulling(qreal visibleFromY, qreal visibleToY)
233 Q_FOREACH(const auto &column, m_columnVisibleItems) {
234 Q_FOREACH(const ViewItem &item, column) {
235 const bool cull = item.y() + item.height() <= visibleFromY || item.y() >= visibleToY;
236 QQuickItemPrivate::get(item.m_item)->setCulled(cull);
237 }
238 }
241#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
242void VerticalJournal::processModelRemoves(const QVector<QQuickChangeSet::Remove> &removes)
243#else
244void VerticalJournal::processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes)
245#endif
247#if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
248 Q_FOREACH(const QQuickChangeSet::Remove &remove, removes) {
249#else
250 Q_FOREACH(const QQmlChangeSet::Remove &remove, removes) {
251#endif
252 for (int i = remove.count - 1; i >= 0; --i) {
253 const int indexToRemove = remove.index + i;
254 // Since we only support removing from the end, indexToRemove
255 // must refer to the last item of one of the columns or
256 // be bigger than them (because it's not in the viewport and
257 // thus we have not created a delegate for it)
258 bool found = false;
259 int lastCreatedIndex = INT_MIN;
260 for (int i = 0; !found && i < m_columnVisibleItems.count(); ++i) {
261 QList<ViewItem> &column = m_columnVisibleItems[i];
262 if (!column.isEmpty()) {
263 const int lastColumnIndex = column.last().m_modelIndex;
264 if (lastColumnIndex == indexToRemove) {
265 releaseItem(column.takeLast().m_item);
266 found = true;
267 }
268 lastCreatedIndex = qMax(lastCreatedIndex, lastColumnIndex);
269 }
270 }
271 if (!found) {
272 if (indexToRemove < lastCreatedIndex) {
273 qFatal("VerticalJournal only supports removal from the end of the model");
274 } else {
275 setImplicitHeightDirty();
276 }
277 }
278 }
279 }