Skip to content

Commit ba34c1e

Browse files
committed
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
1 parent 158e35a commit ba34c1e

File tree

5 files changed

+330
-3
lines changed

5 files changed

+330
-3
lines changed

src/MainWindow.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <QStandardItemModel>
1212
#include <QDragEnterEvent>
1313
#include <QScrollBar>
14+
1415
#include "CreateIndexDialog.h"
1516
#include "AboutDialog.h"
1617
#include "EditTableDialog.h"
@@ -21,6 +22,7 @@
2122
#include "EditDialog.h"
2223
#include "FindDialog.h"
2324
#include "SQLiteSyntaxHighlighter.h"
25+
#include "sqltextedit.h"
2426

2527
MainWindow::MainWindow(QWidget* parent)
2628
: QMainWindow(parent),
@@ -168,6 +170,8 @@ void MainWindow::fileNew()
168170
void MainWindow::populateStructure()
169171
{
170172
ui->dbTreeWidget->model()->removeRows(0, ui->dbTreeWidget->model()->rowCount());
173+
ui->sqlTextEdit->clearFieldCompleterModelMap();
174+
ui->sqlTextEdit->setDefaultCompleterModel(new QStandardItemModel());
171175
if (!db.isOpen()){
172176
return;
173177
}
@@ -177,6 +181,43 @@ void MainWindow::populateStructure()
177181
sqliteHighlighterLogUser->setTableNames(tblnames);
178182
sqliteHighlighterLogApp->setTableNames(tblnames);
179183

184+
// setup models for sqltextedit autocomplete
185+
QStandardItemModel* completerModel = new QStandardItemModel();
186+
completerModel->setRowCount(tblnames.count());
187+
completerModel->setColumnCount(1);
188+
189+
objectMap tab = db.getBrowsableObjects();
190+
int row = 0;
191+
for(objectMap::ConstIterator it=tab.begin(); it!=tab.end(); ++it, ++row)
192+
{
193+
QString sName = it.value().getname();
194+
QStandardItem* item = new QStandardItem(sName);
195+
item->setIcon(QIcon(QString(":icons/%1").arg(it.value().gettype())));
196+
completerModel->setItem(row, 0, item);
197+
198+
// If it is a table add the field Nodes
199+
if((*it).gettype() == "table" || (*it).gettype() == "view")
200+
{
201+
QStandardItemModel* tablefieldmodel = new QStandardItemModel();
202+
tablefieldmodel->setRowCount((*it).fldmap.count());
203+
tablefieldmodel->setColumnCount(1);
204+
205+
fieldMap::ConstIterator fit;
206+
int fldrow = 0;
207+
for ( fit = (*it).fldmap.begin(); fit != (*it).fldmap.end(); ++fit, ++fldrow ) {
208+
QString fieldname = fit.value().getname();
209+
QStandardItem* fldItem = new QStandardItem(fieldname);
210+
fldItem->setIcon(QIcon(":/icons/field"));
211+
tablefieldmodel->setItem(fldrow, 0, fldItem);
212+
}
213+
ui->sqlTextEdit->addFieldCompleterModel(sName.toLower(), tablefieldmodel);
214+
}
215+
216+
}
217+
ui->sqlTextEdit->setDefaultCompleterModel(completerModel);
218+
// end setup models for sqltextedit autocomplete
219+
220+
// fill the structure tab
180221
QMap<QString, QTreeWidgetItem*> typeToParentItem;
181222
QTreeWidgetItem* itemTables = new QTreeWidgetItem(ui->dbTreeWidget);
182223
itemTables->setIcon(0, QIcon(QString(":/icons/table")));

src/MainWindow.ui

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@
775775
</widget>
776776
</item>
777777
<item>
778-
<widget class="QTextEdit" name="sqlTextEdit">
778+
<widget class="SqlTextEdit" name="sqlTextEdit">
779779
<property name="font">
780780
<font>
781781
<family>Monospace</family>
@@ -1393,6 +1393,13 @@
13931393
</property>
13941394
</action>
13951395
</widget>
1396+
<customwidgets>
1397+
<customwidget>
1398+
<class>SqlTextEdit</class>
1399+
<extends>QTextEdit</extends>
1400+
<header>sqltextedit.h</header>
1401+
</customwidget>
1402+
</customwidgets>
13961403
<tabstops>
13971404
<tabstop>dbTreeWidget</tabstop>
13981405
<tabstop>comboBrowseTable</tabstop>

src/sqltextedit.cpp

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
#include "sqltextedit.h"
2+
3+
#include <QKeyEvent>
4+
#include <QAbstractItemView>
5+
#include <QCompleter>
6+
#include <QScrollBar>
7+
//#include <QDebug>
8+
9+
SqlTextEdit::SqlTextEdit(QWidget* parent) :
10+
QTextEdit(parent), m_Completer(0), m_defaultCompleterModel(0)
11+
{
12+
// basic auto completer for sqliteedit
13+
m_Completer = new QCompleter(this);
14+
m_Completer->setCaseSensitivity(Qt::CaseInsensitive);
15+
m_Completer->setCompletionMode(QCompleter::PopupCompletion);
16+
m_Completer->setWrapAround(false);
17+
m_Completer->setWidget(this);
18+
19+
QObject::connect(m_Completer, SIGNAL(activated(QString)),
20+
this, SLOT(insertCompletion(QString)));
21+
}
22+
23+
SqlTextEdit::~SqlTextEdit()
24+
{
25+
clearFieldCompleterModelMap();
26+
delete m_defaultCompleterModel;
27+
}
28+
29+
void SqlTextEdit::setCompleter(QCompleter *completer)
30+
{
31+
if (m_Completer)
32+
QObject::disconnect(m_Completer, 0, this, 0);
33+
34+
m_Completer = completer;
35+
36+
if (!m_Completer)
37+
return;
38+
39+
m_Completer->setWidget(this);
40+
m_Completer->setCompletionMode(QCompleter::PopupCompletion);
41+
m_Completer->setCaseSensitivity(Qt::CaseInsensitive);
42+
QObject::connect(m_Completer, SIGNAL(activated(QString)),
43+
this, SLOT(insertCompletion(QString)));
44+
}
45+
46+
QCompleter* SqlTextEdit::completer() const
47+
{
48+
return m_Completer;
49+
}
50+
51+
void SqlTextEdit::setDefaultCompleterModel(QAbstractItemModel *model)
52+
{
53+
delete m_defaultCompleterModel;
54+
m_defaultCompleterModel = model;
55+
m_Completer->setModel(m_defaultCompleterModel);
56+
}
57+
58+
void SqlTextEdit::clearFieldCompleterModelMap()
59+
{
60+
QAbstractItemModel* model;
61+
foreach (model, m_fieldCompleterMap)
62+
{
63+
delete model;
64+
}
65+
m_fieldCompleterMap.clear();
66+
}
67+
68+
QAbstractItemModel* SqlTextEdit::addFieldCompleterModel(const QString &tablename, QAbstractItemModel* model)
69+
{
70+
m_fieldCompleterMap[tablename] = model;
71+
return model;
72+
}
73+
74+
void SqlTextEdit::insertCompletion(const QString& completion)
75+
{
76+
if (m_Completer->widget() != this)
77+
return;
78+
QTextCursor tc = textCursor();
79+
int extra = completion.length() - m_Completer->completionPrefix().length();
80+
tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
81+
82+
// slight workaround for a field completion without any completionPrefix
83+
// eg. "tablename.;" if you would select a field completion and hit enter
84+
// without this workaround the text would be inserted after the ';'
85+
// because endofword moves to the end of the line
86+
if(tc.selectedText() == ".")
87+
tc.movePosition(QTextCursor::Right);
88+
else
89+
tc.movePosition(QTextCursor::EndOfWord);
90+
91+
tc.insertText(completion.right(extra));
92+
setTextCursor(tc);
93+
}
94+
95+
namespace {
96+
bool isSqliteIdentifierChar(QChar c) {
97+
return c.isLetterOrNumber() || c == '.' || c == '_';
98+
}
99+
}
100+
101+
/**
102+
* @brief SqlTextEdit::identifierUnderCursor
103+
* @return The partial or full sqlite identifier (table(.field)?)? under the cursor
104+
* or a empty string.
105+
*/
106+
QString SqlTextEdit::identifierUnderCursor() const
107+
{
108+
QTextCursor tc = textCursor();
109+
const int abspos = tc.position() - 1;
110+
tc.movePosition(QTextCursor::StartOfLine);
111+
const int linestartpos = tc.position();
112+
const int linepos = abspos - linestartpos;
113+
tc.select(QTextCursor::LineUnderCursor);
114+
QString line = tc.selectedText();
115+
int start = 0, end;
116+
117+
// look where the identifier starts
118+
for( int i = linepos; i >= 0 && i < line.length() && start == 0; --i)
119+
{
120+
if( !(isSqliteIdentifierChar(line.at(i))))
121+
start = i + 1;
122+
}
123+
124+
end = line.length();
125+
// see where the word ends
126+
for( int i = start; i < line.length() && i >= 0 && end == line.length(); ++i)
127+
{
128+
if( !(isSqliteIdentifierChar(line.at(i))))
129+
end = i;
130+
}
131+
132+
// extract the identifier table.field
133+
QString identifier = line.mid(start, end - start);
134+
// check if it has a dot in it
135+
int dotpos = identifier.indexOf('.');
136+
137+
// this is a little hack so editing a table name won't show fields
138+
// fields are only shown if type the word at the end
139+
if( dotpos > -1 && linepos + 1 != end )
140+
return identifier.left(dotpos);
141+
else
142+
return identifier;
143+
}
144+
145+
void SqlTextEdit::focusInEvent(QFocusEvent *e)
146+
{
147+
if (m_Completer)
148+
m_Completer->setWidget(this);
149+
QTextEdit::focusInEvent(e);
150+
}
151+
152+
void SqlTextEdit::keyPressEvent(QKeyEvent *e)
153+
{
154+
if (m_Completer && m_Completer->popup()->isVisible()) {
155+
// The following keys are forwarded by the completer to the widget
156+
switch (e->key()) {
157+
case Qt::Key_Enter:
158+
case Qt::Key_Return:
159+
case Qt::Key_Escape:
160+
case Qt::Key_Tab:
161+
case Qt::Key_Backtab:
162+
e->ignore();
163+
return; // let the completer do default behavior
164+
default:
165+
break;
166+
}
167+
}
168+
169+
bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_Space); // CTRL+SPACE
170+
if (!m_Completer || !isShortcut) // do not process the shortcut when we have a completer
171+
QTextEdit::keyPressEvent(e);
172+
const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
173+
const bool cursorKey = e->key() == Qt::Key_Left ||
174+
e->key() == Qt::Key_Up ||
175+
e->key() == Qt::Key_Right ||
176+
e->key() == Qt::Key_Down;
177+
if (!m_Completer || (ctrlOrShift && e->text().isEmpty()) || cursorKey)
178+
return;
179+
180+
QString identifier = identifierUnderCursor();
181+
QString table = identifier;
182+
QString field;
183+
int dotpos = 0;
184+
if((dotpos = identifier.indexOf('.')) > 0)
185+
{
186+
table = identifier.left(dotpos);
187+
field = identifier.mid(dotpos + 1);
188+
}
189+
// qDebug() << identifier << ":" << table << ":" << field;
190+
if( dotpos > 0 )
191+
{
192+
// swap model to field completion
193+
FieldCompleterModelMap::ConstIterator it = m_fieldCompleterMap.find(table.toLower());
194+
if( it != m_fieldCompleterMap.end() )
195+
{
196+
if( *it != m_Completer->model() )
197+
m_Completer->setModel(*it);
198+
if (field != m_Completer->completionPrefix()) {
199+
m_Completer->setCompletionPrefix(field);
200+
m_Completer->popup()->setCurrentIndex(m_Completer->completionModel()->index(0, 0));
201+
}
202+
QRect cr = cursorRect();
203+
cr.setWidth(m_Completer->popup()->sizeHintForColumn(0)
204+
+ m_Completer->popup()->verticalScrollBar()->sizeHint().width());
205+
m_Completer->complete(cr);
206+
}
207+
return;
208+
}
209+
210+
// table completion mode
211+
if( m_Completer->model() != m_defaultCompleterModel )
212+
m_Completer->setModel(m_defaultCompleterModel);
213+
static QString eow("~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="); // end of word
214+
bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
215+
216+
if (!isShortcut && (hasModifier || e->text().isEmpty()|| identifier.length() < 3
217+
|| eow.contains(e->text().right(1)))) {
218+
m_Completer->popup()->hide();
219+
return;
220+
}
221+
222+
if (identifier != m_Completer->completionPrefix()) {
223+
m_Completer->setCompletionPrefix(identifier);
224+
m_Completer->popup()->setCurrentIndex(m_Completer->completionModel()->index(0, 0));
225+
}
226+
QRect cr = cursorRect();
227+
cr.setWidth(m_Completer->popup()->sizeHintForColumn(0)
228+
+ m_Completer->popup()->verticalScrollBar()->sizeHint().width());
229+
m_Completer->complete(cr); // popup it up!
230+
}

src/sqltextedit.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#ifndef SQLTEXTEDIT_H
2+
#define SQLTEXTEDIT_H
3+
4+
#include <QTextEdit>
5+
6+
class QCompleter;
7+
class QAbstractItemModel;
8+
9+
/**
10+
* @brief The SqlTextEdit class
11+
* With basic table and fieldname auto completion.
12+
* This class is based on the Qt custom completion example.
13+
*/
14+
class SqlTextEdit : public QTextEdit
15+
{
16+
Q_OBJECT
17+
public:
18+
explicit SqlTextEdit(QWidget *parent = 0);
19+
virtual ~SqlTextEdit();
20+
21+
void setCompleter(QCompleter* completer);
22+
QCompleter* completer() const;
23+
void setDefaultCompleterModel(QAbstractItemModel* model);
24+
25+
// map that associates table -> field model
26+
typedef QMap<QString,QAbstractItemModel*> FieldCompleterModelMap;
27+
28+
void clearFieldCompleterModelMap();
29+
QAbstractItemModel* addFieldCompleterModel(const QString& tablename, QAbstractItemModel *model);
30+
31+
protected:
32+
void keyPressEvent(QKeyEvent *e);
33+
void focusInEvent(QFocusEvent *e);
34+
35+
private:
36+
QString identifierUnderCursor() const;
37+
38+
private slots:
39+
void insertCompletion(const QString& completion);
40+
41+
private:
42+
QCompleter* m_Completer;
43+
QAbstractItemModel* m_defaultCompleterModel;
44+
FieldCompleterModelMap m_fieldCompleterMap;
45+
};
46+
47+
#endif // SQLTEXTEDIT_H

src/src.pro

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ HEADERS += \
2323
FindDialog.h \
2424
EditDialog.h \
2525
ExportCsvDialog.h \
26-
ImportCsvDialog.h
26+
ImportCsvDialog.h \
27+
sqltextedit.h
2728

2829
SOURCES += \
2930
sqlitedb.cpp \
@@ -39,7 +40,8 @@ SOURCES += \
3940
FindDialog.cpp \
4041
EditDialog.cpp \
4142
ExportCsvDialog.cpp \
42-
ImportCsvDialog.cpp
43+
ImportCsvDialog.cpp \
44+
sqltextedit.cpp
4345

4446
QMAKE_CXXFLAGS += -DAPP_VERSION=\\\"`cd $$PWD;git log -n1 --format=%h_git`\\\"
4547

0 commit comments

Comments
 (0)