// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "connectioneditorutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace QmlDesigner { void callLater(const std::function &fun) { QTimer::singleShot(0, fun); } void showErrorMessage(const QString &text) { callLater([text]() { QMessageBox::warning(nullptr, Tr::tr("Error"), text); }); } QString idOrTypeName(const ModelNode &modelNode) { QString idLabel = modelNode.id(); if (idLabel.isEmpty()) idLabel = modelNode.simplifiedTypeName(); return idLabel; } PropertyName uniquePropertyName(const PropertyName &suggestion, const ModelNode &modelNode) { PropertyName name = suggestion; if (!modelNode.isValid() || !modelNode.metaInfo().isValid()) return name; int i = 0; while (true) { if (!modelNode.hasProperty(name) && !modelNode.metaInfo().hasProperty(name)) return name; name = suggestion + QString::number(i++).toLatin1(); } return {}; } NodeMetaInfo dynamicTypeMetaInfo(const AbstractProperty& property) { // Note: Uses old mechanism to create the NodeMetaInfo and supports // only types we care about in the connection editor. // TODO: Support all possible AbstractProperty types and move to the // AbstractProperty class. if (property.dynamicTypeName() == "bool") return property.model()->boolMetaInfo(); else if (property.dynamicTypeName() == "int") return property.model()->metaInfo("QML.int"); else if (property.dynamicTypeName() == "real") return property.model()->metaInfo("QML.real"); else if (property.dynamicTypeName() == "color") return property.model()->metaInfo("QML.color"); else if (property.dynamicTypeName() == "string") return property.model()->metaInfo("QML.string"); else if (property.dynamicTypeName() == "url") return property.model()->metaInfo("QML.url"); else if (property.dynamicTypeName() == "variant") return property.model()->metaInfo("QML.variant"); else qWarning() << __FUNCTION__ << " type " << property.dynamicTypeName() << "not found"; return { }; } QVariant typeConvertVariant(const QVariant &variant, const QmlDesigner::TypeName &typeName) { QVariant returnValue = variant; if (typeName == "int") { bool ok; returnValue = variant.toInt(&ok); if (!ok) returnValue = 0; } else if (typeName == "real") { bool ok; returnValue = variant.toReal(&ok); if (!ok) returnValue = 0.0; } else if (typeName == "string") { returnValue = variant.toString(); } else if (typeName == "bool") { returnValue = variant.toBool(); } else if (typeName == "url") { returnValue = variant.toUrl(); } else if (typeName == "color") { if (QColor::isValidColor(variant.toString())) returnValue = variant.toString(); else returnValue = QColor(Qt::black); } else if (typeName == "vector2d") { returnValue = "Qt.vector2d(0, 0)"; } else if (typeName == "vector3d") { returnValue = "Qt.vector3d(0, 0, 0)"; } else if (typeName == "vector4d") { returnValue = "Qt.vector4d(0, 0, 0 ,0)"; } else if (typeName == "TextureInput") { returnValue = "null"; } else if (typeName == "alias") { returnValue = "null"; } else if (typeName == "Item") { returnValue = "null"; } return returnValue; } template void convertPropertyType(const T &property, const QVariant &value) { if (!property.isValid()) return; ModelNode node = property.parentModelNode(); if (!node.isValid()) return; PropertyName name = property.name(); TypeName type = property.dynamicTypeName(); node.removeProperty(name); if constexpr (std::is_same_v) { BindingProperty newProperty = node.bindingProperty(name); if (newProperty.isValid()) newProperty.setDynamicTypeNameAndExpression(type, value.toString()); } else if constexpr (std::is_same_v) { VariantProperty newProperty = node.variantProperty(name); if (newProperty.isValid()) newProperty.setDynamicTypeNameAndValue(type, value); } } void convertVariantToBindingProperty(const VariantProperty &property, const QVariant &value) { convertPropertyType(property, value); } void convertBindingToVariantProperty(const BindingProperty &property, const QVariant &value) { convertPropertyType(property, value); } bool isBindingExpression(const QVariant& value) { if (value.metaType().id() != QMetaType::QString) return false; QRegularExpression regexp("^[a-z_]\\w*|^[A-Z]\\w*\\.{1}([a-z_]\\w*\\.?)+"); QRegularExpressionMatch match = regexp.match(value.toString()); return match.hasMatch(); } bool isDynamicVariantPropertyType(const TypeName &type) { // "variant" is considered value type as it is initialized as one. // This may need to change if we provide any kind of proper editor for it. static const QSet valueTypes{"int", "real", "color", "string", "bool", "url", "variant"}; return valueTypes.contains(type); } QVariant defaultValueForType(const TypeName &type) { QVariant value; if (type == "int") value = 0; else if (type == "real") value = 0.0; else if (type == "color") value = QColor(255, 255, 255); else if (type == "string") value = "This is a string"; else if (type == "bool") value = false; else if (type == "url") value = ""; else if (type == "variant") value = ""; return value; } QString defaultExpressionForType(const TypeName &type) { QString expression; if (type == "alias") expression = "null"; else if (type == "TextureInput") expression = "null"; else if (type == "vector2d") expression = "Qt.vector2d(0, 0)"; else if (type == "vector3d") expression = "Qt.vector3d(0, 0, 0)"; else if (type == "vector4d") expression = "Qt.vector4d(0, 0, 0 ,0)"; return expression; } QStringList availableModelNodes(AbstractView *view) { QStringList sourceNodes; for (const ModelNode &modelNode : view->allModelNodes()) { if (!modelNode.id().isEmpty()) sourceNodes.append(modelNode.id()); } std::sort(sourceNodes.begin(), sourceNodes.end()); return sourceNodes; } QStringList dynamicPropertyNamesFromNode(const ModelNode& node) { QStringList dynamicProperties; for (const VariantProperty &variantProperty : node.variantProperties()) { if (variantProperty.isDynamic()) dynamicProperties << QString::fromUtf8(variantProperty.name()); } for (const BindingProperty &bindingProperty : node.bindingProperties()) { if (bindingProperty.isDynamic()) dynamicProperties << QString::fromUtf8((bindingProperty.name())); } return dynamicProperties; } QStringList availableTargetProperties(const BindingProperty &bindingProperty) { const ModelNode modelNode = bindingProperty.parentModelNode(); if (!modelNode.isValid()) { qWarning() << __FUNCTION__ << " invalid model node"; return {}; } NodeMetaInfo metaInfo = modelNode.metaInfo(); if (metaInfo.isValid()) { const auto properties = metaInfo.properties(); QStringList writableProperties; writableProperties.reserve(static_cast(properties.size())); for (const auto &property : properties) { if (property.isWritable()) writableProperties.push_back(QString::fromUtf8(property.name())); } return dynamicPropertyNamesFromNode(modelNode) + writableProperties; } return dynamicPropertyNamesFromNode(modelNode); } ModelNode getNodeByIdOrParent(AbstractView *view, const QString &id, const ModelNode &targetNode) { if (id != QLatin1String("parent")) return view->modelNodeForId(id); if (targetNode.hasParentProperty()) return targetNode.parentProperty().parentModelNode(); return {}; } bool metaInfoIsCompatible(const NodeMetaInfo& sourceType, const PropertyMetaInfo& metaInfo) { if (sourceType.isVariant()) return true; NodeMetaInfo targetType = metaInfo.propertyType(); if (sourceType.isBool() && targetType.isBool()) return true; if (sourceType == targetType) return true; if (sourceType.isNumber() && targetType.isNumber()) return true; if (sourceType.isString() && targetType.isString()) return true; if (sourceType.isUrl() && targetType.isUrl()) return true; if (sourceType.isColor() && targetType.isColor()) return true; return false; } QStringList availableSourceProperties(const BindingProperty &bindingProperty, AbstractView *view) { const QString expression = bindingProperty.expression(); const QStringList stringlist = expression.split(QLatin1String(".")); QStringList possibleProperties; const QString &id = stringlist.constFirst(); ModelNode modelNode = getNodeByIdOrParent(view, id, bindingProperty.parentModelNode()); if (!modelNode.isValid()) { //if it's not a valid model node, maybe it's a singleton if (RewriterView *rv = view->rewriterView()) { for (const QmlTypeData &data : rv->getQMLTypes()) { if (!data.typeName.isEmpty() && data.typeName == id) { NodeMetaInfo metaInfo = view->model()->metaInfo(data.typeName.toUtf8()); if (metaInfo.isValid()) { for (const auto &property : metaInfo.properties()) { //without check for now possibleProperties.push_back(QString::fromUtf8(property.name())); } return possibleProperties; } } } } qWarning() << __FUNCTION__ << " invalid model node"; return QStringList(); } possibleProperties = possibleProperties + dynamicPropertyNamesFromNode(modelNode); NodeMetaInfo type; if (bindingProperty.isDynamic()) { type = dynamicTypeMetaInfo(bindingProperty); } else if (auto metaInfo = bindingProperty.parentModelNode().metaInfo(); metaInfo.isValid()) { type = metaInfo.property(bindingProperty.name()).propertyType(); } else qWarning() << __FUNCTION__ << " no meta info for target node"; NodeMetaInfo metaInfo = modelNode.metaInfo(); if (metaInfo.isValid()) { for (const auto &property : metaInfo.properties()) { if (metaInfoIsCompatible(type, property) ) possibleProperties.push_back(QString::fromUtf8(property.name())); } } else { qWarning() << __FUNCTION__ << " no meta info for source node"; } return possibleProperties; } QList dynamicPropertiesFromNode(const ModelNode &node) { auto isDynamic = [](const AbstractProperty &p) { return p.isDynamic(); }; auto byName = [](const AbstractProperty &a, const AbstractProperty &b) { return a.name() < b.name(); }; QList dynamicProperties = Utils::filtered(node.properties(), isDynamic); Utils::sort(dynamicProperties, byName); return dynamicProperties; } std::pair splitExpression(const QString &expression) { // ### Todo from original code (getExpressionStrings): // We assume no expressions yet const QStringList stringList = expression.split(QLatin1String(".")); QString sourceNode = stringList.constFirst(); QString propertyName; for (int i = 1; i < stringList.size(); ++i) { propertyName += stringList.at(i); if (i != stringList.size() - 1) propertyName += QLatin1String("."); } if (propertyName.isEmpty()) std::swap(sourceNode, propertyName); return {sourceNode, propertyName}; } } // namespace QmlDesigner