add basic autocompletion for tables and fields to the sql text input

this does only work on full table names, NOT on aliases
for a full auto completion to work we need a sqlite parser
This commit is contained in:
Peinthor Rene
2013-02-14 17:32:54 +01:00
parent 158e35ae65
commit ba34c1e986
5 changed files with 330 additions and 3 deletions

View File

@@ -11,6 +11,7 @@
#include <QStandardItemModel>
#include <QDragEnterEvent>
#include <QScrollBar>
#include "CreateIndexDialog.h"
#include "AboutDialog.h"
#include "EditTableDialog.h"
@@ -21,6 +22,7 @@
#include "EditDialog.h"
#include "FindDialog.h"
#include "SQLiteSyntaxHighlighter.h"
#include "sqltextedit.h"
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent),
@@ -168,6 +170,8 @@ void MainWindow::fileNew()
void MainWindow::populateStructure()
{
ui->dbTreeWidget->model()->removeRows(0, ui->dbTreeWidget->model()->rowCount());
ui->sqlTextEdit->clearFieldCompleterModelMap();
ui->sqlTextEdit->setDefaultCompleterModel(new QStandardItemModel());
if (!db.isOpen()){
return;
}
@@ -177,6 +181,43 @@ void MainWindow::populateStructure()
sqliteHighlighterLogUser->setTableNames(tblnames);
sqliteHighlighterLogApp->setTableNames(tblnames);
// setup models for sqltextedit autocomplete
QStandardItemModel* completerModel = new QStandardItemModel();
completerModel->setRowCount(tblnames.count());
completerModel->setColumnCount(1);
objectMap tab = db.getBrowsableObjects();
int row = 0;
for(objectMap::ConstIterator it=tab.begin(); it!=tab.end(); ++it, ++row)
{
QString sName = it.value().getname();
QStandardItem* item = new QStandardItem(sName);
item->setIcon(QIcon(QString(":icons/%1").arg(it.value().gettype())));
completerModel->setItem(row, 0, item);
// If it is a table add the field Nodes
if((*it).gettype() == "table" || (*it).gettype() == "view")
{
QStandardItemModel* tablefieldmodel = new QStandardItemModel();
tablefieldmodel->setRowCount((*it).fldmap.count());
tablefieldmodel->setColumnCount(1);
fieldMap::ConstIterator fit;
int fldrow = 0;
for ( fit = (*it).fldmap.begin(); fit != (*it).fldmap.end(); ++fit, ++fldrow ) {
QString fieldname = fit.value().getname();
QStandardItem* fldItem = new QStandardItem(fieldname);
fldItem->setIcon(QIcon(":/icons/field"));
tablefieldmodel->setItem(fldrow, 0, fldItem);
}
ui->sqlTextEdit->addFieldCompleterModel(sName.toLower(), tablefieldmodel);
}
}
ui->sqlTextEdit->setDefaultCompleterModel(completerModel);
// end setup models for sqltextedit autocomplete
// fill the structure tab
QMap<QString, QTreeWidgetItem*> typeToParentItem;
QTreeWidgetItem* itemTables = new QTreeWidgetItem(ui->dbTreeWidget);
itemTables->setIcon(0, QIcon(QString(":/icons/table")));

View File

@@ -775,7 +775,7 @@
</widget>
</item>
<item>
<widget class="QTextEdit" name="sqlTextEdit">
<widget class="SqlTextEdit" name="sqlTextEdit">
<property name="font">
<font>
<family>Monospace</family>
@@ -1393,6 +1393,13 @@
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>SqlTextEdit</class>
<extends>QTextEdit</extends>
<header>sqltextedit.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>dbTreeWidget</tabstop>
<tabstop>comboBrowseTable</tabstop>

230
src/sqltextedit.cpp Normal file
View File

@@ -0,0 +1,230 @@
#include "sqltextedit.h"
#include <QKeyEvent>
#include <QAbstractItemView>
#include <QCompleter>
#include <QScrollBar>
//#include <QDebug>
SqlTextEdit::SqlTextEdit(QWidget* parent) :
QTextEdit(parent), m_Completer(0), m_defaultCompleterModel(0)
{
// basic auto completer for sqliteedit
m_Completer = new QCompleter(this);
m_Completer->setCaseSensitivity(Qt::CaseInsensitive);
m_Completer->setCompletionMode(QCompleter::PopupCompletion);
m_Completer->setWrapAround(false);
m_Completer->setWidget(this);
QObject::connect(m_Completer, SIGNAL(activated(QString)),
this, SLOT(insertCompletion(QString)));
}
SqlTextEdit::~SqlTextEdit()
{
clearFieldCompleterModelMap();
delete m_defaultCompleterModel;
}
void SqlTextEdit::setCompleter(QCompleter *completer)
{
if (m_Completer)
QObject::disconnect(m_Completer, 0, this, 0);
m_Completer = completer;
if (!m_Completer)
return;
m_Completer->setWidget(this);
m_Completer->setCompletionMode(QCompleter::PopupCompletion);
m_Completer->setCaseSensitivity(Qt::CaseInsensitive);
QObject::connect(m_Completer, SIGNAL(activated(QString)),
this, SLOT(insertCompletion(QString)));
}
QCompleter* SqlTextEdit::completer() const
{
return m_Completer;
}
void SqlTextEdit::setDefaultCompleterModel(QAbstractItemModel *model)
{
delete m_defaultCompleterModel;
m_defaultCompleterModel = model;
m_Completer->setModel(m_defaultCompleterModel);
}
void SqlTextEdit::clearFieldCompleterModelMap()
{
QAbstractItemModel* model;
foreach (model, m_fieldCompleterMap)
{
delete model;
}
m_fieldCompleterMap.clear();
}
QAbstractItemModel* SqlTextEdit::addFieldCompleterModel(const QString &tablename, QAbstractItemModel* model)
{
m_fieldCompleterMap[tablename] = model;
return model;
}
void SqlTextEdit::insertCompletion(const QString& completion)
{
if (m_Completer->widget() != this)
return;
QTextCursor tc = textCursor();
int extra = completion.length() - m_Completer->completionPrefix().length();
tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
// slight workaround for a field completion without any completionPrefix
// eg. "tablename.;" if you would select a field completion and hit enter
// without this workaround the text would be inserted after the ';'
// because endofword moves to the end of the line
if(tc.selectedText() == ".")
tc.movePosition(QTextCursor::Right);
else
tc.movePosition(QTextCursor::EndOfWord);
tc.insertText(completion.right(extra));
setTextCursor(tc);
}
namespace {
bool isSqliteIdentifierChar(QChar c) {
return c.isLetterOrNumber() || c == '.' || c == '_';
}
}
/**
* @brief SqlTextEdit::identifierUnderCursor
* @return The partial or full sqlite identifier (table(.field)?)? under the cursor
* or a empty string.
*/
QString SqlTextEdit::identifierUnderCursor() const
{
QTextCursor tc = textCursor();
const int abspos = tc.position() - 1;
tc.movePosition(QTextCursor::StartOfLine);
const int linestartpos = tc.position();
const int linepos = abspos - linestartpos;
tc.select(QTextCursor::LineUnderCursor);
QString line = tc.selectedText();
int start = 0, end;
// look where the identifier starts
for( int i = linepos; i >= 0 && i < line.length() && start == 0; --i)
{
if( !(isSqliteIdentifierChar(line.at(i))))
start = i + 1;
}
end = line.length();
// see where the word ends
for( int i = start; i < line.length() && i >= 0 && end == line.length(); ++i)
{
if( !(isSqliteIdentifierChar(line.at(i))))
end = i;
}
// extract the identifier table.field
QString identifier = line.mid(start, end - start);
// check if it has a dot in it
int dotpos = identifier.indexOf('.');
// this is a little hack so editing a table name won't show fields
// fields are only shown if type the word at the end
if( dotpos > -1 && linepos + 1 != end )
return identifier.left(dotpos);
else
return identifier;
}
void SqlTextEdit::focusInEvent(QFocusEvent *e)
{
if (m_Completer)
m_Completer->setWidget(this);
QTextEdit::focusInEvent(e);
}
void SqlTextEdit::keyPressEvent(QKeyEvent *e)
{
if (m_Completer && m_Completer->popup()->isVisible()) {
// The following keys are forwarded by the completer to the widget
switch (e->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
case Qt::Key_Escape:
case Qt::Key_Tab:
case Qt::Key_Backtab:
e->ignore();
return; // let the completer do default behavior
default:
break;
}
}
bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_Space); // CTRL+SPACE
if (!m_Completer || !isShortcut) // do not process the shortcut when we have a completer
QTextEdit::keyPressEvent(e);
const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
const bool cursorKey = e->key() == Qt::Key_Left ||
e->key() == Qt::Key_Up ||
e->key() == Qt::Key_Right ||
e->key() == Qt::Key_Down;
if (!m_Completer || (ctrlOrShift && e->text().isEmpty()) || cursorKey)
return;
QString identifier = identifierUnderCursor();
QString table = identifier;
QString field;
int dotpos = 0;
if((dotpos = identifier.indexOf('.')) > 0)
{
table = identifier.left(dotpos);
field = identifier.mid(dotpos + 1);
}
// qDebug() << identifier << ":" << table << ":" << field;
if( dotpos > 0 )
{
// swap model to field completion
FieldCompleterModelMap::ConstIterator it = m_fieldCompleterMap.find(table.toLower());
if( it != m_fieldCompleterMap.end() )
{
if( *it != m_Completer->model() )
m_Completer->setModel(*it);
if (field != m_Completer->completionPrefix()) {
m_Completer->setCompletionPrefix(field);
m_Completer->popup()->setCurrentIndex(m_Completer->completionModel()->index(0, 0));
}
QRect cr = cursorRect();
cr.setWidth(m_Completer->popup()->sizeHintForColumn(0)
+ m_Completer->popup()->verticalScrollBar()->sizeHint().width());
m_Completer->complete(cr);
}
return;
}
// table completion mode
if( m_Completer->model() != m_defaultCompleterModel )
m_Completer->setModel(m_defaultCompleterModel);
static QString eow("~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="); // end of word
bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
if (!isShortcut && (hasModifier || e->text().isEmpty()|| identifier.length() < 3
|| eow.contains(e->text().right(1)))) {
m_Completer->popup()->hide();
return;
}
if (identifier != m_Completer->completionPrefix()) {
m_Completer->setCompletionPrefix(identifier);
m_Completer->popup()->setCurrentIndex(m_Completer->completionModel()->index(0, 0));
}
QRect cr = cursorRect();
cr.setWidth(m_Completer->popup()->sizeHintForColumn(0)
+ m_Completer->popup()->verticalScrollBar()->sizeHint().width());
m_Completer->complete(cr); // popup it up!
}

47
src/sqltextedit.h Normal file
View File

@@ -0,0 +1,47 @@
#ifndef SQLTEXTEDIT_H
#define SQLTEXTEDIT_H
#include <QTextEdit>
class QCompleter;
class QAbstractItemModel;
/**
* @brief The SqlTextEdit class
* With basic table and fieldname auto completion.
* This class is based on the Qt custom completion example.
*/
class SqlTextEdit : public QTextEdit
{
Q_OBJECT
public:
explicit SqlTextEdit(QWidget *parent = 0);
virtual ~SqlTextEdit();
void setCompleter(QCompleter* completer);
QCompleter* completer() const;
void setDefaultCompleterModel(QAbstractItemModel* model);
// map that associates table -> field model
typedef QMap<QString,QAbstractItemModel*> FieldCompleterModelMap;
void clearFieldCompleterModelMap();
QAbstractItemModel* addFieldCompleterModel(const QString& tablename, QAbstractItemModel *model);
protected:
void keyPressEvent(QKeyEvent *e);
void focusInEvent(QFocusEvent *e);
private:
QString identifierUnderCursor() const;
private slots:
void insertCompletion(const QString& completion);
private:
QCompleter* m_Completer;
QAbstractItemModel* m_defaultCompleterModel;
FieldCompleterModelMap m_fieldCompleterMap;
};
#endif // SQLTEXTEDIT_H

View File

@@ -23,7 +23,8 @@ HEADERS += \
FindDialog.h \
EditDialog.h \
ExportCsvDialog.h \
ImportCsvDialog.h
ImportCsvDialog.h \
sqltextedit.h
SOURCES += \
sqlitedb.cpp \
@@ -39,7 +40,8 @@ SOURCES += \
FindDialog.cpp \
EditDialog.cpp \
ExportCsvDialog.cpp \
ImportCsvDialog.cpp
ImportCsvDialog.cpp \
sqltextedit.cpp
QMAKE_CXXFLAGS += -DAPP_VERSION=\\\"`cd $$PWD;git log -n1 --format=%h_git`\\\"