From c1e3fd9f3e433372118b5d3fd68b1a55790e9909 Mon Sep 17 00:00:00 2001
From: Thomas Hartmann <thomas.hartmann@qt.io>
Date: Fri, 7 Oct 2016 08:41:57 +0200
Subject: [PATCH] QmlDesigner: Implementing NodeHints

This class evaluates expressions from .metainfo files.
Those expressions can script and control the behavior of
items in the designer.

Change-Id: I9f1ec7dd70f1124a684afe6620de5b81c8cc5a30
Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
---
 .../designercore/designercore-lib.pri         |   2 +
 .../designercore/include/nodehints.h          | 111 ++++++++
 .../designercore/metainfo/nodehints.cpp       | 258 ++++++++++++++++++
 src/plugins/qmldesigner/qmldesignerplugin.qbs |   2 +
 4 files changed, 373 insertions(+)
 create mode 100644 src/plugins/qmldesigner/designercore/include/nodehints.h
 create mode 100644 src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp

diff --git a/src/plugins/qmldesigner/designercore/designercore-lib.pri b/src/plugins/qmldesigner/designercore/designercore-lib.pri
index ab3a4eeebc7..8243afd5529 100644
--- a/src/plugins/qmldesigner/designercore/designercore-lib.pri
+++ b/src/plugins/qmldesigner/designercore/designercore-lib.pri
@@ -19,6 +19,7 @@ SOURCES += $$PWD/model/abstractview.cpp \
     $$PWD/metainfo/metainfo.cpp \
     $$PWD/metainfo/metainforeader.cpp \
     $$PWD/metainfo/nodemetainfo.cpp \
+    $$PWD/metainfo/nodehints.cpp \
     $$PWD/metainfo/itemlibraryinfo.cpp \
     $$PWD/metainfo/subcomponentmanager.cpp \
     $$PWD/model/internalproperty.cpp \
@@ -87,6 +88,7 @@ HEADERS += $$PWD/include/qmldesignercorelib_global.h \
     $$PWD/include/metainfo.h \
     $$PWD/include/metainforeader.h \
     $$PWD/include/nodemetainfo.h \
+    $$PWD/include/nodehints.h \
     $$PWD/include/itemlibraryinfo.h \
     $$PWD/model/internalproperty.h \
     $$PWD/include/modelnode.h \
diff --git a/src/plugins/qmldesigner/designercore/include/nodehints.h b/src/plugins/qmldesigner/designercore/include/nodehints.h
new file mode 100644
index 00000000000..01222c2e4ae
--- /dev/null
+++ b/src/plugins/qmldesigner/designercore/include/nodehints.h
@@ -0,0 +1,111 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QList>
+#include <QString>
+#include "modelnode.h"
+
+#include "qmldesignercorelib_global.h"
+#include "invalidmetainfoexception.h"
+
+QT_BEGIN_NAMESPACE
+class QDeclarativeContext;
+QT_END_NAMESPACE
+
+namespace QmlDesigner {
+
+class MetaInfo;
+class Model;
+class AbstractProperty;
+
+namespace Internal {
+    class MetaInfoPrivate;
+    class MetaInfoReader;
+    class SubComponentManagerPrivate;
+    class ItemLibraryEntryData;
+    class NodeMetaInfoPrivate;
+}
+
+class QMLDESIGNERCORE_EXPORT NodeHints
+{
+public:
+    NodeHints();
+    NodeHints(const ModelNode &modelNode);
+
+    bool canBeContainer() const;
+    bool forceClip() const;
+    bool doesLayoutChildren() const;
+    bool canBeDroppedInFormEditor() const;
+    bool canBeDroppedInNavigator() const;
+    bool isMovable() const;
+    bool isStackedContainer() const;
+    QString indexPropertyForStackedContainer() const;
+
+    QHash<QString, QString> hints() const;
+
+private:
+    ModelNode modelNode() const;
+    bool isValid() const;
+    Model *model() const;
+    bool evaluateBooleanExpression(const QString &hintName, bool defaultValue) const;
+
+    ModelNode m_modelNode;
+    QHash<QString, QString> m_hints;
+};
+
+namespace Internal {
+
+class JSObject : public QObject {
+
+    Q_OBJECT
+
+    Q_PROPERTY(bool hasParent READ hasParent NOTIFY modelNodeChanged)
+    Q_PROPERTY(bool hasChildren READ hasChildren NOTIFY modelNodeChanged)
+    Q_PROPERTY(bool currentParentIsRoot READ currentParentIsRoot NOTIFY modelNodeChanged)
+
+public:
+    JSObject();
+    JSObject(QObject *parent = 0);
+    void setModelNode(const ModelNode &node);
+    bool hasParent() const;
+    bool hasChildren() const;
+    bool currentParentIsRoot() const;
+
+    Q_INVOKABLE bool isSubclassOf(const QString &typeName);
+    Q_INVOKABLE bool rootItemIsSubclassOf(const QString &typeName);
+    Q_INVOKABLE bool currentParentIsSubclassOf(const QString &typeName);
+
+signals:
+    void modelNodeChanged();
+
+private:
+    ModelNode m_modelNode;
+};
+
+} //Internal
+
+} //QmlDesigner
diff --git a/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp
new file mode 100644
index 00000000000..477796516db
--- /dev/null
+++ b/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp
@@ -0,0 +1,258 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "nodehints.h"
+#include "model.h"
+
+#include "metainfo.h"
+#include <enumeration.h>
+#include <rewriterview.h>
+#include <propertyparser.h>
+#include <nodeabstractproperty.h>
+
+#include <QDebug>
+
+#include <qmljs/qmljsscopechain.h>
+#include <qmljs/parser/qmljsast_p.h>
+#include <qmljs/qmljsmodelmanagerinterface.h>
+#include <qmljs/qmljsvalueowner.h>
+#include <languageutils/fakemetaobject.h>
+
+#include <utils/qtcassert.h>
+
+#include <itemlibraryinfo.h>
+
+#include <QJSEngine>
+
+namespace QmlDesigner {
+
+
+namespace Internal {
+
+static QJSEngine *s_qJSEngine= nullptr;
+static JSObject *s_jsObject = nullptr;
+
+static QVariant evaluateExpression(const QString &expression, const ModelNode &modelNode)
+{
+    if (!s_qJSEngine) {
+        s_qJSEngine = new QJSEngine;
+        s_jsObject = new JSObject(s_qJSEngine);
+        QJSValue jsValue = s_qJSEngine->newQObject(s_jsObject);
+        s_qJSEngine->globalObject().setProperty("model", jsValue);
+    }
+
+    s_jsObject->setModelNode(modelNode);
+    return s_qJSEngine->evaluate(expression).toVariant();
+}
+
+} //Internal
+
+QmlDesigner::NodeHints::NodeHints()
+{
+
+}
+
+QmlDesigner::NodeHints::NodeHints(const ModelNode &node) : m_modelNode(node)
+{
+    if (isValid()) {
+        const ItemLibraryInfo *libraryInfo = model()->metaInfo().itemLibraryInfo();
+        QList <ItemLibraryEntry> itemLibraryEntryList = libraryInfo->entriesForType(
+                    modelNode().type(), modelNode().majorVersion(), modelNode().minorVersion());
+        m_hints =  itemLibraryEntryList.first().hints();
+    }
+}
+
+bool NodeHints::canBeContainer() const
+{
+    /* The default is true for now to avoid confusion. Once our .metaInfo files in Qt
+       use the feature we can change the default to false. */
+
+    if (!isValid())
+        return true;
+
+    return evaluateBooleanExpression("canBeContainer", true);
+}
+
+bool NodeHints::forceClip() const
+{
+    if (!isValid())
+        return false;
+
+    return evaluateBooleanExpression("forceClip", false);
+}
+
+bool NodeHints::doesLayoutChildren() const
+{
+    if (!isValid())
+        return false;
+
+    return evaluateBooleanExpression("doesLayoutChildren", false);
+}
+
+bool NodeHints::canBeDroppedInFormEditor() const
+{
+    if (!isValid())
+        return false;
+
+    return evaluateBooleanExpression("canBeDroppedInFormEditor", false);
+}
+
+bool NodeHints::canBeDroppedInNavigator() const
+{
+    if (!isValid())
+        return false;
+
+    return evaluateBooleanExpression("canBeDroppedInNavigator", false);
+}
+
+bool NodeHints::isMovable() const
+{
+    if (!isValid())
+        return true;
+
+    return evaluateBooleanExpression("isMovable", true);
+}
+
+bool NodeHints::isStackedContainer() const
+{
+    if (!isValid())
+        return false;
+
+    return evaluateBooleanExpression("isStackedContainer", false);
+}
+
+QString NodeHints::indexPropertyForStackedContainer() const
+{
+    if (!isValid())
+        return QString();
+
+    const QString expression = m_hints.value("indexPropertyForStackedContainer");
+
+    if (expression.isEmpty())
+        return QString();
+
+    return Internal::evaluateExpression(expression, modelNode()).toString();
+}
+
+QHash<QString, QString> NodeHints::hints() const
+{
+    return m_hints;
+}
+
+ModelNode NodeHints::modelNode() const
+{
+    return m_modelNode;
+}
+
+bool NodeHints::isValid() const
+{
+    return modelNode().isValid();
+}
+
+Model *NodeHints::model() const
+{
+    return modelNode().model();
+}
+
+bool NodeHints::evaluateBooleanExpression(const QString &hintName, bool defaultValue) const
+{
+    const QString expression = m_hints.value(hintName);
+
+    if (expression.isEmpty())
+        return defaultValue;
+
+    return Internal::evaluateExpression(expression, modelNode()).toBool();
+}
+
+namespace Internal {
+
+QmlDesigner::Internal::JSObject::JSObject::JSObject(QObject *parent) : QObject(parent)
+{
+
+}
+
+void JSObject::setModelNode(const ModelNode &node)
+{
+    m_modelNode = node;
+    emit modelNodeChanged();
+}
+
+bool JSObject::hasParent() const
+{
+    return !m_modelNode.isRootNode()
+            && m_modelNode.hasParentProperty();
+}
+
+bool JSObject::hasChildren() const
+{
+    return m_modelNode.hasAnySubModelNodes();
+}
+
+bool JSObject::currentParentIsRoot() const
+{
+    return m_modelNode.hasParentProperty()
+            && m_modelNode.parentProperty().isValid()
+            && m_modelNode.parentProperty().parentModelNode().isRootNode();
+}
+
+bool JSObject::isSubclassOf(const QString &typeName)
+{
+    NodeMetaInfo metaInfo = m_modelNode.metaInfo();
+
+    if (metaInfo.isValid())
+        metaInfo.isSubclassOf(typeName.toUtf8());
+
+    return false;
+}
+
+bool JSObject::rootItemIsSubclassOf(const QString &typeName)
+{
+    NodeMetaInfo metaInfo = m_modelNode.view()->rootModelNode().metaInfo();
+
+    if (metaInfo.isValid())
+        metaInfo.isSubclassOf(typeName.toUtf8());
+
+    return false;
+}
+
+bool JSObject::currentParentIsSubclassOf(const QString &typeName)
+{
+    if ( m_modelNode.hasParentProperty()
+         && m_modelNode.parentProperty().isValid()) {
+        NodeMetaInfo metaInfo =  m_modelNode.parentProperty().parentModelNode().metaInfo();
+        if (metaInfo.isValid())
+            metaInfo.isSubclassOf(typeName.toUtf8());
+    }
+    return false;
+}
+
+JSObject::JSObject()
+{
+
+}
+
+} //Internal
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs
index b9954200c11..8e24fd09a7f 100644
--- a/src/plugins/qmldesigner/qmldesignerplugin.qbs
+++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs
@@ -257,6 +257,7 @@ Project {
                 "include/nodeinstanceview.h",
                 "include/nodelistproperty.h",
                 "include/nodemetainfo.h",
+                "include/nodehints.h",
                 "include/nodeproperty.h",
                 "include/notimplementedexception.h",
                 "include/plaintexteditmodifier.h",
@@ -294,6 +295,7 @@ Project {
                 "metainfo/metainfo.cpp",
                 "metainfo/metainforeader.cpp",
                 "metainfo/nodemetainfo.cpp",
+                "metainfo/nodehints.cpp",
                 "metainfo/subcomponentmanager.cpp",
                 "model/abstractproperty.cpp",
                 "model/abstractview.cpp",
-- 
GitLab