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.