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