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 <assam.boudjelthia@qt.io>
This commit is contained in:
Petri Virkkunen 2025-05-08 12:06:51 +03:00
parent 8d5db24c14
commit 2c6be851ae

View File

@ -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<T> 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 = [&parameters]() -> QString {
QList<QString> 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 = [&parameters]() -> QString {
QList<QString> 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<QString> 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;