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
),
39 m_sizeHintResolver(0),
41 m_maximumScrollOffset(0),
43 m_maximumItemOffset(0),
44 m_firstVisibleIndex(-1),
45 m_lastVisibleIndex(-1),
50 m_groupHeaderHeight(0),
55 KItemListViewLayouter::~KItemListViewLayouter()
59 void KItemListViewLayouter::setScrollOrientation(Qt::Orientation orientation
)
61 if (m_scrollOrientation
!= orientation
) {
62 m_scrollOrientation
= orientation
;
67 Qt::Orientation
KItemListViewLayouter::scrollOrientation() const
69 return m_scrollOrientation
;
72 void KItemListViewLayouter::setSize(const QSizeF
& size
)
80 QSizeF
KItemListViewLayouter::size() const
85 void KItemListViewLayouter::setItemSize(const QSizeF
& size
)
87 if (m_itemSize
!= size
) {
93 QSizeF
KItemListViewLayouter::itemSize() const
98 void KItemListViewLayouter::setItemMargin(const QSizeF
& margin
)
100 if (m_itemMargin
!= margin
) {
101 m_itemMargin
= margin
;
106 QSizeF
KItemListViewLayouter::itemMargin() const
111 void KItemListViewLayouter::setHeaderHeight(qreal height
)
113 if (m_headerHeight
!= height
) {
114 m_headerHeight
= height
;
119 qreal
KItemListViewLayouter::headerHeight() const
121 return m_headerHeight
;
124 void KItemListViewLayouter::setGroupHeaderHeight(qreal height
)
126 if (m_groupHeaderHeight
!= height
) {
127 m_groupHeaderHeight
= height
;
132 qreal
KItemListViewLayouter::groupHeaderHeight() const
134 return m_groupHeaderHeight
;
137 void KItemListViewLayouter::setScrollOffset(qreal offset
)
139 if (m_scrollOffset
!= offset
) {
140 m_scrollOffset
= offset
;
141 m_visibleIndexesDirty
= true;
145 qreal
KItemListViewLayouter::scrollOffset() const
147 return m_scrollOffset
;
150 qreal
KItemListViewLayouter::maximumScrollOffset() const
152 const_cast<KItemListViewLayouter
*>(this)->doLayout();
153 return m_maximumScrollOffset
;
156 void KItemListViewLayouter::setItemOffset(qreal offset
)
158 if (m_itemOffset
!= offset
) {
159 m_itemOffset
= offset
;
160 m_visibleIndexesDirty
= true;
164 qreal
KItemListViewLayouter::itemOffset() const
169 qreal
KItemListViewLayouter::maximumItemOffset() const
171 const_cast<KItemListViewLayouter
*>(this)->doLayout();
172 return m_maximumItemOffset
;
175 void KItemListViewLayouter::setModel(const KItemModelBase
* model
)
177 if (m_model
!= model
) {
183 const KItemModelBase
* KItemListViewLayouter::model() const
188 void KItemListViewLayouter::setSizeHintResolver(const KItemListSizeHintResolver
* sizeHintResolver
)
190 if (m_sizeHintResolver
!= sizeHintResolver
) {
191 m_sizeHintResolver
= sizeHintResolver
;
196 const KItemListSizeHintResolver
* KItemListViewLayouter::sizeHintResolver() const
198 return m_sizeHintResolver
;
201 int KItemListViewLayouter::firstVisibleIndex() const
203 const_cast<KItemListViewLayouter
*>(this)->doLayout();
204 return m_firstVisibleIndex
;
207 int KItemListViewLayouter::lastVisibleIndex() const
209 const_cast<KItemListViewLayouter
*>(this)->doLayout();
210 return m_lastVisibleIndex
;
213 QRectF
KItemListViewLayouter::itemRect(int index
) const
215 const_cast<KItemListViewLayouter
*>(this)->doLayout();
216 if (index
< 0 || index
>= m_itemRects
.count()) {
220 if (m_scrollOrientation
== Qt::Horizontal
) {
221 // Rotate the logical direction which is always vertical by 90°
222 // to get the physical horizontal direction
223 const QRectF
& b
= m_itemRects
[index
];
224 QRectF
bounds(b
.y(), b
.x(), b
.height(), b
.width());
225 QPointF pos
= bounds
.topLeft();
226 pos
.rx() -= m_scrollOffset
;
231 QRectF bounds
= m_itemRects
[index
];
232 bounds
.moveTo(bounds
.topLeft() - QPointF(m_itemOffset
, m_scrollOffset
));
236 QRectF
KItemListViewLayouter::groupHeaderRect(int index
) const
238 const_cast<KItemListViewLayouter
*>(this)->doLayout();
240 const QRectF firstItemRect
= itemRect(index
);
241 QPointF pos
= firstItemRect
.topLeft();
246 pos
.ry() -= m_groupHeaderHeight
;
249 if (m_scrollOrientation
== Qt::Vertical
) {
251 size
= QSizeF(m_size
.width(), m_groupHeaderHeight
);
253 size
= QSizeF(minimumGroupHeaderWidth(), m_groupHeaderHeight
);
255 return QRectF(pos
, size
);
258 int KItemListViewLayouter::maximumVisibleItems() const
260 const_cast<KItemListViewLayouter
*>(this)->doLayout();
262 const int height
= static_cast<int>(m_size
.height());
263 const int rowHeight
= static_cast<int>(m_itemSize
.height());
264 int rows
= height
/ rowHeight
;
265 if (height
% rowHeight
!= 0) {
269 return rows
* m_columnCount
;
272 bool KItemListViewLayouter::isFirstGroupItem(int itemIndex
) const
274 const_cast<KItemListViewLayouter
*>(this)->doLayout();
275 return m_groupItemIndexes
.contains(itemIndex
);
278 void KItemListViewLayouter::markAsDirty()
283 void KItemListViewLayouter::doLayout()
286 #ifdef KITEMLISTVIEWLAYOUTER_DEBUG
290 m_visibleIndexesDirty
= true;
292 QSizeF itemSize
= m_itemSize
;
293 QSizeF itemMargin
= m_itemMargin
;
294 QSizeF size
= m_size
;
296 const bool grouped
= createGroupHeaders();
298 const bool horizontalScrolling
= (m_scrollOrientation
== Qt::Horizontal
);
299 if (horizontalScrolling
) {
300 // Flip everything so that the layout logically can work like having
301 // a vertical scrolling
302 itemSize
.setWidth(m_itemSize
.height());
303 itemSize
.setHeight(m_itemSize
.width());
304 itemMargin
.setWidth(m_itemMargin
.height());
305 itemMargin
.setHeight(m_itemMargin
.width());
306 size
.setWidth(m_size
.height());
307 size
.setHeight(m_size
.width());
310 // In the horizontal scrolling case all groups are aligned
311 // at the top, which decreases the available height. For the
312 // flipped data this means that the width must be decreased.
313 size
.rwidth() -= m_groupHeaderHeight
;
317 m_columnWidth
= itemSize
.width() + itemMargin
.width();
318 const qreal widthForColumns
= size
.width() - itemMargin
.width();
319 m_columnCount
= qMax(1, int(widthForColumns
/ m_columnWidth
));
320 m_xPosInc
= itemMargin
.width();
322 const int itemCount
= m_model
->count();
323 if (itemCount
> m_columnCount
&& m_columnWidth
>= 32) {
324 // Apply the unused width equally to each column
325 const qreal unusedWidth
= size
.width() - m_columnCount
* m_columnWidth
;
326 if (unusedWidth
> 0) {
327 const qreal columnInc
= unusedWidth
/ (m_columnCount
+ 1);
328 m_columnWidth
+= columnInc
;
329 m_xPosInc
+= columnInc
;
333 int rowCount
= itemCount
/ m_columnCount
;
334 if (itemCount
% m_columnCount
!= 0) {
338 m_itemRects
.reserve(itemCount
);
340 qreal y
= m_headerHeight
+ itemMargin
.height();
344 while (index
< itemCount
) {
346 qreal maxItemHeight
= itemSize
.height();
349 if (horizontalScrolling
) {
350 // All group headers will always be aligned on the top and not
351 // flipped like the other properties
352 x
+= m_groupHeaderHeight
;
355 if (m_groupItemIndexes
.contains(index
)) {
356 if (!horizontalScrolling
) {
357 // The item is the first item of a group.
358 // Increase the y-position to provide space
359 // for the group header.
360 y
+= m_groupHeaderHeight
;
366 while (index
< itemCount
&& column
< m_columnCount
) {
367 qreal requiredItemHeight
= itemSize
.height();
368 if (m_sizeHintResolver
) {
369 const QSizeF sizeHint
= m_sizeHintResolver
->sizeHint(index
);
370 const qreal sizeHintHeight
= horizontalScrolling
? sizeHint
.width() : sizeHint
.height();
371 if (sizeHintHeight
> requiredItemHeight
) {
372 requiredItemHeight
= sizeHintHeight
;
376 const QRectF
bounds(x
, y
, itemSize
.width(), requiredItemHeight
);
377 if (index
< m_itemRects
.count()) {
378 m_itemRects
[index
] = bounds
;
380 m_itemRects
.append(bounds
);
383 if (grouped
&& horizontalScrolling
) {
384 // When grouping is enabled in the horizontal mode, the header alignment
386 // Header-1 Header-2 Header-3
387 // Item 1 Item 4 Item 7
388 // Item 2 Item 5 Item 8
389 // Item 3 Item 6 Item 9
390 // In this case 'requiredItemHeight' represents the column-width. We don't
391 // check the content of the header in the layouter to determine the required
392 // width, hence assure that at least a minimal width of 15 characters is given
393 // (in average a character requires the halve width of the font height).
395 // TODO: Let the group headers provide a minimum width and respect this width here
396 const qreal headerWidth
= minimumGroupHeaderWidth();
397 if (requiredItemHeight
< headerWidth
) {
398 requiredItemHeight
= headerWidth
;
402 maxItemHeight
= qMax(maxItemHeight
, requiredItemHeight
);
407 if (grouped
&& m_groupItemIndexes
.contains(index
)) {
408 // The item represents the first index of a group
409 // and must aligned in the first column
414 y
+= maxItemHeight
+ itemMargin
.height();
417 if (m_itemRects
.count() > itemCount
) {
418 m_itemRects
.erase(m_itemRects
.begin() + itemCount
,
423 // Calculate the maximum y-range of the last row for m_maximumScrollOffset
424 m_maximumScrollOffset
= m_itemRects
.last().bottom();
425 const qreal rowY
= m_itemRects
.last().y();
427 int index
= m_itemRects
.count() - 2;
428 while (index
>= 0 && m_itemRects
.at(index
).bottom() >= rowY
) {
429 m_maximumScrollOffset
= qMax(m_maximumScrollOffset
, m_itemRects
.at(index
).bottom());
433 m_maximumItemOffset
= m_columnCount
* m_columnWidth
;
435 m_maximumScrollOffset
= 0;
436 m_maximumItemOffset
= 0;
439 #ifdef KITEMLISTVIEWLAYOUTER_DEBUG
440 kDebug() << "[TIME] doLayout() for " << m_model
->count() << "items:" << timer
.elapsed();
445 updateVisibleIndexes();
448 void KItemListViewLayouter::updateVisibleIndexes()
450 if (!m_visibleIndexesDirty
) {
456 if (m_model
->count() <= 0) {
457 m_firstVisibleIndex
= -1;
458 m_lastVisibleIndex
= -1;
459 m_visibleIndexesDirty
= false;
463 const int maxIndex
= m_model
->count() - 1;
465 // Calculate the first visible index that is fully visible
470 mid
= (min
+ max
) / 2;
471 if (m_itemRects
[mid
].top() < m_scrollOffset
) {
476 } while (min
<= max
);
479 // Include the row before the first fully visible index, as it might
481 if (m_itemRects
[mid
].top() >= m_scrollOffset
) {
483 Q_ASSERT(m_itemRects
[mid
].top() < m_scrollOffset
);
486 const qreal rowTop
= m_itemRects
[mid
].top();
487 while (mid
> 0 && m_itemRects
[mid
- 1].top() == rowTop
) {
491 m_firstVisibleIndex
= mid
;
493 // Calculate the last visible index that is (at least partly) visible
494 const int visibleHeight
= (m_scrollOrientation
== Qt::Horizontal
) ? m_size
.width() : m_size
.height();
495 qreal bottom
= m_scrollOffset
+ visibleHeight
;
496 if (m_model
->groupedSorting()) {
497 bottom
+= m_groupHeaderHeight
;
500 min
= m_firstVisibleIndex
;
503 mid
= (min
+ max
) / 2;
504 if (m_itemRects
[mid
].y() <= bottom
) {
509 } while (min
<= max
);
511 while (mid
> 0 && m_itemRects
[mid
].y() > bottom
) {
514 m_lastVisibleIndex
= mid
;
516 m_visibleIndexesDirty
= false;
519 bool KItemListViewLayouter::createGroupHeaders()
521 if (!m_model
->groupedSorting()) {
525 m_groupItemIndexes
.clear();
527 const QList
<QPair
<int, QVariant
> > groups
= m_model
->groups();
528 if (groups
.isEmpty()) {
532 for (int i
= 0; i
< groups
.count(); ++i
) {
533 const int firstItemIndex
= groups
.at(i
).first
;
534 m_groupItemIndexes
.insert(firstItemIndex
);
540 qreal
KItemListViewLayouter::minimumGroupHeaderWidth() const
542 return m_groupHeaderHeight
* 15 / 2;
545 #include "kitemlistviewlayouter_p.moc"