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