1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
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. *
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. *
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 ***************************************************************************/
20 #include "kitemlistviewlayouter_p.h"
22 #include "kitemmodelbase.h"
23 #include "kitemlistsizehintresolver_p.h"
27 #define KITEMLISTVIEWLAYOUTER_DEBUG
31 const int HeaderHeight
= 50;
34 KItemListViewLayouter::KItemListViewLayouter(QObject
* parent
) :
37 m_visibleIndexesDirty(true),
39 m_scrollOrientation(Qt::Vertical
),
44 m_sizeHintResolver(0),
47 m_firstVisibleIndex(-1),
48 m_lastVisibleIndex(-1),
49 m_firstVisibleGroupIndex(-1),
59 KItemListViewLayouter::~KItemListViewLayouter()
63 void KItemListViewLayouter::setScrollOrientation(Qt::Orientation orientation
)
65 if (m_scrollOrientation
!= orientation
) {
66 m_scrollOrientation
= orientation
;
71 Qt::Orientation
KItemListViewLayouter::scrollOrientation() const
73 return m_scrollOrientation
;
76 void KItemListViewLayouter::setSize(const QSizeF
& size
)
84 QSizeF
KItemListViewLayouter::size() const
89 void KItemListViewLayouter::setItemSize(const QSizeF
& size
)
91 if (m_itemSize
!= size
) {
97 QSizeF
KItemListViewLayouter::itemSize() const
102 void KItemListViewLayouter::setHeaderHeight(qreal height
)
104 if (m_headerHeight
!= height
) {
105 m_headerHeight
= height
;
110 qreal
KItemListViewLayouter::headerHeight() const
112 return m_headerHeight
;
115 void KItemListViewLayouter::setOffset(qreal offset
)
117 if (m_offset
!= offset
) {
119 m_visibleIndexesDirty
= true;
123 qreal
KItemListViewLayouter::offset() const
128 void KItemListViewLayouter::setModel(const KItemModelBase
* model
)
130 if (m_model
!= model
) {
136 const KItemModelBase
* KItemListViewLayouter::model() const
141 void KItemListViewLayouter::setSizeHintResolver(const KItemListSizeHintResolver
* sizeHintResolver
)
143 if (m_sizeHintResolver
!= sizeHintResolver
) {
144 m_sizeHintResolver
= sizeHintResolver
;
149 const KItemListSizeHintResolver
* KItemListViewLayouter::sizeHintResolver() const
151 return m_sizeHintResolver
;
154 qreal
KItemListViewLayouter::maximumOffset() const
156 const_cast<KItemListViewLayouter
*>(this)->doLayout();
157 return m_maximumOffset
;
160 int KItemListViewLayouter::firstVisibleIndex() const
162 const_cast<KItemListViewLayouter
*>(this)->doLayout();
163 return m_firstVisibleIndex
;
166 int KItemListViewLayouter::lastVisibleIndex() const
168 const_cast<KItemListViewLayouter
*>(this)->doLayout();
169 return m_lastVisibleIndex
;
172 QRectF
KItemListViewLayouter::itemBoundingRect(int index
) const
174 const_cast<KItemListViewLayouter
*>(this)->doLayout();
175 if (index
< 0 || index
>= m_itemBoundingRects
.count()) {
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
;
190 QRectF bounds
= m_itemBoundingRects
[index
];
191 QPointF pos
= bounds
.topLeft();
192 pos
.ry() -= m_offset
;
197 int KItemListViewLayouter::maximumVisibleItems() const
199 const_cast<KItemListViewLayouter
*>(this)->doLayout();
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) {
208 return rows
* m_columnCount
;
211 int KItemListViewLayouter::itemsPerOffset() const
213 return m_columnCount
;
216 bool KItemListViewLayouter::isFirstGroupItem(int itemIndex
) const
218 return m_groupIndexes
.contains(itemIndex
);
221 void KItemListViewLayouter::markAsDirty()
226 void KItemListViewLayouter::doLayout()
229 #ifdef KITEMLISTVIEWLAYOUTER_DEBUG
233 m_visibleIndexesDirty
= true;
235 QSizeF itemSize
= m_itemSize
;
236 QSizeF size
= m_size
;
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());
246 m_columnWidth
= itemSize
.width();
247 m_columnCount
= qMax(1, int(size
.width() / m_columnWidth
));
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
;
259 int rowCount
= itemCount
/ m_columnCount
;
260 if (itemCount
% m_columnCount
!= 0) {
264 m_itemBoundingRects
.reserve(itemCount
);
266 qreal y
= m_headerHeight
;
270 while (index
< itemCount
) {
272 qreal maxItemHeight
= itemSize
.height();
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
;
285 const QRectF
bounds(x
, y
, itemSize
.width(), requiredItemHeight
);
286 if (index
< m_itemBoundingRects
.count()) {
287 m_itemBoundingRects
[index
] = bounds
;
289 m_itemBoundingRects
.append(bounds
);
292 maxItemHeight
= qMax(maxItemHeight
, requiredItemHeight
);
301 if (m_itemBoundingRects
.count() > itemCount
) {
302 m_itemBoundingRects
.erase(m_itemBoundingRects
.begin() + itemCount
,
303 m_itemBoundingRects
.end());
306 m_maximumOffset
= (itemCount
> 0) ? m_itemBoundingRects
.last().bottom() : 0;
308 m_grouped
= !m_model
->groupRole().isEmpty();
310 createGroupHeaders();
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;
318 // m_maximumOffset = m_minimumRowHeight * rowCount;
321 #ifdef KITEMLISTVIEWLAYOUTER_DEBUG
322 kDebug() << "[TIME] doLayout() for " << m_model
->count() << "items:" << timer
.elapsed();
328 updateGroupedVisibleIndexes();
330 updateVisibleIndexes();
334 void KItemListViewLayouter::updateVisibleIndexes()
336 if (!m_visibleIndexesDirty
) {
340 Q_ASSERT(!m_grouped
);
343 if (m_model
->count() <= 0) {
344 m_firstVisibleIndex
= -1;
345 m_lastVisibleIndex
= -1;
346 m_visibleIndexesDirty
= false;
350 const bool horizontalScrolling
= (m_scrollOrientation
== Qt::Horizontal
);
351 const int minimumHeight
= horizontalScrolling
? m_itemSize
.width()
352 : m_itemSize
.height();
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
;
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
;
365 while (prevRowIndex
>= 0 && m_itemBoundingRects
[prevRowIndex
].bottom() >= m_offset
) {
366 m_firstVisibleIndex
= prevRowIndex
;
367 prevRowIndex
-= m_columnCount
;
369 m_firstVisibleIndex
= qBound(0, m_firstVisibleIndex
, maxIndex
);
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
;
380 m_lastVisibleIndex
+= m_columnCount
- 1; // move it to the last column
381 m_lastVisibleIndex
= qBound(0, m_lastVisibleIndex
, maxIndex
);
383 m_visibleIndexesDirty
= false;
386 void KItemListViewLayouter::updateGroupedVisibleIndexes()
388 if (!m_visibleIndexesDirty
) {
395 if (m_model
->count() <= 0) {
396 m_firstVisibleIndex
= -1;
397 m_lastVisibleIndex
= -1;
398 m_visibleIndexesDirty
= false;
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
) {
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
421 const int nextGroupIndex
= m_groups
[groupIndex
+ 1].firstItemIndex
;
422 if (m_firstVisibleIndex
> nextGroupIndex
) {
423 m_firstVisibleIndex
= nextGroupIndex
;
427 m_firstVisibleGroupIndex
= groupIndex
;
429 const int maxIndex
= m_model
->count() - 1;
430 m_firstVisibleIndex
= qBound(0, m_firstVisibleIndex
, maxIndex
);
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
)) {
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) {
445 m_lastVisibleIndex
+= visibleRowCount
* m_columnCount
- 1;
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;
455 //Q_ASSERT(m_lastVisibleIndex < m_model->count());
456 m_lastVisibleIndex
= qBound(0, m_lastVisibleIndex
, maxIndex
);
458 m_visibleIndexesDirty
= false;
461 void KItemListViewLayouter::createGroupHeaders()
464 m_groupIndexes
.clear();
468 numbers
<< 0 << 5 << 6 << 13 << 20 << 25 << 30 << 35 << 50;
471 for (int i
= 0; i
< numbers
.count(); ++i
) {
473 const int previousGroupItemCount
= numbers
[i
] - m_groups
.last().firstItemIndex
;
474 int previousGroupRowCount
= previousGroupItemCount
/ m_columnCount
;
475 if (previousGroupItemCount
% m_columnCount
!= 0) {
476 ++previousGroupRowCount
;
478 const qreal previousGroupHeight
= previousGroupRowCount
* m_itemSize
.height();
479 y
+= previousGroupHeight
;
484 itemGroup
.firstItemIndex
= numbers
[i
];
487 m_groups
.append(itemGroup
);
488 m_groupIndexes
.insert(itemGroup
.firstItemIndex
);
492 #include "kitemlistviewlayouter_p.moc"