diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5c49612ed3..021491e40d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -261,6 +261,7 @@ install(FILES dolphinpartactions.desktop DESTINATION "${KDE_INSTALL_DATADIR}/dol add_library(dolphinstatic STATIC) target_sources(dolphinstatic PRIVATE + animatedheightwidget.cpp dolphinbookmarkhandler.cpp dolphindockwidget.cpp dolphinmainwindow.cpp @@ -318,6 +319,7 @@ target_sources(dolphinstatic PRIVATE global.cpp dolphin.qrc + animatedheightwidget.h dolphinbookmarkhandler.h dolphindockwidget.h dolphinmainwindow.h diff --git a/src/animatedheightwidget.cpp b/src/animatedheightwidget.cpp new file mode 100644 index 0000000000..cee1a49222 --- /dev/null +++ b/src/animatedheightwidget.cpp @@ -0,0 +1,90 @@ +/* + This file is part of the KDE project + SPDX-FileCopyrightText: 2024 Felix Ernst + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#include "animatedheightwidget.h" + +#include +#include +#include +#include + +AnimatedHeightWidget::AnimatedHeightWidget(QWidget *parent) + : QWidget{parent} +{ + // Showing of this widget is normally animated. We hide it for now and make it small. + hide(); + setMaximumHeight(0); + + auto fillParentLayout = new QGridLayout(this); + fillParentLayout->setContentsMargins(0, 0, 0, 0); + + // Put the contents into a QScrollArea. This prevents increasing the view width + // in case there is not enough available width for the contents. + m_contentsContainerParent = new QScrollArea(this); + fillParentLayout->addWidget(m_contentsContainerParent); + m_contentsContainerParent->setFrameShape(QFrame::NoFrame); + m_contentsContainerParent->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_contentsContainerParent->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_contentsContainerParent->setWidgetResizable(true); + + setMinimumWidth(0); +} + +QSize AnimatedHeightWidget::sizeHint() const +{ + return QSize{1, preferredHeight()}; + // 1 as width because this widget should never be the reason the DolphinViewContainer is made wider. +} + +void AnimatedHeightWidget::setVisible(bool visible, Animated animated) +{ + setEnabled(visible); + if (m_heightAnimation) { + m_heightAnimation->stop(); // deletes because of QAbstractAnimation::DeleteWhenStopped. + } + + if (animated == WithAnimation + && (style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this) < 1 || GlobalConfig::animationDurationFactor() <= 0.0)) { + animated = WithoutAnimation; + } + + if (animated == WithoutAnimation) { + setMaximumHeight(visible ? preferredHeight() : 0); + setVisible(visible); + return; + } + + m_heightAnimation = new QPropertyAnimation(this, "maximumHeight"); + m_heightAnimation->setDuration(2 * style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this) * GlobalConfig::animationDurationFactor()); + + m_heightAnimation->setStartValue(height()); + m_heightAnimation->setEasingCurve(QEasingCurve::OutCubic); + if (visible) { + show(); + m_heightAnimation->setEndValue(preferredHeight()); + } else { + m_heightAnimation->setEndValue(0); + connect(m_heightAnimation, &QAbstractAnimation::finished, this, &QWidget::hide); + } + + m_heightAnimation->start(QAbstractAnimation::DeleteWhenStopped); +} + +QWidget *AnimatedHeightWidget::prepareContentsContainer(QWidget *contentsContainer) +{ + Q_ASSERT_X(!m_contentsContainerParent->widget(), + "AnimatedHeightWidget::prepareContentsContainer", + "Another contentsContainer has already been prepared. There can only be one."); + contentsContainer->setParent(m_contentsContainerParent); + m_contentsContainerParent->setWidget(contentsContainer); + return contentsContainer; +} + +bool AnimatedHeightWidget::isAnimationRunning() const +{ + return m_heightAnimation && m_heightAnimation->state() == QAbstractAnimation::Running; +} diff --git a/src/animatedheightwidget.h b/src/animatedheightwidget.h new file mode 100644 index 0000000000..8f040d5711 --- /dev/null +++ b/src/animatedheightwidget.h @@ -0,0 +1,74 @@ +/* + This file is part of the KDE project + SPDX-FileCopyrightText: 2024 Felix Ernst + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL +*/ + +#ifndef ANIMATEDHEIGHTWIDGET_H +#define ANIMATEDHEIGHTWIDGET_H + +#include "global.h" + +#include +#include + +class QPropertyAnimation; +class QScrollArea; + +/** + * @brief An abstract base class which facilitates animated showing and hiding of sub-classes + */ +class AnimatedHeightWidget : public QWidget +{ +public: + AnimatedHeightWidget(QWidget *parent); + + /** + * Plays a show or hide animation while changing visibility. + * Therefore, if this method is used to hide this widget, the actual hiding will be delayed until the animation finished. + * + * @param visible Whether this bar is supposed to be visible long-term + * @param animated Whether this should be animated. The animation is skipped if the users' settings are configured that way. + * + * @see QWidget::setVisible() + */ + void setVisible(bool visible, Animated animated); + + /** + * @returns a QSize with a width of 1 to make sure that this bar never causes side panels to shrink. + * The returned height equals preferredHeight(). + */ + QSize sizeHint() const override; + +protected: + /** + * AnimatedHeightWidget always requires a singular main child which we call the "contentsContainer". + * Use this method to register such an object. + * + * @returns a "contentsContainer" which is a QWidget that consists of/contains all visible contents of this AnimatedHeightWidget. + * It will be the only grandchild of this AnimatedHeightWidget. + * @param contentsContainer The object that should be used as the "contentsContainer". + */ + QWidget *prepareContentsContainer(QWidget *contentsContainer = new QWidget); + + /** @returns whether this object is currently animating a visibility change. */ + bool isAnimationRunning() const; + +private: + using QWidget::hide; // Use QAbstractAnimation::setVisible() instead. + using QWidget::setVisible; // Makes sure that the setVisible() declaration above doesn't fully hide the one from QWidget so we can still use it privately. + using QWidget::show; // Use QAbstractAnimation::setVisible() instead. + + /** @returns the full preferred height this widget should have when it is done animating and visible. */ + virtual int preferredHeight() const = 0; + +private: + /** @see contentsContainerParent() */ + QScrollArea *m_contentsContainerParent = nullptr; + + /** @see AnimatedHeightWidget::setVisible() */ + QPointer m_heightAnimation; +}; + +#endif // ANIMATEDHEIGHTWIDGET_H diff --git a/src/dolphinviewcontainer.cpp b/src/dolphinviewcontainer.cpp index ecabbc379b..cf7778c824 100644 --- a/src/dolphinviewcontainer.cpp +++ b/src/dolphinviewcontainer.cpp @@ -79,7 +79,7 @@ DolphinViewContainer::DolphinViewContainer(const QUrl &url, QWidget *parent) m_topLayout->setContentsMargins(0, 0, 0, 0); m_searchBox = new DolphinSearchBox(this); - m_searchBox->hide(); + m_searchBox->setVisible(false, WithoutAnimation); connect(m_searchBox, &DolphinSearchBox::activated, this, &DolphinViewContainer::activate); connect(m_searchBox, &DolphinSearchBox::openRequest, this, &DolphinViewContainer::openSearchBox); connect(m_searchBox, &DolphinSearchBox::closeRequest, this, &DolphinViewContainer::closeSearchBox); @@ -112,7 +112,7 @@ DolphinViewContainer::DolphinViewContainer(const QUrl &url, QWidget *parent) // Initialize filter bar m_filterBar = new FilterBar(this); - m_filterBar->setVisible(GeneralSettings::filterBar()); + m_filterBar->setVisible(GeneralSettings::filterBar(), WithoutAnimation); connect(m_filterBar, &FilterBar::filterChanged, this, &DolphinViewContainer::setNameFilter); connect(m_filterBar, &FilterBar::closeRequest, this, &DolphinViewContainer::closeFilterBar); @@ -330,7 +330,8 @@ void DolphinViewContainer::setSelectionModeEnabled(bool enabled, KActionCollecti m_selectionModeBottomBar->setVisible(false, WithAnimation); Q_EMIT selectionModeChanged(false); - if (m_selectionModeTopBar->isAncestorOf(QApplication::focusWidget()) || m_selectionModeBottomBar->isAncestorOf(QApplication::focusWidget())) { + if (!QApplication::focusWidget() || m_selectionModeTopBar->isAncestorOf(QApplication::focusWidget()) + || m_selectionModeBottomBar->isAncestorOf(QApplication::focusWidget())) { m_view->setFocus(); } return; @@ -448,12 +449,12 @@ void DolphinViewContainer::readSettings() bool DolphinViewContainer::isFilterBarVisible() const { - return m_filterBar->isVisible(); + return m_filterBar->isEnabled(); // Gets disabled in AnimatedHeightWidget while animating towards a hidden state. } void DolphinViewContainer::setSearchModeEnabled(bool enabled) { - m_searchBox->setVisible(enabled); + m_searchBox->setVisible(enabled, WithAnimation); if (enabled) { const QUrl &locationUrl = m_urlNavigator->locationUrl(); @@ -585,7 +586,7 @@ void DolphinViewContainer::setFilterBarVisible(bool visible) Q_ASSERT(m_filterBar); if (visible) { m_view->hideToolTip(ToolTipManager::HideBehavior::Instantly); - m_filterBar->show(); + m_filterBar->setVisible(true, WithAnimation); m_filterBar->setFocus(); m_filterBar->selectAll(); } else { diff --git a/src/dolphinviewcontainer.h b/src/dolphinviewcontainer.h index 89fd697607..60c9b90d16 100644 --- a/src/dolphinviewcontainer.h +++ b/src/dolphinviewcontainer.h @@ -157,7 +157,8 @@ public: */ void readSettings(); - /** Returns true, if the filter bar is visible. */ + /** @returns true, if the filter bar is visible. + * false, if it is hidden or currently animating towards a hidden state. */ bool isFilterBarVisible() const; /** Returns true if the search mode is enabled. */ diff --git a/src/filterbar/filterbar.cpp b/src/filterbar/filterbar.cpp index 9d25869ba8..e4aea4b61f 100644 --- a/src/filterbar/filterbar.cpp +++ b/src/filterbar/filterbar.cpp @@ -17,10 +17,12 @@ #include FilterBar::FilterBar(QWidget *parent) - : QWidget(parent) + : AnimatedHeightWidget{parent} { + QWidget *contentsContainer = prepareContentsContainer(); + // Create button to lock text when changing folders - m_lockButton = new QToolButton(this); + m_lockButton = new QToolButton(contentsContainer); m_lockButton->setAutoRaise(true); m_lockButton->setCheckable(true); m_lockButton->setIcon(QIcon::fromTheme(QStringLiteral("object-unlocked"))); @@ -28,7 +30,7 @@ FilterBar::FilterBar(QWidget *parent) connect(m_lockButton, &QToolButton::toggled, this, &FilterBar::slotToggleLockButton); // Create filter editor - m_filterInput = new QLineEdit(this); + m_filterInput = new QLineEdit(contentsContainer); m_filterInput->setLayoutDirection(Qt::LeftToRight); m_filterInput->setClearButtonEnabled(true); m_filterInput->setPlaceholderText(i18n("Filter…")); @@ -36,14 +38,14 @@ FilterBar::FilterBar(QWidget *parent) setFocusProxy(m_filterInput); // Create close button - QToolButton *closeButton = new QToolButton(this); + QToolButton *closeButton = new QToolButton(contentsContainer); closeButton->setAutoRaise(true); closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); closeButton->setToolTip(i18nc("@info:tooltip", "Hide Filter Bar")); connect(closeButton, &QToolButton::clicked, this, &FilterBar::closeRequest); // Apply layout - QHBoxLayout *hLayout = new QHBoxLayout(this); + QHBoxLayout *hLayout = new QHBoxLayout(contentsContainer); hLayout->setContentsMargins(0, 0, 0, 0); hLayout->addWidget(m_lockButton); hLayout->addWidget(m_filterInput); @@ -59,7 +61,7 @@ FilterBar::~FilterBar() void FilterBar::closeFilterBar() { - hide(); + setVisible(false, WithAnimation); clear(); if (m_lockButton) { m_lockButton->setChecked(false); @@ -135,4 +137,9 @@ void FilterBar::keyPressEvent(QKeyEvent *event) QWidget::keyPressEvent(event); } +int FilterBar::preferredHeight() const +{ + return std::max(m_filterInput->sizeHint().height(), m_lockButton->sizeHint().height()); +} + #include "moc_filterbar.cpp" diff --git a/src/filterbar/filterbar.h b/src/filterbar/filterbar.h index 3530558839..1424f4cb81 100644 --- a/src/filterbar/filterbar.h +++ b/src/filterbar/filterbar.h @@ -9,7 +9,7 @@ #ifndef FILTERBAR_H #define FILTERBAR_H -#include +#include "animatedheightwidget.h" class QLineEdit; class QToolButton; @@ -19,7 +19,7 @@ class QToolButton; * * @author Gregor Kališnik */ -class FilterBar : public QWidget +class FilterBar : public AnimatedHeightWidget { Q_OBJECT @@ -64,6 +64,9 @@ protected: void showEvent(QShowEvent *event) override; void keyPressEvent(QKeyEvent *event) override; + /** @see AnimatedHeightWidget::preferredHeight() */ + int preferredHeight() const override; + private: QLineEdit *m_filterInput; QToolButton *m_lockButton; diff --git a/src/search/dolphinsearchbox.cpp b/src/search/dolphinsearchbox.cpp index 5f5577006a..8962bd3d28 100644 --- a/src/search/dolphinsearchbox.cpp +++ b/src/search/dolphinsearchbox.cpp @@ -36,7 +36,7 @@ #include DolphinSearchBox::DolphinSearchBox(QWidget *parent) - : QWidget(parent) + : AnimatedHeightWidget(parent) , m_startedSearching(false) , m_active(true) , m_topLayout(nullptr) @@ -51,6 +51,7 @@ DolphinSearchBox::DolphinSearchBox(QWidget *parent) , m_facetsWidget(nullptr) , m_searchPath() , m_startSearchTimer(nullptr) + , m_initialized(false) { } @@ -176,12 +177,12 @@ bool DolphinSearchBox::isActive() const return m_active; } -bool DolphinSearchBox::event(QEvent *event) +void DolphinSearchBox::setVisible(bool visible, Animated animated) { - if (event->type() == QEvent::Polish) { + if (visible) { init(); } - return QWidget::event(event); + AnimatedHeightWidget::setVisible(visible, animated); } void DolphinSearchBox::showEvent(QShowEvent *event) @@ -329,15 +330,23 @@ void DolphinSearchBox::loadSettings() void DolphinSearchBox::saveSettings() { - SearchSettings::setLocation(m_fromHereButton->isChecked() ? QStringLiteral("FromHere") : QStringLiteral("Everywhere")); - SearchSettings::setWhat(m_fileNameButton->isChecked() ? QStringLiteral("FileName") : QStringLiteral("Content")); - SearchSettings::self()->save(); + if (m_initialized) { + SearchSettings::setLocation(m_fromHereButton->isChecked() ? QStringLiteral("FromHere") : QStringLiteral("Everywhere")); + SearchSettings::setWhat(m_fileNameButton->isChecked() ? QStringLiteral("FileName") : QStringLiteral("Content")); + SearchSettings::self()->save(); + } } void DolphinSearchBox::init() { + if (m_initialized) { + return; // This object is already initialised. + } + + QWidget *contentsContainer = prepareContentsContainer(); + // Create search box - m_searchInput = new QLineEdit(this); + m_searchInput = new QLineEdit(contentsContainer); m_searchInput->setPlaceholderText(i18n("Search…")); m_searchInput->installEventFilter(this); m_searchInput->setClearButtonEnabled(true); @@ -355,7 +364,7 @@ void DolphinSearchBox::init() connect(m_saveSearchAction, &QAction::triggered, this, &DolphinSearchBox::slotSearchSaved); // Create close button - QToolButton *closeButton = new QToolButton(this); + QToolButton *closeButton = new QToolButton(contentsContainer); closeButton->setAutoRaise(true); closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); closeButton->setToolTip(i18nc("@info:tooltip", "Quit searching")); @@ -368,7 +377,7 @@ void DolphinSearchBox::init() searchInputLayout->addWidget(closeButton); // Create "Filename" and "Content" button - m_fileNameButton = new QToolButton(this); + m_fileNameButton = new QToolButton(contentsContainer); m_fileNameButton->setText(i18nc("action:button", "Filename")); initButton(m_fileNameButton); @@ -376,25 +385,25 @@ void DolphinSearchBox::init() m_contentButton->setText(i18nc("action:button", "Content")); initButton(m_contentButton); - QButtonGroup *searchWhatGroup = new QButtonGroup(this); + QButtonGroup *searchWhatGroup = new QButtonGroup(contentsContainer); searchWhatGroup->addButton(m_fileNameButton); searchWhatGroup->addButton(m_contentButton); - m_separator = new KSeparator(Qt::Vertical, this); + m_separator = new KSeparator(Qt::Vertical, contentsContainer); // Create "From Here" and "Your files" buttons - m_fromHereButton = new QToolButton(this); + m_fromHereButton = new QToolButton(contentsContainer); m_fromHereButton->setText(i18nc("action:button", "From Here")); initButton(m_fromHereButton); - m_everywhereButton = new QToolButton(this); + m_everywhereButton = new QToolButton(contentsContainer); m_everywhereButton->setText(i18nc("action:button", "Your files")); m_everywhereButton->setToolTip(i18nc("action:button", "Search in your home directory")); m_everywhereButton->setIcon(QIcon::fromTheme(QStringLiteral("user-home"))); m_everywhereButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); initButton(m_everywhereButton); - QButtonGroup *searchLocationGroup = new QButtonGroup(this); + QButtonGroup *searchLocationGroup = new QButtonGroup(contentsContainer); searchLocationGroup->addButton(m_fromHereButton); searchLocationGroup->addButton(m_everywhereButton); @@ -402,7 +411,7 @@ void DolphinSearchBox::init() QToolButton *kfindToolsButton = nullptr; if (kfind) { - kfindToolsButton = new QToolButton(this); + kfindToolsButton = new QToolButton(contentsContainer); kfindToolsButton->setAutoRaise(true); kfindToolsButton->setPopupMode(QToolButton::InstantPopup); kfindToolsButton->setIcon(QIcon::fromTheme("arrow-down-double")); @@ -418,7 +427,7 @@ void DolphinSearchBox::init() } // Create "Facets" widget - m_facetsWidget = new DolphinFacetsWidget(this); + m_facetsWidget = new DolphinFacetsWidget(contentsContainer); m_facetsWidget->installEventFilter(this); m_facetsWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); m_facetsWidget->layout()->setSpacing(Dolphin::LAYOUT_SPACING_SMALL); @@ -426,7 +435,7 @@ void DolphinSearchBox::init() // Put the options into a QScrollArea. This prevents increasing the view width // in case that not enough width for the options is available. - QWidget *optionsContainer = new QWidget(this); + QWidget *optionsContainer = new QWidget(contentsContainer); // Apply layout for the options QHBoxLayout *optionsLayout = new QHBoxLayout(optionsContainer); @@ -437,13 +446,13 @@ void DolphinSearchBox::init() optionsLayout->addWidget(m_separator); optionsLayout->addWidget(m_fromHereButton); optionsLayout->addWidget(m_everywhereButton); - optionsLayout->addWidget(new KSeparator(Qt::Vertical, this)); + optionsLayout->addWidget(new KSeparator(Qt::Vertical, contentsContainer)); if (kfindToolsButton) { optionsLayout->addWidget(kfindToolsButton); } optionsLayout->addStretch(1); - m_optionsScrollArea = new QScrollArea(this); + m_optionsScrollArea = new QScrollArea(contentsContainer); m_optionsScrollArea->setFrameShape(QFrame::NoFrame); m_optionsScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_optionsScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -452,7 +461,7 @@ void DolphinSearchBox::init() m_optionsScrollArea->setWidget(optionsContainer); m_optionsScrollArea->setWidgetResizable(true); - m_topLayout = new QVBoxLayout(this); + m_topLayout = new QVBoxLayout(contentsContainer); m_topLayout->setContentsMargins(0, Dolphin::LAYOUT_SPACING_SMALL, 0, 0); m_topLayout->setSpacing(Dolphin::LAYOUT_SPACING_SMALL); m_topLayout->addLayout(searchInputLayout); @@ -467,6 +476,8 @@ void DolphinSearchBox::init() m_startSearchTimer->setSingleShot(true); m_startSearchTimer->setInterval(500); connect(m_startSearchTimer, &QTimer::timeout, this, &DolphinSearchBox::emitSearchRequest); + + m_initialized = true; } QString DolphinSearchBox::queryTitle(const QString &text) const @@ -539,6 +550,11 @@ void DolphinSearchBox::updateFacetsVisible() const bool indexingEnabled = isIndexingEnabled(); m_facetsWidget->setEnabled(indexingEnabled); m_facetsWidget->setVisible(indexingEnabled); + + // The m_facetsWidget might have changed visibility. We smoothly animate towards the updated height. + if (isVisible() && isEnabled()) { + setVisible(true, WithAnimation); + } } bool DolphinSearchBox::isIndexingEnabled() const @@ -551,4 +567,9 @@ bool DolphinSearchBox::isIndexingEnabled() const #endif } +int DolphinSearchBox::preferredHeight() const +{ + return m_initialized ? m_topLayout->sizeHint().height() : 0; +} + #include "moc_dolphinsearchbox.cpp" diff --git a/src/search/dolphinsearchbox.h b/src/search/dolphinsearchbox.h index 9f1ad29525..6a847ba57e 100644 --- a/src/search/dolphinsearchbox.h +++ b/src/search/dolphinsearchbox.h @@ -7,8 +7,9 @@ #ifndef DOLPHINSEARCHBOX_H #define DOLPHINSEARCHBOX_H +#include "animatedheightwidget.h" + #include -#include class DolphinFacetsWidget; class DolphinQuery; @@ -29,7 +30,7 @@ class QVBoxLayout; * If Baloo is available and the current folder is indexed, further * options are offered. */ -class DolphinSearchBox : public QWidget +class DolphinSearchBox : public AnimatedHeightWidget { Q_OBJECT @@ -86,8 +87,13 @@ public: */ bool isActive() const; + /* + * @see AnimatedHeightWidget::setVisible() + * @see QWidget::setVisible() + */ + void setVisible(bool visible, Animated animated); + protected: - bool event(QEvent *event) override; void showEvent(QShowEvent *event) override; void hideEvent(QHideEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; @@ -152,6 +158,9 @@ private: bool isIndexingEnabled() const; + /** @see AnimatedHeightWidget::preferredHeight() */ + int preferredHeight() const override; + private: QString queryTitle(const QString &text) const; @@ -173,6 +182,8 @@ private: QUrl m_searchPath; QTimer *m_startSearchTimer; + + bool m_initialized; }; #endif diff --git a/src/selectionmode/bottombar.cpp b/src/selectionmode/bottombar.cpp index b40fc95db4..ee63246caa 100644 --- a/src/selectionmode/bottombar.cpp +++ b/src/selectionmode/bottombar.cpp @@ -12,36 +12,16 @@ #include #include -#include #include #include using namespace SelectionMode; BottomBar::BottomBar(KActionCollection *actionCollection, QWidget *parent) - : QWidget{parent} + : AnimatedHeightWidget{parent} { - // Showing of this widget is normally animated. We hide it for now and make it small. - hide(); - setMaximumHeight(0); - - setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); - setMinimumWidth(0); - - auto fillParentLayout = new QGridLayout(this); - fillParentLayout->setContentsMargins(0, 0, 0, 0); - - // Put the contents into a QScrollArea. This prevents increasing the view width - // in case that not enough width for the contents is available. (this trick is also used in dolphinsearchbox.cpp.) - m_scrollArea = new QScrollArea(this); - fillParentLayout->addWidget(m_scrollArea); - m_scrollArea->setFrameShape(QFrame::NoFrame); - m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - m_scrollArea->setWidgetResizable(true); - - m_contentsContainer = new BottomBarContentsContainer(actionCollection, m_scrollArea); - m_scrollArea->setWidget(m_contentsContainer); + m_contentsContainer = new BottomBarContentsContainer(actionCollection, nullptr); + prepareContentsContainer(m_contentsContainer); m_contentsContainer->installEventFilter(this); // Adjusts the height of this bar to the height of the contentsContainer connect(m_contentsContainer, &BottomBarContentsContainer::error, this, &BottomBar::error); connect(m_contentsContainer, &BottomBarContentsContainer::barVisibilityChangeRequested, this, [this](bool visible) { @@ -52,6 +32,7 @@ BottomBar::BottomBar(KActionCollection *actionCollection, QWidget *parent) }); connect(m_contentsContainer, &BottomBarContentsContainer::selectionModeLeavingRequested, this, &BottomBar::selectionModeLeavingRequested); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); BackgroundColorHelper::instance()->controlBackgroundColor(this); } @@ -63,7 +44,6 @@ void BottomBar::setVisible(bool visible, Animated animated) void BottomBar::setVisibleInternal(bool visible, Animated animated) { - Q_ASSERT_X(animated == WithAnimation, "SelectionModeBottomBar::setVisible", "This wasn't implemented."); if (!visible && contents() == PasteContents) { return; // The bar with PasteContents should not be hidden or users might not know how to paste what they just copied. // Set contents to anything else to circumvent this prevention mechanism. @@ -72,32 +52,7 @@ void BottomBar::setVisibleInternal(bool visible, Animated animated) return; // There is nothing on the bar that we want to show. We keep it invisible and only show it when the selection or the contents change. } - setEnabled(visible); - if (m_heightAnimation) { - m_heightAnimation->stop(); // deletes because of QAbstractAnimation::DeleteWhenStopped. - } - m_heightAnimation = new QPropertyAnimation(this, "maximumHeight"); - m_heightAnimation->setDuration(2 * style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this) * GlobalConfig::animationDurationFactor()); - m_heightAnimation->setStartValue(height()); - m_heightAnimation->setEasingCurve(QEasingCurve::OutCubic); - if (visible) { - show(); - m_heightAnimation->setEndValue(sizeHint().height()); - connect(m_heightAnimation, &QAbstractAnimation::finished, this, [this]() { - setMaximumHeight(sizeHint().height()); - }); - } else { - m_heightAnimation->setEndValue(0); - connect(m_heightAnimation, &QAbstractAnimation::finished, this, &QWidget::hide); - } - - m_heightAnimation->start(QAbstractAnimation::DeleteWhenStopped); -} - -QSize BottomBar::sizeHint() const -{ - return QSize{1, m_contentsContainer->sizeHint().height()}; - // 1 as width because this widget should never be the reason the DolphinViewContainer is made wider. + AnimatedHeightWidget::setVisible(visible, animated); } void BottomBar::slotSelectionChanged(const KFileItemList &selection, const QUrl &baseUrl) @@ -139,7 +94,7 @@ bool BottomBar::eventFilter(QObject *watched, QEvent *event) case QEvent::ChildRemoved: QTimer::singleShot(0, this, [this]() { // The necessary height might have changed because of the added/removed child so we change the height manually. - if (isVisibleTo(parentWidget()) && isEnabled() && (!m_heightAnimation || m_heightAnimation->state() != QAbstractAnimation::Running)) { + if (isVisibleTo(parentWidget()) && isEnabled() && !isAnimationRunning()) { setMaximumHeight(sizeHint().height()); } }); @@ -153,12 +108,17 @@ void BottomBar::resizeEvent(QResizeEvent *resizeEvent) { if (resizeEvent->oldSize().width() == resizeEvent->size().width()) { // The width() didn't change so our custom override isn't needed. - return QWidget::resizeEvent(resizeEvent); + return AnimatedHeightWidget::resizeEvent(resizeEvent); } m_contentsContainer->adaptToNewBarWidth(width()); - return QWidget::resizeEvent(resizeEvent); + return AnimatedHeightWidget::resizeEvent(resizeEvent); +} + +int BottomBar::preferredHeight() const +{ + return m_contentsContainer->sizeHint().height(); } #include "moc_bottombar.cpp" diff --git a/src/selectionmode/bottombar.h b/src/selectionmode/bottombar.h index fd6eaebd9e..73515b0bd2 100644 --- a/src/selectionmode/bottombar.h +++ b/src/selectionmode/bottombar.h @@ -8,18 +8,16 @@ #ifndef BOTTOMBAR_H #define BOTTOMBAR_H +#include "animatedheightwidget.h" #include "global.h" #include #include -#include -#include class KActionCollection; class KFileItemList; class QPushButton; class QResizeEvent; -class QScrollArea; class QUrl; namespace SelectionMode @@ -34,7 +32,7 @@ class BottomBarContentsContainer; * * The visible contents of the bar are managed in BottomBarContentsContainer. This class serves as a wrapper around it. */ -class BottomBar : public QWidget +class BottomBar : public AnimatedHeightWidget { Q_OBJECT @@ -73,6 +71,7 @@ public: * @param visible Whether this bar is supposed to be visible long term * @param animated Whether this should be animated. The animation is skipped if the users' settings are configured that way. * + * @see AnimatedHeightWidget::setVisible() * @see QWidget::setVisible() */ void setVisible(bool visible, Animated animated); @@ -83,9 +82,6 @@ public: void resetContents(Contents contents); Contents contents() const; - /** @returns a width of 1 to make sure that this bar never causes side panels to shrink. */ - QSize sizeHint() const override; - public Q_SLOTS: /** Adapts the contents based on the selection in the related view. */ void slotSelectionChanged(const KFileItemList &selection, const QUrl &baseUrl); @@ -109,8 +105,6 @@ protected: void resizeEvent(QResizeEvent *resizeEvent) override; private: - using QWidget::setVisible; // Makes sure that the setVisible() declaration above doesn't hide the one from QWidget so we can still use it privately. - /** * Identical to SelectionModeBottomBar::setVisible() but doesn't change m_allowedToBeVisible. * @see SelectionModeBottomBar::setVisible() @@ -118,9 +112,10 @@ private: */ void setVisibleInternal(bool visible, Animated animated); + /** @see AnimatedHeightWidget::preferredHeight() */ + int preferredHeight() const override; + private: - /** The only direct child widget of this bar. */ - QScrollArea *m_scrollArea; /** The only direct grandchild of this bar. */ BottomBarContentsContainer *m_contentsContainer; @@ -128,8 +123,6 @@ private: * This is necessary because this bar might have been setVisible(true) but there is no reason to show the bar currently so it was kept hidden. * @see SelectionModeBottomBar::setVisible() */ bool m_allowedToBeVisible = false; - /** @see SelectionModeBottomBar::setVisible() */ - QPointer m_heightAnimation; }; } diff --git a/src/selectionmode/topbar.cpp b/src/selectionmode/topbar.cpp index abe9f74a5c..5d77a4c008 100644 --- a/src/selectionmode/topbar.cpp +++ b/src/selectionmode/topbar.cpp @@ -16,18 +16,13 @@ #include #include #include -#include #include using namespace SelectionMode; TopBar::TopBar(QWidget *parent) - : QWidget{parent} + : AnimatedHeightWidget{parent} { - // Showing of this widget is normally animated. We hide it for now and make it small. - hide(); - setMaximumHeight(0); - setToolTip(KToolTipHelper::whatsThisHintOnly()); setWhatsThis(xi18nc("@info:whatsthis", "Selection ModeSelect files or folders to manage or manipulate them." @@ -36,25 +31,10 @@ TopBar::TopBar(QWidget *parent) "Selection rectangles (created by dragging from an empty area) invert the selection status of items within." "The available action buttons at the bottom change depending on the current selection.")); - auto fillParentLayout = new QGridLayout(this); - fillParentLayout->setContentsMargins(0, 0, 0, 0); - - // Put the contents into a QScrollArea. This prevents increasing the view width - // in case that not enough width for the contents is available. (this trick is also used in bottombar.cpp.) - auto scrollArea = new QScrollArea(this); - fillParentLayout->addWidget(scrollArea); - scrollArea->setFrameShape(QFrame::NoFrame); - scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea->setWidgetResizable(true); - - auto contentsContainer = new QWidget(scrollArea); - scrollArea->setWidget(contentsContainer); + QWidget *contentsContainer = prepareContentsContainer(); BackgroundColorHelper::instance()->controlBackgroundColor(this); - setMinimumWidth(0); - m_fullLabelString = i18nc("@info label above the view explaining the state", "Selection Mode: Click on files or folders to select or deselect them."); m_shortLabelString = i18nc("@info label above the view explaining the state", "Selection Mode"); m_label = new QLabel(contentsContainer); @@ -69,7 +49,6 @@ TopBar::TopBar(QWidget *parent) QHBoxLayout *layout = new QHBoxLayout(contentsContainer); auto contentsMargins = layout->contentsMargins(); m_preferredHeight = contentsMargins.top() + m_label->sizeHint().height() + contentsMargins.bottom(); - scrollArea->setMaximumHeight(m_preferredHeight); m_closeButton->setFixedHeight(m_preferredHeight); layout->setContentsMargins(0, 0, 0, 0); @@ -79,33 +58,10 @@ TopBar::TopBar(QWidget *parent) layout->addWidget(m_closeButton); } -void TopBar::setVisible(bool visible, Animated animated) -{ - Q_ASSERT_X(animated == WithAnimation, "SelectionModeTopBar::setVisible", "This wasn't implemented."); - - if (m_heightAnimation) { - m_heightAnimation->stop(); // deletes because of QAbstractAnimation::DeleteWhenStopped. - } - m_heightAnimation = new QPropertyAnimation(this, "maximumHeight"); - m_heightAnimation->setDuration(2 * style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this) * GlobalConfig::animationDurationFactor()); - - m_heightAnimation->setStartValue(height()); - m_heightAnimation->setEasingCurve(QEasingCurve::OutCubic); - if (visible) { - show(); - m_heightAnimation->setEndValue(m_preferredHeight); - } else { - m_heightAnimation->setEndValue(0); - connect(m_heightAnimation, &QAbstractAnimation::finished, this, &QWidget::hide); - } - - m_heightAnimation->start(QAbstractAnimation::DeleteWhenStopped); -} - void TopBar::resizeEvent(QResizeEvent *resizeEvent) { updateLabelString(); - return QWidget::resizeEvent(resizeEvent); + return AnimatedHeightWidget::resizeEvent(resizeEvent); } void TopBar::updateLabelString() diff --git a/src/selectionmode/topbar.h b/src/selectionmode/topbar.h index 028fc49855..1f9cfdb18a 100644 --- a/src/selectionmode/topbar.h +++ b/src/selectionmode/topbar.h @@ -8,17 +8,11 @@ #ifndef SELECTIONMODETOPBAR_H #define SELECTIONMODETOPBAR_H -#include "global.h" +#include "animatedheightwidget.h" -#include -#include -#include - -class QHideEvent; class QLabel; class QPushButton; class QResizeEvent; -class QShowEvent; namespace SelectionMode { @@ -26,20 +20,13 @@ namespace SelectionMode /** * @brief A bar appearing at the top of the view when in selection mode to make users aware of the selection mode state of the application. */ -class TopBar : public QWidget +class TopBar : public AnimatedHeightWidget { Q_OBJECT public: TopBar(QWidget *parent); - /** - * Plays a show or hide animation while changing visibility. - * Therefore, if this method is used to hide this widget, the actual hiding will be postponed until the animation finished. - * @see QWidget::setVisible() - */ - void setVisible(bool visible, Animated animated); - Q_SIGNALS: void selectionModeLeavingRequested(); @@ -48,11 +35,15 @@ protected: void resizeEvent(QResizeEvent *resizeEvent) override; private: - using QWidget::setVisible; // Makes sure that the setVisible() declaration above doesn't hide the one from QWidget so we can still use it privately. - /** Decides whether the m_fullLabelString or m_shortLabelString should be used based on available width. */ void updateLabelString(); + /** @see AnimatedHeightWidget::preferredHeight() */ + inline int preferredHeight() const override + { + return m_preferredHeight; + }; + private: QLabel *m_label; QPushButton *m_closeButton; @@ -63,8 +54,6 @@ private: QString m_shortLabelString; int m_preferredHeight; - - QPointer m_heightAnimation; }; } diff --git a/src/statusbar/dolphinstatusbar.cpp b/src/statusbar/dolphinstatusbar.cpp index 9ea0ae5aa7..8e7ee55867 100644 --- a/src/statusbar/dolphinstatusbar.cpp +++ b/src/statusbar/dolphinstatusbar.cpp @@ -32,7 +32,7 @@ const int UpdateDelay = 50; } DolphinStatusBar::DolphinStatusBar(QWidget *parent) - : QWidget(parent) + : AnimatedHeightWidget(parent) , m_text() , m_defaultText() , m_label(nullptr) @@ -47,16 +47,19 @@ DolphinStatusBar::DolphinStatusBar(QWidget *parent) , m_textTimestamp() { setProperty("_breeze_statusbar_separator", true); + + QWidget *contentsContainer = prepareContentsContainer(); + // Initialize text label - m_label = new KSqueezedTextLabel(m_text, this); + m_label = new KSqueezedTextLabel(m_text, contentsContainer); m_label->setWordWrap(true); m_label->setTextFormat(Qt::PlainText); // Initialize zoom slider's explanatory label - m_zoomLabel = new KSqueezedTextLabel(i18nc("Used as a noun, i.e. 'Here is the zoom level:'", "Zoom:"), this); + m_zoomLabel = new KSqueezedTextLabel(i18nc("Used as a noun, i.e. 'Here is the zoom level:'", "Zoom:"), contentsContainer); // Initialize zoom widget - m_zoomSlider = new QSlider(Qt::Horizontal, this); + m_zoomSlider = new QSlider(Qt::Horizontal, contentsContainer); m_zoomSlider->setAccessibleName(i18n("Zoom")); m_zoomSlider->setAccessibleDescription(i18nc("Description for zoom-slider (accessibility)", "Sets the size of the file icons.")); m_zoomSlider->setPageStep(1); @@ -67,10 +70,10 @@ DolphinStatusBar::DolphinStatusBar(QWidget *parent) connect(m_zoomSlider, &QSlider::sliderMoved, this, &DolphinStatusBar::showZoomSliderToolTip); // Initialize space information - m_spaceInfo = new StatusBarSpaceInfo(this); + m_spaceInfo = new StatusBarSpaceInfo(contentsContainer); // Initialize progress information - m_stopButton = new QToolButton(this); + m_stopButton = new QToolButton(contentsContainer); m_stopButton->setIcon(QIcon::fromTheme(QStringLiteral("process-stop"))); m_stopButton->setAccessibleName(i18n("Stop")); m_stopButton->setAutoRaise(true); @@ -78,10 +81,10 @@ DolphinStatusBar::DolphinStatusBar(QWidget *parent) m_stopButton->hide(); connect(m_stopButton, &QToolButton::clicked, this, &DolphinStatusBar::stopPressed); - m_progressTextLabel = new QLabel(this); + m_progressTextLabel = new QLabel(contentsContainer); m_progressTextLabel->hide(); - m_progressBar = new QProgressBar(this); + m_progressBar = new QProgressBar(contentsContainer); m_progressBar->hide(); m_showProgressBarTimer = new QTimer(this); @@ -115,18 +118,18 @@ DolphinStatusBar::DolphinStatusBar(QWidget *parent) m_progressBar->setFixedHeight(zoomSliderHeight); m_progressBar->setMaximumWidth(fontMetrics.averageCharWidth() * 20); - QHBoxLayout *topLayout = new QHBoxLayout(this); + m_topLayout = new QHBoxLayout(contentsContainer); updateContentsMargins(); - topLayout->setSpacing(4); - topLayout->addWidget(m_label, 1); - topLayout->addWidget(m_zoomLabel); - topLayout->addWidget(m_zoomSlider, 1); - topLayout->addWidget(m_spaceInfo, 1); - topLayout->addWidget(m_stopButton); - topLayout->addWidget(m_progressTextLabel); - topLayout->addWidget(m_progressBar); + m_topLayout->setSpacing(4); + m_topLayout->addWidget(m_label, 1); + m_topLayout->addWidget(m_zoomLabel); + m_topLayout->addWidget(m_zoomSlider, 1); + m_topLayout->addWidget(m_spaceInfo, 1); + m_topLayout->addWidget(m_stopButton); + m_topLayout->addWidget(m_progressTextLabel); + m_topLayout->addWidget(m_progressBar); - setVisible(GeneralSettings::showStatusBar()); + setVisible(GeneralSettings::showStatusBar(), WithoutAnimation); setExtensionsVisible(true); setWhatsThis(xi18nc("@info:whatsthis Statusbar", "This is " @@ -249,7 +252,7 @@ int DolphinStatusBar::zoomLevel() const void DolphinStatusBar::readSettings() { - setVisible(GeneralSettings::showStatusBar()); + setVisible(GeneralSettings::showStatusBar(), WithAnimation); setExtensionsVisible(true); } @@ -345,9 +348,9 @@ void DolphinStatusBar::updateContentsMargins() { if (GeneralSettings::showSpaceInfo()) { // We reduce the outside margin for the flat button so it visually has the same margin as the status bar text label on the other end of the bar. - layout()->setContentsMargins(6, 0, 2, 0); + m_topLayout->setContentsMargins(6, 0, 2, 0); } else { - layout()->setContentsMargins(6, 0, 6, 0); + m_topLayout->setContentsMargins(6, 0, 6, 0); } } @@ -360,4 +363,9 @@ void DolphinStatusBar::paintEvent(QPaintEvent *paintEvent) style()->drawPrimitive(QStyle::PE_PanelStatusBar, &opt, &p, this); } +int DolphinStatusBar::preferredHeight() const +{ + return m_spaceInfo->height(); +} + #include "moc_dolphinstatusbar.cpp" diff --git a/src/statusbar/dolphinstatusbar.h b/src/statusbar/dolphinstatusbar.h index 5cb1c4b604..75a2cdc6f0 100644 --- a/src/statusbar/dolphinstatusbar.h +++ b/src/statusbar/dolphinstatusbar.h @@ -7,8 +7,9 @@ #ifndef DOLPHINSTATUSBAR_H #define DOLPHINSTATUSBAR_H +#include "animatedheightwidget.h" + #include -#include class QUrl; class StatusBarSpaceInfo; @@ -18,6 +19,7 @@ class QToolButton; class QSlider; class QTimer; class KSqueezedTextLabel; +class QHBoxLayout; /** * @brief Represents the statusbar of a Dolphin view. @@ -25,7 +27,7 @@ class KSqueezedTextLabel; * The statusbar allows to show messages, progress * information and space-information of a disk. */ -class DolphinStatusBar : public QWidget +class DolphinStatusBar : public AnimatedHeightWidget { Q_OBJECT @@ -123,6 +125,9 @@ private: void updateContentsMargins(); + /** @see AnimatedHeightWidget::preferredHeight() */ + int preferredHeight() const override; + private: QString m_text; QString m_defaultText; @@ -140,6 +145,8 @@ private: QTimer *m_delayUpdateTimer; QTime m_textTimestamp; + + QHBoxLayout *m_topLayout; }; #endif diff --git a/src/tests/dolphinsearchboxtest.cpp b/src/tests/dolphinsearchboxtest.cpp index 4bfdff76d5..f21a751212 100644 --- a/src/tests/dolphinsearchboxtest.cpp +++ b/src/tests/dolphinsearchboxtest.cpp @@ -46,12 +46,12 @@ void DolphinSearchBoxTest::cleanup() */ void DolphinSearchBoxTest::testTextClearing() { - m_searchBox->show(); + m_searchBox->setVisible(true, WithoutAnimation); QVERIFY(m_searchBox->text().isEmpty()); m_searchBox->setText("xyz"); - m_searchBox->hide(); - m_searchBox->show(); + m_searchBox->setVisible(false, WithoutAnimation); + m_searchBox->setVisible(true, WithoutAnimation); QCOMPARE(m_searchBox->text(), QString("xyz")); QTest::keyClick(m_searchBox, Qt::Key_Escape);