38#include "moc_qgsvariableeditorwidget.cpp"
40using namespace Qt::StringLiterals;
49 QVBoxLayout *verticalLayout =
new QVBoxLayout(
this );
50 verticalLayout->setSpacing( 3 );
51 verticalLayout->setContentsMargins( 3, 3, 3, 3 );
52 mTreeWidget =
new QgsVariableEditorTree(
this );
53 mTreeWidget->setSelectionMode( QAbstractItemView::SingleSelection );
54 verticalLayout->addWidget( mTreeWidget );
55 QHBoxLayout *horizontalLayout =
new QHBoxLayout();
56 horizontalLayout->setSpacing( 6 );
57 QSpacerItem *horizontalSpacer =
new QSpacerItem( 40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum );
58 horizontalLayout->addItem( horizontalSpacer );
59 mAddButton =
new QPushButton();
61 mAddButton->setEnabled(
false );
62 mAddButton->setToolTip( tr(
"Add variable" ) );
63 horizontalLayout->addWidget( mAddButton );
64 mRemoveButton =
new QPushButton();
66 mRemoveButton->setEnabled(
false );
67 mRemoveButton->setToolTip( tr(
"Remove variable" ) );
68 horizontalLayout->addWidget( mRemoveButton );
69 verticalLayout->addLayout( horizontalLayout );
70 connect( mRemoveButton, &QAbstractButton::clicked,
this, &QgsVariableEditorWidget::mRemoveButton_clicked );
71 connect( mAddButton, &QAbstractButton::clicked,
this, &QgsVariableEditorWidget::mAddButton_clicked );
72 connect( mTreeWidget, &QTreeWidget::itemSelectionChanged,
this, &QgsVariableEditorWidget::selectionChanged );
84 settings.
setValue( saveKey() +
"column0width", mTreeWidget->header()->sectionSize( 0 ) );
99 val = settings.
value( saveKey() +
"column0width" );
101 const int sectionSize = val.toInt( &ok );
104 mTreeWidget->header()->resizeSection( 0, sectionSize );
108 QWidget::showEvent( event );
113 mContext = std::make_unique<QgsExpressionContext>( *
context );
119 mTreeWidget->resetTree();
120 mTreeWidget->setContext( mContext.get() );
121 mTreeWidget->refreshTree();
126 mEditableScopeIndex = scopeIndex;
127 if ( mEditableScopeIndex >= 0 )
129 mAddButton->setEnabled(
true );
131 mTreeWidget->setEditableScopeIndex( scopeIndex );
132 mTreeWidget->refreshTree();
137 if ( !mContext || mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
141 return mContext->scope( mEditableScopeIndex );
146 QVariantMap variables;
147 if ( !mContext || mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
154 for (
const QString &variable : constVariableNames )
159 variables.insert( variable, scope->
variable( variable ) );
165QString QgsVariableEditorWidget::saveKey()
const
169 const QString setGroup = mSettingGroup.isEmpty() ? objectName() : mSettingGroup;
170 QString saveKey =
"/QgsVariableEditorTree/" + setGroup +
'/';
174void QgsVariableEditorWidget::mAddButton_clicked()
176 if ( mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
179 QgsExpressionContextScope *scope = mContext->scope( mEditableScopeIndex );
180 scope->
setVariable( u
"new_variable"_s, QVariant() );
181 mTreeWidget->refreshTree();
182 QTreeWidgetItem *item = mTreeWidget->itemFromVariable( scope, u
"new_variable"_s );
183 const QModelIndex index = mTreeWidget->itemToIndex( item );
184 mTreeWidget->selectionModel()->select( index, QItemSelectionModel::ClearAndSelect );
185 mTreeWidget->editItem( item, 0 );
190void QgsVariableEditorWidget::mRemoveButton_clicked()
192 if ( mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
195 QgsExpressionContextScope *
editableScope = mContext->scope( mEditableScopeIndex );
196 const QList<QTreeWidgetItem *> selectedItems = mTreeWidget->selectedItems();
198 const auto constSelectedItems = selectedItems;
199 for ( QTreeWidgetItem *item : constSelectedItems )
201 if ( !( item->flags() & Qt::ItemIsEditable ) )
204 const QString name = item->text( 0 );
205 QgsExpressionContextScope *itemScope = mTreeWidget->scopeFromItem( item );
213 mTreeWidget->removeItem( item );
215 mTreeWidget->refreshTree();
218void QgsVariableEditorWidget::selectionChanged()
220 if ( mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
222 mRemoveButton->setEnabled(
false );
226 QgsExpressionContextScope *
editableScope = mContext->scope( mEditableScopeIndex );
227 const QList<QTreeWidgetItem *> selectedItems = mTreeWidget->selectedItems();
229 bool removeEnabled =
true;
230 const auto constSelectedItems = selectedItems;
231 for ( QTreeWidgetItem *item : constSelectedItems )
233 if ( !( item->flags() & Qt::ItemIsEditable ) )
235 removeEnabled =
false;
239 const QString name = item->text( 0 );
240 QgsExpressionContextScope *itemScope = mTreeWidget->scopeFromItem( item );
243 removeEnabled =
false;
249 removeEnabled =
false;
253 mRemoveButton->setEnabled( removeEnabled );
262QgsVariableEditorTree::QgsVariableEditorTree( QWidget *parent )
263 : QTreeWidget( parent )
266 if ( mExpandIcon.isNull() )
268 QPixmap pix( 14, 14 );
269 pix.fill( Qt::transparent );
270 mExpandIcon.addPixmap( QgsApplication::getThemeIcon( u
"/mIconExpandSmall.svg"_s ).pixmap( 14, 14 ), QIcon::Normal, QIcon::Off );
271 mExpandIcon.addPixmap( QgsApplication::getThemeIcon( u
"/mIconExpandSmall.svg"_s ).pixmap( 14, 14 ), QIcon::Selected, QIcon::Off );
272 mExpandIcon.addPixmap( QgsApplication::getThemeIcon( u
"/mIconCollapseSmall.svg"_s ).pixmap( 14, 14 ), QIcon::Normal, QIcon::On );
273 mExpandIcon.addPixmap( QgsApplication::getThemeIcon( u
"/mIconCollapseSmall.svg"_s ).pixmap( 14, 14 ), QIcon::Selected, QIcon::On );
276 setIconSize( QSize( 18, 18 ) );
278 setHeaderLabels( QStringList() << tr(
"Variable" ) << tr(
"Value" ) );
279 setEditTriggers( QAbstractItemView::AllEditTriggers );
280 setRootIsDecorated(
false );
281 header()->setSectionsMovable(
false );
282 header()->setSectionResizeMode( QHeaderView::Interactive );
284 mEditorDelegate =
new VariableEditorDelegate(
this,
this );
285 setItemDelegate( mEditorDelegate );
294 const int contextIndex = item->data( 0, ContextIndex ).toInt( &ok );
302 else if ( mContext->scopeCount() > contextIndex )
304 return mContext->scope( contextIndex );
314 const int contextIndex = mContext ? mContext->indexOfScope( scope ) : 0;
315 if ( contextIndex < 0 )
317 return mVariableToItem.value( qMakePair( contextIndex, name ) );
322 if ( !mContext || mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
327 return mContext->scope( mEditableScopeIndex );
330void QgsVariableEditorTree::refreshTree()
332 if ( !mContext || mEditableScopeIndex < 0 )
340 const auto constScopes = mContext->scopes();
343 refreshScopeItems( scope, scopeIndex );
350 const QColor baseColor = rowColor( scopeIndex );
351 const bool isCurrent = scopeIndex == mEditableScopeIndex;
352 QTreeWidgetItem *scopeItem = mScopeToItem.value( scopeIndex );
355 for (
const QString &name : names )
357 QTreeWidgetItem *item = mVariableToItem.value( qMakePair( scopeIndex, name ) );
360 item =
new QTreeWidgetItem( scopeItem );
361 mVariableToItem.insert( qMakePair( scopeIndex, name ), item );
364 const bool readOnly = scope->
isReadOnly( name );
365 bool isActive =
true;
369 activeScope = mContext->activeScopeForVariable( name );
370 isActive = activeScope == scope;
373 item->setFlags( item->flags() | Qt::ItemIsEnabled );
374 item->setText( 0, name );
375 const QVariant value = scope->
variable( name );
377 item->setText( 1, previewString );
378 QFont font = item->font( 0 );
379 if ( readOnly || !isCurrent )
381 font.setItalic(
true );
382 item->setFlags( item->flags() ^ Qt::ItemIsEditable );
386 font.setItalic(
false );
387 item->setFlags( item->flags() | Qt::ItemIsEditable );
392 font.setStrikeOut(
true );
393 const QString toolTip = tr(
"Overridden by value from %1" ).arg( activeScope->
name() );
394 item->setToolTip( 0, toolTip );
395 item->setToolTip( 1, toolTip );
399 font.setStrikeOut(
false );
400 item->setToolTip( 0, name );
401 item->setToolTip( 1, previewString );
403 item->setFont( 0, font );
404 item->setFont( 1, font );
405 item->setData( 0, RowBaseColor, baseColor );
406 item->setData( 0, ContextIndex, scopeIndex );
407 item->setFirstColumnSpanned(
false );
416 const bool isCurrent = scopeIndex == mEditableScopeIndex;
418 QTreeWidgetItem *scopeItem =
nullptr;
419 if ( mScopeToItem.contains( scopeIndex ) )
422 scopeItem = mScopeToItem.value( scopeIndex );
427 scopeItem =
new QTreeWidgetItem();
428 mScopeToItem.insert( scopeIndex, scopeItem );
429 scopeItem->setFlags( scopeItem->flags() | Qt::ItemIsEnabled );
430 scopeItem->setText( 0, scope->
name() );
431 scopeItem->setFlags( scopeItem->flags() ^ Qt::ItemIsEditable );
432 scopeItem->setFirstColumnSpanned(
true );
433 QFont scopeFont = scopeItem->font( 0 );
434 scopeFont.setBold(
true );
435 scopeItem->setFont( 0, scopeFont );
436 scopeItem->setFirstColumnSpanned(
true );
438 addTopLevelItem( scopeItem );
441 if ( isCurrent || settings.
value(
"QgsVariableEditor/" + scopeItem->text( 0 ) +
"/expanded" ).toBool() )
442 scopeItem->setExpanded(
true );
444 scopeItem->setIcon( 0, mExpandIcon );
447 refreshScopeVariables( scope, scopeIndex );
450void QgsVariableEditorTree::removeItem( QTreeWidgetItem *item )
455 mVariableToItem.remove( mVariableToItem.key( item ) );
456 item->parent()->takeChild( item->parent()->indexOfChild( item ) );
461void QgsVariableEditorTree::renameItem( QTreeWidgetItem *item,
const QString &name )
466 const int contextIndex = mVariableToItem.key( item ).first;
467 mVariableToItem.remove( mVariableToItem.key( item ) );
468 mVariableToItem.insert( qMakePair( contextIndex, name ), item );
469 item->setText( 0, name );
474void QgsVariableEditorTree::resetTree()
476 mVariableToItem.clear();
477 mScopeToItem.clear();
481void QgsVariableEditorTree::emitChanged()
486void QgsVariableEditorTree::drawRow( QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
488 QStyleOptionViewItem opt = option;
489 QTreeWidgetItem *item = itemFromIndex( index );
490 if ( index.parent().isValid() )
493 QColor baseColor = item->data( 0, RowBaseColor ).value<QColor>();
494 if ( index.row() % 2 == 1 )
496 baseColor.setAlpha( 59 );
498 painter->fillRect( option.rect, baseColor );
500 QTreeWidget::drawRow( painter, opt, index );
501 const QColor color =
static_cast<QRgb
>( QApplication::style()->styleHint( QStyle::SH_Table_GridLineColor, &opt ) );
503 painter->setPen( QPen( color ) );
504 painter->drawLine( opt.rect.x(), opt.rect.bottom(), opt.rect.right(), opt.rect.bottom() );
507QColor QgsVariableEditorTree::rowColor(
int index )
const
510 const int colorIdx = index % 6;
514 return QColor( 255, 163, 0, 89 );
516 return QColor( 255, 255, 77, 89 );
518 return QColor( 0, 255, 77, 89 );
520 return QColor( 0, 255, 255, 89 );
522 return QColor( 196, 125, 255, 89 );
525 return QColor( 255, 125, 225, 89 );
529void QgsVariableEditorTree::toggleContextExpanded( QTreeWidgetItem *item )
534 item->setExpanded( !item->isExpanded() );
538 settings.
setValue(
"QgsVariableEditor/" + item->text( 0 ) +
"/expanded", item->isExpanded() );
541void QgsVariableEditorTree::editNext(
const QModelIndex &index )
543 if ( !index.isValid() )
546 if ( index.column() == 0 )
549 const QModelIndex nextIndex = index.sibling( index.row(), 1 );
550 if ( nextIndex.isValid() )
552 setCurrentIndex( nextIndex );
558 const QModelIndex nextIndex = model()->index( index.row() + 1, 0, index.parent() );
559 if ( nextIndex.isValid() )
562 setCurrentIndex( nextIndex );
572QModelIndex QgsVariableEditorTree::moveCursor( QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers )
574 if ( cursorAction == QAbstractItemView::MoveNext )
576 const QModelIndex index = currentIndex();
577 if ( index.isValid() )
579 if ( index.column() + 1 < model()->columnCount() )
580 return index.sibling( index.row(), index.column() + 1 );
581 else if ( index.row() + 1 < model()->rowCount( index.parent() ) )
582 return index.sibling( index.row() + 1, 0 );
584 return QModelIndex();
587 else if ( cursorAction == QAbstractItemView::MovePrevious )
589 const QModelIndex index = currentIndex();
590 if ( index.isValid() )
592 if ( index.column() >= 1 )
593 return index.sibling( index.row(), index.column() - 1 );
594 else if ( index.row() >= 1 )
595 return index.sibling( index.row() - 1, model()->columnCount() - 1 );
597 return QModelIndex();
601 return QTreeWidget::moveCursor( cursorAction, modifiers );
604void QgsVariableEditorTree::keyPressEvent( QKeyEvent *event )
606 switch ( event->key() )
612 QTreeWidgetItem *item = currentItem();
613 if ( item && !item->parent() )
616 toggleContextExpanded( item );
619 else if ( item && ( item->flags() & Qt::ItemIsEditable ) )
622 editNext( currentIndex() );
631 if ( event == QKeySequence::Copy )
633 const QList<QTreeWidgetItem *> selected = selectedItems();
634 if ( selected.size() > 0 )
636 QString text = selected.at( 0 )->text( 0 );
637 const QString varName = variableNameFromItem( selected.at( 0 ) );
639 if ( !varName.isEmpty() && scope )
640 text = scope->
variable( varName ).toString();
642 QClipboard *clipboard = QApplication::clipboard();
643 clipboard->setText( text );
649 QTreeWidget::keyPressEvent( event );
652void QgsVariableEditorTree::mousePressEvent( QMouseEvent *event )
654 QTreeWidget::mousePressEvent( event );
655 QTreeWidgetItem *item = itemAt( event->pos() );
659 if ( item->parent() )
665 if ( event->pos().x() + header()->offset() > 20 )
671 if ( event->modifiers() & Qt::ShiftModifier )
674 if ( !item->isExpanded() )
685 toggleContextExpanded( item );
693QWidget *VariableEditorDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex &index )
const
699 if ( !index.parent().isValid() )
702 QTreeWidgetItem *item = mParentTree->indexToItem( index );
704 if ( !item || !scope )
707 const QString variableName = mParentTree->variableNameFromIndex( index );
710 if ( scope != mParentTree->editableScope() || scope->
isReadOnly( variableName ) )
713 QLineEdit *lineEdit =
new QLineEdit( parent );
714 lineEdit->setText( index.column() == 0 ? variableName : mParentTree->editableScope()->variable( variableName ).toString() );
715 lineEdit->setAutoFillBackground(
true );
719void VariableEditorDelegate::updateEditorGeometry( QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex & )
const
721 editor->setGeometry( option.rect.adjusted( 0, 0, 0, -1 ) );
724QSize VariableEditorDelegate::sizeHint(
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
726 return QItemDelegate::sizeHint( option, index ) + QSize( 3, 4 );
729void VariableEditorDelegate::setModelData( QWidget *widget, QAbstractItemModel *model,
const QModelIndex &index )
const
736 QTreeWidgetItem *item = mParentTree->indexToItem( index );
738 if ( !item || !scope )
741 QLineEdit *lineEdit = qobject_cast<QLineEdit *>( widget );
745 const QString variableName = mParentTree->variableNameFromIndex( index );
746 if ( index.column() == 0 )
749 QString newName = lineEdit->text();
750 newName = newName.trimmed();
751 newName = newName.replace(
' ',
'_' );
754 if ( newName == variableName )
761 QMessageBox::warning( mParentTree, tr(
"Rename Variable" ), tr(
"A variable with the name \"%1\" already exists in this context." ).arg( newName ) );
762 newName.append(
"_1" );
765 const QString value = scope->
variable( variableName ).toString();
766 mParentTree->renameItem( item, newName );
769 mParentTree->emitChanged();
771 else if ( index.column() == 1 )
774 const QString value = lineEdit->text();
775 if ( scope->
variable( variableName ).toString() == value )
780 mParentTree->emitChanged();
782 mParentTree->refreshTree();
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
Single scope for storing variables and functions for use within a QgsExpressionContext.
bool hasVariable(const QString &name) const
Tests whether a variable with the specified name exists in the scope.
QVariant variable(const QString &name) const
Retrieves a variable's value from the scope.
bool removeVariable(const QString &name)
Removes a variable from the context scope, if found.
bool isReadOnly(const QString &name) const
Tests whether the specified variable is read only and should not be editable by users.
QString name() const
Returns the friendly display name of the context scope.
QStringList filteredVariableNames() const
Returns a filtered and sorted list of variable names contained within the scope.
void setVariable(const QString &name, const QVariant &value, bool isStatic=false)
Convenience method for setting a variable in the context scope by name name and value.
QStringList variableNames() const
Returns a list of variable names contained within the scope.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
static QString formatPreviewString(const QVariant &value, bool htmlOutput=true, int maximumPreviewLength=60)
Formats an expression result for friendly display to the user.
Scoped object for saving and restoring a QPainter object's state.
Stores settings for use within QGIS.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.