QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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  mFieldTypeList.clear();
156 
157  if ( mVectorLayer )
158  {
159  // store just a list of fields of unknown type or those that match the expected type
160  const QgsFields fields = mVectorLayer->fields();
161  for ( const QgsField &f : fields )
162  {
163  bool fieldMatch = false;
164  QString fieldType;
165 
166  switch ( mDataTypes )
167  {
169  fieldMatch = true;
170  break;
171 
173  fieldMatch = f.isNumeric() || f.type() == QVariant::String;
174  break;
175 
177  fieldMatch = f.type() == QVariant::String;
178  break;
179  }
180 
181  switch ( f.type() )
182  {
183  case QVariant::String:
184  fieldType = tr( "string" );
185  break;
186  case QVariant::Int:
187  fieldType = tr( "integer" );
188  break;
189  case QVariant::LongLong:
190  fieldType = tr( "integer64" );
191  break;
192  case QVariant::Double:
193  fieldType = tr( "double" );
194  break;
195  case QVariant::Bool:
196  fieldType = tr( "boolean" );
197  break;
198  default:
199  fieldType = tr( "unknown type" );
200  }
201  if ( fieldMatch )
202  {
203  mFieldNameList << f.name();
204  mFieldTypeList << fieldType;
205  }
206  }
207  }
208 }
209 
211 {
212  return mProperty;
213 }
214 
216 {
217  mVectorLayer = layer;
218 }
219 
220 void QgsPropertyOverrideButton::registerCheckedWidget( QWidget *widget, bool natural )
221 {
222  const auto constMSiblingWidgets = mSiblingWidgets;
223  for ( const SiblingWidget &sw : constMSiblingWidgets )
224  {
225  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingCheckState )
226  return;
227  }
228  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingCheckState, natural ) );
229  updateSiblingWidgets( isActive() );
230 }
231 
232 void QgsPropertyOverrideButton::registerEnabledWidget( QWidget *widget, bool natural )
233 {
234  const auto constMSiblingWidgets = mSiblingWidgets;
235  for ( const SiblingWidget &sw : constMSiblingWidgets )
236  {
237  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingEnableState )
238  return;
239  }
240  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingEnableState, natural ) );
241  updateSiblingWidgets( isActive() );
242 }
243 
244 void QgsPropertyOverrideButton::registerVisibleWidget( QWidget *widget, bool natural )
245 {
246  const auto constMSiblingWidgets = mSiblingWidgets;
247  for ( const SiblingWidget &sw : constMSiblingWidgets )
248  {
249  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingVisibility )
250  return;
251  }
252  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingVisibility, natural ) );
253  updateSiblingWidgets( isActive() );
254 }
255 
257 {
258  const auto constMSiblingWidgets = mSiblingWidgets;
259  for ( const SiblingWidget &sw : constMSiblingWidgets )
260  {
261  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingExpressionText )
262  return;
263  }
264  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingExpressionText ) );
265  updateSiblingWidgets( isActive() );
266 }
267 
268 
270 {
271  // Ctrl-click to toggle activated state
272  if ( ( event->modifiers() & ( Qt::ControlModifier ) )
273  || event->button() == Qt::RightButton )
274  {
275  setActivePrivate( !mProperty.isActive() );
276  updateGui();
277  emit changed();
278  event->ignore();
279  return;
280  }
281 
282  // pass to default behavior
283  QToolButton::mousePressEvent( event );
284 }
285 
287 {
288  if ( property )
289  {
290  switch ( property.propertyType() )
291  {
294  break;
296  {
297  mFieldName = property.field();
298  break;
299  }
301  {
302  mExpressionString = property.expressionString();
303  break;
304  }
305  }
306  }
307  else
308  {
309  mFieldName.clear();
310  mExpressionString.clear();
311  }
312  mProperty = property;
313  setActive( mProperty && mProperty.isActive() );
314  updateSiblingWidgets( isActive() );
315  updateGui();
316 }
317 
319 void QgsPropertyOverrideButton::aboutToShowMenu()
320 {
321  mDefineMenu->clear();
322  // update fields so that changes made to layer's fields are reflected
324 
325  bool hasExp = !mExpressionString.isEmpty();
326  QString ddTitle = tr( "Data defined override" );
327 
328  QAction *ddTitleAct = mDefineMenu->addAction( ddTitle );
329  QFont titlefont = ddTitleAct->font();
330  titlefont.setItalic( true );
331  ddTitleAct->setFont( titlefont );
332  ddTitleAct->setEnabled( false );
333 
334  bool addActiveAction = false;
335  if ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && hasExp )
336  {
337  QgsExpression exp( mExpressionString );
338  // whether expression is parse-able
339  addActiveAction = !exp.hasParserError();
340  }
341  else if ( mProperty.propertyType() == QgsProperty::FieldBasedProperty )
342  {
343  // whether field exists
344  addActiveAction = mFieldNameList.contains( mFieldName );
345  }
346 
347  if ( addActiveAction )
348  {
349  ddTitleAct->setText( ddTitle + " (" + ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty ? tr( "expression" ) : tr( "field" ) ) + ')' );
350  mDefineMenu->addAction( mActionActive );
351  mActionActive->setText( mProperty.isActive() ? tr( "Deactivate" ) : tr( "Activate" ) );
352  mActionActive->setData( QVariant( !mProperty.isActive() ) );
353  }
354 
355  if ( !mFullDescription.isEmpty() )
356  {
357  mDefineMenu->addAction( mActionDescription );
358  }
359 
360  mDefineMenu->addSeparator();
361 
362  // deactivate button if field already exists
363  if ( mAuxiliaryStorageEnabled && mVectorLayer )
364  {
365  mDefineMenu->addAction( mActionCreateAuxiliaryField );
366 
367  const QgsAuxiliaryLayer *alayer = mVectorLayer->auxiliaryLayer();
368 
369  mActionCreateAuxiliaryField->setEnabled( true );
370  mActionCreateAuxiliaryField->setChecked( false );
371 
372  int index = mVectorLayer->fields().indexFromName( mFieldName );
373  int srcIndex;
374  if ( index >= 0 && alayer && mVectorLayer->isAuxiliaryField( index, srcIndex ) )
375  {
376  mActionCreateAuxiliaryField->setEnabled( false );
377  mActionCreateAuxiliaryField->setChecked( true );
378  }
379  }
380 
381  bool fieldActive = false;
382  if ( !mDataTypesString.isEmpty() )
383  {
384  QAction *fieldTitleAct = mDefineMenu->addAction( tr( "Attribute Field" ) );
385  fieldTitleAct->setFont( titlefont );
386  fieldTitleAct->setEnabled( false );
387 
388  mDefineMenu->addAction( mActionDataTypes );
389 
390  mFieldsMenu->clear();
391 
392  if ( !mFieldNameList.isEmpty() )
393  {
394 
395  for ( int j = 0; j < mFieldNameList.count(); ++j )
396  {
397  QString fldname = mFieldNameList.at( j );
398  QAction *act = mFieldsMenu->addAction( fldname + " (" + mFieldTypeList.at( j ) + ')' );
399  act->setData( QVariant( fldname ) );
400  if ( mFieldName == fldname )
401  {
402  act->setCheckable( true );
403  act->setChecked( mProperty.propertyType() == QgsProperty::FieldBasedProperty );
404  fieldActive = mProperty.propertyType() == QgsProperty::FieldBasedProperty;
405  }
406  }
407  }
408  else
409  {
410  QAction *act = mFieldsMenu->addAction( tr( "No matching field types found" ) );
411  act->setEnabled( false );
412  }
413 
414  mDefineMenu->addSeparator();
415  }
416 
417  mFieldsMenu->menuAction()->setCheckable( true );
418  mFieldsMenu->menuAction()->setChecked( fieldActive && mProperty.propertyType() == QgsProperty::FieldBasedProperty && !mProperty.transformer() );
419 
420  bool colorActive = false;
421  mColorsMenu->clear();
424  {
425  // project colors menu
426  QAction *colorTitleAct = mDefineMenu->addAction( tr( "Project Color" ) );
427  colorTitleAct->setFont( titlefont );
428  colorTitleAct->setEnabled( false );
429 
430  QList<QgsProjectColorScheme *> projectSchemes;
431  QgsApplication::colorSchemeRegistry()->schemes( projectSchemes );
432  if ( projectSchemes.length() > 0 )
433  {
434  QgsProjectColorScheme *scheme = projectSchemes.at( 0 );
435  const QgsNamedColorList colors = scheme->fetchColors();
436  for ( const auto &color : colors )
437  {
438  if ( color.second.isEmpty() )
439  continue;
440 
441  QPixmap icon = QgsColorButton::createMenuIcon( color.first, mDefinition.standardTemplate() == QgsPropertyDefinition::ColorWithAlpha );
442  QAction *act = mColorsMenu->addAction( color.second );
443  act->setIcon( icon );
444  if ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && hasExp && mExpressionString == QStringLiteral( "project_color('%1')" ).arg( color.second ) )
445  {
446  act->setCheckable( true );
447  act->setChecked( true );
448  colorActive = true;
449  }
450  }
451  }
452 
453  if ( mColorsMenu->actions().isEmpty() )
454  {
455  QAction *act = mColorsMenu->addAction( tr( "No colors set" ) );
456  act->setEnabled( false );
457  }
458 
459  mDefineMenu->addAction( mActionColors );
460  mColorsMenu->menuAction()->setCheckable( true );
461  mColorsMenu->menuAction()->setChecked( colorActive && !mProperty.transformer() );
462 
463  mDefineMenu->addSeparator();
464  }
465 
466  QAction *exprTitleAct = mDefineMenu->addAction( tr( "Expression" ) );
467  exprTitleAct->setFont( titlefont );
468  exprTitleAct->setEnabled( false );
469 
470  mVariablesMenu->clear();
471  bool variableActive = false;
472  if ( mExpressionContextGenerator )
473  {
474  QgsExpressionContext context = mExpressionContextGenerator->createExpressionContext();
475  QStringList variables = context.variableNames();
476  const auto constVariables = variables;
477  for ( const QString &variable : constVariables )
478  {
479  if ( context.isReadOnly( variable ) ) //only want to show user-set variables
480  continue;
481  if ( variable.startsWith( '_' ) ) //no hidden variables
482  continue;
483 
484  QAction *act = mVariablesMenu->addAction( variable );
485  act->setData( QVariant( variable ) );
486 
487  if ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && hasExp && mExpressionString == '@' + variable )
488  {
489  act->setCheckable( true );
490  act->setChecked( true );
491  variableActive = true;
492  }
493  }
494  }
495 
496  if ( mVariablesMenu->actions().isEmpty() )
497  {
498  QAction *act = mVariablesMenu->addAction( tr( "No variables set" ) );
499  act->setEnabled( false );
500  }
501 
502  mDefineMenu->addAction( mActionVariables );
503  mVariablesMenu->menuAction()->setCheckable( true );
504  mVariablesMenu->menuAction()->setChecked( variableActive && !mProperty.transformer() );
505 
506  if ( hasExp )
507  {
508  QString expString = mExpressionString;
509  if ( expString.length() > 35 )
510  {
511  expString.truncate( 35 );
512  expString.append( QChar( 0x2026 ) );
513  }
514 
515  expString.prepend( tr( "Current: " ) );
516 
517  if ( !mActionExpression )
518  {
519  mActionExpression = new QAction( expString, this );
520  mActionExpression->setCheckable( true );
521  }
522  else
523  {
524  mActionExpression->setText( expString );
525  }
526  mDefineMenu->addAction( mActionExpression );
527  mActionExpression->setChecked( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && !variableActive && !colorActive && !mProperty.transformer() );
528 
529  mDefineMenu->addAction( mActionExpDialog );
530  mDefineMenu->addAction( mActionCopyExpr );
531  mDefineMenu->addAction( mActionPasteExpr );
532  }
533  else
534  {
535  mDefineMenu->addAction( mActionExpDialog );
536  mDefineMenu->addAction( mActionPasteExpr );
537  }
538 
539  if ( hasExp || !mFieldName.isEmpty() )
540  {
541  mDefineMenu->addSeparator();
542  mDefineMenu->addAction( mActionClearExpr );
543  }
544 
545  if ( !mDefinition.name().isEmpty() && mDefinition.supportsAssistant() )
546  {
547  mDefineMenu->addSeparator();
548  mActionAssistant->setCheckable( mProperty.transformer() );
549  mActionAssistant->setChecked( mProperty.transformer() );
550  mDefineMenu->addAction( mActionAssistant );
551  }
552 }
553 
554 void QgsPropertyOverrideButton::menuActionTriggered( QAction *action )
555 {
556  if ( action == mActionActive )
557  {
558  setActivePrivate( mActionActive->data().toBool() );
559  updateGui();
560  emit changed();
561  }
562  else if ( action == mActionDescription )
563  {
564  showDescriptionDialog();
565  }
566  else if ( action == mActionExpDialog )
567  {
568  showExpressionDialog();
569  }
570  else if ( action == mActionExpression )
571  {
572  mProperty.setExpressionString( mExpressionString );
573  mProperty.setTransformer( nullptr );
574  setActivePrivate( true );
575  updateSiblingWidgets( isActive() );
576  updateGui();
577  emit changed();
578  }
579  else if ( action == mActionCopyExpr )
580  {
581  QApplication::clipboard()->setText( mExpressionString );
582  }
583  else if ( action == mActionPasteExpr )
584  {
585  QString exprString = QApplication::clipboard()->text();
586  if ( !exprString.isEmpty() )
587  {
588  mExpressionString = exprString;
589  mProperty.setExpressionString( mExpressionString );
590  mProperty.setTransformer( nullptr );
591  setActivePrivate( true );
592  updateSiblingWidgets( isActive() );
593  updateGui();
594  emit changed();
595  }
596  }
597  else if ( action == mActionClearExpr )
598  {
599  setActivePrivate( false );
600  mProperty.setStaticValue( QVariant() );
601  mProperty.setTransformer( nullptr );
602  mExpressionString.clear();
603  updateSiblingWidgets( isActive() );
604  updateGui();
605  emit changed();
606  }
607  else if ( action == mActionAssistant )
608  {
609  showAssistant();
610  }
611  else if ( action == mActionCreateAuxiliaryField )
612  {
613  emit createAuxiliaryField();
614  }
615  else if ( mFieldsMenu->actions().contains( action ) ) // a field name clicked
616  {
617  if ( action->isEnabled() )
618  {
619  if ( mFieldName != action->text() )
620  {
621  mFieldName = action->data().toString();
622  }
623  mProperty.setField( mFieldName );
624  mProperty.setTransformer( nullptr );
625  setActivePrivate( true );
626  updateSiblingWidgets( isActive() );
627  updateGui();
628  emit changed();
629  }
630  }
631  else if ( mVariablesMenu->actions().contains( action ) ) // a variable name clicked
632  {
633  if ( mExpressionString != action->text().prepend( "@" ) )
634  {
635  mExpressionString = action->data().toString().prepend( "@" );
636  }
637  mProperty.setExpressionString( mExpressionString );
638  mProperty.setTransformer( nullptr );
639  setActivePrivate( true );
640  updateSiblingWidgets( isActive() );
641  updateGui();
642  emit changed();
643  }
644  else if ( mColorsMenu->actions().contains( action ) ) // a color name clicked
645  {
646  if ( mExpressionString != QStringLiteral( "project_color('%1')" ).arg( action->text() ) )
647  {
648  mExpressionString = QStringLiteral( "project_color('%1')" ).arg( action->text() );
649  }
650  mProperty.setExpressionString( mExpressionString );
651  mProperty.setTransformer( nullptr );
652  setActivePrivate( true );
653  updateSiblingWidgets( isActive() );
654  updateGui();
655  emit changed();
656  }
657 }
659 
660 void QgsPropertyOverrideButton::showDescriptionDialog()
661 {
662  QgsMessageViewer *mv = new QgsMessageViewer( this );
663  mv->setWindowTitle( tr( "Data Definition Description" ) );
664  mv->setMessageAsHtml( mFullDescription );
665  mv->exec();
666 }
667 
668 
669 void QgsPropertyOverrideButton::showExpressionDialog()
670 {
671  QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : QgsExpressionContext();
672 
673  // build sensible initial expression text - see https://github.com/qgis/QGIS/issues/26526
674  QString currentExpression = ( mProperty.propertyType() == QgsProperty::StaticProperty && !mProperty.staticValue().isValid() ) ? QString()
675  : mProperty.asExpression();
676 
677  QgsExpressionBuilderDialog d( const_cast<QgsVectorLayer *>( mVectorLayer ), currentExpression, this, QStringLiteral( "generic" ), context );
678  d.setExpectedOutputFormat( mInputDescription );
679  if ( d.exec() == QDialog::Accepted )
680  {
681  mExpressionString = d.expressionText().trimmed();
682  mProperty.setExpressionString( mExpressionString );
683  mProperty.setTransformer( nullptr );
684  setActivePrivate( !mExpressionString.isEmpty() );
685  updateSiblingWidgets( isActive() );
686  updateGui();
687  emit changed();
688  }
689  activateWindow(); // reset focus to parent window
690 }
691 
692 void QgsPropertyOverrideButton::showAssistant()
693 {
694  //first step - try to convert any existing expression to a transformer if one doesn't
695  //already exist
696  if ( !mProperty.transformer() )
697  {
698  ( void )mProperty.convertToTransformer();
699  }
700 
702  QgsPropertyAssistantWidget *widget = new QgsPropertyAssistantWidget( panel, mDefinition, mProperty, mVectorLayer );
703  widget->registerExpressionContextGenerator( mExpressionContextGenerator );
704  widget->setSymbol( mSymbol ); // we only show legend preview in dialog version
705 
706  if ( panel && panel->dockMode() )
707  {
708  connect( widget, &QgsPropertyAssistantWidget::widgetChanged, this, [this, widget]
709  {
710  widget->updateProperty( this->mProperty );
711  mExpressionString = this->mProperty.asExpression();
712  mFieldName = this->mProperty.field();
713  updateSiblingWidgets( isActive() );
714  this->emit changed();
715  } );
716 
717  // if the source layer is removed, we need to dismiss the assistant immediately
718  connect( mVectorLayer, &QObject::destroyed, widget, &QgsPanelWidget::acceptPanel );
719 
720  connect( widget, &QgsPropertyAssistantWidget::panelAccepted, this, [ = ] { updateGui(); } );
721 
722  panel->openPanel( widget );
723  return;
724  }
725  else
726  {
727  // Show the dialog version if not in a panel
728  QDialog *dlg = new QDialog( this );
729  QString key = QStringLiteral( "/UI/paneldialog/%1" ).arg( widget->panelTitle() );
730  QgsSettings settings;
731  dlg->restoreGeometry( settings.value( key ).toByteArray() );
732  dlg->setWindowTitle( widget->panelTitle() );
733  dlg->setLayout( new QVBoxLayout() );
734  dlg->layout()->addWidget( widget );
735  QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok );
736  connect( buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept );
737  connect( buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject );
738  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsPropertyOverrideButton::showHelp );
739  dlg->layout()->addWidget( buttonBox );
740 
741  if ( dlg->exec() == QDialog::Accepted )
742  {
743  widget->updateProperty( mProperty );
744  mExpressionString = mProperty.asExpression();
745  mFieldName = mProperty.field();
746  widget->acceptPanel();
747  updateSiblingWidgets( isActive() );
748  updateGui();
749 
750  emit changed();
751  }
752  settings.setValue( key, dlg->saveGeometry() );
753  }
754 }
755 
756 void QgsPropertyOverrideButton::updateGui()
757 {
758  bool hasExp = !mExpressionString.isEmpty();
759  bool hasField = !mFieldName.isEmpty();
760 
761  QIcon icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefine.svg" ) );
762  QString deftip = tr( "undefined" );
763  QString deftype;
764  if ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && hasExp )
765  {
766  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpressionOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpression.svg" ) );
767 
768  QRegularExpression rx( QStringLiteral( "^project_color\\('(.*)'\\)$" ) );
769  QRegularExpressionMatch match = rx.match( mExpressionString );
770  if ( match.hasMatch() )
771  {
772  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColorOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColor.svg" ) );
773  deftip = match.captured( 1 );
774  deftype = tr( "project color" );
775  }
776  else
777  {
778  QgsExpression exp( mExpressionString );
779  if ( exp.hasParserError() )
780  {
781  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpressionError.svg" ) );
782  deftip = tr( "Parse error: %1" ).arg( exp.parserErrorString() );
783  }
784  else
785  {
786  deftip = mExpressionString;
787  }
788  }
789  }
790  else if ( mProperty.propertyType() != QgsProperty::ExpressionBasedProperty && hasField )
791  {
792  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefine.svg" ) );
793 
794  if ( !mFieldNameList.contains( mFieldName ) && !mProperty.transformer() )
795  {
796  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineError.svg" ) );
797  deftip = tr( "'%1' field missing" ).arg( mFieldName );
798  }
799  else
800  {
801  deftip = mFieldName;
802  }
803  }
804 
805  setIcon( icon );
806 
807  // build full description for tool tip and popup dialog
808  mFullDescription = tr( "<b><u>Data defined override</u></b><br>" );
809 
810  mFullDescription += tr( "<b>Active: </b>%1&nbsp;&nbsp;&nbsp;<i>(ctrl|right-click toggles)</i><br>" ).arg( mProperty.isActive() ? tr( "yes" ) : tr( "no" ) );
811 
812  if ( !mUsageInfo.isEmpty() )
813  {
814  mFullDescription += tr( "<b>Usage:</b><br>%1<br>" ).arg( mUsageInfo );
815  }
816 
817  if ( !mInputDescription.isEmpty() )
818  {
819  mFullDescription += tr( "<b>Expected input:</b><br>%1<br>" ).arg( mInputDescription );
820  }
821 
822  if ( !mDataTypesString.isEmpty() )
823  {
824  mFullDescription += tr( "<b>Valid input types:</b><br>%1<br>" ).arg( mDataTypesString );
825  }
826 
827  if ( deftype.isEmpty() && deftip != tr( "undefined" ) )
828  {
829  deftype = mProperty.propertyType() == QgsProperty::ExpressionBasedProperty ? tr( "expression" ) : tr( "field" );
830  }
831 
832  // truncate long expressions, or tool tip may be too wide for screen
833  if ( deftip.length() > 75 )
834  {
835  deftip.truncate( 75 );
836  deftip.append( QChar( 0x2026 ) );
837  }
838 
839  mFullDescription += tr( "<b>Current definition (%1):</b><br>%2" ).arg( deftype, deftip );
840 
841  setToolTip( mFullDescription );
842 
843 }
844 
845 void QgsPropertyOverrideButton::setActivePrivate( bool active )
846 {
847  if ( mProperty.isActive() != active )
848  {
849  mProperty.setActive( active );
850  emit activated( mProperty.isActive() );
851  }
852 }
853 
854 void QgsPropertyOverrideButton::updateSiblingWidgets( bool state )
855 {
856  const auto constMSiblingWidgets = mSiblingWidgets;
857  for ( const SiblingWidget &sw : constMSiblingWidgets )
858  {
859  switch ( sw.mSiblingType )
860  {
861 
862  case SiblingCheckState:
863  {
864  // don't uncheck, only set to checked
865  if ( state )
866  {
867  QAbstractButton *btn = qobject_cast< QAbstractButton * >( sw.mWidgetPointer.data() );
868  if ( btn && btn->isCheckable() )
869  {
870  btn->setChecked( sw.mNatural ? state : !state );
871  }
872  else
873  {
874  QGroupBox *grpbx = qobject_cast< QGroupBox * >( sw.mWidgetPointer.data() );
875  if ( grpbx && grpbx->isCheckable() )
876  {
877  grpbx->setChecked( sw.mNatural ? state : !state );
878  }
879  }
880  }
881  break;
882  }
883 
884  case SiblingEnableState:
885  {
886  QLineEdit *le = qobject_cast< QLineEdit * >( sw.mWidgetPointer.data() );
887  if ( le )
888  le->setReadOnly( sw.mNatural ? !state : state );
889  else
890  sw.mWidgetPointer.data()->setEnabled( sw.mNatural ? state : !state );
891  break;
892  }
893 
894  case SiblingVisibility:
895  {
896  sw.mWidgetPointer.data()->setVisible( sw.mNatural ? state : !state );
897  break;
898  }
899 
900  case SiblingExpressionText:
901  {
902  QLineEdit *le = qobject_cast<QLineEdit *>( sw.mWidgetPointer.data() );
903  if ( le )
904  {
905  le->setText( mProperty.asExpression() );
906  }
907  else
908  {
909  QTextEdit *te = qobject_cast<QTextEdit *>( sw.mWidgetPointer.data() );
910  if ( te )
911  {
912  te->setText( mProperty.asExpression() );
913  }
914  }
915  break;
916  }
917 
918  case SiblingLinkedWidget:
919  {
920  if ( QgsColorButton *cb = qobject_cast< QgsColorButton * >( sw.mWidgetPointer.data() ) )
921  {
922  if ( state && mProperty.isProjectColor() )
923  {
924  QRegularExpression rx( QStringLiteral( "^project_color\\('(.*)'\\)$" ) );
925  QRegularExpressionMatch match = rx.match( mExpressionString );
926  if ( match.hasMatch() )
927  {
928  cb->linkToProjectColor( match.captured( 1 ) );
929  }
930  }
931  else
932  {
933  cb->linkToProjectColor( QString() );
934  }
935  }
936  break;
937  }
938  }
939  }
940 }
941 
942 
943 
945 {
946  if ( mProperty.isActive() != active )
947  {
948  mProperty.setActive( active );
949  emit changed();
950  emit activated( mProperty.isActive() );
951  }
952 }
953 
955 {
956  mExpressionContextGenerator = generator;
957 }
958 
960 {
961  for ( const SiblingWidget &sw : qgis::as_const( mSiblingWidgets ) )
962  {
963  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingLinkedWidget )
964  return;
965  }
966  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingLinkedWidget ) );
967 
968  if ( QgsColorButton *cb = qobject_cast< QgsColorButton * >( widget ) )
969  {
970  connect( cb, &QgsColorButton::unlinked, this, [ = ]
971  {
972  setActive( false );
973  updateGui();
974  } );
975  }
976 
977  updateSiblingWidgets( isActive() );
978 }
979 
980 void QgsPropertyOverrideButton::showHelp()
981 {
982  QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html#data-defined" ) );
983 }
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)
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
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.
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.
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:829
QMap< int, QgsPropertyDefinition > QgsPropertiesDefinition
Definition of available properties.