Skip to content

Add database diagrams #1962

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ set(SQLB_MOC_HDR
src/RunSql.h
src/ProxyDialog.h
src/SelectItemsPopup.h
src/DiagramObjects.h
src/DiagramScene.h
src/DiagramTablesListModel.h
)

set(SQLB_SRC
Expand Down Expand Up @@ -232,6 +235,9 @@ set(SQLB_SRC
src/ProxyDialog.cpp
src/IconCache.cpp
src/SelectItemsPopup.cpp
src/DiagramObjects.cpp
src/DiagramScene.cpp
src/DiagramTablesListModel.cpp
)

set(SQLB_FORMS
Expand Down
127 changes: 127 additions & 0 deletions src/DiagramObjects.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#include "DiagramObjects.h"
#include "IconCache.h"

#include <QLabel>
#include <QListView>
#include <QLineF>
#include <QGraphicsLineItem>
#include <QVBoxLayout>

// TableModel

TableModel::TableModel(sqlb::Table& table, QObject *parent)
: QAbstractListModel(parent)
, m_table(table)
{
update();
}

int TableModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return static_cast<int>(m_fields.size());
}

QVariant TableModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();

const std::string& fieldName = m_fields.at(static_cast<size_t>(index.row()));

switch (role) {
case Qt::DisplayRole:
return QString::fromStdString(fieldName);

case Qt::DecorationRole:
if (std::find(m_primaryKeys.begin(), m_primaryKeys.end(), fieldName) != m_primaryKeys.end())
return IconCache::get("field_key");
else if (m_table.constraint({fieldName}, sqlb::Constraint::ForeignKeyConstraintType) != nullptr)
return IconCache::get("field_fk");
else
return IconCache::get("field");

default:
return QVariant();
}
}

Qt::ItemFlags TableModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;

return Qt::ItemIsEnabled;
}

void TableModel::update()
{
if (m_table.name() != m_tableName) {
m_tableName = m_table.name();
emit tableNameChanged(QString::fromStdString(m_tableName));
}

m_fields = m_table.fieldNames();
m_primaryKeys = m_table.primaryKey();

removeRows(0, rowCount());
insertRows(0, static_cast<int>(m_fields.size()));

emit dataChanged(index(0, 0), index(0, static_cast<int>(m_fields.size())));
}

std::string TableModel::tableName() const
{
return m_tableName;
}

// TableWidget

TableWidget::TableWidget(TableModel* model, QWidget *parent)
: QWidget(parent)
, m_tableModel(model)
{
m_tableModel->setParent(this);

m_listView = new QListView(this);
m_listView->setModel(m_tableModel);
m_label = new QLabel(QString::fromStdString(m_tableModel->tableName()), this);

QVBoxLayout* layout = new QVBoxLayout;
layout->addWidget(m_label);
layout->addWidget(m_listView);
setLayout(layout);

connect(m_tableModel, &TableModel::tableNameChanged, m_label, &QLabel::setText);
}

TableModel *TableWidget::tableModel() const
{
return m_tableModel;
}

QSize TableWidget::sizeHint() const
{
return QSize(m_label->sizeHint().width(), QWidget::sizeHint().height());
}

// Relation

Relation::Relation(TableProxy* parentTable, TableProxy* childTable)
: QObject(parentTable)
, m_parentTable(parentTable)
, m_childTable(childTable)
, m_lineItem(nullptr)
{
}

Relation::~Relation()
{
}

QString Relation::tooltipText() const
{
// ToDo: show informative tooltips, e.g:
// foo(bar_id) references bar(id)
return QString();
}
111 changes: 111 additions & 0 deletions src/DiagramObjects.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#ifndef DIAGRAMOBJECTS_H
#define DIAGRAMOBJECTS_H

#include "sql/sqlitetypes.h"

#include <QAbstractListModel>
#include <QGraphicsProxyWidget>
#include <QMap>
#include <QWidget>
#include <QLineF>

#include <string>
#include <vector>

class QLabel;
class QListView;
class QLineF;
class QGraphicsLineItem;

class TableModel : public QAbstractListModel
{
Q_OBJECT

public:
explicit TableModel(sqlb::Table& table, QObject* parent = nullptr);

int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role) const override;
Qt::ItemFlags flags(const QModelIndex& index) const override;

void update();
std::string tableName() const;

signals:
void tableNameChanged(QString);

private:
std::vector<std::string> m_primaryKeys;
std::vector<std::string> m_fields;
std::string m_tableName;

sqlb::Table& m_table;
};

class TableWidget : public QWidget
{
Q_OBJECT

public:
explicit TableWidget(TableModel* model, QWidget* parent = nullptr);
TableModel *tableModel() const;
QSize sizeHint() const override;

private:
TableModel* m_tableModel;
QListView* m_listView;
QLabel* m_label;
};

class TableProxy : public QGraphicsProxyWidget
{
Q_OBJECT

#define OVERRIDE_MOUSE_EVENT(event_type) \
void event_type(QGraphicsSceneMouseEvent* event) override \
{ return QGraphicsItem::event_type(event); }

public:
TableProxy(QGraphicsItem* parent = nullptr)
: QGraphicsProxyWidget(parent)
{
setFlags(flags() | ItemIsMovable | ItemIsSelectable);
}

TableWidget* tableWidget() const
{
return static_cast<TableWidget*>(widget());
}

~TableProxy() override {}

OVERRIDE_MOUSE_EVENT(mouseDoubleClickEvent)
OVERRIDE_MOUSE_EVENT(mouseMoveEvent)
OVERRIDE_MOUSE_EVENT(mousePressEvent)
OVERRIDE_MOUSE_EVENT(mouseReleaseEvent)
};

class Relation : public QObject
{
Q_OBJECT

public:
Relation(TableProxy* parentTable, TableProxy* childTable);
~Relation();

TableProxy* parentTable() const { return m_parentTable; };
TableProxy* childTable() const { return m_childTable; };

void setLineItem(QGraphicsLineItem* item) { m_lineItem = item; };
QGraphicsLineItem* lineItem() const { return m_lineItem; };
QLineF line() const { return QLineF(m_parentTable->scenePos(),
m_childTable->scenePos()); };

QString tooltipText() const;

private:
TableProxy *m_parentTable, *m_childTable;
QGraphicsLineItem* m_lineItem;
};

#endif
105 changes: 105 additions & 0 deletions src/DiagramScene.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#include "sqlitedb.h"
#include "DiagramScene.h"
#include "DiagramObjects.h"

#include <algorithm>

#include <QGraphicsProxyWidget>
#include <QGraphicsSceneMouseEvent>


DiagramScene::DiagramScene(const DBBrowserDB& db, QWidget *parent)
: QGraphicsScene(parent)
, m_db(db)
{

}

void DiagramScene::updateTables()
{
for (TableProxy* proxy : m_tables) {
proxy->tableWidget()->tableModel()->update();
}
}

void DiagramScene::removeTable(const std::string& tableName)
{
std::remove_if(m_tables.begin(), m_tables.end(), [&](TableProxy* proxy)
{
return (proxy->tableWidget()->tableModel()->tableName() == tableName);
});
}

void DiagramScene::addTable(const std::string& tableName)
{
sqlb::TablePtr obj = m_db.getObjectByName<sqlb::Table>(sqlb::ObjectIdentifier("main", tableName));
TableModel* model = new TableModel(*obj);
TableWidget* widget = new TableWidget(model);

TableProxy* proxy = new TableProxy;
proxy->setWidget(widget);
addItem(proxy);
m_tables.push_back(proxy);


// Add relations to parent tables
for (std::string field : obj->fieldNames()) {

auto fk = std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(obj->constraint({field}, sqlb::Constraint::ForeignKeyConstraintType));

if (fk != nullptr) {
for (TableProxy* otherProxy : m_tables) {
if (otherProxy->tableWidget()->tableModel()->tableName() == fk->table()) {
Relation* relation = new Relation(otherProxy, proxy);
addRelation(relation);
}
}
}
}

// Add relations to child tables
for (TableProxy* otherProxy : m_tables) {
sqlb::TablePtr otherTable =
m_db.getObjectByName<sqlb::Table>(sqlb::ObjectIdentifier("main", otherProxy->tableWidget()->tableModel()->tableName()));

for (std::string field : otherTable->fieldNames()) {

auto fk = std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(otherTable->constraint({field}, sqlb::Constraint::ForeignKeyConstraintType));

if (fk != nullptr) {
if (tableName == fk->table()) {
Relation* relation = new Relation(proxy, otherProxy);
addRelation(relation);
}
}
}
}
}

void DiagramScene::addRelation(Relation* rel)
{
m_relations.push_back(rel);
rel->setLineItem(addLine(rel->line()));
}

void DiagramScene::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
QGraphicsScene::mouseMoveEvent(event);

// Move relation lines
for (Relation* rel : m_relations) {
for (TableProxy* proxy : m_tables) {
QLineF line = rel->lineItem()->line();
// Move start point, if from-table has moved.
if (proxy == rel->parentTable() && proxy->scenePos() != line.p1()) {
line.setP1(proxy->scenePos());
}
// Move end point, if to-table has moved.
if (proxy == rel->childTable() && proxy->scenePos() != line.p2()) {
line.setP2(proxy->scenePos());
}
rel->lineItem()->setLine(line);
}
}
}

Loading