OmixVisualization / qtjambi

QtJambi is a wrapper for using Qt in Java.
http://www.qtjambi.io
Other
365 stars 43 forks source link

Computed property fails to load data from lambda created in function call #154

Closed mchistovib closed 1 year ago

mchistovib commented 1 year ago

Describe the bug We have found a bug when using lambda created inside a function for computable property - it never gets real data in it. For one of our colleagues that led to a crash since the code likely didn't expect the undefined value to be there forever. Computed property fails to load data from lambda created in function call, it should work the same as with lambda create on class level

To Reproduce Steps to reproduce the behavior:

Test files: Main.java

import io.qt.QtUtilities;
import io.qt.core.QObject;
import io.qt.core.QTimer;
import io.qt.gui.QWindow;
import io.qt.qml.QQmlApplicationEngine;
import io.qt.widgets.QApplication;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Main {
    private static QWindow m_root;

    public static void main(String[] args) throws IOException {
        QApplication.initialize(args);
        var qmlEngine = new QQmlApplicationEngine();
        qmlEngine.rootContext().setContextProperty("passedModel", new Model());

        qmlEngine.objectCreated.connect((object, url) -> {
            if (object != null && object instanceof QWindow) {
                m_root = (QWindow) object;
                System.out.println("Qml loaded");
            } else {
                System.err.println("Failed to create main window, quitting...");
                System.exit(0);
            }
        });
        qmlEngine.load("qrc:/main.qml");

        QApplication.exec();
        QApplication.quit();
    }

    public static class Model extends QObject {
        private final List<QComputedProperty> computedPropertylist = new ArrayList<>();
        private final Map<Integer, QComputedProperty> computedPropertyMap = new HashMap<>();
        public final QComputedProperty<String> computedProperty = makeComputedProperty(42); // DOESN't work
        public final QComputedProperty<String> computedProperty2 = makeComputedProperty2(42, () -> getValue(42)); // WORKS

        QTimer timer;

        public Model() {
            timer = new QTimer(this);
            timer.timeout.connect(() -> {
                System.out.println("Updating properties...");
                computedProperty.notifyProperty();
                computedProperty2.notifyProperty();
            });
            timer.start(1000*5);
        }

        private QComputedProperty<String> makeComputedProperty(int id) {
            QtUtilities.Supplier<String> getter = () -> getValue(id);
            QComputedProperty<String> property = new QComputedProperty<>(getter);
            computedPropertylist.add(property);
            computedPropertyMap.put(id, property);
            return property;
        }

        private QComputedProperty<String> makeComputedProperty2(int id, QtUtilities.Supplier<String> getter) {
            QComputedProperty<String> property = new QComputedProperty<>(getter);
            computedPropertylist.add(property);
            computedPropertyMap.put(id, property);
            return property;
        }

        private String getValue(int id) {
            return "value: " + id;
        }
    }
}

main.qml

import QtQml
import QtQuick
import QtQuick.Window
import QtQuick.Controls
import QtQuick.Layouts

Window {
    id: root
    objectName: "mainWindow"
    width: 1500
    height: 860
    minimumWidth: 1300
    minimumHeight: 640
    visible: true

    ColumnLayout{
        spacing: 2
        Layout.fillWidth: true

        Label {
            Layout.alignment: Qt.AlignCenter
            font.pixelSize: 22
            text: "Some text"
        }

        Label {
            Layout.alignment: Qt.AlignCenter
            font.pixelSize: 22
            text: "Test text: " + passedModel.computedProperty // DOESN'T load data
        }
        Label {
            Layout.alignment: Qt.AlignCenter
            font.pixelSize: 22
            text: "Test text2: " + passedModel.computedProperty2 // loads data
        }

        Label {
             Layout.alignment: Qt.AlignCenter
            font.pixelSize: 22
            text: "Another text"
        }
    }
}

Expected behavior Both computed properties are returning actual data

Screenshots image

System (please complete the following information):

omix commented 1 year ago

This is not a bug. As stated here the property is automatically detected and the property name is cut if the field name ends with Property. Here, class Model has two properties computed and computedProperty2. Thus, QML binding "Test text: " + passedModel.computedProperty refers to non-existing property.

"In QObject-derived classes, public final fields are considered to be constant (read-only) properties. Additionally, a final QProperty<...> fooProperty field is automatically considered to be property foo. You don't need getter, setter and bindable."

When having unexpected Java <-> QML behavior is is worth printing property and method signatures:

QMetaObject.forType(Model.class).properties().forEach(p->System.out.println(p.typeName()+" "+p.name()));
QMetaObject.forType(Model.class).methods().forEach(m->System.out.println(m.typeName()+" "+m.cppMethodSignature()));
omix commented 1 year ago

To avoid name reduction use @QtPropertyMember(name="computedProperty").

mchistovib commented 1 year ago

There is still a bug that leads to crash though, closing until there is a clear way to reproduce it..