diff --git a/src/kitemviews/kitemlistcontainer.cpp b/src/kitemviews/kitemlistcontainer.cpp index 6974ebb48..6a0c5756c 100644 --- a/src/kitemviews/kitemlistcontainer.cpp +++ b/src/kitemviews/kitemlistcontainer.cpp @@ -1,410 +1,410 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz * * * * Based on the Itemviews NG project from Trolltech Labs: * * http://qt.gitorious.org/qt-labs/itemviews-ng * * * * 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 "kitemlistcontainer.h" #include "kitemlistcontroller.h" #include "kitemlistview.h" #include "kitemmodelbase.h" #include "private/kitemlistsmoothscroller.h" #include #include #include #include #include #include /** * Replaces the default viewport of KItemListContainer by a * non-scrollable viewport. The scrolling is done in an optimized * way by KItemListView internally. */ class KItemListContainerViewport : public QGraphicsView { Q_OBJECT public: KItemListContainerViewport(QGraphicsScene* scene, QWidget* parent); protected: virtual void wheelEvent(QWheelEvent* event); }; KItemListContainerViewport::KItemListContainerViewport(QGraphicsScene* scene, QWidget* parent) : QGraphicsView(scene, parent) { setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setViewportMargins(0, 0, 0, 0); setFrameShape(QFrame::NoFrame); } void KItemListContainerViewport::wheelEvent(QWheelEvent* event) { // Assure that the wheel-event gets forwarded to the parent // and not handled at all by QGraphicsView. event->ignore(); } KItemListContainer::KItemListContainer(KItemListController* controller, QWidget* parent) : QAbstractScrollArea(parent), m_controller(controller), m_horizontalSmoothScroller(0), m_verticalSmoothScroller(0) { Q_ASSERT(controller); controller->setParent(this); QGraphicsView* graphicsView = new KItemListContainerViewport(new QGraphicsScene(this), this); setViewport(graphicsView); m_horizontalSmoothScroller = new KItemListSmoothScroller(horizontalScrollBar(), this); m_verticalSmoothScroller = new KItemListSmoothScroller(verticalScrollBar(), this); if (controller->model()) { slotModelChanged(controller->model(), 0); } if (controller->view()) { slotViewChanged(controller->view(), 0); } connect(controller, &KItemListController::modelChanged, this, &KItemListContainer::slotModelChanged); connect(controller, &KItemListController::viewChanged, this, &KItemListContainer::slotViewChanged); } KItemListContainer::~KItemListContainer() { // Don't rely on the QObject-order to delete the controller, otherwise // the QGraphicsScene might get deleted before the view. delete m_controller; m_controller = 0; } KItemListController* KItemListContainer::controller() const { return m_controller; } void KItemListContainer::setEnabledFrame(bool enable) { QGraphicsView* graphicsView = qobject_cast(viewport()); if (enable) { setFrameShape(QFrame::StyledPanel); graphicsView->setPalette(palette()); graphicsView->viewport()->setAutoFillBackground(true); } else { setFrameShape(QFrame::NoFrame); // Make the background of the container transparent and apply the window-text color // to the text color, so that enough contrast is given for all color // schemes QPalette p = graphicsView->palette(); p.setColor(QPalette::Active, QPalette::Text, p.color(QPalette::Active, QPalette::WindowText)); p.setColor(QPalette::Inactive, QPalette::Text, p.color(QPalette::Inactive, QPalette::WindowText)); p.setColor(QPalette::Disabled, QPalette::Text, p.color(QPalette::Disabled, QPalette::WindowText)); graphicsView->setPalette(p); graphicsView->viewport()->setAutoFillBackground(false); } } bool KItemListContainer::enabledFrame() const { const QGraphicsView* graphicsView = qobject_cast(viewport()); return graphicsView->autoFillBackground(); } void KItemListContainer::keyPressEvent(QKeyEvent* event) { // TODO: We should find a better way to handle the key press events in the view. // The reasons why we need this hack are: // 1. Without reimplementing keyPressEvent() here, the event would not reach the QGraphicsView. // 2. By default, the KItemListView does not have the keyboard focus in the QGraphicsScene, so // simply sending the event to the QGraphicsView which is the KItemListContainer's viewport // does not work. KItemListView* view = m_controller->view(); if (view) { QApplication::sendEvent(view, event); } } void KItemListContainer::showEvent(QShowEvent* event) { QAbstractScrollArea::showEvent(event); updateGeometries(); } void KItemListContainer::resizeEvent(QResizeEvent* event) { QAbstractScrollArea::resizeEvent(event); updateGeometries(); } void KItemListContainer::scrollContentsBy(int dx, int dy) { m_horizontalSmoothScroller->scrollContentsBy(dx); m_verticalSmoothScroller->scrollContentsBy(dy); } void KItemListContainer::wheelEvent(QWheelEvent* event) { if (event->modifiers().testFlag(Qt::ControlModifier)) { event->ignore(); return; } KItemListView* view = m_controller->view(); if (!view) { event->ignore(); return; } const bool scrollHorizontally = (event->orientation() == Qt::Horizontal) || (event->orientation() == Qt::Vertical && !verticalScrollBar()->isVisible()); KItemListSmoothScroller* smoothScroller = scrollHorizontally ? m_horizontalSmoothScroller : m_verticalSmoothScroller; smoothScroller->handleWheelEvent(event); } void KItemListContainer::slotScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous) { Q_UNUSED(previous); updateSmoothScrollers(current); } void KItemListContainer::slotModelChanged(KItemModelBase* current, KItemModelBase* previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListContainer::slotViewChanged(KItemListView* current, KItemListView* previous) { QGraphicsScene* scene = static_cast(viewport())->scene(); if (previous) { scene->removeItem(previous); disconnect(previous, &KItemListView::scrollOrientationChanged, this, &KItemListContainer::slotScrollOrientationChanged); disconnect(previous, &KItemListView::scrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar); disconnect(previous, &KItemListView::maximumScrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar); disconnect(previous, &KItemListView::itemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar); disconnect(previous, &KItemListView::maximumItemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar); disconnect(previous, &KItemListView::scrollTo, this, &KItemListContainer::scrollTo); m_horizontalSmoothScroller->setTargetObject(0); m_verticalSmoothScroller->setTargetObject(0); } if (current) { scene->addItem(current); connect(current, &KItemListView::scrollOrientationChanged, this, &KItemListContainer::slotScrollOrientationChanged); connect(current, &KItemListView::scrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar); connect(current, &KItemListView::maximumScrollOffsetChanged, this, &KItemListContainer::updateScrollOffsetScrollBar); connect(current, &KItemListView::itemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar); connect(current, &KItemListView::maximumItemOffsetChanged, this, &KItemListContainer::updateItemOffsetScrollBar); connect(current, &KItemListView::scrollTo, this, &KItemListContainer::scrollTo); m_horizontalSmoothScroller->setTargetObject(current); m_verticalSmoothScroller->setTargetObject(current); updateSmoothScrollers(current->scrollOrientation()); } } void KItemListContainer::scrollTo(qreal offset) { const KItemListView* view = m_controller->view(); if (view) { if (view->scrollOrientation() == Qt::Vertical) { m_verticalSmoothScroller->scrollTo(offset); } else { m_horizontalSmoothScroller->scrollTo(offset); } } } void KItemListContainer::updateScrollOffsetScrollBar() { const KItemListView* view = m_controller->view(); if (!view) { return; } KItemListSmoothScroller* smoothScroller = 0; QScrollBar* scrollOffsetScrollBar = 0; int singleStep = 0; int pageStep = 0; int maximum = 0; if (view->scrollOrientation() == Qt::Vertical) { smoothScroller = m_verticalSmoothScroller; scrollOffsetScrollBar = verticalScrollBar(); - singleStep = view->itemSize().height(); + singleStep = view->itemSizeHint().height(); // We cannot use view->size().height() because this height might // include the header widget, which is not part of the scrolled area. pageStep = view->verticalPageStep(); // However, the total height of the view must be considered for the // maximum value of the scroll bar. Note that the view's scrollOffset() // refers to the offset of the top part of the view, which might be // hidden behind the header. maximum = qMax(0, int(view->maximumScrollOffset() - view->size().height())); } else { smoothScroller = m_horizontalSmoothScroller; scrollOffsetScrollBar = horizontalScrollBar(); singleStep = view->itemSize().width(); pageStep = view->size().width(); maximum = qMax(0, int(view->maximumScrollOffset() - view->size().width())); } const int value = view->scrollOffset(); if (smoothScroller->requestScrollBarUpdate(maximum)) { const bool updatePolicy = (scrollOffsetScrollBar->maximum() > 0 && maximum == 0) || horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOn; scrollOffsetScrollBar->setSingleStep(singleStep); scrollOffsetScrollBar->setPageStep(pageStep); scrollOffsetScrollBar->setMinimum(0); scrollOffsetScrollBar->setMaximum(maximum); scrollOffsetScrollBar->setValue(value); if (updatePolicy) { // Prevent a potential endless layout loop (see bug #293318). updateScrollOffsetScrollBarPolicy(); } } } void KItemListContainer::updateItemOffsetScrollBar() { const KItemListView* view = m_controller->view(); if (!view) { return; } KItemListSmoothScroller* smoothScroller = 0; QScrollBar* itemOffsetScrollBar = 0; int singleStep = 0; int pageStep = 0; if (view->scrollOrientation() == Qt::Vertical) { smoothScroller = m_horizontalSmoothScroller; itemOffsetScrollBar = horizontalScrollBar(); singleStep = view->size().width() / 10; pageStep = view->size().width(); } else { smoothScroller = m_verticalSmoothScroller; itemOffsetScrollBar = verticalScrollBar(); singleStep = view->size().height() / 10; pageStep = view->size().height(); } const int value = view->itemOffset(); const int maximum = qMax(0, int(view->maximumItemOffset()) - pageStep); if (smoothScroller->requestScrollBarUpdate(maximum)) { itemOffsetScrollBar->setSingleStep(singleStep); itemOffsetScrollBar->setPageStep(pageStep); itemOffsetScrollBar->setMinimum(0); itemOffsetScrollBar->setMaximum(maximum); itemOffsetScrollBar->setValue(value); } } void KItemListContainer::updateGeometries() { QRect rect = geometry(); int extra = frameWidth() * 2; QStyleOption option; option.initFrom(this); int scrollbarSpacing = 0; if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &option, this)) { scrollbarSpacing = style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing, &option, this); } const int widthDec = verticalScrollBar()->isVisible() ? extra + scrollbarSpacing + style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this) : extra; const int heightDec = horizontalScrollBar()->isVisible() ? extra + scrollbarSpacing + style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this) : extra; const QRectF newGeometry(0, 0, rect.width() - widthDec, rect.height() - heightDec); if (m_controller->view()->geometry() != newGeometry) { m_controller->view()->setGeometry(newGeometry); // Get the real geometry of the view again since the scrollbars // visibilities and the view geometry may have changed in re-layout. static_cast(viewport())->scene()->setSceneRect(m_controller->view()->geometry()); static_cast(viewport())->viewport()->setGeometry(m_controller->view()->geometry().toRect()); updateScrollOffsetScrollBar(); updateItemOffsetScrollBar(); } } void KItemListContainer::updateSmoothScrollers(Qt::Orientation orientation) { if (orientation == Qt::Vertical) { m_verticalSmoothScroller->setPropertyName("scrollOffset"); m_horizontalSmoothScroller->setPropertyName("itemOffset"); } else { m_horizontalSmoothScroller->setPropertyName("scrollOffset"); m_verticalSmoothScroller->setPropertyName("itemOffset"); } } void KItemListContainer::updateScrollOffsetScrollBarPolicy() { const KItemListView* view = m_controller->view(); Q_ASSERT(view); const bool vertical = (view->scrollOrientation() == Qt::Vertical); QStyleOption option; option.initFrom(this); const int scrollBarInc = style()->pixelMetric(QStyle::PM_ScrollBarExtent, &option, this); QSizeF newViewSize = m_controller->view()->size(); if (vertical) { newViewSize.rwidth() += scrollBarInc; } else { newViewSize.rheight() += scrollBarInc; } const Qt::ScrollBarPolicy policy = view->scrollBarRequired(newViewSize) ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAsNeeded; if (vertical) { setVerticalScrollBarPolicy(policy); } else { setHorizontalScrollBarPolicy(policy); } } #include "kitemlistcontainer.moc" diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index df6539929..d840509da 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -1,2727 +1,2732 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz * * * * Based on the Itemviews NG project from Trolltech Labs: * * http://qt.gitorious.org/qt-labs/itemviews-ng * * * * 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 "kitemlistview.h" #include "dolphindebug.h" #include "kitemlistcontainer.h" #include "kitemlistcontroller.h" #include "kitemlistheader.h" #include "kitemlistselectionmanager.h" #include "kitemlistwidget.h" #include "private/kitemlistheaderwidget.h" #include "private/kitemlistrubberband.h" #include "private/kitemlistsizehintresolver.h" #include "private/kitemlistviewlayouter.h" #include "private/kitemlistviewanimation.h" #include #include #include #include #include #include #include #include #include "kitemlistviewaccessible.h" namespace { // Time in ms until reaching the autoscroll margin triggers // an initial autoscrolling const int InitialAutoScrollDelay = 700; // Delay in ms for triggering the next autoscroll const int RepeatingAutoScrollDelay = 1000 / 60; } #ifndef QT_NO_ACCESSIBILITY QAccessibleInterface* accessibleInterfaceFactory(const QString& key, QObject* object) { Q_UNUSED(key) if (KItemListContainer* container = qobject_cast(object)) { return new KItemListContainerAccessible(container); } else if (KItemListView* view = qobject_cast(object)) { return new KItemListViewAccessible(view); } return 0; } #endif KItemListView::KItemListView(QGraphicsWidget* parent) : QGraphicsWidget(parent), m_enabledSelectionToggles(false), m_grouped(false), m_supportsItemExpanding(false), m_editingRole(false), m_activeTransactions(0), m_endTransactionAnimationHint(Animation), m_itemSize(), m_controller(0), m_model(0), m_visibleRoles(), m_widgetCreator(0), m_groupHeaderCreator(0), m_styleOption(), m_visibleItems(), m_visibleGroups(), m_visibleCells(), m_sizeHintResolver(0), m_layouter(0), m_animation(0), m_layoutTimer(0), m_oldScrollOffset(0), m_oldMaximumScrollOffset(0), m_oldItemOffset(0), m_oldMaximumItemOffset(0), m_skipAutoScrollForRubberBand(false), m_rubberBand(0), m_mousePos(), m_autoScrollIncrement(0), m_autoScrollTimer(0), m_header(0), m_headerWidget(0), m_dropIndicator() { setAcceptHoverEvents(true); m_sizeHintResolver = new KItemListSizeHintResolver(this); m_layouter = new KItemListViewLayouter(m_sizeHintResolver, this); m_animation = new KItemListViewAnimation(this); connect(m_animation, &KItemListViewAnimation::finished, this, &KItemListView::slotAnimationFinished); m_layoutTimer = new QTimer(this); m_layoutTimer->setInterval(300); m_layoutTimer->setSingleShot(true); connect(m_layoutTimer, &QTimer::timeout, this, &KItemListView::slotLayoutTimerFinished); m_rubberBand = new KItemListRubberBand(this); connect(m_rubberBand, &KItemListRubberBand::activationChanged, this, &KItemListView::slotRubberBandActivationChanged); m_headerWidget = new KItemListHeaderWidget(this); m_headerWidget->setVisible(false); m_header = new KItemListHeader(this); #ifndef QT_NO_ACCESSIBILITY QAccessible::installFactory(accessibleInterfaceFactory); #endif } KItemListView::~KItemListView() { // The group headers are children of the widgets created by // widgetCreator(). So it is mandatory to delete the group headers // first. delete m_groupHeaderCreator; m_groupHeaderCreator = 0; delete m_widgetCreator; m_widgetCreator = 0; delete m_sizeHintResolver; m_sizeHintResolver = 0; } void KItemListView::setScrollOffset(qreal offset) { if (offset < 0) { offset = 0; } const qreal previousOffset = m_layouter->scrollOffset(); if (offset == previousOffset) { return; } m_layouter->setScrollOffset(offset); m_animation->setScrollOffset(offset); // Don't check whether the m_layoutTimer is active: Changing the // scroll offset must always trigger a synchronous layout, otherwise // the smooth-scrolling might get jerky. doLayout(NoAnimation); onScrollOffsetChanged(offset, previousOffset); } qreal KItemListView::scrollOffset() const { return m_layouter->scrollOffset(); } qreal KItemListView::maximumScrollOffset() const { return m_layouter->maximumScrollOffset(); } void KItemListView::setItemOffset(qreal offset) { if (m_layouter->itemOffset() == offset) { return; } m_layouter->setItemOffset(offset); if (m_headerWidget->isVisible()) { m_headerWidget->setOffset(offset); } // Don't check whether the m_layoutTimer is active: Changing the // item offset must always trigger a synchronous layout, otherwise // the smooth-scrolling might get jerky. doLayout(NoAnimation); } qreal KItemListView::itemOffset() const { return m_layouter->itemOffset(); } qreal KItemListView::maximumItemOffset() const { return m_layouter->maximumItemOffset(); } int KItemListView::maximumVisibleItems() const { return m_layouter->maximumVisibleItems(); } void KItemListView::setVisibleRoles(const QList& roles) { const QList previousRoles = m_visibleRoles; m_visibleRoles = roles; onVisibleRolesChanged(roles, previousRoles); m_sizeHintResolver->clearCache(); m_layouter->markAsDirty(); if (m_itemSize.isEmpty()) { m_headerWidget->setColumns(roles); updatePreferredColumnWidths(); if (!m_headerWidget->automaticColumnResizing()) { // The column-width of new roles are still 0. Apply the preferred // column-width as default with. foreach (const QByteArray& role, m_visibleRoles) { if (m_headerWidget->columnWidth(role) == 0) { const qreal width = m_headerWidget->preferredColumnWidth(role); m_headerWidget->setColumnWidth(role, width); } } applyColumnWidthsFromHeader(); } } const bool alternateBackgroundsChanged = m_itemSize.isEmpty() && ((roles.count() > 1 && previousRoles.count() <= 1) || (roles.count() <= 1 && previousRoles.count() > 1)); QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); KItemListWidget* widget = it.value(); widget->setVisibleRoles(roles); if (alternateBackgroundsChanged) { updateAlternateBackgroundForWidget(widget); } } doLayout(NoAnimation); } QList KItemListView::visibleRoles() const { return m_visibleRoles; } void KItemListView::setAutoScroll(bool enabled) { if (enabled && !m_autoScrollTimer) { m_autoScrollTimer = new QTimer(this); m_autoScrollTimer->setSingleShot(true); connect(m_autoScrollTimer, &QTimer::timeout, this, &KItemListView::triggerAutoScrolling); m_autoScrollTimer->start(InitialAutoScrollDelay); } else if (!enabled && m_autoScrollTimer) { delete m_autoScrollTimer; m_autoScrollTimer = 0; } } bool KItemListView::autoScroll() const { return m_autoScrollTimer != 0; } void KItemListView::setEnabledSelectionToggles(bool enabled) { if (m_enabledSelectionToggles != enabled) { m_enabledSelectionToggles = enabled; QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); it.value()->setEnabledSelectionToggle(enabled); } } } bool KItemListView::enabledSelectionToggles() const { return m_enabledSelectionToggles; } KItemListController* KItemListView::controller() const { return m_controller; } KItemModelBase* KItemListView::model() const { return m_model; } void KItemListView::setWidgetCreator(KItemListWidgetCreatorBase* widgetCreator) { if (m_widgetCreator) { delete m_widgetCreator; } m_widgetCreator = widgetCreator; } KItemListWidgetCreatorBase* KItemListView::widgetCreator() const { if (!m_widgetCreator) { m_widgetCreator = defaultWidgetCreator(); } return m_widgetCreator; } void KItemListView::setGroupHeaderCreator(KItemListGroupHeaderCreatorBase* groupHeaderCreator) { if (m_groupHeaderCreator) { delete m_groupHeaderCreator; } m_groupHeaderCreator = groupHeaderCreator; } KItemListGroupHeaderCreatorBase* KItemListView::groupHeaderCreator() const { if (!m_groupHeaderCreator) { m_groupHeaderCreator = defaultGroupHeaderCreator(); } return m_groupHeaderCreator; } QSizeF KItemListView::itemSize() const { return m_itemSize; } +QSizeF KItemListView::itemSizeHint() const +{ + return m_sizeHintResolver->maxSizeHint(); +} + const KItemListStyleOption& KItemListView::styleOption() const { return m_styleOption; } void KItemListView::setGeometry(const QRectF& rect) { QGraphicsWidget::setGeometry(rect); if (!m_model) { return; } const QSizeF newSize = rect.size(); if (m_itemSize.isEmpty()) { m_headerWidget->resize(rect.width(), m_headerWidget->size().height()); if (m_headerWidget->automaticColumnResizing()) { applyAutomaticColumnWidths(); } else { const qreal requiredWidth = columnWidthsSum(); const QSizeF dynamicItemSize(qMax(newSize.width(), requiredWidth), m_itemSize.height()); m_layouter->setItemSize(dynamicItemSize); } // Triggering a synchronous layout is fine from a performance point of view, // as with dynamic item sizes no moving animation must be done. m_layouter->setSize(newSize); doLayout(NoAnimation); } else { const bool animate = !changesItemGridLayout(newSize, m_layouter->itemSize(), m_layouter->itemMargin()); m_layouter->setSize(newSize); if (animate) { // Trigger an asynchronous relayout with m_layoutTimer to prevent // performance bottlenecks. If the timer is exceeded, an animated layout // will be triggered. if (!m_layoutTimer->isActive()) { m_layoutTimer->start(); } } else { m_layoutTimer->stop(); doLayout(NoAnimation); } } } qreal KItemListView::verticalPageStep() const { qreal headerHeight = 0; if (m_headerWidget->isVisible()) { headerHeight = m_headerWidget->size().height(); } return size().height() - headerHeight; } int KItemListView::itemAt(const QPointF& pos) const { QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); const KItemListWidget* widget = it.value(); const QPointF mappedPos = widget->mapFromItem(this, pos); if (widget->contains(mappedPos)) { return it.key(); } } return -1; } bool KItemListView::isAboveSelectionToggle(int index, const QPointF& pos) const { if (!m_enabledSelectionToggles) { return false; } const KItemListWidget* widget = m_visibleItems.value(index); if (widget) { const QRectF selectionToggleRect = widget->selectionToggleRect(); if (!selectionToggleRect.isEmpty()) { const QPointF mappedPos = widget->mapFromItem(this, pos); return selectionToggleRect.contains(mappedPos); } } return false; } bool KItemListView::isAboveExpansionToggle(int index, const QPointF& pos) const { const KItemListWidget* widget = m_visibleItems.value(index); if (widget) { const QRectF expansionToggleRect = widget->expansionToggleRect(); if (!expansionToggleRect.isEmpty()) { const QPointF mappedPos = widget->mapFromItem(this, pos); return expansionToggleRect.contains(mappedPos); } } return false; } int KItemListView::firstVisibleIndex() const { return m_layouter->firstVisibleIndex(); } int KItemListView::lastVisibleIndex() const { return m_layouter->lastVisibleIndex(); } void KItemListView::calculateItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint) const { widgetCreator()->calculateItemSizeHints(logicalHeightHints, logicalWidthHint, this); } void KItemListView::setSupportsItemExpanding(bool supportsExpanding) { if (m_supportsItemExpanding != supportsExpanding) { m_supportsItemExpanding = supportsExpanding; updateSiblingsInformation(); onSupportsItemExpandingChanged(supportsExpanding); } } bool KItemListView::supportsItemExpanding() const { return m_supportsItemExpanding; } QRectF KItemListView::itemRect(int index) const { return m_layouter->itemRect(index); } QRectF KItemListView::itemContextRect(int index) const { QRectF contextRect; const KItemListWidget* widget = m_visibleItems.value(index); if (widget) { contextRect = widget->iconRect() | widget->textRect(); contextRect.translate(itemRect(index).topLeft()); } return contextRect; } void KItemListView::scrollToItem(int index) { QRectF viewGeometry = geometry(); if (m_headerWidget->isVisible()) { const qreal headerHeight = m_headerWidget->size().height(); viewGeometry.adjust(0, headerHeight, 0, 0); } QRectF currentRect = itemRect(index); // Fix for Bug 311099 - View the underscore when using Ctrl + PagDown currentRect.adjust(-m_styleOption.horizontalMargin, -m_styleOption.verticalMargin, m_styleOption.horizontalMargin, m_styleOption.verticalMargin); if (!viewGeometry.contains(currentRect)) { qreal newOffset = scrollOffset(); if (scrollOrientation() == Qt::Vertical) { if (currentRect.top() < viewGeometry.top()) { newOffset += currentRect.top() - viewGeometry.top(); } else if (currentRect.bottom() > viewGeometry.bottom()) { newOffset += currentRect.bottom() - viewGeometry.bottom(); } } else { if (currentRect.left() < viewGeometry.left()) { newOffset += currentRect.left() - viewGeometry.left(); } else if (currentRect.right() > viewGeometry.right()) { newOffset += currentRect.right() - viewGeometry.right(); } } if (newOffset != scrollOffset()) { emit scrollTo(newOffset); } } } void KItemListView::beginTransaction() { ++m_activeTransactions; if (m_activeTransactions == 1) { onTransactionBegin(); } } void KItemListView::endTransaction() { --m_activeTransactions; if (m_activeTransactions < 0) { m_activeTransactions = 0; qCWarning(DolphinDebug) << "Mismatch between beginTransaction()/endTransaction()"; } if (m_activeTransactions == 0) { onTransactionEnd(); doLayout(m_endTransactionAnimationHint); m_endTransactionAnimationHint = Animation; } } bool KItemListView::isTransactionActive() const { return m_activeTransactions > 0; } void KItemListView::setHeaderVisible(bool visible) { if (visible && !m_headerWidget->isVisible()) { QStyleOptionHeader option; const QSize headerSize = style()->sizeFromContents(QStyle::CT_HeaderSection, &option, QSize()); m_headerWidget->setPos(0, 0); m_headerWidget->resize(size().width(), headerSize.height()); m_headerWidget->setModel(m_model); m_headerWidget->setColumns(m_visibleRoles); m_headerWidget->setZValue(1); connect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged, this, &KItemListView::slotHeaderColumnWidthChanged); connect(m_headerWidget, &KItemListHeaderWidget::columnMoved, this, &KItemListView::slotHeaderColumnMoved); connect(m_headerWidget, &KItemListHeaderWidget::sortOrderChanged, this, &KItemListView::sortOrderChanged); connect(m_headerWidget, &KItemListHeaderWidget::sortRoleChanged, this, &KItemListView::sortRoleChanged); m_layouter->setHeaderHeight(headerSize.height()); m_headerWidget->setVisible(true); } else if (!visible && m_headerWidget->isVisible()) { disconnect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged, this, &KItemListView::slotHeaderColumnWidthChanged); disconnect(m_headerWidget, &KItemListHeaderWidget::columnMoved, this, &KItemListView::slotHeaderColumnMoved); disconnect(m_headerWidget, &KItemListHeaderWidget::sortOrderChanged, this, &KItemListView::sortOrderChanged); disconnect(m_headerWidget, &KItemListHeaderWidget::sortRoleChanged, this, &KItemListView::sortRoleChanged); m_layouter->setHeaderHeight(0); m_headerWidget->setVisible(false); } } bool KItemListView::isHeaderVisible() const { return m_headerWidget->isVisible(); } KItemListHeader* KItemListView::header() const { return m_header; } QPixmap KItemListView::createDragPixmap(const KItemSet& indexes) const { QPixmap pixmap; if (indexes.count() == 1) { KItemListWidget* item = m_visibleItems.value(indexes.first()); QGraphicsView* graphicsView = scene()->views()[0]; if (item && graphicsView) { pixmap = item->createDragPixmap(0, graphicsView); } } else { // TODO: Not implemented yet. Probably extend the interface // from KItemListWidget::createDragPixmap() to return a pixmap // that can be used for multiple indexes. } return pixmap; } void KItemListView::editRole(int index, const QByteArray& role) { KItemListWidget* widget = m_visibleItems.value(index); if (!widget || m_editingRole) { return; } m_editingRole = true; widget->setEditedRole(role); connect(widget, &KItemListWidget::roleEditingCanceled, this, &KItemListView::slotRoleEditingCanceled); connect(widget, &KItemListWidget::roleEditingFinished, this, &KItemListView::slotRoleEditingFinished); } void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { QGraphicsWidget::paint(painter, option, widget); if (m_rubberBand->isActive()) { QRectF rubberBandRect = QRectF(m_rubberBand->startPosition(), m_rubberBand->endPosition()).normalized(); const QPointF topLeft = rubberBandRect.topLeft(); if (scrollOrientation() == Qt::Vertical) { rubberBandRect.moveTo(topLeft.x(), topLeft.y() - scrollOffset()); } else { rubberBandRect.moveTo(topLeft.x() - scrollOffset(), topLeft.y()); } QStyleOptionRubberBand opt; opt.initFrom(widget); opt.shape = QRubberBand::Rectangle; opt.opaque = false; opt.rect = rubberBandRect.toRect(); style()->drawControl(QStyle::CE_RubberBand, &opt, painter); } if (!m_dropIndicator.isEmpty()) { const QRectF r = m_dropIndicator.toRect(); QColor color = palette().brush(QPalette::Normal, QPalette::Highlight).color(); painter->setPen(color); // TODO: The following implementation works only for a vertical scroll-orientation // and assumes a height of the m_draggingInsertIndicator of 1. Q_ASSERT(r.height() == 1); painter->drawLine(r.left() + 1, r.top(), r.right() - 1, r.top()); color.setAlpha(128); painter->setPen(color); painter->drawRect(r.left(), r.top() - 1, r.width() - 1, 2); } } QVariant KItemListView::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == QGraphicsItem::ItemSceneHasChanged && scene()) { if (!scene()->views().isEmpty()) { m_styleOption.palette = scene()->views().at(0)->palette(); } } return QGraphicsItem::itemChange(change, value); } void KItemListView::setItemSize(const QSizeF& size) { const QSizeF previousSize = m_itemSize; if (size == previousSize) { return; } // Skip animations when the number of rows or columns // are changed in the grid layout. Although the animation // engine can handle this usecase, it looks obtrusive. const bool animate = !changesItemGridLayout(m_layouter->size(), size, m_layouter->itemMargin()); const bool alternateBackgroundsChanged = (m_visibleRoles.count() > 1) && (( m_itemSize.isEmpty() && !size.isEmpty()) || (!m_itemSize.isEmpty() && size.isEmpty())); m_itemSize = size; if (alternateBackgroundsChanged) { // For an empty item size alternate backgrounds are drawn if more than // one role is shown. Assure that the backgrounds for visible items are // updated when changing the size in this context. updateAlternateBackgrounds(); } if (size.isEmpty()) { if (m_headerWidget->automaticColumnResizing()) { updatePreferredColumnWidths(); } else { // Only apply the changed height and respect the header widths // set by the user const qreal currentWidth = m_layouter->itemSize().width(); const QSizeF newSize(currentWidth, size.height()); m_layouter->setItemSize(newSize); } } else { m_layouter->setItemSize(size); } m_sizeHintResolver->clearCache(); doLayout(animate ? Animation : NoAnimation); onItemSizeChanged(size, previousSize); } void KItemListView::setStyleOption(const KItemListStyleOption& option) { const KItemListStyleOption previousOption = m_styleOption; m_styleOption = option; bool animate = true; const QSizeF margin(option.horizontalMargin, option.verticalMargin); if (margin != m_layouter->itemMargin()) { // Skip animations when the number of rows or columns // are changed in the grid layout. Although the animation // engine can handle this usecase, it looks obtrusive. animate = !changesItemGridLayout(m_layouter->size(), m_layouter->itemSize(), margin); m_layouter->setItemMargin(margin); } if (m_grouped) { updateGroupHeaderHeight(); } if (animate && (previousOption.maxTextLines != option.maxTextLines || previousOption.maxTextWidth != option.maxTextWidth)) { // Animating a change of the maximum text size just results in expensive // temporary eliding and clipping operations and does not look good visually. animate = false; } QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); it.value()->setStyleOption(option); } m_sizeHintResolver->clearCache(); m_layouter->markAsDirty(); doLayout(animate ? Animation : NoAnimation); if (m_itemSize.isEmpty()) { updatePreferredColumnWidths(); } onStyleOptionChanged(option, previousOption); } void KItemListView::setScrollOrientation(Qt::Orientation orientation) { const Qt::Orientation previousOrientation = m_layouter->scrollOrientation(); if (orientation == previousOrientation) { return; } m_layouter->setScrollOrientation(orientation); m_animation->setScrollOrientation(orientation); m_sizeHintResolver->clearCache(); if (m_grouped) { QMutableHashIterator it (m_visibleGroups); while (it.hasNext()) { it.next(); it.value()->setScrollOrientation(orientation); } updateGroupHeaderHeight(); } doLayout(NoAnimation); onScrollOrientationChanged(orientation, previousOrientation); emit scrollOrientationChanged(orientation, previousOrientation); } Qt::Orientation KItemListView::scrollOrientation() const { return m_layouter->scrollOrientation(); } KItemListWidgetCreatorBase* KItemListView::defaultWidgetCreator() const { return 0; } KItemListGroupHeaderCreatorBase* KItemListView::defaultGroupHeaderCreator() const { return 0; } void KItemListView::initializeItemListWidget(KItemListWidget* item) { Q_UNUSED(item); } bool KItemListView::itemSizeHintUpdateRequired(const QSet& changedRoles) const { Q_UNUSED(changedRoles); return true; } void KItemListView::onControllerChanged(KItemListController* current, KItemListController* previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListView::onScrollOffsetChanged(qreal current, qreal previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListView::onVisibleRolesChanged(const QList& current, const QList& previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous) { Q_UNUSED(current); Q_UNUSED(previous); } void KItemListView::onSupportsItemExpandingChanged(bool supportsExpanding) { Q_UNUSED(supportsExpanding); } void KItemListView::onTransactionBegin() { } void KItemListView::onTransactionEnd() { } bool KItemListView::event(QEvent* event) { switch (event->type()) { case QEvent::PaletteChange: updatePalette(); break; case QEvent::FontChange: updateFont(); break; default: // Forward all other events to the controller and handle them there if (!m_editingRole && m_controller && m_controller->processEvent(event, transform())) { event->accept(); return true; } } return QGraphicsWidget::event(event); } void KItemListView::mousePressEvent(QGraphicsSceneMouseEvent* event) { m_mousePos = transform().map(event->pos()); event->accept(); } void KItemListView::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { QGraphicsWidget::mouseMoveEvent(event); m_mousePos = transform().map(event->pos()); if (m_autoScrollTimer && !m_autoScrollTimer->isActive()) { m_autoScrollTimer->start(InitialAutoScrollDelay); } } void KItemListView::dragEnterEvent(QGraphicsSceneDragDropEvent* event) { event->setAccepted(true); setAutoScroll(true); } void KItemListView::dragMoveEvent(QGraphicsSceneDragDropEvent* event) { QGraphicsWidget::dragMoveEvent(event); m_mousePos = transform().map(event->pos()); if (m_autoScrollTimer && !m_autoScrollTimer->isActive()) { m_autoScrollTimer->start(InitialAutoScrollDelay); } } void KItemListView::dragLeaveEvent(QGraphicsSceneDragDropEvent* event) { QGraphicsWidget::dragLeaveEvent(event); setAutoScroll(false); } void KItemListView::dropEvent(QGraphicsSceneDragDropEvent* event) { QGraphicsWidget::dropEvent(event); setAutoScroll(false); } QList KItemListView::visibleItemListWidgets() const { return m_visibleItems.values(); } void KItemListView::updateFont() { if (scene() && !scene()->views().isEmpty()) { KItemListStyleOption option = styleOption(); option.font = scene()->views().first()->font(); option.fontMetrics = QFontMetrics(option.font); setStyleOption(option); } } void KItemListView::updatePalette() { if (scene() && !scene()->views().isEmpty()) { KItemListStyleOption option = styleOption(); option.palette = scene()->views().first()->palette(); setStyleOption(option); } } void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) { if (m_itemSize.isEmpty()) { updatePreferredColumnWidths(itemRanges); } const bool hasMultipleRanges = (itemRanges.count() > 1); if (hasMultipleRanges) { beginTransaction(); } m_layouter->markAsDirty(); m_sizeHintResolver->itemsInserted(itemRanges); int previouslyInsertedCount = 0; foreach (const KItemRange& range, itemRanges) { // range.index is related to the model before anything has been inserted. // As in each loop the current item-range gets inserted the index must // be increased by the already previously inserted items. const int index = range.index + previouslyInsertedCount; const int count = range.count; if (index < 0 || count <= 0) { qCWarning(DolphinDebug) << "Invalid item range (index:" << index << ", count:" << count << ")"; continue; } previouslyInsertedCount += count; // Determine which visible items must be moved QList itemsToMove; QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); const int visibleItemIndex = it.key(); if (visibleItemIndex >= index) { itemsToMove.append(visibleItemIndex); } } // Update the indexes of all KItemListWidget instances that are located // after the inserted items. It is important to adjust the indexes in the order // from the highest index to the lowest index to prevent overlaps when setting the new index. qSort(itemsToMove); for (int i = itemsToMove.count() - 1; i >= 0; --i) { KItemListWidget* widget = m_visibleItems.value(itemsToMove[i]); Q_ASSERT(widget); const int newIndex = widget->index() + count; if (hasMultipleRanges) { setWidgetIndex(widget, newIndex); } else { // Try to animate the moving of the item moveWidgetToIndex(widget, newIndex); } } if (m_model->count() == count && m_activeTransactions == 0) { // Check whether a scrollbar is required to show the inserted items. In this case // the size of the layouter will be decreased before calling doLayout(): This prevents // an unnecessary temporary animation due to the geometry change of the inserted scrollbar. const bool verticalScrollOrientation = (scrollOrientation() == Qt::Vertical); const bool decreaseLayouterSize = ( verticalScrollOrientation && maximumScrollOffset() > size().height()) || (!verticalScrollOrientation && maximumScrollOffset() > size().width()); if (decreaseLayouterSize) { const int scrollBarExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent); int scrollbarSpacing = 0; if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) { scrollbarSpacing = style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarSpacing); } QSizeF layouterSize = m_layouter->size(); if (verticalScrollOrientation) { layouterSize.rwidth() -= scrollBarExtent + scrollbarSpacing; } else { layouterSize.rheight() -= scrollBarExtent + scrollbarSpacing; } m_layouter->setSize(layouterSize); } } if (!hasMultipleRanges) { doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, count); updateSiblingsInformation(); } } if (m_controller) { m_controller->selectionManager()->itemsInserted(itemRanges); } if (hasMultipleRanges) { m_endTransactionAnimationHint = NoAnimation; endTransaction(); updateSiblingsInformation(); } if (m_grouped && (hasMultipleRanges || itemRanges.first().count < m_model->count())) { // In case if items of the same group have been inserted before an item that // currently represents the first item of the group, the group header of // this item must be removed. updateVisibleGroupHeaders(); } if (useAlternateBackgrounds()) { updateAlternateBackgrounds(); } } void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) { if (m_itemSize.isEmpty()) { // Don't pass the item-range: The preferred column-widths of // all items must be adjusted when removing items. updatePreferredColumnWidths(); } const bool hasMultipleRanges = (itemRanges.count() > 1); if (hasMultipleRanges) { beginTransaction(); } m_layouter->markAsDirty(); m_sizeHintResolver->itemsRemoved(itemRanges); for (int i = itemRanges.count() - 1; i >= 0; --i) { const KItemRange& range = itemRanges[i]; const int index = range.index; const int count = range.count; if (index < 0 || count <= 0) { qCWarning(DolphinDebug) << "Invalid item range (index:" << index << ", count:" << count << ")"; continue; } const int firstRemovedIndex = index; const int lastRemovedIndex = index + count - 1; // Remember which items have to be moved because they are behind the removed range. QVector itemsToMove; // Remove all KItemListWidget instances that got deleted foreach (KItemListWidget* widget, m_visibleItems) { const int i = widget->index(); if (i < firstRemovedIndex) { continue; } else if (i > lastRemovedIndex) { itemsToMove.append(i); continue; } m_animation->stop(widget); // Stopping the animation might lead to recycling the widget if // it is invisible (see slotAnimationFinished()). // Check again whether it is still visible: if (!m_visibleItems.contains(i)) { continue; } if (m_model->count() == 0 || hasMultipleRanges || !animateChangedItemCount(count)) { // Remove the widget without animation recycleWidget(widget); } else { // Animate the removing of the items. Special case: When removing an item there // is no valid model index available anymore. For the // remove-animation the item gets removed from m_visibleItems but the widget // will stay alive until the animation has been finished and will // be recycled (deleted) in KItemListView::slotAnimationFinished(). m_visibleItems.remove(i); widget->setIndex(-1); m_animation->start(widget, KItemListViewAnimation::DeleteAnimation); } } // Update the indexes of all KItemListWidget instances that are located // after the deleted items. It is important to update them in ascending // order to prevent overlaps when setting the new index. std::sort(itemsToMove.begin(), itemsToMove.end()); foreach (int i, itemsToMove) { KItemListWidget* widget = m_visibleItems.value(i); Q_ASSERT(widget); const int newIndex = i - count; if (hasMultipleRanges) { setWidgetIndex(widget, newIndex); } else { // Try to animate the moving of the item moveWidgetToIndex(widget, newIndex); } } if (!hasMultipleRanges) { // The decrease-layout-size optimization in KItemListView::slotItemsInserted() // assumes an updated geometry. If items are removed during an active transaction, // the transaction will be temporary deactivated so that doLayout() triggers a // geometry update if necessary. const int activeTransactions = m_activeTransactions; m_activeTransactions = 0; doLayout(animateChangedItemCount(count) ? Animation : NoAnimation, index, -count); m_activeTransactions = activeTransactions; updateSiblingsInformation(); } } if (m_controller) { m_controller->selectionManager()->itemsRemoved(itemRanges); } if (hasMultipleRanges) { m_endTransactionAnimationHint = NoAnimation; endTransaction(); updateSiblingsInformation(); } if (m_grouped && (hasMultipleRanges || m_model->count() > 0)) { // In case if the first item of a group has been removed, the group header // must be applied to the next visible item. updateVisibleGroupHeaders(); } if (useAlternateBackgrounds()) { updateAlternateBackgrounds(); } } void KItemListView::slotItemsMoved(const KItemRange& itemRange, const QList& movedToIndexes) { m_sizeHintResolver->itemsMoved(itemRange, movedToIndexes); m_layouter->markAsDirty(); if (m_controller) { m_controller->selectionManager()->itemsMoved(itemRange, movedToIndexes); } const int firstVisibleMovedIndex = qMax(firstVisibleIndex(), itemRange.index); const int lastVisibleMovedIndex = qMin(lastVisibleIndex(), itemRange.index + itemRange.count - 1); for (int index = firstVisibleMovedIndex; index <= lastVisibleMovedIndex; ++index) { KItemListWidget* widget = m_visibleItems.value(index); if (widget) { updateWidgetProperties(widget, index); initializeItemListWidget(widget); } } doLayout(NoAnimation); updateSiblingsInformation(); } void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, const QSet& roles) { const bool updateSizeHints = itemSizeHintUpdateRequired(roles); if (updateSizeHints && m_itemSize.isEmpty()) { updatePreferredColumnWidths(itemRanges); } foreach (const KItemRange& itemRange, itemRanges) { const int index = itemRange.index; const int count = itemRange.count; if (updateSizeHints) { m_sizeHintResolver->itemsChanged(index, count, roles); m_layouter->markAsDirty(); if (!m_layoutTimer->isActive()) { m_layoutTimer->start(); } } // Apply the changed roles to the visible item-widgets const int lastIndex = index + count - 1; for (int i = index; i <= lastIndex; ++i) { KItemListWidget* widget = m_visibleItems.value(i); if (widget) { widget->setData(m_model->data(i), roles); } } if (m_grouped && roles.contains(m_model->sortRole())) { // The sort-role has been changed which might result // in modified group headers updateVisibleGroupHeaders(); doLayout(NoAnimation); } QAccessibleTableModelChangeEvent ev(this, QAccessibleTableModelChangeEvent::DataChanged); ev.setFirstRow(itemRange.index); ev.setLastRow(itemRange.index + itemRange.count); QAccessible::updateAccessibility(&ev); } } void KItemListView::slotGroupsChanged() { updateVisibleGroupHeaders(); doLayout(NoAnimation); updateSiblingsInformation(); } void KItemListView::slotGroupedSortingChanged(bool current) { m_grouped = current; m_layouter->markAsDirty(); if (m_grouped) { updateGroupHeaderHeight(); } else { // Clear all visible headers. Note that the QHashIterator takes a copy of // m_visibleGroups. Therefore, it remains valid even if items are removed // from m_visibleGroups in recycleGroupHeaderForWidget(). QHashIterator it(m_visibleGroups); while (it.hasNext()) { it.next(); recycleGroupHeaderForWidget(it.key()); } Q_ASSERT(m_visibleGroups.isEmpty()); } if (useAlternateBackgrounds()) { // Changing the group mode requires to update the alternate backgrounds // as with the enabled group mode the altering is done on base of the first // group item. updateAlternateBackgrounds(); } updateSiblingsInformation(); doLayout(NoAnimation); } void KItemListView::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) { Q_UNUSED(current); Q_UNUSED(previous); if (m_grouped) { updateVisibleGroupHeaders(); doLayout(NoAnimation); } } void KItemListView::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous) { Q_UNUSED(current); Q_UNUSED(previous); if (m_grouped) { updateVisibleGroupHeaders(); doLayout(NoAnimation); } } void KItemListView::slotCurrentChanged(int current, int previous) { Q_UNUSED(previous); // In SingleSelection mode (e.g., in the Places Panel), the current item is // always the selected item. It is not necessary to highlight the current item then. if (m_controller->selectionBehavior() != KItemListController::SingleSelection) { KItemListWidget* previousWidget = m_visibleItems.value(previous, 0); if (previousWidget) { previousWidget->setCurrent(false); } KItemListWidget* currentWidget = m_visibleItems.value(current, 0); if (currentWidget) { currentWidget->setCurrent(true); } } QAccessibleEvent ev(this, QAccessible::Focus); ev.setChild(current); QAccessible::updateAccessibility(&ev); } void KItemListView::slotSelectionChanged(const KItemSet& current, const KItemSet& previous) { Q_UNUSED(previous); QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); const int index = it.key(); KItemListWidget* widget = it.value(); widget->setSelected(current.contains(index)); } } void KItemListView::slotAnimationFinished(QGraphicsWidget* widget, KItemListViewAnimation::AnimationType type) { KItemListWidget* itemListWidget = qobject_cast(widget); Q_ASSERT(itemListWidget); switch (type) { case KItemListViewAnimation::DeleteAnimation: { // As we recycle the widget in this case it is important to assure that no // other animation has been started. This is a convention in KItemListView and // not a requirement defined by KItemListViewAnimation. Q_ASSERT(!m_animation->isStarted(itemListWidget)); // All KItemListWidgets that are animated by the DeleteAnimation are not maintained // by m_visibleWidgets and must be deleted manually after the animation has // been finished. recycleGroupHeaderForWidget(itemListWidget); widgetCreator()->recycle(itemListWidget); break; } case KItemListViewAnimation::CreateAnimation: case KItemListViewAnimation::MovingAnimation: case KItemListViewAnimation::ResizeAnimation: { const int index = itemListWidget->index(); const bool invisible = (index < m_layouter->firstVisibleIndex()) || (index > m_layouter->lastVisibleIndex()); if (invisible && !m_animation->isStarted(itemListWidget)) { recycleWidget(itemListWidget); } break; } default: break; } } void KItemListView::slotLayoutTimerFinished() { m_layouter->setSize(geometry().size()); doLayout(Animation); } void KItemListView::slotRubberBandPosChanged() { update(); } void KItemListView::slotRubberBandActivationChanged(bool active) { if (active) { connect(m_rubberBand, &KItemListRubberBand::startPositionChanged, this, &KItemListView::slotRubberBandPosChanged); connect(m_rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListView::slotRubberBandPosChanged); m_skipAutoScrollForRubberBand = true; } else { disconnect(m_rubberBand, &KItemListRubberBand::startPositionChanged, this, &KItemListView::slotRubberBandPosChanged); disconnect(m_rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListView::slotRubberBandPosChanged); m_skipAutoScrollForRubberBand = false; } update(); } void KItemListView::slotHeaderColumnWidthChanged(const QByteArray& role, qreal currentWidth, qreal previousWidth) { Q_UNUSED(role); Q_UNUSED(currentWidth); Q_UNUSED(previousWidth); m_headerWidget->setAutomaticColumnResizing(false); applyColumnWidthsFromHeader(); doLayout(NoAnimation); } void KItemListView::slotHeaderColumnMoved(const QByteArray& role, int currentIndex, int previousIndex) { Q_ASSERT(m_visibleRoles[previousIndex] == role); const QList previous = m_visibleRoles; QList current = m_visibleRoles; current.removeAt(previousIndex); current.insert(currentIndex, role); setVisibleRoles(current); emit visibleRolesChanged(current, previous); } void KItemListView::triggerAutoScrolling() { if (!m_autoScrollTimer) { return; } int pos = 0; int visibleSize = 0; if (scrollOrientation() == Qt::Vertical) { pos = m_mousePos.y(); visibleSize = size().height(); } else { pos = m_mousePos.x(); visibleSize = size().width(); } if (m_autoScrollTimer->interval() == InitialAutoScrollDelay) { m_autoScrollIncrement = 0; } m_autoScrollIncrement = calculateAutoScrollingIncrement(pos, visibleSize, m_autoScrollIncrement); if (m_autoScrollIncrement == 0) { // The mouse position is not above an autoscroll margin (the autoscroll timer // will be restarted in mouseMoveEvent()) m_autoScrollTimer->stop(); return; } if (m_rubberBand->isActive() && m_skipAutoScrollForRubberBand) { // If a rubberband selection is ongoing the autoscrolling may only get triggered // if the direction of the rubberband is similar to the autoscroll direction. This // prevents that starting to create a rubberband within the autoscroll margins starts // an autoscrolling. const qreal minDiff = 4; // Ignore any autoscrolling if the rubberband is very small const qreal diff = (scrollOrientation() == Qt::Vertical) ? m_rubberBand->endPosition().y() - m_rubberBand->startPosition().y() : m_rubberBand->endPosition().x() - m_rubberBand->startPosition().x(); if (qAbs(diff) < minDiff || (m_autoScrollIncrement < 0 && diff > 0) || (m_autoScrollIncrement > 0 && diff < 0)) { // The rubberband direction is different from the scroll direction (e.g. the rubberband has // been moved up although the autoscroll direction might be down) m_autoScrollTimer->stop(); return; } } // As soon as the autoscrolling has been triggered at least once despite having an active rubberband, // the autoscrolling may not get skipped anymore until a new rubberband is created m_skipAutoScrollForRubberBand = false; const qreal maxVisibleOffset = qMax(qreal(0), maximumScrollOffset() - visibleSize); const qreal newScrollOffset = qMin(scrollOffset() + m_autoScrollIncrement, maxVisibleOffset); setScrollOffset(newScrollOffset); // Trigger the autoscroll timer which will periodically call // triggerAutoScrolling() m_autoScrollTimer->start(RepeatingAutoScrollDelay); } void KItemListView::slotGeometryOfGroupHeaderParentChanged() { KItemListWidget* widget = qobject_cast(sender()); Q_ASSERT(widget); KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget); Q_ASSERT(groupHeader); updateGroupHeaderLayout(widget); } void KItemListView::slotRoleEditingCanceled(int index, const QByteArray& role, const QVariant& value) { disconnectRoleEditingSignals(index); emit roleEditingCanceled(index, role, value); m_editingRole = false; } void KItemListView::slotRoleEditingFinished(int index, const QByteArray& role, const QVariant& value) { disconnectRoleEditingSignals(index); emit roleEditingFinished(index, role, value); m_editingRole = false; } void KItemListView::setController(KItemListController* controller) { if (m_controller != controller) { KItemListController* previous = m_controller; if (previous) { KItemListSelectionManager* selectionManager = previous->selectionManager(); disconnect(selectionManager, &KItemListSelectionManager::currentChanged, this, &KItemListView::slotCurrentChanged); disconnect(selectionManager, &KItemListSelectionManager::selectionChanged, this, &KItemListView::slotSelectionChanged); } m_controller = controller; if (controller) { KItemListSelectionManager* selectionManager = controller->selectionManager(); connect(selectionManager, &KItemListSelectionManager::currentChanged, this, &KItemListView::slotCurrentChanged); connect(selectionManager, &KItemListSelectionManager::selectionChanged, this, &KItemListView::slotSelectionChanged); } onControllerChanged(controller, previous); } } void KItemListView::setModel(KItemModelBase* model) { if (m_model == model) { return; } KItemModelBase* previous = m_model; if (m_model) { disconnect(m_model, &KItemModelBase::itemsChanged, this, &KItemListView::slotItemsChanged); disconnect(m_model, &KItemModelBase::itemsInserted, this, &KItemListView::slotItemsInserted); disconnect(m_model, &KItemModelBase::itemsRemoved, this, &KItemListView::slotItemsRemoved); disconnect(m_model, &KItemModelBase::itemsMoved, this, &KItemListView::slotItemsMoved); disconnect(m_model, &KItemModelBase::groupsChanged, this, &KItemListView::slotGroupsChanged); disconnect(m_model, &KItemModelBase::groupedSortingChanged, this, &KItemListView::slotGroupedSortingChanged); disconnect(m_model, &KItemModelBase::sortOrderChanged, this, &KItemListView::slotSortOrderChanged); disconnect(m_model, &KItemModelBase::sortRoleChanged, this, &KItemListView::slotSortRoleChanged); m_sizeHintResolver->itemsRemoved(KItemRangeList() << KItemRange(0, m_model->count())); } m_model = model; m_layouter->setModel(model); m_grouped = model->groupedSorting(); if (m_model) { connect(m_model, &KItemModelBase::itemsChanged, this, &KItemListView::slotItemsChanged); connect(m_model, &KItemModelBase::itemsInserted, this, &KItemListView::slotItemsInserted); connect(m_model, &KItemModelBase::itemsRemoved, this, &KItemListView::slotItemsRemoved); connect(m_model, &KItemModelBase::itemsMoved, this, &KItemListView::slotItemsMoved); connect(m_model, &KItemModelBase::groupsChanged, this, &KItemListView::slotGroupsChanged); connect(m_model, &KItemModelBase::groupedSortingChanged, this, &KItemListView::slotGroupedSortingChanged); connect(m_model, &KItemModelBase::sortOrderChanged, this, &KItemListView::slotSortOrderChanged); connect(m_model, &KItemModelBase::sortRoleChanged, this, &KItemListView::slotSortRoleChanged); const int itemCount = m_model->count(); if (itemCount > 0) { slotItemsInserted(KItemRangeList() << KItemRange(0, itemCount)); } } onModelChanged(model, previous); } KItemListRubberBand* KItemListView::rubberBand() const { return m_rubberBand; } void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int changedCount) { if (m_layoutTimer->isActive()) { m_layoutTimer->stop(); } if (m_activeTransactions > 0) { if (hint == NoAnimation) { // As soon as at least one property change should be done without animation, // the whole transaction will be marked as not animated. m_endTransactionAnimationHint = NoAnimation; } return; } if (!m_model || m_model->count() < 0) { return; } int firstVisibleIndex = m_layouter->firstVisibleIndex(); if (firstVisibleIndex < 0) { emitOffsetChanges(); return; } // Do a sanity check of the scroll-offset property: When properties of the itemlist-view have been changed // it might be possible that the maximum offset got changed too. Assure that the full visible range // is still shown if the maximum offset got decreased. const qreal visibleOffsetRange = (scrollOrientation() == Qt::Horizontal) ? size().width() : size().height(); const qreal maxOffsetToShowFullRange = maximumScrollOffset() - visibleOffsetRange; if (scrollOffset() > maxOffsetToShowFullRange) { m_layouter->setScrollOffset(qMax(qreal(0), maxOffsetToShowFullRange)); firstVisibleIndex = m_layouter->firstVisibleIndex(); } const int lastVisibleIndex = m_layouter->lastVisibleIndex(); int firstSibblingIndex = -1; int lastSibblingIndex = -1; const bool supportsExpanding = supportsItemExpanding(); QList reusableItems = recycleInvisibleItems(firstVisibleIndex, lastVisibleIndex, hint); // Assure that for each visible item a KItemListWidget is available. KItemListWidget // instances from invisible items are reused. If no reusable items are // found then new KItemListWidget instances get created. const bool animate = (hint == Animation); for (int i = firstVisibleIndex; i <= lastVisibleIndex; ++i) { bool applyNewPos = true; bool wasHidden = false; const QRectF itemBounds = m_layouter->itemRect(i); const QPointF newPos = itemBounds.topLeft(); KItemListWidget* widget = m_visibleItems.value(i); if (!widget) { wasHidden = true; if (!reusableItems.isEmpty()) { // Reuse a KItemListWidget instance from an invisible item const int oldIndex = reusableItems.takeLast(); widget = m_visibleItems.value(oldIndex); setWidgetIndex(widget, i); updateWidgetProperties(widget, i); initializeItemListWidget(widget); } else { // No reusable KItemListWidget instance is available, create a new one widget = createWidget(i); } widget->resize(itemBounds.size()); if (animate && changedCount < 0) { // Items have been deleted. if (i >= changedIndex) { // The item is located behind the removed range. Move the // created item to the imaginary old position outside the // view. It will get animated to the new position later. const int previousIndex = i - changedCount; const QRectF itemRect = m_layouter->itemRect(previousIndex); if (itemRect.isEmpty()) { const QPointF invisibleOldPos = (scrollOrientation() == Qt::Vertical) ? QPointF(0, size().height()) : QPointF(size().width(), 0); widget->setPos(invisibleOldPos); } else { widget->setPos(itemRect.topLeft()); } applyNewPos = false; } } if (supportsExpanding && changedCount == 0) { if (firstSibblingIndex < 0) { firstSibblingIndex = i; } lastSibblingIndex = i; } } if (animate) { if (m_animation->isStarted(widget, KItemListViewAnimation::MovingAnimation)) { m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos); applyNewPos = false; } const bool itemsRemoved = (changedCount < 0); const bool itemsInserted = (changedCount > 0); if (itemsRemoved && (i >= changedIndex)) { // The item is located after the removed items. Animate the moving of the position. applyNewPos = !moveWidget(widget, newPos); } else if (itemsInserted && i >= changedIndex) { // The item is located after the first inserted item if (i <= changedIndex + changedCount - 1) { // The item is an inserted item. Animate the appearing of the item. // For performance reasons no animation is done when changedCount is equal // to all available items. if (changedCount < m_model->count()) { m_animation->start(widget, KItemListViewAnimation::CreateAnimation); } } else if (!m_animation->isStarted(widget, KItemListViewAnimation::CreateAnimation)) { // The item was already there before, so animate the moving of the position. // No moving animation is done if the item is animated by a create animation: This // prevents a "move animation mess" when inserting several ranges in parallel. applyNewPos = !moveWidget(widget, newPos); } } else if (!itemsRemoved && !itemsInserted && !wasHidden) { // The size of the view might have been changed. Animate the moving of the position. applyNewPos = !moveWidget(widget, newPos); } } else { m_animation->stop(widget); } if (applyNewPos) { widget->setPos(newPos); } Q_ASSERT(widget->index() == i); widget->setVisible(true); if (widget->size() != itemBounds.size()) { // Resize the widget for the item to the changed size. if (animate) { // If a dynamic item size is used then no animation is done in the direction // of the dynamic size. if (m_itemSize.width() <= 0) { // The width is dynamic, apply the new width without animation. widget->resize(itemBounds.width(), widget->size().height()); } else if (m_itemSize.height() <= 0) { // The height is dynamic, apply the new height without animation. widget->resize(widget->size().width(), itemBounds.height()); } m_animation->start(widget, KItemListViewAnimation::ResizeAnimation, itemBounds.size()); } else { widget->resize(itemBounds.size()); } } // Updating the cell-information must be done as last step: The decision whether the // moving-animation should be started at all is based on the previous cell-information. const Cell cell(m_layouter->itemColumn(i), m_layouter->itemRow(i)); m_visibleCells.insert(i, cell); } // Delete invisible KItemListWidget instances that have not been reused foreach (int index, reusableItems) { recycleWidget(m_visibleItems.value(index)); } if (supportsExpanding && firstSibblingIndex >= 0) { Q_ASSERT(lastSibblingIndex >= 0); updateSiblingsInformation(firstSibblingIndex, lastSibblingIndex); } if (m_grouped) { // Update the layout of all visible group headers QHashIterator it(m_visibleGroups); while (it.hasNext()) { it.next(); updateGroupHeaderLayout(it.key()); } } emitOffsetChanges(); } QList KItemListView::recycleInvisibleItems(int firstVisibleIndex, int lastVisibleIndex, LayoutAnimationHint hint) { // Determine all items that are completely invisible and might be // reused for items that just got (at least partly) visible. If the // animation hint is set to 'Animation' items that do e.g. an animated // moving of their position are not marked as invisible: This assures // that a scrolling inside the view can be done without breaking an animation. QList items; QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); KItemListWidget* widget = it.value(); const int index = widget->index(); const bool invisible = (index < firstVisibleIndex) || (index > lastVisibleIndex); if (invisible) { if (m_animation->isStarted(widget)) { if (hint == NoAnimation) { // Stopping the animation will call KItemListView::slotAnimationFinished() // and the widget will be recycled if necessary there. m_animation->stop(widget); } } else { widget->setVisible(false); items.append(index); if (m_grouped) { recycleGroupHeaderForWidget(widget); } } } } return items; } bool KItemListView::moveWidget(KItemListWidget* widget,const QPointF& newPos) { if (widget->pos() == newPos) { return false; } bool startMovingAnim = false; if (m_itemSize.isEmpty()) { // The items are not aligned in a grid but either as columns or rows. startMovingAnim = true; } else { // When having a grid the moving-animation should only be started, if it is done within // one row in the vertical scroll-orientation or one column in the horizontal scroll-orientation. // Otherwise instead of a moving-animation a create-animation on the new position will be used // instead. This is done to prevent overlapping (and confusing) moving-animations. const int index = widget->index(); const Cell cell = m_visibleCells.value(index); if (cell.column >= 0 && cell.row >= 0) { if (scrollOrientation() == Qt::Vertical) { startMovingAnim = (cell.row == m_layouter->itemRow(index)); } else { startMovingAnim = (cell.column == m_layouter->itemColumn(index)); } } } if (startMovingAnim) { m_animation->start(widget, KItemListViewAnimation::MovingAnimation, newPos); return true; } m_animation->stop(widget); m_animation->start(widget, KItemListViewAnimation::CreateAnimation); return false; } void KItemListView::emitOffsetChanges() { const qreal newScrollOffset = m_layouter->scrollOffset(); if (m_oldScrollOffset != newScrollOffset) { emit scrollOffsetChanged(newScrollOffset, m_oldScrollOffset); m_oldScrollOffset = newScrollOffset; } const qreal newMaximumScrollOffset = m_layouter->maximumScrollOffset(); if (m_oldMaximumScrollOffset != newMaximumScrollOffset) { emit maximumScrollOffsetChanged(newMaximumScrollOffset, m_oldMaximumScrollOffset); m_oldMaximumScrollOffset = newMaximumScrollOffset; } const qreal newItemOffset = m_layouter->itemOffset(); if (m_oldItemOffset != newItemOffset) { emit itemOffsetChanged(newItemOffset, m_oldItemOffset); m_oldItemOffset = newItemOffset; } const qreal newMaximumItemOffset = m_layouter->maximumItemOffset(); if (m_oldMaximumItemOffset != newMaximumItemOffset) { emit maximumItemOffsetChanged(newMaximumItemOffset, m_oldMaximumItemOffset); m_oldMaximumItemOffset = newMaximumItemOffset; } } KItemListWidget* KItemListView::createWidget(int index) { KItemListWidget* widget = widgetCreator()->create(this); widget->setFlag(QGraphicsItem::ItemStacksBehindParent); m_visibleItems.insert(index, widget); m_visibleCells.insert(index, Cell()); updateWidgetProperties(widget, index); initializeItemListWidget(widget); return widget; } void KItemListView::recycleWidget(KItemListWidget* widget) { if (m_grouped) { recycleGroupHeaderForWidget(widget); } const int index = widget->index(); m_visibleItems.remove(index); m_visibleCells.remove(index); widgetCreator()->recycle(widget); } void KItemListView::setWidgetIndex(KItemListWidget* widget, int index) { const int oldIndex = widget->index(); m_visibleItems.remove(oldIndex); m_visibleCells.remove(oldIndex); m_visibleItems.insert(index, widget); m_visibleCells.insert(index, Cell()); widget->setIndex(index); } void KItemListView::moveWidgetToIndex(KItemListWidget* widget, int index) { const int oldIndex = widget->index(); const Cell oldCell = m_visibleCells.value(oldIndex); setWidgetIndex(widget, index); const Cell newCell(m_layouter->itemColumn(index), m_layouter->itemRow(index)); const bool vertical = (scrollOrientation() == Qt::Vertical); const bool updateCell = (vertical && oldCell.row == newCell.row) || (!vertical && oldCell.column == newCell.column); if (updateCell) { m_visibleCells.insert(index, newCell); } } void KItemListView::setLayouterSize(const QSizeF& size, SizeType sizeType) { switch (sizeType) { case LayouterSize: m_layouter->setSize(size); break; case ItemSize: m_layouter->setItemSize(size); break; default: break; } } void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index) { widget->setVisibleRoles(m_visibleRoles); updateWidgetColumnWidths(widget); widget->setStyleOption(m_styleOption); const KItemListSelectionManager* selectionManager = m_controller->selectionManager(); // In SingleSelection mode (e.g., in the Places Panel), the current item is // always the selected item. It is not necessary to highlight the current item then. if (m_controller->selectionBehavior() != KItemListController::SingleSelection) { widget->setCurrent(index == selectionManager->currentItem()); } widget->setSelected(selectionManager->isSelected(index)); widget->setHovered(false); widget->setEnabledSelectionToggle(enabledSelectionToggles()); widget->setIndex(index); widget->setData(m_model->data(index)); widget->setSiblingsInformation(QBitArray()); updateAlternateBackgroundForWidget(widget); if (m_grouped) { updateGroupHeaderForWidget(widget); } } void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget) { Q_ASSERT(m_grouped); const int index = widget->index(); if (!m_layouter->isFirstGroupItem(index)) { // The widget does not represent the first item of a group // and hence requires no header recycleGroupHeaderForWidget(widget); return; } const QList > groups = model()->groups(); if (groups.isEmpty() || !groupHeaderCreator()) { return; } KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget); if (!groupHeader) { groupHeader = groupHeaderCreator()->create(this); groupHeader->setParentItem(widget); m_visibleGroups.insert(widget, groupHeader); connect(widget, &KItemListWidget::geometryChanged, this, &KItemListView::slotGeometryOfGroupHeaderParentChanged); } Q_ASSERT(groupHeader->parentItem() == widget); const int groupIndex = groupIndexForItem(index); Q_ASSERT(groupIndex >= 0); groupHeader->setData(groups.at(groupIndex).second); groupHeader->setRole(model()->sortRole()); groupHeader->setStyleOption(m_styleOption); groupHeader->setScrollOrientation(scrollOrientation()); groupHeader->setItemIndex(index); groupHeader->show(); } void KItemListView::updateGroupHeaderLayout(KItemListWidget* widget) { KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget); Q_ASSERT(groupHeader); const int index = widget->index(); const QRectF groupHeaderRect = m_layouter->groupHeaderRect(index); const QRectF itemRect = m_layouter->itemRect(index); // The group-header is a child of the itemlist widget. Translate the // group header position to the relative position. if (scrollOrientation() == Qt::Vertical) { // In the vertical scroll orientation the group header should always span // the whole width no matter which temporary position the parent widget // has. In this case the x-position and width will be adjusted manually. const qreal x = -widget->x() - itemOffset(); const qreal width = maximumItemOffset(); groupHeader->setPos(x, -groupHeaderRect.height()); groupHeader->resize(width, groupHeaderRect.size().height()); } else { groupHeader->setPos(groupHeaderRect.x() - itemRect.x(), -widget->y()); groupHeader->resize(groupHeaderRect.size()); } } void KItemListView::recycleGroupHeaderForWidget(KItemListWidget* widget) { KItemListGroupHeader* header = m_visibleGroups.value(widget); if (header) { header->setParentItem(0); groupHeaderCreator()->recycle(header); m_visibleGroups.remove(widget); disconnect(widget, &KItemListWidget::geometryChanged, this, &KItemListView::slotGeometryOfGroupHeaderParentChanged); } } void KItemListView::updateVisibleGroupHeaders() { Q_ASSERT(m_grouped); m_layouter->markAsDirty(); QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); updateGroupHeaderForWidget(it.value()); } } int KItemListView::groupIndexForItem(int index) const { Q_ASSERT(m_grouped); const QList > groups = model()->groups(); if (groups.isEmpty()) { return -1; } int min = 0; int max = groups.count() - 1; int mid = 0; do { mid = (min + max) / 2; if (index > groups[mid].first) { min = mid + 1; } else { max = mid - 1; } } while (groups[mid].first != index && min <= max); if (min > max) { while (groups[mid].first > index && mid > 0) { --mid; } } return mid; } void KItemListView::updateAlternateBackgrounds() { QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); updateAlternateBackgroundForWidget(it.value()); } } void KItemListView::updateAlternateBackgroundForWidget(KItemListWidget* widget) { bool enabled = useAlternateBackgrounds(); if (enabled) { const int index = widget->index(); enabled = (index & 0x1) > 0; if (m_grouped) { const int groupIndex = groupIndexForItem(index); if (groupIndex >= 0) { const QList > groups = model()->groups(); const int indexOfFirstGroupItem = groups[groupIndex].first; const int relativeIndex = index - indexOfFirstGroupItem; enabled = (relativeIndex & 0x1) > 0; } } } widget->setAlternateBackground(enabled); } bool KItemListView::useAlternateBackgrounds() const { return m_itemSize.isEmpty() && m_visibleRoles.count() > 1; } QHash KItemListView::preferredColumnWidths(const KItemRangeList& itemRanges) const { QElapsedTimer timer; timer.start(); QHash widths; // Calculate the minimum width for each column that is required // to show the headline unclipped. const QFontMetricsF fontMetrics(m_headerWidget->font()); const int gripMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderGripMargin); const int headerMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderMargin); foreach (const QByteArray& visibleRole, visibleRoles()) { const QString headerText = m_model->roleDescription(visibleRole); const qreal headerWidth = fontMetrics.width(headerText) + gripMargin + headerMargin * 2; widths.insert(visibleRole, headerWidth); } // Calculate the preferred column withs for each item and ignore values // smaller than the width for showing the headline unclipped. const KItemListWidgetCreatorBase* creator = widgetCreator(); int calculatedItemCount = 0; bool maxTimeExceeded = false; foreach (const KItemRange& itemRange, itemRanges) { const int startIndex = itemRange.index; const int endIndex = startIndex + itemRange.count - 1; for (int i = startIndex; i <= endIndex; ++i) { foreach (const QByteArray& visibleRole, visibleRoles()) { qreal maxWidth = widths.value(visibleRole, 0); const qreal width = creator->preferredRoleColumnWidth(visibleRole, i, this); maxWidth = qMax(width, maxWidth); widths.insert(visibleRole, maxWidth); } if (calculatedItemCount > 100 && timer.elapsed() > 200) { // When having several thousands of items calculating the sizes can get // very expensive. We accept a possibly too small role-size in favour // of having no blocking user interface. maxTimeExceeded = true; break; } ++calculatedItemCount; } if (maxTimeExceeded) { break; } } return widths; } void KItemListView::applyColumnWidthsFromHeader() { // Apply the new size to the layouter const qreal requiredWidth = columnWidthsSum(); const QSizeF dynamicItemSize(qMax(size().width(), requiredWidth), m_itemSize.height()); m_layouter->setItemSize(dynamicItemSize); // Update the role sizes for all visible widgets QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); updateWidgetColumnWidths(it.value()); } } void KItemListView::updateWidgetColumnWidths(KItemListWidget* widget) { foreach (const QByteArray& role, m_visibleRoles) { widget->setColumnWidth(role, m_headerWidget->columnWidth(role)); } } void KItemListView::updatePreferredColumnWidths(const KItemRangeList& itemRanges) { Q_ASSERT(m_itemSize.isEmpty()); const int itemCount = m_model->count(); int rangesItemCount = 0; foreach (const KItemRange& range, itemRanges) { rangesItemCount += range.count; } if (itemCount == rangesItemCount) { const QHash preferredWidths = preferredColumnWidths(itemRanges); foreach (const QByteArray& role, m_visibleRoles) { m_headerWidget->setPreferredColumnWidth(role, preferredWidths.value(role)); } } else { // Only a sub range of the roles need to be determined. // The chances are good that the widths of the sub ranges // already fit into the available widths and hence no // expensive update might be required. bool changed = false; const QHash updatedWidths = preferredColumnWidths(itemRanges); QHashIterator it(updatedWidths); while (it.hasNext()) { it.next(); const QByteArray& role = it.key(); const qreal updatedWidth = it.value(); const qreal currentWidth = m_headerWidget->preferredColumnWidth(role); if (updatedWidth > currentWidth) { m_headerWidget->setPreferredColumnWidth(role, updatedWidth); changed = true; } } if (!changed) { // All the updated sizes are smaller than the current sizes and no change // of the stretched roles-widths is required return; } } if (m_headerWidget->automaticColumnResizing()) { applyAutomaticColumnWidths(); } } void KItemListView::updatePreferredColumnWidths() { if (m_model) { updatePreferredColumnWidths(KItemRangeList() << KItemRange(0, m_model->count())); } } void KItemListView::applyAutomaticColumnWidths() { Q_ASSERT(m_itemSize.isEmpty()); Q_ASSERT(m_headerWidget->automaticColumnResizing()); if (m_visibleRoles.isEmpty()) { return; } // Calculate the maximum size of an item by considering the // visible role sizes and apply them to the layouter. If the // size does not use the available view-size the size of the // first role will get stretched. foreach (const QByteArray& role, m_visibleRoles) { const qreal preferredWidth = m_headerWidget->preferredColumnWidth(role); m_headerWidget->setColumnWidth(role, preferredWidth); } const QByteArray firstRole = m_visibleRoles.first(); qreal firstColumnWidth = m_headerWidget->columnWidth(firstRole); QSizeF dynamicItemSize = m_itemSize; qreal requiredWidth = columnWidthsSum(); const qreal availableWidth = size().width(); if (requiredWidth < availableWidth) { // Stretch the first column to use the whole remaining width firstColumnWidth += availableWidth - requiredWidth; m_headerWidget->setColumnWidth(firstRole, firstColumnWidth); } else if (requiredWidth > availableWidth && m_visibleRoles.count() > 1) { // Shrink the first column to be able to show as much other // columns as possible qreal shrinkedFirstColumnWidth = firstColumnWidth - requiredWidth + availableWidth; // TODO: A proper calculation of the minimum width depends on the implementation // of KItemListWidget. Probably a kind of minimum size-hint should be introduced // later. const qreal minWidth = qMin(firstColumnWidth, qreal(m_styleOption.iconSize * 2 + 200)); if (shrinkedFirstColumnWidth < minWidth) { shrinkedFirstColumnWidth = minWidth; } m_headerWidget->setColumnWidth(firstRole, shrinkedFirstColumnWidth); requiredWidth -= firstColumnWidth - shrinkedFirstColumnWidth; } dynamicItemSize.rwidth() = qMax(requiredWidth, availableWidth); m_layouter->setItemSize(dynamicItemSize); // Update the role sizes for all visible widgets QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); updateWidgetColumnWidths(it.value()); } } qreal KItemListView::columnWidthsSum() const { qreal widthsSum = 0; foreach (const QByteArray& role, m_visibleRoles) { widthsSum += m_headerWidget->columnWidth(role); } return widthsSum; } QRectF KItemListView::headerBoundaries() const { return m_headerWidget->isVisible() ? m_headerWidget->geometry() : QRectF(); } bool KItemListView::changesItemGridLayout(const QSizeF& newGridSize, const QSizeF& newItemSize, const QSizeF& newItemMargin) const { if (newItemSize.isEmpty() || newGridSize.isEmpty()) { return false; } if (m_layouter->scrollOrientation() == Qt::Vertical) { const qreal itemWidth = m_layouter->itemSize().width(); if (itemWidth > 0) { const int newColumnCount = itemsPerSize(newGridSize.width(), newItemSize.width(), newItemMargin.width()); if (m_model->count() > newColumnCount) { const int oldColumnCount = itemsPerSize(m_layouter->size().width(), itemWidth, m_layouter->itemMargin().width()); return oldColumnCount != newColumnCount; } } } else { const qreal itemHeight = m_layouter->itemSize().height(); if (itemHeight > 0) { const int newRowCount = itemsPerSize(newGridSize.height(), newItemSize.height(), newItemMargin.height()); if (m_model->count() > newRowCount) { const int oldRowCount = itemsPerSize(m_layouter->size().height(), itemHeight, m_layouter->itemMargin().height()); return oldRowCount != newRowCount; } } } return false; } bool KItemListView::animateChangedItemCount(int changedItemCount) const { if (m_itemSize.isEmpty()) { // We have only columns or only rows, but no grid: An animation is usually // welcome when inserting or removing items. return !supportsItemExpanding(); } if (m_layouter->size().isEmpty() || m_layouter->itemSize().isEmpty()) { return false; } const int maximum = (scrollOrientation() == Qt::Vertical) ? m_layouter->size().width() / m_layouter->itemSize().width() : m_layouter->size().height() / m_layouter->itemSize().height(); // Only animate if up to 2/3 of a row or column are inserted or removed return changedItemCount <= maximum * 2 / 3; } bool KItemListView::scrollBarRequired(const QSizeF& size) const { const QSizeF oldSize = m_layouter->size(); m_layouter->setSize(size); const qreal maxOffset = m_layouter->maximumScrollOffset(); m_layouter->setSize(oldSize); return m_layouter->scrollOrientation() == Qt::Vertical ? maxOffset > size.height() : maxOffset > size.width(); } int KItemListView::showDropIndicator(const QPointF& pos) { QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); const KItemListWidget* widget = it.value(); const QPointF mappedPos = widget->mapFromItem(this, pos); const QRectF rect = itemRect(widget->index()); if (mappedPos.y() >= 0 && mappedPos.y() <= rect.height()) { if (m_model->supportsDropping(widget->index())) { // Keep 30% of the rectangle as the gap instead of always having a fixed gap const int gap = qMax(qreal(4.0), qreal(0.3) * rect.height()); if (mappedPos.y() >= gap && mappedPos.y() <= rect.height() - gap) { return -1; } } const bool isAboveItem = (mappedPos.y () < rect.height() / 2); const qreal y = isAboveItem ? rect.top() : rect.bottom(); const QRectF draggingInsertIndicator(rect.left(), y, rect.width(), 1); if (m_dropIndicator != draggingInsertIndicator) { m_dropIndicator = draggingInsertIndicator; update(); } int index = widget->index(); if (!isAboveItem) { ++index; } return index; } } const QRectF firstItemRect = itemRect(firstVisibleIndex()); return (pos.y() <= firstItemRect.top()) ? 0 : -1; } void KItemListView::hideDropIndicator() { if (!m_dropIndicator.isNull()) { m_dropIndicator = QRectF(); update(); } } void KItemListView::updateGroupHeaderHeight() { qreal groupHeaderHeight = m_styleOption.fontMetrics.height(); qreal groupHeaderMargin = 0; if (scrollOrientation() == Qt::Horizontal) { // The vertical margin above and below the header should be // equal to the horizontal margin, not the vertical margin // from m_styleOption. groupHeaderHeight += 2 * m_styleOption.horizontalMargin; groupHeaderMargin = m_styleOption.horizontalMargin; } else if (m_itemSize.isEmpty()){ groupHeaderHeight += 4 * m_styleOption.padding; groupHeaderMargin = m_styleOption.iconSize / 2; } else { groupHeaderHeight += 2 * m_styleOption.padding + m_styleOption.verticalMargin; groupHeaderMargin = m_styleOption.iconSize / 4; } m_layouter->setGroupHeaderHeight(groupHeaderHeight); m_layouter->setGroupHeaderMargin(groupHeaderMargin); updateVisibleGroupHeaders(); } void KItemListView::updateSiblingsInformation(int firstIndex, int lastIndex) { if (!supportsItemExpanding() || !m_model) { return; } if (firstIndex < 0 || lastIndex < 0) { firstIndex = m_layouter->firstVisibleIndex(); lastIndex = m_layouter->lastVisibleIndex(); } else { const bool isRangeVisible = (firstIndex <= m_layouter->lastVisibleIndex() && lastIndex >= m_layouter->firstVisibleIndex()); if (!isRangeVisible) { return; } } int previousParents = 0; QBitArray previousSiblings; // The rootIndex describes the first index where the siblings get // calculated from. For the calculation the upper most parent item // is required. For performance reasons it is checked first whether // the visible items before or after the current range already // contain a siblings information which can be used as base. int rootIndex = firstIndex; KItemListWidget* widget = m_visibleItems.value(firstIndex - 1); if (!widget) { // There is no visible widget before the range, check whether there // is one after the range: widget = m_visibleItems.value(lastIndex + 1); if (widget) { // The sibling information of the widget may only be used if // all items of the range have the same number of parents. const int parents = m_model->expandedParentsCount(lastIndex + 1); for (int i = lastIndex; i >= firstIndex; --i) { if (m_model->expandedParentsCount(i) != parents) { widget = 0; break; } } } } if (widget) { // Performance optimization: Use the sibling information of the visible // widget beside the given range. previousSiblings = widget->siblingsInformation(); if (previousSiblings.isEmpty()) { return; } previousParents = previousSiblings.count() - 1; previousSiblings.truncate(previousParents); } else { // Potentially slow path: Go back to the upper most parent of firstIndex // to be able to calculate the initial value for the siblings. while (rootIndex > 0 && m_model->expandedParentsCount(rootIndex) > 0) { --rootIndex; } } Q_ASSERT(previousParents >= 0); for (int i = rootIndex; i <= lastIndex; ++i) { // Update the parent-siblings in case if the current item represents // a child or an upper parent. const int currentParents = m_model->expandedParentsCount(i); Q_ASSERT(currentParents >= 0); if (previousParents < currentParents) { previousParents = currentParents; previousSiblings.resize(currentParents); previousSiblings.setBit(currentParents - 1, hasSiblingSuccessor(i - 1)); } else if (previousParents > currentParents) { previousParents = currentParents; previousSiblings.truncate(currentParents); } if (i >= firstIndex) { // The index represents a visible item. Apply the parent-siblings // and update the sibling of the current item. KItemListWidget* widget = m_visibleItems.value(i); if (!widget) { continue; } QBitArray siblings = previousSiblings; siblings.resize(siblings.count() + 1); siblings.setBit(siblings.count() - 1, hasSiblingSuccessor(i)); widget->setSiblingsInformation(siblings); } } } bool KItemListView::hasSiblingSuccessor(int index) const { bool hasSuccessor = false; const int parentsCount = m_model->expandedParentsCount(index); int successorIndex = index + 1; // Search the next sibling const int itemCount = m_model->count(); while (successorIndex < itemCount) { const int currentParentsCount = m_model->expandedParentsCount(successorIndex); if (currentParentsCount == parentsCount) { hasSuccessor = true; break; } else if (currentParentsCount < parentsCount) { break; } ++successorIndex; } if (m_grouped && hasSuccessor) { // If the sibling is part of another group, don't mark it as // successor as the group header is between the sibling connections. for (int i = index + 1; i <= successorIndex; ++i) { if (m_layouter->isFirstGroupItem(i)) { hasSuccessor = false; break; } } } return hasSuccessor; } void KItemListView::disconnectRoleEditingSignals(int index) { KItemListWidget* widget = m_visibleItems.value(index); if (!widget) { return; } disconnect(widget, &KItemListWidget::roleEditingCanceled, this, nullptr); disconnect(widget, &KItemListWidget::roleEditingFinished, this, nullptr); } int KItemListView::calculateAutoScrollingIncrement(int pos, int range, int oldInc) { int inc = 0; const int minSpeed = 4; const int maxSpeed = 128; const int speedLimiter = 96; const int autoScrollBorder = 64; // Limit the increment that is allowed to be added in comparison to 'oldInc'. // This assures that the autoscrolling speed grows gradually. const int incLimiter = 1; if (pos < autoScrollBorder) { inc = -minSpeed + qAbs(pos - autoScrollBorder) * (pos - autoScrollBorder) / speedLimiter; inc = qMax(inc, -maxSpeed); inc = qMax(inc, oldInc - incLimiter); } else if (pos > range - autoScrollBorder) { inc = minSpeed + qAbs(pos - range + autoScrollBorder) * (pos - range + autoScrollBorder) / speedLimiter; inc = qMin(inc, maxSpeed); inc = qMin(inc, oldInc + incLimiter); } return inc; } int KItemListView::itemsPerSize(qreal size, qreal itemSize, qreal itemMargin) { const qreal availableSize = size - itemMargin; const int count = availableSize / (itemSize + itemMargin); return count; } KItemListCreatorBase::~KItemListCreatorBase() { qDeleteAll(m_recycleableWidgets); qDeleteAll(m_createdWidgets); } void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget* widget) { m_createdWidgets.insert(widget); } void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget* widget) { Q_ASSERT(m_createdWidgets.contains(widget)); m_createdWidgets.remove(widget); if (m_recycleableWidgets.count() < 100) { m_recycleableWidgets.append(widget); widget->setVisible(false); } else { delete widget; } } QGraphicsWidget* KItemListCreatorBase::popRecycleableWidget() { if (m_recycleableWidgets.isEmpty()) { return 0; } QGraphicsWidget* widget = m_recycleableWidgets.takeLast(); m_createdWidgets.insert(widget); return widget; } KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase() { } void KItemListWidgetCreatorBase::recycle(KItemListWidget* widget) { widget->setParentItem(0); widget->setOpacity(1.0); pushRecycleableWidget(widget); } KItemListGroupHeaderCreatorBase::~KItemListGroupHeaderCreatorBase() { } void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader* header) { header->setOpacity(1.0); pushRecycleableWidget(header); } diff --git a/src/kitemviews/kitemlistview.h b/src/kitemviews/kitemlistview.h index 9adf9f86f..c93297c47 100644 --- a/src/kitemviews/kitemlistview.h +++ b/src/kitemviews/kitemlistview.h @@ -1,910 +1,915 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz * * * * Based on the Itemviews NG project from Trolltech Labs: * * http://qt.gitorious.org/qt-labs/itemviews-ng * * * * 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 * ***************************************************************************/ #ifndef KITEMLISTVIEW_H #define KITEMLISTVIEW_H #include "dolphin_export.h" #include #include #include #include #include #include #include class KItemListController; class KItemListGroupHeaderCreatorBase; class KItemListHeader; class KItemListHeaderWidget; class KItemListSizeHintResolver; class KItemListRubberBand; class KItemListViewAnimation; class KItemListViewLayouter; class KItemListWidget; class KItemListWidgetInformant; class KItemListWidgetCreatorBase; class QTimer; /** * @brief Represents the view of an item-list. * * The view is responsible for showing the items of the model within * a GraphicsItem. Each visible item is represented by a KItemListWidget. * * The created view must be applied to the KItemListController with * KItemListController::setView() or with the constructor of * KItemListController. * * @see KItemListWidget * @see KItemModelBase */ class DOLPHIN_EXPORT KItemListView : public QGraphicsWidget { Q_OBJECT Q_PROPERTY(qreal scrollOffset READ scrollOffset WRITE setScrollOffset) Q_PROPERTY(qreal itemOffset READ itemOffset WRITE setItemOffset) public: KItemListView(QGraphicsWidget* parent = 0); virtual ~KItemListView(); /** * Offset of the scrollbar that represents the scroll-orientation * (see setScrollOrientation()). */ void setScrollOffset(qreal offset); qreal scrollOffset() const; qreal maximumScrollOffset() const; /** * Offset related to an item, that does not fit into the available * size of the listview. If the scroll-orientation is vertical * the item-offset describes the offset of the horizontal axe, if * the scroll-orientation is horizontal the item-offset describes * the offset of the vertical axe. */ void setItemOffset(qreal scrollOffset); qreal itemOffset() const; qreal maximumItemOffset() const; int maximumVisibleItems() const; void setVisibleRoles(const QList& roles); QList visibleRoles() const; /** * If set to true an automatic scrolling is done as soon as the * mouse is moved near the borders of the view. Per default * the automatic scrolling is turned off. */ void setAutoScroll(bool enabled); bool autoScroll() const; /** * If set to true selection-toggles will be shown when hovering * an item. Per default the selection-toggles are disabled. */ void setEnabledSelectionToggles(bool enabled); bool enabledSelectionToggles() const; /** * @return Controller of the item-list. The controller gets * initialized by KItemListController::setView() and will * result in calling KItemListController::onControllerChanged(). */ KItemListController* controller() const; /** * @return Model of the item-list. The model gets * initialized by KItemListController::setModel() and will * result in calling KItemListController::onModelChanged(). */ KItemModelBase* model() const; /** * Sets the creator that creates a widget showing the * content of one model-item. Usually it is sufficient * to implement a custom widget X derived from KItemListWidget and * set the creator by: * * itemListView->setWidgetCreator(new KItemListWidgetCreator()); * * The ownership of the widget creator is transferred to * the item-list view. **/ void setWidgetCreator(KItemListWidgetCreatorBase* widgetCreator); KItemListWidgetCreatorBase* widgetCreator() const; /** * Sets the creator that creates a group header. Usually it is sufficient * to implement a custom header widget X derived from KItemListGroupHeader and * set the creator by: * * itemListView->setGroupHeaderCreator(new KItemListGroupHeaderCreator()); * * The ownership of the gropup header creator is transferred to * the item-list view. **/ void setGroupHeaderCreator(KItemListGroupHeaderCreatorBase* groupHeaderCreator); KItemListGroupHeaderCreatorBase* groupHeaderCreator() const; /** * @return The basic size of all items. The size of an item may be larger than - * the basic size (see KItemListView::itemSizeHint() and KItemListView::itemRect()). + * the basic size (see KItemListView::itemRect()). */ QSizeF itemSize() const; + /** + * @return The size hint of all items. It is provided by the KItemListSizeHintResolver. + */ + QSizeF itemSizeHint() const; + const KItemListStyleOption& styleOption() const; virtual void setGeometry(const QRectF& rect) Q_DECL_OVERRIDE; /** * @return The page step which should be used by the vertical scroll bar. * This is the height of the view except for the header widget. */ qreal verticalPageStep() const; /** * @return Index of the item that is below the point \a pos. * The position is relative to the upper right of * the visible area. Only (at least partly) visible * items are considered. -1 is returned if no item is * below the position. */ int itemAt(const QPointF& pos) const; bool isAboveSelectionToggle(int index, const QPointF& pos) const; bool isAboveExpansionToggle(int index, const QPointF& pos) const; /** * @return Index of the first item that is at least partly visible. * -1 is returned if the model contains no items. */ int firstVisibleIndex() const; /** * @return Index of the last item that is at least partly visible. * -1 is returned if the model contains no items. */ int lastVisibleIndex() const; /** * @return Calculates the required size for all items in the model. * It might be larger than KItemListView::itemSize(). * In this case the layout grid will be stretched to assure an * unclipped item. * NOTE: the logical height (width) is actually the * width (height) if the scroll orientation is Qt::Vertical! */ void calculateItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint) const; /** * If set to true, items having child-items can be expanded to show the child-items as * part of the view. Per default the expanding of items is is disabled. If expanding of * items is enabled, the methods KItemModelBase::setExpanded(), KItemModelBase::isExpanded(), * KItemModelBase::isExpandable() and KItemModelBase::expandedParentsCount() * must be reimplemented. The view-implementation * has to take care itself how to visually represent the expanded items provided * by the model. */ void setSupportsItemExpanding(bool supportsExpanding); bool supportsItemExpanding() const; /** * @return The rectangle of the item relative to the top/left of * the currently visible area (see KItemListView::offset()). */ QRectF itemRect(int index) const; /** * @return The context rectangle of the item relative to the top/left of * the currently visible area (see KItemListView::offset()). The * context rectangle is defined by by the united rectangle of * the icon rectangle and the text rectangle (see KItemListWidget::iconRect() * and KItemListWidget::textRect()) and is useful as reference for e.g. aligning * a tooltip or a context-menu for an item. Note that a context rectangle will * only be returned for (at least partly) visible items. An empty rectangle will * be returned for fully invisible items. */ QRectF itemContextRect(int index) const; /** * Scrolls to the item with the index \a index so that the item * will be fully visible. */ void scrollToItem(int index); /** * If several properties of KItemListView are changed synchronously, it is * recommended to encapsulate the calls between beginTransaction() and endTransaction(). * This prevents unnecessary and expensive layout-calculations. */ void beginTransaction(); /** * Counterpart to beginTransaction(). The layout changes will only be animated if * all property changes between beginTransaction() and endTransaction() support * animations. */ void endTransaction(); bool isTransactionActive() const; /** * Turns on the header if \p visible is true. Per default the * header is not visible. Usually the header is turned on when * showing a classic "table-view" to describe the shown columns. */ void setHeaderVisible(bool visible); bool isHeaderVisible() const; /** * @return Header of the list. The header is also available if it is not shown * (see KItemListView::setHeaderShown()). */ KItemListHeader* header() const; /** * @return Pixmap that is used for a drag operation based on the * items given by \a indexes. */ virtual QPixmap createDragPixmap(const KItemSet& indexes) const; /** * Lets the user edit the role \a role for item with the index \a index. */ void editRole(int index, const QByteArray& role); virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0) Q_DECL_OVERRIDE; signals: void scrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous); void scrollOffsetChanged(qreal current, qreal previous); void maximumScrollOffsetChanged(qreal current, qreal previous); void itemOffsetChanged(qreal current, qreal previous); void maximumItemOffsetChanged(qreal current, qreal previous); void scrollTo(qreal newOffset); /** * Is emitted if the user has changed the sort order by clicking on a * header item (see KItemListView::setHeaderShown()). The sort order * of the model has already been adjusted to * the current sort order. Note that no signal will be emitted if the * sort order of the model has been changed without user interaction. */ void sortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous); /** * Is emitted if the user has changed the sort role by clicking on a * header item (see KItemListView::setHeaderShown()). The sort role * of the model has already been adjusted to * the current sort role. Note that no signal will be emitted if the * sort role of the model has been changed without user interaction. */ void sortRoleChanged(const QByteArray& current, const QByteArray& previous); /** * Is emitted if the user has changed the visible roles by moving a header * item (see KItemListView::setHeaderShown()). Note that no signal will be * emitted if the roles have been changed without user interaction by * KItemListView::setVisibleRoles(). */ void visibleRolesChanged(const QList& current, const QList& previous); void roleEditingCanceled(int index, const QByteArray& role, const QVariant& value); void roleEditingFinished(int index, const QByteArray& role, const QVariant& value); protected: virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) Q_DECL_OVERRIDE; void setItemSize(const QSizeF& size); void setStyleOption(const KItemListStyleOption& option); /** * If the scroll-orientation is vertical, the items are ordered * from top to bottom (= default setting). If the scroll-orientation * is horizontal, the items are ordered from left to right. */ void setScrollOrientation(Qt::Orientation orientation); Qt::Orientation scrollOrientation() const; /** * Factory method for creating a default widget-creator. The method will be used * in case if setWidgetCreator() has not been set by the application. * @return New instance of the widget-creator that should be used per * default. */ virtual KItemListWidgetCreatorBase* defaultWidgetCreator() const; /** * Factory method for creating a default group-header-creator. The method will be used * in case if setGroupHeaderCreator() has not been set by the application. * @return New instance of the group-header-creator that should be used per * default. */ virtual KItemListGroupHeaderCreatorBase* defaultGroupHeaderCreator() const; /** * Is called when creating a new KItemListWidget instance and allows derived * classes to do a custom initialization. */ virtual void initializeItemListWidget(KItemListWidget* item); /** * @return True if at least one of the changed roles \p changedRoles might result * in the need to update the item-size hint (see KItemListView::itemSizeHint()). * Per default true is returned which means on each role-change of existing items * the item-size hints are recalculated. For performance reasons it is recommended * to return false in case if a role-change will not result in a changed * item-size hint. */ virtual bool itemSizeHintUpdateRequired(const QSet& changedRoles) const; virtual void onControllerChanged(KItemListController* current, KItemListController* previous); virtual void onModelChanged(KItemModelBase* current, KItemModelBase* previous); virtual void onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous); virtual void onItemSizeChanged(const QSizeF& current, const QSizeF& previous); virtual void onScrollOffsetChanged(qreal current, qreal previous); virtual void onVisibleRolesChanged(const QList& current, const QList& previous); virtual void onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous); virtual void onSupportsItemExpandingChanged(bool supportsExpanding); virtual void onTransactionBegin(); virtual void onTransactionEnd(); virtual bool event(QEvent* event) Q_DECL_OVERRIDE; virtual void mousePressEvent(QGraphicsSceneMouseEvent* event) Q_DECL_OVERRIDE; virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event) Q_DECL_OVERRIDE; virtual void dragEnterEvent(QGraphicsSceneDragDropEvent* event) Q_DECL_OVERRIDE; virtual void dragMoveEvent(QGraphicsSceneDragDropEvent* event) Q_DECL_OVERRIDE; virtual void dragLeaveEvent(QGraphicsSceneDragDropEvent* event) Q_DECL_OVERRIDE; virtual void dropEvent(QGraphicsSceneDragDropEvent* event) Q_DECL_OVERRIDE; QList visibleItemListWidgets() const; virtual void updateFont(); virtual void updatePalette(); protected slots: virtual void slotItemsInserted(const KItemRangeList& itemRanges); virtual void slotItemsRemoved(const KItemRangeList& itemRanges); virtual void slotItemsMoved(const KItemRange& itemRange, const QList& movedToIndexes); virtual void slotItemsChanged(const KItemRangeList& itemRanges, const QSet& roles); virtual void slotGroupsChanged(); virtual void slotGroupedSortingChanged(bool current); virtual void slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous); virtual void slotSortRoleChanged(const QByteArray& current, const QByteArray& previous); virtual void slotCurrentChanged(int current, int previous); virtual void slotSelectionChanged(const KItemSet& current, const KItemSet& previous); private slots: void slotAnimationFinished(QGraphicsWidget* widget, KItemListViewAnimation::AnimationType type); void slotLayoutTimerFinished(); void slotRubberBandPosChanged(); void slotRubberBandActivationChanged(bool active); /** * Is invoked if the column-width of one role in the header has * been changed by the user. The automatic resizing of columns * will be turned off as soon as this method has been called at * least once. */ void slotHeaderColumnWidthChanged(const QByteArray& role, qreal currentWidth, qreal previousWidth); /** * Is invoked if a column has been moved by the user. Applies * the moved role to the view. */ void slotHeaderColumnMoved(const QByteArray& role, int currentIndex, int previousIndex); /** * Triggers the autoscrolling if autoScroll() is enabled by checking the * current mouse position. If the mouse position is within the autoscroll * margins a timer will be started that periodically triggers the autoscrolling. */ void triggerAutoScrolling(); /** * Is invoked if the geometry of the parent-widget from a group-header has been * changed. The x-position and width of the group-header gets adjusted to assure * that it always spans the whole width even during temporary transitions of the * parent widget. */ void slotGeometryOfGroupHeaderParentChanged(); void slotRoleEditingCanceled(int index, const QByteArray& role, const QVariant& value); void slotRoleEditingFinished(int index, const QByteArray& role, const QVariant& value); private: enum LayoutAnimationHint { NoAnimation, Animation }; enum SizeType { LayouterSize, ItemSize }; void setController(KItemListController* controller); void setModel(KItemModelBase* model); KItemListRubberBand* rubberBand() const; void doLayout(LayoutAnimationHint hint, int changedIndex = 0, int changedCount = 0); /** * Helper method for doLayout: Returns a list of items that can be reused for the visible * area. Invisible group headers get recycled. The reusable items are items that are * invisible. If the animation hint is 'Animation' then items that are currently animated * won't be reused. Reusing items is faster in comparison to deleting invisible * items and creating a new instance for visible items. */ QList recycleInvisibleItems(int firstVisibleIndex, int lastVisibleIndex, LayoutAnimationHint hint); /** * Helper method for doLayout: Starts a moving-animation for the widget to the given * new position. The moving-animation is only started if the new position is within * the same row or column, otherwise the create-animation is used instead. * @return True if the moving-animation has been applied. */ bool moveWidget(KItemListWidget* widget, const QPointF& newPos); void emitOffsetChanges(); KItemListWidget* createWidget(int index); void recycleWidget(KItemListWidget* widget); /** * Changes the index of the widget to \a index and assures a consistent * update for m_visibleItems and m_visibleCells. The cell-information * for the new index will not be updated and be initialized as empty cell. */ void setWidgetIndex(KItemListWidget* widget, int index); /** * Changes the index of the widget to \a index. In opposite to * setWidgetIndex() the cell-information for the widget gets updated. * This update gives doLayout() the chance to animate the moving * of the item visually (see moveWidget()). */ void moveWidgetToIndex(KItemListWidget* widget, int index); /** * Helper method for prepareLayoutForIncreasedItemCount(). */ void setLayouterSize(const QSizeF& size, SizeType sizeType); /** * Helper method for createWidget() and setWidgetIndex() to update the properties * of the itemlist widget. */ void updateWidgetProperties(KItemListWidget* widget, int index); /** * Helper method for updateWidgetPropertes() to create or update * the itemlist group-header. */ void updateGroupHeaderForWidget(KItemListWidget* widget); /** * Updates the position and size of the group-header that belongs * to the itemlist widget \a widget. The given widget must represent * the first item of a group. */ void updateGroupHeaderLayout(KItemListWidget* widget); /** * Recycles the group-header for the widget. */ void recycleGroupHeaderForWidget(KItemListWidget* widget); /** * Helper method for slotGroupedSortingChanged(), slotSortOrderChanged() * and slotSortRoleChanged(): Iterates through all visible items and updates * the group-header widgets. */ void updateVisibleGroupHeaders(); /** * @return Index for the item in the list returned by KItemModelBase::groups() * that represents the group where the item with the index \a index * belongs to. -1 is returned if no groups are available. */ int groupIndexForItem(int index) const; /** * Updates the alternate background for all visible items. * @see updateAlternateBackgroundForWidget() */ void updateAlternateBackgrounds(); /** * Updates the alternateBackground-property of the widget dependent * on the state of useAlternateBackgrounds() and the grouping state. */ void updateAlternateBackgroundForWidget(KItemListWidget* widget); /** * @return True if alternate backgrounds should be used for the items. * This is the case if an empty item-size is given and if there * is more than one visible role. */ bool useAlternateBackgrounds() const; /** * @param itemRanges Items that must be checked for getting the widths of columns. * @return The preferred width of the column of each visible role. The width will * be respected if the width of the item size is <= 0 (see * KItemListView::setItemSize()). Per default an empty hash * is returned. */ QHash preferredColumnWidths(const KItemRangeList& itemRanges) const; /** * Applies the column-widths from m_headerWidget to the layout * of the view. */ void applyColumnWidthsFromHeader(); /** * Applies the column-widths from m_headerWidget to \a widget. */ void updateWidgetColumnWidths(KItemListWidget* widget); /** * Updates the preferred column-widths of m_groupHeaderWidget by * invoking KItemListView::columnWidths(). */ void updatePreferredColumnWidths(const KItemRangeList& itemRanges); /** * Convenience method for * updatePreferredColumnWidths(KItemRangeList() << KItemRange(0, m_model->count()). */ void updatePreferredColumnWidths(); /** * Resizes the column-widths of m_headerWidget based on the preferred widths * and the vailable view-size. */ void applyAutomaticColumnWidths(); /** * @return Sum of the widths of all columns. */ qreal columnWidthsSum() const; /** * @return Boundaries of the header. An empty rectangle is returned * if no header is shown. */ QRectF headerBoundaries() const; /** * @return True if the number of columns or rows will be changed when applying * the new grid- and item-size. Used to determine whether an animation * should be done when applying the new layout. */ bool changesItemGridLayout(const QSizeF& newGridSize, const QSizeF& newItemSize, const QSizeF& newItemMargin) const; /** * @param changedItemCount Number of inserted or removed items. * @return True if the inserting or removing of items should be animated. * No animation should be done if the number of items is too large * to provide a pleasant animation. */ bool animateChangedItemCount(int changedItemCount) const; /** * @return True if a scrollbar for the given scroll-orientation is required * when using a size of \p size for the view. Calling the method is rather * expansive as a temporary relayout needs to be done. */ bool scrollBarRequired(const QSizeF& size) const; /** * Shows a drop-indicator between items dependent on the given * cursor position. The cursor position is relative to the upper left * edge of the view. * @return Index of the item where the dropping is done. An index of -1 * indicates that the item has been dropped after the last item. */ int showDropIndicator(const QPointF& pos); void hideDropIndicator(); /** * Applies the height of the group header to the layouter. The height * depends on the used scroll orientation. */ void updateGroupHeaderHeight(); /** * Updates the siblings-information for all visible items that are inside * the range of \p firstIndex and \p lastIndex. If firstIndex or lastIndex * is smaller than 0, the siblings-information for all visible items gets * updated. * @see KItemListWidget::setSiblingsInformation() */ void updateSiblingsInformation(int firstIndex = -1, int lastIndex = -1); /** * Helper method for updateExpansionIndicators(). * @return True if the item with the index \a index has a sibling successor * (= the item is not the last item of the current hierarchy). */ bool hasSiblingSuccessor(int index) const; /** * Helper method for slotRoleEditingCanceled() and slotRoleEditingFinished(). * Disconnects the two Signals "roleEditingCanceled" and * "roleEditingFinished" */ void disconnectRoleEditingSignals(int index); /** * Helper function for triggerAutoScrolling(). * @param pos Logical position of the mouse relative to the range. * @param range Range of the visible area. * @param oldInc Previous increment. Is used to assure that the increment * increases only gradually. * @return Scroll increment that should be added to the offset(). * As soon as \a pos is inside the autoscroll-margin a * value != 0 will be returned. */ static int calculateAutoScrollingIncrement(int pos, int range, int oldInc); /** * Helper functions for changesItemCount(). * @return The number of items that fit into the available size by * respecting the size of the item and the margin between the items. */ static int itemsPerSize(qreal size, qreal itemSize, qreal itemMargin); private: bool m_enabledSelectionToggles; bool m_grouped; bool m_supportsItemExpanding; bool m_editingRole; int m_activeTransactions; // Counter for beginTransaction()/endTransaction() LayoutAnimationHint m_endTransactionAnimationHint; QSizeF m_itemSize; KItemListController* m_controller; KItemModelBase* m_model; QList m_visibleRoles; mutable KItemListWidgetCreatorBase* m_widgetCreator; mutable KItemListGroupHeaderCreatorBase* m_groupHeaderCreator; KItemListStyleOption m_styleOption; QHash m_visibleItems; QHash m_visibleGroups; struct Cell { Cell() : column(-1), row(-1) {} Cell(int c, int r) : column(c), row(r) {} int column; int row; }; QHash m_visibleCells; int m_scrollBarExtent; KItemListSizeHintResolver* m_sizeHintResolver; KItemListViewLayouter* m_layouter; KItemListViewAnimation* m_animation; QTimer* m_layoutTimer; // Triggers an asynchronous doLayout() call. qreal m_oldScrollOffset; qreal m_oldMaximumScrollOffset; qreal m_oldItemOffset; qreal m_oldMaximumItemOffset; bool m_skipAutoScrollForRubberBand; KItemListRubberBand* m_rubberBand; QPointF m_mousePos; int m_autoScrollIncrement; QTimer* m_autoScrollTimer; KItemListHeader* m_header; KItemListHeaderWidget* m_headerWidget; // When dragging items into the view where the sort-role of the model // is empty, a visual indicator should be shown during dragging where // the dropping will happen. This indicator is specified by an index // of the item. -1 means that no indicator will be shown at all. // The m_dropIndicator is set by the KItemListController // by KItemListView::showDropIndicator() and KItemListView::hideDropIndicator(). QRectF m_dropIndicator; friend class KItemListContainer; // Accesses scrollBarRequired() friend class KItemListHeader; // Accesses m_headerWidget friend class KItemListController; friend class KItemListControllerTest; friend class KItemListViewAccessible; friend class KItemListAccessibleCell; }; /** * Allows to do a fast logical creation and deletion of QGraphicsWidgets * by recycling existing QGraphicsWidgets instances. Is used by * KItemListWidgetCreatorBase and KItemListGroupHeaderCreatorBase. * @internal */ class DOLPHIN_EXPORT KItemListCreatorBase { public: virtual ~KItemListCreatorBase(); protected: void addCreatedWidget(QGraphicsWidget* widget); void pushRecycleableWidget(QGraphicsWidget* widget); QGraphicsWidget* popRecycleableWidget(); private: QSet m_createdWidgets; QList m_recycleableWidgets; }; /** * @brief Base class for creating KItemListWidgets. * * It is recommended that applications simply use the KItemListWidgetCreator-template class. * For a custom implementation the methods create(), itemSizeHint() and preferredColumnWith() * must be reimplemented. The intention of the widget creator is to prevent repetitive and * expensive instantiations and deletions of KItemListWidgets by recycling existing widget * instances. */ class DOLPHIN_EXPORT KItemListWidgetCreatorBase : public KItemListCreatorBase { public: virtual ~KItemListWidgetCreatorBase(); virtual KItemListWidget* create(KItemListView* view) = 0; virtual void recycle(KItemListWidget* widget); virtual void calculateItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const = 0; virtual qreal preferredRoleColumnWidth(const QByteArray& role, int index, const KItemListView* view) const = 0; }; /** * @brief Template class for creating KItemListWidgets. */ template class KItemListWidgetCreator : public KItemListWidgetCreatorBase { public: KItemListWidgetCreator(); virtual ~KItemListWidgetCreator(); virtual KItemListWidget* create(KItemListView* view); virtual void calculateItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const; virtual qreal preferredRoleColumnWidth(const QByteArray& role, int index, const KItemListView* view) const; private: KItemListWidgetInformant* m_informant; }; template KItemListWidgetCreator::KItemListWidgetCreator() : m_informant(T::createInformant()) { } template KItemListWidgetCreator::~KItemListWidgetCreator() { delete m_informant; } template KItemListWidget* KItemListWidgetCreator::create(KItemListView* view) { KItemListWidget* widget = static_cast(popRecycleableWidget()); if (!widget) { widget = new T(m_informant, view); addCreatedWidget(widget); } return widget; } template void KItemListWidgetCreator::calculateItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint, const KItemListView* view) const { return m_informant->calculateItemSizeHints(logicalHeightHints, logicalWidthHint, view); } template qreal KItemListWidgetCreator::preferredRoleColumnWidth(const QByteArray& role, int index, const KItemListView* view) const { return m_informant->preferredRoleColumnWidth(role, index, view); } /** * @brief Base class for creating KItemListGroupHeaders. * * It is recommended that applications simply use the KItemListGroupHeaderCreator-template class. * For a custom implementation the methods create() and recyle() must be reimplemented. * The intention of the group-header creator is to prevent repetitive and expensive instantiations and * deletions of KItemListGroupHeaders by recycling existing header instances. */ class DOLPHIN_EXPORT KItemListGroupHeaderCreatorBase : public KItemListCreatorBase { public: virtual ~KItemListGroupHeaderCreatorBase(); virtual KItemListGroupHeader* create(KItemListView* view) = 0; virtual void recycle(KItemListGroupHeader* header); }; template class KItemListGroupHeaderCreator : public KItemListGroupHeaderCreatorBase { public: virtual ~KItemListGroupHeaderCreator(); virtual KItemListGroupHeader* create(KItemListView* view); }; template KItemListGroupHeaderCreator::~KItemListGroupHeaderCreator() { } template KItemListGroupHeader* KItemListGroupHeaderCreator::create(KItemListView* view) { KItemListGroupHeader* widget = static_cast(popRecycleableWidget()); if (!widget) { widget = new T(view); addCreatedWidget(widget); } return widget; } #endif diff --git a/src/kitemviews/private/kitemlistsizehintresolver.cpp b/src/kitemviews/private/kitemlistsizehintresolver.cpp index 1d8067026..02f1865b3 100644 --- a/src/kitemviews/private/kitemlistsizehintresolver.cpp +++ b/src/kitemviews/private/kitemlistsizehintresolver.cpp @@ -1,154 +1,167 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz * * * * 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 "kitemlistsizehintresolver.h" #include KItemListSizeHintResolver::KItemListSizeHintResolver(const KItemListView* itemListView) : m_itemListView(itemListView), m_logicalHeightHintCache(), m_logicalWidthHint(0.0), + m_logicalHeightHint(0.0), m_needsResolving(false) { } KItemListSizeHintResolver::~KItemListSizeHintResolver() { } +QSizeF KItemListSizeHintResolver::maxSizeHint() +{ + updateCache(); + return QSizeF(m_logicalWidthHint, m_logicalHeightHint); +} + QSizeF KItemListSizeHintResolver::sizeHint(int index) { updateCache(); return QSizeF(m_logicalWidthHint, m_logicalHeightHintCache.at(index)); } void KItemListSizeHintResolver::itemsInserted(const KItemRangeList& itemRanges) { int insertedCount = 0; foreach (const KItemRange& range, itemRanges) { insertedCount += range.count; } const int currentCount = m_logicalHeightHintCache.count(); m_logicalHeightHintCache.reserve(currentCount + insertedCount); // We build the new list from the end to the beginning to mimize the // number of moves. m_logicalHeightHintCache.insert(m_logicalHeightHintCache.end(), insertedCount, 0.0); int sourceIndex = currentCount - 1; int targetIndex = m_logicalHeightHintCache.count() - 1; int itemsToInsertBeforeCurrentRange = insertedCount; for (int rangeIndex = itemRanges.count() - 1; rangeIndex >= 0; --rangeIndex) { const KItemRange& range = itemRanges.at(rangeIndex); itemsToInsertBeforeCurrentRange -= range.count; // First: move all existing items that must be put behind 'range'. while (targetIndex >= itemsToInsertBeforeCurrentRange + range.index + range.count) { m_logicalHeightHintCache[targetIndex] = m_logicalHeightHintCache[sourceIndex]; --sourceIndex; --targetIndex; } // Then: insert QSizeF() for the items which are inserted into 'range'. while (targetIndex >= itemsToInsertBeforeCurrentRange + range.index) { m_logicalHeightHintCache[targetIndex] = 0.0; --targetIndex; } } m_needsResolving = true; Q_ASSERT(m_logicalHeightHintCache.count() == m_itemListView->model()->count()); } void KItemListSizeHintResolver::itemsRemoved(const KItemRangeList& itemRanges) { const QVector::iterator begin = m_logicalHeightHintCache.begin(); const QVector::iterator end = m_logicalHeightHintCache.end(); KItemRangeList::const_iterator rangeIt = itemRanges.constBegin(); const KItemRangeList::const_iterator rangeEnd = itemRanges.constEnd(); QVector::iterator destIt = begin + rangeIt->index; QVector::iterator srcIt = destIt + rangeIt->count; ++rangeIt; while (srcIt != end) { *destIt = *srcIt; ++destIt; ++srcIt; if (rangeIt != rangeEnd && srcIt == begin + rangeIt->index) { // Skip the items in the next removed range. srcIt += rangeIt->count; ++rangeIt; } } m_logicalHeightHintCache.erase(destIt, end); // Note that the cache size might temporarily not match the model size if // this function is called from KItemListView::setModel() to empty the cache. if (!m_logicalHeightHintCache.isEmpty() && m_itemListView->model()) { Q_ASSERT(m_logicalHeightHintCache.count() == m_itemListView->model()->count()); } } void KItemListSizeHintResolver::itemsMoved(const KItemRange& range, const QList& movedToIndexes) { QVector newLogicalHeightHintCache(m_logicalHeightHintCache); const int movedRangeEnd = range.index + range.count; for (int i = range.index; i < movedRangeEnd; ++i) { const int newIndex = movedToIndexes.at(i - range.index); newLogicalHeightHintCache[newIndex] = m_logicalHeightHintCache.at(i); } m_logicalHeightHintCache = newLogicalHeightHintCache; } void KItemListSizeHintResolver::itemsChanged(int index, int count, const QSet& roles) { Q_UNUSED(roles); while (count) { m_logicalHeightHintCache[index] = 0.0; ++index; --count; } m_needsResolving = true; } void KItemListSizeHintResolver::clearCache() { m_logicalHeightHintCache.fill(0.0); m_needsResolving = true; } void KItemListSizeHintResolver::updateCache() { if (m_needsResolving) { m_itemListView->calculateItemSizeHints(m_logicalHeightHintCache, m_logicalWidthHint); + // Set logical height as the max cached height (if the cache is not empty). + if (m_logicalHeightHintCache.isEmpty()) { + m_logicalHeightHint = 0.0; + } else { + m_logicalHeightHint = *std::max_element(m_logicalHeightHintCache.begin(), m_logicalHeightHintCache.end()); + } m_needsResolving = false; } } diff --git a/src/kitemviews/private/kitemlistsizehintresolver.h b/src/kitemviews/private/kitemlistsizehintresolver.h index ff17f2de2..841e9ca10 100644 --- a/src/kitemviews/private/kitemlistsizehintresolver.h +++ b/src/kitemviews/private/kitemlistsizehintresolver.h @@ -1,56 +1,58 @@ /*************************************************************************** * Copyright (C) 2011 by Peter Penz * * * * 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 * ***************************************************************************/ #ifndef KITEMLISTSIZEHINTRESOLVER_H #define KITEMLISTSIZEHINTRESOLVER_H #include "dolphin_export.h" #include #include #include class KItemListView; /** * @brief Calculates and caches the sizehints of items in KItemListView. */ class DOLPHIN_EXPORT KItemListSizeHintResolver { public: KItemListSizeHintResolver(const KItemListView* itemListView); virtual ~KItemListSizeHintResolver(); + QSizeF maxSizeHint(); QSizeF sizeHint(int index); void itemsInserted(const KItemRangeList& itemRanges); void itemsRemoved(const KItemRangeList& itemRanges); void itemsMoved(const KItemRange& range, const QList& movedToIndexes); void itemsChanged(int index, int count, const QSet& roles); void clearCache(); void updateCache(); private: const KItemListView* m_itemListView; mutable QVector m_logicalHeightHintCache; mutable qreal m_logicalWidthHint; + mutable qreal m_logicalHeightHint; bool m_needsResolving; }; #endif