diff --git a/python/gui/auto_generated/qgsexpressionstoredialog.sip.in b/python/gui/auto_generated/qgsexpressionstoredialog.sip.in new file mode 100644 index 00000000000..877e8baeda8 --- /dev/null +++ b/python/gui/auto_generated/qgsexpressionstoredialog.sip.in @@ -0,0 +1,60 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsexpressionstoredialog.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +class QgsExpressionStoreDialog : QDialog +{ +%Docstring +A generic dialog for editing expression text, label and help text. + +.. versionadded:: 3.12 +%End + +%TypeHeaderCode +#include "qgsexpressionstoredialog.h" +%End + public: + + QgsExpressionStoreDialog( const QString &label, + const QString &expression, + const QString &helpText, + const QStringList &existingLabels, + QWidget *parent = 0 ); +%Docstring +Creates a QgsExpressionStoreDialog with given ``label``, ``expression`` and ``helpText``. + +:param existingLabels: list of existing labels for unique label validation +:param parent: optional parent widget +%End + + QString expression( ); +%Docstring +Returns the expression text +%End + + QString label(); +%Docstring +Returns the label text +%End + + QString helpText(); +%Docstring +Returns the help text +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsexpressionstoredialog.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index 5a22b375c3f..0d3d3f81b16 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -65,6 +65,7 @@ %Include auto_generated/qgsencodingfiledialog.sip %Include auto_generated/qgserrordialog.sip %Include auto_generated/qgsexpressionbuilderdialog.sip +%Include auto_generated/qgsexpressionstoredialog.sip %Include auto_generated/qgsexpressionbuilderwidget.sip %Include auto_generated/qgsexpressionhighlighter.sip %Include auto_generated/qgsexpressionlineedit.sip diff --git a/src/app/qgsfieldcalculator.cpp b/src/app/qgsfieldcalculator.cpp index 47d78cfbeff..78dc2a82c7c 100644 --- a/src/app/qgsfieldcalculator.cpp +++ b/src/app/qgsfieldcalculator.cpp @@ -153,6 +153,7 @@ QgsFieldCalculator::QgsFieldCalculator( QgsVectorLayer *vl, QWidget *parent ) mOnlyUpdateSelectedCheckBox->setText( tr( "Only update %1 selected features" ).arg( vl->selectedFeatureCount() ) ); builder->loadRecent( QStringLiteral( "fieldcalc" ) ); + builder->loadStored( QStringLiteral( "fieldcalc" ) ); mInfoIcon->setPixmap( style()->standardPixmap( QStyle::SP_MessageBoxInformation ) ); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index b91c3b1fb49..105d7de4cec 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -292,6 +292,7 @@ SET(QGIS_GUI_SRCS qgsexpressionhighlighter.cpp qgsexpressionlineedit.cpp qgsexpressionselectiondialog.cpp + qgsexpressionstoredialog.cpp qgsextentgroupbox.cpp qgsexternalresourcewidget.cpp qgsfeatureselectiondlg.cpp @@ -487,6 +488,7 @@ SET(QGIS_GUI_HDRS qgsencodingfiledialog.h qgserrordialog.h qgsexpressionbuilderdialog.h + qgsexpressionstoredialog.h qgsexpressionbuilderwidget.h qgsexpressionhighlighter.h qgsexpressionlineedit.h @@ -919,6 +921,7 @@ SET(QGIS_GUI_UI_HDRS ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgscredentialdialog.h ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsdetaileditemwidgetbase.h ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsexpressionbuilderdialogbase.h + ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsexpressionstoredialogbase.h ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsexpressionbuilder.h ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsexpressionselectiondialogbase.h ${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsfeaturefilterwidget.h diff --git a/src/gui/attributetable/qgsdualview.cpp b/src/gui/attributetable/qgsdualview.cpp index b9b4bc16936..9d6d36ec548 100644 --- a/src/gui/attributetable/qgsdualview.cpp +++ b/src/gui/attributetable/qgsdualview.cpp @@ -861,6 +861,7 @@ void QgsDualView::modifySort() expressionBuilder->setLayer( mLayer ); expressionBuilder->loadFieldNames(); expressionBuilder->loadRecent( QStringLiteral( "generic" ) ); + expressionBuilder->loadStored( QStringLiteral( "generic" ) ); expressionBuilder->setExpressionText( sortExpression().isEmpty() ? mLayer->displayExpression() : sortExpression() ); sortingGroupBox->layout()->addWidget( expressionBuilder ); diff --git a/src/gui/qgsexpressionbuilderdialog.cpp b/src/gui/qgsexpressionbuilderdialog.cpp index fd3a3b7a237..c44bb55db2e 100644 --- a/src/gui/qgsexpressionbuilderdialog.cpp +++ b/src/gui/qgsexpressionbuilderdialog.cpp @@ -33,6 +33,7 @@ QgsExpressionBuilderDialog::QgsExpressionBuilderDialog( QgsVectorLayer *layer, c builder->setExpressionText( startText ); builder->loadFieldNames(); builder->loadRecent( mRecentKey ); + builder->loadStored( mRecentKey ); connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsExpressionBuilderDialog::showHelp ); } diff --git a/src/gui/qgsexpressionbuilderwidget.cpp b/src/gui/qgsexpressionbuilderwidget.cpp index 5c1cd88b502..16e8088d614 100644 --- a/src/gui/qgsexpressionbuilderwidget.cpp +++ b/src/gui/qgsexpressionbuilderwidget.cpp @@ -32,6 +32,7 @@ #include "qgsexpressioncontextutils.h" #include "qgsfieldformatterregistry.h" #include "qgsfieldformatter.h" +#include "qgsexpressionstoredialog.h" #include #include @@ -41,7 +42,7 @@ #include #include #include - +#include QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent ) : QWidget( parent ) @@ -61,6 +62,8 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent ) connect( mValuesListView, &QListView::doubleClicked, this, &QgsExpressionBuilderWidget::mValuesListView_doubleClicked ); connect( btnSaveExpression, &QPushButton::pressed, this, &QgsExpressionBuilderWidget::storeCurrentExpression ); connect( btnRemoveExpression, &QPushButton::pressed, this, &QgsExpressionBuilderWidget::removeSelectedExpression ); + connect( btnClearEditor, &QPushButton::pressed, txtExpressionString, &QgsCodeEditorExpression::clear ); + txtHelpText->setOpenExternalLinks( true ); mValueGroupBox->hide(); @@ -74,6 +77,13 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent ) expressionTree->setSortingEnabled( true ); expressionTree->sortByColumn( 0, Qt::AscendingOrder ); + expressionTree->setSelectionMode( QAbstractItemView::SelectionMode::SingleSelection ); + + // Set icons for tool buttons + btnSaveExpression->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionFileSave.svg" ) ) ); + btnRemoveExpression->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionDeleteSelected.svg" ) ) ); + btnClearEditor->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionFileNew.svg" ) ) ); + expressionTree->setContextMenuPolicy( Qt::CustomContextMenu ); connect( this, &QgsExpressionBuilderWidget::expressionParsed, this, &QgsExpressionBuilderWidget::setExpressionState ); connect( expressionTree, &QWidget::customContextMenuRequested, this, &QgsExpressionBuilderWidget::showContextMenu ); @@ -250,6 +260,10 @@ void QgsExpressionBuilderWidget::currentChanged( const QModelIndex &index, const // Show the help for the current item. QString help = loadFunctionHelp( item ); txtHelpText->setText( help ); + + btnRemoveExpression->setEnabled( item->parent() && + item->parent()->text() == mStoredGroupName ); + } void QgsExpressionBuilderWidget::btnRun_pressed() @@ -594,12 +608,12 @@ void QgsExpressionBuilderWidget::loadRecent( const QString &collection ) void QgsExpressionBuilderWidget::loadStored( const QString &collection ) { mRecentKey = collection; - const QString groupName = tr( "Stored (%1)" ).arg( collection ); + mStoredGroupName = tr( "Stored (%1)" ).arg( collection ); // Cleanup - if ( mExpressionGroups.contains( groupName ) ) + if ( mExpressionGroups.contains( mStoredGroupName ) ) { - QgsExpressionItem *node = mExpressionGroups.value( groupName ); + QgsExpressionItem *node = mExpressionGroups.value( mStoredGroupName ); node->removeRows( 0, node->rowCount() ); } @@ -610,12 +624,13 @@ void QgsExpressionBuilderWidget::loadStored( const QString &collection ) QString helpText; QString expression; int i = 0; - for ( const auto &label : settings.childGroups() ) + mStoredLabels = settings.childGroups(); + for ( const auto &label : qgis::as_const( mStoredLabels ) ) { settings.beginGroup( label ); expression = settings.value( QStringLiteral( "expression" ) ).toString(); helpText = settings.value( QStringLiteral( "helpText" ) ).toString(); - this->registerItem( groupName, label, expression, helpText, QgsExpressionItem::ExpressionNode, false, i++ ); + this->registerItem( mStoredGroupName, label, expression, helpText, QgsExpressionItem::ExpressionNode, false, i++ ); settings.endGroup(); } } @@ -629,6 +644,14 @@ void QgsExpressionBuilderWidget::saveToStored( const QString &label, const QStri settings.setValue( QStringLiteral( "expression" ), expression ); settings.setValue( QStringLiteral( "helpText" ), helpText ); this->loadStored( collection ); + // Scroll + const QModelIndexList idxs { expressionTree->model()->match( expressionTree->model()->index( 0, 0 ), + Qt::DisplayRole, label, 1, + Qt::MatchFlag::MatchRecursive ) }; + if ( ! idxs.isEmpty() ) + { + expressionTree->scrollTo( idxs.first() ); + } } void QgsExpressionBuilderWidget::removeFromStored( const QString &name, const QString &collection ) @@ -771,6 +794,9 @@ void QgsExpressionBuilderWidget::txtExpressionString_textChanged() QString text = expressionText(); clearErrors(); + btnClearEditor->setEnabled( ! txtExpressionString->text().isEmpty() ); + btnSaveExpression->setEnabled( false ); + // If the string is empty the expression will still "fail" although // we don't show the user an error as it will be confusing. if ( text.isEmpty() ) @@ -838,6 +864,7 @@ void QgsExpressionBuilderWidget::txtExpressionString_textChanged() setParserError( false ); setEvalError( false ); createMarkers( exp.rootNode() ); + btnSaveExpression->setEnabled( true ); } } @@ -1201,21 +1228,34 @@ void QgsExpressionBuilderWidget::autosave() void QgsExpressionBuilderWidget::storeCurrentExpression() { const QString expression { this->expressionText() }; - saveToStored( expression, expression, expression, mRecentKey ); + QgsExpressionStoreDialog dlg { expression, expression, QString( ), mStoredLabels }; + if ( dlg.exec() == QDialog::DialogCode::Accepted ) + { + saveToStored( dlg.label(), dlg.expression(), dlg.helpText(), mRecentKey ); + } } void QgsExpressionBuilderWidget::removeSelectedExpression() { - QModelIndex idx { expressionTree->currentIndex() }; + +// Get the item + QModelIndex idx = mProxyModel->mapToSource( expressionTree->currentIndex() ); QgsExpressionItem *item = dynamic_cast( mModel->itemFromIndex( idx ) ); if ( !item ) return; - // Don't handle the double-click if we are on a header node. - if ( item->getItemType() == QgsExpressionItem::Header ) + // Don't handle remove if we are on a header node or the parent + // is not the stored group + if ( item->getItemType() == QgsExpressionItem::Header || + ( item->parent() && item->parent()->text() != mStoredGroupName ) ) return; - removeFromStored( item->text(), mRecentKey ); + if ( QMessageBox::Yes == QMessageBox::question( this, tr( "Remove Stored Expression" ), + tr( "Do you really want to remove stored expressions '%1'?" ).arg( item->text() ), + QMessageBox::Yes | QMessageBox::No ) ) + { + removeFromStored( item->text(), mRecentKey ); + } } @@ -1281,10 +1321,6 @@ QString QgsExpressionBuilderWidget::loadFunctionHelp( QgsExpressionItem *express return "" + helpContents + ""; } - - - - QgsExpressionItemSearchProxy::QgsExpressionItemSearchProxy() { setFilterCaseSensitivity( Qt::CaseInsensitive ); diff --git a/src/gui/qgsexpressionbuilderwidget.h b/src/gui/qgsexpressionbuilderwidget.h index f9aa38bb090..4074a9001d8 100644 --- a/src/gui/qgsexpressionbuilderwidget.h +++ b/src/gui/qgsexpressionbuilderwidget.h @@ -497,6 +497,8 @@ class GUI_EXPORT QgsExpressionBuilderWidget : public QWidget, private Ui::QgsExp QPointer< QgsProject > mProject; bool mEvalError = true; bool mParserError = true; + QString mStoredGroupName; + QStringList mStoredLabels; }; // clazy:excludeall=qstring-allocations diff --git a/src/gui/qgsexpressionselectiondialog.cpp b/src/gui/qgsexpressionselectiondialog.cpp index a19dd9d5ead..d14ff39c141 100644 --- a/src/gui/qgsexpressionselectiondialog.cpp +++ b/src/gui/qgsexpressionselectiondialog.cpp @@ -59,6 +59,7 @@ QgsExpressionSelectionDialog::QgsExpressionSelectionDialog( QgsVectorLayer *laye mExpressionBuilder->setExpressionText( startText ); mExpressionBuilder->loadFieldNames(); mExpressionBuilder->loadRecent( QStringLiteral( "Selection" ) ); + mExpressionBuilder->loadStored( QStringLiteral( "Selection" ) ); QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer ) ); mExpressionBuilder->setExpressionContext( context ); diff --git a/src/gui/qgsexpressionstoredialog.cpp b/src/gui/qgsexpressionstoredialog.cpp new file mode 100644 index 00000000000..c8418ad075a --- /dev/null +++ b/src/gui/qgsexpressionstoredialog.cpp @@ -0,0 +1,33 @@ +#include "qgsexpressionstoredialog.h" +#include +#include + +QgsExpressionStoreDialog::QgsExpressionStoreDialog( const QString &label, const QString &expression, const QString &helpText, const QStringList &existingLabels, QWidget *parent ) + : QDialog( parent ) + , mExistingLabels( existingLabels ) +{ + setupUi( this ); + mExpression->setText( expression ); + mHelpText->setText( helpText ); + connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsExpressionStoreDialog::accept ); + connect( buttonBox, &QDialogButtonBox::rejected, this, &QgsExpressionStoreDialog::reject ); + mValidationError->hide(); + mValidationError->setStyleSheet( QStringLiteral( "QLabel { color : red; }" ) ); + QPushButton *saveBtn { buttonBox->button( QDialogButtonBox::StandardButton::Save ) }; + saveBtn->setEnabled( false ); + connect( mLabel, &QLineEdit::textChanged, this, [ = ]( const QString & text ) + { + if ( mExistingLabels.contains( text ) ) + { + mValidationError->show(); + saveBtn->setEnabled( false ); + } + else + { + mValidationError->hide(); + saveBtn->setEnabled( true ); + } + } ); + mLabel->setText( label ); +} + diff --git a/src/gui/qgsexpressionstoredialog.h b/src/gui/qgsexpressionstoredialog.h new file mode 100644 index 00000000000..0fe4767b039 --- /dev/null +++ b/src/gui/qgsexpressionstoredialog.h @@ -0,0 +1,51 @@ +#ifndef QGSEXPRESSIONSTOREDIALOG_H +#define QGSEXPRESSIONSTOREDIALOG_H + +#include "qgis_gui.h" +#include +#include "ui_qgsexpressionstoredialogbase.h" + + + +/** + * \ingroup gui + * A generic dialog for editing expression text, label and help text. + * \since QGIS 3.12 + */ +class GUI_EXPORT QgsExpressionStoreDialog : public QDialog, private Ui::QgsExpressionStoreDialogBase +{ + public: + + /** + * Creates a QgsExpressionStoreDialog with given \a label, \a expression and \a helpText. + * \param existingLabels list of existing labels for unique label validation + * \param parent optional parent widget + */ + QgsExpressionStoreDialog( const QString &label, + const QString &expression, + const QString &helpText, + const QStringList &existingLabels, + QWidget *parent = nullptr ); + + /** + * Returns the expression text + */ + QString expression( ) { return mExpression->text( ); } + + /** + * Returns the label text + */ + QString label() { return mLabel->text(); } + + /** + * Returns the help text + */ + QString helpText() { return mHelpText->toHtml(); } + + private: + + QStringList mExistingLabels; + +}; + +#endif // QGSEXPRESSIONSTOREDIALOG_H diff --git a/src/ui/qgsexpressionbuilder.ui b/src/ui/qgsexpressionbuilder.ui index f95a73c16c9..58b3b4dea00 100644 --- a/src/ui/qgsexpressionbuilder.ui +++ b/src/ui/qgsexpressionbuilder.ui @@ -325,9 +325,25 @@ 0 - + + + false + - Add the selected expression to the stored expressions + Clear the expression editor + + + Clear + + + + + + + false + + + Add current expression to stored expressions Save @@ -336,6 +352,9 @@ + + false + Removes selected expression from the stored expressions diff --git a/src/ui/qgsexpressionstoredialogbase.ui b/src/ui/qgsexpressionstoredialogbase.ui new file mode 100644 index 00000000000..99266052cb2 --- /dev/null +++ b/src/ui/qgsexpressionstoredialogbase.ui @@ -0,0 +1,82 @@ + + + QgsExpressionStoreDialogBase + + + + 0 + 0 + 395 + 211 + + + + + 0 + 0 + + + + Store expression + + + + + + Label + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Save + + + + + + + + + + + + + Help text + + + + + + + + + + Expression + + + + + + + A stored expression with this name already exists! + + + true + + + + + + + + QgsCodeEditorExpression + QWidget +
qgscodeeditorexpression.h
+ 1 +
+
+ + +