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),
46 m_maximumScrollOffset(0),
48 m_maximumItemOffset(0),
49 m_firstVisibleIndex(-1),
50 m_lastVisibleIndex(-1),
51 m_firstVisibleGroupIndex(-1),
61 KItemListViewLayouter::~KItemListViewLayouter()
65 void KItemListViewLayouter::setScrollOrientation(Qt::Orientation orientation
)
67 if (m_scrollOrientation
!= orientation
) {
68 m_scrollOrientation
= orientation
;
73 Qt::Orientation
KItemListViewLayouter::scrollOrientation() const
75 return m_scrollOrientation
;
78 void KItemListViewLayouter::setSize(const QSizeF
& size
)
86 QSizeF
KItemListViewLayouter::size() const
91 void KItemListViewLayouter::setItemSize(const QSizeF
& size
)
93 if (m_itemSize
!= size
) {
99 QSizeF
KItemListViewLayouter::itemSize() const
104 void KItemListViewLayouter::setHeaderHeight(qreal height
)
106 if (m_headerHeight
!= height
) {
107 m_headerHeight
= height
;
112 qreal
KItemListViewLayouter::headerHeight() const
114 return m_headerHeight
;
117 void KItemListViewLayouter::setScrollOffset(qreal offset
)
119 if (m_scrollOffset
!= offset
) {
120 m_scrollOffset
= offset
;
121 m_visibleIndexesDirty
= true;
125 qreal
KItemListViewLayouter::scrollOffset() const
127 return m_scrollOffset
;
130 qreal
KItemListViewLayouter::maximumScrollOffset() const
132 const_cast<KItemListViewLayouter
*>(this)->doLayout();
133 return m_maximumScrollOffset
;
136 void KItemListViewLayouter::setItemOffset(qreal offset
)
138 if (m_itemOffset
!= offset
) {
139 m_itemOffset
= offset
;
140 m_visibleIndexesDirty
= true;
144 qreal
KItemListViewLayouter::itemOffset() const
149 qreal
KItemListViewLayouter::maximumItemOffset() const
151 const_cast<KItemListViewLayouter
*>(this)->doLayout();
152 return m_maximumItemOffset
;
155 void KItemListViewLayouter::setModel(const KItemModelBase
* model
)
157 if (m_model
!= model
) {
163 const KItemModelBase
* KItemListViewLayouter::model() const
168 void KItemListViewLayouter::setSizeHintResolver(const KItemListSizeHintResolver
* sizeHintResolver
)
170 if (m_sizeHintResolver
!= sizeHintResolver
) {
171 m_sizeHintResolver
= sizeHintResolver
;
176 const KItemListSizeHintResolver
* KItemListViewLayouter::sizeHintResolver() const
178 return m_sizeHintResolver
;
181 int KItemListViewLayouter::firstVisibleIndex() const
183 const_cast<KItemListViewLayouter
*>(this)->doLayout();
184 return m_firstVisibleIndex
;
187 int KItemListViewLayouter::lastVisibleIndex() const
189 const_cast<KItemListViewLayouter
*>(this)->doLayout();
190 return m_lastVisibleIndex
;
193 QRectF
KItemListViewLayouter::itemBoundingRect(int index
) const
195 const_cast<KItemListViewLayouter
*>(this)->doLayout();
196 if (index
< 0 || index
>= m_itemBoundingRects
.count()) {
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
;
211 QRectF bounds
= m_itemBoundingRects
[index
];
212 bounds
.moveTo(bounds
.topLeft() - QPointF(m_itemOffset
, m_scrollOffset
));
216 int KItemListViewLayouter::maximumVisibleItems() const
218 const_cast<KItemListViewLayouter
*>(this)->doLayout();
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) {
227 return rows
* m_columnCount
;
230 int KItemListViewLayouter::itemsPerOffset() const
232 return m_columnCount
;
235 bool KItemListViewLayouter::isFirstGroupItem(int itemIndex
) const
237 return m_groupIndexes
.contains(itemIndex
);
240 void KItemListViewLayouter::markAsDirty()
245 void KItemListViewLayouter::doLayout()
248 #ifdef KITEMLISTVIEWLAYOUTER_DEBUG
252 m_visibleIndexesDirty
= true;
254 QSizeF itemSize
= m_itemSize
;
255 QSizeF size
= m_size
;
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());
265 m_columnWidth
= itemSize
.width();
266 m_columnCount
= qMax(1, int(size
.width() / m_columnWidth
));
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
;
280 int rowCount
= itemCount
/ m_columnCount
;
281 if (itemCount
% m_columnCount
!= 0) {
285 m_itemBoundingRects
.reserve(itemCount
);
287 qreal y
= m_headerHeight
;
291 while (index
< itemCount
) {
293 qreal maxItemHeight
= itemSize
.height();
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
;
306 const QRectF
bounds(x
, y
, itemSize
.width(), requiredItemHeight
);
307 if (index
< m_itemBoundingRects
.count()) {
308 m_itemBoundingRects
[index
] = bounds
;
310 m_itemBoundingRects
.append(bounds
);
313 maxItemHeight
= qMax(maxItemHeight
, requiredItemHeight
);
322 if (m_itemBoundingRects
.count() > itemCount
) {
323 m_itemBoundingRects
.erase(m_itemBoundingRects
.begin() + itemCount
,
324 m_itemBoundingRects
.end());
328 m_maximumScrollOffset
= m_itemBoundingRects
.last().bottom();
329 m_maximumItemOffset
= m_columnCount
* m_columnWidth
;
331 m_maximumScrollOffset
= 0;
332 m_maximumItemOffset
= 0;
335 m_grouped
= m_model
->groupedSorting();
337 createGroupHeaders();
339 const int lastGroupItemCount
= m_model
->count() - m_groups
.last().firstItemIndex
;
340 m_maximumScrollOffset
= m_groups
.last().y
+ (lastGroupItemCount
/ m_columnCount
) * itemSize
.height();
341 if (lastGroupItemCount
% m_columnCount
!= 0) {
342 m_maximumScrollOffset
+= itemSize
.height();
346 #ifdef KITEMLISTVIEWLAYOUTER_DEBUG
347 kDebug() << "[TIME] doLayout() for " << m_model
->count() << "items:" << timer
.elapsed();
353 updateGroupedVisibleIndexes();
355 updateVisibleIndexes();
359 void KItemListViewLayouter::updateVisibleIndexes()
361 if (!m_visibleIndexesDirty
) {
365 Q_ASSERT(!m_grouped
);
368 if (m_model
->count() <= 0) {
369 m_firstVisibleIndex
= -1;
370 m_lastVisibleIndex
= -1;
371 m_visibleIndexesDirty
= false;
375 const bool horizontalScrolling
= (m_scrollOrientation
== Qt::Horizontal
);
376 const int minimumHeight
= horizontalScrolling
? m_itemSize
.width()
377 : m_itemSize
.height();
379 // Calculate the first visible index:
380 // 1. Guess the index by using the minimum row height
381 const int maxIndex
= m_model
->count() - 1;
382 m_firstVisibleIndex
= int(m_scrollOffset
/ minimumHeight
) * m_columnCount
;
384 // 2. Decrease the index by checking the real row heights
385 int prevRowIndex
= m_firstVisibleIndex
- m_columnCount
;
386 while (prevRowIndex
> maxIndex
) {
387 prevRowIndex
-= m_columnCount
;
390 const qreal top
= m_scrollOffset
+ m_headerHeight
;
391 while (prevRowIndex
>= 0 && m_itemBoundingRects
[prevRowIndex
].bottom() >= top
) {
392 m_firstVisibleIndex
= prevRowIndex
;
393 prevRowIndex
-= m_columnCount
;
395 m_firstVisibleIndex
= qBound(0, m_firstVisibleIndex
, maxIndex
);
397 // Calculate the last visible index
398 const int visibleHeight
= horizontalScrolling
? m_size
.width() : m_size
.height();
399 const qreal bottom
= m_scrollOffset
+ visibleHeight
;
400 m_lastVisibleIndex
= m_firstVisibleIndex
; // first visible row, first column
401 int nextRowIndex
= m_lastVisibleIndex
+ m_columnCount
;
402 while (nextRowIndex
<= maxIndex
&& m_itemBoundingRects
[nextRowIndex
].y() <= bottom
) {
403 m_lastVisibleIndex
= nextRowIndex
;
404 nextRowIndex
+= m_columnCount
;
406 m_lastVisibleIndex
+= m_columnCount
- 1; // move it to the last column
407 m_lastVisibleIndex
= qBound(0, m_lastVisibleIndex
, maxIndex
);
409 m_visibleIndexesDirty
= false;
412 void KItemListViewLayouter::updateGroupedVisibleIndexes()
414 if (!m_visibleIndexesDirty
) {
421 if (m_model
->count() <= 0) {
422 m_firstVisibleIndex
= -1;
423 m_lastVisibleIndex
= -1;
424 m_visibleIndexesDirty
= false;
428 // Find the first visible group
429 const int lastGroupIndex
= m_groups
.count() - 1;
430 int groupIndex
= lastGroupIndex
;
431 for (int i
= 1; i
< m_groups
.count(); ++i
) {
432 if (m_groups
[i
].y
>= m_scrollOffset
) {
438 // Calculate the first visible index
439 qreal groupY
= m_groups
[groupIndex
].y
;
440 m_firstVisibleIndex
= m_groups
[groupIndex
].firstItemIndex
;
441 const int invisibleRowCount
= int(m_scrollOffset
- groupY
) / int(m_itemSize
.height());
442 m_firstVisibleIndex
+= invisibleRowCount
* m_columnCount
;
443 if (groupIndex
+ 1 <= lastGroupIndex
) {
444 // Check whether the calculated first visible index remains inside the current
445 // group. If this is not the case let the first element of the next group be the first
447 const int nextGroupIndex
= m_groups
[groupIndex
+ 1].firstItemIndex
;
448 if (m_firstVisibleIndex
> nextGroupIndex
) {
449 m_firstVisibleIndex
= nextGroupIndex
;
453 m_firstVisibleGroupIndex
= groupIndex
;
455 const int maxIndex
= m_model
->count() - 1;
456 m_firstVisibleIndex
= qBound(0, m_firstVisibleIndex
, maxIndex
);
458 // Calculate the last visible index: Find group where the last visible item is shown.
459 const qreal visibleBottom
= m_scrollOffset
+ m_size
.height(); // TODO: respect Qt::Horizontal alignment
460 while ((groupIndex
< lastGroupIndex
) && (m_groups
[groupIndex
+ 1].y
< visibleBottom
)) {
464 groupY
= m_groups
[groupIndex
].y
;
465 m_lastVisibleIndex
= m_groups
[groupIndex
].firstItemIndex
;
466 const int availableHeight
= static_cast<int>(visibleBottom
- groupY
);
467 int visibleRowCount
= availableHeight
/ int(m_itemSize
.height());
468 if (availableHeight
% int(m_itemSize
.height()) != 0) {
471 m_lastVisibleIndex
+= visibleRowCount
* m_columnCount
- 1;
473 if (groupIndex
+ 1 <= lastGroupIndex
) {
474 // Check whether the calculate last visible index remains inside the current group.
475 // If this is not the case let the last element of this group be the last visible index.
476 const int nextGroupIndex
= m_groups
[groupIndex
+ 1].firstItemIndex
;
477 if (m_lastVisibleIndex
>= nextGroupIndex
) {
478 m_lastVisibleIndex
= nextGroupIndex
- 1;
481 m_lastVisibleIndex
= qBound(0, m_lastVisibleIndex
, maxIndex
);
483 m_visibleIndexesDirty
= false;
486 void KItemListViewLayouter::createGroupHeaders()
489 m_groupIndexes
.clear();
491 const QList
<QPair
<int, QVariant
> > groups
= m_model
->groups();
494 for (int i
= 0; i
< groups
.count(); ++i
) {
495 const int firstItemIndex
= groups
.at(i
).first
;
497 const int previousGroupItemCount
= firstItemIndex
- m_groups
.last().firstItemIndex
;
498 int previousGroupRowCount
= previousGroupItemCount
/ m_columnCount
;
499 if (previousGroupItemCount
% m_columnCount
!= 0) {
500 ++previousGroupRowCount
;
502 const qreal previousGroupHeight
= previousGroupRowCount
* m_itemSize
.height();
503 y
+= previousGroupHeight
;
508 itemGroup
.firstItemIndex
= firstItemIndex
;
511 m_groups
.append(itemGroup
);
512 m_groupIndexes
.insert(firstItemIndex
);
516 #include "kitemlistviewlayouter_p.moc"