Libplasma

configview.cpp
1/*
2 SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "configview.h"
8#include <KLocalizedQmlContext>
9
10#include "Plasma/Applet"
11#include "Plasma/Containment"
12#include "appletcontext_p.h"
13#include "appletquickitem.h"
14#include "configcategory_p.h"
15#include "configmodel.h"
16
17#include <QDebug>
18#include <QDir>
19#include <QQmlComponent>
20#include <QQmlContext>
21#include <QQmlEngine>
22#include <QQmlFileSelector>
23#include <QQuickItem>
24
25#include <KAuthorized>
26#include <KLocalizedContext>
27#include <KLocalizedString>
28#include <KPackage/Package>
29
30#include <Plasma/Corona>
31#include <Plasma/PluginLoader>
32#include <qqmlengine.h>
33
34// Unfortunately QWINDOWSIZE_MAX is not exported
35#define DIALOGSIZE_MAX ((1 << 24) - 1)
36
37namespace PlasmaQuick
38{
39//////////////////////////////ConfigView
40
41class ConfigViewPrivate
42{
43public:
44 ConfigViewPrivate(Plasma::Applet *appl, ConfigView *view);
45 ~ConfigViewPrivate() = default;
46
47 void init();
48
49 void updateMinimumWidth();
50 void updateMinimumHeight();
51 void updateMaximumWidth();
52 void updateMaximumHeight();
53 void updateTitle();
54 void mainItemLoaded();
55
56 ConfigView *q;
58 ConfigModel *configModel;
59 ConfigModel *kcmConfigModel;
60 Plasma::Corona *corona;
61 AppletContext *rootContext;
62 QQmlEngine *engine = nullptr;
63 QQuickItem *rootItem = nullptr;
64
65 // Attached Layout property of mainItem, if any
66 QPointer<QObject> mainItemLayout;
67};
68
69ConfigViewPrivate::ConfigViewPrivate(Plasma::Applet *appl, ConfigView *view)
70 : q(view)
71 , applet(appl)
72 , corona(nullptr)
73{
74 engine = new QQmlEngine(q);
75}
76
77void ConfigViewPrivate::init()
78{
79 if (!applet) {
80 qWarning() << "Null applet passed to constructor";
81 return;
82 }
83 if (!applet.data()->pluginMetaData().isValid()) {
84 qWarning() << "Invalid applet passed to constructor";
85 if (applet->containment()) {
86 corona = applet->containment()->corona();
87 }
88 return;
89 }
90
91 rootContext = new AppletContext(q->engine(), applet, nullptr);
92 rootContext->setParent(q->engine());
93
94 applet.data()->setUserConfiguring(true);
95
96 KLocalizedQmlContext *localizedContextObject = new KLocalizedQmlContext(q->engine());
97 localizedContextObject->setTranslationDomain(applet->translationDomain());
98 rootContext->setContextObject(localizedContextObject);
99
100 // FIXME: problem on nvidia, all windows should be transparent or won't show
101 q->setColor(Qt::transparent);
102 updateTitle();
103
104 // systray case
105 if (!applet.data()->containment()->corona()) {
106 Plasma::Applet *a = qobject_cast<Plasma::Applet *>(applet.data()->containment()->parent());
107 if (a) {
108 corona = a->containment()->corona();
109 }
110 } else {
111 if (!applet.data()->containment()->corona()->kPackage().isValid()) {
112 qWarning() << "Invalid home screen package";
113 }
114 corona = applet.data()->containment()->corona();
115 }
116 if (!corona) {
117 qWarning() << "Cannot find a Corona, this should never happen!";
118 return;
119 }
120
121 const auto pkg = corona->kPackage();
122 if (pkg.isValid()) {
123 new QQmlFileSelector(q->engine(), q->engine());
124 }
125
126 if (!qEnvironmentVariableIntValue("PLASMA_NO_CONTEXTPROPERTIES")) {
127 rootContext->setContextProperties({QQmlContext::PropertyPair{QStringLiteral("plasmoid"), QVariant::fromValue(applet.data())},
128 QQmlContext::PropertyPair{QStringLiteral("configDialog"), QVariant::fromValue(q)}});
129 }
130
131 // config model local of the applet
132 QQmlComponent component(q->engine(), applet.data()->configModel());
133 QObject *object = component.create(rootContext);
134 configModel = qobject_cast<ConfigModel *>(object);
135
136 if (configModel) {
137 configModel->setApplet(applet.data());
138 configModel->setParent(q);
139 } else {
140 delete object;
141 }
142
143 QStringList kcms = applet.data()->pluginMetaData().value(u"X-Plasma-ConfigPlugins", QStringList());
144
145 // filter out non-authorized KCMs
146 // KAuthorized expects KCMs with .desktop suffix, so we can't just pass everything
147 // to KAuthorized::authorizeControlModules verbatim
148 kcms.erase(std::remove_if(kcms.begin(),
149 kcms.end(),
150 [](const QString &kcm) {
151 return !KAuthorized::authorizeControlModule(kcm + QLatin1String(".desktop"));
152 }),
153 kcms.end());
154
155 if (!kcms.isEmpty()) {
156 if (!configModel) {
157 configModel = new ConfigModel(q);
158 }
159
160 for (const QString &kcm : std::as_const(kcms)) {
161 // Only look for KCMs in the "kcms_" folder where new QML KCMs live
162 // because we don't support loading QWidgets KCMs
163 KPluginMetaData md(QLatin1String("kcms/") + kcm);
164
165 if (!md.isValid()) {
166 qWarning() << "Could not find" << kcm
167 << "requested by X-Plasma-ConfigPlugins. Ensure that it exists, is a QML KCM, and lives in the 'kcms/' subdirectory.";
168 continue;
169 }
170
171 configModel->appendCategory(md.iconName(), md.name(), QString(), QLatin1String("kcms/") + kcm);
172 }
173 }
174}
175
176void ConfigViewPrivate::updateMinimumWidth()
177{
178 if (mainItemLayout) {
179 q->setMinimumWidth(mainItemLayout.data()->property("minimumWidth").toInt());
180 // Sometimes setMinimumWidth doesn't actually resize: Qt bug?
181
182 q->setWidth(qMax(q->width(), q->minimumWidth()));
183 } else {
184 q->setMinimumWidth(-1);
185 }
186}
187
188void ConfigViewPrivate::updateMinimumHeight()
189{
190 if (mainItemLayout) {
191 q->setMinimumHeight(mainItemLayout.data()->property("minimumHeight").toInt());
192 // Sometimes setMinimumHeight doesn't actually resize: Qt bug?
193
194 q->setHeight(qMax(q->height(), q->minimumHeight()));
195 } else {
196 q->setMinimumHeight(-1);
197 }
198}
199
200void ConfigViewPrivate::updateMaximumWidth()
201{
202 if (mainItemLayout) {
203 const int hint = mainItemLayout.data()->property("maximumWidth").toInt();
204
205 if (hint > 0) {
206 q->setMaximumWidth(hint);
207 } else {
208 q->setMaximumWidth(DIALOGSIZE_MAX);
209 }
210 } else {
211 q->setMaximumWidth(DIALOGSIZE_MAX);
212 }
213}
214
215void ConfigViewPrivate::updateMaximumHeight()
216{
217 if (mainItemLayout) {
218 const int hint = mainItemLayout.data()->property("maximumHeight").toInt();
219
220 if (hint > 0) {
221 q->setMaximumHeight(hint);
222 } else {
223 q->setMaximumHeight(DIALOGSIZE_MAX);
224 }
225 } else {
226 q->setMaximumHeight(DIALOGSIZE_MAX);
227 }
228}
229
230void ConfigViewPrivate::updateTitle()
231{
232 QVariant itemTitle = rootItem ? rootItem->property("title") : QVariant();
233 q->setTitle(itemTitle.canConvert<QString>() ? i18n("%1 — %2 Settings", itemTitle.toString(), applet.data()->title())
234 : i18n("%1 Settings", applet.data()->title()));
235}
236
237void ConfigViewPrivate::mainItemLoaded()
238{
239 if (applet) {
240 KConfigGroup cg = applet.data()->config();
241 cg = KConfigGroup(&cg, QStringLiteral("ConfigDialog"));
242 q->resize(cg.readEntry("DialogWidth", q->width()), cg.readEntry("DialogHeight", q->height()));
243
244 if (rootItem->property("title").isValid()) {
245 QObject::connect(rootItem, SIGNAL(titleChanged()), q, SLOT(updateTitle()));
246 updateTitle();
247 }
248 }
249
250 // Extract the representation's Layout, if any
251 QObject *layout = nullptr;
252
253 // Search a child that has the needed Layout properties
254 // HACK: here we are not type safe, but is the only way to access to a pointer of Layout
255 const auto children = rootItem->children();
256 for (QObject *child : children) {
257 // find for the needed property of Layout: minimum/maximum/preferred sizes and fillWidth/fillHeight
258 if (child->property("minimumWidth").isValid() && child->property("minimumHeight").isValid() && child->property("preferredWidth").isValid()
259 && child->property("preferredHeight").isValid() && child->property("maximumWidth").isValid() && child->property("maximumHeight").isValid()
260 && child->property("fillWidth").isValid() && child->property("fillHeight").isValid()) {
261 layout = child;
262 break;
263 }
264 }
265 mainItemLayout = layout;
266
267 if (layout) {
268 QObject::connect(layout, SIGNAL(minimumWidthChanged()), q, SLOT(updateMinimumWidth()));
269 QObject::connect(layout, SIGNAL(minimumHeightChanged()), q, SLOT(updateMinimumHeight()));
270 QObject::connect(layout, SIGNAL(maximumWidthChanged()), q, SLOT(updateMaximumWidth()));
271 QObject::connect(layout, SIGNAL(maximumHeightChanged()), q, SLOT(updateMaximumHeight()));
272
273 updateMinimumWidth();
274 updateMinimumHeight();
275 updateMaximumWidth();
276 updateMaximumHeight();
277 }
278}
279
280ConfigView::ConfigView(Plasma::Applet *applet, QWindow *parent)
281 : QQuickWindow(parent)
282 , d(new ConfigViewPrivate(applet, this))
283{
284 setIcon(QIcon::fromTheme(QStringLiteral("configure")));
285 // Only register types once
286 [[maybe_unused]] static int configModelRegisterResult = qmlRegisterType<ConfigModel>("org.kde.plasma.configuration", 2, 0, "ConfigModel");
287 [[maybe_unused]] static int configCategoryRegisterResult = qmlRegisterType<ConfigCategory>("org.kde.plasma.configuration", 2, 0, "ConfigCategory");
288 d->init();
289 connect(applet, &QObject::destroyed, this, &ConfigView::close);
290}
291
292ConfigView::~ConfigView()
293{
294 if (d->applet) {
295 d->applet.data()->setUserConfiguring(false);
296 if (d->applet.data()->containment() && d->applet.data()->containment()->corona()) {
297 d->applet.data()->containment()->corona()->requestConfigSync();
298 }
299 }
300 delete d->rootItem;
301}
302
303QQmlEngine *ConfigView::engine()
304{
305 return d->engine;
306}
307
308QQmlContext *ConfigView::rootContext()
309{
310 return d->rootContext;
311}
312
313void ConfigView::setSource(const QUrl &src)
314{
315 QQmlComponent uiComponent(engine(), src);
316 if (uiComponent.isError()) {
317 for (const auto &error : uiComponent.errors()) {
318 qWarning() << error;
319 }
320 }
321
322 std::unique_ptr<QObject> object(uiComponent.createWithInitialProperties({{QStringLiteral("parent"), QVariant::fromValue(contentItem())}}, d->rootContext));
323 d->rootItem = qobject_cast<QQuickItem *>(object.get());
324 if (!d->rootItem) {
325 return;
326 }
327 Q_UNUSED(object.release());
328 d->mainItemLoaded();
329
330 if (d->rootItem->implicitHeight() > 0 || d->rootItem->implicitWidth() > 0) {
331 resize(QSize(d->rootItem->implicitWidth(), d->rootItem->implicitHeight()));
332 }
333 d->rootItem->setSize(QSizeF(width(), height()));
334
335 connect(d->rootItem, &QQuickItem::implicitWidthChanged, this, [this]() {
336 setWidth(d->rootItem->implicitWidth());
337 });
338 connect(d->rootItem, &QQuickItem::implicitHeightChanged, this, [this]() {
339 setWidth(d->rootItem->implicitHeight());
340 });
341}
342
343QQuickItem *ConfigView::rootObject()
344{
345 return d->rootItem;
346}
347
348void ConfigView::init()
349{
350 setSource(d->corona->kPackage().fileUrl("appletconfigurationui"));
351}
352
353Plasma::Applet *ConfigView::applet()
354{
355 return d->applet.data();
356}
357
358ConfigModel *ConfigView::configModel() const
359{
360 return d->configModel;
361}
362
363QString ConfigView::appletGlobalShortcut() const
364{
365 if (!d->applet) {
366 return QString();
367 }
368
369 return d->applet.data()->globalShortcut().toString();
370}
371
372void ConfigView::setAppletGlobalShortcut(const QString &shortcut)
373{
374 if (!d->applet || d->applet.data()->globalShortcut().toString().toLower() == shortcut.toLower()) {
375 return;
376 }
377
378 d->applet.data()->setGlobalShortcut(shortcut);
379 Q_EMIT appletGlobalShortcutChanged();
380}
381
382// To emulate Qt::WA_DeleteOnClose that QWindow doesn't have
383void ConfigView::hideEvent(QHideEvent *ev)
384{
386 deleteLater();
387}
388
389void ConfigView::resizeEvent(QResizeEvent *re)
390{
391 if (!d->rootItem) {
392 return;
393 }
394
395 d->rootItem->setSize(re->size());
396
397 if (d->applet) {
398 KConfigGroup cg = d->applet.data()->config();
399 cg = KConfigGroup(&cg, QStringLiteral("ConfigDialog"));
400 cg.writeEntry("DialogWidth", re->size().width());
401 cg.writeEntry("DialogHeight", re->size().height());
402 }
403
405}
406
407}
408
409#include "moc_configview.cpp"
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
KConfig * config()
QString readEntry(const char *key, const char *aDefault=nullptr) const
The base Applet class.
Definition applet.h:64
Plasma::Containment * containment
The Containment managing this applet.
Definition applet.h:189
Plasma::Corona * corona
The corona for this contaiment.
Definition containment.h:59
A bookkeeping Scene for Plasma::Applets.
Definition corona.h:28
QString i18n(const char *text, const TYPE &arg...)
QAction * hint(const QObject *recvr, const char *slot, QObject *parent)
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
const QList< QKeySequence > & shortcut(StandardShortcut id)
The EdgeEventForwarder class This class forwards edge events to be replayed within the given margin T...
Definition action.h:20
QVariant data() const const
QIcon fromTheme(const QString &name)
iterator begin()
pointer data()
iterator end()
iterator erase(const_iterator begin, const_iterator end)
bool isEmpty() const const
const QObjectList & children() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void destroyed(QObject *obj)
void implicitHeightChanged()
void implicitWidthChanged()
virtual void hideEvent(QHideEvent *) override
virtual void resizeEvent(QResizeEvent *ev) override
const QSize & size() const const
int height() const const
int width() const const
QChar * data()
transparent
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool canConvert() const const
QVariant fromValue(T &&value)
int toInt(bool *ok) const const
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 3 2025 11:57:46 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.