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