From 4f7473fbed2e411d18a7562ce585183333169941 Mon Sep 17 00:00:00 2001 From: Vladislav Tronko Date: Fri, 30 Dec 2016 04:35:26 +0200 Subject: [PATCH] Enhanced UI for creating and editing foreign keys --- CMakeLists.txt | 2 + src/EditTableDialog.cpp | 16 ++- src/ForeignKeyEditorDelegate.cpp | 173 +++++++++++++++++++++++++++++++ src/ForeignKeyEditorDelegate.h | 34 ++++++ src/src.pro | 6 +- 5 files changed, 219 insertions(+), 12 deletions(-) create mode 100644 src/ForeignKeyEditorDelegate.cpp create mode 100644 src/ForeignKeyEditorDelegate.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0fa217d4..6d089852 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,6 +104,7 @@ set(SQLB_MOC_HDR src/ColumnDisplayFormatDialog.h src/FilterLineEdit.h src/RemoteDatabase.h + src/ForeignKeyEditorDelegate.h ) set(SQLB_SRC @@ -137,6 +138,7 @@ set(SQLB_SRC src/ColumnDisplayFormatDialog.cpp src/FilterLineEdit.cpp src/RemoteDatabase.cpp + src/ForeignKeyEditorDelegate.cpp ) set(SQLB_FORMS diff --git a/src/EditTableDialog.cpp b/src/EditTableDialog.cpp index 99eb160a..7c4ec5e3 100644 --- a/src/EditTableDialog.cpp +++ b/src/EditTableDialog.cpp @@ -1,5 +1,6 @@ #include "EditTableDialog.h" #include "Settings.h" +#include "ForeignKeyEditorDelegate.h" #include "ui_EditTableDialog.h" #include "sqlitetablemodel.h" #include "sqlitedb.h" @@ -24,6 +25,8 @@ EditTableDialog::EditTableDialog(DBBrowserDB& db, const QString& tableName, bool ui->widgetExtension->setVisible(false); connect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)),this,SLOT(itemChanged(QTreeWidgetItem*,int))); + // Set item delegate for foreign key column + ui->treeWidget->setItemDelegateForColumn(kForeignKey, new ForeignKeyEditorDelegate(db, m_table, this)); // Editing an existing table? if(m_bNewTable == false) { @@ -80,6 +83,7 @@ void EditTableDialog::updateColumnWidth() ui->treeWidget->setColumnWidth(kPrimaryKey, 30); ui->treeWidget->setColumnWidth(kAutoIncrement, 30); ui->treeWidget->setColumnWidth(kUnique, 30); + ui->treeWidget->setColumnWidth(kForeignKey, 500); } void EditTableDialog::populateFields() @@ -446,16 +450,8 @@ void EditTableDialog::itemChanged(QTreeWidgetItem *item, int column) callRenameColumn = true; break; case kForeignKey: - if(item->text(column).trimmed().isEmpty()) - { - // Remove the foreign key - m_table.removeConstraints({field}, sqlb::Constraint::ConstraintTypes::ForeignKeyConstraintType); - } else { - // Set the foreign key - sqlb::ForeignKeyClause* fk = new sqlb::ForeignKeyClause; - fk->setFromString(item->text(column)); - m_table.setConstraint({field}, sqlb::ConstraintPtr(fk)); - } + // handled in delegate + if(!m_bNewTable) callRenameColumn = true; break; diff --git a/src/ForeignKeyEditorDelegate.cpp b/src/ForeignKeyEditorDelegate.cpp new file mode 100644 index 00000000..7a008502 --- /dev/null +++ b/src/ForeignKeyEditorDelegate.cpp @@ -0,0 +1,173 @@ +#include "sqlitedb.h" +#include "ForeignKeyEditorDelegate.h" + +#include +#include +#include +#include + +class ForeignKeyEditor : public QWidget +{ + Q_OBJECT + +public: + ForeignKeyEditor(QWidget* parent = Q_NULLPTR) + : QWidget(parent) + , tablesComboBox(new QComboBox(this)) + , idsComboBox(new QComboBox(this)) + , clauseEdit(new QLineEdit(this)) + , m_btnReset(new QPushButton(tr("&Reset"), this)) + { + clauseEdit->setPlaceholderText(tr("(foreign key clauses(ON UPDATE, ON DELETE etc.)")); + + QHBoxLayout* layout = new QHBoxLayout(this); + layout->addWidget(tablesComboBox); + layout->addWidget(idsComboBox); + layout->addWidget(clauseEdit); + layout->addWidget(m_btnReset); + setLayout(layout); + + connect(m_btnReset, &QPushButton::clicked, [&] + { + tablesComboBox->setCurrentIndex(-1); + idsComboBox->setCurrentIndex(-1); + clauseEdit->clear(); + }); + + connect(tablesComboBox, static_cast(&QComboBox::currentIndexChanged), + [=](int index) + { + // reset ids combo box + idsComboBox->setCurrentIndex(-1); + + // disable clauses editor if none of tables is selected + bool enableClausesEditor = (index!= -1); + clauseEdit->setEnabled(enableClausesEditor); + }); + } + + QString getSql() const + { + if (tablesComboBox->currentText().isEmpty()) + return QString(); + + const QString table = sqlb::escapeIdentifier(tablesComboBox->currentText()); + const QString clauses = clauseEdit->text(); + + QString id = idsComboBox->currentText(); + if (!id.isEmpty()) + id = QString("(%1)").arg(sqlb::escapeIdentifier(id)); + + return QString("%1%2 %3") + .arg(table) + .arg(id) + .arg(clauses) + .trimmed(); + } + + QComboBox* tablesComboBox; + QComboBox* idsComboBox; + QLineEdit* clauseEdit; // for ON CASCADE and such + +private: + QPushButton* m_btnReset; +}; + +ForeignKeyEditorDelegate::ForeignKeyEditorDelegate(const DBBrowserDB& db, sqlb::Table& table, QObject* parent) + : QStyledItemDelegate(parent) + , m_db(db) + , m_table(table) +{ + +} + +QWidget* ForeignKeyEditorDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + Q_UNUSED(option) + Q_UNUSED(index) + + ForeignKeyEditor* editor = new ForeignKeyEditor(parent); + editor->setAutoFillBackground(true); + + connect(editor->tablesComboBox, static_cast(&QComboBox::currentIndexChanged), + [=](const QString& tableName) + { + QComboBox* box = editor->idsComboBox; + box->clear(); + box->addItem(QString()); // for those heroes who don't like to specify key explicitly + box->addItems(m_tablesIds[tableName]); + box->setCurrentIndex(0); + }); + + return editor; +} + +void ForeignKeyEditorDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const +{ + ForeignKeyEditor* fkEditor = static_cast(editor); + + m_tablesIds.clear(); + const auto objects = m_db.getBrowsableObjects(); + for (auto obj : objects) { + if ("table" == obj.gettype()) { + QString tableName = obj.table.name(); + m_tablesIds.insert(tableName, obj.table.fieldNames()); + } + } + + fkEditor->tablesComboBox->addItems(m_tablesIds.keys()); + + int column = index.row(); // weird? I know right + sqlb::FieldPtr field = m_table.fields().at(column); + QSharedPointer fk = m_table.constraint({field}, sqlb::Constraint::ForeignKeyConstraintType).dynamicCast(); + if (!fk.isNull()) { + fkEditor->tablesComboBox->setCurrentText(fk->table()); + fkEditor->clauseEdit->setText(fk->constraint()); + if (!fk->columns().isEmpty()) + fkEditor->idsComboBox->setCurrentText(fk->columns().first()); + } else { + fkEditor->tablesComboBox->setCurrentIndex(-1); + } +} + +void ForeignKeyEditorDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const +{ + ForeignKeyEditor* fkEditor = static_cast(editor); + QString sql = fkEditor->getSql(); + + int column = index.row(); + sqlb::FieldPtr field = m_table.fields().at(column); + if (sql.isEmpty()) { + // Remove the foreign key + m_table.removeConstraints({field}, sqlb::Constraint::ConstraintTypes::ForeignKeyConstraintType); + } else { + // Set the foreign key + sqlb::ForeignKeyClause* fk = new sqlb::ForeignKeyClause; + + const QString table = fkEditor->tablesComboBox->currentText(); + const QString id = fkEditor->idsComboBox->currentText(); + const QString clause = fkEditor->clauseEdit->text(); + + fk->setTable(table); + + if (!id.isEmpty()) + fk->setColumns({id}); + + if (!clause.trimmed().isEmpty()) { + fk->setConstraint(clause); + } + + m_table.setConstraint({field}, sqlb::ConstraintPtr(fk)); + } + + model->setData(index, sql); +} + +void ForeignKeyEditorDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + Q_UNUSED(index) + + editor->setGeometry(option.rect); +} + +#include "ForeignKeyEditorDelegate.moc" diff --git a/src/ForeignKeyEditorDelegate.h b/src/ForeignKeyEditorDelegate.h new file mode 100644 index 00000000..43b013ae --- /dev/null +++ b/src/ForeignKeyEditorDelegate.h @@ -0,0 +1,34 @@ +#ifndef FOREIGNKEYDELEGATE_H +#define FOREIGNKEYDELEGATE_H + +#include + +class DBBrowserDB; +class QPushButton; +class QComboBox; +class QLineEdit; + +namespace sqlb +{ +class Table; +} + +class ForeignKeyEditorDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + explicit ForeignKeyEditorDelegate(const DBBrowserDB& db, sqlb::Table& table, QObject* parent = Q_NULLPTR); + + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const Q_DECL_OVERRIDE; + void setEditorData(QWidget* editor, const QModelIndex& index) const Q_DECL_OVERRIDE; + void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const Q_DECL_OVERRIDE; + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; + +private: + const DBBrowserDB& m_db; + sqlb::Table& m_table; + mutable QMap m_tablesIds; +}; + +#endif // FOREIGNKEYDELEGATE_H diff --git a/src/src.pro b/src/src.pro index f17fe874..61c6dc5e 100644 --- a/src/src.pro +++ b/src/src.pro @@ -52,7 +52,8 @@ HEADERS += \ FileDialog.h \ ColumnDisplayFormatDialog.h \ FilterLineEdit.h \ - RemoteDatabase.h + RemoteDatabase.h \ + ForeignKeyEditorDelegate.h SOURCES += \ sqlitedb.cpp \ @@ -83,7 +84,8 @@ SOURCES += \ FileDialog.cpp \ ColumnDisplayFormatDialog.cpp \ FilterLineEdit.cpp \ - RemoteDatabase.cpp + RemoteDatabase.cpp \ + ForeignKeyEditorDelegate.cpp RESOURCES += icons/icons.qrc \ translations/flags/flags.qrc \