QGIS API Documentation 3.39.0-Master (3aed037ce22)
Loading...
Searching...
No Matches
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"
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 QString ss = QStringLiteral( "QgsPropertyOverrideButton { background: none; border: 1px solid rgba(0, 0, 0, 0%); } QgsPropertyOverrideButton:focus { border: 1px solid palette(highlight); }" );
46#ifdef Q_OS_MACX
47 ss += QLatin1String( "QgsPropertyOverrideButton::menu-indicator { width: 5px; }" );
48#endif
49 setStyleSheet( ss );
50
51 int iconSize = QgsGuiUtils::scaleIconSize( 24 );
52
53 // button width is 1.25 * icon size, height 1.1 * icon size. But we round to ensure even pixel sizes for equal margins
54 setFixedSize( 2 * static_cast< int >( 1.25 * iconSize / 2.0 ), 2 * static_cast< int >( iconSize * 1.1 / 2.0 ) );
55
56 setIconSize( QSize( iconSize, iconSize ) );
57 setPopupMode( QToolButton::InstantPopup );
58
59 connect( this, &QgsPropertyOverrideButton::activated, this, &QgsPropertyOverrideButton::updateSiblingWidgets );
60
61 mDefineMenu = new QMenu( this );
62 connect( mDefineMenu, &QMenu::aboutToShow, this, &QgsPropertyOverrideButton::aboutToShowMenu );
63 connect( mDefineMenu, &QMenu::triggered, this, &QgsPropertyOverrideButton::menuActionTriggered );
64 setMenu( mDefineMenu );
65
66 mFieldsMenu = new QMenu( this );
67 mActionDataTypes = new QAction( this );
68 // list fields and types in submenu, since there may be many
69 mActionDataTypes->setMenu( mFieldsMenu );
70
71 mActionVariables = new QAction( tr( "Variable" ), this );
72 mVariablesMenu = new QMenu( this );
73 mActionVariables->setMenu( mVariablesMenu );
74
75 mActionColors = new QAction( tr( "Color" ), this );
76 mColorsMenu = new QMenu( this );
77 mActionColors->setMenu( mColorsMenu );
78
79 mActionActive = new QAction( this );
80 QFont f = mActionActive->font();
81 f.setBold( true );
82 mActionActive->setFont( f );
83
84 mActionDescription = new QAction( tr( "Description…" ), this );
85
86 mActionCreateAuxiliaryField = new QAction( tr( "Store Data in the Project" ), this );
87 mActionCreateAuxiliaryField->setCheckable( true );
88
89 mActionExpDialog = new QAction( tr( "Edit…" ), this );
90 mActionExpression = nullptr;
91 mActionPasteExpr = new QAction( tr( "Paste" ), this );
92 mActionCopyExpr = new QAction( tr( "Copy" ), this );
93 mActionClearExpr = new QAction( tr( "Clear" ), this );
94 mActionAssistant = new QAction( tr( "Assistant…" ), this );
95 QFont assistantFont = mActionAssistant->font();
96 assistantFont.setBold( true );
97 mActionAssistant->setFont( assistantFont );
98 mDefineMenu->addAction( mActionAssistant );
99}
100
101
102void QgsPropertyOverrideButton::init( int propertyKey, const QgsProperty &property, const QgsPropertiesDefinition &definitions, const QgsVectorLayer *layer, bool auxiliaryStorageEnabled )
103{
104 init( propertyKey, property, definitions.value( propertyKey ), layer, auxiliaryStorageEnabled );
105}
106
107void QgsPropertyOverrideButton::init( int propertyKey, const QgsProperty &property, const QgsPropertyDefinition &definition, const QgsVectorLayer *layer, bool auxiliaryStorageEnabled )
108{
109 mVectorLayer = layer;
110 mAuxiliaryStorageEnabled = auxiliaryStorageEnabled;
111 setToProperty( property );
112 mPropertyKey = propertyKey;
113
114 mDefinition = definition;
115 mDataTypes = mDefinition.dataType();
116
117 mInputDescription = mDefinition.helpText();
118 mFullDescription.clear();
119 mUsageInfo.clear();
120
121 // set up data types string
122 mDataTypesString.clear();
123
124 QStringList ts;
125 switch ( mDataTypes )
126 {
128 ts << tr( "boolean" );
129 [[fallthrough]];
130
132 ts << tr( "int" );
133 ts << tr( "double" );
134 [[fallthrough]];
135
137 ts << tr( "string" );
138 break;
139 }
140
141 if ( !ts.isEmpty() )
142 {
143 mDataTypesString = ts.join( QLatin1String( ", " ) );
144 mActionDataTypes->setText( tr( "Field type: " ) + mDataTypesString );
145 }
146
148 updateGui();
149 updateSiblingWidgets( isActive() );
150}
151
152void QgsPropertyOverrideButton::init( int propertyKey, const QgsAbstractPropertyCollection &collection, const QgsPropertiesDefinition &definitions, const QgsVectorLayer *layer, bool auxiliaryStorageEnabled )
153{
154 init( propertyKey, collection.property( propertyKey ), definitions, layer, auxiliaryStorageEnabled );
155}
156
157
159{
160 mFieldNameList.clear();
161 mFieldDisplayNameList.clear();
162 mFieldIcons.clear();
163
164 if ( mVectorLayer )
165 {
166 // store just a list of fields of unknown type or those that match the expected type
167 const QgsFields fields = mVectorLayer->fields();
168 int idx = 0;
169 for ( const QgsField &f : fields )
170 {
171 bool fieldMatch = false;
172 switch ( mDataTypes )
173 {
175 fieldMatch = true;
176 break;
177
179 fieldMatch = f.isNumeric() || f.type() == QMetaType::Type::QString;
180 break;
181
183 fieldMatch = f.type() == QMetaType::Type::QString;
184 break;
185 }
186
187 if ( fieldMatch )
188 {
189 mFieldNameList << f.name();
190 mFieldDisplayNameList << f.displayNameWithAlias();
191 mFieldIcons << fields.iconForField( idx, true );
192 }
193 idx++;
194 }
195 }
196}
197
199{
200 return mProperty;
201}
202
204{
205 mVectorLayer = layer;
207 updateGui();
208}
209
210void QgsPropertyOverrideButton::registerCheckedWidget( QWidget *widget, bool natural )
211{
212 const auto constMSiblingWidgets = mSiblingWidgets;
213 for ( const SiblingWidget &sw : constMSiblingWidgets )
214 {
215 if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingCheckState )
216 return;
217 }
218 mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingCheckState, natural ) );
219 updateSiblingWidgets( isActive() );
220}
221
222void QgsPropertyOverrideButton::registerEnabledWidget( QWidget *widget, bool natural )
223{
224 const auto constMSiblingWidgets = mSiblingWidgets;
225 for ( const SiblingWidget &sw : constMSiblingWidgets )
226 {
227 if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingEnableState )
228 return;
229 }
230 mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingEnableState, natural ) );
231 updateSiblingWidgets( isActive() );
232}
233
234void QgsPropertyOverrideButton::registerVisibleWidget( QWidget *widget, bool natural )
235{
236 const auto constMSiblingWidgets = mSiblingWidgets;
237 for ( const SiblingWidget &sw : constMSiblingWidgets )
238 {
239 if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingVisibility )
240 return;
241 }
242 mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingVisibility, natural ) );
243 updateSiblingWidgets( isActive() );
244}
245
247{
248 const auto constMSiblingWidgets = mSiblingWidgets;
249 for ( const SiblingWidget &sw : constMSiblingWidgets )
250 {
251 if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingExpressionText )
252 return;
253 }
254 mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingExpressionText ) );
255 updateSiblingWidgets( isActive() );
256}
257
258
260{
261 // Ctrl-click to toggle activated state
262 if ( ( event->modifiers() & ( Qt::ControlModifier ) )
263 || event->button() == Qt::RightButton )
264 {
265 setActivePrivate( !mProperty.isActive() );
266 updateGui();
267 emit changed();
268 event->ignore();
269 return;
270 }
271
272 // Middle button click to open the Expression Builder dialog
273 if ( event->button() == Qt::MiddleButton )
274 {
275 showExpressionDialog();
276 return;
277 }
278
279 // pass to default behavior
280 QToolButton::mousePressEvent( event );
281}
282
284{
285 if ( property )
286 {
287 switch ( property.propertyType() )
288 {
291 break;
293 {
294 mFieldName = property.field();
295 break;
296 }
298 {
299 mExpressionString = property.expressionString();
300 break;
301 }
302 }
303 }
304 else
305 {
306 mFieldName.clear();
307 mExpressionString.clear();
308 }
309 mProperty = property;
310 setActive( mProperty && mProperty.isActive() );
311 updateSiblingWidgets( isActive() );
312 updateGui();
313}
314
316void QgsPropertyOverrideButton::aboutToShowMenu()
317{
318 mDefineMenu->clear();
319 // update fields so that changes made to layer's fields are reflected
321
322 bool hasExp = !mExpressionString.isEmpty();
323 QString ddTitle = tr( "Data defined override" );
324
325 QAction *ddTitleAct = mDefineMenu->addAction( ddTitle );
326 QFont titlefont = ddTitleAct->font();
327 titlefont.setItalic( true );
328 ddTitleAct->setFont( titlefont );
329 ddTitleAct->setEnabled( false );
330
331 bool addActiveAction = false;
332 if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp )
333 {
334 QgsExpression exp( mExpressionString );
335 // whether expression is parse-able
336 addActiveAction = !exp.hasParserError();
337 }
338 else if ( mProperty.propertyType() == Qgis::PropertyType::Field )
339 {
340 // whether field exists
341 addActiveAction = mFieldNameList.contains( mFieldName );
342 }
343
344 if ( addActiveAction )
345 {
346 ddTitleAct->setText( ddTitle + " (" + ( mProperty.propertyType() == Qgis::PropertyType::Expression ? tr( "expression" ) : tr( "field" ) ) + ')' );
347 mDefineMenu->addAction( mActionActive );
348 mActionActive->setText( mProperty.isActive() ? tr( "Deactivate" ) : tr( "Activate" ) );
349 mActionActive->setData( QVariant( !mProperty.isActive() ) );
350 }
351
352 if ( !mFullDescription.isEmpty() )
353 {
354 mDefineMenu->addAction( mActionDescription );
355 }
356
357 mDefineMenu->addSeparator();
358
359 // deactivate button if field already exists
360 if ( mAuxiliaryStorageEnabled && mVectorLayer )
361 {
362 mDefineMenu->addAction( mActionCreateAuxiliaryField );
363
364 const QgsAuxiliaryLayer *alayer = mVectorLayer->auxiliaryLayer();
365
366 mActionCreateAuxiliaryField->setEnabled( true );
367 mActionCreateAuxiliaryField->setChecked( false );
368
369 int index = mVectorLayer->fields().indexFromName( mFieldName );
370 int srcIndex;
371 if ( index >= 0 && alayer && mVectorLayer->isAuxiliaryField( index, srcIndex ) )
372 {
373 mActionCreateAuxiliaryField->setEnabled( false );
374 mActionCreateAuxiliaryField->setChecked( true );
375 }
376 }
377
378 bool fieldActive = false;
379 if ( !mDataTypesString.isEmpty() )
380 {
381 QAction *fieldTitleAct = mDefineMenu->addAction( tr( "Attribute Field" ) );
382 fieldTitleAct->setFont( titlefont );
383 fieldTitleAct->setEnabled( false );
384
385 mDefineMenu->addAction( mActionDataTypes );
386
387 mFieldsMenu->clear();
388
389 if ( !mFieldNameList.isEmpty() )
390 {
391
392 for ( int j = 0; j < mFieldNameList.count(); ++j )
393 {
394 QString fldname = mFieldNameList.at( j );
395 QAction *act = mFieldsMenu->addAction( mFieldDisplayNameList.at( j ) );
396 act->setIcon( mFieldIcons.at( j ) );
397 act->setData( QVariant( fldname ) );
398 if ( mFieldName == fldname )
399 {
400 act->setCheckable( true );
401 act->setChecked( mProperty.propertyType() == Qgis::PropertyType::Field );
402 fieldActive = mProperty.propertyType() == Qgis::PropertyType::Field;
403 }
404 }
405 }
406 else
407 {
408 QAction *act = mFieldsMenu->addAction( tr( "No matching field types found" ) );
409 act->setEnabled( false );
410 }
411
412 mDefineMenu->addSeparator();
413 }
414
415 mFieldsMenu->menuAction()->setCheckable( true );
416 mFieldsMenu->menuAction()->setChecked( fieldActive && mProperty.propertyType() == Qgis::PropertyType::Field && !mProperty.transformer() );
417
418 bool colorActive = false;
419 mColorsMenu->clear();
422 {
423 // project colors menu
424 QAction *colorTitleAct = mDefineMenu->addAction( tr( "Project Color" ) );
425 colorTitleAct->setFont( titlefont );
426 colorTitleAct->setEnabled( false );
427
428 QList<QgsProjectColorScheme *> projectSchemes;
429 QgsApplication::colorSchemeRegistry()->schemes( projectSchemes );
430 if ( projectSchemes.length() > 0 )
431 {
432 QgsProjectColorScheme *scheme = projectSchemes.at( 0 );
433 const QgsNamedColorList colors = scheme->fetchColors();
434 for ( const auto &color : colors )
435 {
436 if ( color.second.isEmpty() )
437 continue;
438
439 QPixmap icon = QgsColorButton::createMenuIcon( color.first, mDefinition.standardTemplate() == QgsPropertyDefinition::ColorWithAlpha );
440 QAction *act = mColorsMenu->addAction( color.second );
441 act->setIcon( icon );
442 if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp && mExpressionString == QStringLiteral( "project_color('%1')" ).arg( color.second ) )
443 {
444 act->setCheckable( true );
445 act->setChecked( true );
446 colorActive = true;
447 }
448 }
449 }
450
451 if ( mColorsMenu->actions().isEmpty() )
452 {
453 QAction *act = mColorsMenu->addAction( tr( "No colors set" ) );
454 act->setEnabled( false );
455 }
456
457 mDefineMenu->addAction( mActionColors );
458 mColorsMenu->menuAction()->setCheckable( true );
459 mColorsMenu->menuAction()->setChecked( colorActive && !mProperty.transformer() );
460
461 mDefineMenu->addSeparator();
462 }
463
464 QAction *exprTitleAct = mDefineMenu->addAction( tr( "Expression" ) );
465 exprTitleAct->setFont( titlefont );
466 exprTitleAct->setEnabled( false );
467
468 mVariablesMenu->clear();
469 bool variableActive = false;
470 if ( mExpressionContextGenerator )
471 {
472 QgsExpressionContext context = mExpressionContextGenerator->createExpressionContext();
473 QStringList variables = context.variableNames();
474 variables.sort();
475 const auto constVariables = variables;
476 for ( const QString &variable : constVariables )
477 {
478 if ( context.isReadOnly( variable ) ) //only want to show user-set variables
479 continue;
480 if ( variable.startsWith( '_' ) ) //no hidden variables
481 continue;
482
483 QAction *act = mVariablesMenu->addAction( variable );
484 act->setData( QVariant( variable ) );
485
486 if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp && mExpressionString == '@' + variable )
487 {
488 act->setCheckable( true );
489 act->setChecked( true );
490 variableActive = true;
491 }
492 }
493 }
494
495 if ( mVariablesMenu->actions().isEmpty() )
496 {
497 QAction *act = mVariablesMenu->addAction( tr( "No variables set" ) );
498 act->setEnabled( false );
499 }
500
501 mDefineMenu->addAction( mActionVariables );
502 mVariablesMenu->menuAction()->setCheckable( true );
503 mVariablesMenu->menuAction()->setChecked( variableActive && !mProperty.transformer() );
504
505 if ( hasExp )
506 {
507 QString expString = mExpressionString;
508 if ( expString.length() > 35 )
509 {
510 expString.truncate( 35 );
511 expString.append( QChar( 0x2026 ) );
512 }
513
514 expString.prepend( tr( "Current: " ) );
515
516 if ( !mActionExpression )
517 {
518 mActionExpression = new QAction( expString, this );
519 mActionExpression->setCheckable( true );
520 }
521 else
522 {
523 mActionExpression->setText( expString );
524 }
525 mDefineMenu->addAction( mActionExpression );
526 mActionExpression->setChecked( mProperty.propertyType() == Qgis::PropertyType::Expression && !variableActive && !colorActive && !mProperty.transformer() );
527
528 mDefineMenu->addAction( mActionExpDialog );
529 mDefineMenu->addAction( mActionCopyExpr );
530 mDefineMenu->addAction( mActionPasteExpr );
531 }
532 else
533 {
534 mDefineMenu->addAction( mActionExpDialog );
535 mDefineMenu->addAction( mActionPasteExpr );
536 }
537
538 if ( hasExp || !mFieldName.isEmpty() )
539 {
540 mDefineMenu->addSeparator();
541 mDefineMenu->addAction( mActionClearExpr );
542 }
543
544 if ( !mDefinition.name().isEmpty() && mDefinition.supportsAssistant() )
545 {
546 mDefineMenu->addSeparator();
547 mActionAssistant->setCheckable( mProperty.transformer() );
548 mActionAssistant->setChecked( mProperty.transformer() );
549 mDefineMenu->addAction( mActionAssistant );
550 }
551}
552
553void QgsPropertyOverrideButton::menuActionTriggered( QAction *action )
554{
555 if ( action == mActionActive )
556 {
557 setActivePrivate( mActionActive->data().toBool() );
558 updateGui();
559 emit changed();
560 }
561 else if ( action == mActionDescription )
562 {
563 showDescriptionDialog();
564 }
565 else if ( action == mActionExpDialog )
566 {
567 showExpressionDialog();
568 }
569 else if ( action == mActionExpression )
570 {
571 mProperty.setExpressionString( mExpressionString );
572 mProperty.setTransformer( nullptr );
573 setActivePrivate( true );
574 updateSiblingWidgets( isActive() );
575 updateGui();
576 emit changed();
577 }
578 else if ( action == mActionCopyExpr )
579 {
580 QApplication::clipboard()->setText( mExpressionString );
581 }
582 else if ( action == mActionPasteExpr )
583 {
584 QString exprString = QApplication::clipboard()->text();
585 if ( !exprString.isEmpty() )
586 {
587 mExpressionString = exprString;
588 mProperty.setExpressionString( mExpressionString );
589 mProperty.setTransformer( nullptr );
590 setActivePrivate( true );
591 updateSiblingWidgets( isActive() );
592 updateGui();
593 emit changed();
594 }
595 }
596 else if ( action == mActionClearExpr )
597 {
598 setActivePrivate( false );
599 mProperty.setStaticValue( QVariant() );
600 mProperty.setTransformer( nullptr );
601 mExpressionString.clear();
602 mFieldName.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 {
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
660void 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
669void 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() == Qgis::PropertyType::Static && !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 bool active = mProperty.isActive();
683 mProperty.setExpressionString( mExpressionString );
684 mProperty.setTransformer( nullptr );
685 mProperty.setActive( !mExpressionString.isEmpty() );
686 if ( mProperty.isActive() != active )
687 emit activated( mProperty.isActive() );
688 updateSiblingWidgets( isActive() );
689 updateGui();
690 emit changed();
691 }
692 activateWindow(); // reset focus to parent window
693}
694
695void QgsPropertyOverrideButton::showAssistant()
696{
697 //first step - try to convert any existing expression to a transformer if one doesn't
698 //already exist
699 if ( !mProperty.transformer() )
700 {
701 ( void )mProperty.convertToTransformer();
702 }
703
705 QgsPropertyAssistantWidget *widget = new QgsPropertyAssistantWidget( panel, mDefinition, mProperty, mVectorLayer );
706 widget->registerExpressionContextGenerator( mExpressionContextGenerator );
707 widget->setSymbol( mSymbol ); // we only show legend preview in dialog version
708
709 if ( panel && panel->dockMode() )
710 {
711 connect( widget, &QgsPropertyAssistantWidget::widgetChanged, this, [this, widget]
712 {
713 widget->updateProperty( this->mProperty );
714 mExpressionString = this->mProperty.asExpression();
715 mFieldName = this->mProperty.field();
716 updateSiblingWidgets( isActive() );
717 this->emit changed();
718 } );
719
720 // if the source layer is removed, we need to dismiss the assistant immediately
721 if ( mVectorLayer )
722 connect( mVectorLayer, &QObject::destroyed, widget, &QgsPanelWidget::acceptPanel );
723
724 connect( widget, &QgsPropertyAssistantWidget::panelAccepted, this, [ = ] { updateGui(); } );
725
726 panel->openPanel( widget );
727 return;
728 }
729 else
730 {
731 // Show the dialog version if not in a panel
732 QDialog *dlg = new QDialog( this );
733 QString key = QStringLiteral( "/UI/paneldialog/%1" ).arg( widget->panelTitle() );
734 QgsSettings settings;
735 dlg->restoreGeometry( settings.value( key ).toByteArray() );
736 dlg->setWindowTitle( widget->panelTitle() );
737 dlg->setLayout( new QVBoxLayout() );
738 dlg->layout()->addWidget( widget );
739 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok );
740 connect( buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept );
741 connect( buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject );
742 connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsPropertyOverrideButton::showHelp );
743 dlg->layout()->addWidget( buttonBox );
744
745 if ( dlg->exec() == QDialog::Accepted )
746 {
747 widget->updateProperty( mProperty );
748 mExpressionString = mProperty.asExpression();
749 mFieldName = mProperty.field();
750 widget->acceptPanel();
751 updateSiblingWidgets( isActive() );
752 updateGui();
753
754 emit changed();
755 }
756 settings.setValue( key, dlg->saveGeometry() );
757 }
758}
759
760void QgsPropertyOverrideButton::updateGui()
761{
762 bool hasExp = !mExpressionString.isEmpty();
763 bool hasField = !mFieldName.isEmpty();
764
765 QIcon icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefine.svg" ) );
766 QString deftip = tr( "undefined" );
767 QString deftype;
768 if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp )
769 {
770 icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpressionOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpression.svg" ) );
771
772 const thread_local QRegularExpression rx( QStringLiteral( "^project_color\\('(.*)'\\)$" ) );
773 QRegularExpressionMatch match = rx.match( mExpressionString );
774 if ( match.hasMatch() )
775 {
776 icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColorOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColor.svg" ) );
777 deftip = match.captured( 1 );
778 deftype = tr( "project color" );
779 }
780 else
781 {
782 QgsExpression exp( mExpressionString );
783 if ( exp.hasParserError() )
784 {
785 icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpressionError.svg" ) );
786 deftip = tr( "Parse error: %1" ).arg( exp.parserErrorString() );
787 }
788 else
789 {
790 deftip = mExpressionString;
791 }
792 }
793 }
794 else if ( mProperty.propertyType() != Qgis::PropertyType::Expression && hasField )
795 {
796 icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefine.svg" ) );
797
798 if ( !mFieldNameList.contains( mFieldName ) && !mProperty.transformer() )
799 {
800 icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineError.svg" ) );
801 deftip = tr( "'%1' field missing" ).arg( mFieldName );
802 }
803 else
804 {
805 deftip = mFieldName;
806 }
807 }
808
809 setIcon( icon );
810
811 // build full description for tool tip and popup dialog
812 mFullDescription = tr( "<b><u>Data defined override</u></b><br>" );
813
814 mFullDescription += tr( "<b>Active: </b>%1&nbsp;&nbsp;&nbsp;<i>(ctrl|right-click toggles)</i><br>" ).arg( mProperty.isActive() ? tr( "yes" ) : tr( "no" ) );
815
816 if ( !mUsageInfo.isEmpty() )
817 {
818 mFullDescription += tr( "<b>Usage:</b><br>%1<br>" ).arg( mUsageInfo );
819 }
820
821 if ( !mInputDescription.isEmpty() )
822 {
823 mFullDescription += tr( "<b>Expected input:</b><br>%1<br>" ).arg( mInputDescription );
824 }
825
826 if ( !mDataTypesString.isEmpty() )
827 {
828 mFullDescription += tr( "<b>Valid input types:</b><br>%1<br>" ).arg( mDataTypesString );
829 }
830
831 if ( deftype.isEmpty() && deftip != tr( "undefined" ) )
832 {
833 deftype = mProperty.propertyType() == Qgis::PropertyType::Expression ? tr( "expression" ) : tr( "field" );
834 }
835
836 // truncate long expressions, or tool tip may be too wide for screen
837 if ( deftip.length() > 75 )
838 {
839 deftip.truncate( 75 );
840 deftip.append( QChar( 0x2026 ) );
841 }
842
843 mFullDescription += tr( "<b>Current definition (%1):</b><br>%2" ).arg( deftype, deftip );
844
845 setToolTip( mFullDescription );
846
847}
848
849void QgsPropertyOverrideButton::setActivePrivate( bool active )
850{
851 if ( mProperty.isActive() != active )
852 {
853 mProperty.setActive( active );
854 emit activated( mProperty.isActive() );
855 }
856}
857
858void QgsPropertyOverrideButton::updateSiblingWidgets( bool state )
859{
860 const auto constMSiblingWidgets = mSiblingWidgets;
861 for ( const SiblingWidget &sw : constMSiblingWidgets )
862 {
863 switch ( sw.mSiblingType )
864 {
865
866 case SiblingCheckState:
867 {
868 // don't uncheck, only set to checked
869 if ( state )
870 {
871 QAbstractButton *btn = qobject_cast< QAbstractButton * >( sw.mWidgetPointer.data() );
872 if ( btn && btn->isCheckable() )
873 {
874 btn->setChecked( sw.mNatural ? state : !state );
875 }
876 else
877 {
878 QGroupBox *grpbx = qobject_cast< QGroupBox * >( sw.mWidgetPointer.data() );
879 if ( grpbx && grpbx->isCheckable() )
880 {
881 grpbx->setChecked( sw.mNatural ? state : !state );
882 }
883 }
884 }
885 break;
886 }
887
888 case SiblingEnableState:
889 {
890 QLineEdit *le = qobject_cast< QLineEdit * >( sw.mWidgetPointer.data() );
891 if ( le )
892 le->setReadOnly( sw.mNatural ? !state : state );
893 else
894 sw.mWidgetPointer.data()->setEnabled( sw.mNatural ? state : !state );
895 break;
896 }
897
898 case SiblingVisibility:
899 {
900 sw.mWidgetPointer.data()->setVisible( sw.mNatural ? state : !state );
901 break;
902 }
903
904 case SiblingExpressionText:
905 {
906 QLineEdit *le = qobject_cast<QLineEdit *>( sw.mWidgetPointer.data() );
907 if ( le )
908 {
909 le->setText( mProperty.asExpression() );
910 }
911 else
912 {
913 QTextEdit *te = qobject_cast<QTextEdit *>( sw.mWidgetPointer.data() );
914 if ( te )
915 {
916 te->setText( mProperty.asExpression() );
917 }
918 }
919 break;
920 }
921
922 case SiblingLinkedWidget:
923 {
924 if ( QgsColorButton *cb = qobject_cast< QgsColorButton * >( sw.mWidgetPointer.data() ) )
925 {
926 if ( state && mProperty.isProjectColor() )
927 {
928 const thread_local QRegularExpression rx( QStringLiteral( "^project_color\\('(.*)'\\)$" ) );
929 QRegularExpressionMatch match = rx.match( mExpressionString );
930 if ( match.hasMatch() )
931 {
932 cb->linkToProjectColor( match.captured( 1 ) );
933 }
934 }
935 else
936 {
937 cb->linkToProjectColor( QString() );
938 }
939 }
940 break;
941 }
942 }
943 }
944}
945
946
947
949{
950 if ( mProperty.isActive() != active )
951 {
952 mProperty.setActive( active );
953 updateGui();
954 emit changed();
955 emit activated( mProperty.isActive() );
956 }
957}
958
960{
961 mExpressionContextGenerator = generator;
962}
963
965{
966 for ( const SiblingWidget &sw : std::as_const( mSiblingWidgets ) )
967 {
968 if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingLinkedWidget )
969 return;
970 }
971 mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingLinkedWidget ) );
972
973 if ( QgsColorButton *cb = qobject_cast< QgsColorButton * >( widget ) )
974 {
975 connect( cb, &QgsColorButton::unlinked, this, [ = ]
976 {
977 setActive( false );
978 updateGui();
979 } );
980 }
981
982 updateSiblingWidgets( isActive() );
983}
984
985void QgsPropertyOverrideButton::showHelp()
986{
987 QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html#data-defined" ) );
988}
@ Invalid
Invalid (not set) property.
@ Field
Field based property.
@ Static
Static property.
@ Expression
Expression based property.
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:53
Container of fields for a vector layer.
Definition qgsfields.h:46
Q_INVOKABLE int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
QIcon iconForField(int fieldIdx, bool considerOrigin=false) const
Returns an icon corresponding to a field index, based on the field's type and source.
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition qgshelp.cpp:39
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:45
StandardPropertyTemplate standardTemplate() const
Returns the property's standard template, if applicable.
QString helpText() const
Helper text for using the property, including a description of the valid values for the property.
DataType dataType() const
Returns the allowable field/value data type for the property.
@ ColorNoAlpha
Color with no alpha channel.
Definition qgsproperty.h:63
@ ColorWithAlpha
Color with alpha channel.
Definition qgsproperty.h:62
QString name() const
Returns the name of the property.
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:90
@ DataTypeBoolean
Property requires a boolean value.
@ DataTypeNumeric
Property requires a numeric value.
Definition qgsproperty.h:97
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.
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.
Qgis::PropertyType propertyType() const
Returns the property type.
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.
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:64
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.
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.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...
QMap< int, QgsPropertyDefinition > QgsPropertiesDefinition
Definition of available properties.