Make obvious when actions trigger selection mode.
settings/viewmodes/viewmodesettings.cpp
settings/viewpropertiesdialog.cpp
settings/viewpropsprogressinfo.cpp
+ selectionmode/actiontexthelper.cpp
views/dolphinfileitemlistwidget.cpp
views/dolphinitemlistview.cpp
views/dolphinnewfilemenuobserver.cpp
search/dolphinfacetswidget.cpp
search/dolphinquery.cpp
search/dolphinsearchbox.cpp
+ selectionmode/actiontexthelper.cpp
selectionmode/actionwithwidget.cpp
selectionmode/backgroundcolorhelper.cpp
- selectionmode/selectionmodebottombar.cpp
- selectionmode/selectionmodetopbar.cpp
+ selectionmode/bottombar.cpp
+ selectionmode/bottombarcontentscontainer.cpp
+ selectionmode/topbar.cpp
settings/general/behaviorsettingspage.cpp
settings/general/configurepreviewplugindialog.cpp
settings/general/confirmationssettingspage.cpp
#include "panels/folders/folderspanel.h"
#include "panels/places/placespanel.h"
#include "panels/terminal/terminalpanel.h"
+#include "selectionmode/actiontexthelper.h"
#include "settings/dolphinsettingsdialog.h"
#include "statusbar/dolphinstatusbar.h"
#include "views/dolphinviewactionhandler.h"
setComponentName(QStringLiteral("dolphin"), QGuiApplication::applicationDisplayName());
setObjectName(QStringLiteral("Dolphin#"));
- setStateConfigGroup("State");
+ // setStateConfigGroup("State");
connect(&DolphinNewFileMenuObserver::instance(), &DolphinNewFileMenuObserver::errorMessage,
this, &DolphinMainWindow::showErrorMessage);
this, &DolphinMainWindow::updateWindowTitle);
setCentralWidget(m_tabWidget);
+ m_actionTextHelper = new SelectionMode::ActionTextHelper(this);
setupActions();
- m_actionHandler = new DolphinViewActionHandler(actionCollection(), this);
+ m_actionHandler = new DolphinViewActionHandler(actionCollection(), m_actionTextHelper, this);
connect(m_actionHandler, &DolphinViewActionHandler::actionBeingHandled, this, &DolphinMainWindow::clearStatusBar);
connect(m_actionHandler, &DolphinViewActionHandler::createDirectoryTriggered, this, &DolphinMainWindow::createDirectory);
connect(m_actionHandler, &DolphinViewActionHandler::setSelectionMode, this, &DolphinMainWindow::slotSetSelectionMode);
void DolphinMainWindow::cut()
{
if (m_activeViewContainer->view()->selectedItems().isEmpty()) {
- m_activeViewContainer->setSelectionModeEnabled(true, actionCollection(), SelectionModeBottomBar::Contents::CutContents);
+ m_activeViewContainer->setSelectionModeEnabled(true, actionCollection(), SelectionMode::BottomBar::Contents::CutContents);
} else {
m_activeViewContainer->view()->cutSelectedItemsToClipboard();
m_activeViewContainer->setSelectionModeEnabled(false);
void DolphinMainWindow::copy()
{
if (m_activeViewContainer->view()->selectedItems().isEmpty()) {
- m_activeViewContainer->setSelectionModeEnabled(true, actionCollection(), SelectionModeBottomBar::Contents::CopyContents);
+ m_activeViewContainer->setSelectionModeEnabled(true, actionCollection(), SelectionMode::BottomBar::Contents::CopyContents);
} else {
m_activeViewContainer->view()->copySelectedItemsToClipboard();
m_activeViewContainer->setSelectionModeEnabled(false);
}
}
-void DolphinMainWindow::slotSetSelectionMode(bool enabled, SelectionModeBottomBar::Contents bottomBarContents)
+void DolphinMainWindow::slotSetSelectionMode(bool enabled, SelectionMode::BottomBar::Contents bottomBarContents)
{
m_activeViewContainer->setSelectionModeEnabled(enabled, actionCollection(), bottomBarContents);
}
void DolphinMainWindow::copyToInactiveSplitView()
{
if (m_activeViewContainer->view()->selectedItems().isEmpty()) {
- m_activeViewContainer->setSelectionModeEnabled(true, actionCollection(), SelectionModeBottomBar::Contents::CopyToOtherViewContents);
+ m_activeViewContainer->setSelectionModeEnabled(true, actionCollection(), SelectionMode::BottomBar::Contents::CopyToOtherViewContents);
} else {
m_tabWidget->copyToInactiveSplitView();
m_activeViewContainer->setSelectionModeEnabled(false);
void DolphinMainWindow::moveToInactiveSplitView()
{
if (m_activeViewContainer->view()->selectedItems().isEmpty()) {
- m_activeViewContainer->setSelectionModeEnabled(true, actionCollection(), SelectionModeBottomBar::Contents::MoveToOtherViewContents);
+ m_activeViewContainer->setSelectionModeEnabled(true, actionCollection(), SelectionMode::BottomBar::Contents::MoveToOtherViewContents);
} else {
m_tabWidget->moveToInactiveSplitView();
m_activeViewContainer->setSelectionModeEnabled(false);
{
const bool checked = !m_activeViewContainer->isSelectionModeEnabled();
- m_activeViewContainer->setSelectionModeEnabled(checked, actionCollection(), SelectionModeBottomBar::Contents::GeneralContents);
+ m_activeViewContainer->setSelectionModeEnabled(checked, actionCollection(), SelectionMode::BottomBar::Contents::GeneralContents);
actionCollection()->action(QStringLiteral("toggle_selection_mode"))->setChecked(checked);
}
"next to each other on the keyboard: <shortcut>Ctrl+X</shortcut>, "
"<shortcut>Ctrl+C</shortcut> and <shortcut>Ctrl+V</shortcut>.</para>");
QAction* cutAction = KStandardAction::cut(this, &DolphinMainWindow::cut, actionCollection());
+ m_actionTextHelper->registerTextWhenNothingIsSelected(cutAction, i18nc("@action", "Cut…"));
cutAction->setWhatsThis(xi18nc("@info:whatsthis cut", "This copies the items "
"in your current selection to the <emphasis>clipboard</emphasis>.<nl/>"
"Use the <emphasis>Paste</emphasis> action afterwards to copy them from "
"the clipboard to a new location. The items will be removed from their "
"initial location.") + cutCopyPastePara);
QAction* copyAction = KStandardAction::copy(this, &DolphinMainWindow::copy, actionCollection());
+ m_actionTextHelper->registerTextWhenNothingIsSelected(copyAction, i18nc("@action", "Copy…"));
copyAction->setWhatsThis(xi18nc("@info:whatsthis copy", "This copies the "
"items in your current selection to the <emphasis>clipboard</emphasis>."
"<nl/>Use the <emphasis>Paste</emphasis> action afterwards to copy them "
QAction* copyToOtherViewAction = actionCollection()->addAction(QStringLiteral("copy_to_inactive_split_view"));
copyToOtherViewAction->setText(i18nc("@action:inmenu", "Copy to Inactive Split View"));
+ m_actionTextHelper->registerTextWhenNothingIsSelected(copyToOtherViewAction, i18nc("@action:inmenu", "Copy to Inactive Split View…"));
copyToOtherViewAction->setWhatsThis(xi18nc("@info:whatsthis Copy", "This copies the selected items from "
"the <emphasis>active</emphasis> view to the inactive split view."));
copyToOtherViewAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
QAction* moveToOtherViewAction = actionCollection()->addAction(QStringLiteral("move_to_inactive_split_view"));
moveToOtherViewAction->setText(i18nc("@action:inmenu", "Move to Inactive Split View"));
+ m_actionTextHelper->registerTextWhenNothingIsSelected(moveToOtherViewAction, i18nc("@action:inmenu", "Move to Inactive Split View…"));
moveToOtherViewAction->setWhatsThis(xi18nc("@info:whatsthis Move", "This moves the selected items from "
"the <emphasis>active</emphasis> view to the inactive split view."));
moveToOtherViewAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-cut")));
QAction* deleteAction = col->action(KStandardAction::name(KStandardAction::DeleteFile));
QAction* cutAction = col->action(KStandardAction::name(KStandardAction::Cut));
QAction* duplicateAction = col->action(QStringLiteral("duplicate")); // see DolphinViewActionHandler
- QAction* addToPlacesAction = col->action(QStringLiteral("add_to_places"));
+ QAction* addToPlacesAction = col->action(QStringLiteral("add_to_places"));
QAction* copyToOtherViewAction = col->action(QStringLiteral("copy_to_inactive_split_view"));
QAction* moveToOtherViewAction = col->action(QStringLiteral("move_to_inactive_split_view"));
QAction* copyLocation = col->action(QString("copy_location"));
duplicateAction->setEnabled(true);
addToPlacesAction->setEnabled(true);
copyLocation->setEnabled(true);
+ // Them triggering selection mode and not directly acting on selected items is signified by adding "…" to their text.
+ m_actionTextHelper->textsWhenNothingIsSelectedEnabled(true);
+
} else {
+ m_actionTextHelper->textsWhenNothingIsSelectedEnabled(false);
stateChanged(QStringLiteral("has_selection"));
QAction* deleteWithTrashShortcut = col->action(QStringLiteral("delete_shortcut")); // see DolphinViewActionHandler
#define DOLPHIN_MAINWINDOW_H
#include "dolphintabwidget.h"
-#include "selectionmode/selectionmodebottombar.h"
+#include "selectionmode/bottombar.h"
#include "config-dolphin.h"
#include <KFileItemActions>
#include <kio/fileundomanager.h>
namespace KIO {
class OpenUrlJob;
}
+namespace SelectionMode {
+ class ActionTextHelper;
+}
/**
* @short Main window for Dolphin.
void updatePasteAction();
/** Calls DolphinViewContainer::setSelectionMode() for m_activeViewContainer. */
- void slotSetSelectionMode(bool enabled, SelectionModeBottomBar::Contents bottomBarContents);
+ void slotSetSelectionMode(bool enabled, SelectionMode::BottomBar::Contents bottomBarContents);
/** Selects all items from the active view. */
void selectAll();
DolphinRemoteEncoding* m_remoteEncoding;
QPointer<DolphinSettingsDialog> m_settingsDialog;
DolphinBookmarkHandler* m_bookmarkHandler;
+ SelectionMode::ActionTextHelper* m_actionTextHelper;
// Members for the toolbar menu that is shown when the menubar is hidden:
QToolButton* m_controlButton;
connect(m_view, &DolphinView::itemCountChanged, this, &DolphinPart::updateStatusBar);
connect(m_view, &DolphinView::selectionChanged, this, &DolphinPart::updateStatusBar);
- m_actionHandler = new DolphinViewActionHandler(actionCollection(), this);
+ m_actionHandler = new DolphinViewActionHandler(actionCollection(), nullptr, this);
m_actionHandler->setCurrentView(m_view);
connect(m_actionHandler, &DolphinViewActionHandler::createDirectoryTriggered, this, &DolphinPart::createDirectory);
#include "filterbar/filterbar.h"
#include "global.h"
#include "search/dolphinsearchbox.h"
-#include "selectionmode/selectionmodetopbar.h"
+#include "selectionmode/topbar.h"
#include "statusbar/dolphinstatusbar.h"
#include "views/viewmodecontroller.h"
#include "views/viewproperties.h"
m_urlNavigatorConnected = nullptr;
}
-void DolphinViewContainer::setSelectionModeEnabled(bool enabled, KActionCollection *actionCollection, SelectionModeBottomBar::Contents bottomBarContents)
+void DolphinViewContainer::setSelectionModeEnabled(bool enabled, KActionCollection *actionCollection, SelectionMode::BottomBar::Contents bottomBarContents)
{
const bool wasEnabled = m_view->selectionMode();
m_view->setSelectionMode(enabled);
setSelectionModeEnabled(false);
});
- m_selectionModeTopBar = new SelectionModeTopBar(this); // will be created hidden
- connect(m_selectionModeTopBar, &SelectionModeTopBar::leaveSelectionModeRequested, this, [this]() {
+ m_selectionModeTopBar = new SelectionMode::TopBar(this); // will be created hidden
+ connect(m_selectionModeTopBar, &SelectionMode::TopBar::leaveSelectionModeRequested, this, [this]() {
setSelectionModeEnabled(false);
});
m_topLayout->addWidget(m_selectionModeTopBar, positionFor.selectionModeTopBar, 0);
}
if (!m_selectionModeBottomBar) {
- m_selectionModeBottomBar = new SelectionModeBottomBar(actionCollection, this);
+ m_selectionModeBottomBar = new SelectionMode::BottomBar(actionCollection, this);
connect(m_view, &DolphinView::selectionChanged, this, [this](const KFileItemList &selection) {
m_selectionModeBottomBar->slotSelectionChanged(selection, m_view->url());
});
- connect(m_selectionModeBottomBar, &SelectionModeBottomBar::error, this, [this](const QString &errorMessage) {
+ connect(m_selectionModeBottomBar, &SelectionMode::BottomBar::error, this, [this](const QString &errorMessage) {
showErrorMessage(errorMessage);
});
- connect(m_selectionModeBottomBar, &SelectionModeBottomBar::leaveSelectionModeRequested, this, [this]() {
+ connect(m_selectionModeBottomBar, &SelectionMode::BottomBar::leaveSelectionModeRequested, this, [this]() {
setSelectionModeEnabled(false);
});
m_topLayout->addWidget(m_selectionModeBottomBar, positionFor.selectionModeBottomBar, 0);
}
m_selectionModeBottomBar->resetContents(bottomBarContents);
- if (bottomBarContents == SelectionModeBottomBar::GeneralContents) {
+ if (bottomBarContents == SelectionMode::BottomBar::GeneralContents) {
m_selectionModeBottomBar->slotSelectionChanged(m_view->selectedItems(), m_view->url());
}
Q_ASSERT((!isEnabled
// We can't assert that the bars are invisible only because the selection mode is disabled because the hide animation might still be playing.
&& (!m_selectionModeBottomBar || !m_selectionModeBottomBar->isEnabled() ||
- !m_selectionModeBottomBar->isVisible() || m_selectionModeBottomBar->contents() == SelectionModeBottomBar::PasteContents))
+ !m_selectionModeBottomBar->isVisible() || m_selectionModeBottomBar->contents() == SelectionMode::BottomBar::PasteContents))
|| ( isEnabled
&& m_selectionModeTopBar && m_selectionModeTopBar->isVisible()
// The bottom bar is either visible or was hidden because it has nothing to show in GeneralContents mode e.g. because no items are selected.
&& m_selectionModeBottomBar
- && (m_selectionModeBottomBar->isVisible() || m_selectionModeBottomBar->contents() == SelectionModeBottomBar::GeneralContents)));
+ && (m_selectionModeBottomBar->isVisible() || m_selectionModeBottomBar->contents() == SelectionMode::BottomBar::GeneralContents)));
return isEnabled;
}
#include "config-dolphin.h"
#include "dolphinurlnavigator.h"
-#include "selectionmode/selectionmodebottombar.h"
+#include "selectionmode/bottombar.h"
#include "views/dolphinview.h"
#include <KFileItem>
class QUrl;
class DolphinSearchBox;
class DolphinStatusBar;
-class SelectionModeTopBar;
+namespace SelectionMode {
+ class TopBar;
+}
/**
* @short Represents a view for the directory content
*/
void disconnectUrlNavigator();
- void setSelectionModeEnabled(bool enabled, KActionCollection *actionCollection = nullptr, SelectionModeBottomBar::Contents bottomBarContents = SelectionModeBottomBar::Contents::GeneralContents);
+ void setSelectionModeEnabled(bool enabled, KActionCollection *actionCollection = nullptr, SelectionMode::BottomBar::Contents bottomBarContents = SelectionMode::BottomBar::Contents::GeneralContents);
bool isSelectionModeEnabled() const;
/**
KMessageWidget* m_messageWidget;
/// A bar shown at the top of the view to signify that selection mode is currently active.
- SelectionModeTopBar *m_selectionModeTopBar;
+ SelectionMode::TopBar *m_selectionModeTopBar;
DolphinView* m_view;
FilterBar* m_filterBar;
/// A bar shown at the bottom of the view whose contents depend on what the user is currently doing.
- SelectionModeBottomBar *m_selectionModeBottomBar;
+ SelectionMode::BottomBar *m_selectionModeBottomBar;
DolphinStatusBar* m_statusBar;
QTimer* m_statusBarTimer; // Triggers a delayed update
--- /dev/null
+/*
+ This file is part of the KDE project
+ SPDX-FileCopyrightText: 2022 Felix Ernst <felixernst@zohomail.eu>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "actiontexthelper.h"
+
+using namespace SelectionMode;
+
+ActionTextHelper::ActionTextHelper(QObject *parent) :
+ QObject(parent)
+{ }
+
+void ActionTextHelper::registerTextWhenNothingIsSelected(QAction *action, QString registeredText)
+{
+ Q_CHECK_PTR(action);
+ m_registeredActionTextChanges.emplace_back(action, registeredText, TextWhenNothingIsSelected);
+}
+
+void ActionTextHelper::textsWhenNothingIsSelectedEnabled(bool enabled)
+{
+ for (auto i = m_registeredActionTextChanges.begin(); i != m_registeredActionTextChanges.end(); ++i) {
+ if (!i->action) {
+ i = m_registeredActionTextChanges.erase(i);
+ continue;
+ }
+ if (enabled && i->textStateOfRegisteredText == TextWhenNothingIsSelected) {
+ QString textWhenSomethingIsSelected = i->action->text();
+ i->action->setText(i->registeredText);
+ i->registeredText = textWhenSomethingIsSelected;
+ i->textStateOfRegisteredText = TextWhenSomethingIsSelected;
+ } else if (!enabled && i->textStateOfRegisteredText == TextWhenSomethingIsSelected) {
+ QString textWhenNothingIsSelected = i->action->text();
+ i->action->setText(i->registeredText);
+ i->registeredText = textWhenNothingIsSelected;
+ i->textStateOfRegisteredText = TextWhenNothingIsSelected;
+ }
+ }
+}
--- /dev/null
+/*
+ This file is part of the KDE project
+ SPDX-FileCopyrightText: 2022 Felix Ernst <felixernst@zohomail.eu>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef ACTIONTEXTHELPER_H
+#define ACTIONTEXTHELPER_H
+
+#include <QAction>
+#include <QPointer>
+#include <QString>
+
+namespace SelectionMode
+{
+
+/**
+ * @brief Helps changing the texts of actions depending on the current selection.
+ */
+class ActionTextHelper : QObject
+{
+public:
+ explicit ActionTextHelper(QObject *parent);
+
+ /**
+ * Changes the text of \a action to \a text whenever textsWhenNothingIsSelectedEnabled(true) is called.
+ * The texts can be changed back by calling textsWhenNothingIsSelectedEnabled(false) is called.
+ * @see textsWhenNothingIsSelectedEnabled()
+ */
+ void registerTextWhenNothingIsSelected(QAction *action, QString registeredText);
+
+ /**
+ * Changes all texts that were registered previously using registerTextWhenNothingIsSelected() to those
+ * registered texts if called with \a enabled == true. Otherwise resets the texts to the original one.
+ */
+ void textsWhenNothingIsSelectedEnabled(bool enabled);
+
+private:
+ enum TextState {
+ TextWhenNothingIsSelected,
+ TextWhenSomethingIsSelected
+ };
+
+ /**
+ * Utility struct to allow switching back and forth between registered actions showing their
+ * distinct texts for when no items are selected or when items are selected.
+ * An example is "Copy" or "Copy…". The latter one is used when nothing is selected and signifies
+ * that it will trigger SelectionMode so items can be selected and then copied.
+ */
+ struct RegisteredActionTextChange {
+ QPointer<QAction> action;
+ QString registeredText;
+ TextState textStateOfRegisteredText;
+
+ RegisteredActionTextChange(QAction *action, QString registeredText, TextState state) :
+ action{action},
+ registeredText{registeredText},
+ textStateOfRegisteredText{state}
+ { };
+ };
+
+ /**
+ * @see RegisteredActionTextChange
+ */
+ std::vector<RegisteredActionTextChange> m_registeredActionTextChanges;
+};
+
+}
+
+#endif // ACTIONTEXTHELPER_H
#include <QPushButton>
#include <QToolButton>
+using namespace SelectionMode;
+
ActionWithWidget::ActionWithWidget(QAction *action) :
m_action{action}
{ }
return m_widget;
}
-QAbstractButton *newButtonForAction(QAction *action, QWidget *parent)
+QAbstractButton *SelectionMode::newButtonForAction(QAction *action, QWidget *parent)
{
Q_CHECK_PTR(action);
Q_ASSERT(!action->isSeparator());
return toolButton;
}
-void copyActionDataToButton(QAbstractButton *button, QAction *action)
+void SelectionMode::copyActionDataToButton(QAbstractButton *button, QAction *action)
{
button->setText(action->text());
button->setIcon(action->icon());
class QAbstractButton;
+namespace SelectionMode
+{
+
/**
* @brief Small wrapper/helper class that contains an action and its widget.
*
*/
void copyActionDataToButton(QAbstractButton *button, QAction *action);
+}
+
#endif // ACTIONWITHWIDGET_H
#include <QtGlobal>
#include <QWidget>
+using namespace SelectionMode;
+
BackgroundColorHelper *BackgroundColorHelper::instance()
{
if (!s_instance) {
class QWidget;
+namespace SelectionMode
+{
+
/**
* @brief A Singleton class for managing the colors of selection mode widgets.
*/
static BackgroundColorHelper *s_instance;
};
+}
+
#endif // BACKGROUNDCOLORHELPER_H
--- /dev/null
+/*
+ This file is part of the KDE project
+ SPDX-FileCopyrightText: 2022 Felix Ernst <felixernst@zohomail.eu>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#include "bottombar.h"
+
+#include "bottombarcontentscontainer.h"
+#include "backgroundcolorhelper.h"
+#include "global.h"
+
+#include <KActionCollection>
+#include <KFileItem>
+#include <KFileItemListProperties>
+#include <KStandardAction>
+
+#include <QGridLayout>
+#include <QResizeEvent>
+#include <QScrollArea>
+#include <QStyle>
+#include <QtGlobal>
+#include <QTimer>
+
+using namespace SelectionMode;
+
+BottomBar::BottomBar(KActionCollection *actionCollection, QWidget *parent) :
+ QWidget{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->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){
+ if (!m_allowedToBeVisible && visible) {
+ return;
+ }
+ setVisibleInternal(visible, WithAnimation);
+ });
+ connect(m_contentsContainer, &BottomBarContentsContainer::leaveSelectionModeRequested, this, &BottomBar::leaveSelectionModeRequested);
+
+ BackgroundColorHelper::instance()->controlBackgroundColor(this);
+}
+
+void BottomBar::setVisible(bool visible, Animated animated)
+{
+ m_allowedToBeVisible = visible;
+ setVisibleInternal(visible, 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.
+ }
+ if (visible && !m_contentsContainer->hasSomethingToShow()) {
+ 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.
+}
+
+void BottomBar::slotSelectionChanged(const KFileItemList &selection, const QUrl &baseUrl)
+{
+ m_contentsContainer->slotSelectionChanged(selection, baseUrl);
+}
+
+void BottomBar::slotSplitTabDisabled()
+{
+ switch (contents()) {
+ case CopyToOtherViewContents:
+ case MoveToOtherViewContents:
+ Q_EMIT leaveSelectionModeRequested();
+ default:
+ return;
+ }
+}
+
+void BottomBar::resetContents(BottomBar::Contents contents)
+{
+ m_contentsContainer->resetContents(contents);
+
+ if (m_allowedToBeVisible) {
+ setVisibleInternal(true, WithAnimation);
+ }
+}
+
+BottomBar::Contents BottomBar::contents() const
+{
+ return m_contentsContainer->contents();
+}
+
+bool BottomBar::eventFilter(QObject *watched, QEvent *event)
+{
+ Q_ASSERT(qobject_cast<QWidget *>(watched)); // This evenfFilter is only implemented for QWidgets.
+
+ switch (event->type()) {
+ case QEvent::ChildAdded:
+ 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)) {
+ setMaximumHeight(sizeHint().height());
+ }
+ });
+ // Fall through.
+ default:
+ return false;
+ }
+}
+
+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);
+ }
+
+ m_contentsContainer->updateForNewWidth();
+
+ return QWidget::resizeEvent(resizeEvent);
+}
--- /dev/null
+/*
+ This file is part of the KDE project
+ SPDX-FileCopyrightText: 2022 Felix Ernst <felixernst@zohomail.eu>
+
+ SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
+*/
+
+#ifndef BOTTOMBAR_H
+#define BOTTOMBAR_H
+
+#include "global.h"
+
+#include <QAction>
+#include <QPointer>
+#include <QPropertyAnimation>
+#include <QWidget>
+
+#include <memory>
+
+class KActionCollection;
+class KFileItemList;
+class QAction;
+class QPushButton;
+class QResizeEvent;
+class QScrollArea;
+class QUrl;
+
+namespace SelectionMode
+{
+ class BottomBarContentsContainer;
+
+/**
+ * A bar used in selection mode that serves various purposes depending on what the user is currently trying to do.
+ *
+ * The Contents enum below gives a rough idea about the different states this bar might have.
+ * The bar is notified of various changes that make changing or updating the content worthwhile.
+ *
+ * The visible contents of the bar are managed in BottomBarContentsContainer. This class serves as a wrapper around it.
+ */
+class BottomBar : public QWidget
+{
+ Q_OBJECT
+
+public:
+ /** The different contents this bar can have. */
+ enum Contents{
+ CopyContents,
+ CopyLocationContents,
+ CopyToOtherViewContents,
+ CutContents,
+ DeleteContents,
+ DuplicateContents,
+ GeneralContents,
+ MoveToOtherViewContents,
+ MoveToTrashContents,
+ PasteContents,
+ RenameContents
+ };
+
+ /**
+ * @param actionCollection the collection this bar retrieves its actions from
+ * @param parent the parent widget. Typically a DolphinViewContainer
+ */
+ explicit BottomBar(KActionCollection *actionCollection, 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.
+ *
+ * This bar might also not show itself when setVisible(true), when context menu actions are supposed to be shown
+ * for the selected items but no items have been selected yet. In that case it will only show itself once items were selected.
+ * @see QWidget::setVisible()
+ */
+ void setVisible(bool visible, Animated animated);
+ using QWidget::setVisible; // Makes sure that the setVisible() declaration above doesn't hide the one from QWidget.
+
+ 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:
+ void slotSelectionChanged(const KFileItemList &selection, const QUrl &baseUrl);
+
+ /** Used to notify the m_selectionModeBottomBar that there is no other ViewContainer in the tab. */
+ void slotSplitTabDisabled();
+
+Q_SIGNALS:
+ /**
+ * Forwards the errors from the KFileItemAction::error() used for contextual actions.
+ */
+ void error(const QString &errorMessage);
+
+ void leaveSelectionModeRequested();
+
+protected:
+ /** Is installed on an internal widget to make sure that the height of the bar is adjusted to its contents. */
+ bool eventFilter(QObject *watched, QEvent *event) override;
+
+ /** Adapts the way the contents of this bar are displayed based on the available width. */
+ void resizeEvent(QResizeEvent *resizeEvent) override;
+
+private:
+ /**
+ * Identical to SelectionModeBottomBar::setVisible() but doesn't change m_allowedToBeVisible.
+ * @see SelectionModeBottomBar::setVisible()
+ * @see m_allowedToBeVisible
+ */
+ void setVisibleInternal(bool visible, Animated animated);
+
+private:
+ /** The only direct child widget of this bar. */
+ QScrollArea *m_scrollArea;
+ /** The only direct grandchild of this bar. */
+ BottomBarContentsContainer *m_contentsContainer;
+
+ /** Remembers if this bar was setVisible(true) or setVisible(false) the last time.
+ * 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<QPropertyAnimation> m_heightAnimation;
+};
+
+}
+
+#endif // BOTTOMBAR_H
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
-#include "selectionmodebottombar.h"
+#include "bottombarcontentscontainer.h"
-#include "backgroundcolorhelper.h"
#include "dolphin_generalsettings.h"
#include "dolphincontextmenu.h"
#include "dolphinmainwindow.h"
#include "dolphinremoveaction.h"
-#include "global.h"
#include "kitemviews/kfileitemlisttostring.h"
-#include <KActionCollection>
-#include <KColorScheme>
-#include <KFileItem>
-#include <KFileItemListProperties>
#include <KLocalizedString>
-#include <KStandardAction>
-#include <QFontMetrics>
-#include <QGuiApplication>
#include <QHBoxLayout>
#include <QLabel>
-#include <QLayout>
#include <QMenu>
#include <QPushButton>
-#include <QResizeEvent>
-#include <QScrollArea>
-#include <QStyle>
#include <QToolButton>
-#include <QtGlobal>
#include <QVBoxLayout>
#include <unordered_set>
-SelectionModeBottomBar::SelectionModeBottomBar(KActionCollection *actionCollection, QWidget *parent) :
+using namespace SelectionMode;
+
+BottomBarContentsContainer::BottomBarContentsContainer(KActionCollection *actionCollection, QWidget *parent) :
QWidget{parent},
m_actionCollection{actionCollection}
{
- // 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.)
- 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);
- contentsContainer->installEventFilter(this); // Adjusts the height of this bar to the height of the contentsContainer
-
- BackgroundColorHelper::instance()->controlBackgroundColor(this);
-
// We will mostly interact with m_layout when changing the contents and not care about the other internal hierarchy.
- m_layout = new QHBoxLayout(contentsContainer);
-}
-
-void SelectionModeBottomBar::setVisible(bool visible, Animated animated)
-{
- m_allowedToBeVisible = visible;
- setVisibleInternal(visible, animated);
-}
-
-void SelectionModeBottomBar::setVisibleInternal(bool visible, Animated animated)
-{
- Q_ASSERT_X(animated == WithAnimation, "SelectionModeBottomBar::setVisible", "This wasn't implemented.");
- if (!visible && m_contents == PasteContents) {
- return; // The bar with PasteContents should not be hidden or users might not know how to paste what they just copied.
- // Set m_contents to anything else to circumvent this prevention mechanism.
- }
- if (visible && m_contents == GeneralContents && !m_internalContextMenu) {
- 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 SelectionModeBottomBar::sizeHint() const
-{
- return QSize{1, m_layout->parentWidget()->sizeHint().height()};
- // 1 as width because this widget should never be the reason the DolphinViewContainer is made wider.
-}
-
-void SelectionModeBottomBar::slotSelectionChanged(const KFileItemList &selection, const QUrl &baseUrl)
-{
- if (m_contents == GeneralContents) {
- auto contextActions = contextActionsFor(selection, baseUrl);
- m_generalBarActions.clear();
- if (contextActions.empty()) {
- if (isVisibleTo(parentWidget())) {
- setVisibleInternal(false, WithAnimation);
- }
- } else {
- for (auto i = contextActions.begin(); i != contextActions.end(); ++i) {
- m_generalBarActions.emplace_back(ActionWithWidget{*i});
- }
- resetContents(GeneralContents);
-
- if (m_allowedToBeVisible) {
- setVisibleInternal(true, WithAnimation);
- }
- }
- }
- updateMainActionButton(selection);
-}
-
-void SelectionModeBottomBar::slotSplitTabDisabled()
-{
- switch (m_contents) {
- case CopyToOtherViewContents:
- case MoveToOtherViewContents:
- Q_EMIT leaveSelectionModeRequested();
- default:
- return;
- }
+ m_layout = new QHBoxLayout(this);
}
-void SelectionModeBottomBar::resetContents(SelectionModeBottomBar::Contents contents)
+void BottomBarContentsContainer::resetContents(BottomBar::Contents contents)
{
emptyBarContents();
Q_CHECK_PTR(m_actionCollection);
m_contents = contents;
switch (contents) {
- case CopyContents:
- addCopyContents();
- break;
- case CopyLocationContents:
- addCopyLocationContents();
- break;
- case CopyToOtherViewContents:
- addCopyToOtherViewContents();
- break;
- case CutContents:
- addCutContents();
- break;
- case DeleteContents:
- addDeleteContents();
- break;
- case DuplicateContents:
- addDuplicateContents();
- break;
- case GeneralContents:
- addGeneralContents();
- break;
- case PasteContents:
- addPasteContents();
- break;
- case MoveToOtherViewContents:
- addMoveToOtherViewContents();
- break;
- case MoveToTrashContents:
- addMoveToTrashContents();
- break;
- case RenameContents:
+ case BottomBar::CopyContents:
+ return addCopyContents();
+ case BottomBar::CopyLocationContents:
+ return addCopyLocationContents();
+ case BottomBar::CopyToOtherViewContents:
+ return addCopyToOtherViewContents();
+ case BottomBar::CutContents:
+ return addCutContents();
+ case BottomBar::DeleteContents:
+ return addDeleteContents();
+ case BottomBar::DuplicateContents:
+ return addDuplicateContents();
+ case BottomBar::GeneralContents:
+ return addGeneralContents();
+ case BottomBar::PasteContents:
+ return addPasteContents();
+ case BottomBar::MoveToOtherViewContents:
+ return addMoveToOtherViewContents();
+ case BottomBar::MoveToTrashContents:
+ return addMoveToTrashContents();
+ case BottomBar::RenameContents:
return addRenameContents();
}
-
- if (m_allowedToBeVisible) {
- setVisibleInternal(true, WithAnimation);
- }
}
-bool SelectionModeBottomBar::eventFilter(QObject *watched, QEvent *event)
+void BottomBarContentsContainer::updateForNewWidth()
{
- Q_ASSERT(qobject_cast<QWidget *>(watched)); // This evenfFilter is only implemented for QWidgets.
-
- switch (event->type()) {
- case QEvent::ChildAdded:
- 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)) {
- setMaximumHeight(sizeHint().height());
- }
- });
- // Fall through.
- default:
- return false;
- }
-}
-
-void SelectionModeBottomBar::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);
- }
- m_layout->parentWidget()->setFixedWidth(resizeEvent->size().width());
-
- if (m_contents == GeneralContents) {
+ if (m_contents == BottomBar::GeneralContents) {
Q_ASSERT(m_overflowButton);
if (unusedSpace() < 0) {
// The bottom bar is overflowing! We need to hide some of the widgets.
// Hide the leading explanation if it doesn't fit. The buttons are labeled clear enough that this shouldn't be a big UX problem.
updateExplanatoryLabelVisibility();
- return QWidget::resizeEvent(resizeEvent);
}
-void SelectionModeBottomBar::addCopyContents()
+void BottomBarContentsContainer::slotSelectionChanged(const KFileItemList &selection, const QUrl &baseUrl)
+{
+ if (m_contents == BottomBar::GeneralContents) {
+ auto contextActions = contextActionsFor(selection, baseUrl);
+ m_generalBarActions.clear();
+ if (contextActions.empty()) {
+ Q_ASSERT(qobject_cast<BottomBar *>(parentWidget()->parentWidget()->parentWidget()));
+ if (isVisibleTo(parentWidget()->parentWidget()->parentWidget()->parentWidget())) { // is the bar visible
+ Q_EMIT barVisibilityChangeRequested(false);
+ }
+ } else {
+ for (auto i = contextActions.begin(); i != contextActions.end(); ++i) {
+ m_generalBarActions.emplace_back(ActionWithWidget{*i});
+ }
+ resetContents(BottomBar::GeneralContents);
+
+ Q_EMIT barVisibilityChangeRequested(true);
+ }
+ }
+ updateMainActionButton(selection);
+}
+
+void BottomBarContentsContainer::addCopyContents()
{
m_explanatoryLabel = new QLabel(i18nc("@info explaining the next step in a process", "Select the files and folders that should be copied."), this);
m_explanatoryLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
// i18n: Aborts the current step-by-step process to copy files by leaving the selection mode.
auto *cancelButton = new QPushButton(i18nc("@action:button", "Abort Copying"), this);
- connect(cancelButton, &QAbstractButton::clicked, this, &SelectionModeBottomBar::leaveSelectionModeRequested);
+ connect(cancelButton, &QAbstractButton::clicked, this, &BottomBarContentsContainer::leaveSelectionModeRequested);
m_layout->addWidget(cancelButton);
auto *copyButton = new QPushButton(this);
// We claim to have PasteContents already so triggering the copy action next won't instantly hide the bottom bar.
connect(copyButton, &QAbstractButton::clicked, [this]() {
if (GeneralSettings::showPasteBarAfterCopying()) {
- m_contents = Contents::PasteContents;
+ m_contents = BottomBar::Contents::PasteContents;
}
});
// Connect the copy action as a second step.
// Finally connect the lambda that actually changes the contents to the PasteContents.
connect(copyButton, &QAbstractButton::clicked, [this]() {
if (GeneralSettings::showPasteBarAfterCopying()) {
- resetContents(Contents::PasteContents); // resetContents() needs to be connected last because
+ resetContents(BottomBar::Contents::PasteContents); // resetContents() needs to be connected last because
// it instantly deletes the button and then the other slots won't be called.
}
Q_EMIT leaveSelectionModeRequested();
m_layout->addWidget(copyButton);
}
-void SelectionModeBottomBar::addCopyLocationContents()
+void BottomBarContentsContainer::addCopyLocationContents()
{
m_explanatoryLabel = new QLabel(i18nc("@info explaining the next step in a process", "Select one file or folder whose location should be copied."), this);
m_explanatoryLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
// i18n: Aborts the current step-by-step process to copy the location of files by leaving the selection mode.
auto *cancelButton = new QPushButton(i18nc("@action:button", "Abort Copying"), this);
- connect(cancelButton, &QAbstractButton::clicked, this, &SelectionModeBottomBar::leaveSelectionModeRequested);
+ connect(cancelButton, &QAbstractButton::clicked, this, &BottomBarContentsContainer::leaveSelectionModeRequested);
m_layout->addWidget(cancelButton);
auto *copyLocationButton = new QPushButton(this);
m_layout->addWidget(copyLocationButton);
}
-void SelectionModeBottomBar::addCopyToOtherViewContents()
+void BottomBarContentsContainer::addCopyToOtherViewContents()
{
// i18n: "Copy over" refers to copying to the other split view area that is currently visible to the user.
m_explanatoryLabel = new QLabel(i18nc("@info explaining the next step in a process", "Select the files and folders that should be copied over."), this);
// i18n: Aborts the current step-by-step process to copy the location of files by leaving the selection mode.
auto *cancelButton = new QPushButton(i18nc("@action:button", "Abort Copying"), this);
- connect(cancelButton, &QAbstractButton::clicked, this, &SelectionModeBottomBar::leaveSelectionModeRequested);
+ connect(cancelButton, &QAbstractButton::clicked, this, &BottomBarContentsContainer::leaveSelectionModeRequested);
m_layout->addWidget(cancelButton);
auto *copyToOtherViewButton = new QPushButton(this);
m_layout->addWidget(copyToOtherViewButton);
}
-void SelectionModeBottomBar::addCutContents()
+void BottomBarContentsContainer::addCutContents()
{
m_explanatoryLabel = new QLabel(i18nc("@info explaining the next step in a process", "Select the files and folders that should be cut."), this);
m_explanatoryLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
// i18n: Aborts the current step-by-step process to cut files by leaving the selection mode.
auto *cancelButton = new QPushButton(i18nc("@action:button", "Abort Cutting"), this);
- connect(cancelButton, &QAbstractButton::clicked, this, &SelectionModeBottomBar::leaveSelectionModeRequested);
+ connect(cancelButton, &QAbstractButton::clicked, this, &BottomBarContentsContainer::leaveSelectionModeRequested);
m_layout->addWidget(cancelButton);
auto *cutButton = new QPushButton(this);
// We claim to have PasteContents already so triggering the cut action next won't instantly hide the bottom bar.
connect(cutButton, &QAbstractButton::clicked, [this]() {
if (GeneralSettings::showPasteBarAfterCopying()) {
- m_contents = Contents::PasteContents;
+ m_contents = BottomBar::Contents::PasteContents;
}
});
// Connect the cut action as a second step.
// Finally connect the lambda that actually changes the contents to the PasteContents.
connect(cutButton, &QAbstractButton::clicked, [this](){
if (GeneralSettings::showPasteBarAfterCopying()) {
- resetContents(Contents::PasteContents); // resetContents() needs to be connected last because
+ resetContents(BottomBar::Contents::PasteContents); // resetContents() needs to be connected last because
// it instantly deletes the button and then the other slots won't be called.
}
Q_EMIT leaveSelectionModeRequested();
m_layout->addWidget(cutButton);
}
-void SelectionModeBottomBar::addDeleteContents()
+void BottomBarContentsContainer::addDeleteContents()
{
m_explanatoryLabel = new QLabel(i18nc("@info explaining the next step in a process", "Select the files and folders that should be permanently deleted."), this);
m_explanatoryLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
// i18n: Aborts the current step-by-step process to delete files by leaving the selection mode.
auto *cancelButton = new QPushButton(i18nc("@action:button", "Abort"), this);
- connect(cancelButton, &QAbstractButton::clicked, this, &SelectionModeBottomBar::leaveSelectionModeRequested);
+ connect(cancelButton, &QAbstractButton::clicked, this, &BottomBarContentsContainer::leaveSelectionModeRequested);
m_layout->addWidget(cancelButton);
auto *deleteButton = new QPushButton(this);
m_layout->addWidget(deleteButton);
}
-void SelectionModeBottomBar::addDuplicateContents()
+void BottomBarContentsContainer::addDuplicateContents()
{
m_explanatoryLabel = new QLabel(i18nc("@info explaining the next step in a process", "Select the files and folders that should be duplicated here."), this);
m_explanatoryLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
// i18n: Aborts the current step-by-step process to duplicate files by leaving the selection mode.
auto *cancelButton = new QPushButton(i18nc("@action:button", "Abort Duplicating"), this);
- connect(cancelButton, &QAbstractButton::clicked, this, &SelectionModeBottomBar::leaveSelectionModeRequested);
+ connect(cancelButton, &QAbstractButton::clicked, this, &BottomBarContentsContainer::leaveSelectionModeRequested);
m_layout->addWidget(cancelButton);
auto *duplicateButton = new QPushButton(this);
m_layout->addWidget(duplicateButton);
}
-void SelectionModeBottomBar::addGeneralContents()
+void BottomBarContentsContainer::addGeneralContents()
{
if (!m_overflowButton) {
m_overflowButton = new QToolButton{this};
}
}
-void SelectionModeBottomBar::addMoveToOtherViewContents()
+void BottomBarContentsContainer::addMoveToOtherViewContents()
{
// i18n: "Move over" refers to moving to the other split view area that is currently visible to the user.
m_explanatoryLabel = new QLabel(i18nc("@info explaining the next step in a process", "Select the files and folders that should be moved over."), this);
// i18n: Aborts the current step-by-step process to copy the location of files by leaving the selection mode.
auto *cancelButton = new QPushButton(i18nc("@action:button", "Abort Moving"), this);
- connect(cancelButton, &QAbstractButton::clicked, this, &SelectionModeBottomBar::leaveSelectionModeRequested);
+ connect(cancelButton, &QAbstractButton::clicked, this, &BottomBarContentsContainer::leaveSelectionModeRequested);
m_layout->addWidget(cancelButton);
auto *moveToOtherViewButton = new QPushButton(this);
m_layout->addWidget(moveToOtherViewButton);
}
-void SelectionModeBottomBar::addMoveToTrashContents()
+void BottomBarContentsContainer::addMoveToTrashContents()
{
m_explanatoryLabel = new QLabel(i18nc("@info explaining the next step in a process", "Select the files and folders that should be moved to the Trash."), this);
m_explanatoryLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
// i18n: Aborts the current step-by-step process of moving files to the trash by leaving the selection mode.
auto *cancelButton = new QPushButton(i18nc("@action:button", "Abort"), this);
- connect(cancelButton, &QAbstractButton::clicked, this, &SelectionModeBottomBar::leaveSelectionModeRequested);
+ connect(cancelButton, &QAbstractButton::clicked, this, &BottomBarContentsContainer::leaveSelectionModeRequested);
m_layout->addWidget(cancelButton);
auto *moveToTrashButton = new QPushButton(this);
m_layout->addWidget(moveToTrashButton);
}
-void SelectionModeBottomBar::addPasteContents()
+void BottomBarContentsContainer::addPasteContents()
{
m_explanatoryLabel = new QLabel(xi18n("<para>The selected files and folders were added to the Clipboard. "
"Now the <emphasis>Paste</emphasis> action can be used to transfer them from the Clipboard "
/** We are in "PasteContents" mode which means hiding the bottom bar is impossible.
* So we first have to claim that we have different contents before requesting to leave selection mode. */
auto actuallyLeaveSelectionMode = [this]() {
- m_contents = Contents::CopyLocationContents;
+ m_contents = BottomBar::Contents::CopyLocationContents;
Q_EMIT leaveSelectionModeRequested();
};
m_explanatoryLabel->setMaximumHeight(pasteButton->sizeHint().height() + dismissButton->sizeHint().height() + m_explanatoryLabel->fontMetrics().height());
}
-void SelectionModeBottomBar::addRenameContents()
+void BottomBarContentsContainer::addRenameContents()
{
m_explanatoryLabel = new QLabel(i18nc("@info explains the next step in a process", "Select the file or folder that should be renamed.\nBulk renaming is possible when multiple items are selected."), this);
m_explanatoryLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
// i18n: Aborts the current step-by-step process to delete files by leaving the selection mode.
auto *cancelButton = new QPushButton(i18nc("@action:button", "Stop Renaming"), this);
- connect(cancelButton, &QAbstractButton::clicked, this, &SelectionModeBottomBar::leaveSelectionModeRequested);
+ connect(cancelButton, &QAbstractButton::clicked, this, &BottomBarContentsContainer::leaveSelectionModeRequested);
m_layout->addWidget(cancelButton);
auto *renameButton = new QPushButton(this);
m_layout->addWidget(renameButton);
}
-void SelectionModeBottomBar::emptyBarContents()
+void BottomBarContentsContainer::emptyBarContents()
{
QLayoutItem *child;
while ((child = m_layout->takeAt(0)) != nullptr) {
}
}
-std::vector<QAction *> SelectionModeBottomBar::contextActionsFor(const KFileItemList& selectedItems, const QUrl& baseUrl)
+std::vector<QAction *> BottomBarContentsContainer::contextActionsFor(const KFileItemList& selectedItems, const QUrl& baseUrl)
{
if (selectedItems.isEmpty()) {
// There are no contextual actions to show for these items.
if (!m_fileItemActions) {
m_fileItemActions = new KFileItemActions(this);
m_fileItemActions->setParentWidget(dolphinMainWindow);
- connect(m_fileItemActions, &KFileItemActions::error, this, &SelectionModeBottomBar::error);
+ connect(m_fileItemActions, &KFileItemActions::error, this, &BottomBarContentsContainer::error);
}
m_internalContextMenu = std::make_unique<DolphinContextMenu>(dolphinMainWindow, selectedItems.constFirst(), selectedItems, baseUrl, m_fileItemActions);
auto internalContextMenuActions = m_internalContextMenu->actions();
return contextActions;
}
-int SelectionModeBottomBar::unusedSpace() const
+int BottomBarContentsContainer::unusedSpace() const
{
int sumOfPreferredWidths = m_layout->contentsMargins().left() + m_layout->contentsMargins().right();
if (m_overflowButton) {
}
sumOfPreferredWidths += m_layout->itemAt(i)->sizeHint().width() + m_layout->spacing();
}
- return width() - sumOfPreferredWidths - 20; // We consider all space used when there are only 20 pixels left
- // so there is some room to breath and not too much wonkyness while resizing.
+ Q_ASSERT(qobject_cast<BottomBar *>(parentWidget()->parentWidget()->parentWidget()));
+ const int totalBarWidth = parentWidget()->parentWidget()->parentWidget()->width();
+ return totalBarWidth - sumOfPreferredWidths - 20; // We consider all space used when there are only 20 pixels left
+ // so there is some room to breath and not too much wonkyness while resizing.
}
-void SelectionModeBottomBar::updateExplanatoryLabelVisibility()
+void BottomBarContentsContainer::updateExplanatoryLabelVisibility()
{
if (!m_explanatoryLabel) {
return;
}
}
-void SelectionModeBottomBar::updateMainActionButton(const KFileItemList& selection)
+void BottomBarContentsContainer::updateMainActionButton(const KFileItemList& selection)
{
if (!m_mainAction.widget()) {
return;
QString buttonText;
switch (m_contents) {
- case CopyContents:
+ case BottomBar::CopyContents:
buttonText = i18ncp("@action A more elaborate and clearly worded version of the Copy action",
"Copy %2 to the Clipboard", "Copy %2 to the Clipboard", selection.count(),
fileItemListToString(selection, fontMetrics.averageCharWidth() * 20, fontMetrics));
break;
- case CopyLocationContents:
+ case BottomBar::CopyLocationContents:
buttonText = i18ncp("@action A more elaborate and clearly worded version of the Copy Location action",
"Copy the Location of %2 to the Clipboard", "Copy the Location of %2 to the Clipboard", selection.count(),
fileItemListToString(selection, fontMetrics.averageCharWidth() * 20, fontMetrics));
break;
- case CutContents:
+ case BottomBar::CutContents:
buttonText = i18ncp("@action A more elaborate and clearly worded version of the Cut action",
"Cut %2 to the Clipboard", "Cut %2 to the Clipboard", selection.count(),
fileItemListToString(selection, fontMetrics.averageCharWidth() * 20, fontMetrics));
break;
- case DeleteContents:
+ case BottomBar::DeleteContents:
buttonText = i18ncp("@action A more elaborate and clearly worded version of the Delete action",
"Permanently Delete %2", "Permanently Delete %2", selection.count(),
fileItemListToString(selection, fontMetrics.averageCharWidth() * 20, fontMetrics));
break;
- case DuplicateContents:
+ case BottomBar::DuplicateContents:
buttonText = i18ncp("@action A more elaborate and clearly worded version of the Duplicate action",
"Duplicate %2", "Duplicate %2", selection.count(),
fileItemListToString(selection, fontMetrics.averageCharWidth() * 20, fontMetrics));
break;
- case MoveToTrashContents:
+ case BottomBar::MoveToTrashContents:
buttonText = i18ncp("@action A more elaborate and clearly worded version of the Trash action",
"Move %2 to the Trash", "Move %2 to the Trash", selection.count(),
fileItemListToString(selection, fontMetrics.averageCharWidth() * 20, fontMetrics));
break;
- case RenameContents:
+ case BottomBar::RenameContents:
buttonText = i18ncp("@action A more elaborate and clearly worded version of the Rename action",
"Rename %2", "Rename %2", selection.count(),
fileItemListToString(selection, fontMetrics.averageCharWidth() * 20, fontMetrics));
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
-#ifndef SELECTIONMODEBOTTOMBAR_H
-#define SELECTIONMODEBOTTOMBAR_H
+#ifndef BOTTOMBARCONTENTSCONTAINER_H
+#define BOTTOMBARCONTENTSCONTAINER_H
#include "actionwithwidget.h"
-#include "global.h"
+#include "bottombar.h"
-#include <QAction>
#include <QPointer>
-#include <QPropertyAnimation>
+#include <QToolButton>
#include <QWidget>
-#include <memory>
-
class DolphinContextMenu;
class KActionCollection;
class KFileItemActions;
class KFileItemList;
-class QAbstractButton;
-class QAction;
-class QFontMetrics;
class QHBoxLayout;
class QLabel;
-class QPushButton;
-class QResizeEvent;
-class QToolButton;
class QUrl;
+namespace SelectionMode
+{
+
/**
- * A bar mainly used in selection mode that serves various purposes depending on what the user is currently trying to do.
+ * @brief An internal widget of BottomBar that controls the visible contents/widgets on it.
*
- * The Contents enum below gives a rough idea about the different states this bar might have.
- * The bar is notified of various changes that make changing or updating the content worthwhile.
+ * This class should only be interacted with from the BottomBar class.
+ * @see BottomBar
*/
-class SelectionModeBottomBar : public QWidget
+class BottomBarContentsContainer : public QWidget
{
Q_OBJECT
public:
- /** The different contents this bar can have. */
- enum Contents{
- CopyContents,
- CopyLocationContents,
- CopyToOtherViewContents,
- CutContents,
- DeleteContents,
- DuplicateContents,
- GeneralContents,
- MoveToOtherViewContents,
- MoveToTrashContents,
- PasteContents,
- RenameContents
- };
-
/**
- * Default constructor
+ * @param actionCollection the collection where the actions for the contents are retrieved from
+ * @param parent the parent widget. Typically a ScrollView within the BottomBar
*/
- explicit SelectionModeBottomBar(KActionCollection *actionCollection, QWidget *parent);
+ explicit BottomBarContentsContainer(KActionCollection *actionCollection, 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.
- *
- * This bar might also not show itself when setVisible(true), when context menu actions are supposed to be shown
- * for the selected items but no items have been selected yet. In that case it will only show itself once items were selected.
- * @see QWidget::setVisible()
- */
- void setVisible(bool visible, Animated animated);
- using QWidget::setVisible; // Makes sure that the setVisible() declaration above doesn't hide the one from QWidget.
-
- void resetContents(Contents contents);
- inline Contents contents() const
+ void resetContents(BottomBar::Contents contents);
+ inline BottomBar::Contents contents() const
{
return m_contents;
};
- /** @returns a width of 1 to make sure that this bar never causes side panels to shrink. */
- QSize sizeHint() const override;
+ inline bool hasSomethingToShow() {
+ return contents() != BottomBar::GeneralContents || m_internalContextMenu;
+ }
+
+ void updateForNewWidth();
public Q_SLOTS:
void slotSelectionChanged(const KFileItemList &selection, const QUrl &baseUrl);
- /** Used to notify the m_selectionModeBottomBar that there is no other ViewContainer in the tab. */
- void slotSplitTabDisabled();
-
Q_SIGNALS:
/**
* Forwards the errors from the KFileItemAction::error() used for contextual actions.
*/
void error(const QString &errorMessage);
- void leaveSelectionModeRequested();
-
-protected:
- /** Is installed on an internal widget to make sure that the height of the bar is adjusted to its contents. */
- bool eventFilter(QObject *watched, QEvent *event) override;
-
- /** Adapts the way the contents of this bar are displayed based on the available width. */
- void resizeEvent(QResizeEvent *resizeEvent) override;
-
-private:
/**
- * Identical to SelectionModeBottomBar::setVisible() but doesn't change m_allowedToBeVisible.
- * @see SelectionModeBottomBar::setVisible()
- * @see m_allowedToBeVisible
+ * Sometimes the contents see no reason to be visible and request the bar to be hidden instead which emits this signal.
+ * This can later change e.g. because the user selected items. Then this signal is used to request showing of the bar.
*/
- void setVisibleInternal(bool visible, Animated animated);
+ void barVisibilityChangeRequested(bool visible);
+ void leaveSelectionModeRequested();
+
+private:
void addCopyContents();
void addCopyLocationContents();
void addCopyToOtherViewContents();
/// The actionCollection from which the actions for this bar are retrieved.
KActionCollection *m_actionCollection;
/// Describes the current contents of the bar.
- Contents m_contents;
+ BottomBar::Contents m_contents;
/** The layout all the buttons and labels are added to.
* Do not confuse this with layout() because we do have a QScrollView in between this widget and m_layout. */
QHBoxLayout *m_layout;
- /** Remembers if this bar was setVisible(true) or setVisible(false) the last time.
- * 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<QPropertyAnimation> m_heightAnimation;
-
/// The info label used for some of the BarContents. Is hidden for narrow widths.
QPointer<QLabel> m_explanatoryLabel;
};
-#endif // SELECTIONMODEBOTTOMBAR_H
+}
+
+#endif // BOTTOMBARCONTENTSCONTAINER_H
#include <QProxyStyle>
+namespace SelectionMode
+{
+
/**
- * @todo write docs
+ * @brief A simple proxy style to temporarily make single click select and not activate
+ *
+ * @see QProxyStyle
*/
class SingleClickSelectionProxyStyle : public QProxyStyle
{
}
};
+}
+
#endif // SINGLECLICKSELECTIONPROXYSTYLE_H
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
-#include "selectionmodetopbar.h"
+#include "topbar.h"
#include "backgroundcolorhelper.h"
#include <QStyle>
#include <QtGlobal>
-SelectionModeTopBar::SelectionModeTopBar(QWidget *parent) :
+using namespace SelectionMode;
+
+TopBar::TopBar(QWidget *parent) :
QWidget{parent}
{
// Showing of this widget is normally animated. We hide it for now and make it small.
m_closeButton->setAccessibleName(m_closeButton->toolTip());
m_closeButton->setFlat(true);
connect(m_closeButton, &QAbstractButton::pressed,
- this, &SelectionModeTopBar::leaveSelectionModeRequested);
+ this, &TopBar::leaveSelectionModeRequested);
QHBoxLayout *layout = new QHBoxLayout(contentsContainer);
auto contentsMargins = layout->contentsMargins();
layout->addWidget(m_closeButton);
}
-void SelectionModeTopBar::setVisible(bool visible, Animated animated)
+void TopBar::setVisible(bool visible, Animated animated)
{
Q_ASSERT_X(animated == WithAnimation, "SelectionModeTopBar::setVisible", "This wasn't implemented.");
m_heightAnimation->start(QAbstractAnimation::DeleteWhenStopped);
}
-void SelectionModeTopBar::resizeEvent(QResizeEvent */* resizeEvent */)
+void TopBar::resizeEvent(QResizeEvent */* resizeEvent */)
{
updateLabelString();
}
-void SelectionModeTopBar::updateLabelString()
+void TopBar::updateLabelString()
{
QFontMetrics fontMetrics = m_label->fontMetrics();
if (fontMetrics.horizontalAdvance(m_fullLabelString) + m_closeButton->sizeHint().width() + style()->pixelMetric(QStyle::PM_LayoutLeftMargin) * 2 + style()->pixelMetric(QStyle::PM_LayoutRightMargin) * 2 < width()) {
class QResizeEvent;
class QShowEvent;
+namespace SelectionMode
+{
+
/**
- * @todo write docs
+ * @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 SelectionModeTopBar : public QWidget
+class TopBar : public QWidget
{
Q_OBJECT
public:
- SelectionModeTopBar(QWidget *parent);
+ TopBar(QWidget *parent);
/**
* Plays a show or hide animation while changing visibility.
QPointer<QPropertyAnimation> m_heightAnimation;
};
+}
+
#endif // SELECTIONMODETOPBAR_H
void DolphinView::setSelectionMode(const bool enabled)
{
if (enabled) {
- m_proxyStyle = std::make_unique<SingleClickSelectionProxyStyle>();
+ m_proxyStyle = std::make_unique<SelectionMode::SingleClickSelectionProxyStyle>();
setStyle(m_proxyStyle.get());
m_view->setStyle(m_proxyStyle.get());
} else {
#include "dolphindebug.h"
#include "kitemviews/kfileitemlisttostring.h"
#include "kitemviews/kfileitemmodel.h"
+#include "selectionmode/actiontexthelper.h"
#include "settings/viewpropertiesdialog.h"
#include "views/zoomlevelinfo.h"
#include "kconfig_version.h"
#include <QMenu>
#include <QPointer>
-DolphinViewActionHandler::DolphinViewActionHandler(KActionCollection* collection, QObject* parent) :
+DolphinViewActionHandler::DolphinViewActionHandler(KActionCollection* collection, SelectionMode::ActionTextHelper* actionTextHelper, QObject* parent) :
QObject(parent),
m_actionCollection(collection),
m_currentView(nullptr),
m_visibleRoles()
{
Q_ASSERT(m_actionCollection);
- createActions();
+ createActions(actionTextHelper);
}
void DolphinViewActionHandler::setCurrentView(DolphinView* view)
return m_currentView;
}
-void DolphinViewActionHandler::createActions()
+void DolphinViewActionHandler::createActions(SelectionMode::ActionTextHelper *actionTextHelper)
{
// This action doesn't appear in the GUI, it's for the shortcut only.
// KNewFileMenu takes care of the GUI stuff.
m_actionCollection->setDefaultShortcuts(copyPathAction, {Qt::CTRL | Qt::ALT | Qt::Key_C});
connect(copyPathAction, &QAction::triggered, this, &DolphinViewActionHandler::slotCopyPath);
+ if (actionTextHelper) {
+ actionTextHelper->registerTextWhenNothingIsSelected(trashAction, i18nc("@action:inmenu File", "Move to Trash…"));
+ actionTextHelper->registerTextWhenNothingIsSelected(deleteAction, i18nc("@action:inmenu File", "Delete…"));
+ actionTextHelper->registerTextWhenNothingIsSelected(duplicateAction, i18nc("@action:inmenu File", "Duplicate Here…"));
+ actionTextHelper->registerTextWhenNothingIsSelected(copyPathAction, i18nc("@action:incontextmenu", "Copy Location…"));
+ }
+
// This menu makes sure that users who don't know how to open a context menu and haven't
// figured out how to enable the menu bar can still perform basic file manipulation.
// This only works if they know how to select a file.
void DolphinViewActionHandler::slotRename()
{
if (m_currentView->selectedItemsCount() == 0) {
- Q_EMIT setSelectionMode(true, SelectionModeBottomBar::Contents::RenameContents);
+ Q_EMIT setSelectionMode(true, SelectionMode::BottomBar::Contents::RenameContents);
} else {
Q_EMIT actionBeingHandled();
m_currentView->renameSelectedItems();
void DolphinViewActionHandler::slotTrashActivated()
{
if (m_currentView->selectedItemsCount() == 0) {
- Q_EMIT setSelectionMode(true, SelectionModeBottomBar::Contents::MoveToTrashContents);
+ Q_EMIT setSelectionMode(true, SelectionMode::BottomBar::Contents::MoveToTrashContents);
} else {
Q_EMIT actionBeingHandled();
m_currentView->trashSelectedItems();
void DolphinViewActionHandler::slotDeleteItems()
{
if (m_currentView->selectedItemsCount() == 0) {
- Q_EMIT setSelectionMode(true, SelectionModeBottomBar::Contents::DeleteContents);
+ Q_EMIT setSelectionMode(true, SelectionMode::BottomBar::Contents::DeleteContents);
} else {
Q_EMIT actionBeingHandled();
m_currentView->deleteSelectedItems();
void DolphinViewActionHandler::slotDuplicate()
{
if (m_currentView->selectedItemsCount() == 0) {
- Q_EMIT setSelectionMode(true, SelectionModeBottomBar::Contents::DuplicateContents);
+ Q_EMIT setSelectionMode(true, SelectionMode::BottomBar::Contents::DuplicateContents);
} else {
Q_EMIT actionBeingHandled();
m_currentView->duplicateSelectedItems();
void DolphinViewActionHandler::slotCopyPath()
{
if (m_currentView->selectedItemsCount() == 0) {
- Q_EMIT setSelectionMode(true, SelectionModeBottomBar::Contents::CopyLocationContents);
+ Q_EMIT setSelectionMode(true, SelectionMode::BottomBar::Contents::CopyLocationContents);
} else {
m_currentView->copyPathToClipboard();
Q_EMIT setSelectionMode(false);
#define DOLPHINVIEWACTIONHANDLER_H
#include "dolphin_export.h"
-#include "selectionmode/selectionmodebottombar.h"
+#include "selectionmode/bottombar.h"
#include "views/dolphinview.h"
#include <QObject>
class DolphinView;
class KActionCollection;
class KFileItemList;
+namespace SelectionMode {
+ class ActionTextHelper;
+}
/**
* @short Handles all actions for DolphinView
Q_OBJECT
public:
- explicit DolphinViewActionHandler(KActionCollection* collection, QObject* parent);
+ explicit DolphinViewActionHandler(KActionCollection* collection, SelectionMode::ActionTextHelper* actionTextHelper, QObject* parent);
/**
* Sets the view that this action handler should work on.
void createDirectoryTriggered();
/** Used to request selection mode */
- void setSelectionMode(bool enabled, SelectionModeBottomBar::Contents bottomBarContents = SelectionModeBottomBar::Contents::GeneralContents);
+ void setSelectionMode(bool enabled, SelectionMode::BottomBar::Contents bottomBarContents = SelectionMode::BottomBar::Contents::GeneralContents);
private Q_SLOTS:
/**
* Create all the actions.
* This is called only once (by the constructor)
*/
- void createActions();
+ void createActions(SelectionMode::ActionTextHelper *actionTextHelper);
/**
* Creates an action-group out of all roles from KFileItemModel.