┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/disabledactionnotifier.cpp51
-rw-r--r--src/disabledactionnotifier.h60
-rw-r--r--src/dolphinmainwindow.cpp77
-rw-r--r--src/dolphinmainwindow.h2
5 files changed, 182 insertions, 9 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 4c2c3cb6c..ab288a563 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -262,6 +262,7 @@ add_library(dolphinstatic STATIC)
target_sources(dolphinstatic PRIVATE
animatedheightwidget.cpp
+ disabledactionnotifier.cpp
dolphinbookmarkhandler.cpp
dolphindockwidget.cpp
dolphinmainwindow.cpp
diff --git a/src/disabledactionnotifier.cpp b/src/disabledactionnotifier.cpp
new file mode 100644
index 000000000..844e66228
--- /dev/null
+++ b/src/disabledactionnotifier.cpp
@@ -0,0 +1,51 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Jin Liu <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "disabledactionnotifier.h"
+
+DisabledActionNotifier::DisabledActionNotifier(QObject *parent)
+ : QObject(parent)
+{
+}
+
+void DisabledActionNotifier::setDisabledReason(QAction *action, QStringView reason)
+{
+ if (action->isEnabled()) {
+ return;
+ }
+
+ if (m_shortcuts.contains(action)) {
+ m_shortcuts.take(action)->deleteLater();
+ }
+
+ QShortcut *shortcut = new QShortcut(action->shortcut(), parent());
+ m_shortcuts.insert(action, shortcut);
+
+ connect(action, &QAction::enabledChanged, this, [this, action](bool enabled) {
+ if (enabled) {
+ m_shortcuts.take(action)->deleteLater();
+ }
+ });
+
+ // Don't capture QStringView, as it may reference a temporary QString
+ QString reasonString = reason.toString();
+ connect(shortcut, &QShortcut::activated, this, [this, action, reasonString]() {
+ Q_EMIT disabledActionTriggered(action, reasonString);
+ });
+}
+
+void DisabledActionNotifier::clearDisabledReason(QAction *action)
+{
+ if (action->isEnabled()) {
+ return;
+ }
+
+ if (m_shortcuts.contains(action)) {
+ m_shortcuts.take(action)->deleteLater();
+ }
+}
+
+#include "moc_disabledactionnotifier.cpp"
diff --git a/src/disabledactionnotifier.h b/src/disabledactionnotifier.h
new file mode 100644
index 000000000..535e9932b
--- /dev/null
+++ b/src/disabledactionnotifier.h
@@ -0,0 +1,60 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Jin Liu <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <QAction>
+#include <QHash>
+#include <QShortcut>
+
+/**
+ * @brief A helper class to display a notification when the user presses the shortcut of a disabled action.
+ */
+class DisabledActionNotifier : public QObject
+{
+ Q_OBJECT
+
+public:
+ DisabledActionNotifier(QObject *parent = nullptr);
+
+ /**
+ * Set the reason why the action is disabled.
+ *
+ * If the action is enabled, this function does nothing.
+ *
+ * Otherwise, it registers a shortcut, so when the user presses the shortcut for the
+ * disabled action, it emits the disabledActionTriggered() signal with the disabled
+ * action and the reason.
+ *
+ * If a reason has already been set, it will be replaced.
+ */
+ void setDisabledReason(QAction *action, QStringView reason);
+
+ /**
+ * Clear the reason if it's set before by setDisabledReason().
+ *
+ * If the action is enabled, this function does nothing.
+ *
+ * Otherwise, it unregisters any shortcut set by setDisabledReason() on the same action.
+ *
+ * When an action is disabled in two cases, but only case A needs to show the reason,
+ * then case B should call this function. Otherwise, the reason set by case A might be
+ * shown for case B.
+ */
+ void clearDisabledReason(QAction *action);
+
+Q_SIGNALS:
+ /**
+ * Emitted when the user presses the shortcut of a disabled action.
+ *
+ * @param action The disabled action.
+ * @param reason The reason set in setDisabledReason().
+ */
+ void disabledActionTriggered(const QAction *action, QString reason);
+
+private:
+ QHash<QAction *, QShortcut *> m_shortcuts;
+};
diff --git a/src/dolphinmainwindow.cpp b/src/dolphinmainwindow.cpp
index 406edd37c..4cba46554 100644
--- a/src/dolphinmainwindow.cpp
+++ b/src/dolphinmainwindow.cpp
@@ -110,6 +110,7 @@ DolphinMainWindow::DolphinMainWindow()
, m_remoteEncoding(nullptr)
, m_settingsDialog()
, m_bookmarkHandler(nullptr)
+ , m_disabledActionNotifier(nullptr)
, m_lastHandleUrlOpenJob(nullptr)
, m_terminalPanel(nullptr)
, m_placesPanel(nullptr)
@@ -178,6 +179,11 @@ DolphinMainWindow::DolphinMainWindow()
m_remoteEncoding = new DolphinRemoteEncoding(this, m_actionHandler);
connect(this, &DolphinMainWindow::urlChanged, m_remoteEncoding, &DolphinRemoteEncoding::slotAboutToOpenUrl);
+ m_disabledActionNotifier = new DisabledActionNotifier(this);
+ connect(m_disabledActionNotifier, &DisabledActionNotifier::disabledActionTriggered, this, [this](const QAction *, QString reason) {
+ m_activeViewContainer->showMessage(reason, DolphinViewContainer::Warning);
+ });
+
setupDockWidgets();
setupGUI(Save | Create | ToolBar);
@@ -853,6 +859,10 @@ void DolphinMainWindow::updatePasteAction()
QAction *pasteAction = actionCollection()->action(KStandardAction::name(KStandardAction::Paste));
QPair<bool, QString> pasteInfo = m_activeViewContainer->view()->pasteInfo();
pasteAction->setEnabled(pasteInfo.first);
+ m_disabledActionNotifier->setDisabledReason(pasteAction,
+ m_activeViewContainer->rootItem().isWritable()
+ ? i18nc("@info", "Could not paste: The clipboard is empty.")
+ : i18nc("@info", "Could not paste: You do not have permission to write into this folder."));
pasteAction->setText(pasteInfo.second);
}
@@ -1383,6 +1393,11 @@ void DolphinMainWindow::slotWriteStateChanged(bool isFolderWritable)
// trash:/ is writable but we don't want to create new items in it.
// TODO: remove the trash check once https://phabricator.kde.org/T8234 is implemented
newFileMenu()->setEnabled(isFolderWritable && m_activeViewContainer->url().scheme() != QLatin1String("trash"));
+ // When the menu is disabled, actions in it are disabled later in the event loop, and we need to set the disabled reason after that.
+ QTimer::singleShot(0, this, [this]() {
+ m_disabledActionNotifier->setDisabledReason(actionCollection()->action(QStringLiteral("create_dir")),
+ i18nc("@info", "Could not create new folder: You do not have permission to create items in this folder."));
+ });
}
void DolphinMainWindow::openContextMenu(const QPoint &pos, const KFileItem &item, const KFileItemList &selectedItems, const QUrl &url)
@@ -2416,16 +2431,43 @@ void DolphinMainWindow::updateFileAndEditActions()
const bool enableMoveToTrash = capabilitiesSource.isLocal() && capabilitiesSource.supportsMoving();
renameAction->setEnabled(capabilitiesSource.supportsMoving());
- moveToTrashAction->setEnabled(enableMoveToTrash);
+ m_disabledActionNotifier->setDisabledReason(renameAction,
+ i18nc("@info", "Could not rename: You do not have permission to rename items in this folder."));
deleteAction->setEnabled(capabilitiesSource.supportsDeleting());
- deleteWithTrashShortcut->setEnabled(capabilitiesSource.supportsDeleting() && !enableMoveToTrash);
+ m_disabledActionNotifier->setDisabledReason(deleteAction,
+ i18nc("@info", "Could not delete: You do not have permission to remove items from this folder."));
cutAction->setEnabled(capabilitiesSource.supportsMoving());
+ m_disabledActionNotifier->setDisabledReason(cutAction, i18nc("@info", "Could not cut: You do not have permission to move items from this folder."));
copyLocation->setEnabled(list.length() == 1);
showTarget->setEnabled(list.length() == 1 && list.at(0).isLink());
duplicateAction->setEnabled(capabilitiesSource.supportsWriting());
+ m_disabledActionNotifier->setDisabledReason(duplicateAction,
+ i18nc("@info", "Could not duplicate here: You do not have permission to create items in this folder."));
+
+ if (enableMoveToTrash) {
+ moveToTrashAction->setEnabled(true);
+ deleteWithTrashShortcut->setEnabled(false);
+ m_disabledActionNotifier->clearDisabledReason(deleteWithTrashShortcut);
+ } else {
+ moveToTrashAction->setEnabled(false);
+ deleteWithTrashShortcut->setEnabled(capabilitiesSource.supportsDeleting());
+ m_disabledActionNotifier->setDisabledReason(deleteWithTrashShortcut,
+ i18nc("@info", "Could not delete: You do not have permission to remove items from this folder."));
+ }
}
- if (m_tabWidget->currentTabPage()->splitViewEnabled() && !list.isEmpty()) {
+ if (!m_tabWidget->currentTabPage()->splitViewEnabled()) {
+ // No need to set the disabled reason here, as it's obvious to the user that the reason is the split view being disabled.
+ copyToOtherViewAction->setEnabled(false);
+ m_disabledActionNotifier->clearDisabledReason(copyToOtherViewAction);
+ moveToOtherViewAction->setEnabled(false);
+ m_disabledActionNotifier->clearDisabledReason(moveToOtherViewAction);
+ } else if (list.isEmpty()) {
+ copyToOtherViewAction->setEnabled(false);
+ m_disabledActionNotifier->setDisabledReason(copyToOtherViewAction, i18nc("@info", "Could not copy to other view: No files selected."));
+ moveToOtherViewAction->setEnabled(false);
+ m_disabledActionNotifier->setDisabledReason(moveToOtherViewAction, i18nc("@info", "Could not move to other view: No files selected."));
+ } else {
DolphinTabPage *tabPage = m_tabWidget->currentTabPage();
KFileItem capabilitiesDestination;
@@ -2440,12 +2482,29 @@ void DolphinMainWindow::updateFileAndEditActions()
return item.url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash) != destUrl;
});
- copyToOtherViewAction->setEnabled(capabilitiesDestination.isWritable() && allNotTargetOrigin);
- moveToOtherViewAction->setEnabled((list.isEmpty() || capabilitiesSource.supportsMoving()) && capabilitiesDestination.isWritable()
- && allNotTargetOrigin);
- } else {
- copyToOtherViewAction->setEnabled(false);
- moveToOtherViewAction->setEnabled(false);
+ if (!allNotTargetOrigin) {
+ copyToOtherViewAction->setEnabled(false);
+ m_disabledActionNotifier->setDisabledReason(copyToOtherViewAction,
+ i18nc("@info", "Could not copy to other view: The other view already contains these items."));
+ moveToOtherViewAction->setEnabled(false);
+ m_disabledActionNotifier->setDisabledReason(moveToOtherViewAction,
+ i18nc("@info", "Could not move to other view: The other view already contains these items."));
+ } else if (!capabilitiesDestination.isWritable()) {
+ copyToOtherViewAction->setEnabled(false);
+ m_disabledActionNotifier->setDisabledReason(
+ copyToOtherViewAction,
+ i18nc("@info", "Could not copy to other view: You do not have permission to write into the destination folder."));
+ moveToOtherViewAction->setEnabled(false);
+ m_disabledActionNotifier->setDisabledReason(
+ moveToOtherViewAction,
+ i18nc("@info", "Could not move to other view: You do not have permission to write into the destination folder."));
+ } else {
+ copyToOtherViewAction->setEnabled(true);
+ moveToOtherViewAction->setEnabled(capabilitiesSource.supportsMoving());
+ m_disabledActionNotifier->setDisabledReason(
+ moveToOtherViewAction,
+ i18nc("@info", "Could not move to other view: You do not have permission to move items from this folder."));
+ }
}
}
diff --git a/src/dolphinmainwindow.h b/src/dolphinmainwindow.h
index 62f8ceb6e..9a1582c1f 100644
--- a/src/dolphinmainwindow.h
+++ b/src/dolphinmainwindow.h
@@ -10,6 +10,7 @@
#define DOLPHIN_MAINWINDOW_H
#include "config-dolphin.h"
+#include "disabledactionnotifier.h"
#include "dolphintabwidget.h"
#include "selectionmode/bottombar.h"
#include <KActionMenu>
@@ -727,6 +728,7 @@ private:
QPointer<DolphinSettingsDialog> m_settingsDialog;
DolphinBookmarkHandler *m_bookmarkHandler;
SelectionMode::ActionTextHelper *m_actionTextHelper;
+ DisabledActionNotifier *m_disabledActionNotifier;
KIO::OpenUrlJob *m_lastHandleUrlOpenJob;