1 #include "sqlitedb.h"
2 #include "ForeignKeyEditorDelegate.h"
3 
4 #include <QComboBox>
5 #include <QLineEdit>
6 #include <QPushButton>
7 #include <QHBoxLayout>
8 
9 class ForeignKeyEditor : public QWidget
10 {
11     Q_OBJECT
12 
13 public:
ForeignKeyEditor(QWidget * parent=nullptr)14     explicit ForeignKeyEditor(QWidget* parent = nullptr)
15         : QWidget(parent)
16         , tablesComboBox(new QComboBox(this))
17         , idsComboBox(new QComboBox(this))
18         , clauseEdit(new QLineEdit(this))
19         , m_btnReset(new QPushButton(tr("&Reset"), this))
20     {
21         idsComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
22         clauseEdit->setPlaceholderText(tr("Foreign key clauses (ON UPDATE, ON DELETE etc.)"));
23 
24         QHBoxLayout* layout = new QHBoxLayout(this);
25         layout->addWidget(tablesComboBox);
26         layout->addWidget(idsComboBox);
27         layout->addWidget(clauseEdit);
28         layout->addWidget(m_btnReset);
29         layout->setSpacing(0);
30         layout->setMargin(0);
31         setLayout(layout);
32 
33         connect(m_btnReset, &QPushButton::clicked, [&]
34         {
35             tablesComboBox->setCurrentIndex(-1);
36             idsComboBox->setCurrentIndex(-1);
37             clauseEdit->clear();
38         });
39 
40         connect(tablesComboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
41                 [=](int index)
42         {
43             // reset ids combo box
44             idsComboBox->setCurrentIndex(-1);
45 
46             // disable clauses editor if none of tables is selected
47             bool enableClausesEditor = (index!= -1);
48             clauseEdit->setEnabled(enableClausesEditor);
49         });
50     }
51 
getSql() const52     QString getSql() const
53     {
54         if (tablesComboBox->currentText().isEmpty())
55             return QString();
56 
57         const QString table     = sqlb::escapeIdentifier(tablesComboBox->currentText());
58         const QString clauses   = clauseEdit->text();
59 
60         QString id = idsComboBox->currentText();
61         if (!id.isEmpty())
62             id = QString("(%1)").arg(sqlb::escapeIdentifier(id));
63 
64         return QString("%1%2 %3").arg(
65                 table,
66                 id,
67                 clauses)
68                 .trimmed();
69     }
70 
71     QComboBox* tablesComboBox;
72     QComboBox* idsComboBox;
73     QLineEdit* clauseEdit; // for ON CASCADE and such
74 
75 private:
76     QPushButton* m_btnReset;
77 };
78 
ForeignKeyEditorDelegate(const DBBrowserDB & db,sqlb::Table & table,QObject * parent)79 ForeignKeyEditorDelegate::ForeignKeyEditorDelegate(const DBBrowserDB& db, sqlb::Table& table, QObject* parent)
80     : QStyledItemDelegate(parent)
81     , m_db(db)
82     , m_table(table)
83 {
84     for(const auto& it : m_db.schemata)
85     {
86         for(const auto& jt : it.second)
87         {
88             // Don't insert the current table into the list. The name and fields of the current table are always taken from the m_table reference
89             if(jt.second->type() == sqlb::Object::Types::Table && jt.second->name() != m_table.name())
90                 m_tablesIds.insert({jt.second->name(), std::dynamic_pointer_cast<sqlb::Table>(jt.second)->fieldNames()});
91         }
92     }
93 }
94 
createEditor(QWidget * parent,const QStyleOptionViewItem & option,const QModelIndex & index) const95 QWidget* ForeignKeyEditorDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
96 {
97     Q_UNUSED(option)
98     Q_UNUSED(index)
99 
100     ForeignKeyEditor* editor = new ForeignKeyEditor(parent);
101     editor->setAutoFillBackground(true);
102 
103     connect(editor->tablesComboBox, static_cast<void(QComboBox::*)(const QString&)>(&QComboBox::currentIndexChanged),
104             [=](const QString& tableName)
105     {
106         QComboBox* box = editor->idsComboBox;
107         box->clear();
108         box->addItem(QString());                // for those heroes who don't like to specify key explicitly
109 
110         // For recursive foreign keys get the field list from the m_table reference. For other foreign keys from the prepared field lists.
111         if(tableName.toStdString() == m_table.name())
112         {
113             for(const auto& n : m_table.fieldNames())
114                 box->addItem(QString::fromStdString(n));
115         } else {
116             for(const auto& n : m_tablesIds[tableName.toStdString()])
117                 box->addItem(QString::fromStdString(n));
118         }
119 
120 
121         box->setCurrentIndex(0);
122     });
123 
124     editor->tablesComboBox->clear();
125     for(const auto& i : m_tablesIds)
126         editor->tablesComboBox->addItem(QString::fromStdString(i.first));
127     editor->tablesComboBox->addItem(QString::fromStdString(m_table.name()));    // For recursive foreign keys
128 
129     return editor;
130 }
131 
setEditorData(QWidget * editor,const QModelIndex & index) const132 void ForeignKeyEditorDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
133 {
134     ForeignKeyEditor* fkEditor = static_cast<ForeignKeyEditor*>(editor);
135 
136     size_t column = static_cast<size_t>(index.row()); // weird? I know right
137     const sqlb::Field& field = m_table.fields.at(column);
138     auto fk = std::dynamic_pointer_cast<sqlb::ForeignKeyClause>(m_table.constraint({field.name()}, sqlb::Constraint::ForeignKeyConstraintType));
139     if (fk) {
140         fkEditor->tablesComboBox->setCurrentText(QString::fromStdString(fk->table()));
141         fkEditor->clauseEdit->setText(QString::fromStdString(fk->constraint()));
142         if (!fk->columns().empty())
143             fkEditor->idsComboBox->setCurrentText(QString::fromStdString(fk->columns().front()));
144     } else {
145         fkEditor->tablesComboBox->setCurrentIndex(-1);
146     }
147 }
148 
setModelData(QWidget * editor,QAbstractItemModel * model,const QModelIndex & index) const149 void ForeignKeyEditorDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
150 {
151     ForeignKeyEditor* fkEditor = static_cast<ForeignKeyEditor*>(editor);
152     QString sql = fkEditor->getSql();
153 
154     size_t column = static_cast<size_t>(index.row());
155     const sqlb::Field& field = m_table.fields.at(column);
156     if (sql.isEmpty()) {
157         // Remove the foreign key
158         m_table.removeConstraints({field.name()}, sqlb::Constraint::ConstraintTypes::ForeignKeyConstraintType);
159     } else {
160         // Set the foreign key
161         sqlb::ForeignKeyClause* fk = new sqlb::ForeignKeyClause;
162 
163         const QString table     = fkEditor->tablesComboBox->currentText();
164         const std::string id    = fkEditor->idsComboBox->currentText().toStdString();
165         const QString clause    = fkEditor->clauseEdit->text();
166 
167         fk->setTable(table.toStdString());
168         fk->setColumnList({ field.name() });
169 
170         if (!id.empty())
171             fk->setColumns({id});
172 
173         if (!clause.trimmed().isEmpty()) {
174             fk->setConstraint(clause.toStdString());
175         }
176 
177         m_table.setConstraint(sqlb::ConstraintPtr(fk));
178     }
179 
180     model->setData(index, sql);
181 }
182 
updateEditorGeometry(QWidget * editor,const QStyleOptionViewItem & option,const QModelIndex & index) const183 void ForeignKeyEditorDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
184 {
185     Q_UNUSED(index)
186 
187     editor->setGeometry(option.rect);
188 }
189 
190 #include "ForeignKeyEditorDelegate.moc"
191