List the labels of all lodgings in QML

To query the database, i have to run the query inside the same thread
where the database was created, which means that Database should be a
singleton not only within QML, but also in C++, and has to be the _same_
singleton in both worlds.

Although i expose an object that i have created, i followed the same
section titled “Exposing an existing object as a singleton” from Qt’s
documentation[0].  The only difference is that i do not have to declare
the element as a foreign type, because it is a bona fide QObject.

[0]: https://doc.qt.io/qt-6/qml-singleton.html#exposing-an-existing-
object-as-a-singleton
This commit is contained in:
jordi fita mas 2024-12-24 03:46:20 +01:00
parent 7eeccbb033
commit d0e2659c30
7 changed files with 179 additions and 3 deletions

View File

@ -9,6 +9,7 @@ qt_add_qml_module(${PROJECT_NAME}
QtCore
QtQuick
SOURCES
calendarlistmodel.cpp calendarlistmodel.h
database.cpp database.h
mnemonicattached.cpp mnemonicattached.h
QML_FILES

View File

@ -1,4 +1,5 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@ -15,6 +16,18 @@ Page {
}
}
ListView {
anchors.fill: parent
delegate: Text {
required property string name
text: name
}
model: CalendarListModel {
}
}
MnemonicAction {
id: logoutAction

86
src/calendarlistmodel.cpp Normal file
View File

@ -0,0 +1,86 @@
#include "calendarlistmodel.h"
#include <QSqlQuery>
#include <QVariant>
#include "database.h"
struct CalendarListModel::Calendar
{
QString name;
};
CalendarListModel::CalendarListModel(QObject *parent)
: QAbstractListModel{parent}
, m_calendars{}
{
fetch();
}
CalendarListModel::~CalendarListModel()
{
qDeleteAll(m_calendars);
}
QVariant CalendarListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole) {
return {};
}
return section;
}
int CalendarListModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return m_calendars.count();
}
QHash<int, QByteArray> CalendarListModel::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
roles[Name] = "name";
return roles;
}
void CalendarListModel::fetch()
{
Database::query([]() {
QSqlQuery query("select label from campsite order by label");
QVector<Calendar> calendars;
while (query.next()) {
Calendar calendar{query.value(0).toString()};
calendars.append(calendar);
}
return calendars;
}).then([this](const QVector<Calendar> &calendars) {
if (calendars.empty()) {
return;
}
beginInsertRows(QModelIndex(), 0, calendars.count() - 1);
for (const Calendar &calendar : calendars) {
m_calendars.append(new Calendar(calendar));
}
endInsertRows();
});
}
QVariant CalendarListModel::data(const QModelIndex &index, int role) const
{
if (!checkIndex(index,
QAbstractItemModel::CheckIndexOption::IndexIsValid
| QAbstractItemModel::CheckIndexOption::ParentIsInvalid)) {
return {};
}
Calendar *calendar = m_calendars.at(index.row());
switch (role) {
case Name:
return calendar->name;
}
return {};
}
#include "moc_calendarlistmodel.cpp"

40
src/calendarlistmodel.h Normal file
View File

@ -0,0 +1,40 @@
#ifndef CALENDARLISTMODEL_H
#define CALENDARLISTMODEL_H
#include <QAbstractListModel>
#include <QtQmlIntegration>
class CalendarListModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
public:
enum Roles {
Name = Qt::UserRole,
};
explicit CalendarListModel(QObject *parent = nullptr);
~CalendarListModel() override;
QVariant headerData(int section,
Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override;
private:
struct Calendar;
Q_DISABLE_COPY_MOVE(CalendarListModel)
void fetch();
QVector<Calendar *> m_calendars;
};
#endif // CALENDARLISTMODEL_H

View File

@ -2,7 +2,6 @@
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>
#include <QtConcurrent>
Database::Database(QObject *parent)
: QObject{parent}
@ -12,6 +11,29 @@ Database::Database(QObject *parent)
m_pool.setExpiryTimeout(-1);
}
Database &Database::getInstance()
{
static Database instance;
return instance;
}
Database *Database::create(QQmlEngine *qmlEngine, QJSEngine *)
{
Database &instance = getInstance();
Q_ASSERT(qmlEngine->thread() == instance.thread());
static QQmlEngine *engine = nullptr;
if (engine) {
Q_ASSERT(qmlEngine == engine);
} else {
engine = qmlEngine;
}
QJSEngine::setObjectOwnership(&instance, QJSEngine::CppOwnership);
return &instance;
}
QFuture<void> Database::open(const QString &user,
const QString &password,
const QString &hostName,

View File

@ -3,8 +3,9 @@
#include <QFuture>
#include <QObject>
#include <QQmlEngine>
#include <QThreadPool>
#include <QtQmlIntegration>
#include <QtConcurrent>
class Database : public QObject
{
@ -13,7 +14,9 @@ class Database : public QObject
QML_ELEMENT
public:
explicit Database(QObject *parent = nullptr);
static Database &getInstance();
static Database *create(QQmlEngine *qmlEngine, QJSEngine *);
Q_INVOKABLE QFuture<void> open(const QString &user,
const QString &password,
@ -24,12 +27,21 @@ public:
const QString &connectOptions);
Q_INVOKABLE QFuture<void> close();
template<class Function>
static auto query(Function &&f)
{
return QtConcurrent::run(&getInstance().m_pool, f);
}
signals:
void closed();
void errorOcurred(const QString &errorMessage);
void opened();
private:
explicit Database(QObject *parent = nullptr);
Q_DISABLE_COPY_MOVE(Database)
QThreadPool m_pool;
};

View File

@ -1,5 +1,6 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "database.h"
int main(int argc, char *argv[])
{
@ -8,6 +9,7 @@ int main(int argc, char *argv[])
QGuiApplication app(argc, argv);
Database::getInstance(); // return ignored; create instance before QML engine
QQmlApplicationEngine engine;
QObject::connect(
&engine,