diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index 5bd30d7e244..000356b960b 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -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 diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 6fbe34d668d..7330c65b0fe 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -229,6 +229,7 @@ SET(QGIS_GUI_SRCS qgscodeeditorhtml.cpp qgscodeeditorpython.cpp qgscodeeditorsql.cpp + qgscodeeditorexpression.cpp qgscollapsiblegroupbox.cpp qgscolorbrewercolorrampdialog.cpp qgscolorbutton.cpp @@ -406,6 +407,7 @@ SET(QGIS_GUI_MOC_HDRS qgscodeeditorhtml.h qgscodeeditorpython.h qgscodeeditorsql.h + qgscodeeditorexpression.h qgscollapsiblegroupbox.h qgscolorbrewercolorrampdialog.h qgscolorbutton.h diff --git a/src/gui/qgscodeeditorexpression.cpp b/src/gui/qgscodeeditorexpression.cpp new file mode 100644 index 00000000000..f6ed08a3d57 --- /dev/null +++ b/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 +#include +#include +#include + + +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 ¶meters = func->parameters(); + for ( const auto ¶m : 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 diff --git a/src/gui/qgscodeeditorexpression.h b/src/gui/qgscodeeditorexpression.h new file mode 100644 index 00000000000..8593de290cd --- /dev/null +++ b/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 + +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 diff --git a/src/gui/qgsexpressionbuilderwidget.cpp b/src/gui/qgsexpressionbuilderwidget.cpp index a2d5601bd4e..4ff261bccdc 100644 --- a/src/gui/qgsexpressionbuilderwidget.cpp +++ b/src/gui/qgsexpressionbuilderwidget.cpp @@ -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 ); @@ -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() ); } @@ -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() ); @@ -696,6 +701,7 @@ void QgsExpressionBuilderWidget::txtExpressionString_textChanged() void QgsExpressionBuilderWidget::loadExpressionContext() { + txtExpressionString->setExpressionContext( mExpressionContext ); QStringList variableNames = mExpressionContext.filteredVariableNames(); Q_FOREACH ( const QString &variable, variableNames ) { diff --git a/src/gui/qgsexpressionlineedit.cpp b/src/gui/qgsexpressionlineedit.cpp index a92c75cf00a..fd29bd29598 100644 --- a/src/gui/qgsexpressionlineedit.cpp +++ b/src/gui/qgsexpressionlineedit.cpp @@ -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; diff --git a/src/gui/qgsexpressionlineedit.h b/src/gui/qgsexpressionlineedit.h index ec438219bc5..bf92ce248f6 100644 --- a/src/gui/qgsexpressionlineedit.h +++ b/src/gui/qgsexpressionlineedit.h @@ -27,7 +27,7 @@ class QgsFilterLineEdit; class QToolButton; class QgsDistanceArea; class QgsExpressionContextGenerator; -class QgsCodeEditorSQL; +class QgsCodeEditorExpression; /** * \ingroup gui @@ -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 mDa; diff --git a/src/ui/qgsexpressionbuilder.ui b/src/ui/qgsexpressionbuilder.ui index d3a734476c1..deeb184c23b 100644 --- a/src/ui/qgsexpressionbuilder.ui +++ b/src/ui/qgsexpressionbuilder.ui @@ -273,7 +273,7 @@ - + @@ -768,9 +768,9 @@ Saved scripts are auto loaded on QGIS startup.
qgsfilterlineedit.h
- QgsCodeEditorSQL + QgsCodeEditorExpression QWidget -
qgscodeeditorsql.h
+
qgscodeeditorexpression.h
1