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