Foreign key combobox in table browser #614 (#1550)

Foreign key combo-box in table browser

This adds a combo-box for the editor delegate when the associated column
has a foreign key constraint. In this way the user can choose the key value
based from a combo-box.

To minimise possible performance problems, the model only loads the foreign
key field, instead of the whole related table.

Except for not-null columns, a NULL value is given as possible value in
the foreign-key combo-box.

See issue #614
This commit is contained in:
Manuel
2018-12-10 21:39:16 +01:00
committed by GitHub
parent 7e549c7c4c
commit 87e9732fcd
2 changed files with 80 additions and 26 deletions

View File

@@ -3,6 +3,7 @@
#include "FilterTableHeader.h"
#include "sql/sqlitetypes.h"
#include "Settings.h"
#include "sqlitedb.h"
#include <QApplication>
#include <QClipboard>
@@ -20,6 +21,7 @@
#include <QPrintPreviewDialog>
#include <QTextDocument>
#include <QCompleter>
#include <QComboBox>
#include <limits>
@@ -126,44 +128,93 @@ ExtendedTableWidgetEditorDelegate::ExtendedTableWidgetEditorDelegate(QObject* pa
QWidget* ExtendedTableWidgetEditorDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& /*option*/, const QModelIndex& index) const
{
QLineEdit* editor = new QLineEdit(parent);
// If the row count is not greater than the complete threshold setting, set a completer of values based on current values in the column.
if (index.model()->rowCount() <= Settings::getValue("databrowser", "complete_threshold").toInt()) {
QCompleter* completer = new QCompleter(editor);
UniqueFilterModel* completerFilter = new UniqueFilterModel(completer);
// Provide a filter for the source model, so only unique and non-empty values are accepted.
completerFilter->setSourceModel(const_cast<QAbstractItemModel*>(index.model()));
completerFilter->setFilterKeyColumn(index.column());
completer->setModel(completerFilter);
// Complete on this column, using a popup and case-insensitively.
completer->setCompletionColumn(index.column());
completer->setCompletionMode(QCompleter::PopupCompletion);
completer->setCaseSensitivity(Qt::CaseInsensitive);
editor->setCompleter(completer);
SqliteTableModel* m = qobject_cast<SqliteTableModel*>(const_cast<QAbstractItemModel*>(index.model()));
sqlb::ForeignKeyClause fk = m->getForeignKeyClause(index.column()-1);
if(fk.isSet()) {
sqlb::ObjectIdentifier foreignTable = sqlb::ObjectIdentifier(m->currentTableName().schema(), fk.table());
QString column;
// If no column name is set, assume the primary key is meant
if(fk.columns().isEmpty()) {
sqlb::TablePtr obj = m->db().getObjectByName<sqlb::Table>(foreignTable);
column = obj->findPk()->name();
} else
column = fk.columns().at(0);
sqlb::TablePtr currentTable = m->db().getObjectByName<sqlb::Table>(m->currentTableName());
QString query = QString("SELECT %1 FROM %2").arg(sqlb::escapeIdentifier(column)).arg(foreignTable.toString());
// if the current column of the current table does NOT have not-null constraint,
// the NULL is united to the query to get the possible values in the combo-box.
if (!currentTable->fields.at(index.column()-1).notnull())
query.append (" UNION SELECT NULL");
SqliteTableModel* fkModel = new SqliteTableModel(m->db(), parent, m->chunkSize(), m->encoding());
fkModel->setQuery(query);
QComboBox* combo = new QComboBox(parent);
// Complete cache so it is ready when setEditorData is invoked.
fkModel->completeCache();
combo->setModel(fkModel);
return combo;
} else {
QLineEdit* editor = new QLineEdit(parent);
// If the row count is not greater than the complete threshold setting, set a completer of values based on current values in the column.
if (index.model()->rowCount() <= Settings::getValue("databrowser", "complete_threshold").toInt()) {
QCompleter* completer = new QCompleter(editor);
UniqueFilterModel* completerFilter = new UniqueFilterModel(completer);
// Provide a filter for the source model, so only unique and non-empty values are accepted.
completerFilter->setSourceModel(const_cast<QAbstractItemModel*>(index.model()));
completerFilter->setFilterKeyColumn(index.column());
completer->setModel(completerFilter);
// Complete on this column, using a popup and case-insensitively.
completer->setCompletionColumn(index.column());
completer->setCompletionMode(QCompleter::PopupCompletion);
completer->setCaseSensitivity(Qt::CaseInsensitive);
editor->setCompleter(completer);
}
// Set the maximum length to the highest possible value instead of the default 32768.
editor->setMaxLength(std::numeric_limits<int>::max());
return editor;
}
// Set the maximum length to the highest possible value instead of the default 32768.
editor->setMaxLength(std::numeric_limits<int>::max());
return editor;
}
void ExtendedTableWidgetEditorDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
QLineEdit* lineedit = static_cast<QLineEdit*>(editor);
// Set the data for the line editor
QLineEdit* lineedit = dynamic_cast<QLineEdit*>(editor);
// Set the data for the editor
QString data = index.data(Qt::EditRole).toString();
lineedit->setText(data);
// Put the editor in read only mode if the actual data is larger than the maximum length to avoid accidental truncation of the data
lineedit->setReadOnly(data.size() > lineedit->maxLength());
if(!lineedit) {
QComboBox* combo = static_cast<QComboBox*>(editor);
int comboIndex = combo->findText(data);
if (comboIndex >= 0)
// if it is valid, adjust the combobox
combo->setCurrentIndex(comboIndex);
} else {
lineedit->setText(data);
// Put the editor in read only mode if the actual data is larger than the maximum length to avoid accidental truncation of the data
lineedit->setReadOnly(data.size() > lineedit->maxLength());
}
}
void ExtendedTableWidgetEditorDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
{
// Only apply the data back to the model if the editor is not in read only mode to avoid accidental truncation of the data
QLineEdit* lineedit = static_cast<QLineEdit*>(editor);
if(!lineedit->isReadOnly())
model->setData(index, lineedit->text());
QLineEdit* lineedit = dynamic_cast<QLineEdit*>(editor);
if(!lineedit) {
QComboBox* combo = static_cast<QComboBox*>(editor);
model->setData(index, combo->currentData(Qt::EditRole), Qt::EditRole);
} else
if(!lineedit->isReadOnly())
model->setData(index, lineedit->text());
}
void ExtendedTableWidgetEditorDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& /*index*/) const

View File

@@ -86,6 +86,7 @@ public:
void setQuery(const sqlb::Query& query);
void setChunkSize(size_t chunksize);
size_t chunkSize() { return m_chunkSize; };
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
sqlb::ObjectIdentifier currentTableName() const { return m_query.table(); }
@@ -114,6 +115,8 @@ public:
void addCondFormat(int column, const CondFormat& condFormat);
void setCondFormats(int column, const QVector<CondFormat>& condFormats);
DBBrowserDB& db() { return m_db; };
public slots:
void updateFilter(int column, const QString& value);