23 #include <QVBoxLayout>
24 #include <QTreeWidget>
27 #include <QMouseEvent>
29 #include <QPushButton>
30 #include <QHeaderView>
31 #include <QMessageBox>
41 QVBoxLayout *verticalLayout =
new QVBoxLayout(
this );
42 verticalLayout->setSpacing( 3 );
43 verticalLayout->setContentsMargins( 3, 3, 3, 3 );
44 mTreeWidget =
new QgsVariableEditorTree(
this );
45 mTreeWidget->setSelectionMode( QAbstractItemView::SingleSelection );
46 verticalLayout->addWidget( mTreeWidget );
47 QHBoxLayout *horizontalLayout =
new QHBoxLayout();
48 horizontalLayout->setSpacing( 6 );
49 QSpacerItem *horizontalSpacer =
new QSpacerItem( 40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum );
50 horizontalLayout->addItem( horizontalSpacer );
51 mAddButton =
new QPushButton();
53 mAddButton->setEnabled(
false );
54 mAddButton->setToolTip( tr(
"Add variable" ) );
55 horizontalLayout->addWidget( mAddButton );
56 mRemoveButton =
new QPushButton();
58 mRemoveButton->setEnabled(
false );
59 mRemoveButton->setToolTip( tr(
"Remove variable" ) );
60 horizontalLayout->addWidget( mRemoveButton );
61 verticalLayout->addLayout( horizontalLayout );
62 connect( mRemoveButton, &QAbstractButton::clicked,
this, &QgsVariableEditorWidget::mRemoveButton_clicked );
63 connect( mAddButton, &QAbstractButton::clicked,
this, &QgsVariableEditorWidget::mAddButton_clicked );
64 connect( mTreeWidget, &QTreeWidget::itemSelectionChanged,
this, &QgsVariableEditorWidget::selectionChanged );
76 settings.
setValue( saveKey() +
"column0width", mTreeWidget->header()->sectionSize( 0 ) );
91 val = settings.
value( saveKey() +
"column0width" );
93 const int sectionSize = val.toInt( &ok );
96 mTreeWidget->header()->resizeSection( 0, sectionSize );
100 QWidget::showEvent( event );
111 mTreeWidget->resetTree();
112 mTreeWidget->setContext( mContext.get() );
113 mTreeWidget->refreshTree();
118 mEditableScopeIndex = scopeIndex;
119 if ( mEditableScopeIndex >= 0 )
121 mAddButton->setEnabled(
true );
123 mTreeWidget->setEditableScopeIndex( scopeIndex );
124 mTreeWidget->refreshTree();
129 if ( !mContext || mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
133 return mContext->scope( mEditableScopeIndex );
138 QVariantMap variables;
139 if ( !mContext || mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
146 for (
const QString &variable : constVariableNames )
151 variables.insert( variable, scope->
variable( variable ) );
157 QString QgsVariableEditorWidget::saveKey()
const
161 const QString setGroup = mSettingGroup.isEmpty() ? objectName() : mSettingGroup;
162 QString saveKey =
"/QgsVariableEditorTree/" + setGroup +
'/';
166 void QgsVariableEditorWidget::mAddButton_clicked()
168 if ( mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
172 scope->
setVariable( QStringLiteral(
"new_variable" ), QVariant() );
173 mTreeWidget->refreshTree();
174 QTreeWidgetItem *item = mTreeWidget->itemFromVariable( scope, QStringLiteral(
"new_variable" ) );
175 const QModelIndex index = mTreeWidget->itemToIndex( item );
176 mTreeWidget->selectionModel()->select( index, QItemSelectionModel::ClearAndSelect );
177 mTreeWidget->editItem( item, 0 );
182 void QgsVariableEditorWidget::mRemoveButton_clicked()
184 if ( mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
188 const QList<QTreeWidgetItem *> selectedItems = mTreeWidget->selectedItems();
190 const auto constSelectedItems = selectedItems;
191 for ( QTreeWidgetItem *item : constSelectedItems )
193 if ( !( item->flags() & Qt::ItemIsEditable ) )
196 const QString name = item->text( 0 );
205 mTreeWidget->removeItem( item );
207 mTreeWidget->refreshTree();
210 void QgsVariableEditorWidget::selectionChanged()
212 if ( mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
214 mRemoveButton->setEnabled(
false );
219 const QList<QTreeWidgetItem *> selectedItems = mTreeWidget->selectedItems();
221 bool removeEnabled =
true;
222 const auto constSelectedItems = selectedItems;
223 for ( QTreeWidgetItem *item : constSelectedItems )
225 if ( !( item->flags() & Qt::ItemIsEditable ) )
227 removeEnabled =
false;
231 const QString name = item->text( 0 );
235 removeEnabled =
false;
241 removeEnabled =
false;
245 mRemoveButton->setEnabled( removeEnabled );
254 QgsVariableEditorTree::QgsVariableEditorTree( QWidget *parent )
255 : QTreeWidget( parent )
258 if ( mExpandIcon.isNull() )
260 QPixmap pix( 14, 14 );
261 pix.fill( Qt::transparent );
262 mExpandIcon.addPixmap(
QgsApplication::getThemeIcon( QStringLiteral(
"/mIconExpandSmall.svg" ) ).pixmap( 14, 14 ), QIcon::Normal, QIcon::Off );
263 mExpandIcon.addPixmap(
QgsApplication::getThemeIcon( QStringLiteral(
"/mIconExpandSmall.svg" ) ).pixmap( 14, 14 ), QIcon::Selected, QIcon::Off );
264 mExpandIcon.addPixmap(
QgsApplication::getThemeIcon( QStringLiteral(
"/mIconCollapseSmall.svg" ) ).pixmap( 14, 14 ), QIcon::Normal, QIcon::On );
265 mExpandIcon.addPixmap(
QgsApplication::getThemeIcon( QStringLiteral(
"/mIconCollapseSmall.svg" ) ).pixmap( 14, 14 ), QIcon::Selected, QIcon::On );
268 setIconSize( QSize( 18, 18 ) );
270 setHeaderLabels( QStringList() << tr(
"Variable" ) << tr(
"Value" ) );
271 setEditTriggers( QAbstractItemView::AllEditTriggers );
272 setRootIsDecorated(
false );
273 header()->setSectionsMovable(
false );
274 header()->setSectionResizeMode( QHeaderView::Interactive );
276 mEditorDelegate =
new VariableEditorDelegate(
this,
this );
277 setItemDelegate( mEditorDelegate );
286 const int contextIndex = item->data( 0, ContextIndex ).toInt( &ok );
294 else if ( mContext->scopeCount() > contextIndex )
296 return mContext->scope( contextIndex );
306 const int contextIndex = mContext ? mContext->indexOfScope( scope ) : 0;
307 if ( contextIndex < 0 )
309 return mVariableToItem.value( qMakePair( contextIndex, name ) );
314 if ( !mContext || mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
319 return mContext->scope( mEditableScopeIndex );
322 void QgsVariableEditorTree::refreshTree()
324 if ( !mContext || mEditableScopeIndex < 0 )
332 const auto constScopes = mContext->scopes();
335 refreshScopeItems( scope, scopeIndex );
342 const QColor baseColor = rowColor( scopeIndex );
343 const bool isCurrent = scopeIndex == mEditableScopeIndex;
344 QTreeWidgetItem *scopeItem = mScopeToItem.value( scopeIndex );
347 for (
const QString &name : names )
349 QTreeWidgetItem *item = mVariableToItem.value( qMakePair( scopeIndex, name ) );
352 item =
new QTreeWidgetItem( scopeItem );
353 mVariableToItem.insert( qMakePair( scopeIndex, name ), item );
356 const bool readOnly = scope->
isReadOnly( name );
357 bool isActive =
true;
361 activeScope = mContext->activeScopeForVariable( name );
362 isActive = activeScope == scope;
365 item->setFlags( item->flags() | Qt::ItemIsEnabled );
366 item->setText( 0, name );
367 const QVariant value = scope->
variable( name );
369 item->setText( 1, previewString );
370 QFont font = item->font( 0 );
371 if ( readOnly || !isCurrent )
373 font.setItalic(
true );
374 item->setFlags( item->flags() ^ Qt::ItemIsEditable );
378 font.setItalic(
false );
379 item->setFlags( item->flags() | Qt::ItemIsEditable );
384 font.setStrikeOut(
true );
385 const QString toolTip = tr(
"Overridden by value from %1" ).arg( activeScope->
name() );
386 item->setToolTip( 0, toolTip );
387 item->setToolTip( 1, toolTip );
391 font.setStrikeOut(
false );
392 item->setToolTip( 0, name );
393 item->setToolTip( 1, previewString );
395 item->setFont( 0, font );
396 item->setFont( 1, font );
397 item->setData( 0, RowBaseColor, baseColor );
398 item->setData( 0, ContextIndex, scopeIndex );
399 item->setFirstColumnSpanned(
false );
408 const bool isCurrent = scopeIndex == mEditableScopeIndex;
410 QTreeWidgetItem *scopeItem =
nullptr;
411 if ( mScopeToItem.contains( scopeIndex ) )
414 scopeItem = mScopeToItem.
value( scopeIndex );
419 scopeItem =
new QTreeWidgetItem();
420 mScopeToItem.insert( scopeIndex, scopeItem );
421 scopeItem->setFlags( scopeItem->flags() | Qt::ItemIsEnabled );
422 scopeItem->setText( 0, scope->
name() );
423 scopeItem->setFlags( scopeItem->flags() ^ Qt::ItemIsEditable );
424 scopeItem->setFirstColumnSpanned(
true );
425 QFont scopeFont = scopeItem->font( 0 );
426 scopeFont .setBold(
true );
427 scopeItem->setFont( 0, scopeFont );
428 scopeItem->setFirstColumnSpanned(
true );
430 addTopLevelItem( scopeItem );
433 if ( isCurrent || settings.
value(
"QgsVariableEditor/" + scopeItem->text( 0 ) +
"/expanded" ).toBool() )
434 scopeItem->setExpanded(
true );
436 scopeItem->setIcon( 0, mExpandIcon );
439 refreshScopeVariables( scope, scopeIndex );
442 void QgsVariableEditorTree::removeItem( QTreeWidgetItem *item )
447 mVariableToItem.remove( mVariableToItem.key( item ) );
448 item->parent()->takeChild( item->parent()->indexOfChild( item ) );
453 void QgsVariableEditorTree::renameItem( QTreeWidgetItem *item,
const QString &name )
458 const int contextIndex = mVariableToItem.key( item ).first;
459 mVariableToItem.remove( mVariableToItem.key( item ) );
460 mVariableToItem.insert( qMakePair( contextIndex, name ), item );
461 item->setText( 0, name );
466 void QgsVariableEditorTree::resetTree()
468 mVariableToItem.clear();
469 mScopeToItem.clear();
473 void QgsVariableEditorTree::emitChanged()
478 void QgsVariableEditorTree::drawRow( QPainter *painter,
const QStyleOptionViewItem &option,
479 const QModelIndex &index )
const
481 QStyleOptionViewItem opt = option;
482 QTreeWidgetItem *item = itemFromIndex( index );
483 if ( index.parent().isValid() )
486 QColor baseColor = item->data( 0, RowBaseColor ).value<QColor>();
487 if ( index.row() % 2 == 1 )
489 baseColor.setAlpha( 59 );
491 painter->fillRect( option.rect, baseColor );
493 QTreeWidget::drawRow( painter, opt, index );
494 const QColor color =
static_cast<QRgb
>( QApplication::style()->styleHint( QStyle::SH_Table_GridLineColor, &opt ) );
496 painter->setPen( QPen( color ) );
497 painter->drawLine( opt.rect.x(), opt.rect.bottom(), opt.rect.right(), opt.rect.bottom() );
500 QColor QgsVariableEditorTree::rowColor(
int index )
const
503 const int colorIdx = index % 6;
507 return QColor( 255, 163, 0, 89 );
509 return QColor( 255, 255, 77, 89 );
511 return QColor( 0, 255, 77, 89 );
513 return QColor( 0, 255, 255, 89 );
515 return QColor( 196, 125, 255, 89 );
518 return QColor( 255, 125, 225, 89 );
522 void QgsVariableEditorTree::toggleContextExpanded( QTreeWidgetItem *item )
527 item->setExpanded( !item->isExpanded() );
531 settings.
setValue(
"QgsVariableEditor/" + item->text( 0 ) +
"/expanded", item->isExpanded() );
534 void QgsVariableEditorTree::editNext(
const QModelIndex &index )
536 if ( !index.isValid() )
539 if ( index.column() == 0 )
542 const QModelIndex nextIndex = index.sibling( index.row(), 1 );
543 if ( nextIndex.isValid() )
545 setCurrentIndex( nextIndex );
551 const QModelIndex nextIndex = model()->index( index.row() + 1, 0, index.parent() );
552 if ( nextIndex.isValid() )
555 setCurrentIndex( nextIndex );
565 QModelIndex QgsVariableEditorTree::moveCursor( QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers )
567 if ( cursorAction == QAbstractItemView::MoveNext )
569 const QModelIndex index = currentIndex();
570 if ( index.isValid() )
572 if ( index.column() + 1 < model()->columnCount() )
573 return index.sibling( index.row(), index.column() + 1 );
574 else if ( index.row() + 1 < model()->rowCount( index.parent() ) )
575 return index.sibling( index.row() + 1, 0 );
577 return QModelIndex();
580 else if ( cursorAction == QAbstractItemView::MovePrevious )
582 const QModelIndex index = currentIndex();
583 if ( index.isValid() )
585 if ( index.column() >= 1 )
586 return index.sibling( index.row(), index.column() - 1 );
587 else if ( index.row() >= 1 )
588 return index.sibling( index.row() - 1, model()->columnCount() - 1 );
590 return QModelIndex();
594 return QTreeWidget::moveCursor( cursorAction, modifiers );
597 void QgsVariableEditorTree::keyPressEvent( QKeyEvent *event )
599 switch ( event->key() )
605 QTreeWidgetItem *item = currentItem();
606 if ( item && !item->parent() )
609 toggleContextExpanded( item );
612 else if ( item && ( item->flags() & Qt::ItemIsEditable ) )
615 editNext( currentIndex() );
624 if ( event == QKeySequence::Copy )
626 const QList<QTreeWidgetItem *> selected = selectedItems();
627 if ( selected.size() > 0 )
629 QString text = selected.at( 0 )->text( 0 );
630 const QString varName = variableNameFromItem( selected.at( 0 ) );
632 if ( !varName.isEmpty() && scope )
633 text = scope->
variable( varName ).toString();
635 QClipboard *clipboard = QApplication::clipboard();
636 clipboard->setText( text );
642 QTreeWidget::keyPressEvent( event );
645 void QgsVariableEditorTree::mousePressEvent( QMouseEvent *event )
647 QTreeWidget::mousePressEvent( event );
648 QTreeWidgetItem *item = itemAt( event->pos() );
652 if ( item->parent() )
658 if ( event->pos().x() + header()->offset() > 20 )
664 if ( event->modifiers() & Qt::ShiftModifier )
667 if ( !item->isExpanded() )
678 toggleContextExpanded( item );
686 QWidget *VariableEditorDelegate::createEditor( QWidget *parent,
687 const QStyleOptionViewItem &,
688 const QModelIndex &index )
const
694 if ( !index.parent().isValid() )
697 QTreeWidgetItem *item = mParentTree->indexToItem( index );
699 if ( !item || !scope )
702 const QString variableName = mParentTree->variableNameFromIndex( index );
705 if ( scope != mParentTree->editableScope() || scope->
isReadOnly( variableName ) )
708 QLineEdit *lineEdit =
new QLineEdit( parent );
709 lineEdit->setText( index.column() == 0 ? variableName : mParentTree->editableScope()->variable( variableName ).toString() );
710 lineEdit->setAutoFillBackground(
true );
714 void VariableEditorDelegate::updateEditorGeometry( QWidget *editor,
715 const QStyleOptionViewItem &option,
716 const QModelIndex & )
const
718 editor->setGeometry( option.rect.adjusted( 0, 0, 0, -1 ) );
721 QSize VariableEditorDelegate::sizeHint(
const QStyleOptionViewItem &option,
722 const QModelIndex &index )
const
724 return QItemDelegate::sizeHint( option, index ) + QSize( 3, 4 );
727 void VariableEditorDelegate::setModelData( QWidget *widget, QAbstractItemModel *model,
728 const QModelIndex &index )
const
735 QTreeWidgetItem *item = mParentTree->indexToItem( index );
737 if ( !item || !scope )
740 QLineEdit *lineEdit = qobject_cast< QLineEdit * >( widget );
744 const QString variableName = mParentTree->variableNameFromIndex( index );
745 if ( index.column() == 0 )
748 QString newName = lineEdit->text();
749 newName = newName.trimmed();
750 newName = newName.replace(
' ',
'_' );
753 if ( newName == variableName )
760 QMessageBox::warning( mParentTree, tr(
"Rename Variable" ), tr(
"A variable with the name \"%1\" already exists in this context." ).arg( newName ) );
761 newName.append(
"_1" );
764 const QString value = scope->
variable( variableName ).toString();
765 mParentTree->renameItem( item, newName );
768 mParentTree->emitChanged();
770 else if ( index.column() == 1 )
773 const QString value = lineEdit->text();
774 if ( scope->
variable( variableName ).toString() == value )
779 mParentTree->emitChanged();
781 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.
This class is a composition of two QSettings instances:
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.