Basic version of ConnectionTreeModel is working.

This commit is contained in:
eelke 2019-08-25 15:33:51 +02:00
parent 3721808df4
commit 8840d3bcbb
8 changed files with 348 additions and 3 deletions

View file

@ -18,6 +18,7 @@ ConnectionController::ConnectionController(MasterController *parent)
ConnectionController::~ConnectionController() ConnectionController::~ConnectionController()
{ {
delete m_connectionManagerWindow; delete m_connectionManagerWindow;
delete m_connectionTreeModel;
delete m_connectionListModel; delete m_connectionListModel;
} }
@ -31,6 +32,8 @@ void ConnectionController::init()
m_connectionListModel = new ConnectionListModel(this); m_connectionListModel = new ConnectionListModel(this);
m_connectionListModel->load(); m_connectionListModel->load();
m_connectionTreeModel = new ConnectionTreeModel(this, m_masterController->userConfigDatabase());
m_connectionTreeModel->load();
m_connectionManagerWindow = new ConnectionManagerWindow(m_masterController, nullptr); m_connectionManagerWindow = new ConnectionManagerWindow(m_masterController, nullptr);
m_connectionManagerWindow->show(); m_connectionManagerWindow->show();

View file

@ -7,6 +7,7 @@ class MasterController;
class ConnectionConfig; class ConnectionConfig;
class ConnectionList; class ConnectionList;
class ConnectionListModel; class ConnectionListModel;
class ConnectionTreeModel;
class ConnectionManagerWindow; class ConnectionManagerWindow;
class PasswordManager; class PasswordManager;
@ -23,6 +24,11 @@ public:
return m_connectionListModel; return m_connectionListModel;
} }
ConnectionTreeModel *getConnectionTreeModel()
{
return m_connectionTreeModel;
}
void showConnectionManager(); void showConnectionManager();
void openSqlWindowForConnection(int connection_index); void openSqlWindowForConnection(int connection_index);
void openServerWindowForConnection(int connection_index); void openServerWindowForConnection(int connection_index);
@ -38,6 +44,7 @@ private:
MasterController *m_masterController; MasterController *m_masterController;
ConnectionList *m_connectionList = nullptr; ConnectionList *m_connectionList = nullptr;
ConnectionListModel *m_connectionListModel = nullptr; ConnectionListModel *m_connectionListModel = nullptr;
ConnectionTreeModel *m_connectionTreeModel = nullptr;
ConnectionManagerWindow *m_connectionManagerWindow = nullptr; ConnectionManagerWindow *m_connectionManagerWindow = nullptr;
/** Using long lived object so it can remember its master password for sometime /** Using long lived object so it can remember its master password for sometime

View file

@ -4,12 +4,89 @@
#include <botan/cryptobox.h> #include <botan/cryptobox.h>
#include <QDir> #include <QDir>
#include <QStandardPaths>
#include <QSettings> #include <QSettings>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlQuery>
#include <QStandardPaths>
namespace { 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<bool, QSqlError> 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<bool, QSqlError> 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<int>(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<int>(cc.passwordState()));
if (!q.exec()) {
auto err = q.lastError();
return { false, err };
}
return {true, {}};
}
/** Saves a connection configuration. /** Saves a connection configuration.
Before calling this you may want to call beginGroup. Before calling this you may want to call beginGroup.
@ -31,6 +108,8 @@ namespace {
settings.setValue("passwordState", static_cast<int>(cc.passwordState())); settings.setValue("passwordState", static_cast<int>(cc.passwordState()));
} }
template <typename S, typename T> template <typename S, typename T>
bool in_range(T value) bool in_range(T value)
{ {
@ -67,10 +146,10 @@ namespace {
} }
} // end of unnamed namespace } // end of unnamed namespace
ConnectionListModel::ConnectionListModel(QObject *parent) ConnectionListModel::ConnectionListModel(QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
{ {
@ -311,3 +390,164 @@ QString ConnectionListModel::iniFileName()
path += "/connections.ini"; path += "/connections.ini";
return path; return path;
} }
ConnectionTreeModel::ConnectionTreeModel(QObject *parent, QSqlDatabase &db)
: QAbstractItemModel(parent)
, m_db(db)
{
}
void ConnectionTreeModel::load()
{
InitConnectionTables(m_db);
auto g1 = std::make_shared<ConnectionGroup>();
g1->name = "Testing";
for (int i = 1; i < 3; ++i) {
auto cc = std::make_shared<ConnectionConfig>();
cc->setUuid(QUuid::createUuid());
cc->setName("testconn " + std::to_string(i));
g1->add(cc);
}
auto g2 = std::make_shared<ConnectionGroup>();
g2->name = "Production";
for (int i = 1; i < 4; ++i) {
auto cc = std::make_shared<ConnectionConfig>();
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<ConnectionNode*>(index.internalPointer());
if (auto group = dynamic_cast<ConnectionGroup*>(privdata); group != nullptr) {
// This is a group
if (role == Qt::DisplayRole) {
if (index.column() == Name) {
v = group->name;
}
}
}
else if (auto conn = dynamic_cast<ConnectionConfig*>(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<ConnectionNode*>(parent.internalPointer());
if (auto group = dynamic_cast<ConnectionGroup*>(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<ConnectionNode*>(node));
// void *p = nullptr;
// if (parent.isValid()) {
// auto cg = static_cast<ConnectionGroup *>(parent.internalPointer());
// auto cc = &cg->connections().at(row);
// p = const_cast<ConnectionConfig*>(cc);
// }
// else {
// p = const_cast<ConnectionGroup*>(&m_groups.at(row));
// }
// return createIndex(row, column, p);
}
QModelIndex ConnectionTreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return {};
auto privdata = static_cast<ConnectionNode*>(index.internalPointer());
if (auto group = dynamic_cast<ConnectionGroup*>(privdata); group != nullptr) {
return {};
}
else if (auto config = dynamic_cast<ConnectionConfig*>(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<ConnectionNode*>(parent.internalPointer());
if (auto group = dynamic_cast<ConnectionGroup*>(privdata); group != nullptr) {
result = group->connections().size();
}
else if (auto config = dynamic_cast<ConnectionConfig*>(privdata); config != nullptr) {
result = 0;
}
}
else {
result = m_groups.size();
}
return result;
}
int ConnectionTreeModel::columnCount(const QModelIndex &parent) const
{
return ColCount;
}

View file

@ -10,6 +10,48 @@
#include "Expected.h" #include "Expected.h"
#include <QVector> #include <QVector>
//#include <QSqlDatabase>
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<std::shared_ptr<ConnectionGroup>>;
QSqlDatabase &m_db;
Groups m_groups;
};
/** \brief Model class for the list of connections. /** \brief Model class for the list of connections.
* *

View file

@ -18,6 +18,7 @@ ConnectionManagerWindow::ConnectionManagerWindow(MasterController *master, QWidg
{ {
ui->setupUi(this); ui->setupUi(this);
ui->listView->setModel(m_connectionController->getConnectionListModel()); ui->listView->setModel(m_connectionController->getConnectionListModel());
ui->treeView->setModel(m_connectionController->getConnectionTreeModel());
} }

View file

@ -30,6 +30,9 @@
</widget> </widget>
</widget> </widget>
</item> </item>
<item>
<widget class="QTreeView" name="treeView"/>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QMenuBar" name="menubar"> <widget class="QMenuBar" name="menubar">

View file

@ -57,6 +57,16 @@ ConnectionConfig::ConnectionConfig()
: m_applicationName(QCoreApplication::applicationName().toUtf8().data()) : 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) void ConnectionConfig::setUuid(const QUuid &uuid)
{ {
@ -347,3 +357,9 @@ void ConnectionConfig::strToEnv(QProcessEnvironment &env, const QString &var, co
else else
env.insert(var, stdStrToQ(val)); env.insert(var, stdStrToQ(val));
} }
void ConnectionGroup::add(std::shared_ptr<ConnectionConfig> cc)
{
cc->setParent(this);
m_connections.push_back(cc);
}

View file

@ -2,6 +2,7 @@
#define CONNECTION_H #define CONNECTION_H
#include <QUuid> #include <QUuid>
#include <QVector>
#include <vector> #include <vector>
#include <string> #include <string>
@ -24,10 +25,41 @@ enum class PasswordState {
class QProcessEnvironment; class QProcessEnvironment;
class QString; 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<std::shared_ptr<ConnectionConfig>>;
const Connections& connections() const { return m_connections; }
void add(std::shared_ptr<ConnectionConfig> 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: public:
ConnectionConfig(); // Default object containing invalid uuid ConnectionConfig(); // Default object containing invalid uuid
ConnectionGroup* parent();
void setParent(ConnectionGroup *grp);
void setUuid(const QUuid &uuid); void setUuid(const QUuid &uuid);
const QUuid &uuid() const; const QUuid &uuid() const;
@ -102,6 +134,7 @@ private:
PasswordState m_passwordState = PasswordState::NotStored; PasswordState m_passwordState = PasswordState::NotStored;
bool m_dirty = false; bool m_dirty = false;
ConnectionGroup* m_group;
static void strToEnv(QProcessEnvironment &env, const QString &var, const std::string &val); static void strToEnv(QProcessEnvironment &env, const QString &var, const std::string &val);