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