34void QQmlAbstractColumnModel::columns_append(QQmlListProperty<QQmlTableModelColumn> *property,
35 QQmlTableModelColumn *value)
37 auto *model =
static_cast<QQmlAbstractColumnModel *>(property->object);
40 auto *column = qobject_cast<QQmlTableModelColumn *>(value);
42 model->mColumns.append(column);
66void QQmlAbstractColumnModel::columns_replace(QQmlListProperty<QQmlTableModelColumn> *property, qsizetype index, QQmlTableModelColumn *value)
68 auto *model =
static_cast<QQmlAbstractColumnModel *>(property->object);
70 if (
auto *column = qobject_cast<QQmlTableModelColumn *>(value))
71 return model->mColumns.replace(index, column);
89QVariant QQmlAbstractColumnModel::data(
const QModelIndex &index,
int role)
const
91 if (!index.isValid()) {
92 qmlWarning(
this) <<
"data(): invalid QModelIndex";
96 const int row = index.row();
97 if (row < 0 || row >= rowCount(parent(index))) {
98 qmlWarning(
this) <<
"data(): invalid row specified in QModelIndex";
102 const int column = index.column();
103 if (column < 0 || column >= columnCount(parent(index))) {
104 qmlWarning(
this) <<
"data(): invalid column specified in QModelIndex";
108 const ColumnMetadata columnMetadata = mColumnMetadata.at(column);
109 const QString roleName = QString::fromUtf8(mRoleNames.value(role));
110 if (!columnMetadata.roles.contains(roleName)) {
111 qmlWarning(
this) <<
"data(): no role named " << roleName
112 <<
" at column index " << column <<
". The available roles for that column are: "
113 << columnMetadata.roles.keys();
117 const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName);
118 if (roleData.columnRole == ColumnRole::StringRole) {
120 return dataPrivate(index, roleName);
125 QJSValue getter = mColumns.at(column)->getterAtRole(roleName);
128 const auto args = QJSValueList() << qmlEngine(
this)->toScriptValue(index);
129 return getter.call(args).toVariant();
140bool QQmlAbstractColumnModel::setData(
const QModelIndex &index,
const QVariant &value,
int role)
142 Q_ASSERT(index.isValid());
144 const int row = index.row();
145 if (row < 0 || row >= rowCount(parent(index)))
148 const int column = index.column();
149 if (column < 0 || column >= columnCount(parent(index)))
152 const QString roleName = QString::fromUtf8(mRoleNames.value(role));
154 qCDebug(lcColumnModel).nospace() <<
"setData() called with index "
155 << index <<
", value " << value <<
" and role " << roleName;
158 const ColumnMetadata columnMetadata = mColumnMetadata.at(index.column());
159 if (!columnMetadata.roles.contains(roleName)) {
160 qmlWarning(
this) <<
"setData(): no role named \"" << roleName
161 <<
"\" at column index " << column <<
". The available roles for that column are: "
162 << columnMetadata.roles.keys();
168 const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName);
169 QVariant effectiveValue = value;
170 if (value.userType() != roleData.type) {
171 if (!value.canConvert(QMetaType(roleData.type))) {
172 qmlWarning(
this).nospace() <<
"setData(): the value " << value
173 <<
" set at row " << row <<
" column " << column <<
" with role " << roleName
174 <<
" cannot be converted to " << roleData.typeName;
178 if (!effectiveValue.convert(QMetaType(roleData.type))) {
179 qmlWarning(
this).nospace() <<
"setData(): failed converting value " << value
180 <<
" set at row " << row <<
" column " << column <<
" with role " << roleName
181 <<
" to " << roleData.typeName;
186 if (roleData.columnRole == ColumnRole::StringRole) {
188 setDataPrivate(index, roleData.name, value);
190 qmlWarning(
this).nospace() <<
"setData(): manipulation of complex row "
191 <<
"structures is not supported";
195 QVector<
int> rolesChanged;
196 rolesChanged.append(role);
197 emit dataChanged(index, index, rolesChanged);
233QQmlAbstractColumnModel::ColumnRoleMetadata::ColumnRoleMetadata(
234 ColumnRole role, QString name,
int type, QString typeName) :
236 name(std::move(name)),
238 typeName(std::move(typeName))
247QQmlAbstractColumnModel::ColumnRoleMetadata QQmlAbstractColumnModel::fetchColumnRoleData(
const QString &roleNameKey,
248 QQmlTableModelColumn *tableModelColumn,
int columnIndex)
const
250 const QVariant row = firstRow();
251 ColumnRoleMetadata roleData;
253 QJSValue columnRoleGetter = tableModelColumn->getterAtRole(roleNameKey);
254 if (columnRoleGetter.isUndefined()) {
259 if (columnRoleGetter.isString()) {
261 if (row.userType() != QMetaType::QVariantMap) {
262 qmlWarning(
this).quote() <<
"expected row for role "
263 << roleNameKey <<
" of TableModelColumn at index "
264 << columnIndex <<
" to be a simple object, but it's "
265 << row.typeName() <<
" instead: " << row;
269 QString rolePropertyName = columnRoleGetter.toString();
270 const QVariant roleProperty = row.toMap().value(rolePropertyName);
272 roleData.columnRole = ColumnRole::StringRole;
273 roleData.type = roleProperty.userType();
274 roleData.typeName = QString::fromLatin1(roleProperty.typeName());
275 roleData.name = std::move(rolePropertyName);
276 }
else if (columnRoleGetter.isCallable()) {
279 const auto modelIndex = index(0, columnIndex);
280 const auto args = QJSValueList() << qmlEngine(
this)->toScriptValue(modelIndex);
281 const QVariant cellData = columnRoleGetter.call(args).toVariant();
285 roleData.columnRole = ColumnRole::FunctionRole;
286 roleData.type = cellData.userType();
287 roleData.typeName = QString::fromLatin1(cellData.typeName());
290 qmlWarning(
this) <<
"TableModelColumn role for column at index "
291 << columnIndex <<
" must be either a string or a function; actual type is: "
292 << columnRoleGetter.toString();
298void QQmlAbstractColumnModel::fetchColumnMetadata()
300 qCDebug(lcColumnModel) <<
"gathering metadata for" << mColumnCount <<
"columns from first row:";
302 static const auto supportedRoleNames = QQmlTableModelColumn::supportedRoleNames();
307 for (
int columnIndex = 0; columnIndex < mColumns.size(); ++columnIndex) {
308 QQmlTableModelColumn *column = mColumns.at(columnIndex);
309 qCDebug(lcColumnModel).nospace() <<
"- column " << columnIndex <<
":";
311 ColumnMetadata metaData;
312 const auto builtInRoleKeys = supportedRoleNames.keys();
313 for (
const int builtInRoleKey : builtInRoleKeys) {
314 const QString builtInRoleName = supportedRoleNames.value(builtInRoleKey);
315 ColumnRoleMetadata roleData = fetchColumnRoleData(builtInRoleName, column, columnIndex);
316 if (roleData.type == QMetaType::UnknownType) {
321 qCDebug(lcColumnModel).nospace() <<
" - added metadata for built-in role "
322 << builtInRoleName <<
" at column index " << columnIndex
323 <<
": name=" << roleData.name <<
" typeName=" << roleData.typeName
324 <<
" type=" << roleData.type;
327 metaData.roles.insert(builtInRoleName, roleData);
329 mRoleNames[builtInRoleKey] = builtInRoleName.toLatin1();
331 mColumnMetadata.insert(columnIndex, metaData);
335bool QQmlAbstractColumnModel::validateRowType(QLatin1StringView functionName,
const QVariant &row)
const
337 if (!row.canConvert<QJSValue>()) {
338 qmlWarning(
this) << functionName <<
": expected \"row\" argument to be a QJSValue,"
339 <<
" but got " << row.typeName() <<
" instead:\n" << row;
343 const auto rowAsJSValue = row.value<QJSValue>();
344 if (!rowAsJSValue.isObject() && !rowAsJSValue.isArray()) {
345 qmlWarning(
this) << functionName <<
": expected \"row\" argument "
346 <<
"to be an object or array, but got:\n" << rowAsJSValue.toString();
353bool QQmlAbstractColumnModel::validateNewRow(QLatin1StringView functionName,
const QVariant &row,
354 NewRowOperationFlag operation)
const
356 if (mColumnMetadata.isEmpty()) {
363 const bool isVariantMap = (row.userType() == QMetaType::QVariantMap);
367 if (operation != SetRowsOperation && (!isVariantMap && !validateRowType(functionName, row)))
370 const QVariant rowAsVariant = operation == SetRowsOperation || isVariantMap
371 ? row : row.value<QJSValue>().toVariant();
372 if (rowAsVariant.userType() != QMetaType::QVariantMap) {
373 qmlWarning(
this) << functionName <<
": row manipulation functions "
374 <<
"do not support complex rows";
378 const QVariantMap rowAsMap = rowAsVariant.toMap();
379 const int columnCount = rowAsMap.size();
380 if (columnCount < mColumnCount) {
381 qmlWarning(
this) << functionName <<
": expected " << mColumnCount
382 <<
" columns, but only got " << columnCount;
388 for (
int columnIndex = 0; columnIndex < mColumns.size(); ++columnIndex) {
389 QQmlTableModelColumn *column = mColumns.at(columnIndex);
390 const QHash<QString, QJSValue> getters = column->getters();
391 const auto roleNames = getters.keys();
392 const ColumnMetadata columnMetadata = mColumnMetadata.at(columnIndex);
393 for (
const QString &roleName : roleNames) {
394 const ColumnRoleMetadata roleData = columnMetadata.roles.value(roleName);
395 if (roleData.columnRole == ColumnRole::FunctionRole)
398 if (!rowAsMap.contains(roleData.name)) {
399 qmlWarning(
this).noquote() << functionName <<
": expected a property named \""
400 << roleData.name <<
"\" in row";
404 const QVariant rolePropertyValue = rowAsMap.value(roleData.name);
406 if (rolePropertyValue.userType() != roleData.type) {
407 if (!rolePropertyValue.canConvert(QMetaType(roleData.type))) {
408 qmlWarning(
this).noquote() << functionName <<
": expected the property named \""
409 << roleData.name <<
"\" to be of type \"" << roleData.typeName
410 <<
"\", but got \"" << QString::fromLatin1(rolePropertyValue.typeName())
415 QVariant effectiveValue = rolePropertyValue;
416 if (!effectiveValue.convert(QMetaType(roleData.type))) {
417 qmlWarning(
this).noquote() << functionName <<
": failed converting value \""
418 << rolePropertyValue <<
"\" set at column " << columnIndex <<
" with role \""
419 << QString::fromLatin1(rolePropertyValue.typeName()) <<
"\" to \""
420 << roleData.typeName <<
"\"";