diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 75b466bd869..c0d870ba017 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -257,6 +257,7 @@ ENDIF(WITH_CUSTOM_WIDGETS) SET(PY_FILES __init__.py utils.py + user.py ) ADD_CUSTOM_TARGET(pyutils ALL) diff --git a/python/gui/qgsexpressionbuilderwidget.sip b/python/gui/qgsexpressionbuilderwidget.sip index 75a7f098825..9c4691a35e2 100644 --- a/python/gui/qgsexpressionbuilderwidget.sip +++ b/python/gui/qgsexpressionbuilderwidget.sip @@ -111,6 +111,26 @@ class QgsExpressionBuilderWidget : QWidget void loadRecent( QString key ); + /** Create a new file in the function editor + */ + void newFunctionFile( QString fileName = "scratch"); + + /** Save the current function editor text to the given file. + */ + void saveFunctionFile( QString fileName ); + + /** Load code from the given file into the function editor + */ + void loadCodeFromFile( QString path ); + + /** Load code into the function editor + */ + void loadFunctionCode( QString code ); + + /** Update the list of function files found at the given path + */ + void updateFunctionFileList( QString path ); + public slots: void currentChanged( const QModelIndex &index, const QModelIndex & ); void on_expressionTree_doubleClicked( const QModelIndex &index ); diff --git a/python/user.py b/python/user.py new file mode 100644 index 00000000000..e02c3a1c908 --- /dev/null +++ b/python/user.py @@ -0,0 +1,41 @@ +import os +import sys +import glob + +from qgis.core import QgsApplication + +def load_user_expressions(path): + """ + Load all user expressions from the given paths + """ + #Loop all py files and import them + modules = glob.glob(path + "/*.py") + names = [os.path.basename(f)[:-3] for f in modules] + for name in names: + if name == "__init__": + continue + # As user expression functions should be registed with qgsfunction + # just importing the file is enough to get it to load the functions into QGIS + __import__("expressions.{0}".format(name), locals(), globals()) + + +userpythonhome = os.path.join(QgsApplication.qgisSettingsDirPath(), "python") +expressionspath = os.path.join(userpythonhome, "expressions") +startuppy = os.path.join(userpythonhome, "startup.py") + +# exec startup script +if os.path.exists(startuppy): + execfile(startuppy, locals(), globals()) + +if not os.path.exists(expressionspath): + os.makedirs(expressionspath) + +initfile = os.path.join(expressionspath, "__init__.py") +if not os.path.exists(initfile): + open(initfile, "w").close() + +import expressions + +expressions.load = load_user_expressions +expressions.load(expressionspath) + diff --git a/src/gui/qgsexpressionbuilderwidget.cpp b/src/gui/qgsexpressionbuilderwidget.cpp index d00bb382d08..0a99b588d3f 100644 --- a/src/gui/qgsexpressionbuilderwidget.cpp +++ b/src/gui/qgsexpressionbuilderwidget.cpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent ) : QWidget( parent ) @@ -54,71 +56,27 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent ) connect( button, SIGNAL( pressed() ), this, SLOT( operatorButtonClicked() ) ); } - // TODO Can we move this stuff to QgsExpression, like the functions? - registerItem( "Operators", "+", " + ", tr( "Addition operator" ) ); - registerItem( "Operators", "-", " - ", tr( "Subtraction operator" ) ); - registerItem( "Operators", "*", " * ", tr( "Multiplication operator" ) ); - registerItem( "Operators", "/", " / ", tr( "Division operator" ) ); - registerItem( "Operators", "%", " % ", tr( "Modulo operator" ) ); - registerItem( "Operators", "^", " ^ ", tr( "Power operator" ) ); - registerItem( "Operators", "=", " = ", tr( "Equal operator" ) ); - registerItem( "Operators", ">", " > ", tr( "Greater as operator" ) ); - registerItem( "Operators", "<", " < ", tr( "Less than operator" ) ); - registerItem( "Operators", "<>", " <> ", tr( "Unequal operator" ) ); - registerItem( "Operators", "<=", " <= ", tr( "Less or equal operator" ) ); - registerItem( "Operators", ">=", " >= ", tr( "Greater or equal operator" ) ); - registerItem( "Operators", "||", " || ", - QString( "|| %1
%2
%3:%4" ) - .arg( tr( "(String Concatenation)" ) ) - .arg( tr( "Joins two values together into a string" ) ) - .arg( tr( "Usage" ) ) - .arg( tr( "'Dia' || Diameter" ) ) ); - registerItem( "Operators", "IN", " IN " ); - registerItem( "Operators", "LIKE", " LIKE " ); - registerItem( "Operators", "ILIKE", " ILIKE " ); - registerItem( "Operators", "IS", " IS " ); - registerItem( "Operators", "OR", " OR " ); - registerItem( "Operators", "AND", " AND " ); - registerItem( "Operators", "NOT", " NOT " ); - - QString casestring = "CASE WHEN condition THEN result END"; - QString caseelsestring = "CASE WHEN condition THEN result ELSE result END"; - registerItem( "Conditionals", "CASE", casestring ); - registerItem( "Conditionals", "CASE ELSE", caseelsestring ); - - // Load the functions from the QgsExpression class - int count = QgsExpression::functionCount(); - for ( int i = 0; i < count; i++ ) - { - QgsExpression::Function* func = QgsExpression::Functions()[i]; - QString name = func->name(); - if ( name.startsWith( "_" ) ) // do not display private functions - continue; - if ( func->params() != 0 ) - name += "("; - registerItem( func->group(), func->name(), " " + name + " ", func->helptext() ); - } - - QList specials = QgsExpression::specialColumns(); - for ( int i = 0; i < specials.size(); ++i ) - { - QString name = specials[i]->name(); - registerItem( specials[i]->group(), name, " " + name + " " ); - } - txtSearchEdit->setPlaceholderText( tr( "Search" ) ); QSettings settings; splitter->restoreState( settings.value( "/windows/QgsExpressionBuilderWidget/splitter" ).toByteArray() ); -// splitter_2->restoreState( settings.value( "/windows/QgsExpressionBuilderWidget/splitter2" ).toByteArray() ); txtExpressionString->setFoldingVisible( false ); -// customFunctionBotton->setVisible( QgsPythonRunner::isValid() ); - txtPython->setVisible( false ); - cgbCustomFunction->setCollapsed( true ); - txtPython->setText( "@qgsfunction(args=-1, group='Custom')\n" - "def func(values, feature, parent):\n" - " return str(values)" ); + + updateFunctionTree(); + + if ( QgsPythonRunner::isValid() ) + { + QgsPythonRunner::eval( "qgis.user.expressionspath", mFunctionsPath ); + newFunctionFile(); + // The scratch file gets written each time the widget opens. + saveFunctionFile("scratch"); + updateFunctionFileList( mFunctionsPath ); + } + else + { + tab_2->setEnabled( false ); + } } @@ -156,6 +114,113 @@ void QgsExpressionBuilderWidget::currentChanged( const QModelIndex &index, const txtHelpText->setToolTip( txtHelpText->toPlainText() ); } +void QgsExpressionBuilderWidget::on_btnRun_pressed() +{ + saveFunctionFile( cmbFileNames->currentText() ); + runPythonCode( txtPython->text() ); +} + +void QgsExpressionBuilderWidget::runPythonCode( QString code ) +{ + if ( QgsPythonRunner::isValid() ) + { + QString pythontext = code; + QgsPythonRunner::run( pythontext ); + } + updateFunctionTree(); +} + +void QgsExpressionBuilderWidget::saveFunctionFile( QString fileName ) +{ + QDir myDir( mFunctionsPath ); + if ( !myDir.exists() ) + { + myDir.mkpath( mFunctionsPath ); + } + + if ( !fileName.endsWith( ".py" ) ) + { + fileName.append( ".py" ); + } + + fileName = mFunctionsPath + QDir::separator() + fileName; + QFile myFile( fileName ); + if ( myFile.open( QIODevice::WriteOnly | QIODevice::Text ) ) + { + QTextStream myFileStream( &myFile ); + myFileStream << txtPython->text() << endl; + myFile.close(); + } +} + +void QgsExpressionBuilderWidget::updateFunctionFileList( QString path ) +{ + mFunctionsPath = path; + QDir dir( path ); + dir.setNameFilters( QStringList() << "*.py" ); + QStringList files = dir.entryList( QDir::Files ); + cmbFileNames->clear(); + foreach ( QString name, files ) + { + QFileInfo info( mFunctionsPath + QDir::separator() + name ); + if ( info.baseName() == "__init__" ) continue; + cmbFileNames->addItem( info.baseName() ); + } +} + +void QgsExpressionBuilderWidget::newFunctionFile( QString fileName ) +{ + txtPython->setText( "from qgis.core import *\n" + "from qgis.gui import *\n\n" + "@qgsfunction(args=-1, group='Custom')\n" + "def func(values, feature, parent):\n" + " return str(values)" ); + int index = cmbFileNames->findText( fileName ); + if ( index == -1 ) + cmbFileNames->setEditText( fileName ); + else + cmbFileNames->setCurrentIndex( index ); +} + +void QgsExpressionBuilderWidget::on_btnNewFile_pressed() +{ + newFunctionFile(); +} + +void QgsExpressionBuilderWidget::on_cmbFileNames_currentIndexChanged( int index ) +{ + if ( index == -1 ) + return; + + QString path = mFunctionsPath + QDir::separator() + cmbFileNames->currentText(); + loadCodeFromFile( path ); +} + +void QgsExpressionBuilderWidget::loadCodeFromFile( QString path ) +{ + if ( !path.endsWith( ".py" ) ) + path.append( ".py" ); + + txtPython->loadScript( path ); +} + +void QgsExpressionBuilderWidget::loadFunctionCode( QString code ) +{ + txtPython->setText( code ); +} + +void QgsExpressionBuilderWidget::on_btnSaveFile_pressed() +{ + QString name = cmbFileNames->currentText(); + saveFunctionFile( name ); + int index = cmbFileNames->findText( name ); + if ( index == -1 ) + { + cmbFileNames->addItem( name ); + cmbFileNames->setCurrentIndex( cmbFileNames->count() - 1 ); + } +} + void QgsExpressionBuilderWidget::on_expressionTree_doubleClicked( const QModelIndex &index ) { QModelIndex idx = mProxyModel->mapToSource( index ); @@ -292,6 +357,63 @@ void QgsExpressionBuilderWidget::loadRecent( QString key ) } } +void QgsExpressionBuilderWidget::updateFunctionTree() +{ + mModel->clear(); + mExpressionGroups.clear(); + // TODO Can we move this stuff to QgsExpression, like the functions? + registerItem( "Operators", "+", " + ", tr( "Addition operator" ) ); + registerItem( "Operators", "-", " - ", tr( "Subtraction operator" ) ); + registerItem( "Operators", "*", " * ", tr( "Multiplication operator" ) ); + registerItem( "Operators", "/", " / ", tr( "Division operator" ) ); + registerItem( "Operators", "%", " % ", tr( "Modulo operator" ) ); + registerItem( "Operators", "^", " ^ ", tr( "Power operator" ) ); + registerItem( "Operators", "=", " = ", tr( "Equal operator" ) ); + registerItem( "Operators", ">", " > ", tr( "Greater as operator" ) ); + registerItem( "Operators", "<", " < ", tr( "Less than operator" ) ); + registerItem( "Operators", "<>", " <> ", tr( "Unequal operator" ) ); + registerItem( "Operators", "<=", " <= ", tr( "Less or equal operator" ) ); + registerItem( "Operators", ">=", " >= ", tr( "Greater or equal operator" ) ); + registerItem( "Operators", "||", " || ", + QString( "|| %1
%2
%3:%4" ) + .arg( tr( "(String Concatenation)" ) ) + .arg( tr( "Joins two values together into a string" ) ) + .arg( tr( "Usage" ) ) + .arg( tr( "'Dia' || Diameter" ) ) ); + registerItem( "Operators", "IN", " IN " ); + registerItem( "Operators", "LIKE", " LIKE " ); + registerItem( "Operators", "ILIKE", " ILIKE " ); + registerItem( "Operators", "IS", " IS " ); + registerItem( "Operators", "OR", " OR " ); + registerItem( "Operators", "AND", " AND " ); + registerItem( "Operators", "NOT", " NOT " ); + + QString casestring = "CASE WHEN condition THEN result END"; + QString caseelsestring = "CASE WHEN condition THEN result ELSE result END"; + registerItem( "Conditionals", "CASE", casestring ); + registerItem( "Conditionals", "CASE ELSE", caseelsestring ); + + // Load the functions from the QgsExpression class + int count = QgsExpression::functionCount(); + for ( int i = 0; i < count; i++ ) + { + QgsExpression::Function* func = QgsExpression::Functions()[i]; + QString name = func->name(); + if ( name.startsWith( "_" ) ) // do not display private functions + continue; + if ( func->params() != 0 ) + name += "("; + registerItem( func->group(), func->name(), " " + name + " ", func->helptext() ); + } + + QList specials = QgsExpression::specialColumns(); + for ( int i = 0; i < specials.size(); ++i ) + { + QString name = specials[i]->name(); + registerItem( specials[i]->group(), name, " " + name + " " ); + } +} + void QgsExpressionBuilderWidget::setGeomCalculator( const QgsDistanceArea & da ) { mDa = da; @@ -299,11 +421,6 @@ void QgsExpressionBuilderWidget::setGeomCalculator( const QgsDistanceArea & da ) QString QgsExpressionBuilderWidget::expressionText() { - if ( QgsPythonRunner::isValid() ) - { - QString pythontext = txtPython->text(); - QgsPythonRunner::run( pythontext ); - } return txtExpressionString->text(); } diff --git a/src/gui/qgsexpressionbuilderwidget.h b/src/gui/qgsexpressionbuilderwidget.h index 63d144ae564..a7e18138f96 100644 --- a/src/gui/qgsexpressionbuilderwidget.h +++ b/src/gui/qgsexpressionbuilderwidget.h @@ -150,8 +150,32 @@ class GUI_EXPORT QgsExpressionBuilderWidget : public QWidget, private Ui::QgsExp void loadRecent( QString key ); + /** Create a new file in the function editor + */ + void newFunctionFile( QString fileName = "scratch"); + + /** Save the current function editor text to the given file. + */ + void saveFunctionFile( QString fileName ); + + /** Load code from the given file into the function editor + */ + void loadCodeFromFile( QString path ); + + /** Load code into the function editor + */ + void loadFunctionCode( QString code ); + + /** Update the list of function files found at the given path + */ + void updateFunctionFileList( QString path ); + public slots: void currentChanged( const QModelIndex &index, const QModelIndex & ); + void on_btnRun_pressed(); + void on_btnNewFile_pressed(); + void on_cmbFileNames_currentIndexChanged( int index ); + void on_btnSaveFile_pressed(); void on_expressionTree_doubleClicked( const QModelIndex &index ); void on_txtExpressionString_textChanged(); void on_txtSearchEdit_textChanged(); @@ -174,9 +198,12 @@ class GUI_EXPORT QgsExpressionBuilderWidget : public QWidget, private Ui::QgsExp void expressionParsed( bool isValid ); private: + void runPythonCode( QString code ); + void updateFunctionTree(); void fillFieldValues( int fieldIndex, int countLimit ); QString loadFunctionHelp( QgsExpressionItem* functionName ); + QString mFunctionsPath; QgsVectorLayer *mLayer; QStandardItemModel *mModel; QgsExpressionItemSearchProxy *mProxyModel; diff --git a/src/gui/symbology-ng/qgsdatadefinedsymboldialog.cpp b/src/gui/symbology-ng/qgsdatadefinedsymboldialog.cpp index d4391baa7cc..bbd17b6a1fd 100644 --- a/src/gui/symbology-ng/qgsdatadefinedsymboldialog.cpp +++ b/src/gui/symbology-ng/qgsdatadefinedsymboldialog.cpp @@ -5,6 +5,7 @@ #include "qgslogger.h" #include +#include QgsDataDefinedSymbolDialog::QgsDataDefinedSymbolDialog( const QList< DataDefinedSymbolEntry >& entries, const QgsVectorLayer* vl, QWidget * parent, Qt::WindowFlags f ) diff --git a/src/python/qgspythonutilsimpl.cpp b/src/python/qgspythonutilsimpl.cpp index a06f95d812f..f9a07f83952 100644 --- a/src/python/qgspythonutilsimpl.cpp +++ b/src/python/qgspythonutilsimpl.cpp @@ -157,6 +157,7 @@ void QgsPythonUtilsImpl::initPython( QgisInterface* interface ) return; } + // tell the utils script where to look for the plugins runString( "qgis.utils.plugin_paths = [" + pluginpaths.join( "," ) + "]" ); runString( "qgis.utils.sys_plugin_path = \"" + pluginsPath() + "\"" ); @@ -169,8 +170,14 @@ void QgsPythonUtilsImpl::initPython( QgisInterface* interface ) // initialize 'iface' object runString( "qgis.utils.initInterface(" + QString::number(( unsigned long ) interface ) + ")" ); - QString startuppath = homePythonPath() + " + \"/startup.py\""; - runString( "if os.path.exists(" + startuppath + "): from startup import *\n" ); + // import QGIS user + error_msg = QObject::tr( "Couldn't load QGIS user." ) + "\n" + QObject::tr( "Python support will be disabled." ); + if ( !runString( "import qgis.user", error_msg ) ) + { + // Should we really bail because of this?! + exitPython(); + return; + } // release GIL! // Later on, we acquire GIL just before doing some Python calls and diff --git a/src/ui/qgsexpressionbuilder.ui b/src/ui/qgsexpressionbuilder.ui index 31199302cc9..1bb8aa79032 100644 --- a/src/ui/qgsexpressionbuilder.ui +++ b/src/ui/qgsexpressionbuilder.ui @@ -19,357 +19,37 @@ Form - - + + 0 - - - - Qt::Horizontal + + 0 + + + 0 + + + 0 + + + + + 0 - - 4 + + true - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 0 - 0 - - - - Expression - - - true - - - - 0 - - - - - - - - - 0 - 0 - - - - - 27 - 0 - - - - - 300 - 16777215 - - - - - 20 - 0 - - - - - 7 - 0 - - - - Qt::LeftToRight - - - false - - - - 2 - - - 0 - - - 0 - - - 0 - - - - - Equal operator - - - = - - - - - - - - 0 - 0 - - - - Addition operator - - - + - - - - - - - Subtraction operator - - - - - - - - - - - Division operator - - - / - - - - - - - Multiplication operator - - - * - - - - - - - Power operator - - - ^ - - - - - - - String Concatenation - - - || - - - - - - - - 0 - 0 - - - - - 0 - 10 - - - - Open Bracket - - - ( - - - - - - - Close Bracket - - - ) - - - - - - - - - - - - - 0 - 0 - - - - Output preview is generated <br> using the first feature from the layer. - - - Output preview: - - - - - - - - 0 - 0 - - - - - 50 - true - false - false - - - - Output preview is generated <br> using the first feature from the layer. - - - QFrame::NoFrame - - - QFrame::Sunken - - - 0 - - - 0 - - - - - - false - - - false - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 0 - 0 - - - - Custom function - - - true - - - - 0 - - - 9 - - - 0 - - - 0 - - - - mOperatorsGroupBox - - - - - - - - - - - - - Functions - - - true - - + + + Expression + + 0 - 3 + 0 0 @@ -377,165 +57,610 @@ 0 - - 6 - - + + + + Qt::Horizontal + + + 4 + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + 0 + 0 + + + + Expression + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + Output preview is generated <br> using the first feature from the layer. + + + Output preview: + + + + + + + + 0 + 0 + + + + + 50 + true + false + false + + + + Output preview is generated <br> using the first feature from the layer. + + + QFrame::NoFrame + + + QFrame::Sunken + + + 0 + + + 0 + + + + + + false + + + false + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + + + 0 + 0 + + + + + 27 + 0 + + + + + 300 + 16777215 + + + + + 20 + 0 + + + + + 7 + 0 + + + + Qt::LeftToRight + + + false + + + + 2 + + + 0 + + + 0 + + + 0 + + + + + Equal operator + + + = + + + + + + + + 0 + 0 + + + + Addition operator + + + + + + + + + + + Subtraction operator + + + - + + + + + + + Division operator + + + / + + + + + + + Multiplication operator + + + * + + + + + + + Power operator + + + ^ + + + + + + + String Concatenation + + + || + + + + + + + + 0 + 0 + + + + + 0 + 10 + + + + Open Bracket + + + ( + + + + + + + Close Bracket + + + ) + + + + + + + + + + + + + + + + + Functions + + + true + + + + 0 + + + 3 + + + 0 + + + 0 + + + 6 + + + 0 + + + + + true + + + + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + true + + + false + + + + + + + 0 + + + QLayout::SetDefaultConstraint + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + false + + + QAbstractItemView::NoEditTriggers + + + false + + + true + + + QListView::ListMode + + + + + + + Values + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 5 + 20 + + + + + + + + Load values + + + + + + + all unique + + + + + + + 10 samples + + + + + + + + + + + + + + true + + + + + + + + + + + + + + Function Editor + + + 0 - - - - true - - + + 0 + + + 0 + + + 0 + + + - - - - QFrame::StyledPanel - - - QFrame::Sunken - - - QAbstractItemView::NoEditTriggers - - - false - - - false - - - true - - - false - - - - - - - 0 - + + QLayout::SetDefaultConstraint - - 0 - - - 0 - - - 0 - - - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - false - - - QAbstractItemView::NoEditTriggers - - - false - - - true - - - QListView::ListMode - - - - - - - Values - - - - - - - - - - - 0 - - - - - Qt::Horizontal - - - - 5 - 20 - - - - - - - - Load values - - - - - - - all unique - - - - - - - 10 samples - - - - - - - - - - - - - + + + ... + + + + :/images/themes/default/console/iconRunScriptConsole.png:/images/themes/default/console/iconRunScriptConsole.png + + true + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 20 + 20 + + + + + + + + New + + + + :/images/themes/default/console/iconTabEditorConsole.png:/images/themes/default/console/iconTabEditorConsole.png + + + true + + + + + + + + 0 + 0 + + + + true + + + + + + + Save + + + + :/images/themes/default/console/iconSaveConsole.png:/images/themes/default/console/iconSaveConsole.png + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -543,9 +668,6 @@ - mLoadGroupBox - splitter - groupBox @@ -553,12 +675,6 @@ QLineEdit
qgsfilterlineedit.h
- - QgsCollapsibleGroupBox - QGroupBox -
qgscollapsiblegroupbox.h
- 1 -
QgsCodeEditorSQL QWidget @@ -572,6 +688,8 @@ 1
- + + +