X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/4322ef4114b0b92eee8aa1e941e23188496fbab5..573abcaf25f4629725eafe0282e809e47df1ead3:/src/kitemviews/kitemlistview.cpp diff --git a/src/kitemviews/kitemlistview.cpp b/src/kitemviews/kitemlistview.cpp index d840509da..cf590efce 100644 --- a/src/kitemviews/kitemlistview.cpp +++ b/src/kitemviews/kitemlistview.cpp @@ -1,24 +1,10 @@ -/*************************************************************************** - * 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" @@ -27,25 +13,22 @@ #include "kitemlistcontroller.h" #include "kitemlistheader.h" #include "kitemlistselectionmanager.h" -#include "kitemlistwidget.h" +#include "kitemlistviewaccessible.h" +#include "kstandarditemlistwidget.h" #include "private/kitemlistheaderwidget.h" #include "private/kitemlistrubberband.h" #include "private/kitemlistsizehintresolver.h" #include "private/kitemlistviewlayouter.h" -#include "private/kitemlistviewanimation.h" +#include #include #include -#include -#include +#include #include #include -#include - -#include +#include -#include "kitemlistviewaccessible.h" namespace { // Time in ms until reaching the autoscroll margin triggers @@ -54,6 +37,11 @@ namespace { // Delay in ms for triggering the next autoscroll const int RepeatingAutoScrollDelay = 1000 / 60; + + // Copied from the Kirigami.Units.shortDuration + const int RubberFadeSpeed = 150; + + const char* RubberPropertyName = "_kitemviews_rubberBandPosition"; } #ifndef QT_NO_ACCESSIBILITY @@ -67,7 +55,7 @@ QAccessibleInterface* accessibleInterfaceFactory(const QString& key, QObject* ob return new KItemListViewAccessible(view); } - return 0; + return nullptr; } #endif @@ -80,33 +68,35 @@ KItemListView::KItemListView(QGraphicsWidget* parent) : m_activeTransactions(0), m_endTransactionAnimationHint(Animation), m_itemSize(), - m_controller(0), - m_model(0), + m_controller(nullptr), + m_model(nullptr), m_visibleRoles(), - m_widgetCreator(0), - m_groupHeaderCreator(0), + m_widgetCreator(nullptr), + m_groupHeaderCreator(nullptr), m_styleOption(), m_visibleItems(), m_visibleGroups(), m_visibleCells(), - m_sizeHintResolver(0), - m_layouter(0), - m_animation(0), - m_layoutTimer(0), + m_layouter(nullptr), + m_animation(nullptr), m_oldScrollOffset(0), m_oldMaximumScrollOffset(0), m_oldItemOffset(0), m_oldMaximumItemOffset(0), m_skipAutoScrollForRubberBand(false), - m_rubberBand(0), + m_rubberBand(nullptr), + m_tapAndHoldIndicator(nullptr), m_mousePos(), m_autoScrollIncrement(0), - m_autoScrollTimer(0), - m_header(0), - m_headerWidget(0), - m_dropIndicator() + m_autoScrollTimer(nullptr), + m_header(nullptr), + m_headerWidget(nullptr), + m_indicatorAnimation(nullptr), + m_dropIndicator(), + m_sizeHintResolver(nullptr) { setAcceptHoverEvents(true); + setAcceptTouchEvents(true); m_sizeHintResolver = new KItemListSizeHintResolver(this); @@ -116,14 +106,26 @@ KItemListView::KItemListView(QGraphicsWidget* parent) : connect(m_animation, &KItemListViewAnimation::finished, this, &KItemListView::slotAnimationFinished); - m_layoutTimer = new QTimer(this); - m_layoutTimer->setInterval(300); - m_layoutTimer->setSingleShot(true); - connect(m_layoutTimer, &QTimer::timeout, this, &KItemListView::slotLayoutTimerFinished); - m_rubberBand = new KItemListRubberBand(this); connect(m_rubberBand, &KItemListRubberBand::activationChanged, this, &KItemListView::slotRubberBandActivationChanged); + m_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(); + } + }); + m_headerWidget = new KItemListHeaderWidget(this); m_headerWidget->setVisible(false); @@ -141,13 +143,13 @@ KItemListView::~KItemListView() // widgetCreator(). So it is mandatory to delete the group headers // first. delete m_groupHeaderCreator; - m_groupHeaderCreator = 0; + m_groupHeaderCreator = nullptr; delete m_widgetCreator; - m_widgetCreator = 0; + m_widgetCreator = nullptr; delete m_sizeHintResolver; - m_sizeHintResolver = 0; + m_sizeHintResolver = nullptr; } void KItemListView::setScrollOffset(qreal offset) @@ -228,7 +230,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); @@ -270,13 +272,13 @@ void KItemListView::setAutoScroll(bool enabled) 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) @@ -309,9 +311,7 @@ KItemModelBase* KItemListView::model() const void KItemListView::setWidgetCreator(KItemListWidgetCreatorBase* widgetCreator) { - if (m_widgetCreator) { - delete m_widgetCreator; - } + delete m_widgetCreator; m_widgetCreator = widgetCreator; } @@ -325,9 +325,7 @@ KItemListWidgetCreatorBase* KItemListView::widgetCreator() const void KItemListView::setGroupHeaderCreator(KItemListGroupHeaderCreatorBase* groupHeaderCreator) { - if (m_groupHeaderCreator) { - delete m_groupHeaderCreator; - } + delete m_groupHeaderCreator; m_groupHeaderCreator = groupHeaderCreator; } @@ -344,11 +342,6 @@ QSizeF KItemListView::itemSize() const return m_itemSize; } -QSizeF KItemListView::itemSizeHint() const -{ - return m_sizeHintResolver->maxSizeHint(); -} - const KItemListStyleOption& KItemListView::styleOption() const { return m_styleOption; @@ -373,29 +366,12 @@ void KItemListView::setGeometry(const QRectF& rect) m_itemSize.height()); m_layouter->setItemSize(dynamicItemSize); } - - // Triggering a synchronous layout is fine from a performance point of view, - // as with dynamic item sizes no moving animation must be done. - m_layouter->setSize(newSize); - doLayout(NoAnimation); - } else { - const bool animate = !changesItemGridLayout(newSize, - m_layouter->itemSize(), - m_layouter->itemMargin()); - m_layouter->setSize(newSize); - - if (animate) { - // Trigger an asynchronous relayout with m_layoutTimer to prevent - // performance bottlenecks. If the timer is exceeded, an animated layout - // will be triggered. - if (!m_layoutTimer->isActive()) { - m_layoutTimer->start(); - } - } else { - m_layoutTimer->stop(); - doLayout(NoAnimation); - } } + + 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); } qreal KItemListView::verticalPageStep() const @@ -407,7 +383,7 @@ qreal KItemListView::verticalPageStep() const return size().height() - headerHeight; } -int KItemListView::itemAt(const QPointF& pos) const +std::optional KItemListView::itemAt(const QPointF& pos) const { QHashIterator it(m_visibleItems); while (it.hasNext()) { @@ -415,12 +391,12 @@ int KItemListView::itemAt(const QPointF& pos) const 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 @@ -453,6 +429,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(); @@ -463,7 +452,7 @@ int KItemListView::lastVisibleIndex() const return m_layouter->lastVisibleIndex(); } -void KItemListView::calculateItemSizeHints(QVector& logicalHeightHints, qreal& logicalWidthHint) const +void KItemListView::calculateItemSizeHints(QVector>& logicalHeightHints, qreal& logicalWidthHint) const { widgetCreator()->calculateItemSizeHints(logicalHeightHints, logicalWidthHint, this); } @@ -482,6 +471,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); @@ -500,6 +515,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(); @@ -530,9 +550,12 @@ void KItemListView::scrollToItem(int index) } if (newOffset != scrollOffset()) { - emit scrollTo(newOffset); + Q_EMIT scrollTo(newOffset); + return; } } + + Q_EMIT scrollingStopped(); } void KItemListView::beginTransaction() @@ -578,6 +601,8 @@ void KItemListView::setHeaderVisible(bool visible) 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, @@ -590,6 +615,8 @@ void KItemListView::setHeaderVisible(bool visible) } else if (!visible && m_headerWidget->isVisible()) { 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, @@ -620,7 +647,7 @@ QPixmap KItemListView::createDragPixmap(const KItemSet& indexes) const KItemListWidget* item = m_visibleItems.value(indexes.first()); QGraphicsView* graphicsView = scene()->views()[0]; if (item && graphicsView) { - pixmap = item->createDragPixmap(0, graphicsView); + pixmap = item->createDragPixmap(nullptr, graphicsView); } } else { // TODO: Not implemented yet. Probably extend the interface @@ -633,7 +660,7 @@ QPixmap KItemListView::createDragPixmap(const KItemSet& indexes) const void KItemListView::editRole(int index, const QByteArray& role) { - KItemListWidget* widget = m_visibleItems.value(index); + KStandardItemListWidget* widget = qobject_cast(m_visibleItems.value(index)); if (!widget || m_editingRole) { return; } @@ -645,12 +672,39 @@ void KItemListView::editRole(int index, const QByteArray& role) 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(); @@ -663,7 +717,19 @@ 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(); @@ -673,7 +739,7 @@ void KItemListView::paint(QPainter* painter, const QStyleOptionGraphicsItem* opt if (!m_dropIndicator.isEmpty()) { const QRectF r = m_dropIndicator.toRect(); - QColor color = palette().brush(QPalette::Normal, QPalette::Highlight).color(); + QColor color = palette().brush(QPalette::Normal, QPalette::Text).color(); painter->setPen(color); // TODO: The following implementation works only for a vertical scroll-orientation @@ -711,7 +777,7 @@ void KItemListView::setItemSize(const QSizeF& size) size, m_layouter->itemMargin()); - const bool alternateBackgroundsChanged = (m_visibleRoles.count() > 1) && + const bool alternateBackgroundsChanged = m_alternateBackgrounds && (( m_itemSize.isEmpty() && !size.isEmpty()) || (!m_itemSize.isEmpty() && size.isEmpty())); @@ -745,6 +811,10 @@ void KItemListView::setItemSize(const QSizeF& size) void KItemListView::setStyleOption(const KItemListStyleOption& option) { + if (m_styleOption == option) { + return; + } + const KItemListStyleOption previousOption = m_styleOption; m_styleOption = option; @@ -812,7 +882,7 @@ void KItemListView::setScrollOrientation(Qt::Orientation orientation) doLayout(NoAnimation); onScrollOrientationChanged(orientation, previousOrientation); - emit scrollOrientationChanged(orientation, previousOrientation); + Q_EMIT scrollOrientationChanged(orientation, previousOrientation); } Qt::Orientation KItemListView::scrollOrientation() const @@ -822,70 +892,75 @@ Qt::Orientation KItemListView::scrollOrientation() const KItemListWidgetCreatorBase* KItemListView::defaultWidgetCreator() const { - return 0; + return nullptr; } KItemListGroupHeaderCreatorBase* KItemListView::defaultGroupHeaderCreator() const { - return 0; + return nullptr; } void KItemListView::initializeItemListWidget(KItemListWidget* item) { - Q_UNUSED(item); + Q_UNUSED(item) } bool KItemListView::itemSizeHintUpdateRequired(const QSet& changedRoles) const { - Q_UNUSED(changedRoles); + Q_UNUSED(changedRoles) return true; } 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) { - 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) { - 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) { - Q_UNUSED(current); - Q_UNUSED(previous); + Q_UNUSED(current) + Q_UNUSED(previous) } void KItemListView::onStyleOptionChanged(const KItemListStyleOption& current, 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() @@ -1004,7 +1079,7 @@ void KItemListView::slotItemsInserted(const KItemRangeList& itemRanges) 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. @@ -1030,7 +1105,7 @@ 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]); Q_ASSERT(widget); @@ -1130,7 +1205,10 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) QVector itemsToMove; // Remove all KItemListWidget instances that got deleted - foreach (KItemListWidget* widget, m_visibleItems) { + // 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; @@ -1166,7 +1244,7 @@ void KItemListView::slotItemsRemoved(const KItemRangeList& itemRanges) // after the deleted items. It is important to update them in ascending // order to prevent overlaps when setting the new index. std::sort(itemsToMove.begin(), itemsToMove.end()); - foreach (int i, itemsToMove) { + for (int i : qAsConst(itemsToMove)) { KItemListWidget* widget = m_visibleItems.value(i); Q_ASSERT(widget); const int newIndex = i - count; @@ -1244,17 +1322,13 @@ void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, 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 @@ -1278,6 +1352,8 @@ void KItemListView::slotItemsChanged(const KItemRangeList& itemRanges, ev.setLastRow(itemRange.index + itemRange.count); QAccessible::updateAccessibility(&ev); } + + doLayout(NoAnimation); } void KItemListView::slotGroupsChanged() @@ -1318,8 +1394,8 @@ 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); @@ -1328,8 +1404,8 @@ void KItemListView::slotSortOrderChanged(Qt::SortOrder current, Qt::SortOrder pr void KItemListView::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous) { - Q_UNUSED(current); - Q_UNUSED(previous); + Q_UNUSED(current) + Q_UNUSED(previous) if (m_grouped) { updateVisibleGroupHeaders(); doLayout(NoAnimation); @@ -1338,17 +1414,17 @@ void KItemListView::slotSortRoleChanged(const QByteArray& current, const QByteAr void KItemListView::slotCurrentChanged(int current, int previous) { - Q_UNUSED(previous); + Q_UNUSED(previous) // In SingleSelection mode (e.g., in the Places Panel), the current item is // always the selected item. It is not necessary to highlight the current item then. if (m_controller->selectionBehavior() != KItemListController::SingleSelection) { - KItemListWidget* previousWidget = m_visibleItems.value(previous, 0); + KItemListWidget* previousWidget = m_visibleItems.value(previous, nullptr); if (previousWidget) { previousWidget->setCurrent(false); } - KItemListWidget* currentWidget = m_visibleItems.value(current, 0); + KItemListWidget* currentWidget = m_visibleItems.value(current, nullptr); if (currentWidget) { currentWidget->setCurrent(true); } @@ -1361,7 +1437,7 @@ void KItemListView::slotCurrentChanged(int current, int previous) void KItemListView::slotSelectionChanged(const KItemSet& current, const KItemSet& previous) { - Q_UNUSED(previous); + Q_UNUSED(previous) QHashIterator it(m_visibleItems); while (it.hasNext()) { @@ -1378,8 +1454,7 @@ void KItemListView::slotAnimationFinished(QGraphicsWidget* 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. @@ -1390,31 +1465,16 @@ void KItemListView::slotAnimationFinished(QGraphicsWidget* widget, // been finished. recycleGroupHeaderForWidget(itemListWidget); widgetCreator()->recycle(itemListWidget); - break; - } - - case KItemListViewAnimation::CreateAnimation: - case KItemListViewAnimation::MovingAnimation: - case KItemListViewAnimation::ResizeAnimation: { + } else { const int index = itemListWidget->index(); const bool invisible = (index < m_layouter->firstVisibleIndex()) || (index > m_layouter->lastVisibleIndex()); if (invisible && !m_animation->isStarted(itemListWidget)) { recycleWidget(itemListWidget); } - break; - } - - default: break; } } -void KItemListView::slotLayoutTimerFinished() -{ - m_layouter->setSize(geometry().size()); - doLayout(Animation); -} - void KItemListView::slotRubberBandPosChanged() { update(); @@ -1427,6 +1487,30 @@ void KItemListView::slotRubberBandActivationChanged(bool active) connect(m_rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListView::slotRubberBandPosChanged); m_skipAutoScrollForRubberBand = true; } else { + 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; @@ -1439,15 +1523,25 @@ 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::slotSidePaddingChanged(qreal width) +{ + Q_UNUSED(width) + if (m_headerWidget->automaticColumnResizing()) { + applyAutomaticColumnWidths(); + } + applyColumnWidthsFromHeader(); + doLayout(NoAnimation); +} + void KItemListView::slotHeaderColumnMoved(const QByteArray& role, int currentIndex, int previousIndex) @@ -1462,7 +1556,7 @@ void KItemListView::slotHeaderColumnMoved(const QByteArray& role, setVisibleRoles(current); - emit visibleRolesChanged(current, previous); + Q_EMIT visibleRolesChanged(current, previous); } void KItemListView::triggerAutoScrolling() @@ -1537,16 +1631,16 @@ void KItemListView::slotRoleEditingCanceled(int index, const QByteArray& role, c { disconnectRoleEditingSignals(index); - emit roleEditingCanceled(index, role, value); m_editingRole = false; + Q_EMIT roleEditingCanceled(index, role, value); } void KItemListView::slotRoleEditingFinished(int index, const QByteArray& role, const QVariant& value) { disconnectRoleEditingSignals(index); - emit roleEditingFinished(index, role, value); m_editingRole = false; + Q_EMIT roleEditingFinished(index, role, value); } void KItemListView::setController(KItemListController* controller) @@ -1638,10 +1732,6 @@ KItemListRubberBand* KItemListView::rubberBand() const 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, @@ -1685,13 +1775,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); if (!widget) { - wasHidden = true; if (!reusableItems.isEmpty()) { // Reuse a KItemListWidget instance from an invisible item const int oldIndex = reusableItems.takeLast(); @@ -1758,9 +1846,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); @@ -1773,6 +1858,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) { @@ -1789,6 +1876,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 @@ -1798,7 +1896,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)); } @@ -1901,25 +1999,25 @@ 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; } } @@ -2078,7 +2176,7 @@ void KItemListView::recycleGroupHeaderForWidget(KItemListWidget* widget) { KItemListGroupHeader* header = m_visibleGroups.value(widget); if (header) { - header->setParentItem(0); + header->setParentItem(nullptr); groupHeaderCreator()->recycle(header); m_visibleGroups.remove(widget); disconnect(widget, &KItemListWidget::geometryChanged, this, &KItemListView::slotGeometryOfGroupHeaderParentChanged); @@ -2157,7 +2255,7 @@ 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 @@ -2172,23 +2270,23 @@ QHash KItemListView::preferredColumnWidths(const KItemRangeLi const QFontMetricsF fontMetrics(m_headerWidget->font()); const int gripMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderGripMargin); const int headerMargin = m_headerWidget->style()->pixelMetric(QStyle::PM_HeaderMargin); - foreach (const QByteArray& visibleRole, visibleRoles()) { + 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 = creator->preferredRoleColumnWidth(visibleRole, i, this); maxWidth = qMax(width, maxWidth); @@ -2215,7 +2313,7 @@ QHash KItemListView::preferredColumnWidths(const KItemRangeLi void KItemListView::applyColumnWidthsFromHeader() { // Apply the new size to the layouter - const qreal requiredWidth = columnWidthsSum(); + const qreal requiredWidth = columnWidthsSum() + m_headerWidget->sidePadding(); const QSizeF dynamicItemSize(qMax(size().width(), requiredWidth), m_itemSize.height()); m_layouter->setItemSize(dynamicItemSize); @@ -2230,9 +2328,10 @@ void KItemListView::applyColumnWidthsFromHeader() 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) @@ -2240,13 +2339,13 @@ 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 { @@ -2301,7 +2400,7 @@ void KItemListView::applyAutomaticColumnWidths() // 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); } @@ -2310,7 +2409,9 @@ 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 @@ -2348,7 +2449,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; @@ -2539,7 +2640,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; } } @@ -2630,13 +2731,14 @@ bool KItemListView::hasSiblingSuccessor(int index) const void KItemListView::disconnectRoleEditingSignals(int index) { - KItemListWidget* widget = m_visibleItems.value(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) @@ -2701,7 +2803,7 @@ void KItemListCreatorBase::pushRecycleableWidget(QGraphicsWidget* widget) QGraphicsWidget* KItemListCreatorBase::popRecycleableWidget() { if (m_recycleableWidgets.isEmpty()) { - return 0; + return nullptr; } QGraphicsWidget* widget = m_recycleableWidgets.takeLast(); @@ -2715,7 +2817,7 @@ KItemListWidgetCreatorBase::~KItemListWidgetCreatorBase() void KItemListWidgetCreatorBase::recycle(KItemListWidget* widget) { - widget->setParentItem(0); + widget->setParentItem(nullptr); widget->setOpacity(1.0); pushRecycleableWidget(widget); }