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

KDE's Doxygen guidelines are available online.