diff options
| author | Peter Penz <[email protected]> | 2012-04-11 16:06:18 +0200 |
|---|---|---|
| committer | Peter Penz <[email protected]> | 2012-04-11 16:08:32 +0200 |
| commit | 6c3d9acbc22ea9463ba40ef84c9e8c8419dfacf3 (patch) | |
| tree | e7ffd63acd5e28eb71a077f816a23534b06fcae2 /src/kitemviews/private/kitemlistviewlayouter.cpp | |
| parent | d9dbd3398a258d04ec4517fd13e795b437c869d6 (diff) | |
KItemViews: Internal directory restructuration
- Move all private headers from the kitemviews-directory into
the 'private' subdirectory.
- Get rid of DolphinDirLister and just use a directory-lister
internally in KFileItemModel.
- Minor interface-cleanups for signals
Diffstat (limited to 'src/kitemviews/private/kitemlistviewlayouter.cpp')
| -rw-r--r-- | src/kitemviews/private/kitemlistviewlayouter.cpp | 630 |
1 files changed, 630 insertions, 0 deletions
diff --git a/src/kitemviews/private/kitemlistviewlayouter.cpp b/src/kitemviews/private/kitemlistviewlayouter.cpp new file mode 100644 index 000000000..c15b44e13 --- /dev/null +++ b/src/kitemviews/private/kitemlistviewlayouter.cpp @@ -0,0 +1,630 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "kitemlistviewlayouter.h" + +#include <kitemviews/kitemmodelbase.h> +#include "kitemlistsizehintresolver.h" + +#include <KDebug> + +// #define KITEMLISTVIEWLAYOUTER_DEBUG + +KItemListViewLayouter::KItemListViewLayouter(QObject* parent) : + QObject(parent), + m_dirty(true), + m_visibleIndexesDirty(true), + m_scrollOrientation(Qt::Vertical), + m_size(), + m_itemSize(128, 128), + m_itemMargin(), + m_headerHeight(0), + m_model(0), + m_sizeHintResolver(0), + m_scrollOffset(0), + m_maximumScrollOffset(0), + m_itemOffset(0), + m_maximumItemOffset(0), + m_firstVisibleIndex(-1), + m_lastVisibleIndex(-1), + m_columnWidth(0), + m_xPosInc(0), + m_columnCount(0), + m_groupItemIndexes(), + m_groupHeaderHeight(0), + m_groupHeaderMargin(0), + m_itemInfos() +{ +} + +KItemListViewLayouter::~KItemListViewLayouter() +{ +} + +void KItemListViewLayouter::setScrollOrientation(Qt::Orientation orientation) +{ + if (m_scrollOrientation != orientation) { + m_scrollOrientation = orientation; + m_dirty = true; + } +} + +Qt::Orientation KItemListViewLayouter::scrollOrientation() const +{ + return m_scrollOrientation; +} + +void KItemListViewLayouter::setSize(const QSizeF& size) +{ + if (m_size != size) { + m_size = size; + m_dirty = true; + } +} + +QSizeF KItemListViewLayouter::size() const +{ + return m_size; +} + +void KItemListViewLayouter::setItemSize(const QSizeF& size) +{ + if (m_itemSize != size) { + m_itemSize = size; + m_dirty = true; + } +} + +QSizeF KItemListViewLayouter::itemSize() const +{ + 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) { + m_headerHeight = height; + m_dirty = true; + } +} + +qreal KItemListViewLayouter::headerHeight() const +{ + return m_headerHeight; +} + +void KItemListViewLayouter::setGroupHeaderHeight(qreal height) +{ + if (m_groupHeaderHeight != height) { + m_groupHeaderHeight = height; + m_dirty = true; + } +} + +qreal KItemListViewLayouter::groupHeaderHeight() const +{ + return m_groupHeaderHeight; +} + +void KItemListViewLayouter::setGroupHeaderMargin(qreal margin) +{ + if (m_groupHeaderMargin != margin) { + m_groupHeaderMargin = margin; + m_dirty = true; + } +} + +qreal KItemListViewLayouter::groupHeaderMargin() const +{ + return m_groupHeaderMargin; +} + +void KItemListViewLayouter::setScrollOffset(qreal offset) +{ + if (m_scrollOffset != offset) { + m_scrollOffset = offset; + m_visibleIndexesDirty = true; + } +} + +qreal KItemListViewLayouter::scrollOffset() const +{ + return m_scrollOffset; +} + +qreal KItemListViewLayouter::maximumScrollOffset() const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + return m_maximumScrollOffset; +} + +void KItemListViewLayouter::setItemOffset(qreal offset) +{ + if (m_itemOffset != offset) { + m_itemOffset = offset; + m_visibleIndexesDirty = true; + } +} + +qreal KItemListViewLayouter::itemOffset() const +{ + return m_itemOffset; +} + +qreal KItemListViewLayouter::maximumItemOffset() const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + return m_maximumItemOffset; +} + +void KItemListViewLayouter::setModel(const KItemModelBase* model) +{ + if (m_model != model) { + m_model = model; + m_dirty = true; + } +} + +const KItemModelBase* KItemListViewLayouter::model() const +{ + return m_model; +} + +void KItemListViewLayouter::setSizeHintResolver(const KItemListSizeHintResolver* sizeHintResolver) +{ + if (m_sizeHintResolver != sizeHintResolver) { + m_sizeHintResolver = sizeHintResolver; + m_dirty = true; + } +} + +const KItemListSizeHintResolver* KItemListViewLayouter::sizeHintResolver() const +{ + return m_sizeHintResolver; +} + +int KItemListViewLayouter::firstVisibleIndex() const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + return m_firstVisibleIndex; +} + +int KItemListViewLayouter::lastVisibleIndex() const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + return m_lastVisibleIndex; +} + +QRectF KItemListViewLayouter::itemRect(int index) const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + if (index < 0 || index >= m_itemInfos.count()) { + return QRectF(); + } + + if (m_scrollOrientation == Qt::Horizontal) { + // Rotate the logical direction which is always vertical by 90° + // to get the physical horizontal direction + const QRectF& b = m_itemInfos[index].rect; + QRectF bounds(b.y(), b.x(), b.height(), b.width()); + QPointF pos = bounds.topLeft(); + pos.rx() -= m_scrollOffset; + bounds.moveTo(pos); + return bounds; + } + + QRectF bounds = m_itemInfos[index].rect; + bounds.moveTo(bounds.topLeft() - QPointF(m_itemOffset, m_scrollOffset)); + return bounds; +} + +QRectF KItemListViewLayouter::groupHeaderRect(int index) const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + + const QRectF firstItemRect = itemRect(index); + QPointF pos = firstItemRect.topLeft(); + if (pos.isNull()) { + return QRectF(); + } + + QSizeF size; + if (m_scrollOrientation == Qt::Vertical) { + pos.rx() = 0; + pos.ry() -= m_groupHeaderHeight; + size = QSizeF(m_size.width(), m_groupHeaderHeight); + } else { + pos.rx() -= m_itemMargin.width(); + pos.ry() = 0; + + // Determine the maximum width used in the + // current column. As the scroll-direction is + // Qt::Horizontal and m_itemRects is accessed directly, + // the logical height represents the visual width. + qreal width = minimumGroupHeaderWidth(); + const qreal y = m_itemInfos[index].rect.y(); + const int maxIndex = m_itemInfos.count() - 1; + while (index <= maxIndex) { + QRectF bounds = m_itemInfos[index].rect; + if (bounds.y() != y) { + break; + } + + if (bounds.height() > width) { + width = bounds.height(); + } + + ++index; + } + + size = QSizeF(width, m_size.height()); + } + return QRectF(pos, size); +} + +int KItemListViewLayouter::itemColumn(int index) const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + if (index < 0 || index >= m_itemInfos.count()) { + return -1; + } + + return (m_scrollOrientation == Qt::Vertical) + ? m_itemInfos[index].column + : m_itemInfos[index].row; +} + +int KItemListViewLayouter::itemRow(int index) const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + if (index < 0 || index >= m_itemInfos.count()) { + return -1; + } + + return (m_scrollOrientation == Qt::Vertical) + ? m_itemInfos[index].row + : m_itemInfos[index].column; +} + +int KItemListViewLayouter::maximumVisibleItems() const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + + const int height = static_cast<int>(m_size.height()); + const int rowHeight = static_cast<int>(m_itemSize.height()); + int rows = height / rowHeight; + if (height % rowHeight != 0) { + ++rows; + } + + return rows * m_columnCount; +} + +bool KItemListViewLayouter::isFirstGroupItem(int itemIndex) const +{ + const_cast<KItemListViewLayouter*>(this)->doLayout(); + return m_groupItemIndexes.contains(itemIndex); +} + +void KItemListViewLayouter::markAsDirty() +{ + m_dirty = true; +} + + +#ifndef QT_NO_DEBUG + bool KItemListViewLayouter::isDirty() + { + return m_dirty; + } +#endif + +void KItemListViewLayouter::doLayout() +{ + if (m_dirty) { +#ifdef KITEMLISTVIEWLAYOUTER_DEBUG + QElapsedTimer timer; + timer.start(); +#endif + 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() + 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 && m_columnWidth >= 32) { + // Apply the unused width equally to each column + const qreal unusedWidth = widthForColumns - m_columnCount * m_columnWidth; + if (unusedWidth > 0) { + const qreal columnInc = unusedWidth / (m_columnCount + 1); + m_columnWidth += columnInc; + m_xPosInc += columnInc; + } + } + + int rowCount = itemCount / m_columnCount; + if (itemCount % m_columnCount != 0) { + ++rowCount; + } + + m_itemInfos.reserve(itemCount); + + qreal y = m_headerHeight + itemMargin.height(); + int row = 0; + + int index = 0; + while (index < itemCount) { + qreal x = m_xPosInc; + qreal maxItemHeight = itemSize.height(); + + if (grouped) { + if (horizontalScrolling) { + // All group headers will always be aligned on the top and not + // flipped like the other properties + x += m_groupHeaderHeight; + } + + if (m_groupItemIndexes.contains(index)) { + // The item is the first item of a group. + // Increase the y-position to provide space + // for the group header. + if (index > 0) { + // Only add a margin if there has been added another + // group already before + y += m_groupHeaderMargin; + } else if (!horizontalScrolling) { + // The first group header should be aligned on top + y -= itemMargin.height(); + } + + if (!horizontalScrolling) { + y += m_groupHeaderHeight; + } + } + } + + int column = 0; + while (index < itemCount && column < m_columnCount) { + qreal requiredItemHeight = itemSize.height(); + if (m_sizeHintResolver) { + const QSizeF sizeHint = m_sizeHintResolver->sizeHint(index); + const qreal sizeHintHeight = horizontalScrolling ? sizeHint.width() : sizeHint.height(); + if (sizeHintHeight > requiredItemHeight) { + requiredItemHeight = sizeHintHeight; + } + } + + const QRectF bounds(x, y, itemSize.width(), requiredItemHeight); + if (index < m_itemInfos.count()) { + m_itemInfos[index].rect = bounds; + m_itemInfos[index].column = column; + m_itemInfos[index].row = row; + } else { + ItemInfo itemInfo; + itemInfo.rect = bounds; + itemInfo.column = column; + itemInfo.row = row; + m_itemInfos.append(itemInfo); + } + + if (grouped && horizontalScrolling) { + // When grouping is enabled in the horizontal mode, the header alignment + // looks like this: + // Header-1 Header-2 Header-3 + // Item 1 Item 4 Item 7 + // Item 2 Item 5 Item 8 + // Item 3 Item 6 Item 9 + // In this case 'requiredItemHeight' represents the column-width. We don't + // check the content of the header in the layouter to determine the required + // width, hence assure that at least a minimal width of 15 characters is given + // (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 headerWidth = minimumGroupHeaderWidth(); + if (requiredItemHeight < headerWidth) { + requiredItemHeight = headerWidth; + } + } + + maxItemHeight = qMax(maxItemHeight, requiredItemHeight); + x += m_columnWidth; + ++index; + ++column; + + if (grouped && m_groupItemIndexes.contains(index)) { + // The item represents the first index of a group + // and must aligned in the first column + break; + } + } + + y += maxItemHeight + itemMargin.height(); + ++row; + } + if (m_itemInfos.count() > itemCount) { + m_itemInfos.erase(m_itemInfos.begin() + itemCount, + m_itemInfos.end()); + } + + if (itemCount > 0) { + // Calculate the maximum y-range of the last row for m_maximumScrollOffset + m_maximumScrollOffset = m_itemInfos.last().rect.bottom(); + const qreal rowY = m_itemInfos.last().rect.y(); + + int index = m_itemInfos.count() - 2; + while (index >= 0 && m_itemInfos[index].rect.bottom() >= rowY) { + m_maximumScrollOffset = qMax(m_maximumScrollOffset, m_itemInfos[index].rect.bottom()); + --index; + } + + m_maximumScrollOffset += itemMargin.height(); + + m_maximumItemOffset = m_columnCount * m_columnWidth; + } else { + m_maximumScrollOffset = 0; + m_maximumItemOffset = 0; + } + +#ifdef KITEMLISTVIEWLAYOUTER_DEBUG + kDebug() << "[TIME] doLayout() for " << m_model->count() << "items:" << timer.elapsed(); +#endif + m_dirty = false; + } + + updateVisibleIndexes(); +} + +void KItemListViewLayouter::updateVisibleIndexes() +{ + if (!m_visibleIndexesDirty) { + return; + } + + Q_ASSERT(!m_dirty); + + if (m_model->count() <= 0) { + m_firstVisibleIndex = -1; + m_lastVisibleIndex = -1; + m_visibleIndexesDirty = false; + return; + } + + const int maxIndex = m_model->count() - 1; + + // 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_itemInfos[mid].rect.top() < m_scrollOffset) { + min = mid + 1; + } else { + max = mid - 1; + } + } while (min <= max); + + if (mid > 0) { + // Include the row before the first fully visible index, as it might + // be partly visible + if (m_itemInfos[mid].rect.top() >= m_scrollOffset) { + --mid; + Q_ASSERT(m_itemInfos[mid].rect.top() < m_scrollOffset); + } + + const qreal rowTop = m_itemInfos[mid].rect.top(); + while (mid > 0 && m_itemInfos[mid - 1].rect.top() == rowTop) { + --mid; + } + } + m_firstVisibleIndex = mid; + + // Calculate the last visible index that is (at least partly) visible + const int visibleHeight = (m_scrollOrientation == Qt::Horizontal) ? m_size.width() : m_size.height(); + qreal bottom = m_scrollOffset + visibleHeight; + if (m_model->groupedSorting()) { + bottom += m_groupHeaderHeight; + } + + min = m_firstVisibleIndex; + max = maxIndex; + do { + mid = (min + max) / 2; + if (m_itemInfos[mid].rect.y() <= bottom) { + min = mid + 1; + } else { + max = mid - 1; + } + } while (min <= max); + + while (mid > 0 && m_itemInfos[mid].rect.y() > bottom) { + --mid; + } + m_lastVisibleIndex = mid; + + m_visibleIndexesDirty = false; +} + +bool KItemListViewLayouter::createGroupHeaders() +{ + if (!m_model->groupedSorting()) { + return false; + } + + m_groupItemIndexes.clear(); + + const QList<QPair<int, QVariant> > groups = m_model->groups(); + if (groups.isEmpty()) { + return false; + } + + for (int i = 0; i < groups.count(); ++i) { + const int firstItemIndex = groups.at(i).first; + m_groupItemIndexes.insert(firstItemIndex); + } + + return true; +} + +qreal KItemListViewLayouter::minimumGroupHeaderWidth() const +{ + return 100; +} + +#include "kitemlistviewlayouter.moc" |
