]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistviewlayouter.cpp
Don't apply an empty item-size to the layout
[dolphin.git] / src / kitemviews / kitemlistviewlayouter.cpp
1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
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; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
19
20 #include "kitemlistviewlayouter_p.h"
21
22 #include "kitemmodelbase.h"
23 #include "kitemlistsizehintresolver_p.h"
24
25 #include <KDebug>
26
27 #define KITEMLISTVIEWLAYOUTER_DEBUG
28
29 namespace {
30 // TODO
31 const int HeaderHeight = 50;
32 };
33
34 KItemListViewLayouter::KItemListViewLayouter(QObject* parent) :
35 QObject(parent),
36 m_dirty(true),
37 m_visibleIndexesDirty(true),
38 m_grouped(false),
39 m_scrollOrientation(Qt::Vertical),
40 m_size(),
41 m_itemSize(128, 128),
42 m_headerHeight(0),
43 m_model(0),
44 m_sizeHintResolver(0),
45 m_scrollOffset(0),
46 m_maximumScrollOffset(0),
47 m_itemOffset(0),
48 m_maximumItemOffset(0),
49 m_firstVisibleIndex(-1),
50 m_lastVisibleIndex(-1),
51 m_firstVisibleGroupIndex(-1),
52 m_columnWidth(0),
53 m_xPosInc(0),
54 m_columnCount(0),
55 m_groups(),
56 m_groupIndexes(),
57 m_itemBoundingRects()
58 {
59 }
60
61 KItemListViewLayouter::~KItemListViewLayouter()
62 {
63 }
64
65 void KItemListViewLayouter::setScrollOrientation(Qt::Orientation orientation)
66 {
67 if (m_scrollOrientation != orientation) {
68 m_scrollOrientation = orientation;
69 m_dirty = true;
70 }
71 }
72
73 Qt::Orientation KItemListViewLayouter::scrollOrientation() const
74 {
75 return m_scrollOrientation;
76 }
77
78 void KItemListViewLayouter::setSize(const QSizeF& size)
79 {
80 if (m_size != size) {
81 m_size = size;
82 m_dirty = true;
83 }
84 }
85
86 QSizeF KItemListViewLayouter::size() const
87 {
88 return m_size;
89 }
90
91 void KItemListViewLayouter::setItemSize(const QSizeF& size)
92 {
93 if (m_itemSize != size) {
94 m_itemSize = size;
95 m_dirty = true;
96 }
97 }
98
99 QSizeF KItemListViewLayouter::itemSize() const
100 {
101 return m_itemSize;
102 }
103
104 void KItemListViewLayouter::setHeaderHeight(qreal height)
105 {
106 if (m_headerHeight != height) {
107 m_headerHeight = height;
108 m_dirty = true;
109 }
110 }
111
112 qreal KItemListViewLayouter::headerHeight() const
113 {
114 return m_headerHeight;
115 }
116
117 void KItemListViewLayouter::setScrollOffset(qreal offset)
118 {
119 if (m_scrollOffset != offset) {
120 m_scrollOffset = offset;
121 m_visibleIndexesDirty = true;
122 }
123 }
124
125 qreal KItemListViewLayouter::scrollOffset() const
126 {
127 return m_scrollOffset;
128 }
129
130 qreal KItemListViewLayouter::maximumScrollOffset() const
131 {
132 const_cast<KItemListViewLayouter*>(this)->doLayout();
133 return m_maximumScrollOffset;
134 }
135
136 void KItemListViewLayouter::setItemOffset(qreal offset)
137 {
138 if (m_itemOffset != offset) {
139 m_itemOffset = offset;
140 m_visibleIndexesDirty = true;
141 }
142 }
143
144 qreal KItemListViewLayouter::itemOffset() const
145 {
146 return m_itemOffset;
147 }
148
149 qreal KItemListViewLayouter::maximumItemOffset() const
150 {
151 const_cast<KItemListViewLayouter*>(this)->doLayout();
152 return m_maximumItemOffset;
153 }
154
155 void KItemListViewLayouter::setModel(const KItemModelBase* model)
156 {
157 if (m_model != model) {
158 m_model = model;
159 m_dirty = true;
160 }
161 }
162
163 const KItemModelBase* KItemListViewLayouter::model() const
164 {
165 return m_model;
166 }
167
168 void KItemListViewLayouter::setSizeHintResolver(const KItemListSizeHintResolver* sizeHintResolver)
169 {
170 if (m_sizeHintResolver != sizeHintResolver) {
171 m_sizeHintResolver = sizeHintResolver;
172 m_dirty = true;
173 }
174 }
175
176 const KItemListSizeHintResolver* KItemListViewLayouter::sizeHintResolver() const
177 {
178 return m_sizeHintResolver;
179 }
180
181 int KItemListViewLayouter::firstVisibleIndex() const
182 {
183 const_cast<KItemListViewLayouter*>(this)->doLayout();
184 return m_firstVisibleIndex;
185 }
186
187 int KItemListViewLayouter::lastVisibleIndex() const
188 {
189 const_cast<KItemListViewLayouter*>(this)->doLayout();
190 return m_lastVisibleIndex;
191 }
192
193 QRectF KItemListViewLayouter::itemBoundingRect(int index) const
194 {
195 const_cast<KItemListViewLayouter*>(this)->doLayout();
196 if (index < 0 || index >= m_itemBoundingRects.count()) {
197 return QRectF();
198 }
199
200 if (m_scrollOrientation == Qt::Horizontal) {
201 // Rotate the logical direction which is always vertical by 90°
202 // to get the physical horizontal direction
203 const QRectF& b = m_itemBoundingRects[index];
204 QRectF bounds(b.y(), b.x(), b.height(), b.width());
205 QPointF pos = bounds.topLeft();
206 pos.rx() -= m_scrollOffset;
207 bounds.moveTo(pos);
208 return bounds;
209 }
210
211 QRectF bounds = m_itemBoundingRects[index];
212 bounds.moveTo(bounds.topLeft() - QPointF(m_itemOffset, m_scrollOffset));
213 return bounds;
214 }
215
216 int KItemListViewLayouter::maximumVisibleItems() const
217 {
218 const_cast<KItemListViewLayouter*>(this)->doLayout();
219
220 const int height = static_cast<int>(m_size.height());
221 const int rowHeight = static_cast<int>(m_itemSize.height());
222 int rows = height / rowHeight;
223 if (height % rowHeight != 0) {
224 ++rows;
225 }
226
227 return rows * m_columnCount;
228 }
229
230 int KItemListViewLayouter::itemsPerOffset() const
231 {
232 return m_columnCount;
233 }
234
235 bool KItemListViewLayouter::isFirstGroupItem(int itemIndex) const
236 {
237 return m_groupIndexes.contains(itemIndex);
238 }
239
240 void KItemListViewLayouter::markAsDirty()
241 {
242 m_dirty = true;
243 }
244
245 void KItemListViewLayouter::doLayout()
246 {
247 if (m_dirty) {
248 #ifdef KITEMLISTVIEWLAYOUTER_DEBUG
249 QElapsedTimer timer;
250 timer.start();
251 #endif
252 m_visibleIndexesDirty = true;
253
254 QSizeF itemSize = m_itemSize;
255 QSizeF size = m_size;
256
257 const bool horizontalScrolling = (m_scrollOrientation == Qt::Horizontal);
258 if (horizontalScrolling) {
259 itemSize.setWidth(m_itemSize.height());
260 itemSize.setHeight(m_itemSize.width());
261 size.setWidth(m_size.height());
262 size.setHeight(m_size.width());
263 }
264
265 m_columnWidth = itemSize.width();
266 m_columnCount = qMax(1, int(size.width() / m_columnWidth));
267 m_xPosInc = 0;
268
269 const int itemCount = m_model->count();
270 if (itemCount > m_columnCount) {
271 // Apply the unused width equally to each column
272 const qreal unusedWidth = size.width() - m_columnCount * m_columnWidth;
273 if (unusedWidth > 0) {
274 const qreal columnInc = unusedWidth / (m_columnCount + 1);
275 m_columnWidth += columnInc;
276 m_xPosInc += columnInc;
277 }
278 }
279
280 int rowCount = itemCount / m_columnCount;
281 if (itemCount % m_columnCount != 0) {
282 ++rowCount;
283 }
284
285 m_itemBoundingRects.reserve(itemCount);
286
287 qreal y = m_headerHeight;
288 int rowIndex = 0;
289
290 int index = 0;
291 while (index < itemCount) {
292 qreal x = m_xPosInc;
293 qreal maxItemHeight = itemSize.height();
294
295 int column = 0;
296 while (index < itemCount && column < m_columnCount) {
297 qreal requiredItemHeight = itemSize.height();
298 if (m_sizeHintResolver) {
299 const QSizeF sizeHint = m_sizeHintResolver->sizeHint(index);
300 const qreal sizeHintHeight = horizontalScrolling ? sizeHint.width() : sizeHint.height();
301 if (sizeHintHeight > requiredItemHeight) {
302 requiredItemHeight = sizeHintHeight;
303 }
304 }
305
306 const QRectF bounds(x, y, itemSize.width(), requiredItemHeight);
307 if (index < m_itemBoundingRects.count()) {
308 m_itemBoundingRects[index] = bounds;
309 } else {
310 m_itemBoundingRects.append(bounds);
311 }
312
313 maxItemHeight = qMax(maxItemHeight, requiredItemHeight);
314 x += m_columnWidth;
315 ++index;
316 ++column;
317 }
318
319 y += maxItemHeight;
320 ++rowIndex;
321 }
322 if (m_itemBoundingRects.count() > itemCount) {
323 m_itemBoundingRects.erase(m_itemBoundingRects.begin() + itemCount,
324 m_itemBoundingRects.end());
325 }
326
327 if (itemCount > 0) {
328 m_maximumScrollOffset = m_itemBoundingRects.last().bottom();
329 m_maximumItemOffset = m_columnCount * m_columnWidth;
330 } else {
331 m_maximumScrollOffset = 0;
332 m_maximumItemOffset = 0;
333 }
334
335 m_grouped = !m_model->groupRole().isEmpty();
336 /*if (m_grouped) {
337 createGroupHeaders();
338
339 const int lastGroupItemCount = m_model->count() - m_groups.last().firstItemIndex;
340 m_maximumOffset = m_groups.last().y + (lastGroupItemCount / m_columnCount) * m_rowHeight;
341 if (lastGroupItemCount % m_columnCount != 0) {
342 m_maximumOffset += m_rowHeight;
343 }
344 } else {*/
345 // m_maximumOffset = m_minimumRowHeight * rowCount;
346 //}
347
348 #ifdef KITEMLISTVIEWLAYOUTER_DEBUG
349 kDebug() << "[TIME] doLayout() for " << m_model->count() << "items:" << timer.elapsed();
350 #endif
351 m_dirty = false;
352 }
353
354 if (m_grouped) {
355 updateGroupedVisibleIndexes();
356 } else {
357 updateVisibleIndexes();
358 }
359 }
360
361 void KItemListViewLayouter::updateVisibleIndexes()
362 {
363 if (!m_visibleIndexesDirty) {
364 return;
365 }
366
367 Q_ASSERT(!m_grouped);
368 Q_ASSERT(!m_dirty);
369
370 if (m_model->count() <= 0) {
371 m_firstVisibleIndex = -1;
372 m_lastVisibleIndex = -1;
373 m_visibleIndexesDirty = false;
374 return;
375 }
376
377 const bool horizontalScrolling = (m_scrollOrientation == Qt::Horizontal);
378 const int minimumHeight = horizontalScrolling ? m_itemSize.width()
379 : m_itemSize.height();
380
381 // Calculate the first visible index:
382 // 1. Guess the index by using the minimum row height
383 const int maxIndex = m_model->count() - 1;
384 m_firstVisibleIndex = int(m_scrollOffset / minimumHeight) * m_columnCount;
385
386 // 2. Decrease the index by checking the real row heights
387 int prevRowIndex = m_firstVisibleIndex - m_columnCount;
388 while (prevRowIndex > maxIndex) {
389 prevRowIndex -= m_columnCount;
390 }
391
392 const qreal top = m_scrollOffset + m_headerHeight;
393 while (prevRowIndex >= 0 && m_itemBoundingRects[prevRowIndex].bottom() >= top) {
394 m_firstVisibleIndex = prevRowIndex;
395 prevRowIndex -= m_columnCount;
396 }
397 m_firstVisibleIndex = qBound(0, m_firstVisibleIndex, maxIndex);
398
399 // Calculate the last visible index
400 const int visibleHeight = horizontalScrolling ? m_size.width() : m_size.height();
401 const qreal bottom = m_scrollOffset + visibleHeight;
402 m_lastVisibleIndex = m_firstVisibleIndex; // first visible row, first column
403 int nextRowIndex = m_lastVisibleIndex + m_columnCount;
404 while (nextRowIndex <= maxIndex && m_itemBoundingRects[nextRowIndex].y() <= bottom) {
405 m_lastVisibleIndex = nextRowIndex;
406 nextRowIndex += m_columnCount;
407 }
408 m_lastVisibleIndex += m_columnCount - 1; // move it to the last column
409 m_lastVisibleIndex = qBound(0, m_lastVisibleIndex, maxIndex);
410
411 m_visibleIndexesDirty = false;
412 }
413
414 void KItemListViewLayouter::updateGroupedVisibleIndexes()
415 {
416 if (!m_visibleIndexesDirty) {
417 return;
418 }
419
420 Q_ASSERT(m_grouped);
421 Q_ASSERT(!m_dirty);
422
423 if (m_model->count() <= 0) {
424 m_firstVisibleIndex = -1;
425 m_lastVisibleIndex = -1;
426 m_visibleIndexesDirty = false;
427 return;
428 }
429
430 // Find the first visible group
431 const int lastGroupIndex = m_groups.count() - 1;
432 int groupIndex = lastGroupIndex;
433 for (int i = 1; i < m_groups.count(); ++i) {
434 if (m_groups[i].y >= m_scrollOffset) {
435 groupIndex = i - 1;
436 break;
437 }
438 }
439
440 // Calculate the first visible index
441 qreal groupY = m_groups[groupIndex].y;
442 m_firstVisibleIndex = m_groups[groupIndex].firstItemIndex;
443 const int invisibleRowCount = int(m_scrollOffset - groupY) / int(m_itemSize.height());
444 m_firstVisibleIndex += invisibleRowCount * m_columnCount;
445 if (groupIndex + 1 <= lastGroupIndex) {
446 // Check whether the calculated first visible index remains inside the current
447 // group. If this is not the case let the first element of the next group be the first
448 // visible index.
449 const int nextGroupIndex = m_groups[groupIndex + 1].firstItemIndex;
450 if (m_firstVisibleIndex > nextGroupIndex) {
451 m_firstVisibleIndex = nextGroupIndex;
452 }
453 }
454
455 m_firstVisibleGroupIndex = groupIndex;
456
457 const int maxIndex = m_model->count() - 1;
458 m_firstVisibleIndex = qBound(0, m_firstVisibleIndex, maxIndex);
459
460 // Calculate the last visible index: Find group where the last visible item is shown.
461 const qreal visibleBottom = m_scrollOffset + m_size.height(); // TODO: respect Qt::Horizontal alignment
462 while ((groupIndex < lastGroupIndex) && (m_groups[groupIndex + 1].y < visibleBottom)) {
463 ++groupIndex;
464 }
465
466 groupY = m_groups[groupIndex].y;
467 m_lastVisibleIndex = m_groups[groupIndex].firstItemIndex;
468 const int availableHeight = static_cast<int>(visibleBottom - groupY);
469 int visibleRowCount = availableHeight / int(m_itemSize.height());
470 if (availableHeight % int(m_itemSize.height()) != 0) {
471 ++visibleRowCount;
472 }
473 m_lastVisibleIndex += visibleRowCount * m_columnCount - 1;
474
475 if (groupIndex + 1 <= lastGroupIndex) {
476 // Check whether the calculate last visible index remains inside the current group.
477 // If this is not the case let the last element of this group be the last visible index.
478 const int nextGroupIndex = m_groups[groupIndex + 1].firstItemIndex;
479 if (m_lastVisibleIndex >= nextGroupIndex) {
480 m_lastVisibleIndex = nextGroupIndex - 1;
481 }
482 }
483 //Q_ASSERT(m_lastVisibleIndex < m_model->count());
484 m_lastVisibleIndex = qBound(0, m_lastVisibleIndex, maxIndex);
485
486 m_visibleIndexesDirty = false;
487 }
488
489 void KItemListViewLayouter::createGroupHeaders()
490 {
491 m_groups.clear();
492 m_groupIndexes.clear();
493
494 // TODO:
495 QList<int> numbers;
496 numbers << 0 << 5 << 6 << 13 << 20 << 25 << 30 << 35 << 50;
497
498 qreal y = 0;
499 for (int i = 0; i < numbers.count(); ++i) {
500 if (i > 0) {
501 const int previousGroupItemCount = numbers[i] - m_groups.last().firstItemIndex;
502 int previousGroupRowCount = previousGroupItemCount / m_columnCount;
503 if (previousGroupItemCount % m_columnCount != 0) {
504 ++previousGroupRowCount;
505 }
506 const qreal previousGroupHeight = previousGroupRowCount * m_itemSize.height();
507 y += previousGroupHeight;
508 }
509 y += HeaderHeight;
510
511 ItemGroup itemGroup;
512 itemGroup.firstItemIndex = numbers[i];
513 itemGroup.y = y;
514
515 m_groups.append(itemGroup);
516 m_groupIndexes.insert(itemGroup.firstItemIndex);
517 }
518 }
519
520 #include "kitemlistviewlayouter_p.moc"