#include <KDebug>
-#define KITEMLISTVIEWLAYOUTER_DEBUG
+// #define KITEMLISTVIEWLAYOUTER_DEBUG
KItemListViewLayouter::KItemListViewLayouter(QObject* parent) :
QObject(parent),
m_scrollOrientation(Qt::Vertical),
m_size(),
m_itemSize(128, 128),
+ m_itemMargin(),
m_headerHeight(0),
m_model(0),
m_sizeHintResolver(0),
return m_itemSize;
}
+void KItemListViewLayouter::setItemMargin(const QSizeF& margin)
+{
+ if (m_itemMargin != margin) {
+ m_itemMargin = margin;
+ m_dirty = true;
+ }
+}
+
+QSizeF KItemListViewLayouter::itemMargin() const
+{
+ return m_itemMargin;
+}
+
void KItemListViewLayouter::setHeaderHeight(qreal height)
{
if (m_headerHeight != height) {
QRectF KItemListViewLayouter::groupHeaderRect(int index) const
{
+ const_cast<KItemListViewLayouter*>(this)->doLayout();
+
const QRectF firstItemRect = itemRect(index);
QPointF pos = firstItemRect.topLeft();
if (pos.isNull()) {
pos.rx() = 0;
size = QSizeF(m_size.width(), m_groupHeaderHeight);
} else {
- size = QSizeF(firstItemRect.width(), m_groupHeaderHeight);
+ size = QSizeF(minimumGroupHeaderWidth(), m_groupHeaderHeight);
}
return QRectF(pos, size);
}
return rows * m_columnCount;
}
-int KItemListViewLayouter::itemsPerOffset() const
-{
- return m_columnCount;
-}
-
bool KItemListViewLayouter::isFirstGroupItem(int itemIndex) const
{
const_cast<KItemListViewLayouter*>(this)->doLayout();
m_visibleIndexesDirty = true;
QSizeF itemSize = m_itemSize;
+ QSizeF itemMargin = m_itemMargin;
QSizeF size = m_size;
+
+ const bool grouped = createGroupHeaders();
const bool horizontalScrolling = (m_scrollOrientation == Qt::Horizontal);
if (horizontalScrolling) {
+ // Flip everything so that the layout logically can work like having
+ // a vertical scrolling
itemSize.setWidth(m_itemSize.height());
itemSize.setHeight(m_itemSize.width());
+ itemMargin.setWidth(m_itemMargin.height());
+ itemMargin.setHeight(m_itemMargin.width());
size.setWidth(m_size.height());
size.setHeight(m_size.width());
+
+ if (grouped) {
+ // In the horizontal scrolling case all groups are aligned
+ // at the top, which decreases the available height. For the
+ // flipped data this means that the width must be decreased.
+ size.rwidth() -= m_groupHeaderHeight;
+ }
}
- m_columnWidth = itemSize.width();
- m_columnCount = qMax(1, int(size.width() / m_columnWidth));
- m_xPosInc = 0;
+ m_columnWidth = itemSize.width() + itemMargin.width();
+ const qreal widthForColumns = size.width() - itemMargin.width();
+ m_columnCount = qMax(1, int(widthForColumns / m_columnWidth));
+ m_xPosInc = itemMargin.width();
const int itemCount = m_model->count();
- if (itemCount > m_columnCount) {
+ if (itemCount > m_columnCount && m_columnWidth >= 32) {
// Apply the unused width equally to each column
const qreal unusedWidth = size.width() - m_columnCount * m_columnWidth;
if (unusedWidth > 0) {
- // [Comment #1] A cast to int is done on purpose to prevent rounding issues when
- // drawing pixmaps and drawing text or other graphic primitives: Qt uses a different
- // rastering algorithm for the upper/left of pixmaps
- const qreal columnInc = int(unusedWidth / (m_columnCount + 1));
+ const qreal columnInc = unusedWidth / (m_columnCount + 1);
m_columnWidth += columnInc;
m_xPosInc += columnInc;
}
m_itemRects.reserve(itemCount);
- qreal y = m_headerHeight;
+ qreal y = m_headerHeight + itemMargin.height();
int rowIndex = 0;
- const bool grouped = createGroupHeaders();
-
int index = 0;
while (index < itemCount) {
qreal x = m_xPosInc;
// (in average a character requires the halve width of the font height).
//
// TODO: Let the group headers provide a minimum width and respect this width here
- const qreal minimumGroupHeaderWidth = int(m_groupHeaderHeight * 15 / 2); // See [Comment #1]
- if (requiredItemHeight < minimumGroupHeaderWidth) {
- requiredItemHeight = minimumGroupHeaderWidth;
+ const qreal headerWidth = minimumGroupHeaderWidth();
+ if (requiredItemHeight < headerWidth) {
+ requiredItemHeight = headerWidth;
}
}
}
}
- y += maxItemHeight;
+ y += maxItemHeight + itemMargin.height();
++rowIndex;
}
if (m_itemRects.count() > itemCount) {
m_itemRects.erase(m_itemRects.begin() + itemCount,
- m_itemRects.end());
+ m_itemRects.end());
}
if (itemCount > 0) {
+ // Calculate the maximum y-range of the last row for m_maximumScrollOffset
m_maximumScrollOffset = m_itemRects.last().bottom();
+ const qreal rowY = m_itemRects.last().y();
+
+ int index = m_itemRects.count() - 2;
+ while (index >= 0 && m_itemRects.at(index).bottom() >= rowY) {
+ m_maximumScrollOffset = qMax(m_maximumScrollOffset, m_itemRects.at(index).bottom());
+ --index;
+ }
+
m_maximumItemOffset = m_columnCount * m_columnWidth;
} else {
m_maximumScrollOffset = 0;
const int maxIndex = m_model->count() - 1;
- // Calculate the first visible index that is (at least partly) visible
+ // Calculate the first visible index that is fully visible
int min = 0;
int max = maxIndex;
int mid = 0;
do {
mid = (min + max) / 2;
- if (m_itemRects[mid].bottom() < m_scrollOffset) {
+ if (m_itemRects[mid].top() < m_scrollOffset) {
min = mid + 1;
} else {
max = mid - 1;
}
} while (min <= max);
- while (mid < maxIndex && m_itemRects[mid].bottom() < m_scrollOffset) {
- ++mid;
+ if (mid > 0) {
+ // Include the row before the first fully visible index, as it might
+ // be partly visible
+ if (m_itemRects[mid].top() >= m_scrollOffset) {
+ --mid;
+ Q_ASSERT(m_itemRects[mid].top() < m_scrollOffset);
+ }
+
+ const qreal rowTop = m_itemRects[mid].top();
+ while (mid > 0 && m_itemRects[mid - 1].top() == rowTop) {
+ --mid;
+ }
}
m_firstVisibleIndex = mid;
return true;
}
+qreal KItemListViewLayouter::minimumGroupHeaderWidth() const
+{
+ return m_groupHeaderHeight * 15 / 2;
+}
+
#include "kitemlistviewlayouter_p.moc"