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