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