QGIS API Documentation  3.20.0-Odense (decaadbb31)
qgspropertyoverridebutton.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgspropertyoverridebutton.cpp
3  -----------------------------
4  Date : January 2017
5  Copyright : (C) 2017 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 
18 #include "qgsapplication.h"
20 #include "qgsexpression.h"
21 #include "qgsmessageviewer.h"
22 #include "qgsvectorlayer.h"
23 #include "qgspanelwidget.h"
25 #include "qgsauxiliarystorage.h"
26 #include "qgscolorschemeregistry.h"
27 #include "qgscolorbutton.h"
28 #include "qgsguiutils.h"
29 
30 #include <QClipboard>
31 #include <QMenu>
32 #include <QMouseEvent>
33 #include <QPointer>
34 #include <QGroupBox>
35 #include <QRegularExpression>
36 
38  const QgsVectorLayer *layer )
39  : QToolButton( parent )
40  , mVectorLayer( layer )
41 
42 {
43  setFocusPolicy( Qt::StrongFocus );
44 
46 
47  // button width is 1.25 * icon size, height 1.1 * icon size. But we round to ensure even pixel sizes for equal margins
48  setFixedSize( 2 * static_cast< int >( 1.25 * iconSize / 2.0 ), 2 * static_cast< int >( iconSize * 1.1 / 2.0 ) );
49 
50  setIconSize( QSize( iconSize, iconSize ) );
51  setPopupMode( QToolButton::InstantPopup );
52 
53  connect( this, &QgsPropertyOverrideButton::activated, this, &QgsPropertyOverrideButton::updateSiblingWidgets );
54 
55  mDefineMenu = new QMenu( this );
56  connect( mDefineMenu, &QMenu::aboutToShow, this, &QgsPropertyOverrideButton::aboutToShowMenu );
57  connect( mDefineMenu, &QMenu::triggered, this, &QgsPropertyOverrideButton::menuActionTriggered );
58  setMenu( mDefineMenu );
59 
60  mFieldsMenu = new QMenu( this );
61  mActionDataTypes = new QAction( this );
62  // list fields and types in submenu, since there may be many
63  mActionDataTypes->setMenu( mFieldsMenu );
64 
65  mActionVariables = new QAction( tr( "Variable" ), this );
66  mVariablesMenu = new QMenu( this );
67  mActionVariables->setMenu( mVariablesMenu );
68 
69  mActionColors = new QAction( tr( "Color" ), this );
70  mColorsMenu = new QMenu( this );
71  mActionColors->setMenu( mColorsMenu );
72 
73  mActionActive = new QAction( this );
74  QFont f = mActionActive->font();
75  f.setBold( true );
76  mActionActive->setFont( f );
77 
78  mActionDescription = new QAction( tr( "Description…" ), this );
79 
80  mActionCreateAuxiliaryField = new QAction( tr( "Store Data in the Project" ), this );
81  mActionCreateAuxiliaryField->setCheckable( true );
82 
83  mActionExpDialog = new QAction( tr( "Edit…" ), this );
84  mActionExpression = nullptr;
85  mActionPasteExpr = new QAction( tr( "Paste" ), this );
86  mActionCopyExpr = new QAction( tr( "Copy" ), this );
87  mActionClearExpr = new QAction( tr( "Clear" ), this );
88  mActionAssistant = new QAction( tr( "Assistant…" ), this );
89  QFont assistantFont = mActionAssistant->font();
90  assistantFont.setBold( true );
91  mActionAssistant->setFont( assistantFont );
92  mDefineMenu->addAction( mActionAssistant );
93 }
94 
95 
96 void QgsPropertyOverrideButton::init( int propertyKey, const QgsProperty &property, const QgsPropertiesDefinition &definitions, const QgsVectorLayer *layer, bool auxiliaryStorageEnabled )
97 {
98  init( propertyKey, property, definitions.value( propertyKey ), layer, auxiliaryStorageEnabled );
99 }
100 
101 void QgsPropertyOverrideButton::init( int propertyKey, const QgsProperty &property, const QgsPropertyDefinition &definition, const QgsVectorLayer *layer, bool auxiliaryStorageEnabled )
102 {
103  mVectorLayer = layer;
104  mAuxiliaryStorageEnabled = auxiliaryStorageEnabled;
105  setToProperty( property );
106  mPropertyKey = propertyKey;
107 
108  mDefinition = definition;
109  mDataTypes = mDefinition.dataType();
110 
111  mInputDescription = mDefinition.helpText();
112  mFullDescription.clear();
113  mUsageInfo.clear();
114 
115  // set up data types string
116  mDataTypesString.clear();
117 
118  QStringList ts;
119  switch ( mDataTypes )
120  {
122  ts << tr( "boolean" );
124 
126  ts << tr( "int" );
127  ts << tr( "double" );
129 
131  ts << tr( "string" );
132  break;
133  }
134 
135  if ( !ts.isEmpty() )
136  {
137  mDataTypesString = ts.join( QLatin1String( ", " ) );
138  mActionDataTypes->setText( tr( "Field type: " ) + mDataTypesString );
139  }
140 
142  updateGui();
143  updateSiblingWidgets( isActive() );
144 }
145 
146 void QgsPropertyOverrideButton::init( int propertyKey, const QgsAbstractPropertyCollection &collection, const QgsPropertiesDefinition &definitions, const QgsVectorLayer *layer, bool auxiliaryStorageEnabled )
147 {
148  init( propertyKey, collection.property( propertyKey ), definitions, layer, auxiliaryStorageEnabled );
149 }
150 
151 
153 {
154  mFieldNameList.clear();
155  mFieldDisplayNameList.clear();
156  mFieldIcons.clear();
157 
158  if ( mVectorLayer )
159  {
160  // store just a list of fields of unknown type or those that match the expected type
161  const QgsFields fields = mVectorLayer->fields();
162  int idx = 0;
163  for ( const QgsField &f : fields )
164  {
165  bool fieldMatch = false;
166  QString fieldType;
167 
168  switch ( mDataTypes )
169  {
171  fieldMatch = true;
172  break;
173 
175  fieldMatch = f.isNumeric() || f.type() == QVariant::String;
176  break;
177 
179  fieldMatch = f.type() == QVariant::String;
180  break;
181  }
182 
183  switch ( f.type() )
184  {
185  case QVariant::String:
186  fieldType = tr( "string" );
187  break;
188  case QVariant::Int:
189  fieldType = tr( "integer" );
190  break;
191  case QVariant::LongLong:
192  fieldType = tr( "integer64" );
193  break;
194  case QVariant::Double:
195  fieldType = tr( "double" );
196  break;
197  case QVariant::Bool:
198  fieldType = tr( "boolean" );
199  break;
200  default:
201  fieldType = tr( "unknown type" );
202  }
203  if ( fieldMatch )
204  {
205  mFieldNameList << f.name();
206  mFieldDisplayNameList << f.displayNameWithAlias();
207  mFieldIcons << fields.iconForField( idx, true );
208  }
209  idx++;
210  }
211  }
212 }
213 
215 {
216  return mProperty;
217 }
218 
220 {
221  mVectorLayer = layer;
222 }
223 
224 void QgsPropertyOverrideButton::registerCheckedWidget( QWidget *widget, bool natural )
225 {
226  const auto constMSiblingWidgets = mSiblingWidgets;
227  for ( const SiblingWidget &sw : constMSiblingWidgets )
228  {
229  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingCheckState )
230  return;
231  }
232  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingCheckState, natural ) );
233  updateSiblingWidgets( isActive() );
234 }
235 
236 void QgsPropertyOverrideButton::registerEnabledWidget( QWidget *widget, bool natural )
237 {
238  const auto constMSiblingWidgets = mSiblingWidgets;
239  for ( const SiblingWidget &sw : constMSiblingWidgets )
240  {
241  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingEnableState )
242  return;
243  }
244  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingEnableState, natural ) );
245  updateSiblingWidgets( isActive() );
246 }
247 
248 void QgsPropertyOverrideButton::registerVisibleWidget( QWidget *widget, bool natural )
249 {
250  const auto constMSiblingWidgets = mSiblingWidgets;
251  for ( const SiblingWidget &sw : constMSiblingWidgets )
252  {
253  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingVisibility )
254  return;
255  }
256  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingVisibility, natural ) );
257  updateSiblingWidgets( isActive() );
258 }
259 
261 {
262  const auto constMSiblingWidgets = mSiblingWidgets;
263  for ( const SiblingWidget &sw : constMSiblingWidgets )
264  {
265  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingExpressionText )
266  return;
267  }
268  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingExpressionText ) );
269  updateSiblingWidgets( isActive() );
270 }
271 
272 
274 {
275  // Ctrl-click to toggle activated state
276  if ( ( event->modifiers() & ( Qt::ControlModifier ) )
277  || event->button() == Qt::RightButton )
278  {
279  setActivePrivate( !mProperty.isActive() );
280  updateGui();
281  emit changed();
282  event->ignore();
283  return;
284  }
285 
286  // pass to default behavior
287  QToolButton::mousePressEvent( event );
288 }
289 
291 {
292  if ( property )
293  {
294  switch ( property.propertyType() )
295  {
298  break;
300  {
301  mFieldName = property.field();
302  break;
303  }
305  {
306  mExpressionString = property.expressionString();
307  break;
308  }
309  }
310  }
311  else
312  {
313  mFieldName.clear();
314  mExpressionString.clear();
315  }
316  mProperty = property;
317  setActive( mProperty && mProperty.isActive() );
318  updateSiblingWidgets( isActive() );
319  updateGui();
320 }
321 
323 void QgsPropertyOverrideButton::aboutToShowMenu()
324 {
325  mDefineMenu->clear();
326  // update fields so that changes made to layer's fields are reflected
328 
329  bool hasExp = !mExpressionString.isEmpty();
330  QString ddTitle = tr( "Data defined override" );
331 
332  QAction *ddTitleAct = mDefineMenu->addAction( ddTitle );
333  QFont titlefont = ddTitleAct->font();
334  titlefont.setItalic( true );
335  ddTitleAct->setFont( titlefont );
336  ddTitleAct->setEnabled( false );
337 
338  bool addActiveAction = false;
339  if ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && hasExp )
340  {
341  QgsExpression exp( mExpressionString );
342  // whether expression is parse-able
343  addActiveAction = !exp.hasParserError();
344  }
345  else if ( mProperty.propertyType() == QgsProperty::FieldBasedProperty )
346  {
347  // whether field exists
348  addActiveAction = mFieldNameList.contains( mFieldName );
349  }
350 
351  if ( addActiveAction )
352  {
353  ddTitleAct->setText( ddTitle + " (" + ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty ? tr( "expression" ) : tr( "field" ) ) + ')' );
354  mDefineMenu->addAction( mActionActive );
355  mActionActive->setText( mProperty.isActive() ? tr( "Deactivate" ) : tr( "Activate" ) );
356  mActionActive->setData( QVariant( !mProperty.isActive() ) );
357  }
358 
359  if ( !mFullDescription.isEmpty() )
360  {
361  mDefineMenu->addAction( mActionDescription );
362  }
363 
364  mDefineMenu->addSeparator();
365 
366  // deactivate button if field already exists
367  if ( mAuxiliaryStorageEnabled && mVectorLayer )
368  {
369  mDefineMenu->addAction( mActionCreateAuxiliaryField );
370 
371  const QgsAuxiliaryLayer *alayer = mVectorLayer->auxiliaryLayer();
372 
373  mActionCreateAuxiliaryField->setEnabled( true );
374  mActionCreateAuxiliaryField->setChecked( false );
375 
376  int index = mVectorLayer->fields().indexFromName( mFieldName );
377  int srcIndex;
378  if ( index >= 0 && alayer && mVectorLayer->isAuxiliaryField( index, srcIndex ) )
379  {
380  mActionCreateAuxiliaryField->setEnabled( false );
381  mActionCreateAuxiliaryField->setChecked( true );
382  }
383  }
384 
385  bool fieldActive = false;
386  if ( !mDataTypesString.isEmpty() )
387  {
388  QAction *fieldTitleAct = mDefineMenu->addAction( tr( "Attribute Field" ) );
389  fieldTitleAct->setFont( titlefont );
390  fieldTitleAct->setEnabled( false );
391 
392  mDefineMenu->addAction( mActionDataTypes );
393 
394  mFieldsMenu->clear();
395 
396  if ( !mFieldNameList.isEmpty() )
397  {
398 
399  for ( int j = 0; j < mFieldNameList.count(); ++j )
400  {
401  QString fldname = mFieldNameList.at( j );
402  QAction *act = mFieldsMenu->addAction( mFieldDisplayNameList.at( j ) );
403  act->setIcon( mFieldIcons.at( j ) );
404  act->setData( QVariant( fldname ) );
405  if ( mFieldName == fldname )
406  {
407  act->setCheckable( true );
408  act->setChecked( mProperty.propertyType() == QgsProperty::FieldBasedProperty );
409  fieldActive = mProperty.propertyType() == QgsProperty::FieldBasedProperty;
410  }
411  }
412  }
413  else
414  {
415  QAction *act = mFieldsMenu->addAction( tr( "No matching field types found" ) );
416  act->setEnabled( false );
417  }
418 
419  mDefineMenu->addSeparator();
420  }
421 
422  mFieldsMenu->menuAction()->setCheckable( true );
423  mFieldsMenu->menuAction()->setChecked( fieldActive && mProperty.propertyType() == QgsProperty::FieldBasedProperty && !mProperty.transformer() );
424 
425  bool colorActive = false;
426  mColorsMenu->clear();
429  {
430  // project colors menu
431  QAction *colorTitleAct = mDefineMenu->addAction( tr( "Project Color" ) );
432  colorTitleAct->setFont( titlefont );
433  colorTitleAct->setEnabled( false );
434 
435  QList<QgsProjectColorScheme *> projectSchemes;
436  QgsApplication::colorSchemeRegistry()->schemes( projectSchemes );
437  if ( projectSchemes.length() > 0 )
438  {
439  QgsProjectColorScheme *scheme = projectSchemes.at( 0 );
440  const QgsNamedColorList colors = scheme->fetchColors();
441  for ( const auto &color : colors )
442  {
443  if ( color.second.isEmpty() )
444  continue;
445 
446  QPixmap icon = QgsColorButton::createMenuIcon( color.first, mDefinition.standardTemplate() == QgsPropertyDefinition::ColorWithAlpha );
447  QAction *act = mColorsMenu->addAction( color.second );
448  act->setIcon( icon );
449  if ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && hasExp && mExpressionString == QStringLiteral( "project_color('%1')" ).arg( color.second ) )
450  {
451  act->setCheckable( true );
452  act->setChecked( true );
453  colorActive = true;
454  }
455  }
456  }
457 
458  if ( mColorsMenu->actions().isEmpty() )
459  {
460  QAction *act = mColorsMenu->addAction( tr( "No colors set" ) );
461  act->setEnabled( false );
462  }
463 
464  mDefineMenu->addAction( mActionColors );
465  mColorsMenu->menuAction()->setCheckable( true );
466  mColorsMenu->menuAction()->setChecked( colorActive && !mProperty.transformer() );
467 
468  mDefineMenu->addSeparator();
469  }
470 
471  QAction *exprTitleAct = mDefineMenu->addAction( tr( "Expression" ) );
472  exprTitleAct->setFont( titlefont );
473  exprTitleAct->setEnabled( false );
474 
475  mVariablesMenu->clear();
476  bool variableActive = false;
477  if ( mExpressionContextGenerator )
478  {
479  QgsExpressionContext context = mExpressionContextGenerator->createExpressionContext();
480  QStringList variables = context.variableNames();
481  const auto constVariables = variables;
482  for ( const QString &variable : constVariables )
483  {
484  if ( context.isReadOnly( variable ) ) //only want to show user-set variables
485  continue;
486  if ( variable.startsWith( '_' ) ) //no hidden variables
487  continue;
488 
489  QAction *act = mVariablesMenu->addAction( variable );
490  act->setData( QVariant( variable ) );
491 
492  if ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && hasExp && mExpressionString == '@' + variable )
493  {
494  act->setCheckable( true );
495  act->setChecked( true );
496  variableActive = true;
497  }
498  }
499  }
500 
501  if ( mVariablesMenu->actions().isEmpty() )
502  {
503  QAction *act = mVariablesMenu->addAction( tr( "No variables set" ) );
504  act->setEnabled( false );
505  }
506 
507  mDefineMenu->addAction( mActionVariables );
508  mVariablesMenu->menuAction()->setCheckable( true );
509  mVariablesMenu->menuAction()->setChecked( variableActive && !mProperty.transformer() );
510 
511  if ( hasExp )
512  {
513  QString expString = mExpressionString;
514  if ( expString.length() > 35 )
515  {
516  expString.truncate( 35 );
517  expString.append( QChar( 0x2026 ) );
518  }
519 
520  expString.prepend( tr( "Current: " ) );
521 
522  if ( !mActionExpression )
523  {
524  mActionExpression = new QAction( expString, this );
525  mActionExpression->setCheckable( true );
526  }
527  else
528  {
529  mActionExpression->setText( expString );
530  }
531  mDefineMenu->addAction( mActionExpression );
532  mActionExpression->setChecked( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && !variableActive && !colorActive && !mProperty.transformer() );
533 
534  mDefineMenu->addAction( mActionExpDialog );
535  mDefineMenu->addAction( mActionCopyExpr );
536  mDefineMenu->addAction( mActionPasteExpr );
537  }
538  else
539  {
540  mDefineMenu->addAction( mActionExpDialog );
541  mDefineMenu->addAction( mActionPasteExpr );
542  }
543 
544  if ( hasExp || !mFieldName.isEmpty() )
545  {
546  mDefineMenu->addSeparator();
547  mDefineMenu->addAction( mActionClearExpr );
548  }
549 
550  if ( !mDefinition.name().isEmpty() && mDefinition.supportsAssistant() )
551  {
552  mDefineMenu->addSeparator();
553  mActionAssistant->setCheckable( mProperty.transformer() );
554  mActionAssistant->setChecked( mProperty.transformer() );
555  mDefineMenu->addAction( mActionAssistant );
556  }
557 }
558 
559 void QgsPropertyOverrideButton::menuActionTriggered( QAction *action )
560 {
561  if ( action == mActionActive )
562  {
563  setActivePrivate( mActionActive->data().toBool() );
564  updateGui();
565  emit changed();
566  }
567  else if ( action == mActionDescription )
568  {
569  showDescriptionDialog();
570  }
571  else if ( action == mActionExpDialog )
572  {
573  showExpressionDialog();
574  }
575  else if ( action == mActionExpression )
576  {
577  mProperty.setExpressionString( mExpressionString );
578  mProperty.setTransformer( nullptr );
579  setActivePrivate( true );
580  updateSiblingWidgets( isActive() );
581  updateGui();
582  emit changed();
583  }
584  else if ( action == mActionCopyExpr )
585  {
586  QApplication::clipboard()->setText( mExpressionString );
587  }
588  else if ( action == mActionPasteExpr )
589  {
590  QString exprString = QApplication::clipboard()->text();
591  if ( !exprString.isEmpty() )
592  {
593  mExpressionString = exprString;
594  mProperty.setExpressionString( mExpressionString );
595  mProperty.setTransformer( nullptr );
596  setActivePrivate( true );
597  updateSiblingWidgets( isActive() );
598  updateGui();
599  emit changed();
600  }
601  }
602  else if ( action == mActionClearExpr )
603  {
604  setActivePrivate( false );
605  mProperty.setStaticValue( QVariant() );
606  mProperty.setTransformer( nullptr );
607  mExpressionString.clear();
608  mFieldName.clear();
609  updateSiblingWidgets( isActive() );
610  updateGui();
611  emit changed();
612  }
613  else if ( action == mActionAssistant )
614  {
615  showAssistant();
616  }
617  else if ( action == mActionCreateAuxiliaryField )
618  {
619  emit createAuxiliaryField();
620  }
621  else if ( mFieldsMenu->actions().contains( action ) ) // a field name clicked
622  {
623  if ( action->isEnabled() )
624  {
625  if ( mFieldName != action->text() )
626  {
627  mFieldName = action->data().toString();
628  }
629  mProperty.setField( mFieldName );
630  mProperty.setTransformer( nullptr );
631  setActivePrivate( true );
632  updateSiblingWidgets( isActive() );
633  updateGui();
634  emit changed();
635  }
636  }
637  else if ( mVariablesMenu->actions().contains( action ) ) // a variable name clicked
638  {
639  if ( mExpressionString != action->text().prepend( "@" ) )
640  {
641  mExpressionString = action->data().toString().prepend( "@" );
642  }
643  mProperty.setExpressionString( mExpressionString );
644  mProperty.setTransformer( nullptr );
645  setActivePrivate( true );
646  updateSiblingWidgets( isActive() );
647  updateGui();
648  emit changed();
649  }
650  else if ( mColorsMenu->actions().contains( action ) ) // a color name clicked
651  {
652  if ( mExpressionString != QStringLiteral( "project_color('%1')" ).arg( action->text() ) )
653  {
654  mExpressionString = QStringLiteral( "project_color('%1')" ).arg( action->text() );
655  }
656  mProperty.setExpressionString( mExpressionString );
657  mProperty.setTransformer( nullptr );
658  setActivePrivate( true );
659  updateSiblingWidgets( isActive() );
660  updateGui();
661  emit changed();
662  }
663 }
665 
666 void QgsPropertyOverrideButton::showDescriptionDialog()
667 {
668  QgsMessageViewer *mv = new QgsMessageViewer( this );
669  mv->setWindowTitle( tr( "Data Definition Description" ) );
670  mv->setMessageAsHtml( mFullDescription );
671  mv->exec();
672 }
673 
674 
675 void QgsPropertyOverrideButton::showExpressionDialog()
676 {
677  QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : QgsExpressionContext();
678 
679  // build sensible initial expression text - see https://github.com/qgis/QGIS/issues/26526
680  QString currentExpression = ( mProperty.propertyType() == QgsProperty::StaticProperty && !mProperty.staticValue().isValid() ) ? QString()
681  : mProperty.asExpression();
682 
683  QgsExpressionBuilderDialog d( const_cast<QgsVectorLayer *>( mVectorLayer ), currentExpression, this, QStringLiteral( "generic" ), context );
684  d.setExpectedOutputFormat( mInputDescription );
685  if ( d.exec() == QDialog::Accepted )
686  {
687  mExpressionString = d.expressionText().trimmed();
688  mProperty.setExpressionString( mExpressionString );
689  mProperty.setTransformer( nullptr );
690  setActivePrivate( !mExpressionString.isEmpty() );
691  updateSiblingWidgets( isActive() );
692  updateGui();
693  emit changed();
694  }
695  activateWindow(); // reset focus to parent window
696 }
697 
698 void QgsPropertyOverrideButton::showAssistant()
699 {
700  //first step - try to convert any existing expression to a transformer if one doesn't
701  //already exist
702  if ( !mProperty.transformer() )
703  {
704  ( void )mProperty.convertToTransformer();
705  }
706 
708  QgsPropertyAssistantWidget *widget = new QgsPropertyAssistantWidget( panel, mDefinition, mProperty, mVectorLayer );
709  widget->registerExpressionContextGenerator( mExpressionContextGenerator );
710  widget->setSymbol( mSymbol ); // we only show legend preview in dialog version
711 
712  if ( panel && panel->dockMode() )
713  {
714  connect( widget, &QgsPropertyAssistantWidget::widgetChanged, this, [this, widget]
715  {
716  widget->updateProperty( this->mProperty );
717  mExpressionString = this->mProperty.asExpression();
718  mFieldName = this->mProperty.field();
719  updateSiblingWidgets( isActive() );
720  this->emit changed();
721  } );
722 
723  // if the source layer is removed, we need to dismiss the assistant immediately
724  connect( mVectorLayer, &QObject::destroyed, widget, &QgsPanelWidget::acceptPanel );
725 
726  connect( widget, &QgsPropertyAssistantWidget::panelAccepted, this, [ = ] { updateGui(); } );
727 
728  panel->openPanel( widget );
729  return;
730  }
731  else
732  {
733  // Show the dialog version if not in a panel
734  QDialog *dlg = new QDialog( this );
735  QString key = QStringLiteral( "/UI/paneldialog/%1" ).arg( widget->panelTitle() );
736  QgsSettings settings;
737  dlg->restoreGeometry( settings.value( key ).toByteArray() );
738  dlg->setWindowTitle( widget->panelTitle() );
739  dlg->setLayout( new QVBoxLayout() );
740  dlg->layout()->addWidget( widget );
741  QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok );
742  connect( buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept );
743  connect( buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject );
744  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsPropertyOverrideButton::showHelp );
745  dlg->layout()->addWidget( buttonBox );
746 
747  if ( dlg->exec() == QDialog::Accepted )
748  {
749  widget->updateProperty( mProperty );
750  mExpressionString = mProperty.asExpression();
751  mFieldName = mProperty.field();
752  widget->acceptPanel();
753  updateSiblingWidgets( isActive() );
754  updateGui();
755 
756  emit changed();
757  }
758  settings.setValue( key, dlg->saveGeometry() );
759  }
760 }
761 
762 void QgsPropertyOverrideButton::updateGui()
763 {
764  bool hasExp = !mExpressionString.isEmpty();
765  bool hasField = !mFieldName.isEmpty();
766 
767  QIcon icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefine.svg" ) );
768  QString deftip = tr( "undefined" );
769  QString deftype;
770  if ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && hasExp )
771  {
772  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpressionOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpression.svg" ) );
773 
774  QRegularExpression rx( QStringLiteral( "^project_color\\('(.*)'\\)$" ) );
775  QRegularExpressionMatch match = rx.match( mExpressionString );
776  if ( match.hasMatch() )
777  {
778  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColorOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColor.svg" ) );
779  deftip = match.captured( 1 );
780  deftype = tr( "project color" );
781  }
782  else
783  {
784  QgsExpression exp( mExpressionString );
785  if ( exp.hasParserError() )
786  {
787  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpressionError.svg" ) );
788  deftip = tr( "Parse error: %1" ).arg( exp.parserErrorString() );
789  }
790  else
791  {
792  deftip = mExpressionString;
793  }
794  }
795  }
796  else if ( mProperty.propertyType() != QgsProperty::ExpressionBasedProperty && hasField )
797  {
798  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefine.svg" ) );
799 
800  if ( !mFieldNameList.contains( mFieldName ) && !mProperty.transformer() )
801  {
802  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineError.svg" ) );
803  deftip = tr( "'%1' field missing" ).arg( mFieldName );
804  }
805  else
806  {
807  deftip = mFieldName;
808  }
809  }
810 
811  setIcon( icon );
812 
813  // build full description for tool tip and popup dialog
814  mFullDescription = tr( "<b><u>Data defined override</u></b><br>" );
815 
816  mFullDescription += tr( "<b>Active: </b>%1&nbsp;&nbsp;&nbsp;<i>(ctrl|right-click toggles)</i><br>" ).arg( mProperty.isActive() ? tr( "yes" ) : tr( "no" ) );
817 
818  if ( !mUsageInfo.isEmpty() )
819  {
820  mFullDescription += tr( "<b>Usage:</b><br>%1<br>" ).arg( mUsageInfo );
821  }
822 
823  if ( !mInputDescription.isEmpty() )
824  {
825  mFullDescription += tr( "<b>Expected input:</b><br>%1<br>" ).arg( mInputDescription );
826  }
827 
828  if ( !mDataTypesString.isEmpty() )
829  {
830  mFullDescription += tr( "<b>Valid input types:</b><br>%1<br>" ).arg( mDataTypesString );
831  }
832 
833  if ( deftype.isEmpty() && deftip != tr( "undefined" ) )
834  {
835  deftype = mProperty.propertyType() == QgsProperty::ExpressionBasedProperty ? tr( "expression" ) : tr( "field" );
836  }
837 
838  // truncate long expressions, or tool tip may be too wide for screen
839  if ( deftip.length() > 75 )
840  {
841  deftip.truncate( 75 );
842  deftip.append( QChar( 0x2026 ) );
843  }
844 
845  mFullDescription += tr( "<b>Current definition (%1):</b><br>%2" ).arg( deftype, deftip );
846 
847  setToolTip( mFullDescription );
848 
849 }
850 
851 void QgsPropertyOverrideButton::setActivePrivate( bool active )
852 {
853  if ( mProperty.isActive() != active )
854  {
855  mProperty.setActive( active );
856  emit activated( mProperty.isActive() );
857  }
858 }
859 
860 void QgsPropertyOverrideButton::updateSiblingWidgets( bool state )
861 {
862  const auto constMSiblingWidgets = mSiblingWidgets;
863  for ( const SiblingWidget &sw : constMSiblingWidgets )
864  {
865  switch ( sw.mSiblingType )
866  {
867 
868  case SiblingCheckState:
869  {
870  // don't uncheck, only set to checked
871  if ( state )
872  {
873  QAbstractButton *btn = qobject_cast< QAbstractButton * >( sw.mWidgetPointer.data() );
874  if ( btn && btn->isCheckable() )
875  {
876  btn->setChecked( sw.mNatural ? state : !state );
877  }
878  else
879  {
880  QGroupBox *grpbx = qobject_cast< QGroupBox * >( sw.mWidgetPointer.data() );
881  if ( grpbx && grpbx->isCheckable() )
882  {
883  grpbx->setChecked( sw.mNatural ? state : !state );
884  }
885  }
886  }
887  break;
888  }
889 
890  case SiblingEnableState:
891  {
892  QLineEdit *le = qobject_cast< QLineEdit * >( sw.mWidgetPointer.data() );
893  if ( le )
894  le->setReadOnly( sw.mNatural ? !state : state );
895  else
896  sw.mWidgetPointer.data()->setEnabled( sw.mNatural ? state : !state );
897  break;
898  }
899 
900  case SiblingVisibility:
901  {
902  sw.mWidgetPointer.data()->setVisible( sw.mNatural ? state : !state );
903  break;
904  }
905 
906  case SiblingExpressionText:
907  {
908  QLineEdit *le = qobject_cast<QLineEdit *>( sw.mWidgetPointer.data() );
909  if ( le )
910  {
911  le->setText( mProperty.asExpression() );
912  }
913  else
914  {
915  QTextEdit *te = qobject_cast<QTextEdit *>( sw.mWidgetPointer.data() );
916  if ( te )
917  {
918  te->setText( mProperty.asExpression() );
919  }
920  }
921  break;
922  }
923 
924  case SiblingLinkedWidget:
925  {
926  if ( QgsColorButton *cb = qobject_cast< QgsColorButton * >( sw.mWidgetPointer.data() ) )
927  {
928  if ( state && mProperty.isProjectColor() )
929  {
930  QRegularExpression rx( QStringLiteral( "^project_color\\('(.*)'\\)$" ) );
931  QRegularExpressionMatch match = rx.match( mExpressionString );
932  if ( match.hasMatch() )
933  {
934  cb->linkToProjectColor( match.captured( 1 ) );
935  }
936  }
937  else
938  {
939  cb->linkToProjectColor( QString() );
940  }
941  }
942  break;
943  }
944  }
945  }
946 }
947 
948 
949 
951 {
952  if ( mProperty.isActive() != active )
953  {
954  mProperty.setActive( active );
955  emit changed();
956  emit activated( mProperty.isActive() );
957  }
958 }
959 
961 {
962  mExpressionContextGenerator = generator;
963 }
964 
966 {
967  for ( const SiblingWidget &sw : std::as_const( mSiblingWidgets ) )
968  {
969  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingLinkedWidget )
970  return;
971  }
972  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingLinkedWidget ) );
973 
974  if ( QgsColorButton *cb = qobject_cast< QgsColorButton * >( widget ) )
975  {
976  connect( cb, &QgsColorButton::unlinked, this, [ = ]
977  {
978  setActive( false );
979  updateGui();
980  } );
981  }
982 
983  updateSiblingWidgets( isActive() );
984 }
985 
986 void QgsPropertyOverrideButton::showHelp()
987 {
988  QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html#data-defined" ) );
989 }
Abstract base class for QgsPropertyCollection like objects.
virtual QgsProperty property(int key) const =0
Returns a matching property from the collection, if one exists.
Extends QApplication to provide access to QGIS specific resources such as theme paths,...
static QgsColorSchemeRegistry * colorSchemeRegistry()
Returns the application's color scheme registry, used for managing color schemes.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
Class allowing to manage the auxiliary storage for a vector layer.
A cross platform button subclass for selecting colors.
static QPixmap createMenuIcon(const QColor &color, bool showChecks=true)
Creates an icon for displaying a color in a drop-down menu.
void unlinked()
Emitted when the color is unlinked, e.g.
QList< QgsColorScheme * > schemes() const
Returns all color schemes in the registry.
A generic dialog for building expression strings.
Abstract interface for generating an expression context.
virtual QgsExpressionContext createExpressionContext() const =0
This method needs to be reimplemented in all classes which implement this interface and return an exp...
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
bool isReadOnly(const QString &name) const
Returns whether a variable is read only, and should not be modifiable by users.
QStringList variableNames() const
Returns a list of variables names set by all scopes in the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
Container of fields for a vector layer.
Definition: qgsfields.h:45
int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:202
QIcon iconForField(int fieldIdx, bool considerOrigin=false) const
Returns an icon corresponding to a field index, based on the field's type and source.
Definition: qgsfields.cpp:275
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:36
A generic message view for displaying QGIS messages.
void setMessageAsHtml(const QString &msg)
Base class for any widget that can be shown as a inline panel.
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
QString panelTitle()
The title of the panel.
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
void widgetChanged()
Emitted when the widget state changes.
void acceptPanel()
Accept the panel.
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
bool dockMode()
Returns the dock mode state.
A color scheme which contains project specific colors set through project properties dialog.
QgsNamedColorList fetchColors(const QString &context=QString(), const QColor &baseColor=QColor()) override
Gets a list of colors from the scheme.
Shows a user-friendly assistant guiding users through the creation of QgsProperty overrides.
void registerExpressionContextGenerator(QgsExpressionContextGenerator *generator)
Register an expression context generator class that will be used to retrieve an expression context fo...
void setSymbol(std::shared_ptr< QgsSymbol > symbol)
Sets a symbol which can be used for previews inside the widget.
void updateProperty(QgsProperty &property)
Updates a property in place to corresponding to the current settings shown in the widget.
Definition for a property.
Definition: qgsproperty.h:48
StandardPropertyTemplate standardTemplate() const
Returns the property's standard template, if applicable.
Definition: qgsproperty.h:195
QString helpText() const
Helper text for using the property, including a description of the valid values for the property.
Definition: qgsproperty.h:179
DataType dataType() const
Returns the allowable field/value data type for the property.
Definition: qgsproperty.h:189
@ ColorNoAlpha
Color with no alpha channel.
Definition: qgsproperty.h:66
@ ColorWithAlpha
Color with alpha channel.
Definition: qgsproperty.h:65
QString name() const
Returns the name of the property.
Definition: qgsproperty.h:140
bool supportsAssistant() const
Returns true if the property is of a type which is compatible with property override assistants.
@ DataTypeString
Property requires a string value.
Definition: qgsproperty.h:93
@ DataTypeBoolean
Property requires a boolean value.
Definition: qgsproperty.h:107
@ DataTypeNumeric
Property requires a numeric value.
Definition: qgsproperty.h:100
QgsProperty toProperty() const
Returns a QgsProperty object encapsulating the current state of the widget.
void updateFieldLists()
Updates list of fields.
void setVectorLayer(const QgsVectorLayer *layer)
Sets the vector layer associated with the button.
bool isActive() const
Returns true if the button has an active property.
void changed()
Emitted when property definition changes.
void activated(bool isActive)
Emitted when the activated status of the widget changes.
void registerEnabledWidget(QWidget *widget, bool natural=true)
Register a sibling widget that gets enabled when the property is active, and disabled when the proper...
void init(int propertyKey, const QgsProperty &property, const QgsPropertiesDefinition &definitions, const QgsVectorLayer *layer=nullptr, bool auxiliaryStorageEnabled=false)
Initialize a newly constructed property button (useful if button was included in a UI layout).
void registerCheckedWidget(QWidget *widget, bool natural=true)
Register a sibling widget that gets checked when the property is active.
void registerExpressionContextGenerator(QgsExpressionContextGenerator *generator)
Register an expression context generator class that will be used to retrieve an expression context fo...
int propertyKey() const
Returns the property key linked to the button.
void setActive(bool active)
Set whether the current property override definition is to be used.
void setToProperty(const QgsProperty &property)
Sets the widget to reflect the current state of a QgsProperty.
void createAuxiliaryField()
Emitted when creating a new auxiliary field.
void registerVisibleWidget(QWidget *widget, bool natural=true)
Register a sibling widget that gets visible when the property is active, and hidden when the property...
QgsPropertyOverrideButton(QWidget *parent=nullptr, const QgsVectorLayer *layer=nullptr)
Constructor for QgsPropertyOverrideButton.
void mouseReleaseEvent(QMouseEvent *event) override
void registerLinkedWidget(QWidget *widget)
Registers a widget which is linked to this button.
void registerExpressionWidget(QWidget *widget)
Register a sibling widget (line edit, text edit) that will receive the property as an expression.
A store for object properties.
Definition: qgsproperty.h:232
@ ExpressionBasedProperty
Expression based property (QgsExpressionBasedProperty)
Definition: qgsproperty.h:241
@ StaticProperty
Static property (QgsStaticProperty)
Definition: qgsproperty.h:239
@ FieldBasedProperty
Field based property (QgsFieldBasedProperty)
Definition: qgsproperty.h:240
@ InvalidProperty
Invalid (not set) property.
Definition: qgsproperty.h:238
bool isProjectColor() const
Returns true if the property is set to a linked project color.
QString asExpression() const
Returns an expression string representing the state of the property, or an empty string if the proper...
bool convertToTransformer()
Attempts to convert an existing expression based property to a base expression with corresponding tra...
void setTransformer(QgsPropertyTransformer *transformer)
Sets an optional transformer to use for manipulating the calculated values for the property.
void setStaticValue(const QVariant &value)
Sets the static value for the property.
QString field() const
Returns the current field name the property references.
const QgsPropertyTransformer * transformer() const
Returns the existing transformer used for manipulating the calculated values for the property,...
QVariant value(const QgsExpressionContext &context, const QVariant &defaultValue=QVariant(), bool *ok=nullptr) const
Calculates the current value of the property, including any transforms which are set for the property...
bool isActive() const
Returns whether the property is currently active.
void setField(const QString &field)
Sets the field name the property references.
QVariant staticValue() const
Returns the current static value for the property.
Type propertyType() const
Returns the property type.
void setExpressionString(const QString &expression)
Sets the expression to use for the property value.
void setActive(bool active)
Sets whether the property is currently active.
Represents a vector layer which manages a vector based data sets.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QgsAuxiliaryLayer * auxiliaryLayer()
Returns the current auxiliary layer.
bool isAuxiliaryField(int index, int &srcIndex) const
Returns true if the field comes from the auxiliary layer, false otherwise.
QList< QPair< QColor, QString > > QgsNamedColorList
List of colors paired with a friendly display name identifying the color.
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...
#define FALLTHROUGH
Definition: qgis.h:1111
QMap< int, QgsPropertyDefinition > QgsPropertiesDefinition
Definition of available properties.