Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
pixmapeditor.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant reason:default
4
5#include "pixmapeditor.h"
6#include <iconloader_p.h>
7#include <iconselector_p.h>
8#include <qdesigner_utils_p.h>
9
10#include <QtDesigner/abstractformeditor.h>
11
12#include <QtWidgets/qapplication.h>
13#include <QtWidgets/qlabel.h>
14#include <QtWidgets/qtoolbutton.h>
15#include <QtWidgets/qboxlayout.h>
16#include <QtWidgets/qlineedit.h>
17#include <QtWidgets/qdialogbuttonbox.h>
18#include <QtWidgets/qpushbutton.h>
19#include <QtWidgets/qfiledialog.h>
20#include <QtWidgets/qmenu.h>
21
22#include <QtGui/qaction.h>
23#if QT_CONFIG(clipboard)
24#include <QtGui/qclipboard.h>
25#endif
26#include <QtGui/qevent.h>
27
29
30using namespace Qt::StringLiterals;
31
32static constexpr QSize ICON_SIZE{16, 16};
33
34namespace qdesigner_internal {
35
36static void createIconThemeDialog(QDialog *topLevel, const QString &labelText,
37 QWidget *themeEditor)
38{
39 QVBoxLayout *layout = new QVBoxLayout(topLevel);
40 QLabel *label = new QLabel(labelText, topLevel);
41 QDialogButtonBox *buttons = new QDialogButtonBox(topLevel);
42 buttons->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
43 QObject::connect(buttons, &QDialogButtonBox::accepted, topLevel, &QDialog::accept);
44 QObject::connect(buttons, &QDialogButtonBox::rejected, topLevel, &QDialog::reject);
45
46 layout->addWidget(label);
47 layout->addWidget(themeEditor);
48 layout->addWidget(buttons);
49}
50
51IconThemeDialog::IconThemeDialog(QWidget *parent)
52 : QDialog(parent)
53{
54 setWindowTitle(tr("Set Icon From XDG Theme"));
55 m_editor = new IconThemeEditor(this);
56 createIconThemeDialog(this, tr("Select icon name from XDG theme:"), m_editor);
57}
58
59std::optional<QString> IconThemeDialog::getTheme(QWidget *parent, const QString &theme)
60{
61 IconThemeDialog dlg(parent);
62 dlg.m_editor->setTheme(theme);
63 if (dlg.exec() == QDialog::Accepted)
64 return dlg.m_editor->theme();
65 return std::nullopt;
66}
67
68IconThemeEnumDialog::IconThemeEnumDialog(QWidget *parent)
69 : QDialog(parent)
70{
71 setWindowTitle(tr("Set Icon From Theme"));
72 m_editor = new IconThemeEnumEditor(this);
73 createIconThemeDialog(this, tr("Select icon name from theme:"), m_editor);
74}
75
76std::optional<int> IconThemeEnumDialog::getTheme(QWidget *parent, int theme)
77{
78 IconThemeEnumDialog dlg(parent);
79 dlg.m_editor->setThemeEnum(theme);
80 if (dlg.exec() == QDialog::Accepted)
81 return dlg.m_editor->themeEnum();
82 return std::nullopt;
83}
84
85PixmapEditor::PixmapEditor(QDesignerFormEditorInterface *core, QWidget *parent) :
86 QWidget(parent),
87 m_iconThemeModeEnabled(false),
88 m_core(core),
89 m_pixmapLabel(new QLabel(this)),
90 m_pathLabel(new QLabel(this)),
91 m_button(new QToolButton(this)),
92 m_resourceAction(new QAction(tr("Choose Resource..."), this)),
93 m_fileAction(new QAction(tr("Choose File..."), this)),
94 m_themeEnumAction(new QAction(tr("Set Icon From Theme..."), this)),
95 m_themeAction(new QAction(tr("Set Icon From XDG Theme..."), this)),
96 m_copyAction(new QAction(createIconSet(QIcon::ThemeIcon::EditCopy, "editcopy.png"_L1),
97 tr("Copy Path"), this)),
98 m_pasteAction(new QAction(createIconSet(QIcon::ThemeIcon::EditPaste, "editpaste.png"_L1),
99 tr("Paste Path"), this)),
100 m_layout(new QHBoxLayout(this)),
101 m_pixmapCache(nullptr)
102{
103 m_layout->addWidget(m_pixmapLabel);
104 m_layout->addWidget(m_pathLabel);
105 m_button->setText(tr("..."));
106 m_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored);
107 m_button->setFixedWidth(30);
108 m_button->setPopupMode(QToolButton::MenuButtonPopup);
109 m_layout->addWidget(m_button);
110 m_layout->setContentsMargins(QMargins());
111 m_layout->setSpacing(0);
112 m_pixmapLabel->setFixedWidth(ICON_SIZE.width());
113 m_pixmapLabel->setAlignment(Qt::AlignCenter);
114 m_pathLabel->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed));
115 m_themeAction->setVisible(false);
116 m_themeEnumAction->setVisible(false);
117
118 QMenu *menu = new QMenu(this);
119 menu->addAction(m_resourceAction);
120 menu->addAction(m_fileAction);
121 menu->addAction(m_themeEnumAction);
122 menu->addAction(m_themeAction);
123
124 m_button->setMenu(menu);
125 m_button->setText(tr("..."));
126
127 connect(m_button, &QAbstractButton::clicked, this, &PixmapEditor::defaultActionActivated);
128 connect(m_resourceAction, &QAction::triggered, this, &PixmapEditor::resourceActionActivated);
129 connect(m_fileAction, &QAction::triggered, this, &PixmapEditor::fileActionActivated);
130 connect(m_themeEnumAction, &QAction::triggered, this, &PixmapEditor::themeEnumActionActivated);
131 connect(m_themeAction, &QAction::triggered, this, &PixmapEditor::themeActionActivated);
132#if QT_CONFIG(clipboard)
133 connect(m_copyAction, &QAction::triggered, this, &PixmapEditor::copyActionActivated);
134 connect(m_pasteAction, &QAction::triggered, this, &PixmapEditor::pasteActionActivated);
135#endif
136 setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Ignored));
137 setFocusProxy(m_button);
138
139#if QT_CONFIG(clipboard)
140 connect(QApplication::clipboard(), &QClipboard::dataChanged,
141 this, &PixmapEditor::clipboardDataChanged);
142 clipboardDataChanged();
143#endif
144}
145
146void PixmapEditor::setPixmapCache(DesignerPixmapCache *cache)
147{
148 m_pixmapCache = cache;
149}
150
152{
153 if (m_iconThemeModeEnabled == enabled)
154 return;
155 m_iconThemeModeEnabled = enabled;
156 m_themeAction->setVisible(enabled);
157 m_themeEnumAction->setVisible(enabled);
158}
159
160void PixmapEditor::setSpacing(int spacing)
161{
162 m_layout->setSpacing(spacing);
163}
164
165void PixmapEditor::setPath(const QString &path)
166{
167 m_path = path;
168 updateLabels();
169}
170
171void PixmapEditor::setTheme(const QString &theme)
172{
173 m_theme = theme;
174 updateLabels();
175}
176
177QString PixmapEditor::msgThemeIcon(const QString &t)
178{
179 return tr("[Theme] %1").arg(t);
180}
181
182QString PixmapEditor::msgMissingThemeIcon(const QString &t)
183{
184 return tr("[Theme] %1 (missing)").arg(t);
185}
186
188{
189 m_themeEnum = e;
190 updateLabels();
191}
192
193void PixmapEditor::updateLabels()
194{
195 m_pathLabel->setText(displayText(m_themeEnum, m_theme, m_path));
196 switch (state()) {
197 case State::Empty:
198 case State::MissingXdgTheme:
199 case State::MissingThemeEnum:
200 m_pixmapLabel->setPixmap(m_defaultPixmap);
201 m_copyAction->setEnabled(false);
202 break;
203 case State::ThemeEnum:
204 m_pixmapLabel->setPixmap(QIcon::fromTheme(static_cast<QIcon::ThemeIcon>(m_themeEnum)).pixmap(ICON_SIZE));
205 m_copyAction->setEnabled(true);
206 break;
207 case State::XdgTheme:
208 m_pixmapLabel->setPixmap(QIcon::fromTheme(m_theme).pixmap(ICON_SIZE));
209 m_copyAction->setEnabled(true);
210 break;
211 case State::Path:
212 case State::PathFallback:
213 if (m_pixmapCache) {
214 auto pixmap = m_pixmapCache->pixmap(PropertySheetPixmapValue(m_path));
215 m_pixmapLabel->setPixmap(QIcon(pixmap).pixmap(ICON_SIZE));
216 }
217 m_copyAction->setEnabled(true);
218 break;
219 }
220}
221
222void PixmapEditor::setDefaultPixmapIcon(const QIcon &icon)
223{
224 m_defaultPixmap = icon.pixmap(ICON_SIZE);
225 if (state() == State::Empty)
226 m_pixmapLabel->setPixmap(m_defaultPixmap);
227}
228
229void PixmapEditor::setDefaultPixmap(const QPixmap &pixmap)
230{
231 setDefaultPixmapIcon(QIcon(pixmap));
232}
233
234void PixmapEditor::contextMenuEvent(QContextMenuEvent *event)
235{
236 QMenu menu(this);
237 menu.addAction(m_copyAction);
238 menu.addAction(m_pasteAction);
239 menu.exec(event->globalPos());
240 event->accept();
241}
242
243void PixmapEditor::defaultActionActivated()
244{
245 if (m_iconThemeModeEnabled) {
246 themeEnumActionActivated();
247 return;
248 }
249 // Default to resource
250 const PropertySheetPixmapValue::PixmapSource ps = m_path.isEmpty()
251 ? PropertySheetPixmapValue::ResourcePixmap
252 : PropertySheetPixmapValue::getPixmapSource(m_core, m_path);
253 switch (ps) {
254 case PropertySheetPixmapValue::LanguageResourcePixmap:
255 case PropertySheetPixmapValue::ResourcePixmap:
256 resourceActionActivated();
257 break;
258 case PropertySheetPixmapValue::FilePixmap:
259 fileActionActivated();
260 break;
261 }
262}
263
264void PixmapEditor::resourceActionActivated()
265{
266 const QString oldPath = m_path;
267 const QString newPath = IconSelector::choosePixmapResource(m_core, m_core->resourceModel(),
268 oldPath, this);
269 if (!newPath.isEmpty() && newPath != oldPath) {
270 setTheme({});
272 setPath(newPath);
273 emit pathChanged(newPath);
274 }
275}
276
277void PixmapEditor::fileActionActivated()
278{
279 const QString newPath = IconSelector::choosePixmapFile(m_path, m_core->dialogGui(), this);
280 if (!newPath.isEmpty() && newPath != m_path) {
281 setTheme({});
283 setPath(newPath);
284 emit pathChanged(newPath);
285 }
286}
287
288void PixmapEditor::themeEnumActionActivated()
289{
290 const auto newThemeO = IconThemeEnumDialog::getTheme(this, {});
291 if (newThemeO.has_value()) {
292 const int newTheme = newThemeO.value();
293 if (newTheme != m_themeEnum) {
294 setThemeEnum(newTheme);
295 setTheme({});
296 setPath({});
297 emit themeEnumChanged(newTheme);
298 }
299 }
300}
301
302void PixmapEditor::themeActionActivated()
303{
304 const auto newThemeO = IconThemeDialog::getTheme(this, m_theme);
305 if (newThemeO.has_value()) {
306 const QString newTheme = newThemeO.value();
307 if (newTheme != m_theme) {
308 setTheme(newTheme);
310 setPath({});
311 emit themeChanged(newTheme);
312 }
313 }
314}
315
316PixmapEditor::State PixmapEditor::stateFromData(int themeEnum, const QString &xdgTheme,
317 const QString &path)
318{
319 if (themeEnum != -1) {
320 if (QIcon::hasThemeIcon(static_cast<QIcon::ThemeIcon>(themeEnum)))
321 return State::ThemeEnum;
322 return path.isEmpty() ? State::MissingThemeEnum : State::PathFallback;
323 }
324 if (!xdgTheme.isEmpty()) {
325 if (QIcon::hasThemeIcon(xdgTheme))
326 return State::XdgTheme;
327 return path.isEmpty() ? State::MissingXdgTheme : State::PathFallback;
328 }
329 return path.isEmpty() ? State::Empty : State::Path;
330}
331
332PixmapEditor::State PixmapEditor::state() const
333{
334 return stateFromData(m_themeEnum, m_theme, m_path);
335}
336
337QString PixmapEditor::displayText(int themeEnum, const QString &xdgTheme, const QString &path)
338{
339 switch (stateFromData(themeEnum, xdgTheme, path)) {
340 case State::ThemeEnum:
341 return msgThemeIcon(IconThemeEnumEditor::iconName(themeEnum));
342 case State::MissingThemeEnum:
343 return msgMissingThemeIcon(IconThemeEnumEditor::iconName(themeEnum));
344 case State::XdgTheme:
345 return msgThemeIcon(xdgTheme);
346 case State::MissingXdgTheme:
347 return msgMissingThemeIcon(xdgTheme);
348 case State::Path:
349 return QFileInfo(path).fileName();
350 case State::PathFallback:
351 return tr("%1 (fallback)").arg(QFileInfo(path).fileName());
352 case State::Empty:
353 break;
354 }
355 return {};
356}
357
358QString PixmapEditor::displayText(const PropertySheetIconValue &icon)
359{
360 const auto &paths = icon.paths();
361 const auto &it = paths.constFind({QIcon::Normal, QIcon::Off});
362 const QString path = it != paths.constEnd() ? it.value().path() : QString{};
363 return displayText(icon.themeEnum(), icon.theme(), path);
364}
365
366#if QT_CONFIG(clipboard)
368{
370 switch (state()) {
371 case State::ThemeEnum:
374 break;
375 case State::XdgTheme:
378 break;
379 case State::Path:
380 case State::PathFallback:
382 break;
383 case State::Empty:
384 break;
385 }
386}
387
389{
391 QString subtype = u"plain"_s;
393 if (!text.isNull()) {
394 QStringList list = text.split(u'\n');
395 if (!list.isEmpty()) {
396 text = list.at(0);
398 setTheme(text);
399 setPath(QString());
401 } else {
402 setPath(text);
403 setTheme(QString());
405 }
406 }
407 }
408}
409
411{
413 QString subtype = u"plain"_s;
416}
417#endif // QT_CONFIG(clipboard)
418
419} // qdesigner_internal
420
421QT_END_NAMESPACE
void setTheme(const QString &theme)
void setDefaultPixmap(const QPixmap &pixmap)
void setPixmapCache(DesignerPixmapCache *cache)
void setDefaultPixmapIcon(const QIcon &icon)
void setIconThemeModeEnabled(bool enabled)
void contextMenuEvent(QContextMenuEvent *event) override
This event handler, for event event, can be reimplemented in a subclass to receive widget context men...
Combined button and popup list for selecting options.
Auxiliary methods to store/retrieve settings.
static void createIconThemeDialog(QDialog *topLevel, const QString &labelText, QWidget *themeEditor)
static constexpr QSize ICON_SIZE