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