Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Code completion for expression builder [FEATURE]
  • Loading branch information
m-kuhn committed Sep 8, 2018
1 parent 32ee716 commit cebaa81
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 8 deletions.
3 changes: 3 additions & 0 deletions python/gui/gui_auto.sip
Expand Up @@ -79,6 +79,9 @@
%If ( HAVE_QSCI_SIP )
%Include auto_generated/qgscodeeditorsql.sip
%End
%If ( HAVE_QSCI_SIP )
%Include auto_generated/qgscodeeditorexpression.sip
%End
%Include auto_generated/qgscollapsiblegroupbox.sip
%Include auto_generated/qgscolorbrewercolorrampdialog.sip
%Include auto_generated/qgscolorbutton.sip
Expand Down
2 changes: 2 additions & 0 deletions src/gui/CMakeLists.txt
Expand Up @@ -229,6 +229,7 @@ SET(QGIS_GUI_SRCS
qgscodeeditorhtml.cpp
qgscodeeditorpython.cpp
qgscodeeditorsql.cpp
qgscodeeditorexpression.cpp
qgscollapsiblegroupbox.cpp
qgscolorbrewercolorrampdialog.cpp
qgscolorbutton.cpp
Expand Down Expand Up @@ -406,6 +407,7 @@ SET(QGIS_GUI_MOC_HDRS
qgscodeeditorhtml.h
qgscodeeditorpython.h
qgscodeeditorsql.h
qgscodeeditorexpression.h
qgscollapsiblegroupbox.h
qgscolorbrewercolorrampdialog.h
qgscolorbutton.h
Expand Down
163 changes: 163 additions & 0 deletions src/gui/qgscodeeditorexpression.cpp
@@ -0,0 +1,163 @@
/***************************************************************************
qgscodeeditorexpressoin.cpp - An expression editor based on QScintilla
--------------------------------------
Date : 8.9.2018
Copyright : (C) 2018 by Matthias Kuhn
Email : matthias@opengis.ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsapplication.h"
#include "qgscodeeditorexpression.h"

#include <QWidget>
#include <QString>
#include <QFont>
#include <QLabel>


QgsCodeEditorExpression::QgsCodeEditorExpression( QWidget *parent )
: QgsCodeEditor( parent )
{
if ( !parent )
{
setTitle( tr( "Expression Editor" ) );
}
setMarginVisible( false );
setFoldingVisible( true );
setAutoCompletionCaseSensitivity( false );
initializeLexer();
}

void QgsCodeEditorExpression::setExpressionContext( const QgsExpressionContext &context )
{
mVariables.clear();

const QStringList variableNames = context.filteredVariableNames();
for ( const QString &var : variableNames )
{
mVariables << '@' + var;
}

mContextFunctions = context.functionNames();

mFunctions.clear();

const int count = QgsExpression::functionCount();
for ( int i = 0; i < count; i++ )
{
QgsExpressionFunction *func = QgsExpression::Functions()[i];
if ( func->isDeprecated() ) // don't show deprecated functions
continue;
if ( func->isContextual() )
{
//don't show contextual functions by default - it's up the the QgsExpressionContext
//object to provide them if supported
continue;
}

QString signature = func->name();
if ( !signature.startsWith( '$' ) )
{
signature += '(';

QStringList paramNames;
const auto &parameters = func->parameters();
for ( const auto &param : parameters )
{
paramNames << param.name();
}

// No named parameters but there should be parameteres? Show an ellipsis at least
if ( parameters.isEmpty() && func->params() )
signature += QChar( 0x2026 );

signature += paramNames.join( ", " );

signature += ')';
}
mFunctions << signature;
}

updateApis();
}

void QgsCodeEditorExpression::setFields( const QgsFields &fields )
{
mFieldNames.clear();

for ( const QgsField &field : fields )
{
mFieldNames << field.name();
}

updateApis();
}


void QgsCodeEditorExpression::initializeLexer()
{
QFont font = getMonospaceFont();
#ifdef Q_OS_MAC
// The font size gotten from getMonospaceFont() is too small on Mac
font.setPointSize( QLabel().font().pointSize() );
#endif
mSqlLexer = new QgsCaseInsensitiveLexerExpression( this );
mSqlLexer->setDefaultFont( font );
mSqlLexer->setFont( font, -1 );
font.setBold( true );
mSqlLexer->setFont( font, QsciLexerSQL::Keyword );
mSqlLexer->setColor( Qt::darkYellow, QsciLexerSQL::DoubleQuotedString ); // fields

setLexer( mSqlLexer );
}

void QgsCodeEditorExpression::updateApis()
{
mApis = new QsciAPIs( mSqlLexer );

for ( const QString &var : qgis::as_const( mVariables ) )
{
mApis->add( var );
}

for ( const QString &function : qgis::as_const( mContextFunctions ) )
{
mApis->add( function );
}

for ( const QString &function : qgis::as_const( mFunctions ) )
{
mApis->add( function );
}

for ( const QString &fieldName : qgis::as_const( mFieldNames ) )
{
mApis->add( fieldName );
}

mApis->prepare();
mSqlLexer->setAPIs( mApis );
}

bool QgsCaseInsensitiveLexerExpression::caseSensitive() const
{
return false;
}
#if 0
const char *QgsCaseInsensitiveLexerExpression::wordCharacters() const
{
static QString wordChars;

wordChars = QsciLexerSQL::wordCharacters();
wordChars += '@';
return wordChars.toUtf8().constData();
}

#endif
97 changes: 97 additions & 0 deletions src/gui/qgscodeeditorexpression.h
@@ -0,0 +1,97 @@
/***************************************************************************
qgscodeeditorsql.h - A SQL editor based on QScintilla
--------------------------------------
Date : 06-Oct-2013
Copyright : (C) 2013 by Salvatore Larosa
Email : lrssvtml (at) gmail (dot) com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSCODEEDITOREXPRESSION_H
#define QGSCODEEDITOREXPRESSION_H

#include "qgis_sip.h"
#include "qgis_gui.h"
#include "qgscodeeditor.h"
#include "qgsexpressioncontext.h"

#include <Qsci/qscilexersql.h>

SIP_IF_MODULE( HAVE_QSCI_SIP )

/**
* \ingroup gui
*
* A QGIS expression editor based on QScintilla2. Adds syntax highlighting and
* code autocompletion.
*
* \since QGIS 3.4
*/
class GUI_EXPORT QgsCodeEditorExpression : public QgsCodeEditor
{
Q_OBJECT

public:
//! Constructor for QgsCodeEditorExpression
QgsCodeEditorExpression( QWidget *parent SIP_TRANSFERTHIS = nullptr );

/**
* Variables and functions from this expression context will be added to
* the API.
* Will also reload all globally registered functions.
*/
void setExpressionContext( const QgsExpressionContext &context );

/**
* Field names will be added to the API.
*/
void setFields( const QgsFields &fields );

private:
void initializeLexer();
void updateApis();
QsciAPIs *mApis;
QsciLexerSQL *mSqlLexer;

QStringList mVariables;
QStringList mContextFunctions;
QStringList mFunctions;
QStringList mFieldNames;
};

#ifndef SIP_RUN
///@cond PRIVATE

/**
* Internal use.
setAutoCompletionCaseSensitivity( false ) is not sufficient when installing
a lexer, since its caseSensitive() method is actually used, and defaults
to true.
\note not available in Python bindings
\ingroup gui
*/
class QgsCaseInsensitiveLexerExpression : public QsciLexerSQL
{
Q_OBJECT

public:
//! constructor
explicit QgsCaseInsensitiveLexerExpression( QObject *parent = nullptr ) : QsciLexerSQL( parent ) {}

bool caseSensitive() const override;

#if 0
const char *wordCharacters() const override;
#endif
};
///@endcond
#endif

#endif
10 changes: 8 additions & 2 deletions src/gui/qgsexpressionbuilderwidget.cpp
Expand Up @@ -49,7 +49,7 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent )
connect( btnNewFile, &QToolButton::pressed, this, &QgsExpressionBuilderWidget::btnNewFile_pressed );
connect( cmbFileNames, &QListWidget::currentItemChanged, this, &QgsExpressionBuilderWidget::cmbFileNames_currentItemChanged );
connect( expressionTree, &QTreeView::doubleClicked, this, &QgsExpressionBuilderWidget::expressionTree_doubleClicked );
connect( txtExpressionString, &QgsCodeEditorSQL::textChanged, this, &QgsExpressionBuilderWidget::txtExpressionString_textChanged );
connect( txtExpressionString, &QgsCodeEditorExpression::textChanged, this, &QgsExpressionBuilderWidget::txtExpressionString_textChanged );
connect( txtPython, &QgsCodeEditorPython::textChanged, this, &QgsExpressionBuilderWidget::txtPython_textChanged );
connect( txtSearchEditValues, &QgsFilterLineEdit::textChanged, this, &QgsExpressionBuilderWidget::txtSearchEditValues_textChanged );
connect( txtSearchEdit, &QgsFilterLineEdit::textChanged, this, &QgsExpressionBuilderWidget::txtSearchEdit_textChanged );
Expand Down Expand Up @@ -142,7 +142,10 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent )
txtExpressionString->setIndicatorHoverForegroundColor( QColor( Qt::blue ), FUNCTION_MARKER_ID );
txtExpressionString->setIndicatorHoverStyle( QgsCodeEditor::DotsIndicator, FUNCTION_MARKER_ID );

connect( txtExpressionString, &QgsCodeEditorSQL::indicatorClicked, this, &QgsExpressionBuilderWidget::indicatorClicked );
connect( txtExpressionString, &QgsCodeEditorExpression::indicatorClicked, this, &QgsExpressionBuilderWidget::indicatorClicked );
txtExpressionString->setAutoCompletionCaseSensitivity( true );
txtExpressionString->setAutoCompletionSource( QsciScintilla::AcsAPIs );
txtExpressionString->setCallTipsVisible( 0 );

setExpectedOutputFormat( QString() );
}
Expand Down Expand Up @@ -341,6 +344,8 @@ void QgsExpressionBuilderWidget::loadFieldNames( const QgsFields &fields )
if ( fields.isEmpty() )
return;

txtExpressionString->setFields( fields );

QStringList fieldNames;
//Q_FOREACH ( const QgsField& field, fields )
fieldNames.reserve( fields.count() );
Expand Down Expand Up @@ -696,6 +701,7 @@ void QgsExpressionBuilderWidget::txtExpressionString_textChanged()

void QgsExpressionBuilderWidget::loadExpressionContext()
{
txtExpressionString->setExpressionContext( mExpressionContext );
QStringList variableNames = mExpressionContext.filteredVariableNames();
Q_FOREACH ( const QString &variable, variableNames )
{
Expand Down
2 changes: 1 addition & 1 deletion src/gui/qgsexpressionlineedit.cpp
Expand Up @@ -55,7 +55,7 @@ void QgsExpressionLineEdit::setMultiLine( bool multiLine )

if ( multiLine && !mCodeEditor )
{
mCodeEditor = new QgsCodeEditorSQL();
mCodeEditor = new QgsCodeEditorExpression();
mCodeEditor->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
delete mLineEdit;
mLineEdit = nullptr;
Expand Down
4 changes: 2 additions & 2 deletions src/gui/qgsexpressionlineedit.h
Expand Up @@ -27,7 +27,7 @@ class QgsFilterLineEdit;
class QToolButton;
class QgsDistanceArea;
class QgsExpressionContextGenerator;
class QgsCodeEditorSQL;
class QgsCodeEditorExpression;

/**
* \ingroup gui
Expand Down Expand Up @@ -170,7 +170,7 @@ class GUI_EXPORT QgsExpressionLineEdit : public QWidget

private:
QgsFilterLineEdit *mLineEdit = nullptr;
QgsCodeEditorSQL *mCodeEditor = nullptr;
QgsCodeEditorExpression *mCodeEditor = nullptr;
QToolButton *mButton = nullptr;
QString mExpressionDialogTitle;
std::unique_ptr<QgsDistanceArea> mDa;
Expand Down
6 changes: 3 additions & 3 deletions src/ui/qgsexpressionbuilder.ui
Expand Up @@ -273,7 +273,7 @@
</widget>
</item>
<item>
<widget class="QgsCodeEditorSQL" name="txtExpressionString" native="true"/>
<widget class="QgsCodeEditorExpression" name="txtExpressionString" native="true"/>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
Expand Down Expand Up @@ -768,9 +768,9 @@ Saved scripts are auto loaded on QGIS startup.</string>
<header>qgsfilterlineedit.h</header>
</customwidget>
<customwidget>
<class>QgsCodeEditorSQL</class>
<class>QgsCodeEditorExpression</class>
<extends>QWidget</extends>
<header>qgscodeeditorsql.h</header>
<header>qgscodeeditorexpression.h</header>
<container>1</container>
</customwidget>
<customwidget>
Expand Down

0 comments on commit cebaa81

Please sign in to comment.