Skip to content

Commit cebaa81

Browse files
committed
Code completion for expression builder [FEATURE]
1 parent 32ee716 commit cebaa81

8 files changed

+279
-8
lines changed

python/gui/gui_auto.sip

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@
7979
%If ( HAVE_QSCI_SIP )
8080
%Include auto_generated/qgscodeeditorsql.sip
8181
%End
82+
%If ( HAVE_QSCI_SIP )
83+
%Include auto_generated/qgscodeeditorexpression.sip
84+
%End
8285
%Include auto_generated/qgscollapsiblegroupbox.sip
8386
%Include auto_generated/qgscolorbrewercolorrampdialog.sip
8487
%Include auto_generated/qgscolorbutton.sip

src/gui/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ SET(QGIS_GUI_SRCS
229229
qgscodeeditorhtml.cpp
230230
qgscodeeditorpython.cpp
231231
qgscodeeditorsql.cpp
232+
qgscodeeditorexpression.cpp
232233
qgscollapsiblegroupbox.cpp
233234
qgscolorbrewercolorrampdialog.cpp
234235
qgscolorbutton.cpp
@@ -406,6 +407,7 @@ SET(QGIS_GUI_MOC_HDRS
406407
qgscodeeditorhtml.h
407408
qgscodeeditorpython.h
408409
qgscodeeditorsql.h
410+
qgscodeeditorexpression.h
409411
qgscollapsiblegroupbox.h
410412
qgscolorbrewercolorrampdialog.h
411413
qgscolorbutton.h

src/gui/qgscodeeditorexpression.cpp

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/***************************************************************************
2+
qgscodeeditorexpressoin.cpp - An expression editor based on QScintilla
3+
--------------------------------------
4+
Date : 8.9.2018
5+
Copyright : (C) 2018 by Matthias Kuhn
6+
Email : matthias@opengis.ch
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgsapplication.h"
17+
#include "qgscodeeditorexpression.h"
18+
19+
#include <QWidget>
20+
#include <QString>
21+
#include <QFont>
22+
#include <QLabel>
23+
24+
25+
QgsCodeEditorExpression::QgsCodeEditorExpression( QWidget *parent )
26+
: QgsCodeEditor( parent )
27+
{
28+
if ( !parent )
29+
{
30+
setTitle( tr( "Expression Editor" ) );
31+
}
32+
setMarginVisible( false );
33+
setFoldingVisible( true );
34+
setAutoCompletionCaseSensitivity( false );
35+
initializeLexer();
36+
}
37+
38+
void QgsCodeEditorExpression::setExpressionContext( const QgsExpressionContext &context )
39+
{
40+
mVariables.clear();
41+
42+
const QStringList variableNames = context.filteredVariableNames();
43+
for ( const QString &var : variableNames )
44+
{
45+
mVariables << '@' + var;
46+
}
47+
48+
mContextFunctions = context.functionNames();
49+
50+
mFunctions.clear();
51+
52+
const int count = QgsExpression::functionCount();
53+
for ( int i = 0; i < count; i++ )
54+
{
55+
QgsExpressionFunction *func = QgsExpression::Functions()[i];
56+
if ( func->isDeprecated() ) // don't show deprecated functions
57+
continue;
58+
if ( func->isContextual() )
59+
{
60+
//don't show contextual functions by default - it's up the the QgsExpressionContext
61+
//object to provide them if supported
62+
continue;
63+
}
64+
65+
QString signature = func->name();
66+
if ( !signature.startsWith( '$' ) )
67+
{
68+
signature += '(';
69+
70+
QStringList paramNames;
71+
const auto &parameters = func->parameters();
72+
for ( const auto &param : parameters )
73+
{
74+
paramNames << param.name();
75+
}
76+
77+
// No named parameters but there should be parameteres? Show an ellipsis at least
78+
if ( parameters.isEmpty() && func->params() )
79+
signature += QChar( 0x2026 );
80+
81+
signature += paramNames.join( ", " );
82+
83+
signature += ')';
84+
}
85+
mFunctions << signature;
86+
}
87+
88+
updateApis();
89+
}
90+
91+
void QgsCodeEditorExpression::setFields( const QgsFields &fields )
92+
{
93+
mFieldNames.clear();
94+
95+
for ( const QgsField &field : fields )
96+
{
97+
mFieldNames << field.name();
98+
}
99+
100+
updateApis();
101+
}
102+
103+
104+
void QgsCodeEditorExpression::initializeLexer()
105+
{
106+
QFont font = getMonospaceFont();
107+
#ifdef Q_OS_MAC
108+
// The font size gotten from getMonospaceFont() is too small on Mac
109+
font.setPointSize( QLabel().font().pointSize() );
110+
#endif
111+
mSqlLexer = new QgsCaseInsensitiveLexerExpression( this );
112+
mSqlLexer->setDefaultFont( font );
113+
mSqlLexer->setFont( font, -1 );
114+
font.setBold( true );
115+
mSqlLexer->setFont( font, QsciLexerSQL::Keyword );
116+
mSqlLexer->setColor( Qt::darkYellow, QsciLexerSQL::DoubleQuotedString ); // fields
117+
118+
setLexer( mSqlLexer );
119+
}
120+
121+
void QgsCodeEditorExpression::updateApis()
122+
{
123+
mApis = new QsciAPIs( mSqlLexer );
124+
125+
for ( const QString &var : qgis::as_const( mVariables ) )
126+
{
127+
mApis->add( var );
128+
}
129+
130+
for ( const QString &function : qgis::as_const( mContextFunctions ) )
131+
{
132+
mApis->add( function );
133+
}
134+
135+
for ( const QString &function : qgis::as_const( mFunctions ) )
136+
{
137+
mApis->add( function );
138+
}
139+
140+
for ( const QString &fieldName : qgis::as_const( mFieldNames ) )
141+
{
142+
mApis->add( fieldName );
143+
}
144+
145+
mApis->prepare();
146+
mSqlLexer->setAPIs( mApis );
147+
}
148+
149+
bool QgsCaseInsensitiveLexerExpression::caseSensitive() const
150+
{
151+
return false;
152+
}
153+
#if 0
154+
const char *QgsCaseInsensitiveLexerExpression::wordCharacters() const
155+
{
156+
static QString wordChars;
157+
158+
wordChars = QsciLexerSQL::wordCharacters();
159+
wordChars += '@';
160+
return wordChars.toUtf8().constData();
161+
}
162+
163+
#endif

src/gui/qgscodeeditorexpression.h

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/***************************************************************************
2+
qgscodeeditorsql.h - A SQL editor based on QScintilla
3+
--------------------------------------
4+
Date : 06-Oct-2013
5+
Copyright : (C) 2013 by Salvatore Larosa
6+
Email : lrssvtml (at) gmail (dot) com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#ifndef QGSCODEEDITOREXPRESSION_H
17+
#define QGSCODEEDITOREXPRESSION_H
18+
19+
#include "qgis_sip.h"
20+
#include "qgis_gui.h"
21+
#include "qgscodeeditor.h"
22+
#include "qgsexpressioncontext.h"
23+
24+
#include <Qsci/qscilexersql.h>
25+
26+
SIP_IF_MODULE( HAVE_QSCI_SIP )
27+
28+
/**
29+
* \ingroup gui
30+
*
31+
* A QGIS expression editor based on QScintilla2. Adds syntax highlighting and
32+
* code autocompletion.
33+
*
34+
* \since QGIS 3.4
35+
*/
36+
class GUI_EXPORT QgsCodeEditorExpression : public QgsCodeEditor
37+
{
38+
Q_OBJECT
39+
40+
public:
41+
//! Constructor for QgsCodeEditorExpression
42+
QgsCodeEditorExpression( QWidget *parent SIP_TRANSFERTHIS = nullptr );
43+
44+
/**
45+
* Variables and functions from this expression context will be added to
46+
* the API.
47+
* Will also reload all globally registered functions.
48+
*/
49+
void setExpressionContext( const QgsExpressionContext &context );
50+
51+
/**
52+
* Field names will be added to the API.
53+
*/
54+
void setFields( const QgsFields &fields );
55+
56+
private:
57+
void initializeLexer();
58+
void updateApis();
59+
QsciAPIs *mApis;
60+
QsciLexerSQL *mSqlLexer;
61+
62+
QStringList mVariables;
63+
QStringList mContextFunctions;
64+
QStringList mFunctions;
65+
QStringList mFieldNames;
66+
};
67+
68+
#ifndef SIP_RUN
69+
///@cond PRIVATE
70+
71+
/**
72+
* Internal use.
73+
74+
setAutoCompletionCaseSensitivity( false ) is not sufficient when installing
75+
a lexer, since its caseSensitive() method is actually used, and defaults
76+
to true.
77+
\note not available in Python bindings
78+
\ingroup gui
79+
*/
80+
class QgsCaseInsensitiveLexerExpression : public QsciLexerSQL
81+
{
82+
Q_OBJECT
83+
84+
public:
85+
//! constructor
86+
explicit QgsCaseInsensitiveLexerExpression( QObject *parent = nullptr ) : QsciLexerSQL( parent ) {}
87+
88+
bool caseSensitive() const override;
89+
90+
#if 0
91+
const char *wordCharacters() const override;
92+
#endif
93+
};
94+
///@endcond
95+
#endif
96+
97+
#endif

src/gui/qgsexpressionbuilderwidget.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent )
4949
connect( btnNewFile, &QToolButton::pressed, this, &QgsExpressionBuilderWidget::btnNewFile_pressed );
5050
connect( cmbFileNames, &QListWidget::currentItemChanged, this, &QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged );
5151
connect( expressionTree, &QTreeView::doubleClicked, this, &QgsExpressionBuilderWidget::expressionTree_doubleClicked );
52-
connect( txtExpressionString, &QgsCodeEditorSQL::textChanged, this, &QgsExpressionBuilderWidget::txtExpressionString_textChanged );
52+
connect( txtExpressionString, &QgsCodeEditorExpression::textChanged, this, &QgsExpressionBuilderWidget::txtExpressionString_textChanged );
5353
connect( txtPython, &QgsCodeEditorPython::textChanged, this, &QgsExpressionBuilderWidget::txtPython_textChanged );
5454
connect( txtSearchEditValues, &QgsFilterLineEdit::textChanged, this, &QgsExpressionBuilderWidget::txtSearchEditValues_textChanged );
5555
connect( txtSearchEdit, &QgsFilterLineEdit::textChanged, this, &QgsExpressionBuilderWidget::txtSearchEdit_textChanged );
@@ -142,7 +142,10 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent )
142142
txtExpressionString->setIndicatorHoverForegroundColor( QColor( Qt::blue ), FUNCTION_MARKER_ID );
143143
txtExpressionString->setIndicatorHoverStyle( QgsCodeEditor::DotsIndicator, FUNCTION_MARKER_ID );
144144

145-
connect( txtExpressionString, &QgsCodeEditorSQL::indicatorClicked, this, &QgsExpressionBuilderWidget::indicatorClicked );
145+
connect( txtExpressionString, &QgsCodeEditorExpression::indicatorClicked, this, &QgsExpressionBuilderWidget::indicatorClicked );
146+
txtExpressionString->setAutoCompletionCaseSensitivity( true );
147+
txtExpressionString->setAutoCompletionSource( QsciScintilla::AcsAPIs );
148+
txtExpressionString->setCallTipsVisible( 0 );
146149

147150
setExpectedOutputFormat( QString() );
148151
}
@@ -341,6 +344,8 @@ void QgsExpressionBuilderWidget::loadFieldNames( const QgsFields &fields )
341344
if ( fields.isEmpty() )
342345
return;
343346

347+
txtExpressionString->setFields( fields );
348+
344349
QStringList fieldNames;
345350
//Q_FOREACH ( const QgsField& field, fields )
346351
fieldNames.reserve( fields.count() );
@@ -696,6 +701,7 @@ void QgsExpressionBuilderWidget::txtExpressionString_textChanged()
696701

697702
void QgsExpressionBuilderWidget::loadExpressionContext()
698703
{
704+
txtExpressionString->setExpressionContext( mExpressionContext );
699705
QStringList variableNames = mExpressionContext.filteredVariableNames();
700706
Q_FOREACH ( const QString &variable, variableNames )
701707
{

src/gui/qgsexpressionlineedit.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ void QgsExpressionLineEdit::setMultiLine( bool multiLine )
5555

5656
if ( multiLine && !mCodeEditor )
5757
{
58-
mCodeEditor = new QgsCodeEditorSQL();
58+
mCodeEditor = new QgsCodeEditorExpression();
5959
mCodeEditor->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
6060
delete mLineEdit;
6161
mLineEdit = nullptr;

src/gui/qgsexpressionlineedit.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class QgsFilterLineEdit;
2727
class QToolButton;
2828
class QgsDistanceArea;
2929
class QgsExpressionContextGenerator;
30-
class QgsCodeEditorSQL;
30+
class QgsCodeEditorExpression;
3131

3232
/**
3333
* \ingroup gui
@@ -170,7 +170,7 @@ class GUI_EXPORT QgsExpressionLineEdit : public QWidget
170170

171171
private:
172172
QgsFilterLineEdit *mLineEdit = nullptr;
173-
QgsCodeEditorSQL *mCodeEditor = nullptr;
173+
QgsCodeEditorExpression *mCodeEditor = nullptr;
174174
QToolButton *mButton = nullptr;
175175
QString mExpressionDialogTitle;
176176
std::unique_ptr<QgsDistanceArea> mDa;

src/ui/qgsexpressionbuilder.ui

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@
273273
</widget>
274274
</item>
275275
<item>
276-
<widget class="QgsCodeEditorSQL" name="txtExpressionString" native="true"/>
276+
<widget class="QgsCodeEditorExpression" name="txtExpressionString" native="true"/>
277277
</item>
278278
<item>
279279
<layout class="QGridLayout" name="gridLayout">
@@ -768,9 +768,9 @@ Saved scripts are auto loaded on QGIS startup.</string>
768768
<header>qgsfilterlineedit.h</header>
769769
</customwidget>
770770
<customwidget>
771-
<class>QgsCodeEditorSQL</class>
771+
<class>QgsCodeEditorExpression</class>
772772
<extends>QWidget</extends>
773-
<header>qgscodeeditorsql.h</header>
773+
<header>qgscodeeditorexpression.h</header>
774774
<container>1</container>
775775
</customwidget>
776776
<customwidget>

0 commit comments

Comments
 (0)