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