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
29 KItemListViewLayouter::KItemListViewLayouter(QObject
* parent
) :
32 m_visibleIndexesDirty(true),
33 m_scrollOrientation(Qt::Vertical
),
38 m_sizeHintResolver(0),
40 m_maximumScrollOffset(0),
42 m_maximumItemOffset(0),
43 m_firstVisibleIndex(-1),
44 m_lastVisibleIndex(-1),
49 m_groupHeaderHeight(0),
54 KItemListViewLayouter::~KItemListViewLayouter()
58 void KItemListViewLayouter::setScrollOrientation(Qt::Orientation orientation
)
60 if (m_scrollOrientation
!= orientation
) {
61 m_scrollOrientation
= orientation
;
66 Qt::Orientation
KItemListViewLayouter::scrollOrientation() const
68 return m_scrollOrientation
;
71 void KItemListViewLayouter::setSize(const QSizeF
& size
)
79 QSizeF
KItemListViewLayouter::size() const
84 void KItemListViewLayouter::setItemSize(const QSizeF
& size
)
86 if (m_itemSize
!= size
) {
92 QSizeF
KItemListViewLayouter::itemSize() const
97 void KItemListViewLayouter::setHeaderHeight(qreal height
)
99 if (m_headerHeight
!= height
) {
100 m_headerHeight
= height
;
105 qreal
KItemListViewLayouter::headerHeight() const
107 return m_headerHeight
;
110 void KItemListViewLayouter::setGroupHeaderHeight(qreal height
)
112 if (m_groupHeaderHeight
!= height
) {
113 m_groupHeaderHeight
= height
;
118 qreal
KItemListViewLayouter::groupHeaderHeight() const
120 return m_groupHeaderHeight
;
123 void KItemListViewLayouter::setScrollOffset(qreal offset
)
125 if (m_scrollOffset
!= offset
) {
126 m_scrollOffset
= offset
;
127 m_visibleIndexesDirty
= true;
131 qreal
KItemListViewLayouter::scrollOffset() const
133 return m_scrollOffset
;
136 qreal
KItemListViewLayouter::maximumScrollOffset() const
138 const_cast<KItemListViewLayouter
*>(this)->doLayout();
139 return m_maximumScrollOffset
;
142 void KItemListViewLayouter::setItemOffset(qreal offset
)
144 if (m_itemOffset
!= offset
) {
145 m_itemOffset
= offset
;
146 m_visibleIndexesDirty
= true;
150 qreal
KItemListViewLayouter::itemOffset() const
155 qreal
KItemListViewLayouter::maximumItemOffset() const
157 const_cast<KItemListViewLayouter
*>(this)->doLayout();
158 return m_maximumItemOffset
;
161 void KItemListViewLayouter::setModel(const KItemModelBase
* model
)
163 if (m_model
!= model
) {
169 const KItemModelBase
* KItemListViewLayouter::model() const
174 void KItemListViewLayouter::setSizeHintResolver(const KItemListSizeHintResolver
* sizeHintResolver
)
176 if (m_sizeHintResolver
!= sizeHintResolver
) {
177 m_sizeHintResolver
= sizeHintResolver
;
182 const KItemListSizeHintResolver
* KItemListViewLayouter::sizeHintResolver() const
184 return m_sizeHintResolver
;
187 int KItemListViewLayouter::firstVisibleIndex() const
189 const_cast<KItemListViewLayouter
*>(this)->doLayout();
190 return m_firstVisibleIndex
;
193 int KItemListViewLayouter::lastVisibleIndex() const
195 const_cast<KItemListViewLayouter
*>(this)->doLayout();
196 return m_lastVisibleIndex
;
199 QRectF
KItemListViewLayouter::itemRect(int index
) const
201 const_cast<KItemListViewLayouter
*>(this)->doLayout();
202 if (index
< 0 || index
>= m_itemRects
.count()) {
206 if (m_scrollOrientation
== Qt::Horizontal
) {
207 // Rotate the logical direction which is always vertical by 90°
208 // to get the physical horizontal direction
209 const QRectF
& b
= m_itemRects
[index
];
210 QRectF
bounds(b
.y(), b
.x(), b
.height(), b
.width());
211 QPointF pos
= bounds
.topLeft();
212 pos
.rx() -= m_scrollOffset
;
217 QRectF bounds
= m_itemRects
[index
];
218 bounds
.moveTo(bounds
.topLeft() - QPointF(m_itemOffset
, m_scrollOffset
));
222 QRectF
KItemListViewLayouter::groupHeaderRect(int index
) const
224 const QRectF firstItemRect
= itemRect(index
);
225 QPointF pos
= firstItemRect
.topLeft();
230 pos
.ry() -= m_groupHeaderHeight
;
233 if (m_scrollOrientation
== Qt::Vertical
) {
235 size
= QSizeF(m_size
.width(), m_groupHeaderHeight
);
237 size
= QSizeF(firstItemRect
.width(), m_groupHeaderHeight
);
239 return QRectF(pos
, size
);
242 int KItemListViewLayouter::maximumVisibleItems() const
244 const_cast<KItemListViewLayouter
*>(this)->doLayout();
246 const int height
= static_cast<int>(m_size
.height());
247 const int rowHeight
= static_cast<int>(m_itemSize
.height());
248 int rows
= height
/ rowHeight
;
249 if (height
% rowHeight
!= 0) {
253 return rows
* m_columnCount
;
256 bool KItemListViewLayouter::isFirstGroupItem(int itemIndex
) const
258 const_cast<KItemListViewLayouter
*>(this)->doLayout();
259 return m_groupItemIndexes
.contains(itemIndex
);
262 void KItemListViewLayouter::markAsDirty()
267 void KItemListViewLayouter::doLayout()
270 #ifdef KITEMLISTVIEWLAYOUTER_DEBUG
274 m_visibleIndexesDirty
= true;
276 QSizeF itemSize
= m_itemSize
;
277 QSizeF size
= m_size
;
279 const bool horizontalScrolling
= (m_scrollOrientation
== Qt::Horizontal
);
280 if (horizontalScrolling
) {
281 itemSize
.setWidth(m_itemSize
.height());
282 itemSize
.setHeight(m_itemSize
.width());
283 size
.setWidth(m_size
.height());
284 size
.setHeight(m_size
.width());
287 m_columnWidth
= itemSize
.width();
288 m_columnCount
= qMax(1, int(size
.width() / m_columnWidth
));
291 const int itemCount
= m_model
->count();
292 if (itemCount
> m_columnCount
) {
293 // Apply the unused width equally to each column
294 const qreal unusedWidth
= size
.width() - m_columnCount
* m_columnWidth
;
295 if (unusedWidth
> 0) {
296 // [Comment #1] A cast to int is done on purpose to prevent rounding issues when
297 // drawing pixmaps and drawing text or other graphic primitives: Qt uses a different
298 // rastering algorithm for the upper/left of pixmaps
299 const qreal columnInc
= int(unusedWidth
/ (m_columnCount
+ 1));
300 m_columnWidth
+= columnInc
;
301 m_xPosInc
+= columnInc
;
305 int rowCount
= itemCount
/ m_columnCount
;
306 if (itemCount
% m_columnCount
!= 0) {
310 m_itemRects
.reserve(itemCount
);
312 qreal y
= m_headerHeight
;
315 const bool grouped
= createGroupHeaders();
318 while (index
< itemCount
) {
320 qreal maxItemHeight
= itemSize
.height();
323 if (horizontalScrolling
) {
324 // All group headers will always be aligned on the top and not
325 // flipped like the other properties
326 x
+= m_groupHeaderHeight
;
329 if (m_groupItemIndexes
.contains(index
)) {
330 if (!horizontalScrolling
) {
331 // The item is the first item of a group.
332 // Increase the y-position to provide space
333 // for the group header.
334 y
+= m_groupHeaderHeight
;
340 while (index
< itemCount
&& column
< m_columnCount
) {
341 qreal requiredItemHeight
= itemSize
.height();
342 if (m_sizeHintResolver
) {
343 const QSizeF sizeHint
= m_sizeHintResolver
->sizeHint(index
);
344 const qreal sizeHintHeight
= horizontalScrolling
? sizeHint
.width() : sizeHint
.height();
345 if (sizeHintHeight
> requiredItemHeight
) {
346 requiredItemHeight
= sizeHintHeight
;
350 const QRectF
bounds(x
, y
, itemSize
.width(), requiredItemHeight
);
351 if (index
< m_itemRects
.count()) {
352 m_itemRects
[index
] = bounds
;
354 m_itemRects
.append(bounds
);
357 if (grouped
&& horizontalScrolling
) {
358 // When grouping is enabled in the horizontal mode, the header alignment
360 // Header-1 Header-2 Header-3
361 // Item 1 Item 4 Item 7
362 // Item 2 Item 5 Item 8
363 // Item 3 Item 6 Item 9
364 // In this case 'requiredItemHeight' represents the column-width. We don't
365 // check the content of the header in the layouter to determine the required
366 // width, hence assure that at least a minimal width of 15 characters is given
367 // (in average a character requires the halve width of the font height).
369 // TODO: Let the group headers provide a minimum width and respect this width here
370 const qreal minimumGroupHeaderWidth
= int(m_groupHeaderHeight
* 15 / 2); // See [Comment #1]
371 if (requiredItemHeight
< minimumGroupHeaderWidth
) {
372 requiredItemHeight
= minimumGroupHeaderWidth
;
376 maxItemHeight
= qMax(maxItemHeight
, requiredItemHeight
);
381 if (grouped
&& m_groupItemIndexes
.contains(index
)) {
382 // The item represents the first index of a group
383 // and must aligned in the first column
391 if (m_itemRects
.count() > itemCount
) {
392 m_itemRects
.erase(m_itemRects
.begin() + itemCount
,
397 // Calculate the maximum y-range of the last row for m_maximumScrollOffset
398 m_maximumScrollOffset
= m_itemRects
.last().bottom();
399 const qreal rowY
= m_itemRects
.last().y();
401 int index
= m_itemRects
.count() - 2;
402 while (index
>= 0 && m_itemRects
.at(index
).bottom() >= rowY
) {
403 m_maximumScrollOffset
= qMax(m_maximumScrollOffset
, m_itemRects
.at(index
).bottom());
407 m_maximumItemOffset
= m_columnCount
* m_columnWidth
;
409 m_maximumScrollOffset
= 0;
410 m_maximumItemOffset
= 0;
413 #ifdef KITEMLISTVIEWLAYOUTER_DEBUG
414 kDebug() << "[TIME] doLayout() for " << m_model
->count() << "items:" << timer
.elapsed();
419 updateVisibleIndexes();
422 void KItemListViewLayouter::updateVisibleIndexes()
424 if (!m_visibleIndexesDirty
) {
430 if (m_model
->count() <= 0) {
431 m_firstVisibleIndex
= -1;
432 m_lastVisibleIndex
= -1;
433 m_visibleIndexesDirty
= false;
437 const int maxIndex
= m_model
->count() - 1;
439 // Calculate the first visible index that is fully visible
444 mid
= (min
+ max
) / 2;
445 if (m_itemRects
[mid
].top() < m_scrollOffset
) {
450 } while (min
<= max
);
453 // Include the row before the first fully visible index, as it might
455 if (m_itemRects
[mid
].top() >= m_scrollOffset
) {
457 Q_ASSERT(m_itemRects
[mid
].top() < m_scrollOffset
);
460 const qreal rowTop
= m_itemRects
[mid
].top();
461 while (mid
> 0 && m_itemRects
[mid
- 1].top() == rowTop
) {
465 m_firstVisibleIndex
= mid
;
467 // Calculate the last visible index that is (at least partly) visible
468 const int visibleHeight
= (m_scrollOrientation
== Qt::Horizontal
) ? m_size
.width() : m_size
.height();
469 qreal bottom
= m_scrollOffset
+ visibleHeight
;
470 if (m_model
->groupedSorting()) {
471 bottom
+= m_groupHeaderHeight
;
474 min
= m_firstVisibleIndex
;
477 mid
= (min
+ max
) / 2;
478 if (m_itemRects
[mid
].y() <= bottom
) {
483 } while (min
<= max
);
485 while (mid
> 0 && m_itemRects
[mid
].y() > bottom
) {
488 m_lastVisibleIndex
= mid
;
490 m_visibleIndexesDirty
= false;
493 bool KItemListViewLayouter::createGroupHeaders()
495 if (!m_model
->groupedSorting()) {
499 m_groupItemIndexes
.clear();
501 const QList
<QPair
<int, QVariant
> > groups
= m_model
->groups();
502 if (groups
.isEmpty()) {
506 for (int i
= 0; i
< groups
.count(); ++i
) {
507 const int firstItemIndex
= groups
.at(i
).first
;
508 m_groupItemIndexes
.insert(firstItemIndex
);
514 #include "kitemlistviewlayouter_p.moc"