QGIS API Documentation 3.99.0-Master (d270888f95f)
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"
19#include "qgsauxiliarystorage.h"
20#include "qgscolorbutton.h"
22#include "qgsexpression.h"
24#include "qgsguiutils.h"
25#include "qgsmessageviewer.h"
26#include "qgspanelwidget.h"
28#include "qgsvectorlayer.h"
29
30#include <QClipboard>
31#include <QGroupBox>
32#include <QMenu>
33#include <QMouseEvent>
34#include <QPointer>
35#include <QRegularExpression>
36#include <QString>
37
38#include "moc_qgspropertyoverridebutton.cpp"
39
40using namespace Qt::StringLiterals;
41
43 : QToolButton( parent )
44 , mVectorLayer( layer )
45
46{
47 setFocusPolicy( Qt::StrongFocus );
48
49 QString ss = u"QgsPropertyOverrideButton { background: none; border: 1px solid rgba(0, 0, 0, 0%); } QgsPropertyOverrideButton:focus { border: 1px solid palette(highlight); }"_s;
50#ifdef Q_OS_MACOS
51 ss += "QgsPropertyOverrideButton::menu-indicator { width: 5px; }"_L1;
52#endif
53 setStyleSheet( ss );
54
55 int iconSize = QgsGuiUtils::scaleIconSize( 24 );
56
57 // button width is 1.25 * icon size, height 1.1 * icon size. But we round to ensure even pixel sizes for equal margins
58 setFixedSize( 2 * static_cast<int>( 1.25 * iconSize / 2.0 ), 2 * static_cast<int>( iconSize * 1.1 / 2.0 ) );
59
60 setIconSize( QSize( iconSize, iconSize ) );
61 setPopupMode( QToolButton::InstantPopup );
62
63 connect( this, &QgsPropertyOverrideButton::activated, this, &QgsPropertyOverrideButton::updateSiblingWidgets );
64
65 mDefineMenu = new QMenu( this );
66 connect( mDefineMenu, &QMenu::aboutToShow, this, &QgsPropertyOverrideButton::aboutToShowMenu );
67 connect( mDefineMenu, &QMenu::triggered, this, &QgsPropertyOverrideButton::menuActionTriggered );
68 setMenu( mDefineMenu );
69
70 mFieldsMenu = new QMenu( this );
71 mActionDataTypes = new QAction( this );
72 // list fields and types in submenu, since there may be many
73 mActionDataTypes->setMenu( mFieldsMenu );
74
75 mActionVariables = new QAction( tr( "Variable" ), this );
76 mVariablesMenu = new QMenu( this );
77 mActionVariables->setMenu( mVariablesMenu );
78
79 mActionColors = new QAction( tr( "Color" ), this );
80 mColorsMenu = new QMenu( this );
81 mActionColors->setMenu( mColorsMenu );
82
83 mActionActive = new QAction( this );
84 QFont f = mActionActive->font();
85 f.setBold( true );
86 mActionActive->setFont( f );
87
88 mActionDescription = new QAction( tr( "Description…" ), this );
89
90 mActionCreateAuxiliaryField = new QAction( tr( "Store Data in the Project" ), this );
91 mActionCreateAuxiliaryField->setCheckable( true );
92
93 mActionExpDialog = new QAction( tr( "Edit…" ), this );
94 mActionExpression = nullptr;
95 mActionPasteExpr = new QAction( tr( "Paste" ), this );
96 mActionCopyExpr = new QAction( tr( "Copy" ), this );
97 mActionClearExpr = new QAction( tr( "Clear" ), this );
98 mActionAssistant = new QAction( tr( "Assistant…" ), this );
99 QFont assistantFont = mActionAssistant->font();
100 assistantFont.setBold( true );
101 mActionAssistant->setFont( assistantFont );
102 mDefineMenu->addAction( mActionAssistant );
103}
104
105
106void QgsPropertyOverrideButton::init( int propertyKey, const QgsProperty &property, const QgsPropertiesDefinition &definitions, const QgsVectorLayer *layer, bool auxiliaryStorageEnabled )
107{
108 init( propertyKey, property, definitions.value( propertyKey ), layer, auxiliaryStorageEnabled );
109}
110
111void QgsPropertyOverrideButton::init( int propertyKey, const QgsProperty &property, const QgsPropertyDefinition &definition, const QgsVectorLayer *layer, bool auxiliaryStorageEnabled )
112{
113 mVectorLayer = layer;
114 mAuxiliaryStorageEnabled = auxiliaryStorageEnabled;
115 setToProperty( property );
116 mPropertyKey = propertyKey;
117
118 mDefinition = definition;
119 mDataTypes = mDefinition.dataType();
120
121 mInputDescription = mDefinition.helpText();
122 mFullDescription.clear();
123 mUsageInfo.clear();
124
125 // set up data types string
126 mDataTypesString.clear();
127
128 QStringList ts;
129 switch ( mDataTypes )
130 {
132 ts << tr( "boolean" );
133 [[fallthrough]];
134
136 ts << tr( "int" );
137 ts << tr( "double" );
138 [[fallthrough]];
139
141 ts << tr( "string" );
142 break;
143 }
144
145 if ( !ts.isEmpty() )
146 {
147 mDataTypesString = ts.join( ", "_L1 );
148 mActionDataTypes->setText( tr( "Field type: " ) + mDataTypesString );
149 }
150
152 updateGui();
153 updateSiblingWidgets( isActive() );
154}
155
156void QgsPropertyOverrideButton::init( int propertyKey, const QgsAbstractPropertyCollection &collection, const QgsPropertiesDefinition &definitions, const QgsVectorLayer *layer, bool auxiliaryStorageEnabled )
157{
158 init( propertyKey, collection.property( propertyKey ), definitions, layer, auxiliaryStorageEnabled );
159}
160
161
163{
164 mFieldNameList.clear();
165 mFieldDisplayNameList.clear();
166 mFieldIcons.clear();
167
168 if ( mVectorLayer )
169 {
170 // store just a list of fields of unknown type or those that match the expected type
171 const QgsFields fields = mVectorLayer->fields();
172 int idx = 0;
173 for ( const QgsField &f : fields )
174 {
175 bool fieldMatch = false;
176 switch ( mDataTypes )
177 {
179 fieldMatch = true;
180 break;
181
183 fieldMatch = f.isNumeric() || f.type() == QMetaType::Type::QString;
184 break;
185
187 fieldMatch = f.type() == QMetaType::Type::QString;
188 break;
189 }
190
191 if ( fieldMatch )
192 {
193 mFieldNameList << f.name();
194 mFieldDisplayNameList << f.displayNameWithAlias();
195 mFieldIcons << fields.iconForField( idx, true );
196 }
197 idx++;
198 }
199 }
200}
201
203{
204 return mProperty;
205}
206
208{
209 mVectorLayer = layer;
211 updateGui();
212}
213
214void QgsPropertyOverrideButton::registerCheckedWidget( QWidget *widget, bool natural )
215{
216 const auto constMSiblingWidgets = mSiblingWidgets;
217 for ( const SiblingWidget &sw : constMSiblingWidgets )
218 {
219 if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingCheckState )
220 return;
221 }
222 mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingCheckState, natural ) );
223 updateSiblingWidgets( isActive() );
224}
225
226void QgsPropertyOverrideButton::registerEnabledWidget( QWidget *widget, bool natural )
227{
228 const auto constMSiblingWidgets = mSiblingWidgets;
229 for ( const SiblingWidget &sw : constMSiblingWidgets )
230 {
231 if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingEnableState )
232 return;
233 }
234 mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingEnableState, natural ) );
235 updateSiblingWidgets( isActive() );
236}
237
238void QgsPropertyOverrideButton::registerVisibleWidget( QWidget *widget, bool natural )
239{
240 const auto constMSiblingWidgets = mSiblingWidgets;
241 for ( const SiblingWidget &sw : constMSiblingWidgets )
242 {
243 if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingVisibility )
244 return;
245 }
246 mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingVisibility, natural ) );
247 updateSiblingWidgets( isActive() );
248}
249
251{
252 const auto constMSiblingWidgets = mSiblingWidgets;
253 for ( const SiblingWidget &sw : constMSiblingWidgets )
254 {
255 if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingExpressionText )
256 return;
257 }
258 mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingExpressionText ) );
259 updateSiblingWidgets( isActive() );
260}
261
262
264{
265 // Ctrl-click to toggle activated state
266 if ( ( event->modifiers() & ( Qt::ControlModifier ) )
267 || event->button() == Qt::RightButton )
268 {
269 setActivePrivate( !mProperty.isActive() );
270 updateGui();
271 emit changed();
272 event->ignore();
273 return;
274 }
275
276 // Middle button click to open the Expression Builder dialog
277 if ( event->button() == Qt::MiddleButton )
278 {
279 showExpressionDialog();
280 return;
281 }
282
283 // pass to default behavior
284 QToolButton::mousePressEvent( event );
285}
286
288{
289 if ( property )
290 {
291 switch ( property.propertyType() )
292 {
295 break;
297 {
298 mFieldName = property.field();
299 break;
300 }
302 {
303 mExpressionString = property.expressionString();
304 break;
305 }
306 }
307 }
308 else
309 {
310 mFieldName.clear();
311 mExpressionString.clear();
312 }
313 mProperty = property;
314 setActive( mProperty && mProperty.isActive() );
315 updateSiblingWidgets( isActive() );
316 updateGui();
317}
318
320void QgsPropertyOverrideButton::aboutToShowMenu()
321{
322 mDefineMenu->clear();
323 // update fields so that changes made to layer's fields are reflected
325
326 bool hasExp = !mExpressionString.isEmpty();
327 QString ddTitle = tr( "Data defined override" );
328
329 QAction *ddTitleAct = mDefineMenu->addAction( ddTitle );
330 QFont titlefont = ddTitleAct->font();
331 titlefont.setItalic( true );
332 ddTitleAct->setFont( titlefont );
333 ddTitleAct->setEnabled( false );
334
335 bool addActiveAction = false;
336 if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp )
337 {
338 QgsExpression exp( mExpressionString );
339 // whether expression is parse-able
340 addActiveAction = !exp.hasParserError();
341 }
342 else if ( mProperty.propertyType() == Qgis::PropertyType::Field )
343 {
344 // whether field exists
345 addActiveAction = mFieldNameList.contains( mFieldName );
346 }
347
348 if ( addActiveAction )
349 {
350 ddTitleAct->setText( ddTitle + " (" + ( mProperty.propertyType() == Qgis::PropertyType::Expression ? tr( "expression" ) : tr( "field" ) ) + ')' );
351 mDefineMenu->addAction( mActionActive );
352 mActionActive->setText( mProperty.isActive() ? tr( "Deactivate" ) : tr( "Activate" ) );
353 mActionActive->setData( QVariant( !mProperty.isActive() ) );
354 }
355
356 if ( !mFullDescription.isEmpty() )
357 {
358 mDefineMenu->addAction( mActionDescription );
359 }
360
361 mDefineMenu->addSeparator();
362
363 // deactivate button if field already exists
364 if ( mAuxiliaryStorageEnabled && mVectorLayer )
365 {
366 mDefineMenu->addAction( mActionCreateAuxiliaryField );
367
368 const QgsAuxiliaryLayer *alayer = mVectorLayer->auxiliaryLayer();
369
370 mActionCreateAuxiliaryField->setEnabled( true );
371 mActionCreateAuxiliaryField->setChecked( false );
372
373 int index = mVectorLayer->fields().indexFromName( mFieldName );
374 int srcIndex;
375 if ( index >= 0 && alayer && mVectorLayer->isAuxiliaryField( index, srcIndex ) )
376 {
377 mActionCreateAuxiliaryField->setEnabled( false );
378 mActionCreateAuxiliaryField->setChecked( true );
379 }
380 }
381
382 bool fieldActive = false;
383 if ( !mDataTypesString.isEmpty() )
384 {
385 QAction *fieldTitleAct = mDefineMenu->addAction( tr( "Attribute Field" ) );
386 fieldTitleAct->setFont( titlefont );
387 fieldTitleAct->setEnabled( false );
388
389 mDefineMenu->addAction( mActionDataTypes );
390
391 mFieldsMenu->clear();
392
393 if ( !mFieldNameList.isEmpty() )
394 {
395 for ( int j = 0; j < mFieldNameList.count(); ++j )
396 {
397 QString fldname = mFieldNameList.at( j );
398 QAction *act = mFieldsMenu->addAction( mFieldDisplayNameList.at( j ) );
399 act->setIcon( mFieldIcons.at( j ) );
400 act->setData( QVariant( fldname ) );
401 if ( mFieldName == fldname )
402 {
403 act->setCheckable( true );
404 act->setChecked( mProperty.propertyType() == Qgis::PropertyType::Field );
405 fieldActive = mProperty.propertyType() == Qgis::PropertyType::Field;
406 }
407 }
408 }
409 else
410 {
411 QAction *act = mFieldsMenu->addAction( tr( "No matching field types found" ) );
412 act->setEnabled( false );
413 }
414
415 mDefineMenu->addSeparator();
416 }
417
418 mFieldsMenu->menuAction()->setCheckable( true );
419 mFieldsMenu->menuAction()->setChecked( fieldActive && mProperty.propertyType() == Qgis::PropertyType::Field && !mProperty.transformer() );
420
421 bool colorActive = false;
422 mColorsMenu->clear();
423 if ( mDefinition.standardTemplate() == QgsPropertyDefinition::ColorWithAlpha
424 || mDefinition.standardTemplate() == QgsPropertyDefinition::ColorNoAlpha )
425 {
426 // project colors menu
427 QAction *colorTitleAct = mDefineMenu->addAction( tr( "Project Color" ) );
428 colorTitleAct->setFont( titlefont );
429 colorTitleAct->setEnabled( false );
430
431 QList<QgsProjectColorScheme *> projectSchemes;
432 QgsApplication::colorSchemeRegistry()->schemes( projectSchemes );
433 if ( projectSchemes.length() > 0 )
434 {
435 QgsProjectColorScheme *scheme = projectSchemes.at( 0 );
436 const QgsNamedColorList colors = scheme->fetchColors();
437 for ( const auto &color : colors )
438 {
439 if ( color.second.isEmpty() )
440 continue;
441
442 QPixmap icon = QgsColorButton::createMenuIcon( color.first, mDefinition.standardTemplate() == QgsPropertyDefinition::ColorWithAlpha );
443 QAction *act = mColorsMenu->addAction( color.second );
444 act->setIcon( icon );
445 if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp && getColor() == color.second )
446 {
447 act->setCheckable( true );
448 act->setChecked( true );
449 colorActive = true;
450 }
451 }
452 }
453
454 if ( mColorsMenu->actions().isEmpty() )
455 {
456 QAction *act = mColorsMenu->addAction( tr( "No colors set" ) );
457 act->setEnabled( false );
458 }
459
460 mDefineMenu->addAction( mActionColors );
461 mColorsMenu->menuAction()->setCheckable( true );
462 mColorsMenu->menuAction()->setChecked( colorActive && !mProperty.transformer() );
463
464 mDefineMenu->addSeparator();
465 }
466
467 QAction *exprTitleAct = mDefineMenu->addAction( tr( "Expression" ) );
468 exprTitleAct->setFont( titlefont );
469 exprTitleAct->setEnabled( false );
470
471 mVariablesMenu->clear();
472 bool variableActive = false;
473 if ( mExpressionContextGenerator )
474 {
475 QgsExpressionContext context = mExpressionContextGenerator->createExpressionContext();
476 QStringList variables = context.variableNames();
477 variables.sort();
478 const auto constVariables = variables;
479 for ( const QString &variable : constVariables )
480 {
481 if ( context.isReadOnly( variable ) ) //only want to show user-set variables
482 continue;
483 if ( variable.startsWith( '_' ) ) //no hidden variables
484 continue;
485
486 QAction *act = mVariablesMenu->addAction( variable );
487 act->setData( QVariant( variable ) );
488
489 if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp && mExpressionString == '@' + variable )
490 {
491 act->setCheckable( true );
492 act->setChecked( true );
493 variableActive = true;
494 }
495 }
496 }
497
498 if ( mVariablesMenu->actions().isEmpty() )
499 {
500 QAction *act = mVariablesMenu->addAction( tr( "No variables set" ) );
501 act->setEnabled( false );
502 }
503
504 mDefineMenu->addAction( mActionVariables );
505 mVariablesMenu->menuAction()->setCheckable( true );
506 mVariablesMenu->menuAction()->setChecked( variableActive && !mProperty.transformer() );
507
508 if ( hasExp )
509 {
510 QString expString = mExpressionString;
511 if ( expString.length() > 35 )
512 {
513 expString.truncate( 35 );
514 expString.append( QChar( 0x2026 ) );
515 }
516
517 expString.prepend( tr( "Current: " ) );
518
519 if ( !mActionExpression )
520 {
521 mActionExpression = new QAction( expString, this );
522 mActionExpression->setCheckable( true );
523 }
524 else
525 {
526 mActionExpression->setText( expString );
527 }
528 mDefineMenu->addAction( mActionExpression );
529 mActionExpression->setChecked( mProperty.propertyType() == Qgis::PropertyType::Expression && !variableActive && !colorActive && !mProperty.transformer() );
530
531 mDefineMenu->addAction( mActionExpDialog );
532 mDefineMenu->addAction( mActionCopyExpr );
533 mDefineMenu->addAction( mActionPasteExpr );
534 }
535 else
536 {
537 mDefineMenu->addAction( mActionExpDialog );
538 mDefineMenu->addAction( mActionPasteExpr );
539 }
540
541 if ( hasExp || !mFieldName.isEmpty() )
542 {
543 mDefineMenu->addSeparator();
544 mDefineMenu->addAction( mActionClearExpr );
545 }
546
547 if ( !mDefinition.name().isEmpty() && mDefinition.supportsAssistant() )
548 {
549 mDefineMenu->addSeparator();
550 mActionAssistant->setCheckable( mProperty.transformer() );
551 mActionAssistant->setChecked( mProperty.transformer() );
552 mDefineMenu->addAction( mActionAssistant );
553 }
554}
555
556void QgsPropertyOverrideButton::menuActionTriggered( QAction *action )
557{
558 if ( action == mActionActive )
559 {
560 setActivePrivate( mActionActive->data().toBool() );
561 updateGui();
562 emit changed();
563 }
564 else if ( action == mActionDescription )
565 {
566 showDescriptionDialog();
567 }
568 else if ( action == mActionExpDialog )
569 {
570 showExpressionDialog();
571 }
572 else if ( action == mActionExpression )
573 {
574 mProperty.setExpressionString( mExpressionString );
575 mProperty.setTransformer( nullptr );
576 setActivePrivate( true );
577 updateSiblingWidgets( isActive() );
578 updateGui();
579 emit changed();
580 }
581 else if ( action == mActionCopyExpr )
582 {
583 QApplication::clipboard()->setText( mExpressionString );
584 }
585 else if ( action == mActionPasteExpr )
586 {
587 QString exprString = QApplication::clipboard()->text();
588 if ( !exprString.isEmpty() )
589 {
590 mExpressionString = exprString;
591 mProperty.setExpressionString( mExpressionString );
592 mProperty.setTransformer( nullptr );
593 setActivePrivate( true );
594 updateSiblingWidgets( isActive() );
595 updateGui();
596 emit changed();
597 }
598 }
599 else if ( action == mActionClearExpr )
600 {
601 setActivePrivate( false );
602 mProperty.setStaticValue( QVariant() );
603 mProperty.setTransformer( nullptr );
604 mExpressionString.clear();
605 mFieldName.clear();
606 updateSiblingWidgets( isActive() );
607 updateGui();
608 emit changed();
609 }
610 else if ( action == mActionAssistant )
611 {
612 showAssistant();
613 }
614 else if ( action == mActionCreateAuxiliaryField )
615 {
617 }
618 else if ( mFieldsMenu->actions().contains( action ) ) // a field name clicked
619 {
620 if ( action->isEnabled() )
621 {
622 if ( mFieldName != action->text() )
623 {
624 mFieldName = action->data().toString();
625 }
626 mProperty.setField( mFieldName );
627 mProperty.setTransformer( nullptr );
628 setActivePrivate( true );
629 updateSiblingWidgets( isActive() );
630 updateGui();
631 emit changed();
632 }
633 }
634 else if ( mVariablesMenu->actions().contains( action ) ) // a variable name clicked
635 {
636 if ( mExpressionString != action->text().prepend( "@" ) )
637 {
638 mExpressionString = action->data().toString().prepend( "@" );
639 }
640 mProperty.setExpressionString( mExpressionString );
641 mProperty.setTransformer( nullptr );
642 setActivePrivate( true );
643 updateSiblingWidgets( isActive() );
644 updateGui();
645 emit changed();
646 }
647 else if ( mColorsMenu->actions().contains( action ) ) // a color name clicked
648 {
649 if ( getColor() != action->text() )
650 {
651 mExpressionString = u"project_color_object('%1')"_s.arg( action->text() );
652 }
653 mProperty.setExpressionString( mExpressionString );
654 mProperty.setTransformer( nullptr );
655 setActivePrivate( true );
656 updateSiblingWidgets( isActive() );
657 updateGui();
658 emit changed();
659 }
660}
662
663void QgsPropertyOverrideButton::showDescriptionDialog()
664{
665 QgsMessageViewer *mv = new QgsMessageViewer( this );
666 mv->setWindowTitle( tr( "Data Definition Description" ) );
667 mv->setMessageAsHtml( mFullDescription );
668 mv->exec();
669}
670
671
672void QgsPropertyOverrideButton::showExpressionDialog()
673{
674 QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : QgsExpressionContext();
675
676 // build sensible initial expression text - see https://github.com/qgis/QGIS/issues/26526
677 QString currentExpression = ( mProperty.propertyType() == Qgis::PropertyType::Static && !mProperty.staticValue().isValid() ) ? QString()
678 : mProperty.asExpression();
679
680 QgsExpressionBuilderDialog d( const_cast<QgsVectorLayer *>( mVectorLayer ), currentExpression, this, u"generic"_s, context );
681 d.setExpectedOutputFormat( mInputDescription );
682 if ( d.exec() == QDialog::Accepted )
683 {
684 mExpressionString = d.expressionText().trimmed();
685 bool active = mProperty.isActive();
686 mProperty.setExpressionString( mExpressionString );
687 mProperty.setTransformer( nullptr );
688 mProperty.setActive( !mExpressionString.isEmpty() );
689 if ( mProperty.isActive() != active )
690 emit activated( mProperty.isActive() );
691 updateSiblingWidgets( isActive() );
692 updateGui();
693 emit changed();
694 }
695 activateWindow(); // reset focus to parent window
696}
697
698void QgsPropertyOverrideButton::showAssistant()
699{
700 //first step - try to convert any existing expression to a transformer if one doesn't
701 //already exist
702 if ( !mProperty.transformer() )
703 {
704 ( void ) mProperty.convertToTransformer();
705 }
706
707 QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this );
708 QgsPropertyAssistantWidget *widget = new QgsPropertyAssistantWidget( panel, mDefinition, mProperty, mVectorLayer );
709 widget->registerExpressionContextGenerator( mExpressionContextGenerator );
710 widget->setSymbol( mSymbol ); // we only show legend preview in dialog version
711
712 if ( panel && panel->dockMode() )
713 {
714 connect( widget, &QgsPropertyAssistantWidget::widgetChanged, this, [this, widget] {
715 widget->updateProperty( this->mProperty );
716 mExpressionString = this->mProperty.asExpression();
717 mFieldName = this->mProperty.field();
718 updateSiblingWidgets( isActive() );
719 this->emit changed();
720 } );
721
722 connect( widget, &QgsPropertyAssistantWidget::panelAccepted, this, [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 = u"/UI/paneldialog/%1"_s.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( u"/mIconDataDefine.svg"_s );
764 QString deftip = tr( "undefined" );
765 QString deftype;
766 if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp )
767 {
768 icon = mProperty.isActive() ? QgsApplication::getThemeIcon( u"/mIconDataDefineExpressionOn.svg"_s ) : QgsApplication::getThemeIcon( u"/mIconDataDefineExpression.svg"_s );
769
770 const QString colorName = getColor();
771 if ( !colorName.isEmpty() )
772 {
773 icon = mProperty.isActive() ? QgsApplication::getThemeIcon( u"/mIconDataDefineColorOn.svg"_s ) : QgsApplication::getThemeIcon( u"/mIconDataDefineColor.svg"_s );
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( u"/mIconDataDefineExpressionError.svg"_s );
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( u"/mIconDataDefineOn.svg"_s ) : QgsApplication::getThemeIcon( u"/mIconDataDefine.svg"_s );
794
795 if ( !mFieldNameList.contains( mFieldName ) && !mProperty.transformer() )
796 {
797 icon = QgsApplication::getThemeIcon( u"/mIconDataDefineError.svg"_s );
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.clear();
810 if ( !mDefinition.description().isEmpty() )
811 {
812 mFullDescription += u"<b><u>%1</b></u>"_s.arg( mDefinition.description() );
813 }
814 if ( !mDefinition.comment().isEmpty() )
815 {
816 mFullDescription += u"<p><i>%1</i></p>"_s.arg( mDefinition.comment() );
817 }
818 else if ( !mFullDescription.isEmpty() )
819 {
820 mFullDescription += "<br>"_L1;
821 }
822
823 mFullDescription += tr( "<b>Data defined override</b><br>" );
824
825 mFullDescription += tr( "<b>Active: </b>%1&nbsp;&nbsp;&nbsp;<i>(ctrl|right-click toggles)</i><br>" ).arg( mProperty.isActive() ? tr( "yes" ) : tr( "no" ) );
826
827 if ( !mUsageInfo.isEmpty() )
828 {
829 mFullDescription += tr( "<b>Usage:</b><br>%1<br>" ).arg( mUsageInfo );
830 }
831
832 if ( !mInputDescription.isEmpty() )
833 {
834 mFullDescription += tr( "<b>Expected input:</b><br>%1<br>" ).arg( mInputDescription );
835 }
836
837 if ( !mDataTypesString.isEmpty() )
838 {
839 mFullDescription += tr( "<b>Valid input types:</b><br>%1<br>" ).arg( mDataTypesString );
840 }
841
842 if ( deftype.isEmpty() && deftip != tr( "undefined" ) )
843 {
844 deftype = mProperty.propertyType() == Qgis::PropertyType::Expression ? tr( "expression" ) : tr( "field" );
845 }
846
847 // truncate long expressions, or tool tip may be too wide for screen
848 if ( deftip.length() > 75 )
849 {
850 deftip.truncate( 75 );
851 deftip.append( QChar( 0x2026 ) );
852 }
853
854 mFullDescription += tr( "<b>Current definition (%1):</b><br>%2" ).arg( deftype, deftip );
855
856 setToolTip( mFullDescription );
857}
858
859void QgsPropertyOverrideButton::setActivePrivate( bool active )
860{
861 if ( mProperty.isActive() != active )
862 {
863 mProperty.setActive( active );
864 emit activated( mProperty.isActive() );
865 }
866}
867
868void QgsPropertyOverrideButton::updateSiblingWidgets( bool state )
869{
870 const auto constMSiblingWidgets = mSiblingWidgets;
871 for ( const SiblingWidget &sw : constMSiblingWidgets )
872 {
873 switch ( sw.mSiblingType )
874 {
875 case SiblingCheckState:
876 {
877 // don't uncheck, only set to checked
878 if ( state )
879 {
880 QAbstractButton *btn = qobject_cast<QAbstractButton *>( sw.mWidgetPointer.data() );
881 if ( btn && btn->isCheckable() )
882 {
883 btn->setChecked( sw.mNatural ? state : !state );
884 }
885 else
886 {
887 QGroupBox *grpbx = qobject_cast<QGroupBox *>( sw.mWidgetPointer.data() );
888 if ( grpbx && grpbx->isCheckable() )
889 {
890 grpbx->setChecked( sw.mNatural ? state : !state );
891 }
892 }
893 }
894 break;
895 }
896
897 case SiblingEnableState:
898 {
899 QLineEdit *le = qobject_cast<QLineEdit *>( sw.mWidgetPointer.data() );
900 if ( le )
901 le->setReadOnly( sw.mNatural ? !state : state );
902 else
903 sw.mWidgetPointer.data()->setEnabled( sw.mNatural ? state : !state );
904 break;
905 }
906
907 case SiblingVisibility:
908 {
909 sw.mWidgetPointer.data()->setVisible( sw.mNatural ? state : !state );
910 break;
911 }
912
913 case SiblingExpressionText:
914 {
915 QLineEdit *le = qobject_cast<QLineEdit *>( sw.mWidgetPointer.data() );
916 if ( le )
917 {
918 le->setText( mProperty.asExpression() );
919 }
920 else
921 {
922 QTextEdit *te = qobject_cast<QTextEdit *>( sw.mWidgetPointer.data() );
923 if ( te )
924 {
925 te->setText( mProperty.asExpression() );
926 }
927 }
928 break;
929 }
930
931 case SiblingLinkedWidget:
932 {
933 if ( QgsColorButton *cb = qobject_cast<QgsColorButton *>( sw.mWidgetPointer.data() ) )
934 {
935 if ( state && mProperty.isProjectColor() )
936 {
937 const QString colorName = getColor();
938 if ( !colorName.isEmpty() )
939 {
940 cb->linkToProjectColor( colorName );
941 }
942 }
943 else
944 {
945 cb->linkToProjectColor( QString() );
946 }
947 }
948 break;
949 }
950 }
951 }
952}
953
954
956{
957 if ( mProperty.isActive() != active )
958 {
959 mProperty.setActive( active );
960 updateGui();
961 emit changed();
962 emit activated( mProperty.isActive() );
963 }
964}
965
967{
968 mExpressionContextGenerator = generator;
969}
970
972{
973 for ( const SiblingWidget &sw : std::as_const( mSiblingWidgets ) )
974 {
975 if ( widget == sw.mWidgetPointer.data() && sw.mSiblingType == SiblingLinkedWidget )
976 return;
977 }
978 mSiblingWidgets.append( SiblingWidget( QPointer<QWidget>( widget ), SiblingLinkedWidget ) );
979
980 if ( QgsColorButton *cb = qobject_cast<QgsColorButton *>( widget ) )
981 {
982 connect( cb, &QgsColorButton::unlinked, this, [this] {
983 setActive( false );
984 updateGui();
985 } );
986 }
987
988 updateSiblingWidgets( isActive() );
989}
990
991void QgsPropertyOverrideButton::showHelp()
992{
993 QgsHelp::openHelp( u"introduction/general_tools.html#data-defined"_s );
994}
995
996QString QgsPropertyOverrideButton::getColor() const
997{
998 const thread_local QRegularExpression rx( u"^project_color(_object|)\\('(.*)'\\)$"_s );
999 QRegularExpressionMatch match = rx.match( mExpressionString );
1000 return match.hasMatch() ? match.captured( 2 ) : QString();
1001}
@ Invalid
Invalid (not set) property.
Definition qgis.h:702
@ Field
Field based property.
Definition qgis.h:704
@ Static
Static property.
Definition qgis.h:703
@ Expression
Expression based property.
Definition qgis.h:705
Abstract base class for QgsPropertyCollection like objects.
virtual QgsProperty property(int key) const =0
Returns a matching property from the collection, if one exists.
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.
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.
Abstract interface for generating an expression context.
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.
Handles parsing and evaluation of expressions (formerly called "search strings").
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
Container of fields for a vector layer.
Definition qgsfields.h:46
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:41
void setMessageAsHtml(const QString &msg)
QString panelTitle() const
The title of the 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 ...
bool dockMode() const
Returns the dock mode state.
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.
QgsNamedColorList fetchColors(const QString &context=QString(), const QColor &baseColor=QColor()) override
Gets a list of colors from the scheme.
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:47
DataType dataType() const
Returns the allowable field/value data type for the property.
@ ColorNoAlpha
Color with no alpha channel.
Definition qgsproperty.h:65
@ ColorWithAlpha
Color with alpha channel.
Definition qgsproperty.h:64
@ DataTypeString
Property requires a string value.
Definition qgsproperty.h:92
@ DataTypeBoolean
Property requires a boolean value.
@ DataTypeNumeric
Property requires a numeric value.
Definition qgsproperty.h:99
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.
Qgis::PropertyType propertyType() const
Returns the property type.
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...
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 dataset.
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.