aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/jar/org/qtproject/qt/android/QtQuickView.java
blob: 0f8e999d9663c33ec5fb9bfe125d1193361600fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

package org.qtproject.qt.android;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.util.Log;

import java.security.InvalidParameterException;

/**
 * The QtQuickView class lets you easily add QML content to your Android app as a
 * {@link android.view.View View}. QtQuickView instantiates a QQuickView with a given
 * QML component source URI path and embeds it inside itself. You can add it in your Android app's
 * layout as with any other View. QtQuickView is a good choice when you want to extend your non-Qt
 * Android app with QML content but do not want to make the entire app using the Qt framework.
 * It brings the power of Qt Quick into your Android app, making it possible to use various Qt Quick
 * APIs, in Android Java or Kotlin apps.
 * </p>
 * <b>Note:</b> This class is a technical preview. It is subject to change, and no source nor
 * binary compatibility guarantees exist.
 * <p>
 * <b>Known limitations:</b>
 * <p><ul>
 * <li> Only CMake is supported, not qmake.
 * <li> Only one QtQuickView can be added to your app, adding multiple outcomes unknown.
 * </ul><p>
 * @see <a href="https://doc.qt.io/qt-6/qquickview.html">Qt QQuickView</a>
 **/
public class QtQuickView extends QtView {
    private final static String TAG = "QtQuickView";

    /**
     * A callback that notifies clients when a signal is emitted from the QML root object.
     **/
    @FunctionalInterface
    public interface SignalListener<T>
    {
        /**
        * Called on the Android UI thread when the signal has been emitted.
        * @param signalName literal signal name
        * @param value the value delivered by the signal or null if the signal is parameterless
        **/
        void onSignalEmitted(String signalName, T value);
    }

    /**
    * A callback that notifies clients about the status of QML loading.
    **/
    public interface StatusChangeListener
    {
        /**
        * Called on the Android UI thread when the QML component status has changed.
        * @param status The current status. The status can be STATUS_NULL, STATUS_READY,
        *        STATUS_LOADING or STATUS_ERROR.
        **/
        void onStatusChanged(int status);
    }

    /**
     * QML loading status: No source is set or the root object has not been created yet.
     **/
    public static final int STATUS_NULL = 0;
    /**
     * QML loading status: The QML view is loaded and the root object is available.
     * Invoking methods that operate on the QML root object, i.e.
     * {@link QtQuickView#setProperty() setProperty}, {@link QtQuickView#getProperty() getProperty},
     * and {@link QtQuickView#addSignalListener() addSignalListener} would succeed <b>only<b> if
     * the current status is ready.
     **/
    public static final int STATUS_READY = 1;
    /**
    * QML loading status: The QML view is loading the root object from network.
    **/
    public static final int STATUS_LOADING = 2;
    /**
    * QML loading status: One or more errors has occurred.
    **/
    public static final int STATUS_ERROR = 3;

    private String m_qmlUri;
    private String[] m_qmlImportPaths = null;
    private StatusChangeListener m_statusChangeListener = null;
    private int m_lastStatus = STATUS_NULL;
    private boolean m_hasQueuedStatus = false;

    native void createQuickView(String qmlUri, int width, int height, long parentWindowReference,
                                String[] qmlImportPaths);
    native void setRootObjectProperty(long windowReference, String propertyName, Object value);
    native Object getRootObjectProperty(long windowReference, String propertyName);
    native int addRootObjectSignalListener(long windowReference, String signalName, Class argType,
                                          Object listener);
    native boolean removeRootObjectSignalListener(long windowReference, int signalListenerId);

    /**
     * Creates a QtQuickView to load and view a QML component. Instantiating a QtQuickView will load
     * the Qt libraries, including the app library specified by <code>appName</code>. Then it
     * creates a QQuickView that loads the QML source specified by <code>qmlUri</code>.
     * <p>
     * @param context the parent Context
     * @param qmlUri  the URI of the main QML file
     * @param appName the name of the Qt app library to load and start. This corresponds to the
     *                target name set in Qt app's CMakeLists.txt
     * @throws InvalidParameterException if either qmlUri or appName is empty or null
     * @see <a href="https://doc.qt.io/qt-6/qquickview.html">Qt QQuickView</a>
     **/
    public QtQuickView(Context context, String qmlUri, String appName)
        throws InvalidParameterException {
        this(context, qmlUri, appName, null);
    }

    /**
     * Creates a QtQuickView to load and view a QML component. Instantiating a QtQuickView will load
     * the Qt libraries, including the app library specified by appName. Then it creates a
     * QQuickView that loads the QML source specified by qmlUri. This overload accepts an array of
     * strings in the case where the QML application should load QML modules from custom paths.
     * <p>
     * @param context        the parent Context
     * @param qmlUri         the URI of the main QML file
     * @param appName        the name of the Qt app library to load and start. This corresponds to
     *                       the target name set in the Qt app's CMakeLists.txt
     * @param qmlImportPaths an array of strings for additional import paths to be passed to
                             QQmlEngine, or null if additional import paths are not required
     * @throws InvalidParameterException if either qmlUri or appName is empty or null
     * @see <a href="https://doc.qt.io/qt-6/qqmlengine.html">Qt QQmlEngine</a>
     **/
    public QtQuickView(Context context, String qmlUri, String appName, String[] qmlImportPaths)
            throws InvalidParameterException
    {
        super(context, appName);
        if (qmlUri == null || qmlUri.isEmpty()) {
            throw new InvalidParameterException(
                "QtQuickView: argument 'qmlUri' may not be empty or null");
        }
        m_qmlUri = qmlUri;
        m_qmlImportPaths = qmlImportPaths;
    }

    @Override
    protected void createWindow(long parentWindowReference) {
        createQuickView(m_qmlUri, getWidth(), getHeight(), parentWindowReference, m_qmlImportPaths);
    }

    /**
     * Sets the value of an existing property on the QML root object. The supported types are
     * {@link java.lang.Integer}, {@link java.lang.Double}, {@link java.lang.Float},
     * {@link java.lang.Boolean} and {@link java.lang.String}. These types get converted to their
     * corresponding QML types int, double/float, bool and string. This function does not add
     * properties to the QML root object if they do not exist, but prints a warning.
     * <p>
     * @param propertyName the name of the existing root object property to set the value of
     * @param value        the value to set the property to
     * @see <a href="https://doc.qt.io/qt-6/qml-int.html">QML int</a>,
     * @see <a href="https://doc.qt.io/qt-6/qml-double.html">QML double/float</a>,
     * @see <a href="https://doc.qt.io/qt-6/qml-bool.html">QML bool</a>,
     * @see <a href="https://doc.qt.io/qt-6/qml-string.html">QML string</a>.
     **/
    public void setProperty(String propertyName, Object value)
    {
        setRootObjectProperty(windowReference(), propertyName, value);
    }

    /**
     * Gets the value of an existing property of the QML root object. The supported types are
     * {@link java.lang.Integer}, {@link java.lang.Double}, {@link java.lang.Float},
     * {@link java.lang.Boolean} and {@link java.lang.String}. These types get converted to their
     * corresponding QML types int, double/float, bool and string. If the property does not
     * exist or the status of the QML component is anything other than
     * {@link QtQuickView#STATUS_READY STATUS_READY}, this function will return null.
     * <p>
     * @param propertyName the name of the existing root object property
     * @throws ClassCastException if the returned type could not be casted to the requested type.
     * @see <a href="https://doc.qt.io/qt-6/qml-int.html">QML int</a>,
     * @see <a href="https://doc.qt.io/qt-6/qml-double.html">QML double/float</a>,
     * @see <a href="https://doc.qt.io/qt-6/qml-bool.html">QML bool</a>,
     * @see <a href="https://doc.qt.io/qt-6/qml-string.html">QML string</a>.
     **/
    // getRootObjectProperty always returns a primitive type or an Object
    // so it is safe to suppress the unchecked warning
    @SuppressWarnings("unchecked")
    public <T> T getProperty(String propertyName)
    {
        return (T)getRootObjectProperty(windowReference(), propertyName);
    }

    /**
     * Connects a SignalListener to a signal of the QML root object.
     * <p>
     * @param signalName the name of the root object signal
     * @param argType    the Class type of the signal argument
     * @param listener   an instance of the SignalListener interface
     * @return a connection id between signal and listener or the existing connection id if there is
     *         an existing connection between the same signal and listener. Return a negative value
     *         if the signal does not exists on the QML root object.
     **/
    public <T> int connectSignalListener(String signalName, Class<T> argType,
                                    SignalListener<T> listener)
    {
        int signalListenerId =
                addRootObjectSignalListener(windowReference(), signalName, argType, listener);
        if (signalListenerId < 0) {
            Log.w(TAG, "The signal " + signalName + " does not exist in the root object "
                                     + "or the arguments do not match with the listener.");
        }
        return signalListenerId;
    }

    /**
     * Disconnects a SignalListener with a given id obtained from
     * {@link QtQuickView#connectSignalListener() connectSignalListener} call, from listening to
     * a signal.
     * <p>
     * @param signalListenerId the connection id
     * @return Returns true if the connection id is valid and has been successfuly removed,
     *         otherwise returns false.
     **/
    public boolean disconnectSignalListener(int signalListenerId)
    {
        return removeRootObjectSignalListener(windowReference(), signalListenerId);
    }

    /**
     * Gets the status of the QML component.
     * <p>
     * @return Returns STATUS_READY when the QML component is ready. Invoking methods that operate
     *         on the QML root object ({@link QtQuickView#setProperty() setProperty},
     *         {@link QtQuickView#getProperty() getProperty}, and
     *         {@link QtQuickView#addSignalListener() addSignalListener}) would succeed <b>only</b>
     *         if the current STATUS_READY. It can also return STATUS_NULL, STATUS_LOADING, or
     *         STATUS_ERROR based on the status of see underlaying
     *         @see <a href="https://doc.qt.io/qt-6/qquickview.html">QQuickView</a> instance.
     **/
    public int getStatus()
    {
        return m_lastStatus;
    }

    /**
     * Sets a StatusChangeListener to listen to status changes.
     * <p>
     * @param listener an instance of a StatusChangeListener interface
     **/
    public void setStatusChangeListener(StatusChangeListener listener)
    {
        m_statusChangeListener = listener;

        if (m_hasQueuedStatus) {
            QtNative.runAction(() -> { m_statusChangeListener.onStatusChanged(m_lastStatus); });
            m_hasQueuedStatus = false;
        }
    }

    private void handleStatusChange(int status)
    {
        m_lastStatus = status;

        if (m_statusChangeListener != null)
            QtNative.runAction(() -> { m_statusChangeListener.onStatusChanged(status); });
        else
            m_hasQueuedStatus = true;
    }
}