/* * SPDX-FileCopyrightText: 2025 Méven Car * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "setfoldericonitemaction.h" #include "../dolphindebug.h" #include #include #include #include #include #ifdef QT_DBUS_LIB #include #endif #include #include #include #include #include #include #include #include #include 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(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 actions) { m_actions = actions; } bool eventFilter(QObject *object, QEvent *event) override { if (event->type() == QEvent::KeyPress) { const QKeyEvent *keyEvent = static_cast(event); auto widget = qobject_cast(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 m_actions; QMenu *m_subMenu; }; QList 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; // keep in sync with kio/src/filewidgets/knewfilemenu.cpp // default folder icon goes here. const QList 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 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"