QGIS API Documentation 3.41.0-Master (3440c17df1d)
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 const QgsVectorLayer *layer )
40 : QToolButton( parent )
41 , mVectorLayer( layer )
42
43{
44 setFocusPolicy( Qt::StrongFocus );
45
46 QString ss = QStringLiteral( "QgsPropertyOverrideButton { background: none; border: 1px solid rgba(0, 0, 0, 0%); } QgsPropertyOverrideButton:focus { border: 1px solid palette(highlight); }" );
47#ifdef Q_OS_MACOS
48 ss += QLatin1String( "QgsPropertyOverrideButton::menu-indicator { width: 5px; }" );
49#endif
50 setStyleSheet( ss );
51
52 int iconSize = QgsGuiUtils::scaleIconSize( 24 );
53
54 // button width is 1.25 * icon size, height 1.1 * icon size. But we round to ensure even pixel sizes for equal margins
55 setFixedSize( 2 * static_cast< int >( 1.25 * iconSize / 2.0 ), 2 * static_cast< int >( iconSize * 1.1 / 2.0 ) );
56
57 setIconSize( QSize( iconSize, iconSize ) );
58 setPopupMode( QToolButton::InstantPopup );
59
60 connect( this, &QgsPropertyOverrideButton::activated, this, &QgsPropertyOverrideButton::updateSiblingWidgets );
61
62 mDefineMenu = new QMenu( this );
63 connect( mDefineMenu, &QMenu::aboutToShow, this, &QgsPropertyOverrideButton::aboutToShowMenu );
64 connect( mDefineMenu, &QMenu::triggered, this, &QgsPropertyOverrideButton::menuActionTriggered );
65 setMenu( mDefineMenu );
66
67 mFieldsMenu = new QMenu( this );
68 mActionDataTypes = new QAction( this );
69 // list fields and types in submenu, since there may be many
70 mActionDataTypes->setMenu( mFieldsMenu );
71
72 mActionVariables = new QAction( tr( "Variable" ), this );
73 mVariablesMenu = new QMenu( this );
74 mActionVariables->setMenu( mVariablesMenu );
75
76 mActionColors = new QAction( tr( "Color" ), this );
77 mColorsMenu = new QMenu( this );
78 mActionColors->setMenu( mColorsMenu );
79
80 mActionActive = new QAction( this );
81 QFont f = mActionActive->font();
82 f.setBold( true );
83 mActionActive->setFont( f );
84
85 mActionDescription = new QAction( tr( "Description…" ), this );
86
87 mActionCreateAuxiliaryField = new QAction( tr( "Store Data in the Project" ), this );
88 mActionCreateAuxiliaryField->setCheckable( true );
89
90 mActionExpDialog = new QAction( tr( "Edit…" ), this );
91 mActionExpression = nullptr;
92 mActionPasteExpr = new QAction( tr( "Paste" ), this );
93 mActionCopyExpr = new QAction( tr( "Copy" ), this );
94 mActionClearExpr = new QAction( tr( "Clear" ), this );
95 mActionAssistant = new QAction( tr( "Assistant…" ), this );
96 QFont assistantFont = mActionAssistant->font();
97 assistantFont.setBold( true );
98 mActionAssistant->setFont( assistantFont );
99 mDefineMenu->addAction( mActionAssistant );
100}
101
102
103void QgsPropertyOverrideButton::init( int propertyKey, const QgsProperty &property, const QgsPropertiesDefinition &definitions, const QgsVectorLayer *layer, bool auxiliaryStorageEnabled )
104{
105 init( propertyKey, property, definitions.value( propertyKey ), layer, auxiliaryStorageEnabled );
106}
107
108void QgsPropertyOverrideButton::init( int propertyKey, const QgsProperty &property, const QgsPropertyDefinition &definition, const QgsVectorLayer *layer, bool auxiliaryStorageEnabled )
109{
110 mVectorLayer = layer;
111 mAuxiliaryStorageEnabled = auxiliaryStorageEnabled;
112 setToProperty( property );
113 mPropertyKey = propertyKey;
114
115 mDefinition = definition;
116 mDataTypes = mDefinition.dataType();
117
118 mInputDescription = mDefinition.helpText();
119 mFullDescription.clear();
120 mUsageInfo.clear();
121
122 // set up data types string
123 mDataTypesString.clear();
124
125 QStringList ts;
126 switch ( mDataTypes )
127 {
129 ts << tr( "boolean" );
130 [[fallthrough]];
131
133 ts << tr( "int" );
134 ts << tr( "double" );
135 [[fallthrough]];
136
138 ts << tr( "string" );
139 break;
140 }
141
142 if ( !ts.isEmpty() )
143 {
144 mDataTypesString = ts.join( QLatin1String( ", " ) );
145 mActionDataTypes->setText( tr( "Field type: " ) + mDataTypesString );
146 }
147
149 updateGui();
150 updateSiblingWidgets( isActive() );
151}
152
153void QgsPropertyOverrideButton::init( int propertyKey, const QgsAbstractPropertyCollection &collection, const QgsPropertiesDefinition &definitions, const QgsVectorLayer *layer, bool auxiliaryStorageEnabled )
154{
155 init( propertyKey, collection.property( propertyKey ), definitions, layer, auxiliaryStorageEnabled );
156}
157
158
160{
161 mFieldNameList.clear();
162 mFieldDisplayNameList.clear();
163 mFieldIcons.clear();
164
165 if ( mVectorLayer )
166 {
167 // store just a list of fields of unknown type or those that match the expected type
168 const QgsFields fields = mVectorLayer->fields();
169 int idx = 0;
170 for ( const QgsField &f : fields )
171 {
172 bool fieldMatch = false;
173 switch ( mDataTypes )
174 {
176 fieldMatch = true;
177 break;
178
180 fieldMatch = f.isNumeric() || f.type() == QMetaType::Type::QString;
181 break;
182
184 fieldMatch = f.type() == QMetaType::Type::QString;
185 break;
186 }
187
188 if ( fieldMatch )
189 {
190 mFieldNameList << f.name();
191 mFieldDisplayNameList << f.displayNameWithAlias();
192 mFieldIcons << fields.iconForField( idx, true );
193 }
194 idx++;
195 }
196 }
197}
198
200{
201 return mProperty;
202}
203
205{
206 mVectorLayer = layer;
208 updateGui();
209}
210
211void QgsPropertyOverrideButton::registerCheckedWidget( QWidget *widget, bool natural )
212{
213 const auto constMSiblingWidgets = mSiblingWidgets;
214 for ( const SiblingWidget &sw : constMSiblingWidgets )
215 {
216 if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingCheckState )
217 return;
218 }
219 mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingCheckState, natural ) );
220 updateSiblingWidgets( isActive() );
221}
222
223void QgsPropertyOverrideButton::registerEnabledWidget( QWidget *widget, bool natural )
224{
225 const auto constMSiblingWidgets = mSiblingWidgets;
226 for ( const SiblingWidget &sw : constMSiblingWidgets )
227 {
228 if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingEnableState )
229 return;
230 }
231 mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingEnableState, natural ) );
232 updateSiblingWidgets( isActive() );
233}
234
235void QgsPropertyOverrideButton::registerVisibleWidget( QWidget *widget, bool natural )
236{
237 const auto constMSiblingWidgets = mSiblingWidgets;
238 for ( const SiblingWidget &sw : constMSiblingWidgets )
239 {
240 if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingVisibility )
241 return;
242 }
243 mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingVisibility, natural ) );
244 updateSiblingWidgets( isActive() );
245}
246
248{
249 const auto constMSiblingWidgets = mSiblingWidgets;
250 for ( const SiblingWidget &sw : constMSiblingWidgets )
251 {
252 if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingExpressionText )
253 return;
254 }
255 mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingExpressionText ) );
256 updateSiblingWidgets( isActive() );
257}
258
259
261{
262 // Ctrl-click to toggle activated state
263 if ( ( event->modifiers() & ( Qt::ControlModifier ) )
264 || event->button() == Qt::RightButton )
265 {
266 setActivePrivate( !mProperty.isActive() );
267 updateGui();
268 emit changed();
269 event->ignore();
270 return;
271 }
272
273 // Middle button click to open the Expression Builder dialog
274 if ( event->button() == Qt::MiddleButton )
275 {
276 showExpressionDialog();
277 return;
278 }
279
280 // pass to default behavior
281 QToolButton::mousePressEvent( event );
282}
283
285{
286 if ( property )
287 {
288 switch ( property.propertyType() )
289 {
292 break;
294 {
295 mFieldName = property.field();
296 break;
297 }
299 {
300 mExpressionString = property.expressionString();
301 break;
302 }
303 }
304 }
305 else
306 {
307 mFieldName.clear();
308 mExpressionString.clear();
309 }
310 mProperty = property;
311 setActive( mProperty && mProperty.isActive() );
312 updateSiblingWidgets( isActive() );
313 updateGui();
314}
315
317void QgsPropertyOverrideButton::aboutToShowMenu()
318{
319 mDefineMenu->clear();
320 // update fields so that changes made to layer's fields are reflected
322
323 bool hasExp = !mExpressionString.isEmpty();
324 QString ddTitle = tr( "Data defined override" );
325
326 QAction *ddTitleAct = mDefineMenu->addAction( ddTitle );
327 QFont titlefont = ddTitleAct->font();
328 titlefont.setItalic( true );
329 ddTitleAct->setFont( titlefont );
330 ddTitleAct->setEnabled( false );
331
332 bool addActiveAction = false;
333 if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp )
334 {
335 QgsExpression exp( mExpressionString );
336 // whether expression is parse-able
337 addActiveAction = !exp.hasParserError();
338 }
339 else if ( mProperty.propertyType() == Qgis::PropertyType::Field )
340 {
341 // whether field exists
342 addActiveAction = mFieldNameList.contains( mFieldName );
343 }
344
345 if ( addActiveAction )
346 {
347 ddTitleAct->setText( ddTitle + " (" + ( mProperty.propertyType() == Qgis::PropertyType::Expression ? tr( "expression" ) : tr( "field" ) ) + ')' );
348 mDefineMenu->addAction( mActionActive );
349 mActionActive->setText( mProperty.isActive() ? tr( "Deactivate" ) : tr( "Activate" ) );
350 mActionActive->setData( QVariant( !mProperty.isActive() ) );
351 }
352
353 if ( !mFullDescription.isEmpty() )
354 {
355 mDefineMenu->addAction( mActionDescription );
356 }
357
358 mDefineMenu->addSeparator();
359
360 // deactivate button if field already exists
361 if ( mAuxiliaryStorageEnabled && mVectorLayer )
362 {
363 mDefineMenu->addAction( mActionCreateAuxiliaryField );
364
365 const QgsAuxiliaryLayer *alayer = mVectorLayer->auxiliaryLayer();
366
367 mActionCreateAuxiliaryField->setEnabled( true );
368 mActionCreateAuxiliaryField->setChecked( false );
369
370 int index = mVectorLayer->fields().indexFromName( mFieldName );
371 int srcIndex;
372 if ( index >= 0 && alayer && mVectorLayer->isAuxiliaryField( index, srcIndex ) )
373 {
374 mActionCreateAuxiliaryField->setEnabled( false );
375 mActionCreateAuxiliaryField->setChecked( true );
376 }
377 }
378
379 bool fieldActive = false;
380 if ( !mDataTypesString.isEmpty() )
381 {
382 QAction *fieldTitleAct = mDefineMenu->addAction( tr( "Attribute Field" ) );
383 fieldTitleAct->setFont( titlefont );
384 fieldTitleAct->setEnabled( false );
385
386 mDefineMenu->addAction( mActionDataTypes );
387
388 mFieldsMenu->clear();
389
390 if ( !mFieldNameList.isEmpty() )
391 {
392
393 for ( int j = 0; j < mFieldNameList.count(); ++j )
394 {
395 QString fldname = mFieldNameList.at( j );
396 QAction *act = mFieldsMenu->addAction( mFieldDisplayNameList.at( j ) );
397 act->setIcon( mFieldIcons.at( j ) );
398 act->setData( QVariant( fldname ) );
399 if ( mFieldName == fldname )
400 {
401 act->setCheckable( true );
402 act->setChecked( mProperty.propertyType() == Qgis::PropertyType::Field );
403 fieldActive = mProperty.propertyType() == Qgis::PropertyType::Field;
404 }
405 }
406 }
407 else
408 {
409 QAction *act = mFieldsMenu->addAction( tr( "No matching field types found" ) );
410 act->setEnabled( false );
411 }
412
413 mDefineMenu->addSeparator();
414 }
415
416 mFieldsMenu->menuAction()->setCheckable( true );
417 mFieldsMenu->menuAction()->setChecked( fieldActive && mProperty.propertyType() == Qgis::PropertyType::Field && !mProperty.transformer() );
418
419 bool colorActive = false;
420 mColorsMenu->clear();
423 {
424 // project colors menu
425 QAction *colorTitleAct = mDefineMenu->addAction( tr( "Project Color" ) );
426 colorTitleAct->setFont( titlefont );
427 colorTitleAct->setEnabled( false );
428
429 QList<QgsProjectColorScheme *> projectSchemes;
430 QgsApplication::colorSchemeRegistry()->schemes( projectSchemes );
431 if ( projectSchemes.length() > 0 )
432 {
433 QgsProjectColorScheme *scheme = projectSchemes.at( 0 );
434 const QgsNamedColorList colors = scheme->fetchColors();
435 for ( const auto &color : colors )
436 {
437 if ( color.second.isEmpty() )
438 continue;
439
440 QPixmap icon = QgsColorButton::createMenuIcon( color.first, mDefinition.standardTemplate() == QgsPropertyDefinition::ColorWithAlpha );
441 QAction *act = mColorsMenu->addAction( color.second );
442 act->setIcon( icon );
443 if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp && getColor() == color.second )
444 {
445 act->setCheckable( true );
446 act->setChecked( true );
447 colorActive = true;
448 }
449 }
450 }
451
452 if ( mColorsMenu->actions().isEmpty() )
453 {
454 QAction *act = mColorsMenu->addAction( tr( "No colors set" ) );
455 act->setEnabled( false );
456 }
457
458 mDefineMenu->addAction( mActionColors );
459 mColorsMenu->menuAction()->setCheckable( true );
460 mColorsMenu->menuAction()->setChecked( colorActive && !mProperty.transformer() );
461
462 mDefineMenu->addSeparator();
463 }
464
465 QAction *exprTitleAct = mDefineMenu->addAction( tr( "Expression" ) );
466 exprTitleAct->setFont( titlefont );
467 exprTitleAct->setEnabled( false );
468
469 mVariablesMenu->clear();
470 bool variableActive = false;
471 if ( mExpressionContextGenerator )
472 {
473 QgsExpressionContext context = mExpressionContextGenerator->createExpressionContext();
474 QStringList variables = context.variableNames();
475 variables.sort();
476 const auto constVariables = variables;
477 for ( const QString &variable : constVariables )
478 {
479 if ( context.isReadOnly( variable ) ) //only want to show user-set variables
480 continue;
481 if ( variable.startsWith( '_' ) ) //no hidden variables
482 continue;
483
484 QAction *act = mVariablesMenu->addAction( variable );
485 act->setData( QVariant( variable ) );
486
487 if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp && mExpressionString == '@' + variable )
488 {
489 act->setCheckable( true );
490 act->setChecked( true );
491 variableActive = true;
492 }
493 }
494 }
495
496 if ( mVariablesMenu->actions().isEmpty() )
497 {
498 QAction *act = mVariablesMenu->addAction( tr( "No variables set" ) );
499 act->setEnabled( false );
500 }
501
502 mDefineMenu->addAction( mActionVariables );
503 mVariablesMenu->menuAction()->setCheckable( true );
504 mVariablesMenu->menuAction()->setChecked( variableActive && !mProperty.transformer() );
505
506 if ( hasExp )
507 {
508 QString expString = mExpressionString;
509 if ( expString.length() > 35 )
510 {
511 expString.truncate( 35 );
512 expString.append( QChar( 0x2026 ) );
513 }
514
515 expString.prepend( tr( "Current: " ) );
516
517 if ( !mActionExpression )
518 {
519 mActionExpression = new QAction( expString, this );
520 mActionExpression->setCheckable( true );
521 }
522 else
523 {
524 mActionExpression->setText( expString );
525 }
526 mDefineMenu->addAction( mActionExpression );
527 mActionExpression->setChecked( mProperty.propertyType() == Qgis::PropertyType::Expression && !variableActive && !colorActive && !mProperty.transformer() );
528
529 mDefineMenu->addAction( mActionExpDialog );
530 mDefineMenu->addAction( mActionCopyExpr );
531 mDefineMenu->addAction( mActionPasteExpr );
532 }
533 else
534 {
535 mDefineMenu->addAction( mActionExpDialog );
536 mDefineMenu->addAction( mActionPasteExpr );
537 }
538
539 if ( hasExp || !mFieldName.isEmpty() )
540 {
541 mDefineMenu->addSeparator();
542 mDefineMenu->addAction( mActionClearExpr );
543 }
544
545 if ( !mDefinition.name().isEmpty() && mDefinition.supportsAssistant() )
546 {
547 mDefineMenu->addSeparator();
548 mActionAssistant->setCheckable( mProperty.transformer() );
549 mActionAssistant->setChecked( mProperty.transformer() );
550 mDefineMenu->addAction( mActionAssistant );
551 }
552}
553
554void QgsPropertyOverrideButton::menuActionTriggered( QAction *action )
555{
556 if ( action == mActionActive )
557 {
558 setActivePrivate( mActionActive->data().toBool() );
559 updateGui();
560 emit changed();
561 }
562 else if ( action == mActionDescription )
563 {
564 showDescriptionDialog();
565 }
566 else if ( action == mActionExpDialog )
567 {
568 showExpressionDialog();
569 }
570 else if ( action == mActionExpression )
571 {
572 mProperty.setExpressionString( mExpressionString );
573 mProperty.setTransformer( nullptr );
574 setActivePrivate( true );
575 updateSiblingWidgets( isActive() );
576 updateGui();
577 emit changed();
578 }
579 else if ( action == mActionCopyExpr )
580 {
581 QApplication::clipboard()->setText( mExpressionString );
582 }
583 else if ( action == mActionPasteExpr )
584 {
585 QString exprString = QApplication::clipboard()->text();
586 if ( !exprString.isEmpty() )
587 {
588 mExpressionString = exprString;
589 mProperty.setExpressionString( mExpressionString );
590 mProperty.setTransformer( nullptr );
591 setActivePrivate( true );
592 updateSiblingWidgets( isActive() );
593 updateGui();
594 emit changed();
595 }
596 }
597 else if ( action == mActionClearExpr )
598 {
599 setActivePrivate( false );
600 mProperty.setStaticValue( QVariant() );
601 mProperty.setTransformer( nullptr );
602 mExpressionString.clear();
603 mFieldName.clear();
604 updateSiblingWidgets( isActive() );
605 updateGui();
606 emit changed();
607 }
608 else if ( action == mActionAssistant )
609 {
610 showAssistant();
611 }
612 else if ( action == mActionCreateAuxiliaryField )
613 {
615 }
616 else if ( mFieldsMenu->actions().contains( action ) ) // a field name clicked
617 {
618 if ( action->isEnabled() )
619 {
620 if ( mFieldName != action->text() )
621 {
622 mFieldName = action->data().toString();
623 }
624 mProperty.setField( mFieldName );
625 mProperty.setTransformer( nullptr );
626 setActivePrivate( true );
627 updateSiblingWidgets( isActive() );
628 updateGui();
629 emit changed();
630 }
631 }
632 else if ( mVariablesMenu->actions().contains( action ) ) // a variable name clicked
633 {
634 if ( mExpressionString != action->text().prepend( "@" ) )
635 {
636 mExpressionString = action->data().toString().prepend( "@" );
637 }
638 mProperty.setExpressionString( mExpressionString );
639 mProperty.setTransformer( nullptr );
640 setActivePrivate( true );
641 updateSiblingWidgets( isActive() );
642 updateGui();
643 emit changed();
644 }
645 else if ( mColorsMenu->actions().contains( action ) ) // a color name clicked
646 {
647 if ( getColor() != action->text() )
648 {
649 mExpressionString = QStringLiteral( "project_color_object('%1')" ).arg( action->text() );
650 }
651 mProperty.setExpressionString( mExpressionString );
652 mProperty.setTransformer( nullptr );
653 setActivePrivate( true );
654 updateSiblingWidgets( isActive() );
655 updateGui();
656 emit changed();
657 }
658}
660
661void QgsPropertyOverrideButton::showDescriptionDialog()
662{
663 QgsMessageViewer *mv = new QgsMessageViewer( this );
664 mv->setWindowTitle( tr( "Data Definition Description" ) );
665 mv->setMessageAsHtml( mFullDescription );
666 mv->exec();
667}
668
669
670void QgsPropertyOverrideButton::showExpressionDialog()
671{
672 QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : QgsExpressionContext();
673
674 // build sensible initial expression text - see https://github.com/qgis/QGIS/issues/26526
675 QString currentExpression = ( mProperty.propertyType() == Qgis::PropertyType::Static && !mProperty.staticValue().isValid() ) ? QString()
676 : mProperty.asExpression();
677
678 QgsExpressionBuilderDialog d( const_cast<QgsVectorLayer *>( mVectorLayer ), currentExpression, this, QStringLiteral( "generic" ), context );
679 d.setExpectedOutputFormat( mInputDescription );
680 if ( d.exec() == QDialog::Accepted )
681 {
682 mExpressionString = d.expressionText().trimmed();
683 bool active = mProperty.isActive();
684 mProperty.setExpressionString( mExpressionString );
685 mProperty.setTransformer( nullptr );
686 mProperty.setActive( !mExpressionString.isEmpty() );
687 if ( mProperty.isActive() != active )
688 emit activated( mProperty.isActive() );
689 updateSiblingWidgets( isActive() );
690 updateGui();
691 emit changed();
692 }
693 activateWindow(); // reset focus to parent window
694}
695
696void QgsPropertyOverrideButton::showAssistant()
697{
698 //first step - try to convert any existing expression to a transformer if one doesn't
699 //already exist
700 if ( !mProperty.transformer() )
701 {
702 ( void )mProperty.convertToTransformer();
703 }
704
706 QgsPropertyAssistantWidget *widget = new QgsPropertyAssistantWidget( panel, mDefinition, mProperty, mVectorLayer );
707 widget->registerExpressionContextGenerator( mExpressionContextGenerator );
708 widget->setSymbol( mSymbol ); // we only show legend preview in dialog version
709
710 if ( panel && panel->dockMode() )
711 {
712 connect( widget, &QgsPropertyAssistantWidget::widgetChanged, this, [this, widget]
713 {
714 widget->updateProperty( this->mProperty );
715 mExpressionString = this->mProperty.asExpression();
716 mFieldName = this->mProperty.field();
717 updateSiblingWidgets( isActive() );
718 this->emit changed();
719 } );
720
721 // if the source layer is removed, we need to dismiss the assistant immediately
722 if ( mVectorLayer )
723 connect( mVectorLayer, &QObject::destroyed, widget, &QgsPanelWidget::acceptPanel );
724
725 connect( widget, &QgsPropertyAssistantWidget::panelAccepted, this, [ = ] { updateGui(); } );
726
727 panel->openPanel( widget );
728 return;
729 }
730 else
731 {
732 // Show the dialog version if not in a panel
733 QDialog *dlg = new QDialog( this );
734 QString key = QStringLiteral( "/UI/paneldialog/%1" ).arg( widget->panelTitle() );
735 QgsSettings settings;
736 dlg->restoreGeometry( settings.value( key ).toByteArray() );
737 dlg->setWindowTitle( widget->panelTitle() );
738 dlg->setLayout( new QVBoxLayout() );
739 dlg->layout()->addWidget( widget );
740 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok );
741 connect( buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept );
742 connect( buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject );
743 connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsPropertyOverrideButton::showHelp );
744 dlg->layout()->addWidget( buttonBox );
745
746 if ( dlg->exec() == QDialog::Accepted )
747 {
748 widget->updateProperty( mProperty );
749 mExpressionString = mProperty.asExpression();
750 mFieldName = mProperty.field();
751 widget->acceptPanel();
752 updateSiblingWidgets( isActive() );
753 updateGui();
754
755 emit changed();
756 }
757 settings.setValue( key, dlg->saveGeometry() );
758 }
759}
760
761void QgsPropertyOverrideButton::updateGui()
762{
763 bool hasExp = !mExpressionString.isEmpty();
764 bool hasField = !mFieldName.isEmpty();
765
766 QIcon icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefine.svg" ) );
767 QString deftip = tr( "undefined" );
768 QString deftype;
769 if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp )
770 {
771 icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpressionOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpression.svg" ) );
772
773 const QString colorName = getColor();
774 if ( !colorName.isEmpty() )
775 {
776 icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColorOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColor.svg" ) );
777 deftip = colorName;
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 QString colorName = getColor();
929 if ( !colorName.isEmpty() )
930 {
931 cb->linkToProjectColor( colorName );
932 }
933 }
934 else
935 {
936 cb->linkToProjectColor( QString() );
937 }
938 }
939 break;
940 }
941 }
942 }
943}
944
945
946
948{
949 if ( mProperty.isActive() != active )
950 {
951 mProperty.setActive( active );
952 updateGui();
953 emit changed();
954 emit activated( mProperty.isActive() );
955 }
956}
957
959{
960 mExpressionContextGenerator = generator;
961}
962
964{
965 for ( const SiblingWidget &sw : std::as_const( mSiblingWidgets ) )
966 {
967 if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingLinkedWidget )
968 return;
969 }
970 mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingLinkedWidget ) );
971
972 if ( QgsColorButton *cb = qobject_cast< QgsColorButton * >( widget ) )
973 {
974 connect( cb, &QgsColorButton::unlinked, this, [ = ]
975 {
976 setActive( false );
977 updateGui();
978 } );
979 }
980
981 updateSiblingWidgets( isActive() );
982}
983
984void QgsPropertyOverrideButton::showHelp()
985{
986 QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html#data-defined" ) );
987}
988
989QString QgsPropertyOverrideButton::getColor() const
990{
991 const thread_local QRegularExpression rx( QStringLiteral( "^project_color(_object|)\\('(.*)'\\)$" ) );
992 QRegularExpressionMatch match = rx.match( mExpressionString );
993 return match.hasMatch() ? match.captured( 2 ) : QString();
994}
@ 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.