QGIS API Documentation  3.23.0-Master (eb871beae0)
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  if ( mVectorLayer )
725  connect( mVectorLayer, &QObject::destroyed, widget, &QgsPanelWidget::acceptPanel );
726 
727  connect( widget, &QgsPropertyAssistantWidget::panelAccepted, this, [ = ] { updateGui(); } );
728 
729  panel->openPanel( widget );
730  return;
731  }
732  else
733  {
734  // Show the dialog version if not in a panel
735  QDialog *dlg = new QDialog( this );
736  QString key = QStringLiteral( "/UI/paneldialog/%1" ).arg( widget->panelTitle() );
737  QgsSettings settings;
738  dlg->restoreGeometry( settings.value( key ).toByteArray() );
739  dlg->setWindowTitle( widget->panelTitle() );
740  dlg->setLayout( new QVBoxLayout() );
741  dlg->layout()->addWidget( widget );
742  QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok );
743  connect( buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept );
744  connect( buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject );
745  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsPropertyOverrideButton::showHelp );
746  dlg->layout()->addWidget( buttonBox );
747 
748  if ( dlg->exec() == QDialog::Accepted )
749  {
750  widget->updateProperty( mProperty );
751  mExpressionString = mProperty.asExpression();
752  mFieldName = mProperty.field();
753  widget->acceptPanel();
754  updateSiblingWidgets( isActive() );
755  updateGui();
756 
757  emit changed();
758  }
759  settings.setValue( key, dlg->saveGeometry() );
760  }
761 }
762 
763 void QgsPropertyOverrideButton::updateGui()
764 {
765  bool hasExp = !mExpressionString.isEmpty();
766  bool hasField = !mFieldName.isEmpty();
767 
768  QIcon icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefine.svg" ) );
769  QString deftip = tr( "undefined" );
770  QString deftype;
771  if ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && hasExp )
772  {
773  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpressionOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpression.svg" ) );
774 
775  QRegularExpression rx( QStringLiteral( "^project_color\\('(.*)'\\)$" ) );
776  QRegularExpressionMatch match = rx.match( mExpressionString );
777  if ( match.hasMatch() )
778  {
779  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColorOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColor.svg" ) );
780  deftip = match.captured( 1 );
781  deftype = tr( "project color" );
782  }
783  else
784  {
785  QgsExpression exp( mExpressionString );
786  if ( exp.hasParserError() )
787  {
788  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpressionError.svg" ) );
789  deftip = tr( "Parse error: %1" ).arg( exp.parserErrorString() );
790  }
791  else
792  {
793  deftip = mExpressionString;
794  }
795  }
796  }
797  else if ( mProperty.propertyType() != QgsProperty::ExpressionBasedProperty && hasField )
798  {
799  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefine.svg" ) );
800 
801  if ( !mFieldNameList.contains( mFieldName ) && !mProperty.transformer() )
802  {
803  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineError.svg" ) );
804  deftip = tr( "'%1' field missing" ).arg( mFieldName );
805  }
806  else
807  {
808  deftip = mFieldName;
809  }
810  }
811 
812  setIcon( icon );
813 
814  // build full description for tool tip and popup dialog
815  mFullDescription = tr( "<b><u>Data defined override</u></b><br>" );
816 
817  mFullDescription += tr( "<b>Active: </b>%1&nbsp;&nbsp;&nbsp;<i>(ctrl|right-click toggles)</i><br>" ).arg( mProperty.isActive() ? tr( "yes" ) : tr( "no" ) );
818 
819  if ( !mUsageInfo.isEmpty() )
820  {
821  mFullDescription += tr( "<b>Usage:</b><br>%1<br>" ).arg( mUsageInfo );
822  }
823 
824  if ( !mInputDescription.isEmpty() )
825  {
826  mFullDescription += tr( "<b>Expected input:</b><br>%1<br>" ).arg( mInputDescription );
827  }
828 
829  if ( !mDataTypesString.isEmpty() )
830  {
831  mFullDescription += tr( "<b>Valid input types:</b><br>%1<br>" ).arg( mDataTypesString );
832  }
833 
834  if ( deftype.isEmpty() && deftip != tr( "undefined" ) )
835  {
836  deftype = mProperty.propertyType() == QgsProperty::ExpressionBasedProperty ? tr( "expression" ) : tr( "field" );
837  }
838 
839  // truncate long expressions, or tool tip may be too wide for screen
840  if ( deftip.length() > 75 )
841  {
842  deftip.truncate( 75 );
843  deftip.append( QChar( 0x2026 ) );
844  }
845 
846  mFullDescription += tr( "<b>Current definition (%1):</b><br>%2" ).arg( deftype, deftip );
847 
848  setToolTip( mFullDescription );
849 
850 }
851 
852 void QgsPropertyOverrideButton::setActivePrivate( bool active )
853 {
854  if ( mProperty.isActive() != active )
855  {
856  mProperty.setActive( active );
857  emit activated( mProperty.isActive() );
858  }
859 }
860 
861 void QgsPropertyOverrideButton::updateSiblingWidgets( bool state )
862 {
863  const auto constMSiblingWidgets = mSiblingWidgets;
864  for ( const SiblingWidget &sw : constMSiblingWidgets )
865  {
866  switch ( sw.mSiblingType )
867  {
868 
869  case SiblingCheckState:
870  {
871  // don't uncheck, only set to checked
872  if ( state )
873  {
874  QAbstractButton *btn = qobject_cast< QAbstractButton * >( sw.mWidgetPointer.data() );
875  if ( btn && btn->isCheckable() )
876  {
877  btn->setChecked( sw.mNatural ? state : !state );
878  }
879  else
880  {
881  QGroupBox *grpbx = qobject_cast< QGroupBox * >( sw.mWidgetPointer.data() );
882  if ( grpbx && grpbx->isCheckable() )
883  {
884  grpbx->setChecked( sw.mNatural ? state : !state );
885  }
886  }
887  }
888  break;
889  }
890 
891  case SiblingEnableState:
892  {
893  QLineEdit *le = qobject_cast< QLineEdit * >( sw.mWidgetPointer.data() );
894  if ( le )
895  le->setReadOnly( sw.mNatural ? !state : state );
896  else
897  sw.mWidgetPointer.data()->setEnabled( sw.mNatural ? state : !state );
898  break;
899  }
900 
901  case SiblingVisibility:
902  {
903  sw.mWidgetPointer.data()->setVisible( sw.mNatural ? state : !state );
904  break;
905  }
906 
907  case SiblingExpressionText:
908  {
909  QLineEdit *le = qobject_cast<QLineEdit *>( sw.mWidgetPointer.data() );
910  if ( le )
911  {
912  le->setText( mProperty.asExpression() );
913  }
914  else
915  {
916  QTextEdit *te = qobject_cast<QTextEdit *>( sw.mWidgetPointer.data() );
917  if ( te )
918  {
919  te->setText( mProperty.asExpression() );
920  }
921  }
922  break;
923  }
924 
925  case SiblingLinkedWidget:
926  {
927  if ( QgsColorButton *cb = qobject_cast< QgsColorButton * >( sw.mWidgetPointer.data() ) )
928  {
929  if ( state && mProperty.isProjectColor() )
930  {
931  QRegularExpression rx( QStringLiteral( "^project_color\\('(.*)'\\)$" ) );
932  QRegularExpressionMatch match = rx.match( mExpressionString );
933  if ( match.hasMatch() )
934  {
935  cb->linkToProjectColor( match.captured( 1 ) );
936  }
937  }
938  else
939  {
940  cb->linkToProjectColor( QString() );
941  }
942  }
943  break;
944  }
945  }
946  }
947 }
948 
949 
950 
952 {
953  if ( mProperty.isActive() != active )
954  {
955  mProperty.setActive( active );
956  emit changed();
957  emit activated( mProperty.isActive() );
958  }
959 }
960 
962 {
963  mExpressionContextGenerator = generator;
964 }
965 
967 {
968  for ( const SiblingWidget &sw : std::as_const( mSiblingWidgets ) )
969  {
970  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingLinkedWidget )
971  return;
972  }
973  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingLinkedWidget ) );
974 
975  if ( QgsColorButton *cb = qobject_cast< QgsColorButton * >( widget ) )
976  {
977  connect( cb, &QgsColorButton::unlinked, this, [ = ]
978  {
979  setActive( false );
980  updateGui();
981  } );
982  }
983 
984  updateSiblingWidgets( isActive() );
985 }
986 
987 void QgsPropertyOverrideButton::showHelp()
988 {
989  QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html#data-defined" ) );
990 }
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:47
StandardPropertyTemplate standardTemplate() const
Returns the property's standard template, if applicable.
Definition: qgsproperty.h:194
QString helpText() const
Helper text for using the property, including a description of the valid values for the property.
Definition: qgsproperty.h:178
DataType dataType() const
Returns the allowable field/value data type for the property.
Definition: qgsproperty.h:188
@ ColorNoAlpha
Color with no alpha channel.
Definition: qgsproperty.h:65
@ ColorWithAlpha
Color with alpha channel.
Definition: qgsproperty.h:64
QString name() const
Returns the name of the property.
Definition: qgsproperty.h:139
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:92
@ DataTypeBoolean
Property requires a boolean value.
Definition: qgsproperty.h:106
@ DataTypeNumeric
Property requires a numeric value.
Definition: qgsproperty.h:99
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:231
@ ExpressionBasedProperty
Expression based property (QgsExpressionBasedProperty)
Definition: qgsproperty.h:240
@ StaticProperty
Static property (QgsStaticProperty)
Definition: qgsproperty.h:238
@ FieldBasedProperty
Field based property (QgsFieldBasedProperty)
Definition: qgsproperty.h:239
@ InvalidProperty
Invalid (not set) property.
Definition: qgsproperty.h:237
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:2027
QMap< int, QgsPropertyDefinition > QgsPropertiesDefinition
Definition of available properties.