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