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
30 // TODO: Calculate dynamically
31 const int GroupHeaderHeight
= 50;
34 KItemListViewLayouter::KItemListViewLayouter(QObject
* parent
) :
37 m_visibleIndexesDirty(true),
38 m_scrollOrientation(Qt::Vertical
),
43 m_sizeHintResolver(0),
45 m_maximumScrollOffset(0),
47 m_maximumItemOffset(0),
48 m_firstVisibleIndex(-1),
49 m_lastVisibleIndex(-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::setScrollOffset(qreal offset
)
117 if (m_scrollOffset
!= offset
) {
118 m_scrollOffset
= offset
;
119 m_visibleIndexesDirty
= true;
123 qreal
KItemListViewLayouter::scrollOffset() const
125 return m_scrollOffset
;
128 qreal
KItemListViewLayouter::maximumScrollOffset() const
130 const_cast<KItemListViewLayouter
*>(this)->doLayout();
131 return m_maximumScrollOffset
;
134 void KItemListViewLayouter::setItemOffset(qreal offset
)
136 if (m_itemOffset
!= offset
) {
137 m_itemOffset
= offset
;
138 m_visibleIndexesDirty
= true;
142 qreal
KItemListViewLayouter::itemOffset() const
147 qreal
KItemListViewLayouter::maximumItemOffset() const
149 const_cast<KItemListViewLayouter
*>(this)->doLayout();
150 return m_maximumItemOffset
;
153 void KItemListViewLayouter::setModel(const KItemModelBase
* model
)
155 if (m_model
!= model
) {
161 const KItemModelBase
* KItemListViewLayouter::model() const
166 void KItemListViewLayouter::setSizeHintResolver(const KItemListSizeHintResolver
* sizeHintResolver
)
168 if (m_sizeHintResolver
!= sizeHintResolver
) {
169 m_sizeHintResolver
= sizeHintResolver
;
174 const KItemListSizeHintResolver
* KItemListViewLayouter::sizeHintResolver() const
176 return m_sizeHintResolver
;
179 int KItemListViewLayouter::firstVisibleIndex() const
181 const_cast<KItemListViewLayouter
*>(this)->doLayout();
182 return m_firstVisibleIndex
;
185 int KItemListViewLayouter::lastVisibleIndex() const
187 const_cast<KItemListViewLayouter
*>(this)->doLayout();
188 return m_lastVisibleIndex
;
191 QRectF
KItemListViewLayouter::itemBoundingRect(int index
) const
193 const_cast<KItemListViewLayouter
*>(this)->doLayout();
194 if (index
< 0 || index
>= m_itemBoundingRects
.count()) {
198 if (m_scrollOrientation
== Qt::Horizontal
) {
199 // Rotate the logical direction which is always vertical by 90°
200 // to get the physical horizontal direction
201 const QRectF
& b
= m_itemBoundingRects
[index
];
202 QRectF
bounds(b
.y(), b
.x(), b
.height(), b
.width());
203 QPointF pos
= bounds
.topLeft();
204 pos
.rx() -= m_scrollOffset
;
209 QRectF bounds
= m_itemBoundingRects
[index
];
210 bounds
.moveTo(bounds
.topLeft() - QPointF(m_itemOffset
, m_scrollOffset
));
214 int KItemListViewLayouter::maximumVisibleItems() const
216 const_cast<KItemListViewLayouter
*>(this)->doLayout();
218 const int height
= static_cast<int>(m_size
.height());
219 const int rowHeight
= static_cast<int>(m_itemSize
.height());
220 int rows
= height
/ rowHeight
;
221 if (height
% rowHeight
!= 0) {
225 return rows
* m_columnCount
;
228 int KItemListViewLayouter::itemsPerOffset() const
230 return m_columnCount
;
233 bool KItemListViewLayouter::isFirstGroupItem(int itemIndex
) const
235 return m_groupIndexes
.contains(itemIndex
);
238 void KItemListViewLayouter::markAsDirty()
243 void KItemListViewLayouter::doLayout()
246 #ifdef KITEMLISTVIEWLAYOUTER_DEBUG
250 m_visibleIndexesDirty
= true;
252 QSizeF itemSize
= m_itemSize
;
253 QSizeF size
= m_size
;
255 const bool horizontalScrolling
= (m_scrollOrientation
== Qt::Horizontal
);
256 if (horizontalScrolling
) {
257 itemSize
.setWidth(m_itemSize
.height());
258 itemSize
.setHeight(m_itemSize
.width());
259 size
.setWidth(m_size
.height());
260 size
.setHeight(m_size
.width());
263 m_columnWidth
= itemSize
.width();
264 m_columnCount
= qMax(1, int(size
.width() / m_columnWidth
));
267 const int itemCount
= m_model
->count();
268 if (itemCount
> m_columnCount
) {
269 // Apply the unused width equally to each column
270 const qreal unusedWidth
= size
.width() - m_columnCount
* m_columnWidth
;
271 if (unusedWidth
> 0) {
272 const qreal columnInc
= unusedWidth
/ (m_columnCount
+ 1);
273 m_columnWidth
+= columnInc
;
274 m_xPosInc
+= columnInc
;
278 int rowCount
= itemCount
/ m_columnCount
;
279 if (itemCount
% m_columnCount
!= 0) {
283 m_itemBoundingRects
.reserve(itemCount
);
285 qreal y
= m_headerHeight
;
288 const bool grouped
= createGroupHeaders();
290 int firstItemIndexOfGroup
= 0;
293 while (index
< itemCount
) {
295 qreal maxItemHeight
= itemSize
.height();
298 if (horizontalScrolling
) {
299 // All group headers will always be aligned on the top and not
300 // flipped like the other properties
301 x
+= GroupHeaderHeight
;
304 if (index
== firstItemIndexOfGroup
) {
305 if (!horizontalScrolling
) {
306 // The item is the first item of a group.
307 // Increase the y-position to provide space
308 // for the group header.
309 y
+= GroupHeaderHeight
;
312 // Calculate the index of the first item for
315 if (groupIndex
< m_groups
.count()) {
316 firstItemIndexOfGroup
= m_groups
.at(groupIndex
);
318 firstItemIndexOfGroup
= -1;
324 while (index
< itemCount
&& column
< m_columnCount
) {
325 qreal requiredItemHeight
= itemSize
.height();
326 if (m_sizeHintResolver
) {
327 const QSizeF sizeHint
= m_sizeHintResolver
->sizeHint(index
);
328 const qreal sizeHintHeight
= horizontalScrolling
? sizeHint
.width() : sizeHint
.height();
329 if (sizeHintHeight
> requiredItemHeight
) {
330 requiredItemHeight
= sizeHintHeight
;
334 const QRectF
bounds(x
, y
, itemSize
.width(), requiredItemHeight
);
335 if (index
< m_itemBoundingRects
.count()) {
336 m_itemBoundingRects
[index
] = bounds
;
338 m_itemBoundingRects
.append(bounds
);
341 maxItemHeight
= qMax(maxItemHeight
, requiredItemHeight
);
346 if (grouped
&& index
== firstItemIndexOfGroup
) {
347 // The item represents the first index of a group
348 // and must aligned in the first column
356 if (m_itemBoundingRects
.count() > itemCount
) {
357 m_itemBoundingRects
.erase(m_itemBoundingRects
.begin() + itemCount
,
358 m_itemBoundingRects
.end());
362 m_maximumScrollOffset
= m_itemBoundingRects
.last().bottom();
363 m_maximumItemOffset
= m_columnCount
* m_columnWidth
;
365 m_maximumScrollOffset
= 0;
366 m_maximumItemOffset
= 0;
369 #ifdef KITEMLISTVIEWLAYOUTER_DEBUG
370 kDebug() << "[TIME] doLayout() for " << m_model
->count() << "items:" << timer
.elapsed();
375 updateVisibleIndexes();
378 void KItemListViewLayouter::updateVisibleIndexes()
380 if (!m_visibleIndexesDirty
) {
386 if (m_model
->count() <= 0) {
387 m_firstVisibleIndex
= -1;
388 m_lastVisibleIndex
= -1;
389 m_visibleIndexesDirty
= false;
393 const int maxIndex
= m_model
->count() - 1;
395 // Calculate the first visible index that is (at least partly) visible
400 mid
= (min
+ max
) / 2;
401 if (m_itemBoundingRects
[mid
].bottom() < m_scrollOffset
) {
406 } while (min
<= max
);
408 while (mid
< maxIndex
&& m_itemBoundingRects
[mid
].bottom() < m_scrollOffset
) {
411 m_firstVisibleIndex
= mid
;
413 // Calculate the last visible index that is (at least partly) visible
414 const int visibleHeight
= (m_scrollOrientation
== Qt::Horizontal
) ? m_size
.width() : m_size
.height();
415 qreal bottom
= m_scrollOffset
+ visibleHeight
;
416 if (m_model
->groupedSorting()) {
417 bottom
+= GroupHeaderHeight
;
420 min
= m_firstVisibleIndex
;
423 mid
= (min
+ max
) / 2;
424 if (m_itemBoundingRects
[mid
].y() <= bottom
) {
429 } while (min
<= max
);
431 while (mid
> 0 && m_itemBoundingRects
[mid
].y() > bottom
) {
434 m_lastVisibleIndex
= mid
;
436 m_visibleIndexesDirty
= false;
439 bool KItemListViewLayouter::createGroupHeaders()
441 if (!m_model
->groupedSorting()) {
446 m_groupIndexes
.clear();
448 const QList
<QPair
<int, QVariant
> > groups
= m_model
->groups();
449 if (groups
.isEmpty()) {
453 m_groups
.reserve(groups
.count());
454 for (int i
= 0; i
< groups
.count(); ++i
) {
455 const int firstItemIndex
= groups
.at(i
).first
;
456 m_groups
.append(firstItemIndex
);
457 m_groupIndexes
.insert(firstItemIndex
);
463 #include "kitemlistviewlayouter_p.moc"