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