QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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  }
533  else
534  {
535  mDefineMenu->addAction( mActionExpDialog );
536  mDefineMenu->addAction( mActionPasteExpr );
537  }
538 
539  if ( hasExp || !mFieldName.isEmpty() )
540  {
541  mDefineMenu->addSeparator();
542  mDefineMenu->addAction( mActionClearExpr );
543  }
544 
545  if ( !mDefinition.name().isEmpty() && mDefinition.supportsAssistant() )
546  {
547  mDefineMenu->addSeparator();
548  mActionAssistant->setCheckable( mProperty.transformer() );
549  mActionAssistant->setChecked( mProperty.transformer() );
550  mDefineMenu->addAction( mActionAssistant );
551  }
552 }
553 
554 void QgsPropertyOverrideButton::menuActionTriggered( QAction *action )
555 {
556  if ( action == mActionActive )
557  {
558  setActivePrivate( mActionActive->data().toBool() );
559  updateGui();
560  emit changed();
561  }
562  else if ( action == mActionDescription )
563  {
564  showDescriptionDialog();
565  }
566  else if ( action == mActionExpDialog )
567  {
568  showExpressionDialog();
569  }
570  else if ( action == mActionExpression )
571  {
572  mProperty.setExpressionString( mExpressionString );
573  mProperty.setTransformer( nullptr );
574  setActivePrivate( true );
575  updateSiblingWidgets( isActive() );
576  updateGui();
577  emit changed();
578  }
579  else if ( action == mActionCopyExpr )
580  {
581  QApplication::clipboard()->setText( mExpressionString );
582  }
583  else if ( action == mActionPasteExpr )
584  {
585  QString exprString = QApplication::clipboard()->text();
586  if ( !exprString.isEmpty() )
587  {
588  mExpressionString = exprString;
589  mProperty.setExpressionString( mExpressionString );
590  mProperty.setTransformer( nullptr );
591  setActivePrivate( true );
592  updateSiblingWidgets( isActive() );
593  updateGui();
594  emit changed();
595  }
596  }
597  else if ( action == mActionClearExpr )
598  {
599  setActivePrivate( false );
600  mProperty.setStaticValue( QVariant() );
601  mProperty.setTransformer( nullptr );
602  mExpressionString.clear();
603  updateSiblingWidgets( isActive() );
604  updateGui();
605  emit changed();
606  }
607  else if ( action == mActionAssistant )
608  {
609  showAssistant();
610  }
611  else if ( action == mActionCreateAuxiliaryField )
612  {
613  emit createAuxiliaryField();
614  }
615  else if ( mFieldsMenu->actions().contains( action ) ) // a field name clicked
616  {
617  if ( action->isEnabled() )
618  {
619  if ( mFieldName != action->text() )
620  {
621  mFieldName = action->data().toString();
622  }
623  mProperty.setField( mFieldName );
624  mProperty.setTransformer( nullptr );
625  setActivePrivate( true );
626  updateSiblingWidgets( isActive() );
627  updateGui();
628  emit changed();
629  }
630  }
631  else if ( mVariablesMenu->actions().contains( action ) ) // a variable name clicked
632  {
633  if ( mExpressionString != action->text().prepend( "@" ) )
634  {
635  mExpressionString = action->data().toString().prepend( "@" );
636  }
637  mProperty.setExpressionString( mExpressionString );
638  mProperty.setTransformer( nullptr );
639  setActivePrivate( true );
640  updateSiblingWidgets( isActive() );
641  updateGui();
642  emit changed();
643  }
644  else if ( mColorsMenu->actions().contains( action ) ) // a color name clicked
645  {
646  if ( mExpressionString != QStringLiteral( "project_color('%1')" ).arg( action->text() ) )
647  {
648  mExpressionString = QStringLiteral( "project_color('%1')" ).arg( action->text() );
649  }
650  mProperty.setExpressionString( mExpressionString );
651  mProperty.setTransformer( nullptr );
652  setActivePrivate( true );
653  updateSiblingWidgets( isActive() );
654  updateGui();
655  emit changed();
656  }
657 }
659 
660 void QgsPropertyOverrideButton::showDescriptionDialog()
661 {
662  QgsMessageViewer *mv = new QgsMessageViewer( this );
663  mv->setWindowTitle( tr( "Data Definition Description" ) );
664  mv->setMessageAsHtml( mFullDescription );
665  mv->exec();
666 }
667 
668 
669 void QgsPropertyOverrideButton::showExpressionDialog()
670 {
671  QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : QgsExpressionContext();
672 
673  // build sensible initial expression text - see https://github.com/qgis/QGIS/issues/26526
674  QString currentExpression = ( mProperty.propertyType() == QgsProperty::StaticProperty && !mProperty.staticValue().isValid() ) ? QString()
675  : mProperty.asExpression();
676 
677  QgsExpressionBuilderDialog d( const_cast<QgsVectorLayer *>( mVectorLayer ), currentExpression, this, QStringLiteral( "generic" ), context );
678  d.setExpectedOutputFormat( mInputDescription );
679  if ( d.exec() == QDialog::Accepted )
680  {
681  mExpressionString = d.expressionText().trimmed();
682  mProperty.setExpressionString( mExpressionString );
683  mProperty.setTransformer( nullptr );
684  setActivePrivate( !mExpressionString.isEmpty() );
685  updateSiblingWidgets( isActive() );
686  updateGui();
687  emit changed();
688  }
689  activateWindow(); // reset focus to parent window
690 }
691 
692 void QgsPropertyOverrideButton::showAssistant()
693 {
694  //first step - try to convert any existing expression to a transformer if one doesn't
695  //already exist
696  if ( !mProperty.transformer() )
697  {
698  ( void )mProperty.convertToTransformer();
699  }
700 
702  QgsPropertyAssistantWidget *widget = new QgsPropertyAssistantWidget( panel, mDefinition, mProperty, mVectorLayer );
703  widget->registerExpressionContextGenerator( mExpressionContextGenerator );
704  widget->setSymbol( mSymbol ); // we only show legend preview in dialog version
705 
706  if ( panel && panel->dockMode() )
707  {
708  connect( widget, &QgsPropertyAssistantWidget::widgetChanged, this, [this, widget]
709  {
710  widget->updateProperty( this->mProperty );
711  mExpressionString = this->mProperty.asExpression();
712  mFieldName = this->mProperty.field();
713  updateSiblingWidgets( isActive() );
714  this->emit changed();
715  } );
716 
717  connect( widget, &QgsPropertyAssistantWidget::panelAccepted, this, [ = ] { updateGui(); } );
718 
719  panel->openPanel( widget );
720  return;
721  }
722  else
723  {
724  // Show the dialog version if not in a panel
725  QDialog *dlg = new QDialog( this );
726  QString key = QStringLiteral( "/UI/paneldialog/%1" ).arg( widget->panelTitle() );
727  QgsSettings settings;
728  dlg->restoreGeometry( settings.value( key ).toByteArray() );
729  dlg->setWindowTitle( widget->panelTitle() );
730  dlg->setLayout( new QVBoxLayout() );
731  dlg->layout()->addWidget( widget );
732  QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok );
733  connect( buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept );
734  connect( buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject );
735  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsPropertyOverrideButton::showHelp );
736  dlg->layout()->addWidget( buttonBox );
737 
738  if ( dlg->exec() == QDialog::Accepted )
739  {
740  widget->updateProperty( mProperty );
741  mExpressionString = mProperty.asExpression();
742  mFieldName = mProperty.field();
743  widget->acceptPanel();
744  updateSiblingWidgets( isActive() );
745  updateGui();
746 
747  emit changed();
748  }
749  settings.setValue( key, dlg->saveGeometry() );
750  }
751 }
752 
753 void QgsPropertyOverrideButton::updateGui()
754 {
755  bool hasExp = !mExpressionString.isEmpty();
756  bool hasField = !mFieldName.isEmpty();
757 
758  QIcon icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefine.svg" ) );
759  QString deftip = tr( "undefined" );
760  QString deftype;
761  if ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && hasExp )
762  {
763  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpressionOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpression.svg" ) );
764 
765  QRegularExpression rx( QStringLiteral( "^project_color\\('(.*)'\\)$" ) );
766  QRegularExpressionMatch match = rx.match( mExpressionString );
767  if ( match.hasMatch() )
768  {
769  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColorOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColor.svg" ) );
770  deftip = match.captured( 1 );
771  deftype = tr( "project color" );
772  }
773  else
774  {
775  QgsExpression exp( mExpressionString );
776  if ( exp.hasParserError() )
777  {
778  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpressionError.svg" ) );
779  deftip = tr( "Parse error: %1" ).arg( exp.parserErrorString() );
780  }
781  else
782  {
783  deftip = mExpressionString;
784  }
785  }
786  }
787  else if ( mProperty.propertyType() != QgsProperty::ExpressionBasedProperty && hasField )
788  {
789  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefine.svg" ) );
790 
791  if ( !mFieldNameList.contains( mFieldName ) && !mProperty.transformer() )
792  {
793  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineError.svg" ) );
794  deftip = tr( "'%1' field missing" ).arg( mFieldName );
795  }
796  else
797  {
798  deftip = mFieldName;
799  }
800  }
801 
802  setIcon( icon );
803 
804  // build full description for tool tip and popup dialog
805  mFullDescription = tr( "<b><u>Data defined override</u></b><br>" );
806 
807  mFullDescription += tr( "<b>Active: </b>%1&nbsp;&nbsp;&nbsp;<i>(ctrl|right-click toggles)</i><br>" ).arg( mProperty.isActive() ? tr( "yes" ) : tr( "no" ) );
808 
809  if ( !mUsageInfo.isEmpty() )
810  {
811  mFullDescription += tr( "<b>Usage:</b><br>%1<br>" ).arg( mUsageInfo );
812  }
813 
814  if ( !mInputDescription.isEmpty() )
815  {
816  mFullDescription += tr( "<b>Expected input:</b><br>%1<br>" ).arg( mInputDescription );
817  }
818 
819  if ( !mDataTypesString.isEmpty() )
820  {
821  mFullDescription += tr( "<b>Valid input types:</b><br>%1<br>" ).arg( mDataTypesString );
822  }
823 
824  if ( deftype.isEmpty() && deftip != tr( "undefined" ) )
825  {
826  deftype = mProperty.propertyType() == QgsProperty::ExpressionBasedProperty ? tr( "expression" ) : tr( "field" );
827  }
828 
829  // truncate long expressions, or tool tip may be too wide for screen
830  if ( deftip.length() > 75 )
831  {
832  deftip.truncate( 75 );
833  deftip.append( QChar( 0x2026 ) );
834  }
835 
836  mFullDescription += tr( "<b>Current definition (%1):</b><br>%2" ).arg( deftype, deftip );
837 
838  setToolTip( mFullDescription );
839 
840 }
841 
842 void QgsPropertyOverrideButton::setActivePrivate( bool active )
843 {
844  if ( mProperty.isActive() != active )
845  {
846  mProperty.setActive( active );
847  emit activated( mProperty.isActive() );
848  }
849 }
850 
851 void QgsPropertyOverrideButton::updateSiblingWidgets( bool state )
852 {
853  const auto constMSiblingWidgets = mSiblingWidgets;
854  for ( const SiblingWidget &sw : constMSiblingWidgets )
855  {
856  switch ( sw.mSiblingType )
857  {
858 
859  case SiblingCheckState:
860  {
861  // don't uncheck, only set to checked
862  if ( state )
863  {
864  QAbstractButton *btn = qobject_cast< QAbstractButton * >( sw.mWidgetPointer.data() );
865  if ( btn && btn->isCheckable() )
866  {
867  btn->setChecked( sw.mNatural ? state : !state );
868  }
869  else
870  {
871  QGroupBox *grpbx = qobject_cast< QGroupBox * >( sw.mWidgetPointer.data() );
872  if ( grpbx && grpbx->isCheckable() )
873  {
874  grpbx->setChecked( sw.mNatural ? state : !state );
875  }
876  }
877  }
878  break;
879  }
880 
881  case SiblingEnableState:
882  {
883  QLineEdit *le = qobject_cast< QLineEdit * >( sw.mWidgetPointer.data() );
884  if ( le )
885  le->setReadOnly( sw.mNatural ? !state : state );
886  else
887  sw.mWidgetPointer.data()->setEnabled( sw.mNatural ? state : !state );
888  break;
889  }
890 
891  case SiblingVisibility:
892  {
893  sw.mWidgetPointer.data()->setVisible( sw.mNatural ? state : !state );
894  break;
895  }
896 
897  case SiblingExpressionText:
898  {
899  QLineEdit *le = qobject_cast<QLineEdit *>( sw.mWidgetPointer.data() );
900  if ( le )
901  {
902  le->setText( mProperty.asExpression() );
903  }
904  else
905  {
906  QTextEdit *te = qobject_cast<QTextEdit *>( sw.mWidgetPointer.data() );
907  if ( te )
908  {
909  te->setText( mProperty.asExpression() );
910  }
911  }
912  break;
913  }
914 
915  case SiblingLinkedWidget:
916  {
917  if ( QgsColorButton *cb = qobject_cast< QgsColorButton * >( sw.mWidgetPointer.data() ) )
918  {
919  if ( state && mProperty.isProjectColor() )
920  {
921  QRegularExpression rx( QStringLiteral( "^project_color\\('(.*)'\\)$" ) );
922  QRegularExpressionMatch match = rx.match( mExpressionString );
923  if ( match.hasMatch() )
924  {
925  cb->linkToProjectColor( match.captured( 1 ) );
926  }
927  }
928  else
929  {
930  cb->linkToProjectColor( QString() );
931  }
932  }
933  break;
934  }
935  }
936  }
937 }
938 
939 
940 
942 {
943  if ( mProperty.isActive() != active )
944  {
945  mProperty.setActive( active );
946  emit changed();
947  emit activated( mProperty.isActive() );
948  }
949 }
950 
952 {
953  mExpressionContextGenerator = generator;
954 }
955 
957 {
958  for ( const SiblingWidget &sw : qgis::as_const( mSiblingWidgets ) )
959  {
960  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingLinkedWidget )
961  return;
962  }
963  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingLinkedWidget ) );
964 
965  if ( QgsColorButton *cb = qobject_cast< QgsColorButton * >( widget ) )
966  {
967  connect( cb, &QgsColorButton::unlinked, this, [ = ]
968  {
969  setActive( false );
970  updateGui();
971  } );
972  }
973 
974  updateSiblingWidgets( isActive() );
975 }
976 
977 void QgsPropertyOverrideButton::showHelp()
978 {
979  QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html#data-defined" ) );
980 }
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 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:681
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.
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