]> cloud.milkyroute.net Git - dolphin.git/blob - src/itemactions/setfoldericonitemaction.cpp
744283f2f8ddc0e972aaddb8648f984c4ffe20f5
[dolphin.git] / src / itemactions / setfoldericonitemaction.cpp
1 /*
2 * SPDX-FileCopyrightText: 2025 Méven Car <meven@kde.org>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "setfoldericonitemaction.h"
8 #include "../dolphindebug.h"
9
10 #include <KConfigGroup>
11 #include <KDesktopFile>
12 #include <KFileItem>
13 #include <KLocalizedString>
14 #include <KPluginFactory>
15 #ifdef QT_DBUS_LIB
16 #include <KDirNotify>
17 #endif
18
19 #include <QActionGroup>
20 #include <QBoxLayout>
21 #include <QEvent>
22 #include <QFocusEvent>
23 #include <QKeyEvent>
24 #include <QMenu>
25 #include <QPushButton>
26 #include <QUrl>
27 #include <QWidgetAction>
28
29 K_PLUGIN_CLASS_WITH_JSON(SetFolderIconItemAction, "setfoldericonitemaction.json")
30
31 namespace
32 {
33 bool isDefaultFolderIcon(const QString &iconName)
34 {
35 return iconName.isEmpty() || iconName == QLatin1String("folder") || iconName == QLatin1String("inode-directory");
36 }
37 }
38
39 SetFolderIconItemAction::SetFolderIconItemAction(QObject *parent)
40 : KAbstractFileItemActionPlugin(parent)
41 {
42 }
43
44 void SetFolderIconItemAction::setFolderIcon(bool check)
45 {
46 QAction *action = qobject_cast<QAction *>(sender());
47 Q_ASSERT(action);
48
49 action->setChecked(check);
50
51 auto iconName = action->icon().name();
52
53 // Apply custom folder icon, if applicable.
54 const QString fileName = m_localUrl.toLocalFile() + QLatin1String("/.directory");
55 KDesktopFile desktopFile{fileName};
56
57 if (check && !isDefaultFolderIcon(iconName)) {
58 desktopFile.desktopGroup().writeEntry(QStringLiteral("Icon"), iconName);
59 } else {
60 desktopFile.desktopGroup().deleteEntry(QStringLiteral("Icon"));
61 if (desktopFile.desktopGroup().entryMap().isEmpty() && QFile::exists(fileName)) {
62 // clean file
63 QFile::remove(fileName);
64 }
65 }
66
67 #ifdef QT_DBUS_LIB
68 org::kde::KDirNotify::emitFilesChanged({m_url});
69 #endif
70 }
71
72 class ButtonsWithSubMenuWidgetAction : public QWidgetAction
73 {
74 public:
75 ButtonsWithSubMenuWidgetAction(QMenu *subMenu, QWidget *parentWidget)
76 : QWidgetAction(parentWidget)
77 , m_subMenu(subMenu)
78 {
79 }
80
81 void setActions(const QList<QAction *> actions)
82 {
83 m_actions = actions;
84 }
85
86 bool eventFilter(QObject *object, QEvent *event) override
87 {
88 if (event->type() == QEvent::KeyPress) {
89 const QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
90 auto widget = qobject_cast<QWidget *>(object);
91
92 if (keyEvent->keyCombination() == QKeyCombination(Qt::Modifier::SHIFT, Qt::Key_Backtab) || keyEvent->key() == Qt::Key_Left
93 || keyEvent->key() == Qt::Key_Up) {
94 auto previous = widget->previousInFocusChain();
95 if (previous == widget->parentWidget()) {
96 // the next object is the parent, let the focus bubble up
97 return false;
98 }
99
100 previous->setFocus(Qt::BacktabFocusReason);
101 event->accept();
102 return true;
103 }
104
105 if (keyEvent->keyCombination() == QKeyCombination(Qt::Key_Tab) || keyEvent->key() == Qt::Key_Right || keyEvent->key() == Qt::Key_Down) {
106 auto next = widget->nextInFocusChain();
107 if (next->parentWidget() != widget->parentWidget()) {
108 // the next object is not a sibling, let the focus bubble up
109 return false;
110 }
111
112 next->setFocus(Qt::TabFocusReason);
113 event->accept();
114 return true;
115 }
116 }
117
118 // TODO implement proper SHIFT+TAB
119 // See https://bugreports.qt.io/browse/QTBUG-137298
120
121 return false;
122 }
123
124 QWidget *createWidget(QWidget *parent) override
125 {
126 QWidget *widget = new QWidget(parent);
127 auto layout = new QHBoxLayout(widget);
128
129 bool firstAction = false;
130 for (const auto action : std::as_const(m_actions)) {
131 action->setParent(widget);
132
133 auto p = new QPushButton(widget);
134
135 p->setIcon(action->icon());
136 p->setCheckable(true);
137 p->setChecked(action->isChecked());
138 p->setToolTip(action->toolTip());
139 p->installEventFilter(this);
140
141 connect(p, &QPushButton::clicked, action, &QAction::triggered);
142 connect(action, &QAction::toggled, p, &QPushButton::setChecked);
143
144 layout->addWidget(p);
145
146 if (!firstAction) {
147 widget->setFocusProxy(p);
148 firstAction = true;
149 }
150 }
151
152 auto p = new QPushButton(widget);
153 p->setText(i18nc("@action open a submeun with additional entries", "Other"));
154 p->setToolTip(i18nc("@label", "Other folder icon options"));
155 p->setMenu(m_subMenu);
156 layout->addWidget(p);
157 p->installEventFilter(this);
158
159 widget->setFocusPolicy(Qt::StrongFocus);
160
161 return widget;
162 }
163
164 QList<QAction *> m_actions;
165 QMenu *m_subMenu;
166 };
167
168 QList<QAction *> SetFolderIconItemAction::actions(const KFileItemListProperties &fileItemInfos, QWidget *parentWidget)
169 {
170 if (fileItemInfos.items().count() != 1) {
171 return {};
172 }
173
174 auto fileItem = fileItemInfos.items().at(0);
175 m_url = fileItem.url();
176
177 bool local;
178 m_localUrl = fileItem.mostLocalUrl(&local);
179 if (!local || !fileItemInfos.supportsWriting() || !fileItem.isWritable()) {
180 return {};
181 }
182 const short s_numberOfEntriesVisible = 5;
183
184 using StringPair = QPair<KLocalizedString, QString>;
185 // keep in sync with kio/src/filewidgets/knewfilemenu.cpp
186 // default folder icon goes here.
187 const QList<StringPair> icons = {// colors.
188 StringPair{ki18nc("@label as in default folder color", "Red"), QStringLiteral("folder-red")},
189 StringPair{ki18nc("@label as in default folder color", "Yellow"), QStringLiteral("folder-yellow")},
190 StringPair{ki18nc("@label as in default folder color", "Orange"), QStringLiteral("folder-orange")},
191 StringPair{ki18nc("@label as in default folder color", "Green"), QStringLiteral("folder-green")},
192 StringPair{ki18nc("@label as in default folder color", "Cyan"), QStringLiteral("folder-cyan")},
193 // must match s_numberOfEntriesVisible
194 StringPair{ki18nc("@label: as in default folder icon", "Default"), QStringLiteral("inode-directory")},
195
196 StringPair{ki18nc("@label as in default folder color", "Blue"), QStringLiteral("folder-blue")},
197 StringPair{ki18nc("@label as in default folder color", "Violet"), QStringLiteral("folder-violet")},
198 StringPair{ki18nc("@label as in default folder color", "Brown"), QStringLiteral("folder-brown")},
199 StringPair{ki18nc("@label as in default folder color", "Grey"), QStringLiteral("folder-grey")},
200
201 // emblems.
202 StringPair{ki18nc("@label as in default folder color", "Bookmark"), QStringLiteral("folder-bookmark")},
203 StringPair{ki18nc("@label as in default folder color", "Cloud"), QStringLiteral("folder-cloud")},
204 StringPair{ki18nc("@label as in default folder color", "Development"), QStringLiteral("folder-development")},
205 StringPair{ki18nc("@label as in default folder color", "Games"), QStringLiteral("folder-games")},
206 StringPair{ki18nc("@label as in default folder color", "Mail"), QStringLiteral("folder-mail")},
207 StringPair{ki18nc("@label as in default folder color", "Music"), QStringLiteral("folder-music")},
208 StringPair{ki18nc("@label as in default folder color", "Print"), QStringLiteral("folder-print")},
209 StringPair{ki18nc("@label as in default folder color", "Compressed"), QStringLiteral("folder-tar")},
210 StringPair{ki18nc("@label as in default folder color", "Temporary"), QStringLiteral("folder-temp")},
211 StringPair{ki18nc("@label as in default folder color", "Important"), QStringLiteral("folder-important")}};
212
213 QActionGroup *actiongroup = new QActionGroup(this);
214 actiongroup->setExclusionPolicy(QActionGroup::ExclusionPolicy::ExclusiveOptional);
215
216 QMenu *subMenu = new QMenu();
217 auto action = new ButtonsWithSubMenuWidgetAction(subMenu, parentWidget);
218
219 int i = 0;
220 QList<QAction *> actions;
221 const auto fileIconName = fileItem.iconName();
222 for (const auto &[name, iconName] : icons) {
223 auto icon = QIcon::fromTheme(iconName);
224 if (icon.isNull()) {
225 qCWarning(DolphinDebug) << "SetFolderIconItemAction Missing icon:" << iconName;
226 continue;
227 }
228
229 QAction *folderIconAction = new QAction(KLocalizedString(name).toString(), parentWidget);
230 folderIconAction->setIcon(icon);
231 folderIconAction->setCheckable(true);
232 folderIconAction->setChecked(fileIconName == iconName);
233 folderIconAction->setToolTip(i18nc("@label %1 is a folder icon name (Red, Music...) etc", "Set folder icon to %1", folderIconAction->iconText()));
234 actiongroup->addAction(folderIconAction);
235
236 connect(folderIconAction, &QAction::triggered, this, &SetFolderIconItemAction::setFolderIcon);
237 connect(folderIconAction, &QAction::triggered, action, &QAction::triggered);
238
239 ++i;
240 if (i < s_numberOfEntriesVisible + 1) {
241 actions.append(folderIconAction);
242 } else {
243 folderIconAction->setParent(subMenu);
244 subMenu->addAction(folderIconAction);
245 }
246 }
247 action->setActions(actions);
248
249 return {action};
250 }
251
252 #include "moc_setfoldericonitemaction.cpp"
253 #include "setfoldericonitemaction.moc"