17 #include <QMessageBox>
18 #include <QVersionNumber>
34 QString text = QStringLiteral(
"<h3>%1</h3>\n<div class=\"description\"><p>%2</p></div>" )
35 .arg( QCoreApplication::translate(
"relation_help",
"relation %1" ).arg( relation.
name() ),
36 QObject::tr(
"Inserts the relation ID for the relation named '%1'." ).arg( relation.
name() ) );
38 text += QStringLiteral(
"<h4>%1</h4><div class=\"description\"><pre>%2</pre></div>" )
39 .arg( QObject::tr(
"Current value" ), relation.
id() );
48 QString text = QStringLiteral(
"<h3>%1</h3>\n<div class=\"description\"><p>%2</p></div>" )
49 .arg( QCoreApplication::translate(
"layer_help",
"map layer %1" ).arg( layer->
name() ),
50 QObject::tr(
"Inserts the layer ID for the layer named '%1'." ).arg( layer->
name() ) );
52 text += QStringLiteral(
"<h4>%1</h4><div class=\"description\"><pre>%2</pre></div>" )
53 .arg( QObject::tr(
"Current value" ), layer->
id() );
61 QString text = QStringLiteral(
"<h3>%1</h3>\n<div class=\"description\"><p>%2</p></div>" )
62 .arg( QCoreApplication::translate(
"recent_expression_help",
"expression %1" ).arg( label ),
63 QCoreApplication::translate(
"recent_expression_help",
"Recently used expression." ) );
65 text += QStringLiteral(
"<h4>%1</h4><div class=\"description\"><pre>%2</pre></div>" )
66 .arg( QObject::tr(
"Expression" ), expression );
74 QString text = QStringLiteral(
"<h3>%1</h3>\n<div class=\"description\"><p>%2</p></div>" )
75 .arg( QCoreApplication::translate(
"user_expression_help",
"expression %1" ).arg( label ), description );
77 text += QStringLiteral(
"<h4>%1</h4><div class=\"description\"><pre>%2</pre></div>" )
78 .arg( QObject::tr(
"Expression" ), expression );
84 QString
formatVariableHelp(
const QString &variable,
const QString &description,
bool showValue,
const QVariant &value )
86 QString text = QStringLiteral(
"<h3>%1</h3>\n<div class=\"description\"><p>%2</p></div>" )
87 .arg( QCoreApplication::translate(
"variable_help",
"variable %1" ).arg( variable ), description );
91 QString valueString = !value.isValid()
92 ? QCoreApplication::translate(
"variable_help",
"not set" )
95 text += QStringLiteral(
"<h4>%1</h4><div class=\"description\"><p>%2</p></div>" )
96 .arg( QObject::tr(
"Current value" ), valueString );
110 : QTreeView( parent )
113 connect(
this, &QTreeView::doubleClicked,
this, &QgsExpressionTreeView::onDoubleClicked );
115 mModel = std::make_unique<QStandardItemModel>();
116 mProxyModel = std::make_unique<QgsExpressionItemSearchProxy>();
117 mProxyModel->setDynamicSortFilter(
true );
118 mProxyModel->setSourceModel( mModel.get() );
119 setModel( mProxyModel.get() );
120 setSortingEnabled(
true );
121 sortByColumn( 0, Qt::AscendingOrder );
123 setSelectionMode( QAbstractItemView::SelectionMode::SingleSelection );
125 setContextMenuPolicy( Qt::CustomContextMenu );
126 connect(
this, &QWidget::customContextMenuRequested,
this, &QgsExpressionTreeView::showContextMenu );
127 connect( selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsExpressionTreeView::currentItemChanged );
129 updateFunctionTree();
134 QModelIndex firstItem = mProxyModel->index( 0, 0, QModelIndex() );
135 setCurrentIndex( firstItem );
152 mExpressionContext = context;
153 updateFunctionTree();
161 mMenuProvider = provider;
166 updateFunctionTree();
174 QModelIndex idx = mProxyModel->mapToSource( currentIndex() );
192 updateFunctionTree();
198 mProxyModel->setFilterString( text );
199 if ( text.isEmpty() )
206 QModelIndex index = mProxyModel->index( 0, 0 );
207 if ( mProxyModel->hasChildren( index ) )
209 QModelIndex child = mProxyModel->index( 0, 0, index );
210 selectionModel()->setCurrentIndex( child, QItemSelectionModel::ClearAndSelect );
215 void QgsExpressionTreeView::onDoubleClicked(
const QModelIndex &index )
217 QModelIndex idx = mProxyModel->mapToSource( index );
229 void QgsExpressionTreeView::showContextMenu( QPoint pt )
231 QModelIndex idx = indexAt( pt );
232 idx = mProxyModel->mapToSource( idx );
237 if ( !mMenuProvider )
243 menu->popup( mapToGlobal( pt ) );
246 void QgsExpressionTreeView::currentItemChanged(
const QModelIndex &index,
const QModelIndex & )
249 QModelIndex idx = mProxyModel->mapToSource( index );
257 void QgsExpressionTreeView::updateFunctionTree()
260 mExpressionGroups.clear();
263 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"+" ), QStringLiteral(
" + " ) );
264 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"-" ), QStringLiteral(
" - " ) );
265 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"*" ), QStringLiteral(
" * " ) );
266 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"/" ), QStringLiteral(
" / " ) );
267 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"%" ), QStringLiteral(
" % " ) );
268 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"^" ), QStringLiteral(
" ^ " ) );
269 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"=" ), QStringLiteral(
" = " ) );
270 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"~" ), QStringLiteral(
" ~ " ) );
271 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
">" ), QStringLiteral(
" > " ) );
272 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"<" ), QStringLiteral(
" < " ) );
273 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"<>" ), QStringLiteral(
" <> " ) );
274 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"<=" ), QStringLiteral(
" <= " ) );
275 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
">=" ), QStringLiteral(
" >= " ) );
276 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"[]" ), QStringLiteral(
"[ ]" ) );
277 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"||" ), QStringLiteral(
" || " ) );
278 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"BETWEEN" ), QStringLiteral(
" BETWEEN " ) );
279 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"NOT BETWEEN" ), QStringLiteral(
" NOT BETWEEN " ) );
280 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"IN" ), QStringLiteral(
" IN " ) );
281 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"LIKE" ), QStringLiteral(
" LIKE " ) );
282 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"ILIKE" ), QStringLiteral(
" ILIKE " ) );
283 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"IS" ), QStringLiteral(
" IS " ) );
284 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"IS NOT" ), QStringLiteral(
" IS NOT " ) );
285 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"OR" ), QStringLiteral(
" OR " ) );
286 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"AND" ), QStringLiteral(
" AND " ) );
287 registerItem( QStringLiteral(
"Operators" ), QStringLiteral(
"NOT" ), QStringLiteral(
" NOT " ) );
289 QString casestring = QStringLiteral(
"CASE WHEN condition THEN result END" );
290 registerItem( QStringLiteral(
"Conditionals" ), QStringLiteral(
"CASE" ), casestring );
293 registerItem( QStringLiteral(
"Fields and Values" ), QStringLiteral(
"NULL" ), QStringLiteral(
"NULL" ), QString(),
QgsExpressionItem::ExpressionNode,
false, -1 );
297 for (
int i = 0; i < count; i++ )
300 QString name = func->
name();
301 if ( name.startsWith(
'_' ) )
311 if ( func->
params() != 0 )
313 else if ( !name.startsWith(
'$' ) )
314 name += QLatin1String(
"()" );
325 loadExpressionContext();
329 const QString &label,
330 const QString &expressionText,
331 const QString &helpText,
332 QgsExpressionItem::ItemType type,
bool highlightedItem,
int sortOrder,
const QIcon &icon,
const QStringList &tags,
const QString &name )
335 item->setData( label, Qt::UserRole );
339 item->setIcon( icon );
342 if ( mExpressionGroups.contains( group ) )
345 groupNode->appendRow( item );
351 newgroupNode->setData( group, Qt::UserRole );
354 newgroupNode->appendRow( item );
355 newgroupNode->setBackground( QBrush( QColor( 150, 150, 150, 150 ) ) );
356 mModel->appendRow( newgroupNode );
357 mExpressionGroups.insert( group, newgroupNode );
360 if ( highlightedItem )
364 topLevelItem->setData( label, Qt::UserRole );
366 QFont font = topLevelItem->font();
367 font.setBold(
true );
368 topLevelItem->setFont( font );
369 mModel->appendRow( topLevelItem );
374 void QgsExpressionTreeView::registerItemForAllGroups(
const QStringList &groups,
const QString &label,
const QString &expressionText,
const QString &helpText,
QgsExpressionItem::ItemType type,
bool highlightedItem,
int sortOrder,
const QStringList &tags )
376 const auto constGroups = groups;
377 for (
const QString &group : constGroups )
379 registerItem( group, label, expressionText, helpText, type, highlightedItem, sortOrder, QIcon(), tags );
383 void QgsExpressionTreeView::loadExpressionContext()
386 const auto constVariableNames = variableNames;
387 for (
const QString &variable : constVariableNames )
389 registerItem( QStringLiteral(
"Variables" ), variable,
" @" + variable +
' ',
396 QStringList contextFunctions = mExpressionContext.
functionNames();
397 const auto constContextFunctions = contextFunctions;
398 for (
const QString &functionName : constContextFunctions )
401 QString name = func->
name();
402 if ( name.startsWith(
'_' ) )
404 if ( func->
params() != 0 )
410 void QgsExpressionTreeView::loadLayers()
415 QMap<QString, QgsMapLayer *> layers = mProject->mapLayers();
416 QMap<QString, QgsMapLayer *>::const_iterator layerIt = layers.constBegin();
417 for ( ; layerIt != layers.constEnd(); ++layerIt )
421 loadLayerFields( qobject_cast<QgsVectorLayer *>( layerIt.value() ), parentItem );
432 for (
int fieldIdx = 0; fieldIdx < fields.
count(); ++fieldIdx )
438 item->setData( label, Qt::UserRole );
443 item->setIcon( icon );
444 parentItem->appendRow( item );
450 for (
int i = 0; i < fields.
count(); ++i )
459 void QgsExpressionTreeView::loadFieldNames()
462 if ( mExpressionGroups.contains( QStringLiteral(
"Fields and Values" ) ) )
464 QgsExpressionItem *node = mExpressionGroups.value( QStringLiteral(
"Fields and Values" ) );
465 node->removeRows( 0, node->rowCount() );
468 registerItem( QStringLiteral(
"Fields and Values" ), QStringLiteral(
"NULL" ), QStringLiteral(
"NULL" ), QString(),
QgsExpressionItem::ExpressionNode,
false, -1 );
480 void QgsExpressionTreeView::loadRelations()
485 QMap<QString, QgsRelation> relations = mProject->relationManager()->relations();
486 QMap<QString, QgsRelation>::const_iterator relIt = relations.constBegin();
487 for ( ; relIt != relations.constEnd(); ++relIt )
489 registerItemForAllGroups( QStringList() << tr(
"Relations" ), relIt->name(), QStringLiteral(
"'%1'" ).arg( relIt->id() ),
formatRelationHelp( relIt.value() ) );
495 mRecentKey = collection;
496 QString name = tr(
"Recent (%1)" ).arg( collection );
497 if ( mExpressionGroups.contains( name ) )
500 node->removeRows( 0, node->rowCount() );
504 const QString location = QStringLiteral(
"/expressions/recent/%1" ).arg( collection );
505 const QStringList expressions = settings.
value( location ).toStringList();
507 for (
const QString &expression : expressions )
510 QString label = expression;
511 label.replace(
'\n',
' ' );
520 QString location = QStringLiteral(
"/expressions/recent/%1" ).arg( collection );
521 QStringList expressions = settings.
value( location ).toStringList();
522 expressions.removeAll( expressionText );
524 expressions.prepend( expressionText );
526 while ( expressions.count() > 20 )
528 expressions.pop_back();
531 settings.
setValue( location, expressions );
538 const QString location = QStringLiteral(
"user" );
539 settings.
beginGroup( location, QgsSettings::Section::Expressions );
541 settings.
setValue( QStringLiteral(
"expression" ), expression );
542 settings.
setValue( QStringLiteral(
"helpText" ), helpText );
545 const QModelIndexList idxs { mModel->match( mModel->index( 0, 0 ), Qt::DisplayRole, label, 1, Qt::MatchFlag::MatchRecursive ) };
546 if ( ! idxs.isEmpty() )
548 scrollTo( idxs.first() );
555 settings.
remove( QStringLiteral(
"user/%1" ).arg( label ), QgsSettings::Section::Expressions );
563 if ( mExpressionGroups.contains( QStringLiteral(
"UserGroup" ) ) )
565 QgsExpressionItem *node = mExpressionGroups.value( QStringLiteral(
"UserGroup" ) );
566 node->removeRows( 0, node->rowCount() );
570 const QString location = QStringLiteral(
"user" );
571 settings.
beginGroup( location, QgsSettings::Section::Expressions );
576 for (
const auto &label : std::as_const( mUserExpressionLabels ) )
579 expression = settings.
value( QStringLiteral(
"expression" ) ).toString();
588 return mUserExpressionLabels;
593 const QString group = QStringLiteral(
"user" );
595 QJsonArray exportList;
596 QJsonObject exportObject
599 {
"exported_at", QDateTime::currentDateTime().toString( Qt::ISODate )},
601 {
"expressions", exportList}
604 settings.
beginGroup( group, QgsSettings::Section::Expressions );
608 for (
const QString &label : std::as_const( mUserExpressionLabels ) )
612 const QString expression = settings.
value( QStringLiteral(
"expression" ) ).toString();
613 const QString helpText = settings.
value( QStringLiteral(
"helpText" ) ).toString();
614 const QJsonObject expressionObject
617 {
"type",
"expression"},
618 {
"expression", expression},
620 {
"description", helpText}
622 exportList.push_back( expressionObject );
627 exportObject[QStringLiteral(
"expressions" )] = exportList;
628 QJsonDocument exportJson = QJsonDocument( exportObject );
636 if ( ! expressionsDocument.isObject() )
639 QJsonObject expressionsObject = expressionsDocument.object();
642 if ( ! expressionsObject[QStringLiteral(
"qgis_version" )].isString()
643 || ! expressionsObject[QStringLiteral(
"exported_at" )].isString()
644 || ! expressionsObject[QStringLiteral(
"author" )].isString()
645 || ! expressionsObject[QStringLiteral(
"expressions" )].isArray() )
649 QVersionNumber qgisJsonVersion = QVersionNumber::fromString( expressionsObject[QStringLiteral(
"qgis_version" )].toString() );
650 QVersionNumber qgisVersion = QVersionNumber::fromString(
Qgis::version() );
654 if ( qgisJsonVersion > qgisVersion )
656 QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::No;
657 switch ( QMessageBox::question(
this,
658 tr(
"QGIS Version Mismatch" ),
659 tr(
"The imported expressions are from newer version of QGIS (%1) "
660 "and some of the expression might not work the current version (%2). "
661 "Are you sure you want to continue?" ).arg( qgisJsonVersion.toString(), qgisVersion.toString() ), buttons ) )
663 case QMessageBox::No:
666 case QMessageBox::Yes:
675 QStringList skippedExpressionLabels;
676 bool isApplyToAll =
false;
677 bool isOkToOverwrite =
false;
680 settings.
beginGroup( QStringLiteral(
"user" ), QgsSettings::Section::Expressions );
683 const QJsonArray expressions = expressionsObject[QStringLiteral(
"expressions" )].toArray();
684 for (
const QJsonValue && expressionValue : expressions )
687 if ( ! expressionValue.isObject() )
690 skippedExpressionLabels.append( expressionValue.toString() );
694 QJsonObject expressionObj = expressionValue.toObject();
697 if ( ! expressionObj[QStringLiteral(
"name" )].isString()
698 || ! expressionObj[QStringLiteral(
"type" )].isString()
699 || ! expressionObj[QStringLiteral(
"expression" )].isString()
700 || ! expressionObj[QStringLiteral(
"group" )].isString()
701 || ! expressionObj[QStringLiteral(
"description" )].isString() )
704 if ( ! expressionObj[QStringLiteral(
"name" )].toString().isEmpty() )
705 skippedExpressionLabels.append( expressionObj[QStringLiteral(
"name" )].toString() );
707 skippedExpressionLabels.append( expressionObj[QStringLiteral(
"expression" )].toString() );
713 if ( expressionObj[QStringLiteral(
"type" )].toString() != QLatin1String(
"expression" ) )
715 skippedExpressionLabels.append( expressionObj[QStringLiteral(
"name" )].toString() );
720 if ( expressionObj[QStringLiteral(
"group" )].toString() != QLatin1String(
"user" ) )
722 skippedExpressionLabels.append( expressionObj[QStringLiteral(
"name" )].toString() );
726 const QString label = expressionObj[QStringLiteral(
"name" )].toString();
727 const QString expression = expressionObj[QStringLiteral(
"expression" )].toString();
728 const QString helpText = expressionObj[QStringLiteral(
"description" )].toString();
731 if ( label.contains( QLatin1String(
"\\" ) ) || label.contains(
'/' ) )
733 skippedExpressionLabels.append( expressionObj[QStringLiteral(
"name" )].toString() );
738 const QString oldExpression = settings.
value( QStringLiteral(
"expression" ) ).toString();
742 if ( mUserExpressionLabels.contains( label ) && expression != oldExpression )
744 if ( ! isApplyToAll )
745 showMessageBoxConfirmExpressionOverwrite( isApplyToAll, isOkToOverwrite, label, oldExpression, expression );
747 if ( isOkToOverwrite )
751 skippedExpressionLabels.append( label );
763 if ( ! skippedExpressionLabels.isEmpty() )
765 QStringList skippedExpressionLabelsQuoted;
766 skippedExpressionLabelsQuoted.reserve( skippedExpressionLabels.size() );
767 for (
const QString &skippedExpressionLabel : skippedExpressionLabels )
768 skippedExpressionLabelsQuoted.append( QStringLiteral(
"'%1'" ).arg( skippedExpressionLabel ) );
770 QMessageBox::information(
this,
771 tr(
"Skipped Expression Imports" ),
772 QStringLiteral(
"%1\n%2" ).arg( tr(
"The following expressions have been skipped:" ),
773 skippedExpressionLabelsQuoted.join( QLatin1String(
", " ) ) ) );
779 QList<QgsExpressionItem *> result;
780 const QList<QStandardItem *> found { mModel->findItems( label, Qt::MatchFlag::MatchRecursive ) };
781 result.reserve( found.size() );
782 std::transform( found.begin(), found.end(), std::back_inserter( result ),
783 []( QStandardItem * item ) ->
QgsExpressionItem* { return static_cast<QgsExpressionItem *>( item ); } );
787 void QgsExpressionTreeView::showMessageBoxConfirmExpressionOverwrite(
789 bool &isOkToOverwrite,
790 const QString &label,
791 const QString &oldExpression,
792 const QString &newExpression )
794 QMessageBox::StandardButtons buttons = QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll;
795 switch ( QMessageBox::question(
this,
796 tr(
"Expression Overwrite" ),
797 tr(
"The expression with label '%1' was already defined."
798 "The old expression \"%2\" will be overwritten by \"%3\"."
799 "Are you sure you want to overwrite the expression?" ).arg( label, oldExpression, newExpression ), buttons ) )
801 case QMessageBox::NoToAll:
803 isOkToOverwrite =
false;
806 case QMessageBox::No:
807 isApplyToAll =
false;
808 isOkToOverwrite =
false;
811 case QMessageBox::YesToAll:
813 isOkToOverwrite =
true;
816 case QMessageBox::Yes:
817 isApplyToAll =
false;
818 isOkToOverwrite =
true;
835 setFilterCaseSensitivity( Qt::CaseInsensitive );
840 QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
846 int count = sourceModel()->rowCount( index );
847 bool matchchild =
false;
848 for (
int i = 0; i < count; ++i )
860 const QString name = sourceModel()->data( index, Qt::DisplayRole ).toString();
861 if ( name.contains( mFilterString, Qt::CaseInsensitive ) )
867 return std::any_of( tags.begin(), tags.end(), [
this](
const QString & tag )
869 return tag.contains( mFilterString, Qt::CaseInsensitive );
875 mFilterString = string;
883 if ( leftSort != rightSort )
884 return leftSort < rightSort;
886 QString leftString = sourceModel()->data( left, Qt::DisplayRole ).toString();
887 QString rightString = sourceModel()->data( right, Qt::DisplayRole ).toString();
890 if ( leftString.startsWith(
'$' ) )
891 leftString = leftString.mid( 1 );
892 if ( rightString.startsWith(
'$' ) )
893 rightString = rightString.mid( 1 );
895 return QString::localeAwareCompare( leftString, rightString ) < 0;