diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(cachedtable) diff --git a/examples/cachedtable/CMakeLists.txt b/examples/cachedtable/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/examples/cachedtable/CMakeLists.txt @@ -0,0 +1,19 @@ +find_package(Qt5 COMPONENTS Widgets REQUIRED) + +set(cachedtable_SRCS + main.cpp + MessageHandler.cpp + TableEditor.cpp + TableModel.cpp +) + +add_executable(cachedtable ${cachedtable_SRCS}) +target_include_directories(cachedtable + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} + PRIVATE ${CMAKE_CURRENT_BINARY_DIR} +) +set_target_properties(cachedtable PROPERTIES + AUTOUIC TRUE + AUTOMOC TRUE +) +target_link_libraries(cachedtable Qt5::Widgets KDb) diff --git a/examples/cachedtable/MessageHandler.h b/examples/cachedtable/MessageHandler.h new file mode 100644 --- /dev/null +++ b/examples/cachedtable/MessageHandler.h @@ -0,0 +1,38 @@ +/* This file is part of the KDE project + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include + +class MessageHandler : public KDbMessageHandler +{ +public: + explicit MessageHandler(QWidget *parent = nullptr); + ~MessageHandler() override; + + void showErrorMessage(KDbMessageHandler::MessageType messageType, + const QString &message, + const QString &details = QString(), + const QString &caption = QString()) override; + + void showErrorMessage(const KDbResult &result, + KDbMessageHandler::MessageType messageType = Error, + const QString &message = QString(), + const QString &caption = QString()) override; +}; diff --git a/examples/cachedtable/MessageHandler.cpp b/examples/cachedtable/MessageHandler.cpp new file mode 100644 --- /dev/null +++ b/examples/cachedtable/MessageHandler.cpp @@ -0,0 +1,56 @@ +/* This file is part of the KDE project + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "MessageHandler.h" +#include +#include + +MessageHandler::MessageHandler(QWidget *parent) + : KDbMessageHandler(parent) +{} + +MessageHandler::~MessageHandler() {} + +void MessageHandler::showErrorMessage(KDbMessageHandler::MessageType messageType, + const QString &message, + const QString &details, + const QString &caption) +{ + QString text = QStringLiteral("%1\n\n%2").arg(message, details); + switch (messageType) { + case Information: + QMessageBox::information(parentWidget(), caption, text); + break; + case Sorry: + case Warning: + QMessageBox::warning(parentWidget(), caption, text); + break; + case Error: + case Fatal: + QMessageBox::critical(parentWidget(), caption, text); + break; + } +} + +void MessageHandler::showErrorMessage(const KDbResult &result, + KDbMessageHandler::MessageType messageType, + const QString &message, + const QString &caption) +{ + showErrorMessage(messageType, result.message(), message, caption); +} diff --git a/examples/cachedtable/TableEditor.h b/examples/cachedtable/TableEditor.h new file mode 100644 --- /dev/null +++ b/examples/cachedtable/TableEditor.h @@ -0,0 +1,55 @@ +/* This file is part of the KDE project + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include +#include + +namespace Ui { +class TableEditor; +} + +class KDbConnectionData; +class KDbConnection; +class KDbDriverManager; +class TableModel; + +class TableEditor : public QMainWindow, public KDbResultable +{ + Q_OBJECT + +public: + explicit TableEditor(const KDbConnectionData &connectionData, QWidget *parent = nullptr); + ~TableEditor() override; + + void closeEvent(QCloseEvent *event) override; + +private Q_SLOTS: + void quit(); + void revert(); + void submit(); + +private: + bool createDatabase(const KDbConnectionData &connectionData); + + KDbConnection *m_connection; + KDbDriverManager *m_driverManager; + TableModel *m_model; + Ui::TableEditor *ui; +}; diff --git a/examples/cachedtable/TableEditor.cpp b/examples/cachedtable/TableEditor.cpp new file mode 100644 --- /dev/null +++ b/examples/cachedtable/TableEditor.cpp @@ -0,0 +1,167 @@ +/* This file is part of the KDE project + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "TableEditor.h" +#include "MessageHandler.h" +#include "TableModel.h" +#include "ui_TableEditor.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TableEditor::TableEditor(const KDbConnectionData &connectionData, QWidget *parent) + : QMainWindow(parent) + , m_connection(nullptr) + , m_driverManager(new KDbDriverManager) + , m_model(new TableModel) + , ui(new Ui::TableEditor) +{ + setMessageHandler(new MessageHandler(this)); + m_model->setMessageHandler(messageHandler()); + if (!createDatabase(connectionData)) { + return; + } + ui->setupUi(this); + ui->tableView->setModel(m_model); + ui->tableView->resizeColumnsToContents(); +} + +TableEditor::~TableEditor() +{ + delete ui; + delete m_driverManager; +} + +void TableEditor::closeEvent(QCloseEvent *event) +{ + QMainWindow::closeEvent(event); + delete m_model; + m_model = nullptr; +} + +void TableEditor::quit() +{ + close(); +} + +void TableEditor::revert() +{ + m_model->revertAll(); +} + +void TableEditor::submit() +{ + KDbMessageGuard guard(m_model); + KDbTransactionGuard transaction(m_connection); + if (m_model->submitAll()) { + transaction.commit(); + } +} + +bool TableEditor::createDatabase(const KDbConnectionData &connectionData) +{ + KDbMessageGuard guard(this); + + KDbDriver *driver = m_driverManager->driver(connectionData.driverId()); + if (!driver) { + m_result = m_driverManager->result(); + qWarning() << "Error getting driver:" << m_result; + return false; + } + + m_connection = driver->createConnection(connectionData); + if (!m_connection) { + m_result = driver->result(); + qWarning() << "Error creating connection:" << m_result; + return false; + } + if (!m_connection->connect()) { + m_result = m_connection->result(); + qWarning() << "Error connecting:" << m_result; + return false; + } + if (m_connection->databaseExists(connectionData.databaseName())) { + if (!m_connection->dropDatabase(connectionData.databaseName())) { + m_result = m_connection->result(); + qWarning() << "Error dropping database:" << m_result; + return false; + } + } + if (!m_connection->createDatabase(connectionData.databaseName())) { + m_result = m_connection->result(); + qWarning() << "Error creating database:" << m_result; + return false; + } + if (!m_connection->useDatabase(connectionData.databaseName())) { + m_result = m_connection->result(); + qWarning() << "Error opening database:" << m_result; + return false; + } + + QScopedPointer developersTable(new KDbTableSchema("developer")); + developersTable->addField(new KDbField("id", + KDbField::Integer, + KDbField::PrimaryKey | KDbField::AutoInc, + KDbField::Unsigned)); + developersTable->addField(new KDbField("devname", KDbField::Text, KDbField::NotNull)); + developersTable->addField(new KDbField("project", KDbField::Text)); + developersTable->addField(new KDbField("country", KDbField::Text)); + developersTable->addField(new KDbField("mobile", KDbField::Text)); + developersTable->addField(new KDbField("lat", KDbField::Text)); + developersTable->addField(new KDbField("lon", KDbField::Text)); + developersTable->addField(new KDbField("code", KDbField::Text)); + developersTable->addField(new KDbField("projectlead", KDbField::Boolean, KDbField::NotNull, + KDbField::NoOptions, 0, 0, false)); + if (!m_connection->createTable(developersTable.data())) { + m_result = m_connection->result(); + qWarning() << "Error creating developers table:" << m_result; + return false; + } + // createTable takes ownership of the pointer + KDbTableSchema *table = developersTable.take(); + QScopedPointer fields(table->subList( + "devname", "project", "country", "mobile", "lat", "lon", "code", "projectlead")); + QVector records{ + {"Adam Pigg", "Kexi", QObject::tr("United Kingdom"), "0123456789", 58.816, -3.1484, "1746287369", false}, + {"Jaroslaw Staniek", "Kexi", QObject::tr("Poland"), "8472947462", 51.895182, 19.623270, "1234567890", true}, + {"Boudewijn Rempt", "Krita", QObject::tr("Netherlands"), "8472947462", 48.858915, 2.347661, "1234567890", true}, + }; + for (const QVariantList &record : records) { + if (!m_connection->insertRecord(fields.data(), record)) { + m_result = m_connection->result(); + qWarning() << "Error inserting record:" << m_result; + return false; + } + } + if (!m_model->setTable(table, m_connection)) { + m_result = m_model->result(); + return false; + } + return true; +} diff --git a/examples/cachedtable/TableEditor.ui b/examples/cachedtable/TableEditor.ui new file mode 100644 --- /dev/null +++ b/examples/cachedtable/TableEditor.ui @@ -0,0 +1,118 @@ + + + TableEditor + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + + + + + + + &Submit + + + + + + + &Revert + + + + + + + &Quit + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + quitButton + clicked() + TableEditor + quit() + + + 749 + 79 + + + 399 + 299 + + + + + revertButton + clicked() + TableEditor + revert() + + + 749 + 50 + + + 399 + 299 + + + + + submitButton + clicked() + TableEditor + submit() + + + 749 + 21 + + + 399 + 299 + + + + + + submit() + revert() + quit() + + diff --git a/examples/cachedtable/TableModel.h b/examples/cachedtable/TableModel.h new file mode 100644 --- /dev/null +++ b/examples/cachedtable/TableModel.h @@ -0,0 +1,64 @@ +/* This file is part of the KDE project + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include +#include +#include + +class KDbConnection; +class KDbCursor; +class KDbTableSchema; +class KDbRecordEditBuffer; +class KDbRecordData; + +class TableModel : public QAbstractTableModel, public KDbResultable +{ + Q_OBJECT + +public: + explicit TableModel(QObject *parent = nullptr); + ~TableModel() override; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setTable(KDbTableSchema *tableScheme, KDbConnection *connection); + QVariant headerData(int section, + Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + void revertAll(); + void revertRow(int row); + bool submitAll(); + + Qt::ItemFlags flags(const QModelIndex &index) const override; + +private: + void clear(); + bool resetCursor(KDbCursor *cursor); + + QVector m_data; + KDbCursor *m_cursor; + KDbTableSchema *m_table; + QMap m_editBuffers; +}; diff --git a/examples/cachedtable/TableModel.cpp b/examples/cachedtable/TableModel.cpp new file mode 100644 --- /dev/null +++ b/examples/cachedtable/TableModel.cpp @@ -0,0 +1,211 @@ +/* This file is part of the KDE project + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "TableModel.h" +#include +#include +#include +#include +#include +#include +#include +#include + +TableModel::TableModel(QObject *parent) + : QAbstractTableModel(parent) + , m_data() + , m_cursor(nullptr) + , m_table(nullptr) + , m_editBuffers() +{} + +TableModel::~TableModel() +{ + resetCursor(nullptr); +} + +int TableModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + return m_data.count(); +} + +int TableModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid() || !m_table) { + return 0; + } + return m_table->fieldCount(); +} + +bool TableModel::setTable(KDbTableSchema *tableScheme, KDbConnection *connection) +{ + beginResetModel(); + m_table = tableScheme; + bool result = resetCursor(connection->executeQuery(m_table)); + endResetModel(); + return result; +} + +QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal || role != Qt::DisplayRole || !m_table) { + return QAbstractTableModel::headerData(section, orientation, role); + } + return m_table->field(section)->name(); +} + +QVariant TableModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || m_data.isEmpty()) { + return QVariant(); + } + + if (role != Qt::DisplayRole && role != Qt::EditRole) { + return QVariant(); + } + KDbRecordData *record = m_data.at(index.row()); + KDbRecordEditBuffer *editBuffer = m_editBuffers.value(index.row()); + if (!editBuffer) { + return record->value(index.column()); + } + const QVariant *value + = editBuffer->at(m_cursor->query()->expandedOrInternalField(m_cursor->connection(), + index.column()), + false); + if (!value) { + return record->value(index.column()); + } + return *value; +} + +bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (role == Qt::EditRole && !m_data.isEmpty() && data(index, role) != value) { + KDbRecordEditBuffer *editBuffer = m_editBuffers.value(index.row()); + if (!editBuffer) { + editBuffer = new KDbRecordEditBuffer(true); + m_editBuffers.insert(index.row(), editBuffer); + } + editBuffer->insert(m_cursor->query()->expandedOrInternalField(m_cursor->connection(), + index.column()), + value); + emit dataChanged(index, index, QVector() << role); + return true; + } + return false; +} + +void TableModel::revertAll() +{ + const QList rows(m_editBuffers.keys()); + for (int row = rows.size() - 1; row >= 0; row--) { + revertRow(rows.at(row)); + } +} + +void TableModel::revertRow(int row) +{ + if (row < 0) { + return; + } + KDbRecordEditBuffer *editBuffer = m_editBuffers.value(row); + if (!editBuffer) { + return; + } + m_editBuffers.remove(row); + delete editBuffer; + emit dataChanged(index(row, 0), index(row, columnCount() - 1)); +} + +bool TableModel::submitAll() +{ + if (!m_cursor) { + return true; + } + const QList rows(m_editBuffers.keys()); + for (int i = rows.size() - 1; i >= 0; i--) { + int row = rows.at(i); + KDbRecordEditBuffer *editBuffer = m_editBuffers.value(row); + if (!editBuffer) { + continue; + } + KDbRecordData *record = m_data.at(row); + if (!m_cursor->updateRecord(record, editBuffer)) { + m_result = m_cursor->result(); + return false; + } + m_editBuffers.remove(row); + delete editBuffer; + } + return true; +} + +Qt::ItemFlags TableModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) { + return Qt::NoItemFlags; + } + Qt::ItemFlags flags = QAbstractTableModel::flags(index); + return flags | Qt::ItemIsEditable; +} + +void TableModel::clear() +{ + revertAll(); + qDeleteAll(m_data); + m_data.clear(); +} + +bool TableModel::resetCursor(KDbCursor *cursor) +{ + if (cursor == m_cursor) { + return true; + } + if (m_cursor) { + clear(); + if (!m_cursor->connection()->deleteCursor(m_cursor)) { + return false; + } + } + m_cursor = cursor; + if (m_cursor) { + if (!m_cursor->moveFirst() && m_cursor->result().isError()) { + m_result = m_cursor->result(); + return false; + } + while (!m_cursor->eof()) { + KDbRecordData *record = m_cursor->storeCurrentRecord(); + if (!record) { + clear(); + m_result = KDbResult(ERR_CURSOR_RECORD_FETCHING, + tr("Could not retrieve record from cursor")); + return false; + } + m_data.append(record); + if (!m_cursor->moveNext() && m_cursor->result().isError()) { + clear(); + m_result = m_cursor->result(); + return false; + } + } + } + return true; +} diff --git a/examples/cachedtable/main.cpp b/examples/cachedtable/main.cpp new file mode 100644 --- /dev/null +++ b/examples/cachedtable/main.cpp @@ -0,0 +1,59 @@ +/* This file is part of the KDE project + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "TableEditor.h" +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + QCoreApplication::setApplicationName(QStringLiteral("CachedTableExample")); + QCoreApplication::setApplicationVersion(QStringLiteral(KDB_VERSION_STRING)); + QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org")); + + QTemporaryFile databaseFile; + if (!databaseFile.open()) { + qWarning() << "Could not create temporary file for database"; + return EXIT_FAILURE; + } + + KDbConnectionData connectionData; + connectionData.setCaption("Example Database"); + connectionData.setDriverId("org.kde.kdb.sqlite"); + connectionData.setHostName(QString()); + connectionData.setPort(0); + connectionData.setDatabaseName(databaseFile.fileName()); + connectionData.setLocalSocketFileName(QString()); + connectionData.setUseLocalSocketFile(true); + connectionData.setUserName(QString()); + connectionData.setPassword(QString()); + connectionData.setSavePassword(false); + + TableEditor editor(connectionData); + if (editor.result().isError()) { + return EXIT_FAILURE; + } + editor.show(); + + return app.exec(); +}