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

KDE's Doxygen guidelines are available online.