diff --git a/src/android/jar/CMakeLists.txt b/src/android/jar/CMakeLists.txt index b786354a21a..9cc4707edbb 100644 --- a/src/android/jar/CMakeLists.txt +++ b/src/android/jar/CMakeLists.txt @@ -46,6 +46,10 @@ set(java_sources src/org/qtproject/qt/android/QtMenuInterface.java src/org/qtproject/qt/android/QtLayoutInterface.java src/org/qtproject/qt/android/QtInputInterface.java + src/org/qtproject/qt/android/QtAbstractItemModel.java + src/org/qtproject/qt/android/QtAbstractItemModelProxy.java + src/org/qtproject/qt/android/QtModelIndex.java + src/org/qtproject/qt/android/QtAbstractListModel.java ) qt_internal_add_jar(Qt${QtBase_VERSION_MAJOR}Android diff --git a/src/android/jar/src/org/qtproject/qt/android/QtAbstractItemModel.java b/src/android/jar/src/org/qtproject/qt/android/QtAbstractItemModel.java new file mode 100644 index 00000000000..42059a8ac36 --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/QtAbstractItemModel.java @@ -0,0 +1,490 @@ +// 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 java.util.HashMap; + +/** + * QtAbstractItemModel is a base class for implementing custom models in Java, + * similar to the C++ QAbstractItemModel. + * + * The QAbstractItemModel class defines the standard interface that item + * models must use to be able to interoperate with other components in the + * model/view architecture. It is not supposed to be instantiated directly. + * Instead, you should extend it to create new models. + * + * A QtAbstractItemModel can be used as the underlying data model for the + * item view elements in QML. + * + * If you need a model to use with an item view, such as QML's ListView element, + * you should consider extending {@link QtAbstractListModel} instead of this class. + * + * The underlying data model is exposed to views and delegates as a hierarchy + * of tables. If you do not use the hierarchy, the model is a simple table of + * rows and columns. Each item has a unique index specified by a {@link QtModelIndex}. + * + * @image modelindex-no-parent.png + * + * Every item of data that can be accessed via a model has an associated model + * index. You can obtain this model index using the {@link #index(int, int)} method. + * Each index may have a {@link #sibling(int, int)} index; child items have a + * {@link #parent()} index. + * + * Each item has data elements associated with it, and they can be + * retrieved by specifying a role to the model's {@link #data(QtModelIndex, int)} function. + * + * If an item has child objects, {@link #hasChildren(QtModelIndex)} returns true for the + * corresponding index. + * + * The model has a {@link #rowCount(QtModelIndex)} and a {@link #columnCount(QtModelIndex)} + * for each level of the hierarchy. + * + * Extending QtAbstractItemModel: + * + * Some general guidelines for sub-classing models are available in the + * {@link https://doc.qt.io/qt-6/model-view-programming.html#model-subclassing-reference} + * model sub-classing reference. + * + * When sub-classing QtAbstractItemModel, at the very least, you must implement + * {@link #index(int, int)}, {@link #parent(QtModelIndex)},{@link #rowCount(QtModelIndex)}, + * {@link #columnCount(QtModelIndex)}, and {@link #data(QtModelIndex, int)}. + * These abstract methods are used in all models. + * + * You can also re-implement {@link #hasChildren(QtModelIndex)} to provide special behavior for + * models where the implementation of {@link #rowCount(QtModelIndex)} is expensive. This makes it + * possible for models to restrict the amount of data requested by views and + * can be used as a way to implement the population of model data. + * + * Custom models need to create model indexes for other components to use. To + * do this, call {@link #createIndex(int, int, long)} with suitable row and column numbers for the + * item, and a long type identifier for it. + * The combination of these values must be unique for each item. Custom models + * typically use these unique identifiers in other re-implemented functions to + * retrieve item data and access information about the item's parents and + * children. + * + * To create models that populate incrementally, you can re-implement + * {@link #fetchMore(QtModelIndex)} and {@link #canFetchMore(QtModelIndex)}. + * If the re-implementation of {@link #fetchMore(QtModelIndex)} adds + * rows to the model, {@link QtAbstractItemModel#beginInsertRows(QtModelIndex, int, int)} and + * {@link QtAbstractItemModel#endInsertRows()} must be called. + * @since 6.8 +*/ +public abstract class QtAbstractItemModel +{ + /** + * Constructs a new QtAbstractItemModel. + */ + public QtAbstractItemModel(){}; + /** + * Returns the number of columns for the children of the given parent. + * In most subclasses, the number of columns is independent of the parent. + * + * For example: + * @Override + * int columnCount(const QtModelIndex parent) + * { + * return 3; + * } + * + * When implementing a table-based model, columnCount() should return 0, + * when the parent is valid. + * + * @param parent The parent index. + * @return The number of columns. + * @see #rowCount(QtModelIndex) + */ + public abstract int columnCount(QtModelIndex parent); + /** + * Returns the data for the given index and role. + * Types conversions are: + * QML <- Java + * int <- Integer + * string <- String + * double <- Double + * real <- Double + * bool <- Boolean + * + * @param index The index. + * @param role The role. + * @return The data object. + */ + public abstract Object data(QtModelIndex index, int role); + /** + * Returns the index for the specified row and column for the supplied parent index. + * When re-implementing this function in a subclass, call createIndex() to generate model + * indexes that other components can use to refer to items in your model. + * + * @param row The row. + * @param column The column. + * @param parent The parent index. + * @return The index. + * @see #createIndex(int row, int column, long id) + */ + public abstract QtModelIndex index(int row, int column, QtModelIndex parent); + /** + * Returns the parent of the model item with the given index. If the item + * has no parent, then an invalid QtModelIndex is returned. + * + * A common convention used in models that expose tree data structures is that + * only items in the first column have children. For that case, when + * re-implementing this function in a subclass, the column of the returned + * QtModelIndex would be 0. + + * When re-implementing this function in a subclass, be careful to avoid + * calling QtModelIndex member functions, such as QtModelIndex::parent(), since + * indexes belonging to your model will call your implementation, + * leading to infinite recursion. + * + * @param index The index. + * @return The parent index. + * @see #createIndex(int row, int column, long id) + */ + public abstract QtModelIndex parent(QtModelIndex index); + /** + * Returns the number of rows under the given parent. When the parent is + * valid, it means that rowCount is returning the number of children of the parent. + + * Note: when implementing a table-based model, rowCount() should return 0, + * when the parent is valid. + * + * @param parent The parent index. + * @return The number of rows. + * @see #columnCount(QtModelIndex parent) + */ + public abstract int rowCount(QtModelIndex parent); + /** + * Returns whether more items can be fetched for the given parent index. + * + * @param parent The parent index. + * @return True if more items can be fetched, false otherwise. + */ + public native boolean canFetchMore(QtModelIndex parent); + /** + * Fetches any available data for the items with the parent specified by the + * parent index. + * + * @param parent The parent index. + */ + public native void fetchMore(QtModelIndex parent); + /** + * Returns true if the parent has any children; otherwise, returns false. + * Use rowCount() on the parent to get the number of children. + * + * @param parent The parent index. + * @return True if the parent has children, false otherwise. + * @see #parent(QtModelIndex index) + * @see #index(int row, int column, QtModelIndex parent) + */ + public native boolean hasChildren(QtModelIndex parent); + /** + * Returns true if the model returns a valid QModelIndex for row and + * column with parent, otherwise returns \c{false}. + * + * @param row The row. + * @param column The column. + * @param parent The parent index. + * @return True if the index exists, false otherwise. + */ + public native boolean hasIndex(int row, int column, QtModelIndex parent); + /** + * Returns a map of role names. + * You must override this to provide your own role names or the + * {@link https://doc.qt.io/qt-6/qabstractitemmodel.html#roleNames defaults} + * will be used. + * + * @return The role names map. + */ + public HashMap roleNames() + { + return (HashMap)jni_roleNames(); + } + /** + * Returns the sibling at row and column for the item at index or an + * invalid QModelIndex if there is no sibling at that location. + * + * sibling() is just a convenience function that finds the item's parent and + * uses it to retrieve the index of the child item in the specified row and + * column. + * + * This method can optionally be overridden to optimize a specific implementation. + * + * @param row The row. + * @param column The column. + * @param parent The parent index. + * @return The sibling index. + */ + public QtModelIndex sibling(int row, int column, QtModelIndex parent) + { + return (QtModelIndex)jni_sibling(row, column, parent); + } + /** + * Begins a column insertion operation. + + * The parent index corresponds to the parent into which the new columns + * are inserted; first and last are the column numbers of the new + * columns will have after they have been inserted. + * + * @see #endInsertColumns() + */ + protected final void beginInsertColumns(QtModelIndex parent, int first, int last) + { + jni_beginInsertColumns(parent, first, last); + } + /** + * Begins a row insertion operation. + * Parent index corresponds to the parent into which the new rows + * are inserted; first and last are the row numbers the new rows will have + * after they have been inserted. + + * @see #endInsertRows() + */ + protected final void beginInsertRows(QtModelIndex parent, int first, int last) + { + jni_beginInsertRows(parent, first, last); + } + /** + Begins a column move operation. + + When re-implementing a subclass, this method simplifies moving + entities in your model. This method is responsible for moving + persistent indexes in the model. + + The sourceParent index corresponds to the parent from which the + columns are moved; sourceFirst and sourceLast are the first and last + column numbers of the columns to be moved. The destinationParent index + corresponds to the parent into which those columns are moved. The + destinationChild is the column to which the columns will be moved. That + is, the index at column sourceFirst in sourceParent will become + column destinationChild in destinationParent, followed by all other + columns up to sourceLast. + + However, when moving columns down in the same parent (sourceParent + and destinationParent are equal), the columns will be placed before the + destinationChild index. If you wish to move columns 0 and 1 so + they will become columns 1 and 2, destinationChild should be 3. In this + case, the new index for the source column i (which is between + sourceFirst and sourceLast) is equal to (destinationChild-sourceLast-1+i). + + Note that if sourceParent and destinationParent are the same, + you must ensure that the destinationChild is not within the range + of sourceFirst and sourceLast + 1. You must also ensure that you + do not attempt to move a column to one of its own children or ancestors. + This method returns false if either condition is true, in which case you + should abort your move operation. + + @see endMoveColumns() + */ + protected final boolean beginMoveColumns(QtModelIndex sourceParent, int sourceFirst, + int sourceLast, QtModelIndex destinationParent, + int destinationChild) + { + return jni_beginMoveColumns(sourceParent, sourceFirst, sourceLast, destinationParent, + destinationChild); + } + /** + * Begins a row move operation. + + * When re-implementing a subclass, this method simplifies moving + * entities in your model. + + * The sourceParent index corresponds to the parent from which the + * rows are moved; sourceFirst and sourceLast are the first and last + * row numbers of the rows to be moved. The destinationParent index + * corresponds to the parent into which those rows are moved. The + * destinationChild is the row to which the rows will be moved. That + * is, the index at row sourceFirst in sourceParent will become + * row destinationChild in destinationParent, followed by all other + * rows up to sourceLast. + + * However, when moving rows down in the same parent (sourceParent + * and destinationParent are equal), the rows will be placed before the + * destinationChild index. That is, if you wish to move rows 0 and 1 so + * they will become rows 1 and 2, destinationChild should be 3. In this + * case, the new index for the source row i (which is between + * sourceFirst and sourceLast) is equal to + * (destinationChild-sourceLast-1+i). + + * Note that if sourceParent and destinationParent are the same, + * you must ensure that the destinationChild is not within the range + * of sourceFirst and sourceLast + 1. You must also ensure that you + * do not attempt to move a row to one of its own children or ancestors. + * This method returns false if either condition is true, in which case you + * should abort your move operation. + + * {@link https://doc.qt.io/qt-6/qabstractitemmodel.html#beginMoveRows PossibleOps} + + * @see #endMoveRows() + */ + protected final boolean beginMoveRows(QtModelIndex sourceParent, int sourceFirst, + int sourceLast, QtModelIndex destinationParent, + int destinationChild) + { + return jni_beginMoveRows(sourceParent, sourceFirst, sourceLast, destinationParent, + destinationChild); + } + /** + * Begins a column removal operation. + + * When re-implementing removeColumns() in a subclass, you must call this + * function before removing data from the model's underlying data store. + + * The parent index corresponds to the parent from which the new columns + * are removed; first and last are the column numbers of the first and + * last columns to be removed. + * + * {@link https://doc.qt.io/qt-6/qabstractitemmodel.html#beginRemoveColumns RemoveColums} + * @see #endRemoveColumns() + */ + protected final void beginRemoveColumns(QtModelIndex parent, int first, int last) + { + jni_beginRemoveColumns(parent, first, last); + } + /** + * Begins a row removal operation. + + * When re-implementing removeRows() in a subclass, you must call this + * function before removing data from the model's underlying data store. + + * The parent index corresponds to the parent from which the new rows are + * removed; first and last are the row numbers of the rows to be. + + {@link https://doc.qt.io/qt-6/qabstractitemmodel.html#beginRemoveRows RemoveRows} + * @see #endRemoveRow() + */ + protected final void beginRemoveRows(QtModelIndex parent, int first, int last) + { + jni_beginRemoveRows(parent, first, last); + } + /** + * Begins a model reset operation. + + * A reset operation resets the model to its current state in any attached views. + + * Note: any views attached to this model will also be reset. + + * When a model is reset, any previous data reported from the + * model is now invalid and has to be queried again. This also means that + * the current and any selected items will become invalid. + + * When a model radically changes its data, it can sometimes be easier to just + * call this function rather than emit dataChanged() to inform other + * components when the underlying data source, or its structure, has changed. + + * You must call this function before resetting any internal data structures + in your model. + + * @see #modelAboutToBeReset() + * @see #modelReset() + * @see #endResetModel() + */ + protected final void beginResetModel() { jni_beginResetModel(); } + + /** + * Creates a model index for the given row and column with the internal + * identifier id. + + * This function provides a consistent interface, which model sub classes must + * use to create model indexes. + */ + protected final QtModelIndex createIndex(int row, int column, long id) + { + return (QtModelIndex)jni_createIndex(row, column, id); + } + /** + * Ends a column insertion operation. + + * When re-implementing insertColumns() in a subclass, you must call this + * function after inserting data into the model's underlying data + * store. + + * @see #beginInsertColumns() + */ + protected final void endInsertColumns() { jni_endInsertColumns(); } + /** + * Ends a row insertion operation. + + * When re-implementing insertRows() in a subclass, you must call this function + * after inserting data into the model's underlying data store. + + * @see #beginInsertRows() + */ + protected final void endInsertRows() { jni_endInsertRows(); } + /** + * Ends a column move operation. + + * When implementing a subclass, you must call this + * function after moving data within the model's underlying data + * store. + + * @see #beginMoveColumns() + */ + protected final void endMoveColumns() { jni_endMoveColumns(); } + /** + Ends a row move operation. + + When implementing a subclass, you must call this + function after moving data within the model's underlying data + store. + + @see #beginMoveRows() + */ + protected final void endMoveRows() { jni_endMoveRows(); } + /** + * Ends a column removal operation. + + * When reimplementing removeColumns() in a subclass, you must call this + * function after removing data from the model's underlying data store. + + * @see #beginRemoveColumns() + */ + protected final void endRemoveColumns() { jni_endRemoveColumns(); } + /** + * Ends a row move operation. + + * When implementing a subclass, you must call this + * function after moving data within the model's underlying data + * store. + + * @see #beginMoveRows(QtModelIndex sourceParent, int sourceFirst, int sourceLast, QtModelIndexdestinationParent, int destinationChild) + */ + protected final void endRemoveRows() { jni_endRemoveRows(); } + /** + * Completes a model reset operation. + + * You must call this function after resetting any internal data structure in your model. + + * @see #beginResetModel() + */ + protected final void endResetModel() { jni_endResetModel(); } + + private native void jni_beginInsertColumns(QtModelIndex parent, int first, int last); + private native void jni_beginInsertRows(QtModelIndex parent, int first, int last); + private native boolean jni_beginMoveColumns(QtModelIndex sourceParent, int sourceFirst, + int sourceLast, QtModelIndex destinationParent, + int destinationChild); + private native boolean jni_beginMoveRows(QtModelIndex sourceParent, int sourceFirst, + int sourceLast, QtModelIndex destinationParent, + int destinationChild); + private native void jni_beginRemoveColumns(QtModelIndex parent, int first, int last); + private native void jni_beginRemoveRows(QtModelIndex parent, int first, int last); + private native void jni_beginResetModel(); + private native Object jni_createIndex(int row, int column, long id); + private native void jni_endInsertColumns(); + private native void jni_endInsertRows(); + private native void jni_endMoveColumns(); + private native void jni_endMoveRows(); + private native void jni_endRemoveColumns(); + private native void jni_endRemoveRows(); + private native void jni_endResetModel(); + private native Object jni_roleNames(); + private native Object jni_sibling(int row, int column, QtModelIndex parent); + + private long m_nativeReference = 0; + private QtAbstractItemModel(long nativeReference) { m_nativeReference = nativeReference; } + private void detachFromNative() { m_nativeReference = 0; }; + private long nativeReference() { return m_nativeReference; } + private void setNativeReference(long nativeReference) { m_nativeReference = nativeReference; } + private static boolean instanceOf(Object obj) { return (obj instanceof QtAbstractItemModel); } +} diff --git a/src/android/jar/src/org/qtproject/qt/android/QtAbstractItemModelProxy.java b/src/android/jar/src/org/qtproject/qt/android/QtAbstractItemModelProxy.java new file mode 100644 index 00000000000..6432a3e12ea --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/QtAbstractItemModelProxy.java @@ -0,0 +1,35 @@ +// 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; + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +class QtAndroidItemModelProxy extends QtAbstractItemModel +{ + @Override public int columnCount(QtModelIndex parent) { return jni_columnCount(parent); }; + @Override public Object data(QtModelIndex index, int role) { return jni_data(index, role); } + @Override public QtModelIndex index(int row, int column, QtModelIndex parent) + { + return (QtModelIndex)jni_index(row, column, parent); + } + @Override public QtModelIndex parent(QtModelIndex index) + { + return (QtModelIndex)jni_parent(index); + } + @Override public int rowCount(QtModelIndex parent) { return jni_rowCount(parent); } + + private native int jni_columnCount(QtModelIndex parent); + private native Object jni_data(QtModelIndex index, int role); + private native Object jni_index(int row, int column, QtModelIndex parent); + private native Object jni_parent(QtModelIndex index); + private native int jni_rowCount(QtModelIndex parent); +} diff --git a/src/android/jar/src/org/qtproject/qt/android/QtAbstractListModel.java b/src/android/jar/src/org/qtproject/qt/android/QtAbstractListModel.java new file mode 100644 index 00000000000..5a49caca9ce --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/QtAbstractListModel.java @@ -0,0 +1,30 @@ +// 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 java.util.HashMap; + +public abstract class QtAbstractListModel extends QtAbstractItemModel +{ + public QtAbstractListModel(){}; + + @Override public final int columnCount(QtModelIndex parent) { return parent.isValid() ? 0 : 1; } + + @Override public QtModelIndex index(int row, int column, QtModelIndex parent) + { + return hasIndex(row, column, parent) ? createIndex(row, column, 0) : new QtModelIndex(); + } + + @Override public final QtModelIndex parent(QtModelIndex index) { return new QtModelIndex(); } + + @Override public final boolean hasChildren(QtModelIndex parent) + { + return parent.isValid() ? false : (rowCount(new QtModelIndex()) > 0); + } + + @Override public QtModelIndex sibling(int row, int column, QtModelIndex parent) + { + return index(row, column, new QtModelIndex()); + } +} diff --git a/src/android/jar/src/org/qtproject/qt/android/QtModelIndex.java b/src/android/jar/src/org/qtproject/qt/android/QtModelIndex.java new file mode 100644 index 00000000000..6fb3135e34b --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt/android/QtModelIndex.java @@ -0,0 +1,81 @@ +// 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; + +/** + * Represents an index in a custom item model, similar to + * {@link https://doc.qt.io/qt-6/https://doc.qt.io/qt-6/qmodelindex.html QModelindex} + * in c++. + */ +public class QtModelIndex +{ + /** + * Constructs a new QtModelIndex. + */ + public QtModelIndex() { } + /** + * Returns the column of this index. + * + * @return The column. + */ + public int column() { return (int)m_privateData[1]; } + /** + * Retrieves data for this index based on the specified role. + * + * @param role The role for which data is requested. + * @return The data object. + */ + public native Object data(int role); + /** + * Returns the internal ID associated with this index. + * + * @return The internal ID. + */ + public native long internalId(); + /** + * Checks if this index is valid. + * + * @return True if the index is valid, false otherwise. + */ + public native boolean isValid(); + /** + * Returns the parent index of this index. + * + * @return The parent index. + */ + public native QtModelIndex parent(); + /** + * Returns the row of this index. + * + * @return The row. + */ + public int row() { return (int)m_privateData[0]; } + + private long[] m_privateData = { -1 /*row*/, -1 /*column*/, 0 /*internalId*/, + 0 /*modelReference*/ }; + private QtModelIndex m_parent = null; + private QtModelIndex(int row, int column, long internalId, long modelReference) + { + m_privateData[0] = row; + m_privateData[1] = column; + m_privateData[2] = internalId; + m_privateData[3] = modelReference; + m_parent = null; + } + private QtModelIndex(int row, int column, QtModelIndex parent, long modelReference) + { + m_privateData[0] = row; + m_privateData[1] = column; + m_privateData[2] = 0; + m_privateData[3] = modelReference; + m_parent = parent; + } + private void detachFromNative() + { + m_privateData[0] = -1; + m_privateData[1] = -1; + m_privateData[2] = 0; + m_privateData[3] = 0; + }; +} diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 3fff08b0fe1..53ebb6ca8ec 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -1064,6 +1064,8 @@ qt_internal_extend_target(Core CONDITION ANDROID platform/android/qandroidnativeinterface.cpp platform/android/qandroidtypes_p.h platform/android/qandroidtypeconverter_p.h + platform/android/qandroiditemmodelproxy_p.h platform/android/qandroiditemmodelproxy.cpp + platform/android/qandroidmodelindexproxy_p.h platform/android/qandroidmodelindexproxy.cpp NO_UNITY_BUILD_SOURCES platform/android/qandroidextras.cpp # qtNativeClassName conflicts with similar symbols in android headers diff --git a/src/corelib/platform/android/qandroiditemmodelproxy.cpp b/src/corelib/platform/android/qandroiditemmodelproxy.cpp new file mode 100644 index 00000000000..1ddca53c90e --- /dev/null +++ b/src/corelib/platform/android/qandroiditemmodelproxy.cpp @@ -0,0 +1,379 @@ +// 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 + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace QtJniTypes; + +jint QAndroidItemModelProxy::columnCount(const QModelIndex &parent) const +{ + Q_ASSERT(jInstance.isValid()); + auto parentIndex = QAndroidModelIndexProxy::jInstance(parent); + return jInstance.callMethod("columnCount", parentIndex); +} + +bool QAndroidItemModelProxy::canFetchMore(const QModelIndex &parent) const +{ + Q_ASSERT(jInstance.isValid()); + auto parentIndex = QAndroidModelIndexProxy::jInstance(parent); + return jInstance.callMethod("canFetchMore", parentIndex); +} + +bool QAndroidItemModelProxy::canFetchMoreDefault(const QModelIndex &parent) const +{ + return QAbstractItemModel::canFetchMore(parent); +} + +QVariant QAndroidItemModelProxy::data(const QModelIndex &index, int role) const +{ + Q_ASSERT(jInstance.isValid()); + auto jIndex = QAndroidModelIndexProxy::jInstance(index); + QJniObject jData = jInstance.callMethod("data", jIndex, role); + return QAndroidTypeConverter::toQVariant(jData); +} + +QModelIndex QAndroidItemModelProxy::index(int row, int column, const QModelIndex &parent) const +{ + Q_ASSERT(jInstance.isValid()); + JQtModelIndex jIndex = jInstance.callMethod( + "index", row, column, QAndroidModelIndexProxy::jInstance(parent)); + return QAndroidModelIndexProxy::qInstance(jIndex); +} + +QModelIndex QAndroidItemModelProxy::parent(const QModelIndex &index) const +{ + Q_ASSERT(jInstance.isValid()); + + auto jIndex = QAndroidModelIndexProxy::jInstance(index); + return QAndroidModelIndexProxy::qInstance( + jInstance.callMethod("parent", jIndex)); +} +int QAndroidItemModelProxy::rowCount(const QModelIndex &parent) const +{ + Q_ASSERT(jInstance.isValid()); + + auto parentIndex = QAndroidModelIndexProxy::jInstance(parent); + return jInstance.callMethod("rowCount", parentIndex); +} + +QHash QAndroidItemModelProxy::roleNames() const +{ + Q_ASSERT(jInstance.isValid()); + + QHash roleNames; + HashMap hashMap = jInstance.callMethod("roleNames"); + Set set = hashMap.callMethod("keySet"); + QJniArray keyArray = set.callMethod>("toArray"); + + for (auto key : keyArray) { + const QJniObject roleName = hashMap.callMethod("get", key); + const int intKey = QJniObject(key).callMethod("intValue"); + const QByteArray roleByteArray = String(roleName).toString().toLatin1(); + roleNames.insert(intKey, roleByteArray); + } + return roleNames; +} + +QHash QAndroidItemModelProxy::defaultRoleNames() const +{ + return QAbstractItemModel::roleNames(); +} + +void QAndroidItemModelProxy::fetchMore(const QModelIndex &parent) +{ + Q_ASSERT(jInstance.isValid()); + auto parentIndex = QAndroidModelIndexProxy::jInstance(parent); + jInstance.callMethod("fetchMore", parentIndex); +} + +void QAndroidItemModelProxy::fetchMoreDefault(const QModelIndex &parent) +{ + QAbstractItemModel::fetchMore(parent); +} + +bool QAndroidItemModelProxy::hasChildren(const QModelIndex &parent) const +{ + Q_ASSERT(jInstance.isValid()); + auto parentIndex = QAndroidModelIndexProxy::jInstance(parent); + return jInstance.callMethod("hasChildren", parentIndex); +} + +bool QAndroidItemModelProxy::hasChildrenDefault(const QModelIndex &parent) const +{ + return QAbstractItemModel::hasChildren(parent); +} + +QModelIndex QAndroidItemModelProxy::sibling(int row, int column, const QModelIndex &parent) const +{ + Q_ASSERT(jInstance.isValid()); + return QAndroidModelIndexProxy::qInstance(jInstance.callMethod( + "sibling", row, column, QAndroidModelIndexProxy::jInstance(parent))); +} + +QModelIndex QAndroidItemModelProxy::siblingDefault(int row, int column, const QModelIndex &parent) +{ + return QAbstractItemModel::sibling(row, column, parent); +} + +Q_REQUIRED_RESULT QAbstractItemModel * +QAndroidItemModelProxy::nativeInstance(JQtAbstractItemModel itemModel) +{ + jlong nativeReference = itemModel.callMethod("nativeReference"); + return reinterpret_cast(nativeReference); +} + +Q_REQUIRED_RESULT QAbstractItemModel * +QAndroidItemModelProxy::createNativeProxy(QJniObject itemModel) +{ + QAbstractItemModel *nativeProxy = nativeInstance(itemModel); + if (!nativeProxy) { + nativeProxy = new QAndroidItemModelProxy(itemModel); + + itemModel.callMethod("setNativeReference", reinterpret_cast(nativeProxy)); + connect(nativeProxy, &QAndroidItemModelProxy::destroyed, nativeProxy, [](QObject *obj) { + auto proxy = qobject_cast(obj); + if (proxy) + proxy->jInstance.callMethod("detachFromNative"); + }); + } + return nativeProxy; +} + +QJniObject QAndroidItemModelProxy::createProxy(QAbstractItemModel *itemModel) +{ + return JQtAndroidItemModelProxy(reinterpret_cast(itemModel)); +} + +int QAndroidItemModelProxy::jni_columnCount(JNIEnv *env, jobject object, JQtModelIndex parent) +{ + const QModelIndex nativeParent = QAndroidModelIndexProxy::qInstance(parent); + return invokeNativeMethod(env, object, &QAbstractItemModel::columnCount, nativeParent); +} + +jobject QAndroidItemModelProxy::jni_data(JNIEnv *env, jobject object, JQtModelIndex index, + jint role) +{ + const QModelIndex nativeIndex = QAndroidModelIndexProxy::qInstance(index); + const QVariant data = + invokeNativeMethod(env, object, &QAbstractItemModel::data, nativeIndex, role); + return QAndroidTypeConverter::toJavaObject(data, env); +} + +jobject QAndroidItemModelProxy::jni_index(JNIEnv *env, jobject object, jint row, jint column, + JQtModelIndex parent) +{ + auto nativeParent = QAndroidModelIndexProxy::qInstance(parent); + const QModelIndex modelIndex = + invokeNativeMethod(env, object, &QAbstractItemModel::index, row, column, nativeParent); + return env->NewLocalRef(QAndroidModelIndexProxy::jInstance(modelIndex).object()); +} + +jobject QAndroidItemModelProxy::jni_parent(JNIEnv *env, jobject object, JQtModelIndex index) +{ + const QModelIndex nativeIndex = QAndroidModelIndexProxy::qInstance(index); + QModelIndex (QAbstractItemModel::*parentOverloadPtr)(const QModelIndex &) const = + &QAbstractItemModel::parent; + const QModelIndex parent = invokeNativeMethod(env, object, parentOverloadPtr, nativeIndex); + return env->NewLocalRef(QAndroidModelIndexProxy::jInstance(parent).object()); +} + +jint QAndroidItemModelProxy::jni_rowCount(JNIEnv *env, jobject object, JQtModelIndex parent) +{ + return invokeNativeMethod(env, object, &QAbstractItemModel::rowCount, + QAndroidModelIndexProxy::qInstance(parent)); +} + +jobject QAndroidItemModelProxy::jni_roleNames(JNIEnv *env, jobject object) +{ + auto roleNames = invokeNativeImpl(env, object, &QAndroidItemModelProxy::defaultRoleNames, + &QAbstractItemModel::roleNames); + HashMap jRoleNames{}; + for (auto [role, roleName] : roleNames.asKeyValueRange()) { + const Integer jRole(role); + const QJniObject jRoleName = QJniObject::fromString(roleName); + jRoleNames.callMethod("put", jRole.object(), jRoleName.object()); + } + return env->NewLocalRef(jRoleNames.object()); +} + +jobject QAndroidItemModelProxy::jni_createIndex(JNIEnv *env, jobject object, jint row, jint column, + jlong id) +{ + QModelIndex (QAndroidItemModelProxy::*createIndexPtr)(int, int, quintptr) const = + &QAndroidItemModelProxy::createIndex; + const QModelIndex index = invokeNativeProxyMethod(env, object, createIndexPtr, row, column, id); + return env->NewLocalRef(QAndroidModelIndexProxy::jInstance(index).object()); +} + +jboolean QAndroidItemModelProxy::jni_canFetchMore(JNIEnv *env, jobject object, JQtModelIndex parent) +{ + return invokeNativeImpl(env, object, &QAndroidItemModelProxy::canFetchMoreDefault, + &QAbstractItemModel::canFetchMore, + QAndroidModelIndexProxy::qInstance(parent)); +} + +void QAndroidItemModelProxy::jni_fetchMore(JNIEnv *env, jobject object, JQtModelIndex parent) +{ + return invokeNativeImpl(env, object, &QAndroidItemModelProxy::fetchMoreDefault, + &QAbstractItemModel::fetchMore, + QAndroidModelIndexProxy::qInstance(parent)); +} + +jboolean QAndroidItemModelProxy::jni_hasChildren(JNIEnv *env, jobject object, JQtModelIndex parent) +{ + return invokeNativeImpl(env, object, &QAndroidItemModelProxy::hasChildrenDefault, + &QAbstractItemModel::hasChildren, + QAndroidModelIndexProxy::qInstance(parent)); +} + +jboolean QAndroidItemModelProxy::jni_hasIndex(JNIEnv *env, jobject object, jint row, jint column, + JQtModelIndex parent) +{ + return invokeNativeMethod(env, object, &QAbstractItemModel::hasIndex, row, column, + QAndroidModelIndexProxy::qInstance(parent)); +} + +void QAndroidItemModelProxy::jni_beginInsertColumns(JNIEnv *env, jobject object, + JQtModelIndex parent, jint first, jint last) +{ + + invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::beginInsertColumns, + QAndroidModelIndexProxy::qInstance(parent), first, last); +} + +void QAndroidItemModelProxy::jni_beginInsertRows(JNIEnv *env, jobject object, JQtModelIndex parent, + jint first, jint last) +{ + invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::beginInsertRows, + QAndroidModelIndexProxy::qInstance(parent), first, last); +} + +jboolean QAndroidItemModelProxy::jni_beginMoveColumns(JNIEnv *env, jobject object, + JQtModelIndex sourceParent, jint sourceFirst, + jint sourceLast, + JQtModelIndex destinationParent, + jint destinationChild) +{ + return invokeNativeProxyMethod( + env, object, &QAndroidItemModelProxy::beginMoveColumns, + QAndroidModelIndexProxy::qInstance(sourceParent), sourceFirst, sourceLast, + QAndroidModelIndexProxy::qInstance(destinationParent), destinationChild); +} + +jboolean QAndroidItemModelProxy::jni_beginMoveRows(JNIEnv *env, jobject object, + JQtModelIndex sourceParent, jint sourceFirst, + jint sourceLast, JQtModelIndex destinationParent, + jint destinationChild) +{ + return invokeNativeProxyMethod( + env, object, &QAndroidItemModelProxy::beginMoveRows, + QAndroidModelIndexProxy::qInstance(sourceParent), sourceFirst, sourceLast, + QAndroidModelIndexProxy::qInstance(destinationParent), destinationChild); +} + +void QAndroidItemModelProxy::jni_beginRemoveColumns(JNIEnv *env, jobject object, + JQtModelIndex parent, jint first, jint last) +{ + invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::beginRemoveColumns, + QAndroidModelIndexProxy::qInstance(parent), first, last); +} + +void QAndroidItemModelProxy::jni_beginRemoveRows(JNIEnv *env, jobject object, JQtModelIndex parent, + jint first, jint last) +{ + invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::beginRemoveRows, + QAndroidModelIndexProxy::qInstance(parent), first, last); +} + +void QAndroidItemModelProxy::jni_beginResetModel(JNIEnv *env, jobject object) +{ + invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::beginResetModel); +} + +void QAndroidItemModelProxy::jni_endInsertColumns(JNIEnv *env, jobject object) +{ + invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::endInsertColumns); +} + +void QAndroidItemModelProxy::jni_endInsertRows(JNIEnv *env, jobject object) +{ + invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::endInsertRows); +} + +void QAndroidItemModelProxy::jni_endMoveColumns(JNIEnv *env, jobject object) +{ + invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::endMoveColumns); +} + +void QAndroidItemModelProxy::jni_endMoveRows(JNIEnv *env, jobject object) +{ + invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::endMoveRows); +} + +void QAndroidItemModelProxy::jni_endRemoveColumns(JNIEnv *env, jobject object) +{ + invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::endRemoveColumns); +} + +void QAndroidItemModelProxy::jni_endRemoveRows(JNIEnv *env, jobject object) +{ + invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::endRemoveRows); +} + +void QAndroidItemModelProxy::jni_endResetModel(JNIEnv *env, jobject object) +{ + invokeNativeProxyMethod(env, object, &QAndroidItemModelProxy::endResetModel); +} + +jobject QAndroidItemModelProxy::jni_sibling(JNIEnv *env, jobject object, jint row, jint column, + JQtModelIndex parent) +{ + const QModelIndex index = invokeNativeImpl(env, object, &QAndroidItemModelProxy::siblingDefault, + &QAbstractItemModel::sibling, row, column, + QAndroidModelIndexProxy::qInstance(parent)); + return env->NewLocalRef(QAndroidModelIndexProxy::jInstance(index).object()); +} + +bool QAndroidItemModelProxy::registerAbstractNatives(QJniEnvironment &env) +{ + return env.registerNativeMethods( + Traits::className(), + { Q_JNI_NATIVE_SCOPED_METHOD(jni_roleNames, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_canFetchMore, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_createIndex, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_fetchMore, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_hasChildren, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_hasIndex, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_beginInsertColumns, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_beginInsertRows, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_beginMoveColumns, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_beginMoveRows, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_beginRemoveColumns, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_beginRemoveRows, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_beginResetModel, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_endInsertColumns, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_endInsertRows, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_endMoveColumns, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_endMoveRows, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_endRemoveColumns, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_endRemoveRows, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_endResetModel, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_sibling, QAndroidItemModelProxy) }); +} + +bool QAndroidItemModelProxy::registerProxyNatives(QJniEnvironment &env) +{ + return env.registerNativeMethods( + Traits::className(), + { Q_JNI_NATIVE_SCOPED_METHOD(jni_columnCount, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_data, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_index, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_parent, QAndroidItemModelProxy), + Q_JNI_NATIVE_SCOPED_METHOD(jni_rowCount, QAndroidItemModelProxy) }); +} + +QT_END_NAMESPACE diff --git a/src/corelib/platform/android/qandroiditemmodelproxy_p.h b/src/corelib/platform/android/qandroiditemmodelproxy_p.h new file mode 100644 index 00000000000..241f4966b13 --- /dev/null +++ b/src/corelib/platform/android/qandroiditemmodelproxy_p.h @@ -0,0 +1,190 @@ +// 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 + +#ifndef QANDROIDITEMMODELPROXY_P_H +#define QANDROIDITEMMODELPROXY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Q_CORE_EXPORT QAndroidItemModelProxy : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit QAndroidItemModelProxy(QtJniTypes::JQtAbstractItemModel jInstance) + : jInstance(jInstance) + { + } + + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool canFetchMore(const QModelIndex &parent) const override; + bool canFetchMoreDefault(const QModelIndex &parent) const; + QHash roleNames() const override; + QHash defaultRoleNames() const; + void fetchMore(const QModelIndex &parent) override; + void fetchMoreDefault(const QModelIndex &parent); + bool hasChildren(const QModelIndex &parent) const override; + bool hasChildrenDefault(const QModelIndex &parent) const; + QModelIndex sibling(int row, int column, const QModelIndex &parent) const override; + QModelIndex siblingDefault(int row, int column, const QModelIndex &parent); + + Q_REQUIRED_RESULT static QAbstractItemModel * + nativeInstance(QtJniTypes::JQtAbstractItemModel itemModel); + Q_REQUIRED_RESULT static QAbstractItemModel *createNativeProxy(QJniObject itemModel); + static QJniObject createProxy(QAbstractItemModel *abstractClass); + + template + static auto invokeNativeProxyMethod(JNIEnv */*env*/, jobject jvmObject, Func func, Args &&...args) + { + Q_ASSERT(jvmObject); + auto model = qobject_cast(nativeInstance(jvmObject)); + Q_ASSERT(model); + return std::invoke(func, model, std::forward(args)...); + } + + template + static auto invokeNativeMethod(JNIEnv */*env*/, jobject jvmObject, Func func, Args &&...args) + { + Q_ASSERT(jvmObject); + auto model = nativeInstance(jvmObject); + Q_ASSERT(model); + return std::invoke(func, model, std::forward(args)...); + } + + template + static auto invokeNativeImpl(JNIEnv */*env*/, jobject jvmObject, Func1 defaultFunc, Func2 func, + Args &&...args) + { + Q_ASSERT(jvmObject); + auto nativeModel = nativeInstance(jvmObject); + auto nativeProxyModel = qobject_cast(nativeModel); + if (nativeProxyModel) + return std::invoke(defaultFunc, nativeProxyModel, std::forward(args)...); + else + return std::invoke(func, nativeModel, std::forward(args)...); + } + + static jint jni_columnCount(JNIEnv *env, jobject object, JQtModelIndex parent); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_columnCount) + + static jobject jni_data(JNIEnv *env, jobject object, JQtModelIndex index, jint role); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_data) + + static jobject jni_index(JNIEnv *env, jobject object, jint row, jint column, + JQtModelIndex parent); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_index) + + static jobject jni_parent(JNIEnv *env, jobject object, JQtModelIndex index); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_parent) + + static jint jni_rowCount(JNIEnv *env, jobject object, JQtModelIndex parent); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_rowCount) + + static jboolean jni_canFetchMore(JNIEnv *env, jobject object, JQtModelIndex parent); + QT_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE_2(jni_canFetchMore, canFetchMore) + + static void jni_fetchMore(JNIEnv *env, jobject object, JQtModelIndex parent); + QT_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE_2(jni_fetchMore, fetchMore) + + static jboolean jni_hasChildren(JNIEnv *env, jobject object, JQtModelIndex parent); + QT_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE_2(jni_hasChildren, hasChildren) + + static jboolean jni_hasIndex(JNIEnv *env, jobject object, jint row, jint column, + JQtModelIndex parent); + QT_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE_2(jni_hasIndex, hasIndex) + + static jobject jni_roleNames(JNIEnv *env, jobject object); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_roleNames) + + static void jni_beginInsertColumns(JNIEnv *env, jobject object, JQtModelIndex parent, + jint first, jint last); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_beginInsertColumns) + + static void jni_beginInsertRows(JNIEnv *env, jobject object, JQtModelIndex parent, jint first, + jint last); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_beginInsertRows) + + static jboolean jni_beginMoveColumns(JNIEnv *env, jobject object, JQtModelIndex sourceParent, + jint sourceFirst, jint sourceLast, + JQtModelIndex destinationParent, jint destinationChild); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_beginMoveColumns) + + static jboolean jni_beginMoveRows(JNIEnv *env, jobject object, JQtModelIndex sourceParent, + jint sourceFirst, jint sourceLast, + JQtModelIndex destinationParent, jint destinationChild); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_beginMoveRows) + + static void jni_beginRemoveColumns(JNIEnv *env, jobject object, JQtModelIndex parent, + jint first, jint last); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_beginRemoveColumns) + + static void jni_beginRemoveRows(JNIEnv *env, jobject object, JQtModelIndex parent, jint first, + jint last); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_beginRemoveRows) + + static void jni_beginResetModel(JNIEnv *env, jobject object); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_beginResetModel) + + static jobject jni_createIndex(JNIEnv *env, jobject object, jint row, jint column, jlong id); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_createIndex) + + static void jni_endInsertColumns(JNIEnv *env, jobject object); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_endInsertColumns) + + static void jni_endInsertRows(JNIEnv *env, jobject object); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_endInsertRows) + + static void jni_endMoveColumns(JNIEnv *env, jobject object); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_endMoveColumns) + + static void jni_endMoveRows(JNIEnv *env, jobject object); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_endMoveRows) + + static void jni_endRemoveColumns(JNIEnv *env, jobject object); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_endRemoveColumns) + + static void jni_endRemoveRows(JNIEnv *env, jobject object); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_endRemoveRows) + + static void jni_endResetModel(JNIEnv *env, jobject object); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_endResetModel) + + static jobject jni_sibling(JNIEnv *env, jobject object, jint row, jint column, + JQtModelIndex parent); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(jni_sibling) + + static bool registerAbstractNatives(QJniEnvironment &env); + static bool registerProxyNatives(QJniEnvironment &env); + +private: + QJniObject jInstance; + friend class QAndroidModelIndexProxy; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDITEMMODELPROXY_P_H diff --git a/src/corelib/platform/android/qandroidmodelindexproxy.cpp b/src/corelib/platform/android/qandroidmodelindexproxy.cpp new file mode 100644 index 00000000000..243f751db23 --- /dev/null +++ b/src/corelib/platform/android/qandroidmodelindexproxy.cpp @@ -0,0 +1,103 @@ +// 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 + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace QtJniTypes; + +QModelIndex QAndroidModelIndexProxy::qInstance(JQtModelIndex jModelIndex) +{ + if (!jModelIndex.isValid()) + return QModelIndex(); + + const QJniArray jPrivateArray = jModelIndex.getField("m_privateData"); + const auto privateData = jPrivateArray.toContainer(); + Q_ASSERT(privateData.size() == 4); + + const jlong modelReference = privateData[3]; + if (!modelReference) + return QModelIndex(); + + const jint row = privateData[0]; + const jint column = privateData[1]; + QAbstractItemModel *model = reinterpret_cast(modelReference); + QAndroidItemModelProxy *proxyModel = qobject_cast(model); + + // If the native model instance is a proxy we have access to the protected function + // createIndex(). Else, if the native instance is not a results Java->Qt proxy, we + // use index() to get the QModelIndex. + if (proxyModel) { + const jint internalId = privateData[2]; + return proxyModel->createIndex(row, column, internalId); + } else { + const JQtModelIndex parent = jModelIndex.getField("m_parent"); + if (parent.isValid()) + return model->index(row, column, QAndroidModelIndexProxy::qInstance(parent)); + } + return QModelIndex(); +} + +JQtModelIndex QAndroidModelIndexProxy::jInstance(QModelIndex modelIndex) +{ + if (!modelIndex.isValid()) + return JQtModelIndex(); + bool isModelProxy = qobject_cast(modelIndex.model()); + if (isModelProxy) + return JQtModelIndex(modelIndex.row(), modelIndex.column(), jlong(modelIndex.internalId()), + reinterpret_cast(modelIndex.model())); + else + return JQtModelIndex(modelIndex.row(), modelIndex.column(), + QAndroidModelIndexProxy::jInstance(modelIndex.parent()), + reinterpret_cast(modelIndex.model())); +} + +jobject QAndroidModelIndexProxy::data(JNIEnv *env, jobject object, int role) +{ + Q_ASSERT(env); + Q_ASSERT(object); + + QModelIndex modelIndex = qInstance(object); + if (!modelIndex.isValid()) + return nullptr; + + return QAndroidTypeConverter::toJavaObject(modelIndex.model()->data(modelIndex, role), env); +} + +jlong QAndroidModelIndexProxy::internalId(JNIEnv *env, jobject object) +{ + Q_ASSERT(env); + Q_ASSERT(object); + return qInstance(object).internalId(); +}; + +jboolean QAndroidModelIndexProxy::isValid(JNIEnv *env, jobject object) +{ + Q_ASSERT(env); + Q_ASSERT(object); + return qInstance(object).isValid(); +} + +JQtModelIndex QAndroidModelIndexProxy::parent(JNIEnv *env, jobject object) +{ + Q_ASSERT(env); + Q_ASSERT(object); + return jInstance(qInstance(object).parent()); +}; + +bool QAndroidModelIndexProxy::registerNatives(QJniEnvironment &env) +{ + return env.registerNativeMethods( + Traits::className(), + { Q_JNI_NATIVE_SCOPED_METHOD(data, QAndroidModelIndexProxy), + Q_JNI_NATIVE_SCOPED_METHOD(internalId, QAndroidModelIndexProxy), + Q_JNI_NATIVE_SCOPED_METHOD(isValid, QAndroidModelIndexProxy), + Q_JNI_NATIVE_SCOPED_METHOD(parent, QAndroidModelIndexProxy) }); +} + +QT_END_NAMESPACE diff --git a/src/corelib/platform/android/qandroidmodelindexproxy_p.h b/src/corelib/platform/android/qandroidmodelindexproxy_p.h new file mode 100644 index 00000000000..db284f17a90 --- /dev/null +++ b/src/corelib/platform/android/qandroidmodelindexproxy_p.h @@ -0,0 +1,55 @@ +// 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 + +#ifndef QANDROIDMODELINDEXPROXY_P_H +#define QANDROIDMODELINDEXPROXY_P_H + +#include + +#include +#include +#include +#include +#include + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +using namespace QtJniTypes; + +class QAndroidItemModelProxy; + +class Q_CORE_EXPORT QAndroidModelIndexProxy +{ +public: + static JQtModelIndex jInstance(QModelIndex modelIndex); + static QModelIndex qInstance(JQtModelIndex jModelIndex); + + static jobject data(JNIEnv *env, jobject object, int role); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(data) + + static jlong internalId(JNIEnv *env, jobject object); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(internalId) + + static jboolean isValid(JNIEnv *env, jobject object); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(isValid) + + static JQtModelIndex parent(JNIEnv *env, jobject object); + Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(parent) + + static bool registerNatives(QJniEnvironment &env); +}; + +QT_BEGIN_NAMESPACE + +#endif // QANDROIDMODELINDEXPROXY_P_H diff --git a/src/corelib/platform/android/qandroidtypeconverter_p.h b/src/corelib/platform/android/qandroidtypeconverter_p.h index 1bde5a5464e..ff294a1a46a 100644 --- a/src/corelib/platform/android/qandroidtypeconverter_p.h +++ b/src/corelib/platform/android/qandroidtypeconverter_p.h @@ -14,8 +14,8 @@ // // We mean it. -#include -#include +#include +#include #include #include diff --git a/src/plugins/platforms/android/androidjnimain.cpp b/src/plugins/platforms/android/androidjnimain.cpp index 5bd2b924fc4..bd4b18e057e 100644 --- a/src/plugins/platforms/android/androidjnimain.cpp +++ b/src/plugins/platforms/android/androidjnimain.cpp @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include #include @@ -881,7 +883,10 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/) || !QAndroidPlatformClipboard::registerNatives(env) || !QAndroidPlatformWindow::registerNatives(env) || !QtAndroidWindowEmbedding::registerNatives(env) - || !AndroidBackendRegister::registerNatives()) { + || !AndroidBackendRegister::registerNatives() + || !QAndroidModelIndexProxy::registerNatives(env) + || !QAndroidItemModelProxy::registerAbstractNatives(env) + || !QAndroidItemModelProxy::registerProxyNatives(env)) { __android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed"); return -1; }