From 8840d3bcbb6608c978b520dc31f0fc9b6d9467cb Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 25 Aug 2019 15:33:51 +0200 Subject: [PATCH] Basic version of ConnectionTreeModel is working. --- pglab/ConnectionController.cpp | 3 + pglab/ConnectionController.h | 7 + pglab/ConnectionListModel.cpp | 244 +++++++++++++++++++++++++++++- pglab/ConnectionListModel.h | 42 +++++ pglab/ConnectionManagerWindow.cpp | 1 + pglab/ConnectionManagerWindow.ui | 3 + pglablib/ConnectionConfig.cpp | 16 ++ pglablib/ConnectionConfig.h | 35 ++++- 8 files changed, 348 insertions(+), 3 deletions(-) diff --git a/pglab/ConnectionController.cpp b/pglab/ConnectionController.cpp index 013e3cc..281eeba 100644 --- a/pglab/ConnectionController.cpp +++ b/pglab/ConnectionController.cpp @@ -18,6 +18,7 @@ ConnectionController::ConnectionController(MasterController *parent) ConnectionController::~ConnectionController() { delete m_connectionManagerWindow; + delete m_connectionTreeModel; delete m_connectionListModel; } @@ -31,6 +32,8 @@ void ConnectionController::init() m_connectionListModel = new ConnectionListModel(this); m_connectionListModel->load(); + m_connectionTreeModel = new ConnectionTreeModel(this, m_masterController->userConfigDatabase()); + m_connectionTreeModel->load(); m_connectionManagerWindow = new ConnectionManagerWindow(m_masterController, nullptr); m_connectionManagerWindow->show(); diff --git a/pglab/ConnectionController.h b/pglab/ConnectionController.h index a5c8f11..06c3633 100644 --- a/pglab/ConnectionController.h +++ b/pglab/ConnectionController.h @@ -7,6 +7,7 @@ class MasterController; class ConnectionConfig; class ConnectionList; class ConnectionListModel; +class ConnectionTreeModel; class ConnectionManagerWindow; class PasswordManager; @@ -23,6 +24,11 @@ public: return m_connectionListModel; } + ConnectionTreeModel *getConnectionTreeModel() + { + return m_connectionTreeModel; + } + void showConnectionManager(); void openSqlWindowForConnection(int connection_index); void openServerWindowForConnection(int connection_index); @@ -38,6 +44,7 @@ private: MasterController *m_masterController; ConnectionList *m_connectionList = nullptr; ConnectionListModel *m_connectionListModel = nullptr; + ConnectionTreeModel *m_connectionTreeModel = nullptr; ConnectionManagerWindow *m_connectionManagerWindow = nullptr; /** Using long lived object so it can remember its master password for sometime diff --git a/pglab/ConnectionListModel.cpp b/pglab/ConnectionListModel.cpp index b14b652..24eaca5 100644 --- a/pglab/ConnectionListModel.cpp +++ b/pglab/ConnectionListModel.cpp @@ -4,12 +4,89 @@ #include #include -#include #include +#include +#include +#include +#include namespace { + const char * const q_create_table_conngroup = +R"__( +CREATE TABLE IF NOT EXISTS conngroup ( + conngroup_id INTEGER PRIMARY KEY, + gname TEXT NOT NULL +);)__"; + + const char * const q_create_table_connection = +R"__( +CREATE TABLE IF NOT EXISTS connection ( + uuid TEXT PRIMARY KEY, + cname TEXT NOT NULL, + conngroup_id INTEGER NOT NULL, + host TEXT NOT NULL, + hostaddr TEXT NOT NULL, + port INTEGER NOT NULL, + user TEXT NOT NULL, + dbname TEXT NOT NULL, + sslmode INTEGER NOT NULL, + sslcert TEXT NOT NULL, + sslkey TEXT NOT NULL, + sslrootcert TEXT NOT NULL, + sslcrl TEXT NOT NULL, + passwordstate INTEGER NOT NULL +);)__"; + + const char * const q_insert_or_replace_into_connection = +R"__(INSERT OR REPLACE INTO connection + VALUES (:uuid, :name, :conngroup_id, :host, :hostaddr, :port, :user, :dbname, + :sslmode, :sslcert, :sslkey, :sslrootcert, :sslcrl, :passwordstate); +)__" ; + + std::tuple InitConnectionTables(QSqlDatabase &db) + { + QSqlQuery q_create_table(db); + q_create_table.prepare(q_create_table_conngroup); + if (!q_create_table.exec()) { + auto err = q_create_table.lastError(); + return { false, err }; + } + q_create_table.prepare(q_create_table_connection); + if (!q_create_table.exec()) { + auto err = q_create_table.lastError(); + return { false, err }; + } + return {true, {}}; + } + + std::tuple SaveConnectionConfig(QSqlDatabase &db, const ConnectionConfig &cc, int conngroup_id) + { + QSqlQuery q(db); + q.prepare(q_insert_or_replace_into_connection); + q.bindValue(":uuid", cc.uuid().toString()); + q.bindValue(":name", stdStrToQ(cc.name())); + q.bindValue(":conngroup_id", conngroup_id); + q.bindValue(":host", stdStrToQ(cc.host())); + q.bindValue(":hostaddr", stdStrToQ(cc.hostAddr())); + q.bindValue(":port", cc.port()); + q.bindValue(":user", stdStrToQ(cc.user())); + q.bindValue(":dbname", stdStrToQ(cc.dbname())); + q.bindValue(":sslmode", static_cast(cc.sslMode())); + q.bindValue(":sslcert", stdStrToQ(cc.sslCert())); + q.bindValue(":sslkey", stdStrToQ(cc.sslKey())); + q.bindValue(":sslrootcert", stdStrToQ(cc.sslRootCert())); + q.bindValue(":sslcrl", stdStrToQ(cc.sslCrl())); + q.bindValue(":passwordstate", static_cast(cc.passwordState())); + + if (!q.exec()) { + auto err = q.lastError(); + return { false, err }; + } + return {true, {}}; + } + /** Saves a connection configuration. Before calling this you may want to call beginGroup. @@ -31,6 +108,8 @@ namespace { settings.setValue("passwordState", static_cast(cc.passwordState())); } + + template bool in_range(T value) { @@ -67,10 +146,10 @@ namespace { } - } // end of unnamed namespace + ConnectionListModel::ConnectionListModel(QObject *parent) : QAbstractListModel(parent) { @@ -311,3 +390,164 @@ QString ConnectionListModel::iniFileName() path += "/connections.ini"; return path; } + + +ConnectionTreeModel::ConnectionTreeModel(QObject *parent, QSqlDatabase &db) + : QAbstractItemModel(parent) + , m_db(db) +{ +} + +void ConnectionTreeModel::load() +{ + InitConnectionTables(m_db); + + auto g1 = std::make_shared(); + g1->name = "Testing"; + + for (int i = 1; i < 3; ++i) { + auto cc = std::make_shared(); + cc->setUuid(QUuid::createUuid()); + cc->setName("testconn " + std::to_string(i)); + g1->add(cc); + } + + auto g2 = std::make_shared(); + g2->name = "Production"; + for (int i = 1; i < 4; ++i) { + auto cc = std::make_shared(); + cc->setUuid(QUuid::createUuid()); + cc->setName("prodconn " + std::to_string(i)); + g2->add(cc); + } + + m_groups = { g1, g2 }; +} + +QVariant ConnectionTreeModel::data(const QModelIndex &index, int role) const +{ + // Code below assumes two level tree groups/connections + // it will fail for nested groups + QVariant v; + + auto privdata = static_cast(index.internalPointer()); + if (auto group = dynamic_cast(privdata); group != nullptr) { + // This is a group + if (role == Qt::DisplayRole) { + if (index.column() == Name) { + v = group->name; + } + } + } + else if (auto conn = dynamic_cast(privdata); conn != nullptr) { + // This is a connection + if (role == Qt::DisplayRole) { + switch (index.column()) { + case Name: v = stdStrToQ(conn->name()); break; + case Host: v = stdStrToQ(conn->host()); break; + case Port: v = conn->port(); break; + case User: v = stdStrToQ(conn->user()); break; + case DbName: v= stdStrToQ(conn->dbname()); break; + } + } + } + + return v; +} + +QVariant ConnectionTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + QVariant v; + if (orientation == Qt::Horizontal) { + if (role == Qt::DisplayRole) { + switch (section) { + case Name: v = tr("Name"); break; + case Host: v = tr("Host"); break; + case Port: v = tr("Port"); break; + case User: v = tr("User"); break; + case DbName: v= tr("Database"); break; + } + } + } + return v; +} + +QModelIndex ConnectionTreeModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return {}; + + const ConnectionNode *node = nullptr; + if (parent.isValid()) { + auto privdata = static_cast(parent.internalPointer()); + if (auto group = dynamic_cast(privdata); group != nullptr) { + node = group->connections().at(row).get(); + } + else { + throw std::logic_error("Should never ask for a child index of a connectionconfig"); + } + } + else { + node = m_groups[row].get(); + } + return createIndex(row, column, const_cast(node)); + +// void *p = nullptr; +// if (parent.isValid()) { +// auto cg = static_cast(parent.internalPointer()); +// auto cc = &cg->connections().at(row); +// p = const_cast(cc); +// } +// else { +// p = const_cast(&m_groups.at(row)); +// } +// return createIndex(row, column, p); +} + +QModelIndex ConnectionTreeModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return {}; + + auto privdata = static_cast(index.internalPointer()); + if (auto group = dynamic_cast(privdata); group != nullptr) { + return {}; + } + else if (auto config = dynamic_cast(privdata); config != nullptr) { + auto p = config->parent(); + auto find_res = std::find_if(m_groups.begin(), m_groups.end(), [p] (auto item) -> bool { return *p == *item; }); + if (find_res != m_groups.end()) { + return createIndex(find_res - m_groups.begin(), 0, config->parent()); + } + } + throw std::logic_error("Should never get here"); +} + +int ConnectionTreeModel::rowCount(const QModelIndex &parent) const +{ + int result = 0; + if (parent.isValid()) { +// if (parent.column() <= 0) { +// result = m_groups[parent.row()].connections().size(); +// } +// else { +// result = 1; +// } + auto privdata = static_cast(parent.internalPointer()); + if (auto group = dynamic_cast(privdata); group != nullptr) { + result = group->connections().size(); + } + else if (auto config = dynamic_cast(privdata); config != nullptr) { + result = 0; + } + } + else { + result = m_groups.size(); + } + return result; +} + +int ConnectionTreeModel::columnCount(const QModelIndex &parent) const +{ + return ColCount; +} diff --git a/pglab/ConnectionListModel.h b/pglab/ConnectionListModel.h index 3496464..b053cc9 100644 --- a/pglab/ConnectionListModel.h +++ b/pglab/ConnectionListModel.h @@ -10,6 +10,48 @@ #include "Expected.h" #include +//#include +class QSqlDatabase; + +class ConnectionTreeModel : public QAbstractItemModel { + Q_OBJECT +public: + enum Columns { + Name, + Host, + Port, + User, + DbName, + + ColCount + }; + + ConnectionTreeModel(QObject *parent, QSqlDatabase &db); + + void load(); + + QVariant data(const QModelIndex &index, int role) const override; + +// Qt::ItemFlags flags(const QModelIndex &index) const override; + + QVariant headerData(int section, Qt::Orientation orientation, + 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; + + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + +private: + using Groups = QVector>; + + QSqlDatabase &m_db; + Groups m_groups; +}; /** \brief Model class for the list of connections. * diff --git a/pglab/ConnectionManagerWindow.cpp b/pglab/ConnectionManagerWindow.cpp index 3fe7019..ec7210e 100644 --- a/pglab/ConnectionManagerWindow.cpp +++ b/pglab/ConnectionManagerWindow.cpp @@ -18,6 +18,7 @@ ConnectionManagerWindow::ConnectionManagerWindow(MasterController *master, QWidg { ui->setupUi(this); ui->listView->setModel(m_connectionController->getConnectionListModel()); + ui->treeView->setModel(m_connectionController->getConnectionTreeModel()); } diff --git a/pglab/ConnectionManagerWindow.ui b/pglab/ConnectionManagerWindow.ui index 41fc0f6..cca5094 100644 --- a/pglab/ConnectionManagerWindow.ui +++ b/pglab/ConnectionManagerWindow.ui @@ -30,6 +30,9 @@ + + + diff --git a/pglablib/ConnectionConfig.cpp b/pglablib/ConnectionConfig.cpp index 1dd47b7..f2ba553 100644 --- a/pglablib/ConnectionConfig.cpp +++ b/pglablib/ConnectionConfig.cpp @@ -57,6 +57,16 @@ ConnectionConfig::ConnectionConfig() : m_applicationName(QCoreApplication::applicationName().toUtf8().data()) {} +ConnectionGroup *ConnectionConfig::parent() +{ + return m_group; +} + +void ConnectionConfig::setParent(ConnectionGroup *grp) +{ + m_group = grp; +} + void ConnectionConfig::setUuid(const QUuid &uuid) { @@ -347,3 +357,9 @@ void ConnectionConfig::strToEnv(QProcessEnvironment &env, const QString &var, co else env.insert(var, stdStrToQ(val)); } + +void ConnectionGroup::add(std::shared_ptr cc) +{ + cc->setParent(this); + m_connections.push_back(cc); +} diff --git a/pglablib/ConnectionConfig.h b/pglablib/ConnectionConfig.h index a58d69a..20e9109 100644 --- a/pglablib/ConnectionConfig.h +++ b/pglablib/ConnectionConfig.h @@ -2,6 +2,7 @@ #define CONNECTION_H #include +#include #include #include @@ -24,10 +25,41 @@ enum class PasswordState { class QProcessEnvironment; class QString; -class ConnectionConfig { +class ConnectionConfig; + +/** Base class for ConnectionGroup and ConnectionConfig + * to enable the use of RTTI in the tree model class. + */ +class ConnectionNode { +public: + virtual ~ConnectionNode() = default; +}; + +class ConnectionGroup: public ConnectionNode { +public: + int conngroup_id; + QString name; + + using Connections = QVector>; + const Connections& connections() const { return m_connections; } + + void add(std::shared_ptr cc); + + bool operator==(const ConnectionGroup &rhs) const { + return conngroup_id == rhs.conngroup_id + && name == rhs.name; + } +private: + Connections m_connections; +}; + +class ConnectionConfig: public ConnectionNode { public: ConnectionConfig(); // Default object containing invalid uuid + ConnectionGroup* parent(); + void setParent(ConnectionGroup *grp); + void setUuid(const QUuid &uuid); const QUuid &uuid() const; @@ -102,6 +134,7 @@ private: PasswordState m_passwordState = PasswordState::NotStored; bool m_dirty = false; + ConnectionGroup* m_group; static void strToEnv(QProcessEnvironment &env, const QString &var, const std::string &val);