QGIS API Documentation  3.6.0-Noosa (5873452)
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 
45  int iconSize = QgsGuiUtils::scaleIconSize( 24 );
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  Q_FOREACH ( const SiblingWidget &sw, mSiblingWidgets )
223  {
224  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingCheckState )
225  return;
226  }
227  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingCheckState, natural ) );
228  updateSiblingWidgets( isActive() );
229 }
230 
231 void QgsPropertyOverrideButton::registerEnabledWidget( QWidget *widget, bool natural )
232 {
233  Q_FOREACH ( const SiblingWidget &sw, mSiblingWidgets )
234  {
235  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingEnableState )
236  return;
237  }
238  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingEnableState, natural ) );
239  updateSiblingWidgets( isActive() );
240 }
241 
242 void QgsPropertyOverrideButton::registerVisibleWidget( QWidget *widget, bool natural )
243 {
244  Q_FOREACH ( const SiblingWidget &sw, mSiblingWidgets )
245  {
246  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingVisibility )
247  return;
248  }
249  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingVisibility, natural ) );
250  updateSiblingWidgets( isActive() );
251 }
252 
254 {
255  Q_FOREACH ( const SiblingWidget &sw, mSiblingWidgets )
256  {
257  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingExpressionText )
258  return;
259  }
260  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingExpressionText ) );
261  updateSiblingWidgets( isActive() );
262 }
263 
264 
266 {
267  // Ctrl-click to toggle activated state
268  if ( ( event->modifiers() & ( Qt::ControlModifier ) )
269  || event->button() == Qt::RightButton )
270  {
271  setActivePrivate( !mProperty.isActive() );
272  updateGui();
273  emit changed();
274  event->ignore();
275  return;
276  }
277 
278  // pass to default behavior
279  QToolButton::mousePressEvent( event );
280 }
281 
283 {
284  if ( property )
285  {
286  switch ( property.propertyType() )
287  {
290  break;
292  {
293  mFieldName = property.field();
294  break;
295  }
297  {
298  mExpressionString = property.expressionString();
299  break;
300  }
301  }
302  }
303  else
304  {
305  mFieldName.clear();
306  mExpressionString.clear();
307  }
308  mProperty = property;
309  setActive( mProperty && mProperty.isActive() );
310  updateSiblingWidgets( isActive() );
311  updateGui();
312 }
313 
315 void QgsPropertyOverrideButton::aboutToShowMenu()
316 {
317  mDefineMenu->clear();
318  // update fields so that changes made to layer's fields are reflected
320 
321  bool hasExp = !mExpressionString.isEmpty();
322  QString ddTitle = tr( "Data defined override" );
323 
324  QAction *ddTitleAct = mDefineMenu->addAction( ddTitle );
325  QFont titlefont = ddTitleAct->font();
326  titlefont.setItalic( true );
327  ddTitleAct->setFont( titlefont );
328  ddTitleAct->setEnabled( false );
329 
330  bool addActiveAction = false;
331  if ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && hasExp )
332  {
333  QgsExpression exp( mExpressionString );
334  // whether expression is parse-able
335  addActiveAction = !exp.hasParserError();
336  }
337  else if ( mProperty.propertyType() == QgsProperty::FieldBasedProperty )
338  {
339  // whether field exists
340  addActiveAction = mFieldNameList.contains( mFieldName );
341  }
342 
343  if ( addActiveAction )
344  {
345  ddTitleAct->setText( ddTitle + " (" + ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty ? tr( "expression" ) : tr( "field" ) ) + ')' );
346  mDefineMenu->addAction( mActionActive );
347  mActionActive->setText( mProperty.isActive() ? tr( "Deactivate" ) : tr( "Activate" ) );
348  mActionActive->setData( QVariant( !mProperty.isActive() ) );
349  }
350 
351  if ( !mFullDescription.isEmpty() )
352  {
353  mDefineMenu->addAction( mActionDescription );
354  }
355 
356  mDefineMenu->addSeparator();
357 
358  // deactivate button if field already exists
359  if ( mAuxiliaryStorageEnabled && mVectorLayer )
360  {
361  mDefineMenu->addAction( mActionCreateAuxiliaryField );
362 
363  const QgsAuxiliaryLayer *alayer = mVectorLayer->auxiliaryLayer();
364 
365  mActionCreateAuxiliaryField->setEnabled( true );
366  mActionCreateAuxiliaryField->setChecked( false );
367 
368  int index = mVectorLayer->fields().indexFromName( mFieldName );
369  int srcIndex;
370  if ( index >= 0 && alayer && mVectorLayer->isAuxiliaryField( index, srcIndex ) )
371  {
372  mActionCreateAuxiliaryField->setEnabled( false );
373  mActionCreateAuxiliaryField->setChecked( true );
374  }
375  }
376 
377  bool fieldActive = false;
378  if ( !mDataTypesString.isEmpty() )
379  {
380  QAction *fieldTitleAct = mDefineMenu->addAction( tr( "Attribute Field" ) );
381  fieldTitleAct->setFont( titlefont );
382  fieldTitleAct->setEnabled( false );
383 
384  mDefineMenu->addAction( mActionDataTypes );
385 
386  mFieldsMenu->clear();
387 
388  if ( !mFieldNameList.isEmpty() )
389  {
390 
391  for ( int j = 0; j < mFieldNameList.count(); ++j )
392  {
393  QString fldname = mFieldNameList.at( j );
394  QAction *act = mFieldsMenu->addAction( fldname + " (" + mFieldTypeList.at( j ) + ')' );
395  act->setData( QVariant( fldname ) );
396  if ( mFieldName == fldname )
397  {
398  act->setCheckable( true );
399  act->setChecked( mProperty.propertyType() == QgsProperty::FieldBasedProperty );
400  fieldActive = mProperty.propertyType() == QgsProperty::FieldBasedProperty;
401  }
402  }
403  }
404  else
405  {
406  QAction *act = mFieldsMenu->addAction( tr( "No matching field types found" ) );
407  act->setEnabled( false );
408  }
409 
410  mDefineMenu->addSeparator();
411  }
412 
413  mFieldsMenu->menuAction()->setCheckable( true );
414  mFieldsMenu->menuAction()->setChecked( fieldActive && mProperty.propertyType() == QgsProperty::FieldBasedProperty && !mProperty.transformer() );
415 
416  bool colorActive = false;
417  mColorsMenu->clear();
420  {
421  // project colors menu
422  QAction *colorTitleAct = mDefineMenu->addAction( tr( "Project Color" ) );
423  colorTitleAct->setFont( titlefont );
424  colorTitleAct->setEnabled( false );
425 
426  QList<QgsProjectColorScheme *> projectSchemes;
427  QgsApplication::colorSchemeRegistry()->schemes( projectSchemes );
428  if ( projectSchemes.length() > 0 )
429  {
430  QgsProjectColorScheme *scheme = projectSchemes.at( 0 );
431  const QgsNamedColorList colors = scheme->fetchColors();
432  for ( const auto &color : colors )
433  {
434  if ( color.second.isEmpty() )
435  continue;
436 
437  QPixmap icon = QgsColorButton::createMenuIcon( color.first, mDefinition.standardTemplate() == QgsPropertyDefinition::ColorWithAlpha );
438  QAction *act = mColorsMenu->addAction( color.second );
439  act->setIcon( icon );
440  if ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && hasExp && mExpressionString == QStringLiteral( "project_color('%1')" ).arg( color.second ) )
441  {
442  act->setCheckable( true );
443  act->setChecked( true );
444  colorActive = true;
445  }
446  }
447  }
448 
449  if ( mColorsMenu->actions().isEmpty() )
450  {
451  QAction *act = mColorsMenu->addAction( tr( "No colors set" ) );
452  act->setEnabled( false );
453  }
454 
455  mDefineMenu->addAction( mActionColors );
456  mColorsMenu->menuAction()->setCheckable( true );
457  mColorsMenu->menuAction()->setChecked( colorActive && !mProperty.transformer() );
458 
459  mDefineMenu->addSeparator();
460  }
461 
462  QAction *exprTitleAct = mDefineMenu->addAction( tr( "Expression" ) );
463  exprTitleAct->setFont( titlefont );
464  exprTitleAct->setEnabled( false );
465 
466  mVariablesMenu->clear();
467  bool variableActive = false;
468  if ( mExpressionContextGenerator )
469  {
470  QgsExpressionContext context = mExpressionContextGenerator->createExpressionContext();
471  QStringList variables = context.variableNames();
472  Q_FOREACH ( const QString &variable, variables )
473  {
474  if ( context.isReadOnly( variable ) ) //only want to show user-set variables
475  continue;
476  if ( variable.startsWith( '_' ) ) //no hidden variables
477  continue;
478 
479  QAction *act = mVariablesMenu->addAction( variable );
480  act->setData( QVariant( variable ) );
481 
482  if ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && hasExp && mExpressionString == '@' + variable )
483  {
484  act->setCheckable( true );
485  act->setChecked( true );
486  variableActive = true;
487  }
488  }
489  }
490 
491  if ( mVariablesMenu->actions().isEmpty() )
492  {
493  QAction *act = mVariablesMenu->addAction( tr( "No variables set" ) );
494  act->setEnabled( false );
495  }
496 
497  mDefineMenu->addAction( mActionVariables );
498  mVariablesMenu->menuAction()->setCheckable( true );
499  mVariablesMenu->menuAction()->setChecked( variableActive && !mProperty.transformer() );
500 
501  if ( hasExp )
502  {
503  QString expString = mExpressionString;
504  if ( expString.length() > 35 )
505  {
506  expString.truncate( 35 );
507  expString.append( QChar( 0x2026 ) );
508  }
509 
510  expString.prepend( tr( "Current: " ) );
511 
512  if ( !mActionExpression )
513  {
514  mActionExpression = new QAction( expString, this );
515  mActionExpression->setCheckable( true );
516  }
517  else
518  {
519  mActionExpression->setText( expString );
520  }
521  mDefineMenu->addAction( mActionExpression );
522  mActionExpression->setChecked( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && !variableActive && !colorActive && !mProperty.transformer() );
523 
524  mDefineMenu->addAction( mActionExpDialog );
525  mDefineMenu->addAction( mActionCopyExpr );
526  mDefineMenu->addAction( mActionPasteExpr );
527  mDefineMenu->addAction( mActionClearExpr );
528  }
529  else
530  {
531  mDefineMenu->addAction( mActionExpDialog );
532  mDefineMenu->addAction( mActionPasteExpr );
533  }
534 
535  if ( !mDefinition.name().isEmpty() && mDefinition.supportsAssistant() )
536  {
537  mDefineMenu->addSeparator();
538  mActionAssistant->setCheckable( mProperty.transformer() );
539  mActionAssistant->setChecked( mProperty.transformer() );
540  mDefineMenu->addAction( mActionAssistant );
541  }
542 }
543 
544 void QgsPropertyOverrideButton::menuActionTriggered( QAction *action )
545 {
546  if ( action == mActionActive )
547  {
548  setActivePrivate( mActionActive->data().toBool() );
549  updateGui();
550  emit changed();
551  }
552  else if ( action == mActionDescription )
553  {
554  showDescriptionDialog();
555  }
556  else if ( action == mActionExpDialog )
557  {
558  showExpressionDialog();
559  }
560  else if ( action == mActionExpression )
561  {
562  mProperty.setExpressionString( mExpressionString );
563  mProperty.setTransformer( nullptr );
564  setActivePrivate( true );
565  updateSiblingWidgets( isActive() );
566  updateGui();
567  emit changed();
568  }
569  else if ( action == mActionCopyExpr )
570  {
571  QApplication::clipboard()->setText( mExpressionString );
572  }
573  else if ( action == mActionPasteExpr )
574  {
575  QString exprString = QApplication::clipboard()->text();
576  if ( !exprString.isEmpty() )
577  {
578  mExpressionString = exprString;
579  mProperty.setExpressionString( mExpressionString );
580  mProperty.setTransformer( nullptr );
581  setActivePrivate( true );
582  updateSiblingWidgets( isActive() );
583  updateGui();
584  emit changed();
585  }
586  }
587  else if ( action == mActionClearExpr )
588  {
589  setActivePrivate( false );
590  mProperty.setStaticValue( QVariant() );
591  mProperty.setTransformer( nullptr );
592  mExpressionString.clear();
593  updateSiblingWidgets( isActive() );
594  updateGui();
595  emit changed();
596  }
597  else if ( action == mActionAssistant )
598  {
599  showAssistant();
600  }
601  else if ( action == mActionCreateAuxiliaryField )
602  {
603  emit createAuxiliaryField();
604  }
605  else if ( mFieldsMenu->actions().contains( action ) ) // a field name clicked
606  {
607  if ( action->isEnabled() )
608  {
609  if ( mFieldName != action->text() )
610  {
611  mFieldName = action->data().toString();
612  }
613  mProperty.setField( mFieldName );
614  mProperty.setTransformer( nullptr );
615  setActivePrivate( true );
616  updateSiblingWidgets( isActive() );
617  updateGui();
618  emit changed();
619  }
620  }
621  else if ( mVariablesMenu->actions().contains( action ) ) // a variable name clicked
622  {
623  if ( mExpressionString != action->text().prepend( "@" ) )
624  {
625  mExpressionString = action->data().toString().prepend( "@" );
626  }
627  mProperty.setExpressionString( mExpressionString );
628  mProperty.setTransformer( nullptr );
629  setActivePrivate( true );
630  updateSiblingWidgets( isActive() );
631  updateGui();
632  emit changed();
633  }
634  else if ( mColorsMenu->actions().contains( action ) ) // a color name clicked
635  {
636  if ( mExpressionString != QStringLiteral( "project_color('%1')" ).arg( action->text() ) )
637  {
638  mExpressionString = QStringLiteral( "project_color('%1')" ).arg( action->text() );
639  }
640  mProperty.setExpressionString( mExpressionString );
641  mProperty.setTransformer( nullptr );
642  setActivePrivate( true );
643  updateSiblingWidgets( isActive() );
644  updateGui();
645  emit changed();
646  }
647 }
649 
650 void QgsPropertyOverrideButton::showDescriptionDialog()
651 {
652  QgsMessageViewer *mv = new QgsMessageViewer( this );
653  mv->setWindowTitle( tr( "Data Definition Description" ) );
654  mv->setMessageAsHtml( mFullDescription );
655  mv->exec();
656 }
657 
658 
659 void QgsPropertyOverrideButton::showExpressionDialog()
660 {
661  QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : QgsExpressionContext();
662 
663  // build sensible initial expression text - see https://issues.qgis.org/issues/18638
664  QString currentExpression = ( mProperty.propertyType() == QgsProperty::StaticProperty && !mProperty.staticValue().isValid() ) ? QString()
665  : mProperty.asExpression();
666 
667  QgsExpressionBuilderDialog d( const_cast<QgsVectorLayer *>( mVectorLayer ), currentExpression, this, QStringLiteral( "generic" ), context );
668  d.setExpectedOutputFormat( mInputDescription );
669  if ( d.exec() == QDialog::Accepted )
670  {
671  mExpressionString = d.expressionText().trimmed();
672  mProperty.setExpressionString( mExpressionString );
673  mProperty.setTransformer( nullptr );
674  setActivePrivate( !mExpressionString.isEmpty() );
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  connect( widget, &QgsPropertyAssistantWidget::panelAccepted, this, [ = ] { updateGui(); } );
708 
709  panel->openPanel( widget );
710  return;
711  }
712  else
713  {
714  // Show the dialog version if not in a panel
715  QDialog *dlg = new QDialog( this );
716  QString key = QStringLiteral( "/UI/paneldialog/%1" ).arg( widget->panelTitle() );
717  QgsSettings settings;
718  dlg->restoreGeometry( settings.value( key ).toByteArray() );
719  dlg->setWindowTitle( widget->panelTitle() );
720  dlg->setLayout( new QVBoxLayout() );
721  dlg->layout()->addWidget( widget );
722  QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok );
723  connect( buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept );
724  connect( buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject );
725  connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsPropertyOverrideButton::showHelp );
726  dlg->layout()->addWidget( buttonBox );
727 
728  if ( dlg->exec() == QDialog::Accepted )
729  {
730  widget->updateProperty( mProperty );
731  mExpressionString = mProperty.asExpression();
732  mFieldName = mProperty.field();
733  widget->acceptPanel();
734  updateSiblingWidgets( isActive() );
735  updateGui();
736 
737  emit changed();
738  }
739  settings.setValue( key, dlg->saveGeometry() );
740  }
741 }
742 
743 void QgsPropertyOverrideButton::updateGui()
744 {
745  bool hasExp = !mExpressionString.isEmpty();
746  bool hasField = !mFieldName.isEmpty();
747 
748  QIcon icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefine.svg" ) );
749  QString deftip = tr( "undefined" );
750  QString deftype;
751  if ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && hasExp )
752  {
753  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpressionOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpression.svg" ) );
754 
755  QRegularExpression rx( QStringLiteral( "^project_color\\('(.*)'\\)$" ) );
756  QRegularExpressionMatch match = rx.match( mExpressionString );
757  if ( match.hasMatch() )
758  {
759  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColorOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColor.svg" ) );
760  deftip = match.captured( 1 );
761  deftype = tr( "project color" );
762  }
763  else
764  {
765  QgsExpression exp( mExpressionString );
766  if ( exp.hasParserError() )
767  {
768  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpressionError.svg" ) );
769  deftip = tr( "Parse error: %1" ).arg( exp.parserErrorString() );
770  }
771  else
772  {
773  deftip = mExpressionString;
774  }
775  }
776  }
777  else if ( mProperty.propertyType() != QgsProperty::ExpressionBasedProperty && hasField )
778  {
779  icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefine.svg" ) );
780 
781  if ( !mFieldNameList.contains( mFieldName ) && !mProperty.transformer() )
782  {
783  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineError.svg" ) );
784  deftip = tr( "'%1' field missing" ).arg( mFieldName );
785  }
786  else
787  {
788  deftip = mFieldName;
789  }
790  }
791 
792  setIcon( icon );
793 
794  // build full description for tool tip and popup dialog
795  mFullDescription = tr( "<b><u>Data defined override</u></b><br>" );
796 
797  mFullDescription += tr( "<b>Active: </b>%1&nbsp;&nbsp;&nbsp;<i>(ctrl|right-click toggles)</i><br>" ).arg( mProperty.isActive() ? tr( "yes" ) : tr( "no" ) );
798 
799  if ( !mUsageInfo.isEmpty() )
800  {
801  mFullDescription += tr( "<b>Usage:</b><br>%1<br>" ).arg( mUsageInfo );
802  }
803 
804  if ( !mInputDescription.isEmpty() )
805  {
806  mFullDescription += tr( "<b>Expected input:</b><br>%1<br>" ).arg( mInputDescription );
807  }
808 
809  if ( !mDataTypesString.isEmpty() )
810  {
811  mFullDescription += tr( "<b>Valid input types:</b><br>%1<br>" ).arg( mDataTypesString );
812  }
813 
814  if ( deftype.isEmpty() && deftip != tr( "undefined" ) )
815  {
816  deftype = mProperty.propertyType() == QgsProperty::ExpressionBasedProperty ? tr( "expression" ) : tr( "field" );
817  }
818 
819  // truncate long expressions, or tool tip may be too wide for screen
820  if ( deftip.length() > 75 )
821  {
822  deftip.truncate( 75 );
823  deftip.append( QChar( 0x2026 ) );
824  }
825 
826  mFullDescription += tr( "<b>Current definition (%1):</b><br>%2" ).arg( deftype, deftip );
827 
828  setToolTip( mFullDescription );
829 
830 }
831 
832 void QgsPropertyOverrideButton::setActivePrivate( bool active )
833 {
834  if ( mProperty.isActive() != active )
835  {
836  mProperty.setActive( active );
837  emit activated( mProperty.isActive() );
838  }
839 }
840 
841 void QgsPropertyOverrideButton::updateSiblingWidgets( bool state )
842 {
843  Q_FOREACH ( const SiblingWidget &sw, mSiblingWidgets )
844  {
845  switch ( sw.mSiblingType )
846  {
847 
848  case SiblingCheckState:
849  {
850  // don't uncheck, only set to checked
851  if ( state )
852  {
853  QAbstractButton *btn = qobject_cast< QAbstractButton * >( sw.mWidgetPointer.data() );
854  if ( btn && btn->isCheckable() )
855  {
856  btn->setChecked( sw.mNatural ? state : !state );
857  }
858  else
859  {
860  QGroupBox *grpbx = qobject_cast< QGroupBox * >( sw.mWidgetPointer.data() );
861  if ( grpbx && grpbx->isCheckable() )
862  {
863  grpbx->setChecked( sw.mNatural ? state : !state );
864  }
865  }
866  }
867  break;
868  }
869 
870  case SiblingEnableState:
871  {
872  QLineEdit *le = qobject_cast< QLineEdit * >( sw.mWidgetPointer.data() );
873  if ( le )
874  le->setReadOnly( sw.mNatural ? !state : state );
875  else
876  sw.mWidgetPointer.data()->setEnabled( sw.mNatural ? state : !state );
877  break;
878  }
879 
880  case SiblingVisibility:
881  {
882  sw.mWidgetPointer.data()->setVisible( sw.mNatural ? state : !state );
883  break;
884  }
885 
886  case SiblingExpressionText:
887  {
888  QLineEdit *le = qobject_cast<QLineEdit *>( sw.mWidgetPointer.data() );
889  if ( le )
890  {
891  le->setText( mProperty.asExpression() );
892  }
893  else
894  {
895  QTextEdit *te = qobject_cast<QTextEdit *>( sw.mWidgetPointer.data() );
896  if ( te )
897  {
898  te->setText( mProperty.asExpression() );
899  }
900  }
901  break;
902  }
903 
904  case SiblingLinkedWidget:
905  {
906  if ( QgsColorButton *cb = qobject_cast< QgsColorButton * >( sw.mWidgetPointer.data() ) )
907  {
908  if ( state && mProperty.isProjectColor() )
909  {
910  QRegularExpression rx( QStringLiteral( "^project_color\\('(.*)'\\)$" ) );
911  QRegularExpressionMatch match = rx.match( mExpressionString );
912  if ( match.hasMatch() )
913  {
914  cb->linkToProjectColor( match.captured( 1 ) );
915  }
916  }
917  else
918  {
919  cb->linkToProjectColor( QString() );
920  }
921  }
922  break;
923  }
924  }
925  }
926 }
927 
928 
929 
931 {
932  if ( mProperty.isActive() != active )
933  {
934  mProperty.setActive( active );
935  emit changed();
936  emit activated( mProperty.isActive() );
937  }
938 }
939 
941 {
942  mExpressionContextGenerator = generator;
943 }
944 
946 {
947  for ( const SiblingWidget &sw : qgis::as_const( mSiblingWidgets ) )
948  {
949  if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingLinkedWidget )
950  return;
951  }
952  mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingLinkedWidget ) );
953 
954  if ( QgsColorButton *cb = qobject_cast< QgsColorButton * >( widget ) )
955  {
956  connect( cb, &QgsColorButton::unlinked, this, [ = ]
957  {
958  setActive( false );
959  updateGui();
960  } );
961  }
962 
963  updateSiblingWidgets( isActive() );
964 }
965 
966 void QgsPropertyOverrideButton::showHelp()
967 {
968  QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html#data-defined" ) );
969 }
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
Expression based property (QgsExpressionBasedProperty)
Definition: qgsproperty.h:239
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly...
void setSymbol(std::shared_ptr< QgsSymbol > symbol)
Sets a symbol which can be used for previews inside the widget.
Class allowing to manage the auxiliary storage for a vector layer.
void registerEnabledWidget(QWidget *widget, bool natural=true)
Register a sibling widget that gets enabled when the property is active, and disabled when the proper...
void setVectorLayer(const QgsVectorLayer *layer)
Sets the vector layer associated with the button.
void mouseReleaseEvent(QMouseEvent *event) override
void registerExpressionContextGenerator(QgsExpressionContextGenerator *generator)
Register an expression context generator class that will be used to retrieve an expression context fo...
A cross platform button subclass for selecting colors.
Container of fields for a vector layer.
Definition: qgsfields.h:42
bool isActive() const
Returns true if the button has an active property.
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
Color with alpha channel.
Definition: qgsproperty.h:64
Base class for any widget that can be shown as a inline panel.
void activated(bool isActive)
Emitted when the activated status of the widget changes.
QVariant value(const QgsExpressionContext &context, const QVariant &defaultValue=QVariant(), bool *ok=nullptr) const
Calculates the current value of the property, including any transforms which are set for the property...
bool isAuxiliaryField(int index, int &srcIndex) const
Returns true if the field comes from the auxiliary layer, false otherwise.
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
void registerVisibleWidget(QWidget *widget, bool natural=true)
Register a sibling widget that gets visible when the property is active, and hidden when the property...
QString parserErrorString() const
Returns parser error.
const QgsPropertyTransformer * transformer() const
Returns the existing transformer used for manipulating the calculated values for the property...
void createAuxiliaryField()
Emitted when creating a new auxiliary field.
void setMessageAsHtml(const QString &msg)
void setField(const QString &field)
Sets the field name the property references.
QList< QgsColorScheme * > schemes() const
Returns all color schemes in the registry.
bool isProjectColor() const
Returns true if the property is set to a linked project color.
virtual QgsExpressionContext createExpressionContext() const =0
This method needs to be reimplemented in all classes which implement this interface and return an exp...
int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:202
DataType dataType() const
Returns the allowable field/value data type for the property.
Definition: qgsproperty.h:187
QgsFields fields() const FINAL
Returns the list of fields of this layer.
#define FALLTHROUGH
Definition: qgis.h:656
Type propertyType() const
Returns the property type.
Property requires a boolean value.
Definition: qgsproperty.h:105
bool convertToTransformer()
Attempts to convert an existing expression based property to a base expression with corresponding tra...
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget...
QgsProperty toProperty() const
Returns a QgsProperty object encapsulating the current state of the widget.
void setToProperty(const QgsProperty &property)
Sets the widget to reflect the current state of a QgsProperty.
void acceptPanel()
Accept the panel.
void registerExpressionContextGenerator(QgsExpressionContextGenerator *generator)
Register an expression context generator class that will be used to retrieve an expression context fo...
Shows a user-friendly assistant guiding users through the creation of QgsProperty overrides...
void setActive(bool active)
Sets whether the property is currently active.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Abstract base class for QgsPropertyCollection like objects.
Property requires a numeric value.
Definition: qgsproperty.h:98
QList< QPair< QColor, QString > > QgsNamedColorList
List of colors paired with a friendly display name identifying the color.
QString panelTitle()
The title of the panel.
virtual QgsProperty property(int key) const =0
Returns a matching property from the collection, if one exists.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:48
QStringList variableNames() const
Returns a list of variables names set by all scopes in the context.
A store for object properties.
Definition: qgsproperty.h:229
QgsAuxiliaryLayer * auxiliaryLayer()
Returns the current auxiliary layer.
void widgetChanged()
Emitted when the widget state changes.
void setExpressionString(const QString &expression)
Sets the expression to use for the property value.
Definition for a property.
Definition: qgsproperty.h:46
QString field() const
Returns the current field name the property references.
QMap< int, QgsPropertyDefinition > QgsPropertiesDefinition
Definition of available properties.
QString helpText() const
Helper text for using the property, including a description of the valid values for the property...
Definition: qgsproperty.h:177
Abstract interface for generating an expression context.
void registerLinkedWidget(QWidget *widget)
Registers a widget which is linked to this button.
void changed()
Emitted when property definition changes.
void registerCheckedWidget(QWidget *widget, bool natural=true)
Register a sibling widget that gets checked when the property is active.
static QgsColorSchemeRegistry * colorSchemeRegistry()
Returns the application&#39;s color scheme registry, used for managing color schemes. ...
void unlinked()
Emitted when the color is unlinked, e.g.
Property requires a string value.
Definition: qgsproperty.h:91
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
void registerExpressionWidget(QWidget *widget)
Register a sibling widget (line edit, text edit) that will receive the property as an expression...
int propertyKey() const
Returns the property key linked to the button.
QgsPropertyOverrideButton(QWidget *parent=nullptr, const QgsVectorLayer *layer=nullptr)
Constructor for QgsPropertyOverrideButton.
A generic message view for displaying QGIS messages.
QVariant staticValue() const
Returns the current static value for the property.
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:36
QString name() const
Returns the name of the property.
Definition: qgsproperty.h:138
Represents a vector layer which manages a vector based data sets.
void updateFieldLists()
Updates list of fields.
Invalid (not set) property.
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