QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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( QStringLiteral( ", " ) );
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  mDefineMenu->addAction( mActionClearExpr );
533  }
534  else
535  {
536  mDefineMenu->addAction( mActionExpDialog );
537  mDefineMenu->addAction( mActionPasteExpr );
538  }
539 
540  if ( !mDefinition.name().isEmpty() && mDefinition.supportsAssistant() )
541  {
542  mDefineMenu->addSeparator();
543  mActionAssistant->setCheckable( mProperty.transformer() );
544  mActionAssistant->setChecked( mProperty.transformer() );
545  mDefineMenu->addAction( mActionAssistant );
546  }
547 }
548 
549 void QgsPropertyOverrideButton::menuActionTriggered( QAction *action )
550 {
551  if ( action == mActionActive )
552  {
553  setActivePrivate( mActionActive->data().toBool() );
554  updateGui();
555  emit changed();
556  }
557  else if ( action == mActionDescription )
558  {
559  showDescriptionDialog();
560  }
561  else if ( action == mActionExpDialog )
562  {
563  showExpressionDialog();
564  }
565  else if ( action == mActionExpression )
566  {
567  mProperty.setExpressionString( mExpressionString );
568  mProperty.setTransformer( nullptr );
569  setActivePrivate( true );
570  updateSiblingWidgets( isActive() );
571  updateGui();
572  emit changed();
573  }
574  else if ( action == mActionCopyExpr )
575  {
576  QApplication::clipboard()->setText( mExpressionString );
577  }
578  else if ( action == mActionPasteExpr )
579  {
580  QString exprString = QApplication::clipboard()->text();
581  if ( !exprString.isEmpty() )
582  {
583  mExpressionString = exprString;
584  mProperty.setExpressionString( mExpressionString );
585  mProperty.setTransformer( nullptr );
586  setActivePrivate( true );
587  updateSiblingWidgets( isActive() );
588  updateGui();
589  emit changed();
590  }
591  }
592  else if ( action == mActionClearExpr )
593  {
594  setActivePrivate( false );
595  mProperty.setStaticValue( QVariant() );
596  mProperty.setTransformer( nullptr );
597  mExpressionString.clear();
598  updateSiblingWidgets( isActive() );
599  updateGui();
600  emit changed();
601  }
602  else if ( action == mActionAssistant )
603  {
604  showAssistant();
605  }
606  else if ( action == mActionCreateAuxiliaryField )
607  {
608  emit createAuxiliaryField();
609  }
610  else if ( mFieldsMenu->actions().contains( action ) ) // a field name clicked
611  {
612  if ( action->isEnabled() )
613  {
614  if ( mFieldName != action->text() )
615  {
616  mFieldName = action->data().toString();
617  }
618  mProperty.setField( mFieldName );
619  mProperty.setTransformer( nullptr );
620  setActivePrivate( true );
621  updateSiblingWidgets( isActive() );
622  updateGui();
623  emit changed();
624  }
625  }
626  else if ( mVariablesMenu->actions().contains( action ) ) // a variable name clicked
627  {
628  if ( mExpressionString != action->text().prepend( "@" ) )
629  {
630  mExpressionString = action->data().toString().prepend( "@" );
631  }
632  mProperty.setExpressionString( mExpressionString );
633  mProperty.setTransformer( nullptr );
634  setActivePrivate( true );
635  updateSiblingWidgets( isActive() );
636  updateGui();
637  emit changed();
638  }
639  else if ( mColorsMenu->actions().contains( action ) ) // a color name clicked
640  {
641  if ( mExpressionString != QStringLiteral( "project_color('%1')" ).arg( action->text() ) )
642  {
643  mExpressionString = QStringLiteral( "project_color('%1')" ).arg( action->text() );
644  }
645  mProperty.setExpressionString( mExpressionString );
646  mProperty.setTransformer( nullptr );
647  setActivePrivate( true );
648  updateSiblingWidgets( isActive() );
649  updateGui();
650  emit changed();
651  }
652 }
654 
655 void QgsPropertyOverrideButton::showDescriptionDialog()
656 {
657  QgsMessageViewer *mv = new QgsMessageViewer( this );
658  mv->setWindowTitle( tr( "Data Definition Description" ) );
659  mv->setMessageAsHtml( mFullDescription );
660  mv->exec();
661 }
662 
663 
664 void QgsPropertyOverrideButton::showExpressionDialog()
665 {
666  QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : QgsExpressionContext();
667 
668  // build sensible initial expression text - see https://github.com/qgis/QGIS/issues/26526
669  QString currentExpression = ( mProperty.propertyType() == QgsProperty::StaticProperty && !mProperty.staticValue().isValid() ) ? QString()
670  : mProperty.asExpression();
671 
672  QgsExpressionBuilderDialog d( const_cast<QgsVectorLayer *>( mVectorLayer ), currentExpression, this, QStringLiteral( "generic" ), context );
673  d.setExpectedOutputFormat( mInputDescription );
674  if ( d.exec() == QDialog::Accepted )
675  {
676  mExpressionString = d.expressionText().trimmed();
677  mProperty.setExpressionString( mExpressionString );
678  mProperty.setTransformer( nullptr );
679  setActivePrivate( !mExpressionString.isEmpty() );
680  updateSiblingWidgets( isActive() );
681  updateGui();
682  emit changed();
683  }
684  activateWindow(); // reset focus to parent window
685 }
686 
687 void QgsPropertyOverrideButton::showAssistant()
688 {
689  //first step - try to convert any existing expression to a transformer if one doesn't
690  //already exist
691  if ( !mProperty.transformer() )
692  {
693  ( void )mProperty.convertToTransformer();
694  }
695 
697  QgsPropertyAssistantWidget *widget = new QgsPropertyAssistantWidget( panel, mDefinition, mProperty, mVectorLayer );
698  widget->registerExpressionContextGenerator( mExpressionContextGenerator );
699  widget->setSymbol( mSymbol ); // we only show legend preview in dialog version
700 
701  if ( panel && panel->dockMode() )
702  {
703  connect( widget, &QgsPropertyAssistantWidget::widgetChanged, this, [this, widget]
704  {
705  widget->updateProperty( this->mProperty );
706  mExpressionString = this->mProperty.asExpression();
707  mFieldName = this->mProperty.field();
708  updateSiblingWidgets( isActive() );
709  this->emit changed();
710  } );
711 
712  connect( widget, &QgsPropertyAssistantWidget::panelAccepted, this, [ = ] { updateGui(); } );
713 
714  panel->openPanel( widget );
715  return;
716  }
717  else
718  {
719  // Show the dialog version if not in a panel
720  QDialog *dlg = new QDialog( this );
721  QString key = QStringLiteral( "/UI/paneldialog/%1" ).arg( widget->panelTitle() );
722  QgsSettings settings;
723  dlg->restoreGeometry( settings.value( key ).toByteArray() );
724  dlg->setWindowTitle( widget->panelTitle() );
725  dlg->setLayout( new QVBoxLayout() );
726  dlg->layout()->addWidget( widget );
727  QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok );
728  connect( buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept );
729  connect( buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject );
730  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsPropertyOverrideButton::showHelp );
731  dlg->layout()->addWidget( buttonBox );
732 
733  if ( dlg->exec() == QDialog::Accepted )
734  {
735  widget->updateProperty( mProperty );
736  mExpressionString = mProperty.asExpression();
737  mFieldName = mProperty.field();
738  widget->acceptPanel();
739  updateSiblingWidgets( isActive() );
740  updateGui();
741 
742  emit changed();
743  }
744  settings.setValue( key, dlg->saveGeometry() );
745  }
746 }
747 
748 void QgsPropertyOverrideButton::updateGui()
749 {
750  bool hasExp = !mExpressionString.isEmpty();
751  bool hasField = !mFieldName.isEmpty();
752 
753  QIcon icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefine.svg" ) );
754  QString deftip = tr( "undefined" );
755  QString deftype;
756  if ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && hasExp )
757  {
758  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpressionOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpression.svg" ) );
759 
760  QRegularExpression rx( QStringLiteral( "^project_color\\('(.*)'\\)$" ) );
761  QRegularExpressionMatch match = rx.match( mExpressionString );
762  if ( match.hasMatch() )
763  {
764  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColorOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColor.svg" ) );
765  deftip = match.captured( 1 );
766  deftype = tr( "project color" );
767  }
768  else
769  {
770  QgsExpression exp( mExpressionString );
771  if ( exp.hasParserError() )
772  {
773  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpressionError.svg" ) );
774  deftip = tr( "Parse error: %1" ).arg( exp.parserErrorString() );
775  }
776  else
777  {
778  deftip = mExpressionString;
779  }
780  }
781  }
782  else if ( mProperty.propertyType() != QgsProperty::ExpressionBasedProperty && hasField )
783  {
784  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefine.svg" ) );
785 
786  if ( !mFieldNameList.contains( mFieldName ) && !mProperty.transformer() )
787  {
788  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineError.svg" ) );
789  deftip = tr( "'%1' field missing" ).arg( mFieldName );
790  }
791  else
792  {
793  deftip = mFieldName;
794  }
795  }
796 
797  setIcon( icon );
798 
799  // build full description for tool tip and popup dialog
800  mFullDescription = tr( "<b><u>Data defined override</u></b><br>" );
801 
802  mFullDescription += tr( "<b>Active: </b>%1&nbsp;&nbsp;&nbsp;<i>(ctrl|right-click toggles)</i><br>" ).arg( mProperty.isActive() ? tr( "yes" ) : tr( "no" ) );
803 
804  if ( !mUsageInfo.isEmpty() )
805  {
806  mFullDescription += tr( "<b>Usage:</b><br>%1<br>" ).arg( mUsageInfo );
807  }
808 
809  if ( !mInputDescription.isEmpty() )
810  {
811  mFullDescription += tr( "<b>Expected input:</b><br>%1<br>" ).arg( mInputDescription );
812  }
813 
814  if ( !mDataTypesString.isEmpty() )
815  {
816  mFullDescription += tr( "<b>Valid input types:</b><br>%1<br>" ).arg( mDataTypesString );
817  }
818 
819  if ( deftype.isEmpty() && deftip != tr( "undefined" ) )
820  {
821  deftype = mProperty.propertyType() == QgsProperty::ExpressionBasedProperty ? tr( "expression" ) : tr( "field" );
822  }
823 
824  // truncate long expressions, or tool tip may be too wide for screen
825  if ( deftip.length() > 75 )
826  {
827  deftip.truncate( 75 );
828  deftip.append( QChar( 0x2026 ) );
829  }
830 
831  mFullDescription += tr( "<b>Current definition (%1):</b><br>%2" ).arg( deftype, deftip );
832 
833  setToolTip( mFullDescription );
834 
835 }
836 
837 void QgsPropertyOverrideButton::setActivePrivate( bool active )
838 {
839  if ( mProperty.isActive() != active )
840  {
841  mProperty.setActive( active );
842  emit activated( mProperty.isActive() );
843  }
844 }
845 
846 void QgsPropertyOverrideButton::updateSiblingWidgets( bool state )
847 {
848  const auto constMSiblingWidgets = mSiblingWidgets;
849  for ( const SiblingWidget &sw : constMSiblingWidgets )
850  {
851  switch ( sw.mSiblingType )
852  {
853 
854  case SiblingCheckState:
855  {
856  // don't uncheck, only set to checked
857  if ( state )
858  {
859  QAbstractButton *btn = qobject_cast< QAbstractButton * >( sw.mWidgetPointer.data() );
860  if ( btn && btn->isCheckable() )
861  {
862  btn->setChecked( sw.mNatural ? state : !state );
863  }
864  else
865  {
866  QGroupBox *grpbx = qobject_cast< QGroupBox * >( sw.mWidgetPointer.data() );
867  if ( grpbx && grpbx->isCheckable() )
868  {
869  grpbx->setChecked( sw.mNatural ? state : !state );
870  }
871  }
872  }
873  break;
874  }
875 
876  case SiblingEnableState:
877  {
878  QLineEdit *le = qobject_cast< QLineEdit * >( sw.mWidgetPointer.data() );
879  if ( le )
880  le->setReadOnly( sw.mNatural ? !state : state );
881  else
882  sw.mWidgetPointer.data()->setEnabled( sw.mNatural ? state : !state );
883  break;
884  }
885 
886  case SiblingVisibility:
887  {
888  sw.mWidgetPointer.data()->setVisible( sw.mNatural ? state : !state );
889  break;
890  }
891 
892  case SiblingExpressionText:
893  {
894  QLineEdit *le = qobject_cast<QLineEdit *>( sw.mWidgetPointer.data() );
895  if ( le )
896  {
897  le->setText( mProperty.asExpression() );
898  }
899  else
900  {
901  QTextEdit *te = qobject_cast<QTextEdit *>( sw.mWidgetPointer.data() );
902  if ( te )
903  {
904  te->setText( mProperty.asExpression() );
905  }
906  }
907  break;
908  }
909 
910  case SiblingLinkedWidget:
911  {
912  if ( QgsColorButton *cb = qobject_cast< QgsColorButton * >( sw.mWidgetPointer.data() ) )
913  {
914  if ( state && mProperty.isProjectColor() )
915  {
916  QRegularExpression rx( QStringLiteral( "^project_color\\('(.*)'\\)$" ) );
917  QRegularExpressionMatch match = rx.match( mExpressionString );
918  if ( match.hasMatch() )
919  {
920  cb->linkToProjectColor( match.captured( 1 ) );
921  }
922  }
923  else
924  {
925  cb->linkToProjectColor( QString() );
926  }
927  }
928  break;
929  }
930  }
931  }
932 }
933 
934 
935 
937 {
938  if ( mProperty.isActive() != active )
939  {
940  mProperty.setActive( active );
941  emit changed();
942  emit activated( mProperty.isActive() );
943  }
944 }
945 
947 {
948  mExpressionContextGenerator = generator;
949 }
950 
952 {
953  for ( const SiblingWidget &sw : qgis::as_const( mSiblingWidgets ) )
954  {
955  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingLinkedWidget )
956  return;
957  }
958  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingLinkedWidget ) );
959 
960  if ( QgsColorButton *cb = qobject_cast< QgsColorButton * >( widget ) )
961  {
962  connect( cb, &QgsColorButton::unlinked, this, [ = ]
963  {
964  setActive( false );
965  updateGui();
966  } );
967  }
968 
969  updateSiblingWidgets( isActive() );
970 }
971 
972 void QgsPropertyOverrideButton::showHelp()
973 {
974  QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html#data-defined" ) );
975 }
Class for parsing and evaluation of expressions (formerly called "search strings").
void openPanel(QgsPanelWidget *panel)
Open a panel or dialog depending on dock mode setting If dock mode is true this method will emit the ...
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
static QPixmap createMenuIcon(const QColor &color, bool showChecks=true)
Creates an icon for displaying a color in a drop-down menu.
bool supportsAssistant() const
Returns true if the property is of a type which is compatible with property override assistants...
QString asExpression() const
Returns an expression string representing the state of the property, or an empty string if the proper...
Field based property (QgsFieldBasedProperty)
Definition: qgsproperty.h:238
bool isReadOnly(const QString &name) const
Returns whether a variable is read only, and should not be modifiable by users.
bool dockMode()
Returns the dock mode state.
void setExpectedOutputFormat(const QString &expected)
Set the expected format string, which is shown in the dialog.
A color scheme which contains project specific colors set through project properties dialog...
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
Invalid (not set) property.
Definition: qgsproperty.h:236
Expression based property (QgsExpressionBasedProperty)
Definition: qgsproperty.h:239
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly...
void setSymbol(std::shared_ptr< QgsSymbol > symbol)
Sets a symbol which can be used for previews inside the widget.
Class allowing to manage the auxiliary storage for a vector layer.
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 setVectorLayer(const QgsVectorLayer *layer)
Sets the vector layer associated with the button.
void mouseReleaseEvent(QMouseEvent *event) override
void registerExpressionContextGenerator(QgsExpressionContextGenerator *generator)
Register an expression context generator class that will be used to retrieve an expression context fo...
A cross platform button subclass for selecting colors.
Container of fields for a vector layer.
Definition: qgsfields.h:42
bool isActive() const
Returns true if the button has an active property.
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
Color with alpha channel.
Definition: qgsproperty.h:64
Base class for any widget that can be shown as a inline panel.
void activated(bool isActive)
Emitted when the activated status of the widget changes.
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 isAuxiliaryField(int index, int &srcIndex) const
Returns true if the field comes from the auxiliary layer, false otherwise.
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
void registerVisibleWidget(QWidget *widget, bool natural=true)
Register a sibling widget that gets visible when the property is active, and hidden when the property...
QString parserErrorString() const
Returns parser error.
const QgsPropertyTransformer * transformer() const
Returns the existing transformer used for manipulating the calculated values for the property...
void createAuxiliaryField()
Emitted when creating a new auxiliary field.
void setMessageAsHtml(const QString &msg)
void setField(const QString &field)
Sets the field name the property references.
QList< QgsColorScheme * > schemes() const
Returns all color schemes in the registry.
bool isProjectColor() const
Returns true if the property is set to a linked project color.
virtual QgsExpressionContext createExpressionContext() const =0
This method needs to be reimplemented in all classes which implement this interface and return an exp...
int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:202
DataType dataType() const
Returns the allowable field/value data type for the property.
Definition: qgsproperty.h:187
QgsFields fields() const FINAL
Returns the list of fields of this layer.
#define FALLTHROUGH
Definition: qgis.h:656
Type propertyType() const
Returns the property type.
Property requires a boolean value.
Definition: qgsproperty.h:105
bool convertToTransformer()
Attempts to convert an existing expression based property to a base expression with corresponding tra...
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget...
QgsProperty toProperty() const
Returns a QgsProperty object encapsulating the current state of the widget.
void setToProperty(const QgsProperty &property)
Sets the widget to reflect the current state of a QgsProperty.
void acceptPanel()
Accept the panel.
void registerExpressionContextGenerator(QgsExpressionContextGenerator *generator)
Register an expression context generator class that will be used to retrieve an expression context fo...
Shows a user-friendly assistant guiding users through the creation of QgsProperty overrides...
void setActive(bool active)
Sets whether the property is currently active.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Abstract base class for QgsPropertyCollection like objects.
Property requires a numeric value.
Definition: qgsproperty.h:98
QList< QPair< QColor, QString > > QgsNamedColorList
List of colors paired with a friendly display name identifying the color.
QString panelTitle()
The title of the panel.
virtual QgsProperty property(int key) const =0
Returns a matching property from the collection, if one exists.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:48
QStringList variableNames() const
Returns a list of variables names set by all scopes in the context.
A store for object properties.
Definition: qgsproperty.h:229
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window&#39;s toolbar icons.
QgsAuxiliaryLayer * auxiliaryLayer()
Returns the current auxiliary layer.
void widgetChanged()
Emitted when the widget state changes.
void setExpressionString(const QString &expression)
Sets the expression to use for the property value.
Definition for a property.
Definition: qgsproperty.h:46
QString field() const
Returns the current field name the property references.
QMap< int, QgsPropertyDefinition > QgsPropertiesDefinition
Definition of available properties.
QString helpText() const
Helper text for using the property, including a description of the valid values for the property...
Definition: qgsproperty.h:177
Abstract interface for generating an expression context.
void registerLinkedWidget(QWidget *widget)
Registers a widget which is linked to this button.
void changed()
Emitted when property definition changes.
void registerCheckedWidget(QWidget *widget, bool natural=true)
Register a sibling widget that gets checked when the property is active.
static QgsColorSchemeRegistry * colorSchemeRegistry()
Returns the application&#39;s color scheme registry, used for managing color schemes. ...
void unlinked()
Emitted when the color is unlinked, e.g.
Property requires a string value.
Definition: qgsproperty.h:91
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
void registerExpressionWidget(QWidget *widget)
Register a sibling widget (line edit, text edit) that will receive the property as an expression...
int propertyKey() const
Returns the property key linked to the button.
QgsPropertyOverrideButton(QWidget *parent=nullptr, const QgsVectorLayer *layer=nullptr)
Constructor for QgsPropertyOverrideButton.
A generic message view for displaying QGIS messages.
QVariant staticValue() const
Returns the current static value for the property.
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:36
QString name() const
Returns the name of the property.
Definition: qgsproperty.h:138
Represents a vector layer which manages a vector based data sets.
void updateFieldLists()
Updates list of fields.
Static property (QgsStaticProperty)
Definition: qgsproperty.h:237
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)...
QgsNamedColorList fetchColors(const QString &context=QString(), const QColor &baseColor=QColor()) override
Gets a list of colors from the scheme.
void setStaticValue(const QVariant &value)
Sets the static value for the property.
A generic dialog for building expression strings.
void setActive(bool active)
Set whether the current property override definition is to be used.
void setTransformer(QgsPropertyTransformer *transformer)
Sets an optional transformer to use for manipulating the calculated values for the property...
bool isActive() const
Returns whether the property is currently active.
StandardPropertyTemplate standardTemplate() const
Returns the property&#39;s standard template, if applicable.
Definition: qgsproperty.h:193
void updateProperty(QgsProperty &property)
Updates a property in place to corresponding to the current settings shown in the widget...
Color with no alpha channel.
Definition: qgsproperty.h:65