From 2c6be851ae4b9e6f8c5fcbc24a306dad3c6fc865 Mon Sep 17 00:00:00 2001 From: Petri Virkkunen Date: Thu, 8 May 2025 12:06:51 +0300 Subject: [PATCH] Android: Support multi-arg signal generation in androiddeployqt This commit introduces support for generating Java code for multi-argument QML signals. Pre-existing code remains unchanged, and the new code is only executed when the number of params in a signal is above 1. Multi-arg signals are handled with a new generated interface type, named after the signal, which has a default method that takes an Object[] array and calls the user-implemented signal method with the arguments cast to the desired types. For example, a QML signal with the following signature: signal manyTypeArgSignal(intValue: int, boolValue: bool, doubleValue: double, stringValue: string) Is generated into this Java code: @FunctionalInterface public interface manyTypeArgSignalListener { default void onSignalEmitted(Object[] args) { onSignalEmitted((Integer) args[0], (Boolean) args[1], (Double) args[2], (String) args[3]); } void onManyTypeArgSignal(Integer intValue, Boolean boolValue, Double doubleValue, String stringValue); } public int connectManyTypeArgSignalListener(manyTypeArgSignalListener signalListener) { return connectSignalListener("manyTypeArgSignal", new Class[]{ Integer.class, Boolean.class, Double.class, String.class }, signalListener); } Task-number: QTBUG-124489 Change-Id: I94e3e88e807017bcbeba16cf0e34263e28e5885f Reviewed-by: Assam Boudjelthia --- src/tools/androiddeployqt/main.cpp | 87 +++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 14 deletions(-) diff --git a/src/tools/androiddeployqt/main.cpp b/src/tools/androiddeployqt/main.cpp index c31f5329014..946314fab5a 100644 --- a/src/tools/androiddeployqt/main.cpp +++ b/src/tools/androiddeployqt/main.cpp @@ -3773,25 +3773,84 @@ int generateJavaQmlComponents(const Options &options) if (methodData["methodType"_L1] != 0) return; const QJsonArray parameters = methodData["parameters"_L1].toArray(); - if (parameters.size() > 1) - return; const QString methodName = methodData["name"_L1].toString(); if (methodName.isEmpty()) return; - const QString upperMethodName = firstCharToUpper(methodName); - const QString typeName = !parameters.isEmpty() - ? parameters[0].toObject()["typeName"_L1].toString() - : "void"_L1; - const QString javaTypeName = qmlToJavaType.value(typeName, "Object"_L1); - stream << indent - << "public int connect%1Listener(QtSignalListener<%2> signalListener) {\n"_L1.arg( - upperMethodName, javaTypeName) - << indent - << " return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1.arg( - methodName, javaTypeName) - << indent << "}\n"; + const QString upperMethodName = firstCharToUpper(methodName); + if (parameters.size() <= 1) { // Generate a QtSignalListener API for this property/signal + const QString typeName = !parameters.isEmpty() + ? parameters[0].toObject()["typeName"_L1].toString() + : "void"_L1; + const QString javaTypeName = qmlToJavaType.value(typeName, "Object"_L1); + stream << indent + << "public int connect%1Listener(QtSignalListener<%2> signalListener) {\n"_L1 + .arg(upperMethodName, javaTypeName) + << indent + << " return connectSignalListener(\"%1\", %2.class, signalListener);\n"_L1 + .arg(methodName, javaTypeName) + << indent << "}\n"; + } else { // Multi-arg signal; Generate a custom listener interface for this signal + // Returns a comma-separated parameter list of java types deduced from the QML DOM array + const auto getJavaArgsString = [¶meters]() -> QString { + QList javaArgsList; + for (const auto param : parameters) { + const auto typeName = param["typeName"_L1].toString(); + const auto javaTypeName = qmlToJavaType.value(typeName, "Object"_L1); + const auto qmlParamName = param["name"_L1].toString(); + + javaArgsList.emplace_back( + QStringLiteral("%1%2").arg(javaTypeName, " %1"_L1.arg(qmlParamName))); + } + return javaArgsList.join(", "_L1); + }; + // Returns a comma-separated parameter list of java classes deduced from QML DOM array + const auto getJavaClassesString = [¶meters]() -> QString { + QList javaArgsList; + for (const auto param : parameters) { + const auto typeName = param["typeName"_L1].toString(); + const auto javaTypeName = qmlToJavaType.value(typeName, "Object"_L1); + + javaArgsList.emplace_back( + QStringLiteral("%1%2").arg(javaTypeName, ".class"_L1)); + } + return javaArgsList.join(", "_L1); + }; + + const auto javaParamsString = getJavaArgsString(); + const auto javaParamsClassesString = getJavaClassesString(); + + // e.g. "{(String) args[0], (Integer) args[1], (Boolean) args[2]}" + QList objectToTypeConversion; + for (auto i = 0; i < parameters.size(); ++i) { + const auto typeName = parameters.at(i).toObject().value("typeName"_L1).toString(); + objectToTypeConversion.emplace_back("(%1) args[%2]"_L1.arg( + qmlToJavaType.value(typeName, "Object"_L1), QString::number(i))); + } + + // Generate new interface type for this signal + const auto signalInterfaceName = "%1Listener"_L1.arg(methodName); + const auto objectToTypeConversionString = objectToTypeConversion.join(", "_L1); + stream << indent << "@FunctionalInterface\n" + << indent << "public interface %1 {\n"_L1.arg(signalInterfaceName) << indent + << " default void onSignalEmitted(Object[] args) {\n" + << indent + << " on%1(%2);\n"_L1.arg(upperMethodName, objectToTypeConversionString) + << indent << " }\n" + << indent + << " void on%1(%2);\n"_L1.arg(upperMethodName, javaParamsString); + stream << indent << "}\n"_L1; + + // Generate the connection function with this new interface type + stream << indent + << "public int connect%1(%2 signalListener) {\n"_L1.arg( + firstCharToUpper(signalInterfaceName), signalInterfaceName) + << indent + << " return connectSignalListener(\"%1\", new Class[]{ %2 }, signalListener);\n"_L1 + .arg(methodName, javaParamsClassesString) + << indent << "}\n\n"; + } }; constexpr static auto markerFileName = "qml_java_contents"_L1;