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