QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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 ) ) || event->button() == Qt::RightButton )
267 {
268 setActivePrivate( !mProperty.isActive() );
269 updateGui();
270 emit changed();
271 event->ignore();
272 return;
273 }
274
275 // Middle button click to open the Expression Builder dialog
276 if ( event->button() == Qt::MiddleButton )
277 {
278 showExpressionDialog();
279 return;
280 }
281
282 // pass to default behavior
283 QToolButton::mousePressEvent( event );
284}
285
287{
288 if ( property )
289 {
290 switch ( property.propertyType() )
291 {
294 break;
296 {
297 mFieldName = property.field();
298 break;
299 }
301 {
302 mExpressionString = property.expressionString();
303 break;
304 }
305 }
306 }
307 else
308 {
309 mFieldName.clear();
310 mExpressionString.clear();
311 }
312 mProperty = property;
313 setActive( mProperty && mProperty.isActive() );
314 updateSiblingWidgets( isActive() );
315 updateGui();
316}
317
319void QgsPropertyOverrideButton::aboutToShowMenu()
320{
321 mDefineMenu->clear();
322 // update fields so that changes made to layer's fields are reflected
324
325 bool hasExp = !mExpressionString.isEmpty();
326 QString ddTitle = tr( "Data defined override" );
327
328 QAction *ddTitleAct = mDefineMenu->addAction( ddTitle );
329 QFont titlefont = ddTitleAct->font();
330 titlefont.setItalic( true );
331 ddTitleAct->setFont( titlefont );
332 ddTitleAct->setEnabled( false );
333
334 bool addActiveAction = false;
335 if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp )
336 {
337 QgsExpression exp( mExpressionString );
338 // whether expression is parse-able
339 addActiveAction = !exp.hasParserError();
340 }
341 else if ( mProperty.propertyType() == Qgis::PropertyType::Field )
342 {
343 // whether field exists
344 addActiveAction = mFieldNameList.contains( mFieldName );
345 }
346
347 if ( addActiveAction )
348 {
349 ddTitleAct->setText( ddTitle + " (" + ( mProperty.propertyType() == Qgis::PropertyType::Expression ? tr( "expression" ) : tr( "field" ) ) + ')' );
350 mDefineMenu->addAction( mActionActive );
351 mActionActive->setText( mProperty.isActive() ? tr( "Deactivate" ) : tr( "Activate" ) );
352 mActionActive->setData( QVariant( !mProperty.isActive() ) );
353 }
354
355 if ( !mFullDescription.isEmpty() )
356 {
357 mDefineMenu->addAction( mActionDescription );
358 }
359
360 mDefineMenu->addSeparator();
361
362 // deactivate button if field already exists
363 if ( mAuxiliaryStorageEnabled && mVectorLayer )
364 {
365 mDefineMenu->addAction( mActionCreateAuxiliaryField );
366
367 const QgsAuxiliaryLayer *alayer = mVectorLayer->auxiliaryLayer();
368
369 mActionCreateAuxiliaryField->setEnabled( true );
370 mActionCreateAuxiliaryField->setChecked( false );
371
372 int index = mVectorLayer->fields().indexFromName( mFieldName );
373 int srcIndex;
374 if ( index >= 0 && alayer && mVectorLayer->isAuxiliaryField( index, srcIndex ) )
375 {
376 mActionCreateAuxiliaryField->setEnabled( false );
377 mActionCreateAuxiliaryField->setChecked( true );
378 }
379 }
380
381 bool fieldActive = false;
382 if ( !mDataTypesString.isEmpty() )
383 {
384 QAction *fieldTitleAct = mDefineMenu->addAction( tr( "Attribute Field" ) );
385 fieldTitleAct->setFont( titlefont );
386 fieldTitleAct->setEnabled( false );
387
388 mDefineMenu->addAction( mActionDataTypes );
389
390 mFieldsMenu->clear();
391
392 if ( !mFieldNameList.isEmpty() )
393 {
394 for ( int j = 0; j < mFieldNameList.count(); ++j )
395 {
396 QString fldname = mFieldNameList.at( j );
397 QAction *act = mFieldsMenu->addAction( mFieldDisplayNameList.at( j ) );
398 act->setIcon( mFieldIcons.at( j ) );
399 act->setData( QVariant( fldname ) );
400 if ( mFieldName == fldname )
401 {
402 act->setCheckable( true );
403 act->setChecked( mProperty.propertyType() == Qgis::PropertyType::Field );
404 fieldActive = mProperty.propertyType() == Qgis::PropertyType::Field;
405 }
406 }
407 }
408 else
409 {
410 QAction *act = mFieldsMenu->addAction( tr( "No matching field types found" ) );
411 act->setEnabled( false );
412 }
413
414 mDefineMenu->addSeparator();
415 }
416
417 mFieldsMenu->menuAction()->setCheckable( true );
418 mFieldsMenu->menuAction()->setChecked( fieldActive && mProperty.propertyType() == Qgis::PropertyType::Field && !mProperty.transformer() );
419
420 bool colorActive = false;
421 mColorsMenu->clear();
422 if ( mDefinition.standardTemplate() == QgsPropertyDefinition::ColorWithAlpha || mDefinition.standardTemplate() == QgsPropertyDefinition::ColorNoAlpha )
423 {
424 // project colors menu
425 QAction *colorTitleAct = mDefineMenu->addAction( tr( "Project Color" ) );
426 colorTitleAct->setFont( titlefont );
427 colorTitleAct->setEnabled( false );
428
429 QList<QgsProjectColorScheme *> projectSchemes;
430 QgsApplication::colorSchemeRegistry()->schemes( projectSchemes );
431 if ( projectSchemes.length() > 0 )
432 {
433 QgsProjectColorScheme *scheme = projectSchemes.at( 0 );
434 const QgsNamedColorList colors = scheme->fetchColors();
435 for ( const auto &color : colors )
436 {
437 if ( color.second.isEmpty() )
438 continue;
439
440 QPixmap icon = QgsColorButton::createMenuIcon( color.first, mDefinition.standardTemplate() == QgsPropertyDefinition::ColorWithAlpha );
441 QAction *act = mColorsMenu->addAction( color.second );
442 act->setIcon( icon );
443 if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp && getColor() == color.second )
444 {
445 act->setCheckable( true );
446 act->setChecked( true );
447 colorActive = true;
448 }
449 }
450 }
451
452 if ( mColorsMenu->actions().isEmpty() )
453 {
454 QAction *act = mColorsMenu->addAction( tr( "No colors set" ) );
455 act->setEnabled( false );
456 }
457
458 mDefineMenu->addAction( mActionColors );
459 mColorsMenu->menuAction()->setCheckable( true );
460 mColorsMenu->menuAction()->setChecked( colorActive && !mProperty.transformer() );
461
462 mDefineMenu->addSeparator();
463 }
464
465 QAction *exprTitleAct = mDefineMenu->addAction( tr( "Expression" ) );
466 exprTitleAct->setFont( titlefont );
467 exprTitleAct->setEnabled( false );
468
469 mVariablesMenu->clear();
470 bool variableActive = false;
471 if ( mExpressionContextGenerator )
472 {
473 QgsExpressionContext context = mExpressionContextGenerator->createExpressionContext();
474 QStringList variables = context.variableNames();
475 variables.sort();
476 const auto constVariables = variables;
477 for ( const QString &variable : constVariables )
478 {
479 if ( context.isReadOnly( variable ) ) //only want to show user-set variables
480 continue;
481 if ( variable.startsWith( '_' ) ) //no hidden variables
482 continue;
483
484 QAction *act = mVariablesMenu->addAction( variable );
485 act->setData( QVariant( variable ) );
486
487 if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp && mExpressionString == '@' + variable )
488 {
489 act->setCheckable( true );
490 act->setChecked( true );
491 variableActive = true;
492 }
493 }
494 }
495
496 if ( mVariablesMenu->actions().isEmpty() )
497 {
498 QAction *act = mVariablesMenu->addAction( tr( "No variables set" ) );
499 act->setEnabled( false );
500 }
501
502 mDefineMenu->addAction( mActionVariables );
503 mVariablesMenu->menuAction()->setCheckable( true );
504 mVariablesMenu->menuAction()->setChecked( variableActive && !mProperty.transformer() );
505
506 if ( hasExp )
507 {
508 QString expString = mExpressionString;
509 if ( expString.length() > 35 )
510 {
511 expString.truncate( 35 );
512 expString.append( QChar( 0x2026 ) );
513 }
514
515 expString.prepend( tr( "Current: " ) );
516
517 if ( !mActionExpression )
518 {
519 mActionExpression = new QAction( expString, this );
520 mActionExpression->setCheckable( true );
521 }
522 else
523 {
524 mActionExpression->setText( expString );
525 }
526 mDefineMenu->addAction( mActionExpression );
527 mActionExpression->setChecked( mProperty.propertyType() == Qgis::PropertyType::Expression && !variableActive && !colorActive && !mProperty.transformer() );
528
529 mDefineMenu->addAction( mActionExpDialog );
530 mDefineMenu->addAction( mActionCopyExpr );
531 mDefineMenu->addAction( mActionPasteExpr );
532 }
533 else
534 {
535 mDefineMenu->addAction( mActionExpDialog );
536 mDefineMenu->addAction( mActionPasteExpr );
537 }
538
539 if ( hasExp || !mFieldName.isEmpty() )
540 {
541 mDefineMenu->addSeparator();
542 mDefineMenu->addAction( mActionClearExpr );
543 }
544
545 if ( !mDefinition.name().isEmpty() && mDefinition.supportsAssistant() )
546 {
547 mDefineMenu->addSeparator();
548 mActionAssistant->setCheckable( mProperty.transformer() );
549 mActionAssistant->setChecked( mProperty.transformer() );
550 mDefineMenu->addAction( mActionAssistant );
551 }
552}
553
554void QgsPropertyOverrideButton::menuActionTriggered( QAction *action )
555{
556 if ( action == mActionActive )
557 {
558 setActivePrivate( mActionActive->data().toBool() );
559 updateGui();
560 emit changed();
561 }
562 else if ( action == mActionDescription )
563 {
564 showDescriptionDialog();
565 }
566 else if ( action == mActionExpDialog )
567 {
568 showExpressionDialog();
569 }
570 else if ( action == mActionExpression )
571 {
572 mProperty.setExpressionString( mExpressionString );
573 mProperty.setTransformer( nullptr );
574 setActivePrivate( true );
575 updateSiblingWidgets( isActive() );
576 updateGui();
577 emit changed();
578 }
579 else if ( action == mActionCopyExpr )
580 {
581 QApplication::clipboard()->setText( mExpressionString );
582 }
583 else if ( action == mActionPasteExpr )
584 {
585 QString exprString = QApplication::clipboard()->text();
586 if ( !exprString.isEmpty() )
587 {
588 mExpressionString = exprString;
589 mProperty.setExpressionString( mExpressionString );
590 mProperty.setTransformer( nullptr );
591 setActivePrivate( true );
592 updateSiblingWidgets( isActive() );
593 updateGui();
594 emit changed();
595 }
596 }
597 else if ( action == mActionClearExpr )
598 {
599 setActivePrivate( false );
600 mProperty.setStaticValue( QVariant() );
601 mProperty.setTransformer( nullptr );
602 mExpressionString.clear();
603 mFieldName.clear();
604 updateSiblingWidgets( isActive() );
605 updateGui();
606 emit changed();
607 }
608 else if ( action == mActionAssistant )
609 {
610 showAssistant();
611 }
612 else if ( action == mActionCreateAuxiliaryField )
613 {
615 }
616 else if ( mFieldsMenu->actions().contains( action ) ) // a field name clicked
617 {
618 if ( action->isEnabled() )
619 {
620 if ( mFieldName != action->text() )
621 {
622 mFieldName = action->data().toString();
623 }
624 mProperty.setField( mFieldName );
625 mProperty.setTransformer( nullptr );
626 setActivePrivate( true );
627 updateSiblingWidgets( isActive() );
628 updateGui();
629 emit changed();
630 }
631 }
632 else if ( mVariablesMenu->actions().contains( action ) ) // a variable name clicked
633 {
634 if ( mExpressionString != action->text().prepend( "@" ) )
635 {
636 mExpressionString = action->data().toString().prepend( "@" );
637 }
638 mProperty.setExpressionString( mExpressionString );
639 mProperty.setTransformer( nullptr );
640 setActivePrivate( true );
641 updateSiblingWidgets( isActive() );
642 updateGui();
643 emit changed();
644 }
645 else if ( mColorsMenu->actions().contains( action ) ) // a color name clicked
646 {
647 if ( getColor() != action->text() )
648 {
649 mExpressionString = u"project_color_object('%1')"_s.arg( action->text() );
650 }
651 mProperty.setExpressionString( mExpressionString );
652 mProperty.setTransformer( nullptr );
653 setActivePrivate( true );
654 updateSiblingWidgets( isActive() );
655 updateGui();
656 emit changed();
657 }
658}
660
661void QgsPropertyOverrideButton::showDescriptionDialog()
662{
663 QgsMessageViewer *mv = new QgsMessageViewer( this );
664 mv->setWindowTitle( tr( "Data Definition Description" ) );
665 mv->setMessageAsHtml( mFullDescription );
666 mv->exec();
667}
668
669
670void QgsPropertyOverrideButton::showExpressionDialog()
671{
672 QgsExpressionContext context = mExpressionContextGenerator ? mExpressionContextGenerator->createExpressionContext() : QgsExpressionContext();
673
674 // build sensible initial expression text - see https://github.com/qgis/QGIS/issues/26526
675 QString currentExpression = ( mProperty.propertyType() == Qgis::PropertyType::Static && !mProperty.staticValue().isValid() ) ? QString() : mProperty.asExpression();
676
677 QgsExpressionBuilderDialog d( const_cast<QgsVectorLayer *>( mVectorLayer ), currentExpression, this, u"generic"_s, 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 = u"/UI/paneldialog/%1"_s.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( u"/mIconDataDefine.svg"_s );
761 QString deftip = tr( "undefined" );
762 QString deftype;
763 if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp )
764 {
765 icon = mProperty.isActive() ? QgsApplication::getThemeIcon( u"/mIconDataDefineExpressionOn.svg"_s ) : QgsApplication::getThemeIcon( u"/mIconDataDefineExpression.svg"_s );
766
767 const QString colorName = getColor();
768 if ( !colorName.isEmpty() )
769 {
770 icon = mProperty.isActive() ? QgsApplication::getThemeIcon( u"/mIconDataDefineColorOn.svg"_s ) : QgsApplication::getThemeIcon( u"/mIconDataDefineColor.svg"_s );
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( u"/mIconDataDefineExpressionError.svg"_s );
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( u"/mIconDataDefineOn.svg"_s ) : QgsApplication::getThemeIcon( u"/mIconDataDefine.svg"_s );
791
792 if ( !mFieldNameList.contains( mFieldName ) && !mProperty.transformer() )
793 {
794 icon = QgsApplication::getThemeIcon( u"/mIconDataDefineError.svg"_s );
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 += u"<b><u>%1</b></u>"_s.arg( mDefinition.description() );
810 }
811 if ( !mDefinition.comment().isEmpty() )
812 {
813 mFullDescription += u"<p><i>%1</i></p>"_s.arg( mDefinition.comment() );
814 }
815 else if ( !mFullDescription.isEmpty() )
816 {
817 mFullDescription += "<br>"_L1;
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( u"introduction/general_tools.html#data-defined"_s );
991}
992
993QString QgsPropertyOverrideButton::getColor() const
994{
995 const thread_local QRegularExpression rx( u"^project_color(_object|)\\('(.*)'\\)$"_s );
996 QRegularExpressionMatch match = rx.match( mExpressionString );
997 return match.hasMatch() ? match.captured( 2 ) : QString();
998}
@ Invalid
Invalid (not set) property.
Definition qgis.h:709
@ Field
Field based property.
Definition qgis.h:711
@ Static
Static property.
Definition qgis.h:710
@ Expression
Expression based property.
Definition qgis.h:712
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:64
@ ColorWithAlpha
Color with alpha channel.
Definition qgsproperty.h:63
@ 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.
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.