X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/a4ede4e0ebaf0bba57da8fc2924577f1a8ff7b54..38c34eeca:/src/kitemviews/kitemlistview.cpp diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index ddc65c387..d9455ce9e 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -1,201 +1,156 @@ -/*************************************************************************** - * 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 * - ***************************************************************************/ +/* + * SPDX-FileCopyrightText: 2011 Peter Penz + * + * Based on the Itemviews NG project from Trolltech Labs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "kitemlistview.h" +#include "dolphindebug.h" +#include "kitemlistcontainer.h" #include "kitemlistcontroller.h" #include "kitemlistheader.h" -#include "kitemlistheaderwidget_p.h" -#include "kitemlistrubberband_p.h" #include "kitemlistselectionmanager.h" -#include "kitemlistsizehintresolver_p.h" -#include "kitemlistviewlayouter_p.h" -#include "kitemlistviewanimation_p.h" -#include "kitemlistwidget.h" +#include "kitemlistviewaccessible.h" +#include "kstandarditemlistwidget.h" -#include +#include "private/kitemlistheaderwidget.h" +#include "private/kitemlistrubberband.h" +#include "private/kitemlistsizehintresolver.h" +#include "private/kitemlistviewlayouter.h" -#include +#include #include -#include +#include #include -#include #include #include +#include -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; -} - -KItemListView::KItemListView(QGraphicsWidget* parent) : - QGraphicsWidget(parent), - m_enabledSelectionToggles(false), - m_grouped(false), - m_supportsItemExpanding(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) +namespace { - setAcceptHoverEvents(true); - - m_sizeHintResolver = new KItemListSizeHintResolver(this); +// Time in ms until reaching the autoscroll margin triggers +// an initial autoscrolling +const int InitialAutoScrollDelay = 700; - m_layouter = new KItemListViewLayouter(this); - m_layouter->setSizeHintResolver(m_sizeHintResolver); +// Delay in ms for triggering the next autoscroll +const int RepeatingAutoScrollDelay = 1000 / 60; - m_animation = new KItemListViewAnimation(this); - connect(m_animation, SIGNAL(finished(QGraphicsWidget*,KItemListViewAnimation::AnimationType)), - this, SLOT(slotAnimationFinished(QGraphicsWidget*,KItemListViewAnimation::AnimationType))); - - m_layoutTimer = new QTimer(this); - m_layoutTimer->setInterval(300); - m_layoutTimer->setSingleShot(true); - connect(m_layoutTimer, SIGNAL(timeout()), this, SLOT(slotLayoutTimerFinished())); - - m_rubberBand = new KItemListRubberBand(this); - connect(m_rubberBand, SIGNAL(activationChanged(bool)), this, SLOT(slotRubberBandActivationChanged(bool))); +// Copied from the Kirigami.Units.shortDuration +const int RubberFadeSpeed = 150; - m_headerWidget = new KItemListHeaderWidget(this); - m_headerWidget->setVisible(false); - - m_header = new KItemListHeader(this); +const char *RubberPropertyName = "_kitemviews_rubberBandPosition"; } -KItemListView::~KItemListView() +#ifndef QT_NO_ACCESSIBILITY +QAccessibleInterface *accessibleInterfaceFactory(const QString &key, QObject *object) { - delete m_sizeHintResolver; - m_sizeHintResolver = 0; -} + Q_UNUSED(key) -void KItemListView::setScrollOrientation(Qt::Orientation orientation) -{ - const Qt::Orientation previousOrientation = m_layouter->scrollOrientation(); - if (orientation == previousOrientation) { - return; + if (KItemListContainer *container = qobject_cast(object)) { + return new KItemListContainerAccessible(container); + } else if (KItemListView *view = qobject_cast(object)) { + return new KItemListViewAccessible(view); } - 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); + return nullptr; } +#endif -Qt::Orientation KItemListView::scrollOrientation() const +KItemListView::KItemListView(QGraphicsWidget *parent) + : QGraphicsWidget(parent) + , m_enabledSelectionToggles(false) + , m_grouped(false) + , m_highlightEntireRow(false) + , m_alternateBackgrounds(false) + , m_supportsItemExpanding(false) + , m_editingRole(false) + , m_activeTransactions(0) + , m_endTransactionAnimationHint(Animation) + , m_itemSize() + , m_controller(nullptr) + , m_model(nullptr) + , m_visibleRoles() + , m_widgetCreator(nullptr) + , m_groupHeaderCreator(nullptr) + , m_styleOption() + , m_visibleItems() + , m_visibleGroups() + , m_visibleCells() + , m_scrollBarExtent(0) + , m_layouter(nullptr) + , m_animation(nullptr) + , m_oldScrollOffset(0) + , m_oldMaximumScrollOffset(0) + , m_oldItemOffset(0) + , m_oldMaximumItemOffset(0) + , m_skipAutoScrollForRubberBand(false) + , m_rubberBand(nullptr) + , m_tapAndHoldIndicator(nullptr) + , m_mousePos() + , m_autoScrollIncrement(0) + , m_autoScrollTimer(nullptr) + , m_header(nullptr) + , m_headerWidget(nullptr) + , m_indicatorAnimation(nullptr) + , m_dropIndicator() + , m_sizeHintResolver(nullptr) { - return m_layouter->scrollOrientation(); -} + setAcceptHoverEvents(true); + setAcceptTouchEvents(true); -void KItemListView::setItemSize(const QSizeF& itemSize) -{ - const QSizeF previousSize = m_itemSize; - if (itemSize == previousSize) { - return; - } + m_sizeHintResolver = new KItemListSizeHintResolver(this); - // 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(), - itemSize, - m_layouter->itemMargin()); + m_layouter = new KItemListViewLayouter(m_sizeHintResolver, this); - const bool alternateBackgroundsChanged = (m_visibleRoles.count() > 1) && - (( m_itemSize.isEmpty() && !itemSize.isEmpty()) || - (!m_itemSize.isEmpty() && itemSize.isEmpty())); + m_animation = new KItemListViewAnimation(this); + connect(m_animation, &KItemListViewAnimation::finished, this, &KItemListView::slotAnimationFinished); - m_itemSize = itemSize; + m_rubberBand = new KItemListRubberBand(this); + connect(m_rubberBand, &KItemListRubberBand::activationChanged, this, &KItemListView::slotRubberBandActivationChanged); + + m_tapAndHoldIndicator = new KItemListRubberBand(this); + m_indicatorAnimation = new QPropertyAnimation(m_tapAndHoldIndicator, "endPosition", this); + connect(m_tapAndHoldIndicator, &KItemListRubberBand::activationChanged, this, [this](bool active) { + if (active) { + m_indicatorAnimation->setDuration(150); + m_indicatorAnimation->setStartValue(QPointF(1, 1)); + m_indicatorAnimation->setEndValue(QPointF(40, 40)); + m_indicatorAnimation->start(); + } + update(); + }); + connect(m_tapAndHoldIndicator, &KItemListRubberBand::endPositionChanged, this, [this]() { + if (m_tapAndHoldIndicator->isActive()) { + update(); + } + }); - 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(); - } + m_headerWidget = new KItemListHeaderWidget(this); + m_headerWidget->setVisible(false); - if (itemSize.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, itemSize.height()); - m_layouter->setItemSize(newSize); - } - } else { - m_layouter->setItemSize(itemSize); - } + m_header = new KItemListHeader(this); - m_sizeHintResolver->clearCache(); - doLayout(animate ? Animation : NoAnimation); - onItemSizeChanged(itemSize, previousSize); +#ifndef QT_NO_ACCESSIBILITY + QAccessible::installFactory(accessibleInterfaceFactory); +#endif } -QSizeF KItemListView::itemSize() const +KItemListView::~KItemListView() { - return m_itemSize; + // 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 = nullptr; + + delete m_widgetCreator; + m_widgetCreator = nullptr; + + delete m_sizeHintResolver; + m_sizeHintResolver = nullptr; } void KItemListView::setScrollOffset(qreal offset) @@ -256,7 +211,12 @@ qreal KItemListView::maximumItemOffset() const return m_layouter->maximumItemOffset(); } -void KItemListView::setVisibleRoles(const QList& roles) +int KItemListView::maximumVisibleItems() const +{ + return m_layouter->maximumVisibleItems(); +} + +void KItemListView::setVisibleRoles(const QList &roles) { const QList previousRoles = m_visibleRoles; m_visibleRoles = roles; @@ -271,7 +231,7 @@ void KItemListView::setVisibleRoles(const QList& roles) 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) { + for (const QByteArray &role : qAsConst(m_visibleRoles)) { if (m_headerWidget->columnWidth(role) == 0) { const qreal width = m_headerWidget->preferredColumnWidth(role); m_headerWidget->setColumnWidth(role, width); @@ -282,14 +242,13 @@ void KItemListView::setVisibleRoles(const QList& roles) } } - const bool alternateBackgroundsChanged = m_itemSize.isEmpty() && - ((roles.count() > 1 && previousRoles.count() <= 1) || - (roles.count() <= 1 && previousRoles.count() > 1)); + const bool alternateBackgroundsChanged = + m_itemSize.isEmpty() && ((roles.count() > 1 && previousRoles.count() <= 1) || (roles.count() <= 1 && previousRoles.count() > 1)); - QHashIterator it(m_visibleItems); + QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); - KItemListWidget* widget = it.value(); + KItemListWidget *widget = it.value(); widget->setVisibleRoles(roles); if (alternateBackgroundsChanged) { updateAlternateBackgroundForWidget(widget); @@ -309,17 +268,17 @@ void KItemListView::setAutoScroll(bool enabled) if (enabled && !m_autoScrollTimer) { m_autoScrollTimer = new QTimer(this); m_autoScrollTimer->setSingleShot(true); - connect(m_autoScrollTimer, SIGNAL(timeout()), this, SLOT(triggerAutoScrolling())); + connect(m_autoScrollTimer, &QTimer::timeout, this, &KItemListView::triggerAutoScrolling); m_autoScrollTimer->start(InitialAutoScrollDelay); } else if (!enabled && m_autoScrollTimer) { delete m_autoScrollTimer; - m_autoScrollTimer = 0; + m_autoScrollTimer = nullptr; } } bool KItemListView::autoScroll() const { - return m_autoScrollTimer != 0; + return m_autoScrollTimer != nullptr; } void KItemListView::setEnabledSelectionToggles(bool enabled) @@ -327,7 +286,7 @@ void KItemListView::setEnabledSelectionToggles(bool enabled) if (m_enabledSelectionToggles != enabled) { m_enabledSelectionToggles = enabled; - QHashIterator it(m_visibleItems); + QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); it.value()->setEnabledSelectionToggle(enabled); @@ -340,75 +299,55 @@ bool KItemListView::enabledSelectionToggles() const return m_enabledSelectionToggles; } -KItemListController* KItemListView::controller() const +KItemListController *KItemListView::controller() const { return m_controller; } -KItemModelBase* KItemListView::model() const +KItemModelBase *KItemListView::model() const { return m_model; } -void KItemListView::setWidgetCreator(KItemListWidgetCreatorBase* widgetCreator) +void KItemListView::setWidgetCreator(KItemListWidgetCreatorBase *widgetCreator) { + delete m_widgetCreator; m_widgetCreator = widgetCreator; } -KItemListWidgetCreatorBase* KItemListView::widgetCreator() const +KItemListWidgetCreatorBase *KItemListView::widgetCreator() const { + if (!m_widgetCreator) { + m_widgetCreator = defaultWidgetCreator(); + } return m_widgetCreator; } -void KItemListView::setGroupHeaderCreator(KItemListGroupHeaderCreatorBase* groupHeaderCreator) +void KItemListView::setGroupHeaderCreator(KItemListGroupHeaderCreatorBase *groupHeaderCreator) { + delete m_groupHeaderCreator; m_groupHeaderCreator = groupHeaderCreator; } -KItemListGroupHeaderCreatorBase* KItemListView::groupHeaderCreator() const +KItemListGroupHeaderCreatorBase *KItemListView::groupHeaderCreator() const { + if (!m_groupHeaderCreator) { + m_groupHeaderCreator = defaultGroupHeaderCreator(); + } return m_groupHeaderCreator; } -void KItemListView::setStyleOption(const KItemListStyleOption& option) +QSizeF KItemListView::itemSize() const { - 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(); - } - - QHashIterator it(m_visibleItems); - while (it.hasNext()) { - it.next(); - it.value()->setStyleOption(option); - } - - m_sizeHintResolver->clearCache(); - doLayout(animate ? Animation : NoAnimation); - - onStyleOptionChanged(option, previousOption); + return m_itemSize; } -const KItemListStyleOption& KItemListView::styleOption() const +const KItemListStyleOption &KItemListView::styleOption() const { return m_styleOption; } -void KItemListView::setGeometry(const QRectF& rect) +void KItemListView::setGeometry(const QRectF &rect) { QGraphicsWidget::setGeometry(rect); @@ -423,58 +362,49 @@ void KItemListView::setGeometry(const QRectF& rect) applyAutomaticColumnWidths(); } else { const qreal requiredWidth = columnWidthsSum(); - const QSizeF dynamicItemSize(qMax(newSize.width(), requiredWidth), - m_itemSize.height()); + 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); + m_layouter->setSize(newSize); + // We don't animate the moving of the items here because + // it would look like the items are slow to find their position. + doLayout(NoAnimation); +} - 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 +std::optional KItemListView::itemAt(const QPointF &pos) const { - QHashIterator it(m_visibleItems); + QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); - const KItemListWidget* widget = it.value(); + const KItemListWidget *widget = it.value(); const QPointF mappedPos = widget->mapFromItem(this, pos); - if (widget->contains(mappedPos)) { + if (widget->contains(mappedPos) || widget->selectionRect().contains(mappedPos)) { return it.key(); } } - return -1; + return std::nullopt; } -bool KItemListView::isAboveSelectionToggle(int index, const QPointF& pos) const +bool KItemListView::isAboveSelectionToggle(int index, const QPointF &pos) const { if (!m_enabledSelectionToggles) { return false; } - const KItemListWidget* widget = m_visibleItems.value(index); + const KItemListWidget *widget = m_visibleItems.value(index); if (widget) { const QRectF selectionToggleRect = widget->selectionToggleRect(); if (!selectionToggleRect.isEmpty()) { @@ -485,9 +415,9 @@ bool KItemListView::isAboveSelectionToggle(int index, const QPointF& pos) const return false; } -bool KItemListView::isAboveExpansionToggle(int index, const QPointF& pos) const +bool KItemListView::isAboveExpansionToggle(int index, const QPointF &pos) const { - const KItemListWidget* widget = m_visibleItems.value(index); + const KItemListWidget *widget = m_visibleItems.value(index); if (widget) { const QRectF expansionToggleRect = widget->expansionToggleRect(); if (!expansionToggleRect.isEmpty()) { @@ -498,6 +428,19 @@ bool KItemListView::isAboveExpansionToggle(int index, const QPointF& pos) const return false; } +bool KItemListView::isAboveText(int index, const QPointF &pos) const +{ + const KItemListWidget *widget = m_visibleItems.value(index); + if (widget) { + const QRectF &textRect = widget->textRect(); + if (!textRect.isEmpty()) { + const QPointF mappedPos = widget->mapFromItem(this, pos); + return textRect.contains(mappedPos); + } + } + return false; +} + int KItemListView::firstVisibleIndex() const { return m_layouter->firstVisibleIndex(); @@ -508,9 +451,9 @@ int KItemListView::lastVisibleIndex() const return m_layouter->lastVisibleIndex(); } -QSizeF KItemListView::itemSizeHint(int index) const +void KItemListView::calculateItemSizeHints(QVector> &logicalHeightHints, qreal &logicalWidthHint) const { - return m_widgetCreator->itemSizeHint(index, this); + widgetCreator()->calculateItemSizeHints(logicalHeightHints, logicalWidthHint, this); } void KItemListView::setSupportsItemExpanding(bool supportsExpanding) @@ -527,6 +470,32 @@ bool KItemListView::supportsItemExpanding() const return m_supportsItemExpanding; } +void KItemListView::setHighlightEntireRow(bool highlightEntireRow) +{ + if (m_highlightEntireRow != highlightEntireRow) { + m_highlightEntireRow = highlightEntireRow; + onHighlightEntireRowChanged(highlightEntireRow); + } +} + +bool KItemListView::highlightEntireRow() const +{ + return m_highlightEntireRow; +} + +void KItemListView::setAlternateBackgrounds(bool alternate) +{ + if (m_alternateBackgrounds != alternate) { + m_alternateBackgrounds = alternate; + updateAlternateBackgrounds(); + } +} + +bool KItemListView::alternateBackgrounds() const +{ + return m_alternateBackgrounds; +} + QRectF KItemListView::itemRect(int index) const { return m_layouter->itemRect(index); @@ -536,7 +505,7 @@ QRectF KItemListView::itemContextRect(int index) const { QRectF contextRect; - const KItemListWidget* widget = m_visibleItems.value(index); + const KItemListWidget *widget = m_visibleItems.value(index); if (widget) { contextRect = widget->iconRect() | widget->textRect(); contextRect.translate(itemRect(index).topLeft()); @@ -545,6 +514,11 @@ QRectF KItemListView::itemContextRect(int index) const return contextRect; } +bool KItemListView::isElided(int index) const +{ + return m_sizeHintResolver->isElided(index); +} + void KItemListView::scrollToItem(int index) { QRectF viewGeometry = geometry(); @@ -552,7 +526,10 @@ void KItemListView::scrollToItem(int index) const qreal headerHeight = m_headerWidget->size().height(); viewGeometry.adjust(0, headerHeight, 0, 0); } - const QRectF currentRect = itemRect(index); + 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(); @@ -571,9 +548,12 @@ void KItemListView::scrollToItem(int index) } if (newOffset != scrollOffset()) { - emit scrollTo(newOffset); + Q_EMIT scrollTo(newOffset); + return; } } + + Q_EMIT scrollingStopped(); } void KItemListView::beginTransaction() @@ -589,7 +569,7 @@ void KItemListView::endTransaction() --m_activeTransactions; if (m_activeTransactions < 0) { m_activeTransactions = 0; - kWarning() << "Mismatch between beginTransaction()/endTransaction()"; + qCWarning(DolphinDebug) << "Mismatch between beginTransaction()/endTransaction()"; } if (m_activeTransactions == 0) { @@ -604,42 +584,32 @@ bool KItemListView::isTransactionActive() const return m_activeTransactions > 0; } -// TODO: -#include -#include void KItemListView::setHeaderVisible(bool visible) { if (visible && !m_headerWidget->isVisible()) { QStyleOptionHeader option; - const QSize headerSize = style()->sizeFromContents(QStyle::CT_HeaderSection, - &option, QSize()); + const QSize headerSize = style()->sizeFromContents(QStyle::CT_HeaderSection, &option, QSize()); - m_headerWidget->setPos(0, 0); + 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, SIGNAL(columnWidthChanged(QByteArray,qreal,qreal)), - this, SLOT(slotHeaderColumnWidthChanged(QByteArray,qreal,qreal))); - connect(m_headerWidget, SIGNAL(columnMoved(QByteArray,int,int)), - this, SLOT(slotHeaderColumnMoved(QByteArray,int,int))); - connect(m_headerWidget, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), - this, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder))); - connect(m_headerWidget, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), - this, SIGNAL(sortRoleChanged(QByteArray,QByteArray))); + connect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged, this, &KItemListView::slotHeaderColumnWidthChanged); + connect(m_headerWidget, &KItemListHeaderWidget::sidePaddingChanged, this, &KItemListView::slotSidePaddingChanged); + 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, SIGNAL(columnWidthChanged(QByteArray,qreal,qreal)), - this, SLOT(slotHeaderColumnWidthChanged(QByteArray,qreal,qreal))); - disconnect(m_headerWidget, SIGNAL(columnMoved(QByteArray,int,int)), - this, SLOT(slotHeaderColumnMoved(QByteArray,int,int))); - disconnect(m_headerWidget, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), - this, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder))); - disconnect(m_headerWidget, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), - this, SIGNAL(sortRoleChanged(QByteArray,QByteArray))); + disconnect(m_headerWidget, &KItemListHeaderWidget::columnWidthChanged, this, &KItemListView::slotHeaderColumnWidthChanged); + disconnect(m_headerWidget, &KItemListHeaderWidget::sidePaddingChanged, this, &KItemListView::slotSidePaddingChanged); + 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); @@ -651,24 +621,76 @@ bool KItemListView::isHeaderVisible() const return m_headerWidget->isVisible(); } -KItemListHeader* KItemListView::header() const +KItemListHeader *KItemListView::header() const { return m_header; } -QPixmap KItemListView::createDragPixmap(const QSet& indexes) const +QPixmap KItemListView::createDragPixmap(const KItemSet &indexes) const { - Q_UNUSED(indexes); - return QPixmap(); + QPixmap pixmap; + + if (indexes.count() == 1) { + KItemListWidget *item = m_visibleItems.value(indexes.first()); + QGraphicsView *graphicsView = scene()->views()[0]; + if (item && graphicsView) { + pixmap = item->createDragPixmap(nullptr, 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::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +void KItemListView::editRole(int index, const QByteArray &role) +{ + KStandardItemListWidget *widget = qobject_cast(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); + + connect(this, &KItemListView::scrollOffsetChanged, widget, &KStandardItemListWidget::finishRoleEditing); +} + +void KItemListView::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { QGraphicsWidget::paint(painter, option, widget); + for (auto animation : qAsConst(m_rubberBandAnimations)) { + QRectF rubberBandRect = animation->property(RubberPropertyName).toRectF(); + + 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; + initStyleOption(&opt); + opt.shape = QRubberBand::Rectangle; + opt.opaque = false; + opt.rect = rubberBandRect.toRect(); + + painter->save(); + + painter->setOpacity(animation->currentValue().toReal()); + style()->drawControl(QStyle::CE_RubberBand, &opt, painter); + + painter->restore(); + } + if (m_rubberBand->isActive()) { - QRectF rubberBandRect = QRectF(m_rubberBand->startPosition(), - m_rubberBand->endPosition()).normalized(); + QRectF rubberBandRect = QRectF(m_rubberBand->startPosition(), m_rubberBand->endPosition()).normalized(); const QPointF topLeft = rubberBandRect.topLeft(); if (scrollOrientation() == Qt::Vertical) { @@ -678,70 +700,242 @@ void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt } QStyleOptionRubberBand opt; - opt.initFrom(widget); + initStyleOption(&opt); opt.shape = QRubberBand::Rectangle; opt.opaque = false; opt.rect = rubberBandRect.toRect(); style()->drawControl(QStyle::CE_RubberBand, &opt, painter); } + + if (m_tapAndHoldIndicator->isActive()) { + const QPointF indicatorSize = m_tapAndHoldIndicator->endPosition(); + const QRectF rubberBandRect = + QRectF(m_tapAndHoldIndicator->startPosition() - indicatorSize, (m_tapAndHoldIndicator->startPosition()) + indicatorSize).normalized(); + QStyleOptionRubberBand opt; + initStyleOption(&opt); + 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::Text).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_alternateBackgrounds && ((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::initializeItemListWidget(KItemListWidget* item) +void KItemListView::setStyleOption(const KItemListStyleOption &option) { - Q_UNUSED(item); + if (m_styleOption == option) { + return; + } + + 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); } -bool KItemListView::itemSizeHintUpdateRequired(const QSet& changedRoles) const +void KItemListView::setScrollOrientation(Qt::Orientation orientation) { - Q_UNUSED(changedRoles); + 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); + Q_EMIT scrollOrientationChanged(orientation, previousOrientation); +} + +Qt::Orientation KItemListView::scrollOrientation() const +{ + return m_layouter->scrollOrientation(); +} + +KItemListWidgetCreatorBase *KItemListView::defaultWidgetCreator() const +{ + return nullptr; +} + +KItemListGroupHeaderCreatorBase *KItemListView::defaultGroupHeaderCreator() const +{ + return nullptr; +} + +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) +void KItemListView::onControllerChanged(KItemListController *current, KItemListController *previous) { - Q_UNUSED(current); - Q_UNUSED(previous); + Q_UNUSED(current) + Q_UNUSED(previous) } -void KItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous) +void KItemListView::onModelChanged(KItemModelBase *current, KItemModelBase *previous) { - Q_UNUSED(current); - Q_UNUSED(previous); + Q_UNUSED(current) + Q_UNUSED(previous) } void KItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous) { - Q_UNUSED(current); - Q_UNUSED(previous); + Q_UNUSED(current) + Q_UNUSED(previous) } -void KItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous) +void KItemListView::onItemSizeChanged(const QSizeF ¤t, const QSizeF &previous) { - Q_UNUSED(current); - Q_UNUSED(previous); + Q_UNUSED(current) + Q_UNUSED(previous) } void KItemListView::onScrollOffsetChanged(qreal current, qreal previous) { - Q_UNUSED(current); - Q_UNUSED(previous); + Q_UNUSED(current) + Q_UNUSED(previous) } -void KItemListView::onVisibleRolesChanged(const QList& current, const QList& previous) +void KItemListView::onVisibleRolesChanged(const QList ¤t, const QList &previous) { - Q_UNUSED(current); - Q_UNUSED(previous); + Q_UNUSED(current) + Q_UNUSED(previous) } -void KItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous) +void KItemListView::onStyleOptionChanged(const KItemListStyleOption ¤t, const KItemListStyleOption &previous) { - Q_UNUSED(current); - Q_UNUSED(previous); + Q_UNUSED(current) + Q_UNUSED(previous) +} + +void KItemListView::onHighlightEntireRowChanged(bool highlightEntireRow) +{ + Q_UNUSED(highlightEntireRow) } void KItemListView::onSupportsItemExpandingChanged(bool supportsExpanding) { - Q_UNUSED(supportsExpanding); + Q_UNUSED(supportsExpanding) } void KItemListView::onTransactionBegin() @@ -752,23 +946,35 @@ void KItemListView::onTransactionEnd() { } -bool KItemListView::event(QEvent* event) +bool KItemListView::event(QEvent *event) { - // Forward all events to the controller and handle them there - if (m_controller && m_controller->processEvent(event, transform())) { - event->accept(); - return true; + 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) +void KItemListView::mousePressEvent(QGraphicsSceneMouseEvent *event) { m_mousePos = transform().map(event->pos()); event->accept(); } -void KItemListView::mouseMoveEvent(QGraphicsSceneMouseEvent* event) +void KItemListView::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { QGraphicsWidget::mouseMoveEvent(event); @@ -778,7 +984,7 @@ void KItemListView::mouseMoveEvent(QGraphicsSceneMouseEvent* event) } } -void KItemListView::dragEnterEvent(QGraphicsSceneDragDropEvent* event) +void KItemListView::dragEnterEvent(QGraphicsSceneDragDropEvent *event) { event->setAccepted(true); setAutoScroll(true); @@ -800,18 +1006,39 @@ void KItemListView::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) setAutoScroll(false); } -void KItemListView::dropEvent(QGraphicsSceneDragDropEvent* event) +void KItemListView::dropEvent(QGraphicsSceneDragDropEvent *event) { QGraphicsWidget::dropEvent(event); setAutoScroll(false); } -QList KItemListView::visibleItemListWidgets() const +QList KItemListView::visibleItemListWidgets() const { return m_visibleItems.values(); } -void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) +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); @@ -824,24 +1051,24 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) m_layouter->markAsDirty(); + m_sizeHintResolver->itemsInserted(itemRanges); + int previouslyInsertedCount = 0; - foreach (const KItemRange& range, itemRanges) { + for (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) { - kWarning() << "Invalid item range (index:" << index << ", count:" << count << ")"; + qCWarning(DolphinDebug) << "Invalid item range (index:" << index << ", count:" << count << ")"; continue; } previouslyInsertedCount += count; - m_sizeHintResolver->itemsInserted(index, count); - // Determine which visible items must be moved QList itemsToMove; - QHashIterator it(m_visibleItems); + QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); const int visibleItemIndex = it.key(); @@ -853,9 +1080,9 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) // 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); + std::sort(itemsToMove.begin(), itemsToMove.end()); for (int i = itemsToMove.count() - 1; i >= 0; --i) { - KItemListWidget* widget = m_visibleItems.value(itemsToMove[i]); + KItemListWidget *widget = m_visibleItems.value(itemsToMove[i]); Q_ASSERT(widget); const int newIndex = widget->index() + count; if (hasMultipleRanges) { @@ -871,15 +1098,21 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) // 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()); + 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; + layouterSize.rwidth() -= scrollBarExtent + scrollbarSpacing; } else { - layouterSize.rheight() -= scrollBarExtent; + layouterSize.rheight() -= scrollBarExtent + scrollbarSpacing; } m_layouter->setSize(layouterSize); } @@ -896,23 +1129,25 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) } if (hasMultipleRanges) { -#ifndef QT_NO_DEBUG - // Important: Don't read any m_layouter-property inside the for-loop in case if - // multiple ranges are given! m_layouter accesses m_sizeHintResolver which is - // updated in each loop-cycle and has only a consistent state after the loop. - Q_ASSERT(m_layouter->isDirty()); -#endif 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) +void KItemListView::slotItemsRemoved(const KItemRangeList &itemRanges) { if (m_itemSize.isEmpty()) { // Don't pass the item-range: The preferred column-widths of @@ -927,25 +1162,33 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) m_layouter->markAsDirty(); + m_sizeHintResolver->itemsRemoved(itemRanges); + for (int i = itemRanges.count() - 1; i >= 0; --i) { - const KItemRange& range = itemRanges.at(i); + const KItemRange &range = itemRanges[i]; const int index = range.index; const int count = range.count; if (index < 0 || count <= 0) { - kWarning() << "Invalid item range (index:" << index << ", count:" << count << ")"; + qCWarning(DolphinDebug) << "Invalid item range (index:" << index << ", count:" << count << ")"; continue; } - m_sizeHintResolver->itemsRemoved(index, count); - const int firstRemovedIndex = index; const int lastRemovedIndex = index + count - 1; - const int lastIndex = m_model->count() + 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 - for (int i = firstRemovedIndex; i <= lastRemovedIndex; ++i) { - KItemListWidget* widget = m_visibleItems.value(i); - if (!widget) { + // Iterate over a const copy because the container is mutated within the loop + // directly and in `recycleWidget()` (https://bugs.kde.org/show_bug.cgi?id=428374) + const auto visibleItems = m_visibleItems; + for (KItemListWidget *widget : visibleItems) { + const int i = widget->index(); + if (i < firstRemovedIndex) { + continue; + } else if (i > lastRemovedIndex) { + itemsToMove.append(i); continue; } @@ -973,17 +1216,18 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) } // Update the indexes of all KItemListWidget instances that are located - // after the deleted items - for (int i = lastRemovedIndex + 1; i <= lastIndex; ++i) { - KItemListWidget* widget = m_visibleItems.value(i); - if (widget) { - const int newIndex = i - count; - if (hasMultipleRanges) { - setWidgetIndex(widget, newIndex); - } else { - // Try to animate the moving of the item - moveWidgetToIndex(widget, newIndex); - } + // 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()); + for (int i : qAsConst(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); } } @@ -1005,25 +1249,25 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) } if (hasMultipleRanges) { -#ifndef QT_NO_DEBUG - // Important: Don't read any m_layouter-property inside the for-loop in case if - // multiple ranges are given! m_layouter accesses m_sizeHintResolver which is - // updated in each loop-cycle and has only a consistent state after the loop. - Q_ASSERT(m_layouter->isDirty()); -#endif 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) +void KItemListView::slotItemsMoved(const KItemRange &itemRange, const QList &movedToIndexes) { - m_sizeHintResolver->itemsMoved(itemRange.index, itemRange.count); + m_sizeHintResolver->itemsMoved(itemRange, movedToIndexes); m_layouter->markAsDirty(); if (m_controller) { @@ -1034,7 +1278,7 @@ void KItemListView::slotItemsMoved(const KItemRange& itemRange, const QList const int lastVisibleMovedIndex = qMin(lastVisibleIndex(), itemRange.index + itemRange.count - 1); for (int index = firstVisibleMovedIndex; index <= lastVisibleMovedIndex; ++index) { - KItemListWidget* widget = m_visibleItems.value(index); + KItemListWidget *widget = m_visibleItems.value(index); if (widget) { updateWidgetProperties(widget, index); initializeItemListWidget(widget); @@ -1045,31 +1289,26 @@ void KItemListView::slotItemsMoved(const KItemRange& itemRange, const QList updateSiblingsInformation(); } -void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, - const QSet& roles) +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) { + for (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); + KItemListWidget *widget = m_visibleItems.value(i); if (widget) { widget->setData(m_model->data(i), roles); } @@ -1081,7 +1320,21 @@ void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, updateVisibleGroupHeaders(); doLayout(NoAnimation); } + + QAccessibleTableModelChangeEvent ev(this, QAccessibleTableModelChangeEvent::DataChanged); + ev.setFirstRow(itemRange.index); + ev.setLastRow(itemRange.index + itemRange.count); + QAccessible::updateAccessibility(&ev); } + + doLayout(NoAnimation); +} + +void KItemListView::slotGroupsChanged() +{ + updateVisibleGroupHeaders(); + doLayout(NoAnimation); + updateSiblingsInformation(); } void KItemListView::slotGroupedSortingChanged(bool current) @@ -1092,8 +1345,10 @@ void KItemListView::slotGroupedSortingChanged(bool current) if (m_grouped) { updateGroupHeaderHeight(); } else { - // Clear all visible headers - QMutableHashIterator it (m_visibleGroups); + // 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()); @@ -1113,18 +1368,18 @@ void KItemListView::slotGroupedSortingChanged(bool current) void KItemListView::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder previous) { - Q_UNUSED(current); - Q_UNUSED(previous); + Q_UNUSED(current) + Q_UNUSED(previous) if (m_grouped) { updateVisibleGroupHeaders(); doLayout(NoAnimation); } } -void KItemListView::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous) +void KItemListView::slotSortRoleChanged(const QByteArray ¤t, const QByteArray &previous) { - Q_UNUSED(current); - Q_UNUSED(previous); + Q_UNUSED(current) + Q_UNUSED(previous) if (m_grouped) { updateVisibleGroupHeaders(); doLayout(NoAnimation); @@ -1133,40 +1388,46 @@ void KItemListView::slotSortRoleChanged(const QByteArray& current, const QByteAr void KItemListView::slotCurrentChanged(int current, int previous) { - Q_UNUSED(previous); + Q_UNUSED(previous) - KItemListWidget* previousWidget = m_visibleItems.value(previous, 0); - if (previousWidget) { - previousWidget->setCurrent(false); - } + // 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, nullptr); + if (previousWidget) { + previousWidget->setCurrent(false); + } - KItemListWidget* currentWidget = m_visibleItems.value(current, 0); - if (currentWidget) { - currentWidget->setCurrent(true); + KItemListWidget *currentWidget = m_visibleItems.value(current, nullptr); + if (currentWidget) { + currentWidget->setCurrent(true); + } } + + QAccessibleEvent ev(this, QAccessible::Focus); + ev.setChild(current); + QAccessible::updateAccessibility(&ev); } -void KItemListView::slotSelectionChanged(const QSet& current, const QSet& previous) +void KItemListView::slotSelectionChanged(const KItemSet ¤t, const KItemSet &previous) { - Q_UNUSED(previous); + Q_UNUSED(previous) - QHashIterator it(m_visibleItems); + QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); const int index = it.key(); - KItemListWidget* widget = it.value(); + KItemListWidget *widget = it.value(); widget->setSelected(current.contains(index)); } } -void KItemListView::slotAnimationFinished(QGraphicsWidget* widget, - KItemListViewAnimation::AnimationType type) +void KItemListView::slotAnimationFinished(QGraphicsWidget *widget, KItemListViewAnimation::AnimationType type) { - KItemListWidget* itemListWidget = qobject_cast(widget); + KItemListWidget *itemListWidget = qobject_cast(widget); Q_ASSERT(itemListWidget); - switch (type) { - case KItemListViewAnimation::DeleteAnimation: { + if (type == 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. @@ -1176,32 +1437,16 @@ void KItemListView::slotAnimationFinished(QGraphicsWidget* widget, // by m_visibleWidgets and must be deleted manually after the animation has // been finished. recycleGroupHeaderForWidget(itemListWidget); - m_widgetCreator->recycle(itemListWidget); - break; - } - - case KItemListViewAnimation::CreateAnimation: - case KItemListViewAnimation::MovingAnimation: - case KItemListViewAnimation::ResizeAnimation: { + widgetCreator()->recycle(itemListWidget); + } else { const int index = itemListWidget->index(); - const bool invisible = (index < m_layouter->firstVisibleIndex()) || - (index > m_layouter->lastVisibleIndex()); + 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(); @@ -1210,34 +1455,63 @@ void KItemListView::slotRubberBandPosChanged() void KItemListView::slotRubberBandActivationChanged(bool active) { if (active) { - connect(m_rubberBand, SIGNAL(startPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged())); - connect(m_rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged())); + connect(m_rubberBand, &KItemListRubberBand::startPositionChanged, this, &KItemListView::slotRubberBandPosChanged); + connect(m_rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListView::slotRubberBandPosChanged); m_skipAutoScrollForRubberBand = true; } else { - disconnect(m_rubberBand, SIGNAL(startPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged())); - disconnect(m_rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandPosChanged())); + QRectF rubberBandRect = QRectF(m_rubberBand->startPosition(), m_rubberBand->endPosition()).normalized(); + + auto animation = new QVariantAnimation(this); + animation->setStartValue(1.0); + animation->setEndValue(0.0); + animation->setDuration(RubberFadeSpeed); + animation->setProperty(RubberPropertyName, rubberBandRect); + + QEasingCurve curve; + curve.setType(QEasingCurve::BezierSpline); + curve.addCubicBezierSegment(QPointF(0.4, 0.0), QPointF(1.0, 1.0), QPointF(1.0, 1.0)); + animation->setEasingCurve(curve); + + connect(animation, &QVariantAnimation::valueChanged, this, [=](const QVariant &) { + update(); + }); + connect(animation, &QVariantAnimation::finished, this, [=]() { + m_rubberBandAnimations.removeAll(animation); + delete animation; + }); + animation->start(); + m_rubberBandAnimations << animation; + + 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) +void KItemListView::slotHeaderColumnWidthChanged(const QByteArray &role, qreal currentWidth, qreal previousWidth) { - Q_UNUSED(role); - Q_UNUSED(currentWidth); - Q_UNUSED(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) +void KItemListView::slotSidePaddingChanged(qreal width) +{ + Q_UNUSED(width) + if (m_headerWidget->automaticColumnResizing()) { + applyAutomaticColumnWidths(); + } + applyColumnWidthsFromHeader(); + doLayout(NoAnimation); +} + +void KItemListView::slotHeaderColumnMoved(const QByteArray &role, int currentIndex, int previousIndex) { Q_ASSERT(m_visibleRoles[previousIndex] == role); @@ -1249,7 +1523,7 @@ void KItemListView::slotHeaderColumnMoved(const QByteArray& role, setVisibleRoles(current); - emit visibleRolesChanged(current, previous); + Q_EMIT visibleRolesChanged(current, previous); } void KItemListView::triggerAutoScrolling() @@ -1287,9 +1561,8 @@ void KItemListView::triggerAutoScrolling() // 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(); + 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) @@ -1306,65 +1579,77 @@ void KItemListView::triggerAutoScrolling() const qreal newScrollOffset = qMin(scrollOffset() + m_autoScrollIncrement, maxVisibleOffset); setScrollOffset(newScrollOffset); - // Trigger the autoscroll timer which will periodically call - // triggerAutoScrolling() - m_autoScrollTimer->start(RepeatingAutoScrollDelay); + // Trigger the autoscroll timer which will periodically call + // triggerAutoScrolling() + m_autoScrollTimer->start(RepeatingAutoScrollDelay); } void KItemListView::slotGeometryOfGroupHeaderParentChanged() { - KItemListWidget* widget = qobject_cast(sender()); + KItemListWidget *widget = qobject_cast(sender()); Q_ASSERT(widget); - KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget); + KItemListGroupHeader *groupHeader = m_visibleGroups.value(widget); Q_ASSERT(groupHeader); updateGroupHeaderLayout(widget); } -void KItemListView::setController(KItemListController* controller) +void KItemListView::slotRoleEditingCanceled(int index, const QByteArray &role, const QVariant &value) +{ + disconnectRoleEditingSignals(index); + + m_editingRole = false; + Q_EMIT roleEditingCanceled(index, role, value); +} + +void KItemListView::slotRoleEditingFinished(int index, const QByteArray &role, const QVariant &value) +{ + disconnectRoleEditingSignals(index); + + m_editingRole = false; + Q_EMIT roleEditingFinished(index, role, value); +} + +void KItemListView::setController(KItemListController *controller) { if (m_controller != controller) { - KItemListController* previous = m_controller; + KItemListController *previous = m_controller; if (previous) { - KItemListSelectionManager* selectionManager = previous->selectionManager(); - disconnect(selectionManager, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int))); - disconnect(selectionManager, SIGNAL(selectionChanged(QSet,QSet)), this, SLOT(slotSelectionChanged(QSet,QSet))); + 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, SIGNAL(currentChanged(int,int)), this, SLOT(slotCurrentChanged(int,int))); - connect(selectionManager, SIGNAL(selectionChanged(QSet,QSet)), this, SLOT(slotSelectionChanged(QSet,QSet))); + 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) +void KItemListView::setModel(KItemModelBase *model) { if (m_model == model) { return; } - KItemModelBase* previous = m_model; + KItemModelBase *previous = m_model; if (m_model) { - disconnect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), - this, SLOT(slotItemsChanged(KItemRangeList,QSet))); - disconnect(m_model, SIGNAL(itemsInserted(KItemRangeList)), - this, SLOT(slotItemsInserted(KItemRangeList))); - disconnect(m_model, SIGNAL(itemsRemoved(KItemRangeList)), - this, SLOT(slotItemsRemoved(KItemRangeList))); - disconnect(m_model, SIGNAL(itemsMoved(KItemRange,QList)), - this, SLOT(slotItemsMoved(KItemRange,QList))); - disconnect(m_model, SIGNAL(groupedSortingChanged(bool)), - this, SLOT(slotGroupedSortingChanged(bool))); - disconnect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), - this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder))); - disconnect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), - this, SLOT(slotSortRoleChanged(QByteArray,QByteArray))); + 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; @@ -1372,36 +1657,31 @@ void KItemListView::setModel(KItemModelBase* model) m_grouped = model->groupedSorting(); if (m_model) { - connect(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet)), - this, SLOT(slotItemsChanged(KItemRangeList,QSet))); - connect(m_model, SIGNAL(itemsInserted(KItemRangeList)), - this, SLOT(slotItemsInserted(KItemRangeList))); - connect(m_model, SIGNAL(itemsRemoved(KItemRangeList)), - this, SLOT(slotItemsRemoved(KItemRangeList))); - connect(m_model, SIGNAL(itemsMoved(KItemRange,QList)), - this, SLOT(slotItemsMoved(KItemRange,QList))); - connect(m_model, SIGNAL(groupedSortingChanged(bool)), - this, SLOT(slotGroupedSortingChanged(bool))); - connect(m_model, SIGNAL(sortOrderChanged(Qt::SortOrder,Qt::SortOrder)), - this, SLOT(slotSortOrderChanged(Qt::SortOrder,Qt::SortOrder))); - connect(m_model, SIGNAL(sortRoleChanged(QByteArray,QByteArray)), - this, SLOT(slotSortRoleChanged(QByteArray,QByteArray))); + 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 +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, @@ -1445,13 +1725,11 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha 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); + 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(); @@ -1466,18 +1744,21 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha widget->resize(itemBounds.size()); if (animate && changedCount < 0) { - // Items have been deleted, move the created item to the - // imaginary old position. They will get animated to the new position - // later. - const QRectF itemRect = m_layouter->itemRect(i - changedCount); - 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()); + // 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; } - applyNewPos = false; } if (supportsExpanding && changedCount == 0) { @@ -1489,9 +1770,14 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha } 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 + changedCount + 1)) { + 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) { @@ -1509,9 +1795,6 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha // 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); @@ -1524,6 +1807,8 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha Q_ASSERT(widget->index() == i); widget->setVisible(true); + bool animateIconResizing = animate; + if (widget->size() != itemBounds.size()) { // Resize the widget for the item to the changed size. if (animate) { @@ -1540,6 +1825,17 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha } else { widget->resize(itemBounds.size()); } + } else { + animateIconResizing = false; + } + + const int newIconSize = widget->styleOption().iconSize; + if (widget->iconSize() != newIconSize) { + if (animateIconResizing) { + m_animation->start(widget, KItemListViewAnimation::IconResizeAnimation, newIconSize); + } else { + widget->setIconSize(newIconSize); + } } // Updating the cell-information must be done as last step: The decision whether the @@ -1549,7 +1845,7 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha } // Delete invisible KItemListWidget instances that have not been reused - foreach (int index, reusableItems) { + for (int index : qAsConst(reusableItems)) { recycleWidget(m_visibleItems.value(index)); } @@ -1560,7 +1856,7 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha if (m_grouped) { // Update the layout of all visible group headers - QHashIterator it(m_visibleGroups); + QHashIterator it(m_visibleGroups); while (it.hasNext()) { it.next(); updateGroupHeaderLayout(it.key()); @@ -1570,9 +1866,7 @@ void KItemListView::doLayout(LayoutAnimationHint hint, int changedIndex, int cha emitOffsetChanges(); } -QList KItemListView::recycleInvisibleItems(int firstVisibleIndex, - int lastVisibleIndex, - LayoutAnimationHint hint) +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 @@ -1582,11 +1876,11 @@ QList KItemListView::recycleInvisibleItems(int firstVisibleIndex, QList items; - QHashIterator it(m_visibleItems); + QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); - KItemListWidget* widget = it.value(); + KItemListWidget *widget = it.value(); const int index = widget->index(); const bool invisible = (index < firstVisibleIndex) || (index > lastVisibleIndex); @@ -1611,7 +1905,7 @@ QList KItemListView::recycleInvisibleItems(int firstVisibleIndex, return items; } -bool KItemListView::moveWidget(KItemListWidget* widget,const QPointF& newPos) +bool KItemListView::moveWidget(KItemListWidget *widget, const QPointF &newPos) { if (widget->pos() == newPos) { return false; @@ -1619,17 +1913,22 @@ bool KItemListView::moveWidget(KItemListWidget* widget,const QPointF& newPos) bool startMovingAnim = false; - // 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 (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)); + } } } @@ -1647,32 +1946,32 @@ void KItemListView::emitOffsetChanges() { const qreal newScrollOffset = m_layouter->scrollOffset(); if (m_oldScrollOffset != newScrollOffset) { - emit scrollOffsetChanged(newScrollOffset, m_oldScrollOffset); + Q_EMIT scrollOffsetChanged(newScrollOffset, m_oldScrollOffset); m_oldScrollOffset = newScrollOffset; } const qreal newMaximumScrollOffset = m_layouter->maximumScrollOffset(); if (m_oldMaximumScrollOffset != newMaximumScrollOffset) { - emit maximumScrollOffsetChanged(newMaximumScrollOffset, m_oldMaximumScrollOffset); + Q_EMIT maximumScrollOffsetChanged(newMaximumScrollOffset, m_oldMaximumScrollOffset); m_oldMaximumScrollOffset = newMaximumScrollOffset; } const qreal newItemOffset = m_layouter->itemOffset(); if (m_oldItemOffset != newItemOffset) { - emit itemOffsetChanged(newItemOffset, m_oldItemOffset); + Q_EMIT itemOffsetChanged(newItemOffset, m_oldItemOffset); m_oldItemOffset = newItemOffset; } const qreal newMaximumItemOffset = m_layouter->maximumItemOffset(); if (m_oldMaximumItemOffset != newMaximumItemOffset) { - emit maximumItemOffsetChanged(newMaximumItemOffset, m_oldMaximumItemOffset); + Q_EMIT maximumItemOffsetChanged(newMaximumItemOffset, m_oldMaximumItemOffset); m_oldMaximumItemOffset = newMaximumItemOffset; } } -KItemListWidget* KItemListView::createWidget(int index) +KItemListWidget *KItemListView::createWidget(int index) { - KItemListWidget* widget = m_widgetCreator->create(this); + KItemListWidget *widget = widgetCreator()->create(this); widget->setFlag(QGraphicsItem::ItemStacksBehindParent); m_visibleItems.insert(index, widget); @@ -1682,7 +1981,7 @@ KItemListWidget* KItemListView::createWidget(int index) return widget; } -void KItemListView::recycleWidget(KItemListWidget* widget) +void KItemListView::recycleWidget(KItemListWidget *widget) { if (m_grouped) { recycleGroupHeaderForWidget(widget); @@ -1692,10 +1991,10 @@ void KItemListView::recycleWidget(KItemListWidget* widget) m_visibleItems.remove(index); m_visibleCells.remove(index); - m_widgetCreator->recycle(widget); + widgetCreator()->recycle(widget); } -void KItemListView::setWidgetIndex(KItemListWidget* widget, int index) +void KItemListView::setWidgetIndex(KItemListWidget *widget, int index) { const int oldIndex = widget->index(); m_visibleItems.remove(oldIndex); @@ -1707,7 +2006,7 @@ void KItemListView::setWidgetIndex(KItemListWidget* widget, int index) widget->setIndex(index); } -void KItemListView::moveWidgetToIndex(KItemListWidget* widget, int index) +void KItemListView::moveWidgetToIndex(KItemListWidget *widget, int index) { const int oldIndex = widget->index(); const Cell oldCell = m_visibleCells.value(oldIndex); @@ -1716,30 +2015,39 @@ void KItemListView::moveWidgetToIndex(KItemListWidget* widget, int 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); + 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) +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; + case LayouterSize: + m_layouter->setSize(size); + break; + case ItemSize: + m_layouter->setItemSize(size); + break; + default: + break; } } -void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index) +void KItemListView::updateWidgetProperties(KItemListWidget *widget, int index) { widget->setVisibleRoles(m_visibleRoles); updateWidgetColumnWidths(widget); widget->setStyleOption(m_styleOption); - const KItemListSelectionManager* selectionManager = m_controller->selectionManager(); - widget->setCurrent(index == selectionManager->currentItem()); + 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()); @@ -1753,7 +2061,7 @@ void KItemListView::updateWidgetProperties(KItemListWidget* widget, int index) } } -void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget) +void KItemListView::updateGroupHeaderForWidget(KItemListWidget *widget) { Q_ASSERT(m_grouped); @@ -1765,17 +2073,17 @@ void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget) return; } - const QList > groups = model()->groups(); - if (groups.isEmpty()) { + const QList> groups = model()->groups(); + if (groups.isEmpty() || !groupHeaderCreator()) { return; } - KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget); + KItemListGroupHeader *groupHeader = m_visibleGroups.value(widget); if (!groupHeader) { - groupHeader = m_groupHeaderCreator->create(this); + groupHeader = groupHeaderCreator()->create(this); groupHeader->setParentItem(widget); m_visibleGroups.insert(widget, groupHeader); - connect(widget, SIGNAL(geometryChanged()), this, SLOT(slotGeometryOfGroupHeaderParentChanged())); + connect(widget, &KItemListWidget::geometryChanged, this, &KItemListView::slotGeometryOfGroupHeaderParentChanged); } Q_ASSERT(groupHeader->parentItem() == widget); @@ -1790,9 +2098,9 @@ void KItemListView::updateGroupHeaderForWidget(KItemListWidget* widget) groupHeader->show(); } -void KItemListView::updateGroupHeaderLayout(KItemListWidget* widget) +void KItemListView::updateGroupHeaderLayout(KItemListWidget *widget) { - KItemListGroupHeader* groupHeader = m_visibleGroups.value(widget); + KItemListGroupHeader *groupHeader = m_visibleGroups.value(widget); Q_ASSERT(groupHeader); const int index = widget->index(); @@ -1805,22 +2113,24 @@ void KItemListView::updateGroupHeaderLayout(KItemListWidget* widget) // 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. - groupHeader->setPos(-widget->x(), -groupHeaderRect.height()); - groupHeader->resize(size().width(), groupHeaderRect.size().height()); + 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) +void KItemListView::recycleGroupHeaderForWidget(KItemListWidget *widget) { - KItemListGroupHeader* header = m_visibleGroups.value(widget); + KItemListGroupHeader *header = m_visibleGroups.value(widget); if (header) { - header->setParentItem(0); - m_groupHeaderCreator->recycle(header); + header->setParentItem(nullptr); + groupHeaderCreator()->recycle(header); m_visibleGroups.remove(widget); - disconnect(widget, SIGNAL(geometryChanged()), this, SLOT(slotGeometryOfGroupHeaderParentChanged())); + disconnect(widget, &KItemListWidget::geometryChanged, this, &KItemListView::slotGeometryOfGroupHeaderParentChanged); } } @@ -1829,7 +2139,7 @@ void KItemListView::updateVisibleGroupHeaders() Q_ASSERT(m_grouped); m_layouter->markAsDirty(); - QHashIterator it(m_visibleItems); + QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); updateGroupHeaderForWidget(it.value()); @@ -1840,7 +2150,7 @@ int KItemListView::groupIndexForItem(int index) const { Q_ASSERT(m_grouped); - const QList > groups = model()->groups(); + const QList> groups = model()->groups(); if (groups.isEmpty()) { return -1; } @@ -1868,14 +2178,14 @@ int KItemListView::groupIndexForItem(int index) const void KItemListView::updateAlternateBackgrounds() { - QHashIterator it(m_visibleItems); + QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); updateAlternateBackgroundForWidget(it.value()); } } -void KItemListView::updateAlternateBackgroundForWidget(KItemListWidget* widget) +void KItemListView::updateAlternateBackgroundForWidget(KItemListWidget *widget) { bool enabled = useAlternateBackgrounds(); if (enabled) { @@ -1884,7 +2194,7 @@ void KItemListView::updateAlternateBackgroundForWidget(KItemListWidget* widget) if (m_grouped) { const int groupIndex = groupIndexForItem(index); if (groupIndex >= 0) { - const QList > groups = model()->groups(); + const QList> groups = model()->groups(); const int indexOfFirstGroupItem = groups[groupIndex].first; const int relativeIndex = index - indexOfFirstGroupItem; enabled = (relativeIndex & 0x1) > 0; @@ -1896,10 +2206,10 @@ void KItemListView::updateAlternateBackgroundForWidget(KItemListWidget* widget) bool KItemListView::useAlternateBackgrounds() const { - return m_itemSize.isEmpty() && m_visibleRoles.count() > 1; + return m_alternateBackgrounds && m_itemSize.isEmpty(); } -QHash KItemListView::preferredColumnWidths(const KItemRangeList& itemRanges) const +QHash KItemListView::preferredColumnWidths(const KItemRangeList &itemRanges) const { QElapsedTimer timer; timer.start(); @@ -1909,26 +2219,27 @@ QHash KItemListView::preferredColumnWidths(const KItemRangeLi // 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 gripMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderGripMargin); const int headerMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderMargin); - foreach (const QByteArray& visibleRole, visibleRoles()) { + for (const QByteArray &visibleRole : qAsConst(m_visibleRoles)) { const QString headerText = m_model->roleDescription(visibleRole); - const qreal headerWidth = fontMetrics.width(headerText) + gripMargin + headerMargin * 2; + const qreal headerWidth = fontMetrics.horizontalAdvance(headerText) + gripMargin + headerMargin * 2; widths.insert(visibleRole, headerWidth); } - // Calculate the preferred column withs for each item and ignore values + // Calculate the preferred column widths 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) { + for (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()) { + for (const QByteArray &visibleRole : qAsConst(m_visibleRoles)) { qreal maxWidth = widths.value(visibleRole, 0); - const qreal width = m_widgetCreator->preferredRoleColumnWidth(visibleRole, i, this); + const qreal width = creator->preferredRoleColumnWidth(visibleRole, i, this); maxWidth = qMax(width, maxWidth); widths.insert(visibleRole, maxWidth); } @@ -1953,38 +2264,38 @@ QHash KItemListView::preferredColumnWidths(const KItemRangeLi void KItemListView::applyColumnWidthsFromHeader() { // Apply the new size to the layouter - const qreal requiredWidth = columnWidthsSum(); - const QSizeF dynamicItemSize(qMax(size().width(), requiredWidth), - m_itemSize.height()); + const qreal requiredWidth = columnWidthsSum() + m_headerWidget->sidePadding(); + 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); + QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); updateWidgetColumnWidths(it.value()); } } -void KItemListView::updateWidgetColumnWidths(KItemListWidget* widget) +void KItemListView::updateWidgetColumnWidths(KItemListWidget *widget) { - foreach (const QByteArray& role, m_visibleRoles) { + for (const QByteArray &role : qAsConst(m_visibleRoles)) { widget->setColumnWidth(role, m_headerWidget->columnWidth(role)); } + widget->setSidePadding(m_headerWidget->sidePadding()); } -void KItemListView::updatePreferredColumnWidths(const KItemRangeList& itemRanges) +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) { + for (const KItemRange &range : itemRanges) { rangesItemCount += range.count; } if (itemCount == rangesItemCount) { const QHash preferredWidths = preferredColumnWidths(itemRanges); - foreach (const QByteArray& role, m_visibleRoles) { + for (const QByteArray &role : qAsConst(m_visibleRoles)) { m_headerWidget->setPreferredColumnWidth(role, preferredWidths.value(role)); } } else { @@ -1998,7 +2309,7 @@ void KItemListView::updatePreferredColumnWidths(const KItemRangeList& itemRanges QHashIterator it(updatedWidths); while (it.hasNext()) { it.next(); - const QByteArray& role = it.key(); + const QByteArray &role = it.key(); const qreal updatedWidth = it.value(); const qreal currentWidth = m_headerWidget->preferredColumnWidth(role); if (updatedWidth > currentWidth) { @@ -2030,13 +2341,16 @@ 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) { + for (const QByteArray &role : qAsConst(m_visibleRoles)) { const qreal preferredWidth = m_headerWidget->preferredColumnWidth(role); m_headerWidget->setColumnWidth(role, preferredWidth); } @@ -2045,13 +2359,15 @@ void KItemListView::applyAutomaticColumnWidths() qreal firstColumnWidth = m_headerWidget->columnWidth(firstRole); QSizeF dynamicItemSize = m_itemSize; - qreal requiredWidth = columnWidthsSum(); + qreal requiredWidth = columnWidthsSum() + m_headerWidget->sidePadding() + + m_headerWidget->sidePadding(); // Adding the padding a second time so we have the same padding symmetrically on both sides of the view. + // This improves UX, looks better and increases the chances of users figuring out that the padding area can be used for deselecting and dropping files. 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) { + } 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; @@ -2073,7 +2389,7 @@ void KItemListView::applyAutomaticColumnWidths() m_layouter->setItemSize(dynamicItemSize); // Update the role sizes for all visible widgets - QHashIterator it(m_visibleItems); + QHashIterator it(m_visibleItems); while (it.hasNext()) { it.next(); updateWidgetColumnWidths(it.value()); @@ -2083,7 +2399,7 @@ void KItemListView::applyAutomaticColumnWidths() qreal KItemListView::columnWidthsSum() const { qreal widthsSum = 0; - foreach (const QByteArray& role, m_visibleRoles) { + for (const QByteArray &role : qAsConst(m_visibleRoles)) { widthsSum += m_headerWidget->columnWidth(role); } return widthsSum; @@ -2094,9 +2410,7 @@ 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 +bool KItemListView::changesItemGridLayout(const QSizeF &newGridSize, const QSizeF &newItemSize, const QSizeF &newItemMargin) const { if (newItemSize.isEmpty() || newGridSize.isEmpty()) { return false; @@ -2105,26 +2419,18 @@ bool KItemListView::changesItemGridLayout(const QSizeF& newGridSize, 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()); + 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()); + 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()); + 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()); + const int oldRowCount = itemsPerSize(m_layouter->size().height(), itemHeight, m_layouter->itemMargin().height()); return oldRowCount != newRowCount; } } @@ -2135,19 +2441,23 @@ bool KItemListView::changesItemGridLayout(const QSizeF& newGridSize, 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(); + 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 +bool KItemListView::scrollBarRequired(const QSizeF &size) const { const QSizeF oldSize = m_layouter->size(); @@ -2155,8 +2465,54 @@ bool KItemListView::scrollBarRequired(const QSizeF& size) const const qreal maxOffset = m_layouter->maximumScrollOffset(); m_layouter->setSize(oldSize); - return m_layouter->scrollOrientation() == Qt::Vertical ? maxOffset > size.height() - : maxOffset > size.width(); + 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() @@ -2170,7 +2526,7 @@ void KItemListView::updateGroupHeaderHeight() // from m_styleOption. groupHeaderHeight += 2 * m_styleOption.horizontalMargin; groupHeaderMargin = m_styleOption.horizontalMargin; - } else if (m_itemSize.isEmpty()){ + } else if (m_itemSize.isEmpty()) { groupHeaderHeight += 4 * m_styleOption.padding; groupHeaderMargin = m_styleOption.iconSize / 2; } else { @@ -2191,10 +2547,9 @@ void KItemListView::updateSiblingsInformation(int firstIndex, int lastIndex) if (firstIndex < 0 || lastIndex < 0) { firstIndex = m_layouter->firstVisibleIndex(); - lastIndex = m_layouter->lastVisibleIndex(); + lastIndex = m_layouter->lastVisibleIndex(); } else { - const bool isRangeVisible = (firstIndex <= m_layouter->lastVisibleIndex() && - lastIndex >= m_layouter->firstVisibleIndex()); + const bool isRangeVisible = (firstIndex <= m_layouter->lastVisibleIndex() && lastIndex >= m_layouter->firstVisibleIndex()); if (!isRangeVisible) { return; } @@ -2210,7 +2565,7 @@ void KItemListView::updateSiblingsInformation(int firstIndex, int lastIndex) // contain a siblings information which can be used as base. int rootIndex = firstIndex; - KItemListWidget* widget = m_visibleItems.value(firstIndex - 1); + 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: @@ -2221,7 +2576,7 @@ void KItemListView::updateSiblingsInformation(int firstIndex, int lastIndex) const int parents = m_model->expandedParentsCount(lastIndex + 1); for (int i = lastIndex; i >= firstIndex; --i) { if (m_model->expandedParentsCount(i) != parents) { - widget = 0; + widget = nullptr; break; } } @@ -2263,7 +2618,7 @@ void KItemListView::updateSiblingsInformation(int firstIndex, int lastIndex) 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); + KItemListWidget *widget = m_visibleItems.value(i); if (!widget) { continue; } @@ -2310,6 +2665,18 @@ bool KItemListView::hasSiblingSuccessor(int index) const return hasSuccessor; } +void KItemListView::disconnectRoleEditingSignals(int index) +{ + KStandardItemListWidget *widget = qobject_cast(m_visibleItems.value(index)); + if (!widget) { + return; + } + + disconnect(widget, &KItemListWidget::roleEditingCanceled, this, nullptr); + disconnect(widget, &KItemListWidget::roleEditingFinished, this, nullptr); + disconnect(this, &KItemListView::scrollOffsetChanged, widget, nullptr); +} + int KItemListView::calculateAutoScrollingIncrement(int pos, int range, int oldInc) { int inc = 0; @@ -2343,20 +2710,18 @@ int KItemListView::itemsPerSize(qreal size, qreal itemSize, qreal itemMargin) return count; } - - KItemListCreatorBase::~KItemListCreatorBase() { qDeleteAll(m_recycleableWidgets); qDeleteAll(m_createdWidgets); } -void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget* widget) +void KItemListCreatorBase::addCreatedWidget(QGraphicsWidget *widget) { m_createdWidgets.insert(widget); } -void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget* widget) +void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget *widget) { Q_ASSERT(m_createdWidgets.contains(widget)); m_createdWidgets.remove(widget); @@ -2369,13 +2734,13 @@ void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget* widget) } } -QGraphicsWidget* KItemListCreatorBase::popRecycleableWidget() +QGraphicsWidget *KItemListCreatorBase::popRecycleableWidget() { if (m_recycleableWidgets.isEmpty()) { - return 0; + return nullptr; } - QGraphicsWidget* widget = m_recycleableWidgets.takeLast(); + QGraphicsWidget *widget = m_recycleableWidgets.takeLast(); m_createdWidgets.insert(widget); return widget; } @@ -2384,9 +2749,9 @@ KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase() { } -void KItemListWidgetCreatorBase::recycle(KItemListWidget* widget) +void KItemListWidgetCreatorBase::recycle(KItemListWidget *widget) { - widget->setParentItem(0); + widget->setParentItem(nullptr); widget->setOpacity(1.0); pushRecycleableWidget(widget); } @@ -2395,10 +2760,8 @@ KItemListGroupHeaderCreatorBase::~KItemListGroupHeaderCreatorBase() { } -void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader* header) +void KItemListGroupHeaderCreatorBase::recycle(KItemListGroupHeader *header) { header->setOpacity(1.0); pushRecycleableWidget(header); } - -#include "kitemlistview.moc"