File indexing completed on 2024-05-19 15:46:45

0001 /*
0002     SPDX-FileCopyrightText: 2013 Sven Brauch <svenbrauch@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "propertypreviewwidget.h"
0008 
0009 #include <QQuickWidget>
0010 #include <QQuickItem>
0011 #include <QQmlContext>
0012 #include <QHBoxLayout>
0013 #include <QLabel>
0014 #include <QStandardPaths>
0015 
0016 #include <KLocalizedString>
0017 #include <KLocalizedContext>
0018 #include <KTextEditor/Document>
0019 #include <KTextEditor/View>
0020 #include <KDeclarative/KDeclarative>
0021 
0022 #include <language/duchain/ducontext.h>
0023 #include <language/duchain/duchainlock.h>
0024 
0025 // List of supported properties. The string must be the name of the property,
0026 // which can contain dots if necessary
0027 QMultiHash<QString, SupportedProperty> PropertyPreviewWidget::supportedProperties;
0028 
0029 QWidget* PropertyPreviewWidget::constructIfPossible(KTextEditor::Document* doc,
0030                                                     const KTextEditor::Range& keyRange,
0031                                                     const KTextEditor::Range& valueRange,
0032                                                     Declaration* decl,
0033                                                     const QString& key,
0034                                                     const QString& value)
0035 {
0036 #define PROP(key, filename, type, class) \
0037     supportedProperties.insert(key, SupportedProperty(QUrl(base + filename), type, class));
0038 
0039     if ( supportedProperties.isEmpty() ) {
0040         QString base = QStandardPaths::locate(
0041             QStandardPaths::GenericDataLocation,
0042             QStringLiteral("kdevqmljssupport/propertywidgets"),
0043             QStandardPaths::LocateDirectory
0044         ) + QLatin1Char('/');
0045 
0046         // Positioning
0047         PROP(QStringLiteral("width"), QLatin1String("Width.qml"), QString(), QString())
0048         PROP(QStringLiteral("height"), QLatin1String("Height.qml"), QString(), QString())
0049         PROP(QStringLiteral("spacing"), QLatin1String("Spacing.qml"), QString(), QString())
0050 
0051         // Margins
0052         PROP(QStringLiteral("margins"), QLatin1String("Spacing.qml"), QString(), QStringLiteral("QQuickAnchors"));         // matches anchors.margins and anchors { margins: }
0053         PROP(QStringLiteral("margins"), QLatin1String("Spacing.qml"), QString(), QStringLiteral("QDeclarativeAnchors"));
0054         PROP(QStringLiteral("leftMargin"), QLatin1String("Spacing.qml"), QString(), QStringLiteral("QQuickAnchors"));
0055         PROP(QStringLiteral("leftMargin"), QLatin1String("Spacing.qml"), QString(), QStringLiteral("QDeclarativeAnchors"));
0056         PROP(QStringLiteral("rightMargin"), QLatin1String("Spacing.qml"), QString(), QStringLiteral("QQuickAnchors"));
0057         PROP(QStringLiteral("rightMargin"), QLatin1String("Spacing.qml"), QString(), QStringLiteral("QDeclarativeAnchors"));
0058         PROP(QStringLiteral("topMargin"), QLatin1String("Spacing.qml"), QString(), QStringLiteral("QQuickAnchors"));
0059         PROP(QStringLiteral("topMargin"), QLatin1String("Spacing.qml"), QString(), QStringLiteral("QDeclarativeAnchors"));
0060         PROP(QStringLiteral("bottomMargin"), QLatin1String("Spacing.qml"), QString(), QStringLiteral("QQuickAnchors"));
0061         PROP(QStringLiteral("bottomMargin"), QLatin1String("Spacing.qml"), QString(), QStringLiteral("QDeclarativeAnchors"));
0062 
0063         // Animations
0064         PROP(QStringLiteral("duration"), QLatin1String("Duration.qml"), QString(), QString())
0065 
0066         // Font QDeclarativeFontValueType, QQuickFontValueType
0067         PROP(QStringLiteral("family"), QLatin1String("FontFamily.qml"), QString(), QStringLiteral("QDeclarativeFontValueType"))
0068         PROP(QStringLiteral("family"), QLatin1String("FontFamily.qml"), QString(), QStringLiteral("QQuickFontValueType"))
0069         PROP(QStringLiteral("pointSize"), QLatin1String("FontSize.qml"), QString(), QStringLiteral("QDeclarativeFontValueType"))
0070         PROP(QStringLiteral("pointSize"), QLatin1String("FontSize.qml"), QString(), QStringLiteral("QQuickFontValueType"))
0071 
0072         // Appearance
0073         PROP(QStringLiteral("opacity"), QLatin1String("Opacity.qml"), QString(), QString())
0074 
0075         // Type-dependent widgets
0076         PROP(QString(), QLatin1String("ColorPicker.qml"), QStringLiteral("color"), QString())
0077     }
0078 #undef PROP
0079 
0080     QList<SupportedProperty> properties;
0081 
0082     properties << supportedProperties.values(key.section(QLatin1Char('.'), -1, -1));
0083     properties << supportedProperties.values(QString());
0084 
0085     // Explore each possible supported property and return the first supported widget
0086     DUChainReadLocker lock;
0087 
0088     for (const SupportedProperty& property : qAsConst(properties)) {
0089         if (!decl || !decl->abstractType() || !decl->context() || !decl->context()->owner()) {
0090             continue;
0091         }
0092 
0093         if (!decl->abstractType()->toString().contains(property.typeContains)) {
0094             continue;
0095         }
0096 
0097         if (!decl->context()->owner()->toString().contains(property.classContains)) {
0098             continue;
0099         }
0100 
0101         return new PropertyPreviewWidget(doc, keyRange, valueRange, property, value);
0102     }
0103 
0104     return nullptr;
0105 }
0106 
0107 void PropertyPreviewWidget::updateValue()
0108 {
0109     QString newValue = view->rootObject()->property("value").toString();
0110 
0111     // set the cursor to the edited range, otherwise the view will jump if we call doc->endEditing()
0112     //document->activeView()->setCursorPosition(KTextEditor::Cursor(valueRange.start.line, valueRange.start.column));
0113     if (valueRange.end().column() - valueRange.start().column() == newValue.size()) {
0114         document->replaceText(valueRange, newValue);
0115     }
0116     else {
0117         // the length of the text changed so don't replace it but remove the old
0118         // and insert the new text.
0119         KTextEditor::Document::EditingTransaction transaction(document);
0120         document->removeText(valueRange);
0121         document->insertText(valueRange.start(), newValue);
0122 
0123         valueRange.setRange(
0124             valueRange.start(),
0125             KTextEditor::Cursor(valueRange.start().line(), valueRange.start().column() + newValue.size())
0126         );
0127     }
0128 }
0129 
0130 PropertyPreviewWidget::~PropertyPreviewWidget()
0131 {
0132 }
0133 
0134 PropertyPreviewWidget::PropertyPreviewWidget(KTextEditor::Document* doc,
0135                                              const KTextEditor::Range& keyRange, const KTextEditor::Range& valueRange,
0136                                              const SupportedProperty& property, const QString& value)
0137     : QWidget()
0138     , view(new QQuickWidget)
0139     , document(doc)
0140     , keyRange(keyRange)
0141     , valueRange(valueRange)
0142     , property(property)
0143 {
0144     //setup kdeclarative library
0145     KDeclarative::KDeclarative::setupEngine(view->engine());
0146     KLocalizedContext *localizedContextObject = new KLocalizedContext(view->engine());
0147     localizedContextObject->setTranslationDomain(QStringLiteral("kdevqmljs"));
0148     view->engine()->rootContext()->setContextObject(localizedContextObject);
0149 
0150     // Configure layout
0151     auto l = new QHBoxLayout;
0152 
0153     l->setContentsMargins(0, 0, 0, 0);
0154     setLayout(l);
0155 
0156     // see docstring for ILanguageSupport::specialLanguageObjectNavigationWidget
0157     setProperty("DoNotCloseOnCursorMove", true);
0158 
0159     view->setSource(property.qmlfile);
0160 
0161     if (!view->rootObject()) {
0162         // don't crash because of a syntax error or missing QML file
0163         l->addWidget(new QLabel(i18n("Error loading QML file: %1", property.qmlfile.path())));
0164         delete view;
0165         return;
0166     }
0167 
0168     // set the initial value read from the document
0169     view->rootObject()->setProperty("initialValue", value);
0170 
0171     // connect to the slot which has to be emitted from QML when the value changes
0172     QObject::connect(view->rootObject(), SIGNAL(valueChanged()),
0173                      this, SLOT(updateValue()));
0174     l->addWidget(view);
0175 }
0176 
0177 #include "moc_propertypreviewwidget.cpp"