QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
qgsvariableeditorwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsvariableeditorwidget.cpp
3  ---------------------------
4  Date : April 2015
5  Copyright : (C) 2015 by Nyall Dawson
6  Email : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
17 #include "qgsexpressioncontext.h"
18 #include "qgsapplication.h"
19 #include "qgssettings.h"
20 #include "qgsexpression.h"
21 #include "qgsrendercontext.h"
22 
23 #include <QVBoxLayout>
24 #include <QTreeWidget>
25 #include <QPainter>
26 #include <QKeyEvent>
27 #include <QMouseEvent>
28 #include <QLineEdit>
29 #include <QPushButton>
30 #include <QHeaderView>
31 #include <QMessageBox>
32 #include <QClipboard>
33 
34 //
35 // QgsVariableEditorWidget
36 //
37 
39  : QWidget( parent )
40 {
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();
52  mAddButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/symbologyAdd.svg" ) ) );
53  mAddButton->setEnabled( false );
54  mAddButton->setToolTip( tr( "Add variable" ) );
55  horizontalLayout->addWidget( mAddButton );
56  mRemoveButton = new QPushButton();
57  mRemoveButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/symbologyRemove.svg" ) ) );
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 );
65  connect( mTreeWidget, &QgsVariableEditorTree::scopeChanged, this, &QgsVariableEditorWidget::scopeChanged );
66 
67  //setContext clones context
70  delete context;
71 }
72 
74 {
75  QgsSettings settings;
76  settings.setValue( saveKey() + "column0width", mTreeWidget->header()->sectionSize( 0 ) );
77 }
78 
79 void QgsVariableEditorWidget::showEvent( QShowEvent *event )
80 {
81  // initialize widget on first show event only
82  if ( mShown )
83  {
84  event->accept();
85  return;
86  }
87 
88  //restore split size
89  const QgsSettings settings;
90  QVariant val;
91  val = settings.value( saveKey() + "column0width" );
92  bool ok;
93  const int sectionSize = val.toInt( &ok );
94  if ( ok )
95  {
96  mTreeWidget->header()->resizeSection( 0, sectionSize );
97  }
98  mShown = true;
99 
100  QWidget::showEvent( event );
101 }
102 
104 {
105  mContext.reset( new QgsExpressionContext( *context ) );
106  reloadContext();
107 }
108 
110 {
111  mTreeWidget->resetTree();
112  mTreeWidget->setContext( mContext.get() );
113  mTreeWidget->refreshTree();
114 }
115 
117 {
118  mEditableScopeIndex = scopeIndex;
119  if ( mEditableScopeIndex >= 0 )
120  {
121  mAddButton->setEnabled( true );
122  }
123  mTreeWidget->setEditableScopeIndex( scopeIndex );
124  mTreeWidget->refreshTree();
125 }
126 
128 {
129  if ( !mContext || mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
130  {
131  return nullptr;
132  }
133  return mContext->scope( mEditableScopeIndex );
134 }
135 
137 {
138  QVariantMap variables;
139  if ( !mContext || mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
140  {
141  return variables;
142  }
143 
144  QgsExpressionContextScope *scope = mContext->scope( mEditableScopeIndex );
145  const auto constVariableNames = scope->variableNames();
146  for ( const QString &variable : constVariableNames )
147  {
148  if ( scope->isReadOnly( variable ) )
149  continue;
150 
151  variables.insert( variable, scope->variable( variable ) );
152  }
153 
154  return variables;
155 }
156 
157 QString QgsVariableEditorWidget::saveKey() const
158 {
159  // save key for load/save state
160  // currently QgsVariableEditorTree/window()/object
161  const QString setGroup = mSettingGroup.isEmpty() ? objectName() : mSettingGroup;
162  QString saveKey = "/QgsVariableEditorTree/" + setGroup + '/';
163  return saveKey;
164 }
165 
166 void QgsVariableEditorWidget::mAddButton_clicked()
167 {
168  if ( mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
169  return;
170 
171  QgsExpressionContextScope *scope = mContext->scope( mEditableScopeIndex );
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 );
178 
179  emit scopeChanged();
180 }
181 
182 void QgsVariableEditorWidget::mRemoveButton_clicked()
183 {
184  if ( mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
185  return;
186 
187  QgsExpressionContextScope *editableScope = mContext->scope( mEditableScopeIndex );
188  const QList<QTreeWidgetItem *> selectedItems = mTreeWidget->selectedItems();
189 
190  const auto constSelectedItems = selectedItems;
191  for ( QTreeWidgetItem *item : constSelectedItems )
192  {
193  if ( !( item->flags() & Qt::ItemIsEditable ) )
194  continue;
195 
196  const QString name = item->text( 0 );
197  QgsExpressionContextScope *itemScope = mTreeWidget->scopeFromItem( item );
198  if ( itemScope != editableScope )
199  continue;
200 
201  if ( itemScope->isReadOnly( name ) )
202  continue;
203 
204  itemScope->removeVariable( name );
205  mTreeWidget->removeItem( item );
206  }
207  mTreeWidget->refreshTree();
208 }
209 
210 void QgsVariableEditorWidget::selectionChanged()
211 {
212  if ( mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
213  {
214  mRemoveButton->setEnabled( false );
215  return;
216  }
217 
218  QgsExpressionContextScope *editableScope = mContext->scope( mEditableScopeIndex );
219  const QList<QTreeWidgetItem *> selectedItems = mTreeWidget->selectedItems();
220 
221  bool removeEnabled = true;
222  const auto constSelectedItems = selectedItems;
223  for ( QTreeWidgetItem *item : constSelectedItems )
224  {
225  if ( !( item->flags() & Qt::ItemIsEditable ) )
226  {
227  removeEnabled = false;
228  break;
229  }
230 
231  const QString name = item->text( 0 );
232  QgsExpressionContextScope *itemScope = mTreeWidget->scopeFromItem( item );
233  if ( itemScope != editableScope )
234  {
235  removeEnabled = false;
236  break;
237  }
238 
239  if ( editableScope->isReadOnly( name ) )
240  {
241  removeEnabled = false;
242  break;
243  }
244  }
245  mRemoveButton->setEnabled( removeEnabled );
246 }
247 
248 
250 //
251 // VariableEditorTree
252 //
253 
254 QgsVariableEditorTree::QgsVariableEditorTree( QWidget *parent )
255  : QTreeWidget( parent )
256 {
257  // init icons
258  if ( mExpandIcon.isNull() )
259  {
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 );
266  }
267 
268  setIconSize( QSize( 18, 18 ) );
269  setColumnCount( 2 );
270  setHeaderLabels( QStringList() << tr( "Variable" ) << tr( "Value" ) );
271  setEditTriggers( QAbstractItemView::AllEditTriggers );
272  setRootIsDecorated( false );
273  header()->setSectionsMovable( false );
274  header()->setSectionResizeMode( QHeaderView::Interactive );
275 
276  mEditorDelegate = new VariableEditorDelegate( this, this );
277  setItemDelegate( mEditorDelegate );
278 }
279 
280 QgsExpressionContextScope *QgsVariableEditorTree::scopeFromItem( QTreeWidgetItem *item ) const
281 {
282  if ( !item )
283  return nullptr;
284 
285  bool ok;
286  const int contextIndex = item->data( 0, ContextIndex ).toInt( &ok );
287  if ( !ok )
288  return nullptr;
289 
290  if ( !mContext )
291  {
292  return nullptr;
293  }
294  else if ( mContext->scopeCount() > contextIndex )
295  {
296  return mContext->scope( contextIndex );
297  }
298  else
299  {
300  return nullptr;
301  }
302 }
303 
304 QTreeWidgetItem *QgsVariableEditorTree::itemFromVariable( QgsExpressionContextScope *scope, const QString &name ) const
305 {
306  const int contextIndex = mContext ? mContext->indexOfScope( scope ) : 0;
307  if ( contextIndex < 0 )
308  return nullptr;
309  return mVariableToItem.value( qMakePair( contextIndex, name ) );
310 }
311 
312 QgsExpressionContextScope *QgsVariableEditorTree::editableScope()
313 {
314  if ( !mContext || mEditableScopeIndex < 0 || mEditableScopeIndex >= mContext->scopeCount() )
315  {
316  return nullptr;
317  }
318 
319  return mContext->scope( mEditableScopeIndex );
320 }
321 
322 void QgsVariableEditorTree::refreshTree()
323 {
324  if ( !mContext || mEditableScopeIndex < 0 )
325  {
326  clear();
327  return;
328  }
329 
330  //add all scopes from the context
331  int scopeIndex = 0;
332  const auto constScopes = mContext->scopes();
333  for ( QgsExpressionContextScope *scope : constScopes )
334  {
335  refreshScopeItems( scope, scopeIndex );
336  scopeIndex++;
337  }
338 }
339 
340 void QgsVariableEditorTree::refreshScopeVariables( QgsExpressionContextScope *scope, int scopeIndex )
341 {
342  const QColor baseColor = rowColor( scopeIndex );
343  const bool isCurrent = scopeIndex == mEditableScopeIndex;
344  QTreeWidgetItem *scopeItem = mScopeToItem.value( scopeIndex );
345 
346  const QStringList names = scope->filteredVariableNames();
347  for ( const QString &name : names )
348  {
349  QTreeWidgetItem *item = mVariableToItem.value( qMakePair( scopeIndex, name ) );
350  if ( !item )
351  {
352  item = new QTreeWidgetItem( scopeItem );
353  mVariableToItem.insert( qMakePair( scopeIndex, name ), item );
354  }
355 
356  const bool readOnly = scope->isReadOnly( name );
357  bool isActive = true;
358  QgsExpressionContextScope *activeScope = nullptr;
359  if ( mContext )
360  {
361  activeScope = mContext->activeScopeForVariable( name );
362  isActive = activeScope == scope;
363  }
364 
365  item->setFlags( item->flags() | Qt::ItemIsEnabled );
366  item->setText( 0, name );
367  const QVariant value = scope->variable( name );
368  const QString previewString = QgsExpression::formatPreviewString( value, false );
369  item->setText( 1, previewString );
370  QFont font = item->font( 0 );
371  if ( readOnly || !isCurrent )
372  {
373  font.setItalic( true );
374  item->setFlags( item->flags() ^ Qt::ItemIsEditable );
375  }
376  else
377  {
378  font.setItalic( false );
379  item->setFlags( item->flags() | Qt::ItemIsEditable );
380  }
381  if ( !isActive )
382  {
383  //overridden
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 );
388  }
389  else
390  {
391  font.setStrikeOut( false );
392  item->setToolTip( 0, name );
393  item->setToolTip( 1, previewString );
394  }
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 );
400  }
401 }
402 
403 void QgsVariableEditorTree::refreshScopeItems( QgsExpressionContextScope *scope, int scopeIndex )
404 {
405  const QgsSettings settings;
406 
407  //add top level item
408  const bool isCurrent = scopeIndex == mEditableScopeIndex;
409 
410  QTreeWidgetItem *scopeItem = nullptr;
411  if ( mScopeToItem.contains( scopeIndex ) )
412  {
413  //retrieve existing item
414  scopeItem = mScopeToItem.value( scopeIndex );
415  }
416  else
417  {
418  //create new top-level item
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 );
429 
430  addTopLevelItem( scopeItem );
431 
432  //expand by default if current context or context was previously expanded
433  if ( isCurrent || settings.value( "QgsVariableEditor/" + scopeItem->text( 0 ) + "/expanded" ).toBool() )
434  scopeItem->setExpanded( true );
435 
436  scopeItem->setIcon( 0, mExpandIcon );
437  }
438 
439  refreshScopeVariables( scope, scopeIndex );
440 }
441 
442 void QgsVariableEditorTree::removeItem( QTreeWidgetItem *item )
443 {
444  if ( !item )
445  return;
446 
447  mVariableToItem.remove( mVariableToItem.key( item ) );
448  item->parent()->takeChild( item->parent()->indexOfChild( item ) );
449 
450  emit scopeChanged();
451 }
452 
453 void QgsVariableEditorTree::renameItem( QTreeWidgetItem *item, const QString &name )
454 {
455  if ( !item )
456  return;
457 
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 );
462 
463  emit scopeChanged();
464 }
465 
466 void QgsVariableEditorTree::resetTree()
467 {
468  mVariableToItem.clear();
469  mScopeToItem.clear();
470  clear();
471 }
472 
473 void QgsVariableEditorTree::emitChanged()
474 {
475  emit scopeChanged();
476 }
477 
478 void QgsVariableEditorTree::drawRow( QPainter *painter, const QStyleOptionViewItem &option,
479  const QModelIndex &index ) const
480 {
481  QStyleOptionViewItem opt = option;
482  QTreeWidgetItem *item = itemFromIndex( index );
483  if ( index.parent().isValid() )
484  {
485  //not a top-level item, so shade row background by context
486  QColor baseColor = item->data( 0, RowBaseColor ).value<QColor>();
487  if ( index.row() % 2 == 1 )
488  {
489  baseColor.setAlpha( 59 );
490  }
491  painter->fillRect( option.rect, baseColor );
492  }
493  QTreeWidget::drawRow( painter, opt, index );
494  const QColor color = static_cast<QRgb>( QApplication::style()->styleHint( QStyle::SH_Table_GridLineColor, &opt ) );
495  const QgsScopedQPainterState painterState( painter );
496  painter->setPen( QPen( color ) );
497  painter->drawLine( opt.rect.x(), opt.rect.bottom(), opt.rect.right(), opt.rect.bottom() );
498 }
499 
500 QColor QgsVariableEditorTree::rowColor( int index ) const
501 {
502  //return some nice (inspired by Qt Designer) background row colors
503  const int colorIdx = index % 6;
504  switch ( colorIdx )
505  {
506  case 0:
507  return QColor( 255, 163, 0, 89 );
508  case 1:
509  return QColor( 255, 255, 77, 89 );
510  case 2:
511  return QColor( 0, 255, 77, 89 );
512  case 3:
513  return QColor( 0, 255, 255, 89 );
514  case 4:
515  return QColor( 196, 125, 255, 89 );
516  case 5:
517  default:
518  return QColor( 255, 125, 225, 89 );
519  }
520 }
521 
522 void QgsVariableEditorTree::toggleContextExpanded( QTreeWidgetItem *item )
523 {
524  if ( !item )
525  return;
526 
527  item->setExpanded( !item->isExpanded() );
528 
529  //save expanded state
530  QgsSettings settings;
531  settings.setValue( "QgsVariableEditor/" + item->text( 0 ) + "/expanded", item->isExpanded() );
532 }
533 
534 void QgsVariableEditorTree::editNext( const QModelIndex &index )
535 {
536  if ( !index.isValid() )
537  return;
538 
539  if ( index.column() == 0 )
540  {
541  //switch to next column
542  const QModelIndex nextIndex = index.sibling( index.row(), 1 );
543  if ( nextIndex.isValid() )
544  {
545  setCurrentIndex( nextIndex );
546  edit( nextIndex );
547  }
548  }
549  else
550  {
551  const QModelIndex nextIndex = model()->index( index.row() + 1, 0, index.parent() );
552  if ( nextIndex.isValid() )
553  {
554  //start editing next row
555  setCurrentIndex( nextIndex );
556  edit( nextIndex );
557  }
558  else
559  {
560  edit( index );
561  }
562  }
563 }
564 
565 QModelIndex QgsVariableEditorTree::moveCursor( QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers )
566 {
567  if ( cursorAction == QAbstractItemView::MoveNext )
568  {
569  const QModelIndex index = currentIndex();
570  if ( index.isValid() )
571  {
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 );
576  else
577  return QModelIndex();
578  }
579  }
580  else if ( cursorAction == QAbstractItemView::MovePrevious )
581  {
582  const QModelIndex index = currentIndex();
583  if ( index.isValid() )
584  {
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 );
589  else
590  return QModelIndex();
591  }
592  }
593 
594  return QTreeWidget::moveCursor( cursorAction, modifiers );
595 }
596 
597 void QgsVariableEditorTree::keyPressEvent( QKeyEvent *event )
598 {
599  switch ( event->key() )
600  {
601  case Qt::Key_Return:
602  case Qt::Key_Enter:
603  case Qt::Key_Space:
604  {
605  QTreeWidgetItem *item = currentItem();
606  if ( item && !item->parent() )
607  {
608  event->accept();
609  toggleContextExpanded( item );
610  return;
611  }
612  else if ( item && ( item->flags() & Qt::ItemIsEditable ) )
613  {
614  event->accept();
615  editNext( currentIndex() );
616  return;
617  }
618  break;
619  }
620  default:
621  break;
622  }
623 
624  if ( event == QKeySequence::Copy )
625  {
626  const QList<QTreeWidgetItem *> selected = selectedItems();
627  if ( selected.size() > 0 )
628  {
629  QString text = selected.at( 0 )->text( 0 );
630  const QString varName = variableNameFromItem( selected.at( 0 ) );
631  QgsExpressionContextScope *scope = scopeFromItem( selected.at( 0 ) );
632  if ( !varName.isEmpty() && scope )
633  text = scope->variable( varName ).toString();
634 
635  QClipboard *clipboard = QApplication::clipboard();
636  clipboard->setText( text );
637  event->accept();
638  return;
639  }
640  }
641 
642  QTreeWidget::keyPressEvent( event );
643 }
644 
645 void QgsVariableEditorTree::mousePressEvent( QMouseEvent *event )
646 {
647  QTreeWidget::mousePressEvent( event );
648  QTreeWidgetItem *item = itemAt( event->pos() );
649  if ( !item )
650  return;
651 
652  if ( item->parent() )
653  {
654  //not a top-level item
655  return;
656  }
657 
658  if ( event->pos().x() + header()->offset() > 20 )
659  {
660  //not clicking on expand icon
661  return;
662  }
663 
664  if ( event->modifiers() & Qt::ShiftModifier )
665  {
666  //shift modifier expands all
667  if ( !item->isExpanded() )
668  {
669  expandAll();
670  }
671  else
672  {
673  collapseAll();
674  }
675  }
676  else
677  {
678  toggleContextExpanded( item );
679  }
680 }
681 
682 //
683 // VariableEditorDelegate
684 //
685 
686 QWidget *VariableEditorDelegate::createEditor( QWidget *parent,
687  const QStyleOptionViewItem &,
688  const QModelIndex &index ) const
689 {
690  if ( !mParentTree )
691  return nullptr;
692 
693  //no editing for top level items
694  if ( !index.parent().isValid() )
695  return nullptr;
696 
697  QTreeWidgetItem *item = mParentTree->indexToItem( index );
698  QgsExpressionContextScope *scope = mParentTree->scopeFromItem( item );
699  if ( !item || !scope )
700  return nullptr;
701 
702  const QString variableName = mParentTree->variableNameFromIndex( index );
703 
704  //no editing inherited or read-only variables
705  if ( scope != mParentTree->editableScope() || scope->isReadOnly( variableName ) )
706  return nullptr;
707 
708  QLineEdit *lineEdit = new QLineEdit( parent );
709  lineEdit->setText( index.column() == 0 ? variableName : mParentTree->editableScope()->variable( variableName ).toString() );
710  lineEdit->setAutoFillBackground( true );
711  return lineEdit;
712 }
713 
714 void VariableEditorDelegate::updateEditorGeometry( QWidget *editor,
715  const QStyleOptionViewItem &option,
716  const QModelIndex & ) const
717 {
718  editor->setGeometry( option.rect.adjusted( 0, 0, 0, -1 ) );
719 }
720 
721 QSize VariableEditorDelegate::sizeHint( const QStyleOptionViewItem &option,
722  const QModelIndex &index ) const
723 {
724  return QItemDelegate::sizeHint( option, index ) + QSize( 3, 4 );
725 }
726 
727 void VariableEditorDelegate::setModelData( QWidget *widget, QAbstractItemModel *model,
728  const QModelIndex &index ) const
729 {
730  Q_UNUSED( model )
731 
732  if ( !mParentTree )
733  return;
734 
735  QTreeWidgetItem *item = mParentTree->indexToItem( index );
736  QgsExpressionContextScope *scope = mParentTree->scopeFromItem( item );
737  if ( !item || !scope )
738  return;
739 
740  QLineEdit *lineEdit = qobject_cast< QLineEdit * >( widget );
741  if ( !lineEdit )
742  return;
743 
744  const QString variableName = mParentTree->variableNameFromIndex( index );
745  if ( index.column() == 0 )
746  {
747  //edited variable name
748  QString newName = lineEdit->text();
749  newName = newName.trimmed();
750  newName = newName.replace( ' ', '_' );
751 
752  //test for validity
753  if ( newName == variableName )
754  {
755  return;
756  }
757  if ( scope->hasVariable( newName ) )
758  {
759  //existing name
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" );
762  }
763 
764  const QString value = scope->variable( variableName ).toString();
765  mParentTree->renameItem( item, newName );
766  scope->removeVariable( variableName );
767  scope->setVariable( newName, value );
768  mParentTree->emitChanged();
769  }
770  else if ( index.column() == 1 )
771  {
772  //edited variable value
773  const QString value = lineEdit->text();
774  if ( scope->variable( variableName ).toString() == value )
775  {
776  return;
777  }
778  scope->setVariable( variableName, value );
779  mParentTree->emitChanged();
780  }
781  mParentTree->refreshTree();
782 }
783 
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:
Definition: qgssettings.h:62
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.
void scopeChanged()
Emitted when the user has modified a scope using the widget.
QVariantMap variablesInActiveScope() const
Returns a map variables set within the editable scope.
void reloadContext()
Reloads all scopes from the editor's current context.
QgsExpressionContext * context() const
Returns the current expression context for the widget.
void showEvent(QShowEvent *event) override
void setEditableScopeIndex(int scopeIndex)
Sets the editable scope for the widget.
QgsVariableEditorWidget(QWidget *parent=nullptr)
Constructor for QgsVariableEditorWidget.
void setContext(QgsExpressionContext *context)
Overwrites the QgsExpressionContext for the widget.
QgsExpressionContextScope * editableScope() const
Returns the current editable scope for the widget.