Unity 8
 All Classes Functions
verticaljournal.cpp
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  */
16 
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"
29 
30 #pragma GCC diagnostic push
31 #pragma GCC diagnostic ignored "-pedantic"
32 #include <private/qquickitem_p.h>
33 #pragma GCC diagnostic pop
34 
35 static const qreal bufferRatio = 0.5;
36 
37 VerticalJournal::VerticalJournal()
38  : m_columnWidth(0)
39 {
40 }
41 
42 qreal VerticalJournal::columnWidth() const
43 {
44  return m_columnWidth;
45 }
46 
47 void VerticalJournal::setColumnWidth(qreal columnWidth)
48 {
49  if (columnWidth != m_columnWidth) {
50  m_columnWidth = columnWidth;
51  Q_EMIT columnWidthChanged();
52 
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  }
62 }
63 
64 void VerticalJournal::findBottomModelIndexToAdd(int *modelIndex, qreal *yPos)
65 {
66  *modelIndex = 0;
67  *yPos = std::numeric_limits<qreal>::max();
68 
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  }
78 }
79 
80 void VerticalJournal::findTopModelIndexToAdd(int *modelIndex, qreal *yPos)
81 {
82  *modelIndex = 0;
83  *yPos = std::numeric_limits<qreal>::lowest();
84  int columnToAddTo = -1;
85 
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  }
99 
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  }
111 }
112 
113 bool VerticalJournal::removeNonVisibleItems(qreal bufferFromY, qreal bufferToY)
114 {
115  bool changed = false;
116 
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  }
123 
124  while (!column.isEmpty() && column.last().y() > bufferToY) {
125  releaseItem(column.takeLast().m_item);
126  changed = true;
127  }
128  }
129 
130  return changed;
131 }
132 
133 void VerticalJournal::addItemToView(int modelIndex, QQuickItem *item)
134 {
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  }
139 
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  }
152 
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());
157 
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();
164 
165  item->setX(columnToAddTo * (m_columnWidth + columnSpacing()));
166  item->setY(columnToAddY - rowSpacing() - item->height());
167 
168  m_columnVisibleItems[columnToAddTo].prepend(ViewItem(item, modelIndex));
169  }
170 }
171 
172 void VerticalJournal::cleanupExistingItems()
173 {
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();
182  setImplicitHeightDirty();
183 }
184 
185 void VerticalJournal::calculateImplicitHeight()
186 {
187  int lastModelIndex = -1;
188  qreal bottomMostY = 0;
189  Q_FOREACH(const auto &column, m_columnVisibleItems) {
190  if (!column.isEmpty()) {
191  const ViewItem &item = column.last();
192  lastModelIndex = qMax(lastModelIndex, item.m_modelIndex);
193  bottomMostY = qMax(bottomMostY, item.y() + item.height());
194  }
195  }
196  if (lastModelIndex >= 0) {
197  const double averageHeight = bottomMostY / (lastModelIndex + 1);
198  setImplicitHeight(bottomMostY + averageHeight * (model()->rowCount() - lastModelIndex - 1));
199  } else {
200  setImplicitHeight(0);
201  }
202 }
203 
204 void VerticalJournal::doRelayout()
205 {
206  QList<ViewItem> allItems;
207  Q_FOREACH(const auto &column, m_columnVisibleItems)
208  allItems << column;
209 
210  qSort(allItems);
211 
212  const int nColumns = qMax(1., floor((double)(width() + columnSpacing()) / (m_columnWidth + columnSpacing())));
213  m_columnVisibleItems.resize(nColumns);
214  m_indexColumnMap.clear();
215  for (int i = 0; i < nColumns; ++i)
216  m_columnVisibleItems[i].clear();
217 
218  // If the first of allItems doesn't contain index 0 we need to drop them
219  // all since we can't consistently relayout without the first item being there
220 
221  if (!allItems.isEmpty()) {
222  if (allItems.first().m_modelIndex == 0) {
223  Q_FOREACH(const ViewItem &item, allItems)
224  addItemToView(item.m_modelIndex, item.m_item);
225  } else {
226  Q_FOREACH(const ViewItem &item, allItems)
227  releaseItem(item.m_item);
228  }
229  }
230 }
231 
232 void VerticalJournal::updateItemCulling(qreal visibleFromY, qreal visibleToY)
233 {
234  Q_FOREACH(const auto &column, m_columnVisibleItems) {
235  Q_FOREACH(const ViewItem &item, column) {
236  const bool cull = item.y() + item.height() <= visibleFromY || item.y() >= visibleToY;
237  QQuickItemPrivate::get(item.m_item)->setCulled(cull);
238  }
239  }
240 }
241 
242 void VerticalJournal::processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes)
243 {
244  Q_FOREACH(const QQmlChangeSet::Remove &remove, removes) {
245  for (int i = remove.count - 1; i >= 0; --i) {
246  const int indexToRemove = remove.index + i;
247  // Since we only support removing from the end, indexToRemove
248  // must refer to the last item of one of the columns or
249  // be bigger than them (because it's not in the viewport and
250  // thus we have not created a delegate for it)
251  bool found = false;
252  int lastCreatedIndex = INT_MIN;
253  for (int i = 0; !found && i < m_columnVisibleItems.count(); ++i) {
254  QList<ViewItem> &column = m_columnVisibleItems[i];
255  if (!column.isEmpty()) {
256  const int lastColumnIndex = column.last().m_modelIndex;
257  if (lastColumnIndex == indexToRemove) {
258  releaseItem(column.takeLast().m_item);
259  found = true;
260  }
261  lastCreatedIndex = qMax(lastCreatedIndex, lastColumnIndex);
262  }
263  }
264  if (!found) {
265  if (indexToRemove < lastCreatedIndex) {
266  qFatal("VerticalJournal only supports removal from the end of the model");
267  } else {
268  setImplicitHeightDirty();
269  }
270  }
271  }
272  }
273 }