┌   ┐
54
└   ┘

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMéven Car <[email protected]>2025-06-09 12:16:36 +0000
committerMéven Car <[email protected]>2025-06-09 12:16:36 +0000
commit697d58e9727e229abb81956d27a05d1f02d8c775 (patch)
tree639c00be3e8f26f221379fe33accc44c49f9740a
parent2369b0c46ccab88e1cee310de22def6aaff41b00 (diff)
Add a SetFolderIcon ItemAction plugin
To allow to change folder icon from the context menu. CCBUG: 467221
-rw-r--r--src/CMakeLists.txt12
-rw-r--r--src/dolphincontextmenu.cpp5
-rw-r--r--src/itemactions/CMakeLists.txt26
-rw-r--r--src/itemactions/setfoldericonitemaction.cpp253
-rw-r--r--src/itemactions/setfoldericonitemaction.h25
-rw-r--r--src/itemactions/setfoldericonitemaction.json11
6 files changed, 319 insertions, 13 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 3bf64454e..5b02a0f76 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -672,14 +672,4 @@ if(BUILD_TESTING)
add_subdirectory(tests)
endif()
-# movetonewfolderitemaction plugin
-
-kcoreaddons_add_plugin(movetonewfolderitemaction
- SOURCES itemactions/movetonewfolderitemaction.cpp itemactions/movetonewfolderitemaction.h
- INSTALL_NAMESPACE "kf6/kfileitemaction")
-
-target_link_libraries(movetonewfolderitemaction
- KF6::I18n
- KF6::KIOCore
- KF6::KIOWidgets
- KF6::KIOFileWidgets)
+add_subdirectory(itemactions)
diff --git a/src/dolphincontextmenu.cpp b/src/dolphincontextmenu.cpp
index 8372060aa..e1c67aad1 100644
--- a/src/dolphincontextmenu.cpp
+++ b/src/dolphincontextmenu.cpp
@@ -122,7 +122,7 @@ void DolphinContextMenu::addTrashContextMenu()
{
Q_ASSERT(m_context & TrashContext);
- QAction *emptyTrashAction = addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action:inmenu", "Empty Trash"), [this]() {
+ QAction *emptyTrashAction = addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action:inmenu", "Empty Trash"), this, [this]() {
Trash::empty(m_mainWindow);
});
emptyTrashAction->setEnabled(!Trash::isEmpty());
@@ -154,6 +154,7 @@ void DolphinContextMenu::addTrashItemContextMenu()
"Restore to Former Location",
"Restore to Former Locations",
m_selectedItems.count()),
+ this,
[this]() {
QList<QUrl> selectedUrls;
selectedUrls.reserve(m_selectedItems.count());
@@ -202,7 +203,7 @@ void DolphinContextMenu::addDirectoryItemContextMenu()
// set up 'Create New' menu
QAction *newDirAction = m_mainWindow->actionCollection()->action(QStringLiteral("create_dir"));
QAction *newFileAction = m_mainWindow->actionCollection()->action(QStringLiteral("create_file"));
- DolphinNewFileMenu *newFileMenu = new DolphinNewFileMenu(newDirAction, newFileAction, m_mainWindow);
+ DolphinNewFileMenu *newFileMenu = new DolphinNewFileMenu(newDirAction, newFileAction, this);
newFileMenu->checkUpToDate();
newFileMenu->setWorkingDirectory(m_fileInfo.url());
newFileMenu->setEnabled(selectedItemsProps.supportsWriting());
diff --git a/src/itemactions/CMakeLists.txt b/src/itemactions/CMakeLists.txt
new file mode 100644
index 000000000..6610b0e4a
--- /dev/null
+++ b/src/itemactions/CMakeLists.txt
@@ -0,0 +1,26 @@
+
+# movetonewfolderitemaction plugin
+
+kcoreaddons_add_plugin(movetonewfolderitemaction
+ SOURCES movetonewfolderitemaction.cpp movetonewfolderitemaction.h
+ INSTALL_NAMESPACE "kf6/kfileitemaction")
+
+target_link_libraries(movetonewfolderitemaction
+ KF6::I18n
+ KF6::KIOCore
+ KF6::KIOWidgets
+ KF6::KIOFileWidgets)
+
+
+if(NOT WIN32)
+ # setfoldericon plugin
+
+ kcoreaddons_add_plugin(setfoldericonitemaction
+ SOURCES setfoldericonitemaction.cpp setfoldericonitemaction.h ../dolphindebug.h ../dolphindebug.cpp
+ INSTALL_NAMESPACE "kf6/kfileitemaction")
+
+ target_link_libraries(setfoldericonitemaction
+ KF6::I18n
+ KF6::KIOCore
+ KF6::KIOWidgets)
+endif()
diff --git a/src/itemactions/setfoldericonitemaction.cpp b/src/itemactions/setfoldericonitemaction.cpp
new file mode 100644
index 000000000..744283f2f
--- /dev/null
+++ b/src/itemactions/setfoldericonitemaction.cpp
@@ -0,0 +1,253 @@
+/*
+ * SPDX-FileCopyrightText: 2025 Méven Car <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "setfoldericonitemaction.h"
+#include "../dolphindebug.h"
+
+#include <KConfigGroup>
+#include <KDesktopFile>
+#include <KFileItem>
+#include <KLocalizedString>
+#include <KPluginFactory>
+#ifdef QT_DBUS_LIB
+#include <KDirNotify>
+#endif
+
+#include <QActionGroup>
+#include <QBoxLayout>
+#include <QEvent>
+#include <QFocusEvent>
+#include <QKeyEvent>
+#include <QMenu>
+#include <QPushButton>
+#include <QUrl>
+#include <QWidgetAction>
+
+K_PLUGIN_CLASS_WITH_JSON(SetFolderIconItemAction, "setfoldericonitemaction.json")
+
+namespace
+{
+bool isDefaultFolderIcon(const QString &iconName)
+{
+ return iconName.isEmpty() || iconName == QLatin1String("folder") || iconName == QLatin1String("inode-directory");
+}
+}
+
+SetFolderIconItemAction::SetFolderIconItemAction(QObject *parent)
+ : KAbstractFileItemActionPlugin(parent)
+{
+}
+
+void SetFolderIconItemAction::setFolderIcon(bool check)
+{
+ QAction *action = qobject_cast<QAction *>(sender());
+ Q_ASSERT(action);
+
+ action->setChecked(check);
+
+ auto iconName = action->icon().name();
+
+ // Apply custom folder icon, if applicable.
+ const QString fileName = m_localUrl.toLocalFile() + QLatin1String("/.directory");
+ KDesktopFile desktopFile{fileName};
+
+ if (check && !isDefaultFolderIcon(iconName)) {
+ desktopFile.desktopGroup().writeEntry(QStringLiteral("Icon"), iconName);
+ } else {
+ desktopFile.desktopGroup().deleteEntry(QStringLiteral("Icon"));
+ if (desktopFile.desktopGroup().entryMap().isEmpty() && QFile::exists(fileName)) {
+ // clean file
+ QFile::remove(fileName);
+ }
+ }
+
+#ifdef QT_DBUS_LIB
+ org::kde::KDirNotify::emitFilesChanged({m_url});
+#endif
+}
+
+class ButtonsWithSubMenuWidgetAction : public QWidgetAction
+{
+public:
+ ButtonsWithSubMenuWidgetAction(QMenu *subMenu, QWidget *parentWidget)
+ : QWidgetAction(parentWidget)
+ , m_subMenu(subMenu)
+ {
+ }
+
+ void setActions(const QList<QAction *> actions)
+ {
+ m_actions = actions;
+ }
+
+ bool eventFilter(QObject *object, QEvent *event) override
+ {
+ if (event->type() == QEvent::KeyPress) {
+ const QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
+ auto widget = qobject_cast<QWidget *>(object);
+
+ if (keyEvent->keyCombination() == QKeyCombination(Qt::Modifier::SHIFT, Qt::Key_Backtab) || keyEvent->key() == Qt::Key_Left
+ || keyEvent->key() == Qt::Key_Up) {
+ auto previous = widget->previousInFocusChain();
+ if (previous == widget->parentWidget()) {
+ // the next object is the parent, let the focus bubble up
+ return false;
+ }
+
+ previous->setFocus(Qt::BacktabFocusReason);
+ event->accept();
+ return true;
+ }
+
+ if (keyEvent->keyCombination() == QKeyCombination(Qt::Key_Tab) || keyEvent->key() == Qt::Key_Right || keyEvent->key() == Qt::Key_Down) {
+ auto next = widget->nextInFocusChain();
+ if (next->parentWidget() != widget->parentWidget()) {
+ // the next object is not a sibling, let the focus bubble up
+ return false;
+ }
+
+ next->setFocus(Qt::TabFocusReason);
+ event->accept();
+ return true;
+ }
+ }
+
+ // TODO implement proper SHIFT+TAB
+ // See https://bugreports.qt.io/browse/QTBUG-137298
+
+ return false;
+ }
+
+ QWidget *createWidget(QWidget *parent) override
+ {
+ QWidget *widget = new QWidget(parent);
+ auto layout = new QHBoxLayout(widget);
+
+ bool firstAction = false;
+ for (const auto action : std::as_const(m_actions)) {
+ action->setParent(widget);
+
+ auto p = new QPushButton(widget);
+
+ p->setIcon(action->icon());
+ p->setCheckable(true);
+ p->setChecked(action->isChecked());
+ p->setToolTip(action->toolTip());
+ p->installEventFilter(this);
+
+ connect(p, &QPushButton::clicked, action, &QAction::triggered);
+ connect(action, &QAction::toggled, p, &QPushButton::setChecked);
+
+ layout->addWidget(p);
+
+ if (!firstAction) {
+ widget->setFocusProxy(p);
+ firstAction = true;
+ }
+ }
+
+ auto p = new QPushButton(widget);
+ p->setText(i18nc("@action open a submeun with additional entries", "Other"));
+ p->setToolTip(i18nc("@label", "Other folder icon options"));
+ p->setMenu(m_subMenu);
+ layout->addWidget(p);
+ p->installEventFilter(this);
+
+ widget->setFocusPolicy(Qt::StrongFocus);
+
+ return widget;
+ }
+
+ QList<QAction *> m_actions;
+ QMenu *m_subMenu;
+};
+
+QList<QAction *> SetFolderIconItemAction::actions(const KFileItemListProperties &fileItemInfos, QWidget *parentWidget)
+{
+ if (fileItemInfos.items().count() != 1) {
+ return {};
+ }
+
+ auto fileItem = fileItemInfos.items().at(0);
+ m_url = fileItem.url();
+
+ bool local;
+ m_localUrl = fileItem.mostLocalUrl(&local);
+ if (!local || !fileItemInfos.supportsWriting() || !fileItem.isWritable()) {
+ return {};
+ }
+ const short s_numberOfEntriesVisible = 5;
+
+ using StringPair = QPair<KLocalizedString, QString>;
+ // keep in sync with kio/src/filewidgets/knewfilemenu.cpp
+ // default folder icon goes here.
+ const QList<StringPair> icons = {// colors.
+ StringPair{ki18nc("@label as in default folder color", "Red"), QStringLiteral("folder-red")},
+ StringPair{ki18nc("@label as in default folder color", "Yellow"), QStringLiteral("folder-yellow")},
+ StringPair{ki18nc("@label as in default folder color", "Orange"), QStringLiteral("folder-orange")},
+ StringPair{ki18nc("@label as in default folder color", "Green"), QStringLiteral("folder-green")},
+ StringPair{ki18nc("@label as in default folder color", "Cyan"), QStringLiteral("folder-cyan")},
+ // must match s_numberOfEntriesVisible
+ StringPair{ki18nc("@label: as in default folder icon", "Default"), QStringLiteral("inode-directory")},
+
+ StringPair{ki18nc("@label as in default folder color", "Blue"), QStringLiteral("folder-blue")},
+ StringPair{ki18nc("@label as in default folder color", "Violet"), QStringLiteral("folder-violet")},
+ StringPair{ki18nc("@label as in default folder color", "Brown"), QStringLiteral("folder-brown")},
+ StringPair{ki18nc("@label as in default folder color", "Grey"), QStringLiteral("folder-grey")},
+
+ // emblems.
+ StringPair{ki18nc("@label as in default folder color", "Bookmark"), QStringLiteral("folder-bookmark")},
+ StringPair{ki18nc("@label as in default folder color", "Cloud"), QStringLiteral("folder-cloud")},
+ StringPair{ki18nc("@label as in default folder color", "Development"), QStringLiteral("folder-development")},
+ StringPair{ki18nc("@label as in default folder color", "Games"), QStringLiteral("folder-games")},
+ StringPair{ki18nc("@label as in default folder color", "Mail"), QStringLiteral("folder-mail")},
+ StringPair{ki18nc("@label as in default folder color", "Music"), QStringLiteral("folder-music")},
+ StringPair{ki18nc("@label as in default folder color", "Print"), QStringLiteral("folder-print")},
+ StringPair{ki18nc("@label as in default folder color", "Compressed"), QStringLiteral("folder-tar")},
+ StringPair{ki18nc("@label as in default folder color", "Temporary"), QStringLiteral("folder-temp")},
+ StringPair{ki18nc("@label as in default folder color", "Important"), QStringLiteral("folder-important")}};
+
+ QActionGroup *actiongroup = new QActionGroup(this);
+ actiongroup->setExclusionPolicy(QActionGroup::ExclusionPolicy::ExclusiveOptional);
+
+ QMenu *subMenu = new QMenu();
+ auto action = new ButtonsWithSubMenuWidgetAction(subMenu, parentWidget);
+
+ int i = 0;
+ QList<QAction *> actions;
+ const auto fileIconName = fileItem.iconName();
+ for (const auto &[name, iconName] : icons) {
+ auto icon = QIcon::fromTheme(iconName);
+ if (icon.isNull()) {
+ qCWarning(DolphinDebug) << "SetFolderIconItemAction Missing icon:" << iconName;
+ continue;
+ }
+
+ QAction *folderIconAction = new QAction(KLocalizedString(name).toString(), parentWidget);
+ folderIconAction->setIcon(icon);
+ folderIconAction->setCheckable(true);
+ folderIconAction->setChecked(fileIconName == iconName);
+ folderIconAction->setToolTip(i18nc("@label %1 is a folder icon name (Red, Music...) etc", "Set folder icon to %1", folderIconAction->iconText()));
+ actiongroup->addAction(folderIconAction);
+
+ connect(folderIconAction, &QAction::triggered, this, &SetFolderIconItemAction::setFolderIcon);
+ connect(folderIconAction, &QAction::triggered, action, &QAction::triggered);
+
+ ++i;
+ if (i < s_numberOfEntriesVisible + 1) {
+ actions.append(folderIconAction);
+ } else {
+ folderIconAction->setParent(subMenu);
+ subMenu->addAction(folderIconAction);
+ }
+ }
+ action->setActions(actions);
+
+ return {action};
+}
+
+#include "moc_setfoldericonitemaction.cpp"
+#include "setfoldericonitemaction.moc"
diff --git a/src/itemactions/setfoldericonitemaction.h b/src/itemactions/setfoldericonitemaction.h
new file mode 100644
index 000000000..c2ebbd521
--- /dev/null
+++ b/src/itemactions/setfoldericonitemaction.h
@@ -0,0 +1,25 @@
+/*
+ * SPDX-FileCopyrightText: 2025 Méven Car <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <KAbstractFileItemActionPlugin>
+#include <KFileItemListProperties>
+
+class SetFolderIconItemAction : public KAbstractFileItemActionPlugin
+{
+ Q_OBJECT
+
+public:
+ SetFolderIconItemAction(QObject *parent);
+
+ QList<QAction *> actions(const KFileItemListProperties &fileItemInfos, QWidget *parentWidget) override;
+
+private:
+ void setFolderIcon(bool check);
+ QUrl m_url;
+ QUrl m_localUrl;
+};
diff --git a/src/itemactions/setfoldericonitemaction.json b/src/itemactions/setfoldericonitemaction.json
new file mode 100644
index 000000000..bca39ac78
--- /dev/null
+++ b/src/itemactions/setfoldericonitemaction.json
@@ -0,0 +1,11 @@
+{
+ "KPlugin": {
+ "Icon": "folder-new",
+ "MimeTypes": [
+ "inode/directory"
+ ],
+ "Name": "Set Folder Icon"
+ },
+ "X-KDE-Require": "Write",
+ "X-KDE-Show-In-Submenu": "true"
+}