39#include "moc_qgsvariableeditorwidget.cpp"
41using namespace Qt::StringLiterals;
50 QVBoxLayout *verticalLayout =
new QVBoxLayout(
this );
51 verticalLayout->setSpacing( 3 );
52 verticalLayout->setContentsMargins( 3, 3, 3, 3 );
53 mTreeWidget =
new QgsVariableEditorTree(
this );
54 mTreeWidget->setSelectionMode( QAbstractItemView::SingleSelection );
55 verticalLayout->addWidget( mTreeWidget );
56 QHBoxLayout *horizontalLayout =
new QHBoxLayout();
57 horizontalLayout->setSpacing( 6 );
58 QSpacerItem *horizontalSpacer =
new QSpacerItem( 40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum );
59 horizontalLayout->addItem( horizontalSpacer );
60 mAddButton =
new QPushButton();
62 mAddButton->setEnabled(
false );
63 mAddButton->setToolTip( tr(
"Add variable" ) );
64 horizontalLayout->addWidget( mAddButton );
65 mRemoveButton =
new QPushButton();
67 mRemoveButton->setEnabled(
false );
68 mRemoveButton->setToolTip( tr(
"Remove variable" ) );
69 horizontalLayout->addWidget( mRemoveButton );
70 verticalLayout->addLayout( horizontalLayout );
71 connect( mRemoveButton, &QAbstractButton::clicked,
this, &QgsVariableEditorWidget::mRemoveButton_clicked );
72 connect( mAddButton, &QAbstractButton::clicked,
this, &QgsVariableEditorWidget::mAddButton_clicked );
73 connect( mTreeWidget, &QTreeWidget::itemSelectionChanged,
this, &QgsVariableEditorWidget::selectionChanged );
85 settings.
setValue( saveKey() +
"column0width", mTreeWidget->header()->sectionSize( 0 ) );
100 val = settings.
value( saveKey() +
"column0width" );
102 const int sectionSize = val.toInt( &ok );
105 mTreeWidget->header()->resizeSection( 0, sectionSize );
109 QWidget::showEvent( event );
114 mContext = std::make_unique<QgsExpressionContext>( *
context );
120 mTreeWidget->resetTree();
121 mTreeWidget->setContext( mContext.get() );
122 mTreeWidget->refreshTree();
127 mEditableScopeIndex = scopeIndex;
128 if ( mEditableScopeIndex >= 0 )
130 mAddButton->setEnabled(
true );
132 mTreeWidget->setEditableScopeIndex( scopeIndex );
133 mTreeWidget->refreshTree();
138 if ( !mContext || mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
142 return mContext->scope( mEditableScopeIndex );
147 QVariantMap variables;
148 if ( !mContext || mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
155 for (
const QString &variable : constVariableNames )
160 variables.insert( variable, scope->
variable( variable ) );
166QString QgsVariableEditorWidget::saveKey()
const
170 const QString setGroup = mSettingGroup.isEmpty() ? objectName() : mSettingGroup;
171 QString saveKey =
"/QgsVariableEditorTree/" + setGroup +
'/';
175void QgsVariableEditorWidget::mAddButton_clicked()
177 if ( mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
180 QgsExpressionContextScope *scope = mContext->scope( mEditableScopeIndex );
181 scope->
setVariable( u
"new_variable"_s, QVariant() );
182 mTreeWidget->refreshTree();
183 QTreeWidgetItem *item = mTreeWidget->itemFromVariable( scope, u
"new_variable"_s );
184 const QModelIndex index = mTreeWidget->itemToIndex( item );
185 mTreeWidget->selectionModel()->select( index, QItemSelectionModel::ClearAndSelect );
186 mTreeWidget->editItem( item, 0 );
191void QgsVariableEditorWidget::mRemoveButton_clicked()
193 if ( mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
196 QgsExpressionContextScope *
editableScope = mContext->scope( mEditableScopeIndex );
197 const QList<QTreeWidgetItem *> selectedItems = mTreeWidget->selectedItems();
199 const auto constSelectedItems = selectedItems;
200 for ( QTreeWidgetItem *item : constSelectedItems )
202 if ( !( item->flags() & Qt::ItemIsEditable ) )
205 const QString name = item->text( 0 );
206 QgsExpressionContextScope *itemScope = mTreeWidget->scopeFromItem( item );
214 mTreeWidget->removeItem( item );
216 mTreeWidget->refreshTree();
219void QgsVariableEditorWidget::selectionChanged()
221 if ( mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
223 mRemoveButton->setEnabled(
false );
227 QgsExpressionContextScope *
editableScope = mContext->scope( mEditableScopeIndex );
228 const QList<QTreeWidgetItem *> selectedItems = mTreeWidget->selectedItems();
230 bool removeEnabled =
true;
231 const auto constSelectedItems = selectedItems;
232 for ( QTreeWidgetItem *item : constSelectedItems )
234 if ( !( item->flags() & Qt::ItemIsEditable ) )
236 removeEnabled =
false;
240 const QString name = item->text( 0 );
241 QgsExpressionContextScope *itemScope = mTreeWidget->scopeFromItem( item );
244 removeEnabled =
false;
250 removeEnabled =
false;
254 mRemoveButton->setEnabled( removeEnabled );
263QgsVariableEditorTree::QgsVariableEditorTree( QWidget *parent )
264 : QTreeWidget( parent )
267 if ( mExpandIcon.isNull() )
269 QPixmap pix( 14, 14 );
270 pix.fill( Qt::transparent );
271 mExpandIcon.addPixmap( QgsApplication::getThemeIcon( u
"/mIconExpandSmall.svg"_s ).pixmap( 14, 14 ), QIcon::Normal, QIcon::Off );
272 mExpandIcon.addPixmap( QgsApplication::getThemeIcon( u
"/mIconExpandSmall.svg"_s ).pixmap( 14, 14 ), QIcon::Selected, QIcon::Off );
273 mExpandIcon.addPixmap( QgsApplication::getThemeIcon( u
"/mIconCollapseSmall.svg"_s ).pixmap( 14, 14 ), QIcon::Normal, QIcon::On );
274 mExpandIcon.addPixmap( QgsApplication::getThemeIcon( u
"/mIconCollapseSmall.svg"_s ).pixmap( 14, 14 ), QIcon::Selected, QIcon::On );
277 setIconSize( QSize( 18, 18 ) );
279 setHeaderLabels( QStringList() << tr(
"Variable" ) << tr(
"Value" ) );
280 setEditTriggers( QAbstractItemView::AllEditTriggers );
281 setRootIsDecorated(
false );
282 header()->setSectionsMovable(
false );
283 header()->setSectionResizeMode( QHeaderView::Interactive );
285 mEditorDelegate =
new VariableEditorDelegate(
this,
this );
286 setItemDelegate( mEditorDelegate );
295 const int contextIndex = item->data( 0, ContextIndex ).toInt( &ok );
303 else if ( mContext->scopeCount() > contextIndex )
305 return mContext->scope( contextIndex );
315 const int contextIndex = mContext ? mContext->indexOfScope( scope ) : 0;
316 if ( contextIndex < 0 )
318 return mVariableToItem.value( qMakePair( contextIndex, name ) );
323 if ( !mContext || mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
328 return mContext->scope( mEditableScopeIndex );
331void QgsVariableEditorTree::refreshTree()
333 if ( !mContext || mEditableScopeIndex < 0 )
341 const auto constScopes = mContext->scopes();
344 refreshScopeItems( scope, scopeIndex );
351 const QColor baseColor = rowColor( scopeIndex );
352 const bool isCurrent = scopeIndex == mEditableScopeIndex;
353 QTreeWidgetItem *scopeItem = mScopeToItem.value( scopeIndex );
356 for (
const QString &name : names )
358 QTreeWidgetItem *item = mVariableToItem.value( qMakePair( scopeIndex, name ) );
361 item =
new QTreeWidgetItem( scopeItem );
362 mVariableToItem.insert( qMakePair( scopeIndex, name ), item );
365 const bool readOnly = scope->
isReadOnly( name );
366 bool isActive =
true;
370 activeScope = mContext->activeScopeForVariable( name );
371 isActive = activeScope == scope;
374 item->setFlags( item->flags() | Qt::ItemIsEnabled );
375 item->setText( 0, name );
376 const QVariant value = scope->
variable( name );
378 item->setText( 1, previewString );
379 QFont font = item->font( 0 );
380 if ( readOnly || !isCurrent )
382 font.setItalic(
true );
383 item->setFlags( item->flags() ^ Qt::ItemIsEditable );
387 font.setItalic(
false );
388 item->setFlags( item->flags() | Qt::ItemIsEditable );
393 font.setStrikeOut(
true );
394 const QString toolTip = tr(
"Overridden by value from %1" ).arg( activeScope->
name() );
395 item->setToolTip( 0, toolTip );
396 item->setToolTip( 1, toolTip );
400 font.setStrikeOut(
false );
401 item->setToolTip( 0, name );
402 item->setToolTip( 1, previewString );
404 item->setFont( 0, font );
405 item->setFont( 1, font );
406 item->setData( 0, RowBaseColor, baseColor );
407 item->setData( 0, ContextIndex, scopeIndex );
408 item->setFirstColumnSpanned(
false );
417 const bool isCurrent = scopeIndex == mEditableScopeIndex;
419 QTreeWidgetItem *scopeItem =
nullptr;
420 if ( mScopeToItem.contains( scopeIndex ) )
423 scopeItem = mScopeToItem.value( scopeIndex );
428 scopeItem =
new QTreeWidgetItem();
429 mScopeToItem.insert( scopeIndex, scopeItem );
430 scopeItem->setFlags( scopeItem->flags() | Qt::ItemIsEnabled );
431 scopeItem->setText( 0, scope->
name() );
432 scopeItem->setFlags( scopeItem->flags() ^ Qt::ItemIsEditable );
433 scopeItem->setFirstColumnSpanned(
true );
434 QFont scopeFont = scopeItem->font( 0 );
435 scopeFont.setBold(
true );
436 scopeItem->setFont( 0, scopeFont );
437 scopeItem->setFirstColumnSpanned(
true );
439 addTopLevelItem( scopeItem );
442 if ( isCurrent || settings.
value(
"QgsVariableEditor/" + scopeItem->text( 0 ) +
"/expanded" ).toBool() )
443 scopeItem->setExpanded(
true );
445 scopeItem->setIcon( 0, mExpandIcon );
448 refreshScopeVariables( scope, scopeIndex );
451void QgsVariableEditorTree::removeItem( QTreeWidgetItem *item )
456 mVariableToItem.remove( mVariableToItem.key( item ) );
457 item->parent()->takeChild( item->parent()->indexOfChild( item ) );
462void QgsVariableEditorTree::renameItem( QTreeWidgetItem *item,
const QString &name )
467 const int contextIndex = mVariableToItem.key( item ).first;
468 mVariableToItem.remove( mVariableToItem.key( item ) );
469 mVariableToItem.insert( qMakePair( contextIndex, name ), item );
470 item->setText( 0, name );
475void QgsVariableEditorTree::resetTree()
477 mVariableToItem.clear();
478 mScopeToItem.clear();
482void QgsVariableEditorTree::emitChanged()
487void QgsVariableEditorTree::drawRow( QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
489 QStyleOptionViewItem opt = option;
490 QTreeWidgetItem *item = itemFromIndex( index );
491 if ( index.parent().isValid() )
494 QColor baseColor = item->data( 0, RowBaseColor ).value<QColor>();
495 if ( index.row() % 2 == 1 )
497 baseColor.setAlpha( 59 );
499 painter->fillRect( option.rect, baseColor );
501 QTreeWidget::drawRow( painter, opt, index );
502 const QColor color =
static_cast<QRgb
>( QApplication::style()->styleHint( QStyle::SH_Table_GridLineColor, &opt ) );
504 painter->setPen( QPen( color ) );
505 painter->drawLine( opt.rect.x(), opt.rect.bottom(), opt.rect.right(), opt.rect.bottom() );
508QColor QgsVariableEditorTree::rowColor(
int index )
const
511 const int colorIdx = index % 6;
515 return QColor( 255, 163, 0, 89 );
517 return QColor( 255, 255, 77, 89 );
519 return QColor( 0, 255, 77, 89 );
521 return QColor( 0, 255, 255, 89 );
523 return QColor( 196, 125, 255, 89 );
526 return QColor( 255, 125, 225, 89 );
530void QgsVariableEditorTree::toggleContextExpanded( QTreeWidgetItem *item )
535 item->setExpanded( !item->isExpanded() );
539 settings.
setValue(
"QgsVariableEditor/" + item->text( 0 ) +
"/expanded", item->isExpanded() );
542void QgsVariableEditorTree::editNext(
const QModelIndex &index )
544 if ( !index.isValid() )
547 if ( index.column() == 0 )
550 const QModelIndex nextIndex = index.sibling( index.row(), 1 );
551 if ( nextIndex.isValid() )
553 setCurrentIndex( nextIndex );
559 const QModelIndex nextIndex = model()->index( index.row() + 1, 0, index.parent() );
560 if ( nextIndex.isValid() )
563 setCurrentIndex( nextIndex );
573QModelIndex QgsVariableEditorTree::moveCursor( QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers )
575 if ( cursorAction == QAbstractItemView::MoveNext )
577 const QModelIndex index = currentIndex();
578 if ( index.isValid() )
580 if ( index.column() + 1 < model()->columnCount() )
581 return index.sibling( index.row(), index.column() + 1 );
582 else if ( index.row() + 1 < model()->rowCount( index.parent() ) )
583 return index.sibling( index.row() + 1, 0 );
585 return QModelIndex();
588 else if ( cursorAction == QAbstractItemView::MovePrevious )
590 const QModelIndex index = currentIndex();
591 if ( index.isValid() )
593 if ( index.column() >= 1 )
594 return index.sibling( index.row(), index.column() - 1 );
595 else if ( index.row() >= 1 )
596 return index.sibling( index.row() - 1, model()->columnCount() - 1 );
598 return QModelIndex();
602 return QTreeWidget::moveCursor( cursorAction, modifiers );
605void QgsVariableEditorTree::keyPressEvent( QKeyEvent *event )
607 switch ( event->key() )
613 QTreeWidgetItem *item = currentItem();
614 if ( item && !item->parent() )
617 toggleContextExpanded( item );
620 else if ( item && ( item->flags() & Qt::ItemIsEditable ) )
623 editNext( currentIndex() );
632 if ( event == QKeySequence::Copy )
634 const QList<QTreeWidgetItem *> selected = selectedItems();
635 if ( selected.size() > 0 )
637 QString text = selected.at( 0 )->text( 0 );
638 const QString varName = variableNameFromItem( selected.at( 0 ) );
640 if ( !varName.isEmpty() && scope )
641 text = scope->
variable( varName ).toString();
643 QClipboard *clipboard = QApplication::clipboard();
644 clipboard->setText( text );
650 QTreeWidget::keyPressEvent( event );
653void QgsVariableEditorTree::mousePressEvent( QMouseEvent *event )
655 QTreeWidget::mousePressEvent( event );
656 QTreeWidgetItem *item = itemAt( event->pos() );
660 if ( item->parent() )
666 if ( event->pos().x() + header()->offset() > 20 )
672 if ( event->modifiers() & Qt::ShiftModifier )
675 if ( !item->isExpanded() )
686 toggleContextExpanded( item );
694QWidget *VariableEditorDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &,
const QModelIndex &index )
const
700 if ( !index.parent().isValid() )
703 QTreeWidgetItem *item = mParentTree->indexToItem( index );
705 if ( !item || !scope )
708 const QString variableName = mParentTree->variableNameFromIndex( index );
711 if ( scope != mParentTree->editableScope() || scope->
isReadOnly( variableName ) )
714 QLineEdit *lineEdit =
new QLineEdit( parent );
715 lineEdit->setText( index.column() == 0 ? variableName : mParentTree->editableScope()->variable( variableName ).toString() );
716 lineEdit->setAutoFillBackground(
true );
720void VariableEditorDelegate::updateEditorGeometry( QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex & )
const
722 editor->setGeometry( option.rect.adjusted( 0, 0, 0, -1 ) );
725QSize VariableEditorDelegate::sizeHint(
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
727 return QItemDelegate::sizeHint( option, index ) + QSize( 3, 4 );
730void VariableEditorDelegate::setModelData( QWidget *widget, QAbstractItemModel *model,
const QModelIndex &index )
const
737 QTreeWidgetItem *item = mParentTree->indexToItem( index );
739 if ( !item || !scope )
742 QLineEdit *lineEdit = qobject_cast<QLineEdit *>( widget );
746 const QString variableName = mParentTree->variableNameFromIndex( index );
747 if ( index.column() == 0 )
750 QString newName = lineEdit->text();
751 newName = newName.trimmed();
752 newName = newName.replace(
' ',
'_' );
755 if ( newName == variableName )
762 QMessageBox::warning( mParentTree, tr(
"Rename Variable" ), tr(
"A variable with the name \"%1\" already exists in this context." ).arg( newName ) );
763 newName.append(
"_1" );
766 const QString value = scope->
variable( variableName ).toString();
767 mParentTree->renameItem( item, newName );
770 mParentTree->emitChanged();
772 else if ( index.column() == 1 )
775 const QString value = lineEdit->text();
776 if ( scope->
variable( variableName ).toString() == value )
781 mParentTree->emitChanged();
783 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.