Unity 8
 All Classes Functions Properties
horizontaljournal.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_visibleItems
19  * that a list for each of the items in the view.
20  * m_firstVisibleIndex is the index of the first item in m_visibleItems
21  * m_lastInRowIndexPosition is a map that contains the x position
22  * of items that are the last ones of a row so we can reconstruct the rows
23  * when building back
24  */
25 
26 #include "horizontaljournal.h"
27 
28 #include <qqmlengine.h>
29 #pragma GCC diagnostic push
30 #pragma GCC diagnostic ignored "-pedantic"
31 #if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
32 #include <private/qquickvisualdatamodel_p.h>
33 #else
34 #include <private/qqmldelegatemodel_p.h>
35 #include <qqmlinfo.h>
36 #endif
37 #include <private/qquickitem_p.h>
38 #pragma GCC diagnostic pop
39 
40 static const qreal bufferRatio = 0.5;
41 
42 HorizontalJournal::HorizontalJournal()
43  : m_firstVisibleIndex(-1)
44  , m_rowHeight(0)
45 {
46 }
47 
48 qreal HorizontalJournal::rowHeight() const
49 {
50  return m_rowHeight;
51 }
52 
53 void HorizontalJournal::setRowHeight(qreal rowHeight)
54 {
55  if (rowHeight != m_rowHeight) {
56  m_rowHeight = rowHeight;
57  Q_EMIT rowHeightChanged();
58 
59  if (isComponentComplete()) {
60  Q_FOREACH(QQuickItem *item, m_visibleItems) {
61  item->setHeight(rowHeight);
62  }
63  relayout();
64  }
65  }
66 }
67 
68 void HorizontalJournal::findBottomModelIndexToAdd(int *modelIndex, qreal *yPos)
69 {
70  if (m_visibleItems.isEmpty()) {
71  *modelIndex = 0;
72  *yPos = 0;
73  } else {
74  *modelIndex = m_firstVisibleIndex + m_visibleItems.count();
75  if (m_lastInRowIndexPosition.contains(*modelIndex - 1)) {
76  *yPos = m_visibleItems.last()->y() + m_rowHeight + rowSpacing();
77  } else {
78  *yPos = m_visibleItems.last()->y();
79  }
80  }
81 }
82 
83 void HorizontalJournal::findTopModelIndexToAdd(int *modelIndex, qreal *yPos)
84 {
85  if (m_visibleItems.isEmpty()) {
86  *modelIndex = -1;
87  *yPos = INT_MIN;
88  } else {
89  *modelIndex = m_firstVisibleIndex - 1;
90  if (m_lastInRowIndexPosition.contains(*modelIndex)) {
91  *yPos = m_visibleItems.first()->y() - rowSpacing() - m_rowHeight;
92  } else {
93  *yPos = m_visibleItems.first()->y();
94  }
95  }
96 }
97 
98 bool HorizontalJournal::removeNonVisibleItems(qreal bufferFromY, qreal bufferToY)
99 {
100  bool changed = false;
101 
102  while (!m_visibleItems.isEmpty() && m_visibleItems.first()->y() + m_rowHeight < bufferFromY) {
103  releaseItem(m_visibleItems.takeFirst());
104  changed = true;
105  m_firstVisibleIndex++;
106  }
107 
108  while (!m_visibleItems.isEmpty() && m_visibleItems.last()->y() > bufferToY) {
109  releaseItem(m_visibleItems.takeLast());
110  changed = true;
111  m_lastInRowIndexPosition.remove(m_firstVisibleIndex + m_visibleItems.count());
112  }
113 
114  if (m_visibleItems.isEmpty()) {
115  m_firstVisibleIndex = -1;
116  }
117 
118  return changed;
119 }
120 
121 void HorizontalJournal::addItemToView(int modelIndex, QQuickItem *item)
122 {
123  if (item->height() != m_rowHeight) {
124  qWarning() << "Item" << modelIndex << "height is not the one that the rowHeight mandates, resetting it";
125  item->setHeight(m_rowHeight);
126  }
127 
128  if (m_visibleItems.isEmpty()) {
129  Q_ASSERT(modelIndex == 0);
130  item->setY(0);
131  item->setX(0);
132  m_visibleItems << item;
133  m_firstVisibleIndex = 0;
134  } else {
135  // modelIndex has to be either m_firstVisibleIndex - 1 or m_firstVisibleIndex + m_visibleItems.count()
136  if (modelIndex == m_firstVisibleIndex + m_visibleItems.count()) {
137  QQuickItem *lastItem = m_visibleItems.last();
138  if (lastItem->x() + lastItem->width() + columnSpacing() + item->width() <= width()) {
139  // Fits in the row
140  item->setY(lastItem->y());
141  item->setX(lastItem->x() + lastItem->width() + columnSpacing());
142  } else {
143  // Starts a new row
144  item->setY(lastItem->y() + m_rowHeight + rowSpacing());
145  item->setX(0);
146  m_lastInRowIndexPosition[modelIndex - 1] = lastItem->x();
147  }
148  m_visibleItems << item;
149  } else if (modelIndex == m_firstVisibleIndex - 1) {
150  QQuickItem *firstItem = m_visibleItems.first();
151  if (m_lastInRowIndexPosition.contains(modelIndex)) {
152  // It is the last item of its row, so start a new one since we're going back
153  item->setY(firstItem->y() - rowSpacing() - m_rowHeight);
154  item->setX(m_lastInRowIndexPosition[modelIndex]);
155  } else {
156  item->setY(firstItem->y());
157  item->setX(firstItem->x() - columnSpacing() - item->width());
158  }
159  m_firstVisibleIndex = modelIndex;
160  m_visibleItems.prepend(item);
161  } else {
162  qWarning() << "HorizontalJournal::addItemToView - Got unexpected modelIndex"
163  << modelIndex << m_firstVisibleIndex << m_visibleItems.count();
164  }
165  }
166 }
167 
168 void HorizontalJournal::cleanupExistingItems()
169 {
170  // Cleanup the existing items
171  Q_FOREACH(QQuickItem *item, m_visibleItems)
172  releaseItem(item);
173  m_visibleItems.clear();
174  m_lastInRowIndexPosition.clear();
175  m_firstVisibleIndex = -1;
176  setImplicitHeightDirty();
177 }
178 
179 void HorizontalJournal::calculateImplicitHeight()
180 {
181  if (m_firstVisibleIndex >= 0) {
182  const int nIndexes = m_firstVisibleIndex + m_visibleItems.count();
183  const double bottomMostY = m_visibleItems.last()->y() + m_rowHeight;
184  const double averageHeight = bottomMostY / nIndexes;
185  setImplicitHeight(bottomMostY + averageHeight * (model()->rowCount() - nIndexes));
186  } else {
187  setImplicitHeight(0);
188  }
189 }
190 
191 #if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
192 void HorizontalJournal::processModelRemoves(const QVector<QQuickChangeSet::Remove> &removes)
193 #else
194 void HorizontalJournal::processModelRemoves(const QVector<QQmlChangeSet::Remove> &removes)
195 #endif
196 {
197 #if (QT_VERSION < QT_VERSION_CHECK(5, 1, 0))
198  Q_FOREACH(const QQuickChangeSet::Remove &remove, removes) {
199 #else
200  Q_FOREACH(const QQmlChangeSet::Remove &remove, removes) {
201 #endif
202  for (int i = remove.count - 1; i >= 0; --i) {
203  const int indexToRemove = remove.index + i;
204  // We only support removing from the end so
205  // any of the last items of a column has to be indexToRemove
206  const int lastIndex = m_firstVisibleIndex + m_visibleItems.count() - 1;
207  if (indexToRemove == lastIndex) {
208  releaseItem(m_visibleItems.takeLast());
209  m_lastInRowIndexPosition.remove(indexToRemove);
210  } else {
211  if (indexToRemove < lastIndex) {
212  qFatal("HorizontalJournal only supports removal from the end of the model");
213  } else {
214  setImplicitHeightDirty();
215  }
216  }
217  }
218  }
219  if (m_visibleItems.isEmpty()) {
220  m_firstVisibleIndex = -1;
221  }
222 }
223 
224 
225 void HorizontalJournal::doRelayout()
226 {
227  // If m_firstVisibleIndex is not 0 we need to drop all the delegates
228  // since we can't consistently relayout without the first item being there
229 
230  if (m_firstVisibleIndex == 0) {
231  int i = 0;
232  const QList<QQuickItem*> allItems = m_visibleItems;
233  m_visibleItems.clear();
234  m_lastInRowIndexPosition.clear();
235  Q_FOREACH(QQuickItem *item, allItems) {
236  addItemToView(i, item);
237  ++i;
238  }
239  } else {
240  Q_FOREACH(QQuickItem *item, m_visibleItems) {
241  releaseItem(item);
242  }
243  m_visibleItems.clear();
244  m_lastInRowIndexPosition.clear();
245  m_firstVisibleIndex = -1;
246  }
247 }
248 
249 void HorizontalJournal::updateItemCulling(qreal visibleFromY, qreal visibleToY)
250 {
251  Q_FOREACH(QQuickItem *item, m_visibleItems) {
252  QQuickItemPrivate::get(item)->setCulled(item->y() + m_rowHeight <= visibleFromY || item->y() >= visibleToY);
253  }
254 }