diff --git a/python/PyQt6/core/auto_generated/qgsdiagramrenderer.sip.in b/python/PyQt6/core/auto_generated/qgsdiagramrenderer.sip.in index 2998f60d175a..8ef0bdf1c523 100644 --- a/python/PyQt6/core/auto_generated/qgsdiagramrenderer.sip.in +++ b/python/PyQt6/core/auto_generated/qgsdiagramrenderer.sip.in @@ -1017,30 +1017,24 @@ Writes stacked renderers state to a DOM element. virtual QList< QgsLayerTreeModelLegendNode * > legendItems( QgsLayerTreeLayer *nodeLayer ) const /Factory/; - QList< QgsDiagramRenderer * > renderers() const; + QList< QgsDiagramRenderer * > renderers( bool sortByDiagramMode = false ) const; %Docstring Returns an ordered list with the renderers of the stacked renderer object. -If the stacked diagram orientation is vertical, the list is returned backwards. +@param sortByDiagramMode If true, the list is returned backwards for vertical orientation. %End void addRenderer( QgsDiagramRenderer *renderer ); %Docstring Adds a renderer to the stacked renderer object. - -:param renderer: diagram renderer to be added to the stacked renderer - Renderers added first will render their diagrams first, i.e., more to - the left (horizontal mode) or more to the top (vertical mode). +@param renderer diagram renderer to be added to the stacked renderer +Renderers added first will render their diagrams first, i.e., more to +the left (horizontal mode) or more to the top (vertical mode). %End const QgsDiagramRenderer *renderer( const int index ) const; %Docstring Returns the renderer at the given ``index``. @param index index of the disired renderer in the stacked renderer -%End - - int rendererCount() const; -%Docstring -Returns the number of renderers that this stacked renderer is composed of. %End protected: diff --git a/python/core/auto_generated/qgsdiagramrenderer.sip.in b/python/core/auto_generated/qgsdiagramrenderer.sip.in index d54d9d6cf6d5..6f3ec3ebd753 100644 --- a/python/core/auto_generated/qgsdiagramrenderer.sip.in +++ b/python/core/auto_generated/qgsdiagramrenderer.sip.in @@ -1017,30 +1017,24 @@ Writes stacked renderers state to a DOM element. virtual QList< QgsLayerTreeModelLegendNode * > legendItems( QgsLayerTreeLayer *nodeLayer ) const /Factory/; - QList< QgsDiagramRenderer * > renderers() const; + QList< QgsDiagramRenderer * > renderers( bool sortByDiagramMode = false ) const; %Docstring Returns an ordered list with the renderers of the stacked renderer object. -If the stacked diagram orientation is vertical, the list is returned backwards. +@param sortByDiagramMode If true, the list is returned backwards for vertical orientation. %End void addRenderer( QgsDiagramRenderer *renderer ); %Docstring Adds a renderer to the stacked renderer object. - -:param renderer: diagram renderer to be added to the stacked renderer - Renderers added first will render their diagrams first, i.e., more to - the left (horizontal mode) or more to the top (vertical mode). +@param renderer diagram renderer to be added to the stacked renderer +Renderers added first will render their diagrams first, i.e., more to +the left (horizontal mode) or more to the top (vertical mode). %End const QgsDiagramRenderer *renderer( const int index ) const; %Docstring Returns the renderer at the given ``index``. @param index index of the disired renderer in the stacked renderer -%End - - int rendererCount() const; -%Docstring -Returns the number of renderers that this stacked renderer is composed of. %End protected: diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 7aa546f9353d..b94a18b5b7b0 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -8049,35 +8049,8 @@ void QgisApp::diagramProperties() return; } - QDialog dlg; - dlg.setWindowTitle( tr( "Layer Diagram Properties" ) ); - QgsStackedDiagramProperties *gui = new QgsStackedDiagramProperties( vlayer, &dlg, mMapCanvas ); - gui->layout()->setContentsMargins( 0, 0, 0, 0 ); - QVBoxLayout *layout = new QVBoxLayout( &dlg ); - layout->addWidget( gui ); - - QDialogButtonBox *buttonBox = new QDialogButtonBox( - QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply, - Qt::Horizontal, &dlg ); - layout->addWidget( buttonBox ); - - dlg.setLayout( layout ); - - connect( buttonBox->button( QDialogButtonBox::Ok ), &QAbstractButton::clicked, - &dlg, &QDialog::accept ); - connect( buttonBox->button( QDialogButtonBox::Cancel ), &QAbstractButton::clicked, - &dlg, &QDialog::reject ); - connect( buttonBox->button( QDialogButtonBox::Apply ), &QAbstractButton::clicked, - gui, &QgsStackedDiagramProperties::apply ); - connect( buttonBox->button( QDialogButtonBox::Help ), &QAbstractButton::clicked, gui, [ = ] - { - QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#diagrams-properties" ) ); - } ); - - if ( dlg.exec() ) - gui->apply(); - - activateDeactivateLayerRelatedActions( vlayer ); + mapStyleDock( true ); + mMapStyleWidget->setCurrentPage( QgsLayerStylingWidget::VectorDiagram ); } void QgisApp::createAnnotationLayer() diff --git a/src/app/qgslayerstylingwidget.cpp b/src/app/qgslayerstylingwidget.cpp index ed18fd191873..c0fd515a574d 100644 --- a/src/app/qgslayerstylingwidget.cpp +++ b/src/app/qgslayerstylingwidget.cpp @@ -23,6 +23,7 @@ #include "qgsapplication.h" #include "qgslabelingwidget.h" #include "qgsmaskingwidget.h" +#include "qgsdiagramwidget.h" #include "qgslayerstylingwidget.h" #include "qgsrastertransparencywidget.h" #include "qgsrendererpropertiesdialog.h" @@ -217,6 +218,11 @@ void QgsLayerStylingWidget::setLayer( QgsMapLayer *layer ) symbol3DItem->setToolTip( tr( "3D View" ) ); mOptionsListWidget->addItem( symbol3DItem ); #endif + + QListWidgetItem *diagramItem = new QListWidgetItem( QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/diagram.svg" ) ), QString() ); + diagramItem->setData( Qt::UserRole, VectorDiagram ); + diagramItem->setToolTip( tr( "Diagrams" ) ); + mOptionsListWidget->addItem( diagramItem ); break; } case Qgis::LayerType::Raster: @@ -334,12 +340,6 @@ void QgsLayerStylingWidget::apply() bool styleWasChanged = false; bool triggerRepaint = false; // whether the change needs the layer to be repainted - if ( QgsLabelingWidget *widget = qobject_cast( current ) ) - { - widget->apply(); - styleWasChanged = true; - undoName = QStringLiteral( "Label Change" ); - } if ( QgsMaskingWidget *widget = qobject_cast( current ) ) { widget->apply(); @@ -373,8 +373,23 @@ void QgsLayerStylingWidget::apply() styleWasChanged = true; triggerRepaint = true; } + else if ( QgsLabelingWidget *widget = qobject_cast( current ) ) + { + widget->apply(); + styleWasChanged = true; + undoName = QStringLiteral( "Label Change" ); + } + else if ( QgsDiagramWidget *widget = qobject_cast( current ) ) + { + widget->apply(); + styleWasChanged = true; + undoName = QStringLiteral( "Diagram Change" ); + } else if ( QgsMapLayerConfigWidget *widget = qobject_cast( current ) ) { + // Warning: All classes inheriting from QgsMapLayerConfigWidget + // should come in the current if block, before this else-if + // clause, to avoid duplicate calls to apply()! widget->apply(); styleWasChanged = true; triggerRepaint = widget->shouldTriggerLayerRepaint(); @@ -463,6 +478,10 @@ void QgsLayerStylingWidget::updateCurrentWidgetLayer() mMesh3DWidget = widget; } #endif + else if ( QgsDiagramWidget *widget = qobject_cast( current ) ) + { + mDiagramWidget = widget; + } } mWidgetStack->clear(); @@ -495,6 +514,11 @@ void QgsLayerStylingWidget::updateCurrentWidgetLayer() { QgsVectorLayer *vlayer = qobject_cast( mCurrentLayer ); +#ifdef HAVE_3D + const int tabShift = 1; // To move subsequent tabs +#else + const int tabShift = 0; +#endif switch ( row ) { case 0: // Style @@ -550,6 +574,15 @@ void QgsLayerStylingWidget::updateCurrentWidgetLayer() break; } #endif + case 3 + tabShift: // Diagrams + { + mDiagramWidget = new QgsDiagramWidget( vlayer, mMapCanvas, mWidgetStack ); + mDiagramWidget->setDockMode( true ); + connect( mDiagramWidget, &QgsDiagramWidget::widgetChanged, this, &QgsLayerStylingWidget::autoApply ); + mDiagramWidget->syncToOwnLayer(); + mWidgetStack->setMainPanel( mDiagramWidget ); + break; + } default: break; } diff --git a/src/app/qgslayerstylingwidget.h b/src/app/qgslayerstylingwidget.h index 32f06ffa1f21..832f37846814 100644 --- a/src/app/qgslayerstylingwidget.h +++ b/src/app/qgslayerstylingwidget.h @@ -34,6 +34,7 @@ class QgsLabelingWidget; class QgsMaskingWidget; +class QgsDiagramWidget; class QgsMapLayer; class QgsMapCanvas; class QgsRendererPropertiesDialog; @@ -101,6 +102,7 @@ class APP_EXPORT QgsLayerStylingWidget : public QWidget, private Ui::QgsLayerSty History, Symbology3D, RasterAttributeTables, //!< Raster attribute tables, since QGIS 3.30 + VectorDiagram, //!< Vector diagram, since QGIS 3.40 }; QgsLayerStylingWidget( QgsMapCanvas *canvas, QgsMessageBar *messageBar, const QList &pages, QWidget *parent = nullptr ); @@ -175,6 +177,7 @@ class APP_EXPORT QgsLayerStylingWidget : public QWidget, private Ui::QgsLayerSty QgsVectorLayer3DRendererWidget *mVector3DWidget = nullptr; QgsMeshLayer3DRendererWidget *mMesh3DWidget = nullptr; #endif + QgsDiagramWidget *mDiagramWidget = nullptr; QgsRendererRasterPropertiesWidget *mRasterStyleWidget = nullptr; QgsRasterAttributeTableWidget *mRasterAttributeTableWidget = nullptr; QgsPanelWidget *mRasterAttributeTableDisabledWidget = nullptr; diff --git a/src/core/qgsdiagramrenderer.cpp b/src/core/qgsdiagramrenderer.cpp index 522fa6495e6c..fda219a0c094 100644 --- a/src/core/qgsdiagramrenderer.cpp +++ b/src/core/qgsdiagramrenderer.cpp @@ -528,7 +528,7 @@ void QgsDiagramRenderer::renderDiagram( const QgsFeature &feature, QgsRenderCont QSizeF QgsDiagramRenderer::sizeMapUnits( const QgsFeature &feature, const QgsRenderContext &c ) const { QgsDiagramSettings s; - if ( !diagramSettings( feature, c, s ) ) + if ( !diagramSettings( feature, c, s ) || !s.enabled ) { return QSizeF(); } @@ -845,16 +845,18 @@ QgsStackedDiagramRenderer *QgsStackedDiagramRenderer::clone() const QSizeF QgsStackedDiagramRenderer::sizeMapUnits( const QgsFeature &feature, const QgsRenderContext &c ) const { QSizeF stackedSize( 0, 0 ); + int enabledDiagramCount = 0; // We'll add spacing only for enabled subDiagrams // Iterate renderers. For each renderer, get the diagram // size for the feature and add it to the total size // accounting for stacked diagram defined spacing - for ( int i = 0; i < mDiagramRenderers.count(); i++ ) + for ( const auto &subRenderer : std::as_const( mDiagramRenderers ) ) { - QSizeF size = mDiagramRenderers.at( i )->sizeMapUnits( feature, c ); + QSizeF size = subRenderer->sizeMapUnits( feature, c ); if ( size.isValid() ) { + enabledDiagramCount++; switch ( mSettings.stackedDiagramMode ) { case QgsDiagramSettings::Horizontal: @@ -875,11 +877,11 @@ QSizeF QgsStackedDiagramRenderer::sizeMapUnits( const QgsFeature &feature, const switch ( mSettings.stackedDiagramMode ) { case QgsDiagramSettings::Horizontal: - stackedSize.scale( stackedSize.width() + spacing * ( mDiagramRenderers.count() - 1 ), stackedSize.height(), Qt::IgnoreAspectRatio ); + stackedSize.scale( stackedSize.width() + spacing * ( enabledDiagramCount - 1 ), stackedSize.height(), Qt::IgnoreAspectRatio ); break; case QgsDiagramSettings::Vertical: - stackedSize.scale( stackedSize.width(), stackedSize.height() + spacing * ( mDiagramRenderers.count() - 1 ), Qt::IgnoreAspectRatio ); + stackedSize.scale( stackedSize.width(), stackedSize.height() + spacing * ( enabledDiagramCount - 1 ), Qt::IgnoreAspectRatio ); break; } return stackedSize; @@ -893,11 +895,15 @@ void QgsStackedDiagramRenderer::renderDiagram( const QgsFeature &feature, QgsRen } QPointF newPos = pos; // Each subdiagram will have its own newPos - QList< QgsDiagramRenderer * > stackedRenderers = renderers(); - for ( const auto &stackedRenderer : std::as_const( stackedRenderers ) ) + + // Get subrenderers sorted by mode (vertical diagrams are returned backwards) + const QList< QgsDiagramRenderer * > stackedRenderers = renderers( true ); + + for ( const auto &stackedRenderer : stackedRenderers ) { if ( stackedRenderer->rendererName() == QStringLiteral( "Stacked" ) ) { + // Nested stacked diagrams will use this recursion stackedRenderer->renderDiagram( feature, c, newPos, properties ); continue; } @@ -905,7 +911,12 @@ void QgsStackedDiagramRenderer::renderDiagram( const QgsFeature &feature, QgsRen QgsDiagramSettings s; if ( !stackedRenderer->diagramSettings( feature, c, s ) ) { - return; + continue; + } + + if ( !s.enabled ) + { + continue; } if ( properties.hasActiveProperties() ) @@ -963,26 +974,19 @@ QList QgsStackedDiagramRenderer::diagramAttributes() const QList< QgsLayerTreeModelLegendNode * > QgsStackedDiagramRenderer::legendItems( QgsLayerTreeLayer *nodeLayer ) const { QList< QgsLayerTreeModelLegendNode * > nodes; - for ( int i = 0; i < rendererCount(); i++ ) + for ( const auto &renderer : std::as_const( mDiagramRenderers ) ) { - nodes << mDiagramRenderers.at( i )->legendItems( nodeLayer ); + nodes << renderer->legendItems( nodeLayer ); } return nodes; } -QList< QgsDiagramRenderer * > QgsStackedDiagramRenderer::renderers() const +QList< QgsDiagramRenderer * > QgsStackedDiagramRenderer::renderers( bool sortByDiagramMode ) const { QList< QgsDiagramRenderer * > renderers; - if ( mSettings.stackedDiagramMode == QgsDiagramSettings::Horizontal ) - { - for ( const auto &item : std::as_const( mDiagramRenderers ) ) - { - renderers.append( item ); - } - } - else + if ( sortByDiagramMode && mSettings.stackedDiagramMode == QgsDiagramSettings::Vertical ) { // We draw vertical diagrams backwards, so // we return the subrenderers in reverse order @@ -992,6 +996,10 @@ QList< QgsDiagramRenderer * > QgsStackedDiagramRenderer::renderers() const renderers.append( *iter ); } } + else + { + renderers = mDiagramRenderers; + } return renderers; } @@ -1013,11 +1021,6 @@ const QgsDiagramRenderer *QgsStackedDiagramRenderer::renderer( const int index ) return nullptr; } -int QgsStackedDiagramRenderer::rendererCount() const -{ - return mDiagramRenderers.count(); -} - void QgsStackedDiagramRenderer::readXml( const QDomElement &elem, const QgsReadWriteContext &context ) { const QDomElement categoryElem = elem.firstChildElement( QStringLiteral( "DiagramCategory" ) ); @@ -1234,7 +1237,7 @@ QList< QgsLayerTreeModelLegendNode * > QgsDiagramRenderer::legendItems( QgsLayer QList< QgsLayerTreeModelLegendNode * > QgsSingleCategoryDiagramRenderer::legendItems( QgsLayerTreeLayer *nodeLayer ) const { QList< QgsLayerTreeModelLegendNode * > nodes; - if ( mShowAttributeLegend ) + if ( mShowAttributeLegend && mSettings.enabled ) nodes = mSettings.legendItems( nodeLayer ); return nodes; @@ -1243,6 +1246,11 @@ QList< QgsLayerTreeModelLegendNode * > QgsSingleCategoryDiagramRenderer::legendI QList< QgsLayerTreeModelLegendNode * > QgsLinearlyInterpolatedDiagramRenderer::legendItems( QgsLayerTreeLayer *nodeLayer ) const { QList< QgsLayerTreeModelLegendNode * > nodes; + if ( !mSettings.enabled ) + { + return nodes; + } + if ( mShowAttributeLegend ) nodes = mSettings.legendItems( nodeLayer ); diff --git a/src/core/qgsdiagramrenderer.h b/src/core/qgsdiagramrenderer.h index 4fce0f63bfca..6f9bdbb04dba 100644 --- a/src/core/qgsdiagramrenderer.h +++ b/src/core/qgsdiagramrenderer.h @@ -1039,13 +1039,13 @@ class CORE_EXPORT QgsStackedDiagramRenderer : public QgsDiagramRenderer /** * Returns an ordered list with the renderers of the stacked renderer object. - * If the stacked diagram orientation is vertical, the list is returned backwards. + * @param sortByDiagramMode If true, the list is returned backwards for vertical orientation. */ - QList< QgsDiagramRenderer * > renderers() const; + QList< QgsDiagramRenderer * > renderers( bool sortByDiagramMode = false ) const; /** * Adds a renderer to the stacked renderer object. - * \param renderer diagram renderer to be added to the stacked renderer + * @param renderer diagram renderer to be added to the stacked renderer * Renderers added first will render their diagrams first, i.e., more to * the left (horizontal mode) or more to the top (vertical mode). */ @@ -1057,11 +1057,6 @@ class CORE_EXPORT QgsStackedDiagramRenderer : public QgsDiagramRenderer */ const QgsDiagramRenderer *renderer( const int index ) const; - /** - * Returns the number of renderers that this stacked renderer is composed of. - */ - int rendererCount() const; - protected: bool diagramSettings( const QgsFeature &feature, const QgsRenderContext &c, QgsDiagramSettings &s ) const override; QSizeF diagramSize( const QgsFeature &, const QgsRenderContext &c ) const override; diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index c3493aff5cf5..ddaeede527be 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -35,6 +35,7 @@ set(QGIS_GUI_SRCS vector/qgsattributesforminitcode.cpp vector/qgsattributesformproperties.cpp vector/qgsdiagramproperties.cpp + vector/qgsdiagramwidget.cpp vector/qgsfieldcalculator.cpp vector/qgsjoindialog.cpp vector/qgssourcefieldsproperties.cpp @@ -1497,6 +1498,7 @@ set(QGIS_GUI_HDRS vector/qgsattributesforminitcode.h vector/qgsattributesformproperties.h vector/qgsdiagramproperties.h + vector/qgsdiagramwidget.h vector/qgsfieldcalculator.h vector/qgsjoindialog.h vector/qgssourcefieldsproperties.h diff --git a/src/gui/vector/qgsdiagramproperties.cpp b/src/gui/vector/qgsdiagramproperties.cpp index 44122d47e1e1..f72a82b8b46c 100644 --- a/src/gui/vector/qgsdiagramproperties.cpp +++ b/src/gui/vector/qgsdiagramproperties.cpp @@ -59,10 +59,10 @@ QgsExpressionContext QgsDiagramProperties::createExpressionContext() const } QgsDiagramProperties::QgsDiagramProperties( QgsVectorLayer *layer, QWidget *parent, QgsMapCanvas *canvas ) - : QWidget( parent ) + : QgsPanelWidget( parent ) + , mLayer( layer ) , mMapCanvas( canvas ) { - mLayer = layer; if ( !layer ) { return; @@ -80,6 +80,10 @@ QgsDiagramProperties::QgsDiagramProperties( QgsVectorLayer *layer, QWidget *pare // get rid of annoying outer focus rect on Mac mDiagramOptionsListWidget->setAttribute( Qt::WA_MacShowFocusRect, false ); + const int iconSize = QgsGuiUtils::scaleIconSize( 20 ); + mOptionsTab->setIconSize( QSize( iconSize, iconSize ) ); + mDiagramOptionsListWidget->setIconSize( QSize( iconSize, iconSize ) ) ; + mBarSpacingSpinBox->setClearValue( 0 ); mBarSpacingUnitComboBox->setUnits( { Qgis::RenderUnit::Millimeters, Qgis::RenderUnit::MetersInMapUnits, @@ -144,6 +148,7 @@ QgsDiagramProperties::QgsDiagramProperties( QgsVectorLayer *layer, QWidget *pare if ( layerType == Qgis::GeometryType::Unknown || layerType == Qgis::GeometryType::Null ) { mDiagramTypeComboBox->setEnabled( false ); + mOptionsTab->setEnabled( false ); mDiagramFrame->setEnabled( false ); } @@ -224,6 +229,10 @@ QgsDiagramProperties::QgsDiagramProperties( QgsVectorLayer *layer, QWidget *pare mDiagramOptionsSplitter->restoreState( settings.value( QStringLiteral( "Windows/Diagrams/OptionsSplitState" ) ).toByteArray() ); mDiagramOptionsListWidget->setCurrentRow( settings.value( QStringLiteral( "Windows/Diagrams/Tab" ), 0 ).toInt() ); + // set correct initial tab to match displayed setting page + whileBlocking( mOptionsTab )->setCurrentIndex( mDiagramStackedWidget->currentIndex() ); + mOptionsTab->tabBar()->setUsesScrollButtons( true ); + // field combo and expression button mSizeFieldExpressionWidget->setLayer( mLayer ); QgsDistanceArea myDa; @@ -250,6 +259,11 @@ QgsDiagramProperties::QgsDiagramProperties( QgsVectorLayer *layer, QWidget *pare mOrientationUpButton->setProperty( "direction", QgsDiagramSettings::Up ); mOrientationDownButton->setProperty( "direction", QgsDiagramSettings::Down ); + // Labels to let users know some widgets are not present + // when editing sub diagrams in a stacked diagram. + mDlsLabel_1->hide(); + mDlsLabel_2->hide(); + //force a refresh of widget status to match diagram type mDiagramTypeComboBox_currentIndexChanged( mDiagramTypeComboBox->currentIndex() ); @@ -268,15 +282,99 @@ QgsDiagramProperties::QgsDiagramProperties( QgsVectorLayer *layer, QWidget *pare registerDataDefinedButton( mStartAngleDDBtn, QgsDiagramLayerSettings::Property::StartAngle ); connect( mButtonSizeLegendSettings, &QPushButton::clicked, this, &QgsDiagramProperties::showSizeLegendDialog ); + + QList widgets; + widgets << chkLineAbove; + widgets << chkLineBelow; + widgets << chkLineOn; + widgets << chkLineOrientationDependent; + widgets << mAngleDirectionComboBox; + widgets << mAngleOffsetComboBox; + widgets << mAttributeBasedScalingRadio; + widgets << mAxisLineStyleButton; + widgets << mBackgroundColorButton; + widgets << mBarSpacingSpinBox; + widgets << mBarSpacingUnitComboBox; + widgets << mBarWidthSpinBox; + widgets << mCheckBoxAttributeLegend; + widgets << mDiagramAttributesTreeWidget; + widgets << mDiagramDistanceSpinBox; + //widgets << mDiagramFontButton; + widgets << mDiagramPenColorButton; + widgets << mDiagramSizeSpinBox; + widgets << mDiagramLineUnitComboBox; + widgets << mDiagramTypeComboBox; + widgets << mDiagramUnitComboBox; + widgets << mFixedSizeRadio; + widgets << mIncreaseMinimumSizeSpinBox; + widgets << mIncreaseSmallDiagramsCheck; + widgets << mLabelPlacementComboBox; + widgets << mMaxValueSpinBox; + widgets << mPenWidthSpinBox; + widgets << mPrioritySlider; + widgets << mOpacityWidget; + widgets << mOrientationDownButton; + widgets << mOrientationLeftButton; + widgets << mOrientationRightButton; + widgets << mOrientationUpButton; + widgets << mScaleDependencyComboBox; + widgets << mScaleRangeWidget; + widgets << mScaleVisibilityGroupBox; + widgets << mShowAllCheckBox; + widgets << mShowAxisGroupBox; + widgets << mSizeFieldExpressionWidget; + widgets << mSizeSpinBox; + widgets << mZIndexSpinBox; + widgets << radAroundCentroid; + widgets << radAroundLine; + widgets << radAroundPoint; + widgets << radInsidePolygon; + widgets << radOverCentroid; + widgets << radOverLine; + widgets << radOverPoint; + widgets << radPolygonPerimeter; + + connectValueChanged( widgets, SIGNAL( widgetChanged() ) ); +} + +void QgsDiagramProperties::setDockMode( bool dockMode ) +{ + QgsPanelWidget::setDockMode( dockMode ); + mOptionsTab->setVisible( dockMode ); + mOptionsTab->setTabToolTip( 0, tr( "Attributes" ) ); + mOptionsTab->setTabToolTip( 1, tr( "Rendering" ) ); + mOptionsTab->setTabToolTip( 2, tr( "Size" ) ); + mOptionsTab->setTabToolTip( 3, tr( "Placement" ) ); + mOptionsTab->setTabToolTip( 4, tr( "Options" ) ); + mOptionsTab->setTabToolTip( 5, tr( "Legend" ) ); + mDiagramOptionsListFrame->setVisible( !dockMode ); +} + +void QgsDiagramProperties::setDiagramType( const QString diagramType ) +{ + mDiagramType = diagramType; + + mDiagramTypeComboBox->setVisible( false ); + mDiagramTypeComboBox->blockSignals( true ); + mDiagramTypeComboBox->setCurrentIndex( mDiagramTypeComboBox->findData( mDiagramType ) ); + mDiagramTypeComboBox->blockSignals( false ); + + //force a refresh of widget status to match diagram type + mDiagramTypeComboBox_currentIndexChanged( mDiagramTypeComboBox->currentIndex() ); +} + +void QgsDiagramProperties::syncToLayer() +{ + const QgsDiagramRenderer *renderer = mLayer->diagramRenderer(); + syncToRenderer( renderer ); + + const QgsDiagramLayerSettings *layerDls = mLayer->diagramLayerSettings(); + syncToSettings( layerDls ); } -void QgsDiagramProperties::syncToLayer( const QgsDiagramRenderer *dr ) +void QgsDiagramProperties::syncToRenderer( const QgsDiagramRenderer *dr ) { mDiagramAttributesTreeWidget->clear(); - if ( !dr ) - { - dr = mLayer->diagramRenderer(); - } if ( !dr ) //no diagram renderer yet, insert reasonable default { @@ -344,6 +442,7 @@ void QgsDiagramProperties::syncToLayer( const QgsDiagramRenderer *dr ) const QList settingList = dr->diagramSettings(); if ( !settingList.isEmpty() ) { + mOptionsTab->setEnabled( settingList.at( 0 ).enabled ); mDiagramFrame->setEnabled( settingList.at( 0 ).enabled ); mDiagramFontButton->setCurrentFont( settingList.at( 0 ).font ); const QSizeF size = settingList.at( 0 ).size; @@ -458,51 +557,6 @@ void QgsDiagramProperties::syncToLayer( const QgsDiagramRenderer *dr ) } } - const QgsDiagramLayerSettings *dls = mLayer->diagramLayerSettings(); - if ( dls ) - { - mDiagramDistanceSpinBox->setValue( dls->distance() ); - mPrioritySlider->setValue( dls->priority() ); - mZIndexSpinBox->setValue( dls->zIndex() ); - - switch ( dls->placement() ) - { - case QgsDiagramLayerSettings::AroundPoint: - radAroundPoint->setChecked( true ); - radAroundCentroid->setChecked( true ); - break; - - case QgsDiagramLayerSettings::OverPoint: - radOverPoint->setChecked( true ); - radOverCentroid->setChecked( true ); - break; - - case QgsDiagramLayerSettings::Line: - radAroundLine->setChecked( true ); - radPolygonPerimeter->setChecked( true ); - break; - - case QgsDiagramLayerSettings::Horizontal: - radOverLine->setChecked( true ); - radInsidePolygon->setChecked( true ); - break; - - default: - break; - } - - chkLineAbove->setChecked( dls->linePlacementFlags() & QgsDiagramLayerSettings::AboveLine ); - chkLineBelow->setChecked( dls->linePlacementFlags() & QgsDiagramLayerSettings::BelowLine ); - chkLineOn->setChecked( dls->linePlacementFlags() & QgsDiagramLayerSettings::OnLine ); - if ( !( dls->linePlacementFlags() & QgsDiagramLayerSettings::MapOrientation ) ) - chkLineOrientationDependent->setChecked( true ); - updatePlacementWidgets(); - - mShowAllCheckBox->setChecked( dls->showAllDiagrams() ); - - mDataDefinedProperties = dls->dataDefinedProperties(); - } - if ( dr->diagram() ) { mDiagramType = dr->diagram()->diagramName(); @@ -523,6 +577,53 @@ void QgsDiagramProperties::syncToLayer( const QgsDiagramRenderer *dr ) mPaintEffectWidget->setPaintEffect( mPaintEffect.get() ); } +void QgsDiagramProperties::syncToSettings( const QgsDiagramLayerSettings *dls ) +{ + if ( dls ) + { + mDiagramDistanceSpinBox->setValue( dls->distance() ); + mPrioritySlider->setValue( dls->priority() ); + mZIndexSpinBox->setValue( dls->zIndex() ); + + switch ( dls->placement() ) + { + case QgsDiagramLayerSettings::AroundPoint: + radAroundPoint->setChecked( true ); + radAroundCentroid->setChecked( true ); + break; + + case QgsDiagramLayerSettings::OverPoint: + radOverPoint->setChecked( true ); + radOverCentroid->setChecked( true ); + break; + + case QgsDiagramLayerSettings::Line: + radAroundLine->setChecked( true ); + radPolygonPerimeter->setChecked( true ); + break; + + case QgsDiagramLayerSettings::Horizontal: + radOverLine->setChecked( true ); + radInsidePolygon->setChecked( true ); + break; + + default: + break; + } + + chkLineAbove->setChecked( dls->linePlacementFlags() & QgsDiagramLayerSettings::AboveLine ); + chkLineBelow->setChecked( dls->linePlacementFlags() & QgsDiagramLayerSettings::BelowLine ); + chkLineOn->setChecked( dls->linePlacementFlags() & QgsDiagramLayerSettings::OnLine ); + if ( !( dls->linePlacementFlags() & QgsDiagramLayerSettings::MapOrientation ) ) + chkLineOrientationDependent->setChecked( true ); + updatePlacementWidgets(); + + mShowAllCheckBox->setChecked( dls->showAllDiagrams() ); + + mDataDefinedProperties = dls->dataDefinedProperties(); + } +} + QgsDiagramProperties::~QgsDiagramProperties() { QgsSettings settings; @@ -543,6 +644,7 @@ void QgsDiagramProperties::updateProperty() QgsPropertyOverrideButton *button = qobject_cast( sender() ); const QgsDiagramLayerSettings::Property key = static_cast< QgsDiagramLayerSettings::Property >( button->propertyKey() ); mDataDefinedProperties.setProperty( key, button->toProperty() ); + emit widgetChanged(); } void QgsDiagramProperties::mDiagramTypeComboBox_currentIndexChanged( int index ) @@ -750,6 +852,29 @@ void QgsDiagramProperties::mDiagramAttributesTreeWidget_itemDoubleClicked( QTree } } +std::unique_ptr< QgsDiagram > QgsDiagramProperties::createDiagramObject() +{ + std::unique_ptr< QgsDiagram > diagram; + + if ( mDiagramType == DIAGRAM_NAME_TEXT ) + { + diagram = std::make_unique< QgsTextDiagram >(); + } + else if ( mDiagramType == DIAGRAM_NAME_PIE ) + { + diagram = std::make_unique< QgsPieDiagram >(); + } + else if ( mDiagramType == DIAGRAM_NAME_STACKED_BAR ) + { + diagram = std::make_unique< QgsStackedBarDiagram >(); + } + else // if ( diagramType == DIAGRAM_NAME_HISTOGRAM ) + { + diagram = std::make_unique< QgsHistogramDiagram >(); + } + return diagram; +} + std::unique_ptr QgsDiagramProperties::createDiagramSettings() { std::unique_ptr< QgsDiagramSettings > ds = std::make_unique< QgsDiagramSettings>(); @@ -821,13 +946,15 @@ std::unique_ptr QgsDiagramProperties::createDiagramSettings( return ds; } -std::unique_ptr QgsDiagramProperties::createRendererBaseInfo( const QgsDiagramSettings &ds ) +std::unique_ptr QgsDiagramProperties::createRenderer() { + std::unique_ptr< QgsDiagramSettings > ds = createDiagramSettings(); + std::unique_ptr< QgsDiagramRenderer > renderer; if ( mFixedSizeRadio->isChecked() ) { std::unique_ptr< QgsSingleCategoryDiagramRenderer > dr = std::make_unique< QgsSingleCategoryDiagramRenderer >(); - dr->setDiagramSettings( ds ); + dr->setDiagramSettings( *ds ); renderer = std::move( dr ); } else @@ -849,7 +976,7 @@ std::unique_ptr QgsDiagramProperties::createRendererBaseInfo { dr->setClassificationField( sizeFieldNameOrExp ); } - dr->setDiagramSettings( ds ); + dr->setDiagramSettings( *ds ); dr->setDataDefinedSizeLegend( mSizeLegend ? new QgsDataDefinedSizeLegend( *mSizeLegend ) : nullptr ); @@ -857,6 +984,10 @@ std::unique_ptr QgsDiagramProperties::createRendererBaseInfo } renderer->setAttributeLegend( mCheckBoxAttributeLegend->isChecked() ); + + std::unique_ptr< QgsDiagram > diagram = createDiagramObject(); + renderer->setDiagram( diagram.release() ); + return renderer; } @@ -914,34 +1045,18 @@ void QgsDiagramProperties::apply() const int index = mDiagramTypeComboBox->currentIndex(); const bool diagramsEnabled = ( index != -1 ); - std::unique_ptr< QgsDiagram > diagram; - - if ( diagramsEnabled && 0 == mDiagramAttributesTreeWidget->topLevelItemCount() ) - { - QMessageBox::warning( this, tr( "Diagrams: No attributes added." ), - tr( "You did not add any attributes to this diagram layer. Please specify the attributes to visualize on the diagrams or disable diagrams." ) ); - } - - if ( mDiagramType == DIAGRAM_NAME_TEXT ) - { - diagram = std::make_unique< QgsTextDiagram >(); - } - else if ( mDiagramType == DIAGRAM_NAME_PIE ) - { - diagram = std::make_unique< QgsPieDiagram >(); - } - else if ( mDiagramType == DIAGRAM_NAME_STACKED_BAR ) - { - diagram = std::make_unique< QgsStackedBarDiagram >(); - } - else // if ( diagramType == DIAGRAM_NAME_HISTOGRAM ) + // Avoid this messageBox when in both dock and liveUpdate mode + QgsSettings settings; + if ( !dockMode() || !settings.value( QStringLiteral( "UI/autoApplyStyling" ), true ).toBool() ) { - diagram = std::make_unique< QgsHistogramDiagram >(); + if ( diagramsEnabled && 0 == mDiagramAttributesTreeWidget->topLevelItemCount() ) + { + QMessageBox::warning( this, tr( "Diagrams: No attributes added." ), + tr( "You did not add any attributes to this diagram layer. Please specify the attributes to visualize on the diagrams or disable diagrams." ) ); + } } - std::unique_ptr< QgsDiagramSettings > ds = createDiagramSettings(); - std::unique_ptr< QgsDiagramRenderer > renderer = createRendererBaseInfo( *ds ); - renderer->setDiagram( diagram.release() ); + std::unique_ptr< QgsDiagramRenderer > renderer = createRenderer(); mLayer->setDiagramRenderer( renderer.release() ); QgsDiagramLayerSettings dls = createDiagramLayerSettings(); @@ -1051,6 +1166,30 @@ void QgsDiagramProperties::scalingTypeChanged() mButtonSizeLegendSettings->setEnabled( mAttributeBasedScalingRadio->isChecked() ); } +void QgsDiagramProperties::setAllowedToEditDiagramLayerSettings( bool allowed ) +{ + mAllowedToEditDls = allowed; + + label_16->setVisible( allowed ); + mZIndexSpinBox->setVisible( allowed ); + mZOrderDDBtn->setVisible( allowed ); + mShowAllCheckBox->setVisible( allowed ); + mDlsLabel_1->setVisible( !allowed ); + + mCoordinatesGrpBox->setVisible( allowed ); + mLinePlacementFrame->setVisible( allowed ); + mObstaclesGrpBox->setVisible( allowed ); + mPlacementFrame->setVisible( allowed ); + mPriorityGrpBox->setVisible( allowed ); + stackedPlacement->setVisible( allowed ); + mDlsLabel_2->setVisible( !allowed ); +} + +bool QgsDiagramProperties::isAllowedToEditDiagramLayerSettings() const +{ + return mAllowedToEditDls; +} + void QgsDiagramProperties::showSizeLegendDialog() { // prepare size transformer @@ -1112,3 +1251,71 @@ void QgsDiagramProperties::createAuxiliaryField() emit auxiliaryFieldCreated(); } + +void QgsDiagramProperties::connectValueChanged( const QList &widgets, const char *signal ) +{ + const auto constWidgets = widgets; + for ( QWidget *widget : constWidgets ) + { + if ( QgsSymbolButton *w = qobject_cast( widget ) ) + { + connect( w, SIGNAL( changed() ), this, signal ); + } + else if ( QgsFieldExpressionWidget *w = qobject_cast< QgsFieldExpressionWidget *>( widget ) ) + { + connect( w, SIGNAL( fieldChanged( QString ) ), this, signal ); + } + else if ( QgsOpacityWidget *w = qobject_cast< QgsOpacityWidget *>( widget ) ) + { + connect( w, SIGNAL( opacityChanged( double ) ), this, signal ); + } + else if ( QgsUnitSelectionWidget *w = qobject_cast( widget ) ) + { + connect( w, SIGNAL( changed() ), this, signal ); + } + else if ( QComboBox *w = qobject_cast( widget ) ) + { + connect( w, SIGNAL( currentIndexChanged( int ) ), this, signal ); + } + else if ( QSpinBox *w = qobject_cast( widget ) ) + { + connect( w, SIGNAL( valueChanged( int ) ), this, signal ); + } + else if ( QDoubleSpinBox *w = qobject_cast( widget ) ) + { + connect( w, SIGNAL( valueChanged( double ) ), this, signal ); + } + else if ( QgsColorButton *w = qobject_cast( widget ) ) + { + connect( w, SIGNAL( colorChanged( QColor ) ), this, signal ); + } + else if ( QCheckBox *w = qobject_cast( widget ) ) + { + connect( w, SIGNAL( toggled( bool ) ), this, signal ); + } + else if ( QRadioButton *w = qobject_cast( widget ) ) + { + connect( w, SIGNAL( toggled( bool ) ), this, signal ); + } + else if ( QSlider *w = qobject_cast( widget ) ) + { + connect( w, SIGNAL( valueChanged( int ) ), this, signal ); + } + else if ( QGroupBox *w = qobject_cast( widget ) ) + { + connect( w, SIGNAL( toggled( bool ) ), this, signal ); + } + else if ( QTreeWidget *w = qobject_cast( widget ) ) + { + connect( w, SIGNAL( itemChanged( QTreeWidgetItem *, int ) ), this, signal ); + } + else if ( QgsScaleRangeWidget *w = qobject_cast( widget ) ) + { + connect( w, SIGNAL( rangeChanged( double, double ) ), this, signal ); + } + else + { + QgsLogger::warning( QStringLiteral( "Could not create connection for widget %1" ).arg( widget->objectName() ) ); + } + } +} diff --git a/src/gui/vector/qgsdiagramproperties.h b/src/gui/vector/qgsdiagramproperties.h index fbb4a43d0c89..b967af33aa59 100644 --- a/src/gui/vector/qgsdiagramproperties.h +++ b/src/gui/vector/qgsdiagramproperties.h @@ -36,26 +36,68 @@ class QgsMapCanvas; /** * \ingroup gui * \class QgsDiagramProperties + * + * \note This class is not a part of public API */ -class GUI_EXPORT QgsDiagramProperties : public QWidget, private Ui::QgsDiagramPropertiesBase, private QgsExpressionContextGenerator +class GUI_EXPORT QgsDiagramProperties : public QgsPanelWidget, private Ui::QgsDiagramPropertiesBase, private QgsExpressionContextGenerator { Q_OBJECT public: QgsDiagramProperties( QgsVectorLayer *layer, QWidget *parent, QgsMapCanvas *canvas ); + ~QgsDiagramProperties() override; /** * Updates the widget to reflect the layer's current diagram settings. * * \since QGIS 3.16 */ - void syncToLayer( const QgsDiagramRenderer *dr = nullptr ); + void syncToLayer(); - ~QgsDiagramProperties() override; + /** + * Updates the widget to reflect the diagram renderer. + * \param dr Diagram renderer where settings are taken from. + * + * \since QGIS 3.40 + */ + void syncToRenderer( const QgsDiagramRenderer *dr ); + + /** + * Updates the widget to reflect the diagram layer settings. + * \param dls Diagram Layer Settings to update the widget. + * + * \since QGIS 3.40 + */ + void syncToSettings( const QgsDiagramLayerSettings *dls ); //! Adds an attribute from the list of available attributes to the assigned attributes with a random color. void addAttribute( QTreeWidgetItem *item ); + /** + * Sets the widget in dock mode. + * \param dockMode TRUE for dock mode. + */ + void setDockMode( bool dockMode ) override; + + /** + * Defines the widget's diagram type and lets it know it should hide the type comboBox. + * @param diagramType Type of diagram to be set + */ + void setDiagramType( const QString diagramType ); + + /** + * Sets whether the widget should show diagram layer settings. + * Used by stacked diagrams, which disable editing of DLS for sub diagrams + * other than the first one. + * @param allowed Whether this widget should be allowed to edit diagram layer settings. + */ + void setAllowedToEditDiagramLayerSettings( bool allowed ); + + /** + * Returns whether this widget is allowed to edit diagram layer settings. + */ + bool isAllowedToEditDiagramLayerSettings() const; + signals: void auxiliaryFieldCreated(); @@ -118,8 +160,24 @@ class GUI_EXPORT QgsDiagramProperties : public QWidget, private Ui::QgsDiagramPr QgsExpressionContext createExpressionContext() const override; + bool mAllowedToEditDls = true; + void registerDataDefinedButton( QgsPropertyOverrideButton *button, QgsDiagramLayerSettings::Property key ); + /** + * Convenience function to chain widgets' change value signal to another signal. + * @param widgets List of widgets. + * @param signal Signal to be triggered by each widget's change value signal. + */ + void connectValueChanged( const QList &widgets, const char *signal ); + + /** + * Creates a QgsDiagram object from the GUI settings. + * + * \since QGIS 3.40 + */ + std::unique_ptr< QgsDiagram > createDiagramObject(); + /** * Creates a QgsDiagramSettings object from the GUI settings. * @@ -132,7 +190,7 @@ class GUI_EXPORT QgsDiagramProperties : public QWidget, private Ui::QgsDiagramPr * * \since QGIS 3.40 */ - std::unique_ptr createRendererBaseInfo( const QgsDiagramSettings &ds ); + std::unique_ptr createRenderer(); /** * Creates a QgsDiagramLayerSettings object from the GUI settings. @@ -142,6 +200,7 @@ class GUI_EXPORT QgsDiagramProperties : public QWidget, private Ui::QgsDiagramPr QgsDiagramLayerSettings createDiagramLayerSettings(); friend class QgsStackedDiagramProperties; + friend class QgsStackedDiagramPropertiesDialog; }; diff --git a/src/gui/vector/qgsdiagramwidget.cpp b/src/gui/vector/qgsdiagramwidget.cpp new file mode 100644 index 000000000000..1235971ee48c --- /dev/null +++ b/src/gui/vector/qgsdiagramwidget.cpp @@ -0,0 +1,220 @@ +/*************************************************************************** + qgsdiagramwidget.h + Container widget for diagram layers + ------------------- + begin : September 2024 + copyright : (C) Germán Carrillo + email : german at opengis dot ch + + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "diagram/qgshistogramdiagram.h" +#include "diagram/qgspiediagram.h" +#include "diagram/qgstextdiagram.h" +#include "diagram/qgsstackedbardiagram.h" + +#include "qgsdiagramwidget.h" +#include "qgsvectorlayer.h" +#include "qgsapplication.h" +#include "qgsguiutils.h" +#include "qgslabelengineconfigdialog.h" +#include "qgsdiagramproperties.h" +#include "qgsstackeddiagramproperties.h" + + +QgsDiagramWidget::QgsDiagramWidget( QgsVectorLayer *layer, QgsMapCanvas *canvas, QWidget *parent ) + : QgsMapLayerConfigWidget( layer, canvas, parent ) + , mLayer( layer ) + , mCanvas( canvas ) +{ + if ( !layer ) + { + return; + } + + setupUi( this ); + + // Initialize stacked diagram controls + mDiagramTypeComboBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "diagramNone.svg" ) ), tr( "No Diagrams" ), ModeNone ); + mDiagramTypeComboBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "pie-chart.svg" ) ), tr( "Pie Chart" ), ModePie ); + mDiagramTypeComboBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "text.svg" ) ), tr( "Text Diagram" ), ModeText ); + mDiagramTypeComboBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "histogram.svg" ) ), tr( "Histogram" ), ModeHistogram ); + mDiagramTypeComboBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "stacked-bar.svg" ) ), tr( "Stacked Bars" ), ModeStackedBar ); + mDiagramTypeComboBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "diagramNone.svg" ) ), tr( "Stacked Diagram" ), ModeStacked ); + + connect( mEngineSettingsButton, &QAbstractButton::clicked, this, &QgsDiagramWidget::showEngineConfigDialog ); + + connect( mDiagramTypeComboBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsDiagramWidget::mDiagramTypeComboBox_currentIndexChanged ); + + const int iconSize16 = QgsGuiUtils::scaleIconSize( 16 ); + mEngineSettingsButton->setIconSize( QSize( iconSize16, iconSize16 ) ); +} + +void QgsDiagramWidget::apply() +{ + const Mode mode = static_cast< Mode >( mDiagramTypeComboBox->currentData().toInt() ); + + switch ( mode ) + { + case ModeStacked: + { + // Delegate to stacked diagram's apply + static_cast( mWidget )->apply(); + break; + } + case ModePie: + case ModeText: + case ModeHistogram: + case ModeStackedBar: + { + // Delegate to single diagram's apply + static_cast( mWidget )->apply(); + break; + } + case ModeNone: + { + mLayer->setDiagramRenderer( nullptr ); + + QgsDiagramLayerSettings dls; + mLayer->setDiagramLayerSettings( dls ); + + // refresh + QgsProject::instance()->setDirty( true ); + mLayer->triggerRepaint(); + } + } +} + +void QgsDiagramWidget::syncToOwnLayer() +{ + if ( !mLayer ) + { + return; + } + + whileBlocking( mDiagramTypeComboBox )->setCurrentIndex( -1 ); + + const QgsDiagramRenderer *dr = mLayer->diagramRenderer(); + + // pick the right mode from the layer + if ( dr && dr->diagram() ) + { + if ( dr->rendererName() == QStringLiteral( "Stacked" ) ) + { + mDiagramTypeComboBox->setCurrentIndex( ModeStacked ); + } + else // Single diagram + { + const QString diagramName = dr->diagram()->diagramName(); + if ( diagramName == DIAGRAM_NAME_PIE ) + { + mDiagramTypeComboBox->setCurrentIndex( ModePie ) ; + } + else if ( diagramName == DIAGRAM_NAME_TEXT ) + { + mDiagramTypeComboBox->setCurrentIndex( ModeText ) ; + } + else if ( diagramName == DIAGRAM_NAME_STACKED_BAR ) + { + mDiagramTypeComboBox->setCurrentIndex( ModeStackedBar ) ; + } + else // diagramName == DIAGRAM_NAME_HISTOGRAM + { + // Play safe and set to histogram by default if the diagram name is unknown + mDiagramTypeComboBox->setCurrentIndex( ModeHistogram ); + } + // TODO: if we get a stacked diagram, take the first subdiagram, + // Set its diagram type and sync to its settings + + // Delegate to single diagram's syncToLayer + static_cast( mWidget )->syncToLayer(); + } + } + else // No Diagram + { + mDiagramTypeComboBox->setCurrentIndex( ModeNone ); + } +} + +void QgsDiagramWidget::mDiagramTypeComboBox_currentIndexChanged( int index ) +{ + if ( mWidget ) + mStackedWidget->removeWidget( mWidget ); + + delete mWidget; + mWidget = nullptr; + + if ( index < 0 ) + return; + + const Mode mode = static_cast< Mode >( mDiagramTypeComboBox->currentData().toInt() ); + + switch ( mode ) + { + case ModePie: + case ModeText: + case ModeHistogram: + case ModeStackedBar: + { + QgsDiagramProperties *singleWidget = new QgsDiagramProperties( mLayer, this, mMapCanvas ); + singleWidget->layout()->setContentsMargins( 0, 0, 0, 0 ); + singleWidget->setDockMode( dockMode() ); + + if ( mode == ModePie ) + singleWidget->setDiagramType( DIAGRAM_NAME_PIE ); + else if ( mode == ModeText ) + singleWidget->setDiagramType( DIAGRAM_NAME_TEXT ); + else if ( mode == ModeHistogram ) + singleWidget->setDiagramType( DIAGRAM_NAME_HISTOGRAM ); + else if ( mode == ModeStackedBar ) + singleWidget->setDiagramType( DIAGRAM_NAME_STACKED_BAR ); + + connect( singleWidget, &QgsDiagramProperties::widgetChanged, this, &QgsDiagramWidget::widgetChanged ); + connect( singleWidget, &QgsDiagramProperties::auxiliaryFieldCreated, this, &QgsDiagramWidget::auxiliaryFieldCreated ); + + mWidget = singleWidget; + mStackedWidget->addWidget( mWidget ); + mStackedWidget->setCurrentWidget( mWidget ); + break; + } + case ModeStacked: + { + QgsStackedDiagramProperties *stackedWidget = new QgsStackedDiagramProperties( mLayer, this, mCanvas ); + stackedWidget->setDockMode( dockMode() ); + connect( stackedWidget, &QgsPanelWidget::showPanel, this, &QgsPanelWidget::openPanel ); + connect( stackedWidget, &QgsStackedDiagramProperties::widgetChanged, this, &QgsDiagramWidget::widgetChanged ); + mWidget = stackedWidget; + mStackedWidget->addWidget( mWidget ); + mStackedWidget->setCurrentWidget( mWidget ); + break; + } + case ModeNone: + break; + } + emit widgetChanged(); +} + +void QgsDiagramWidget::showEngineConfigDialog() +{ + QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ); + if ( panel && panel->dockMode() ) + { + QgsLabelEngineConfigWidget *widget = new QgsLabelEngineConfigWidget( mCanvas ); + connect( widget, &QgsLabelEngineConfigWidget::widgetChanged, widget, &QgsLabelEngineConfigWidget::apply ); + panel->openPanel( widget ); + } + else + { + QgsLabelEngineConfigDialog dialog( mCanvas, this ); + dialog.exec(); + // reactivate button's window + activateWindow(); + } +} diff --git a/src/gui/vector/qgsdiagramwidget.h b/src/gui/vector/qgsdiagramwidget.h new file mode 100644 index 000000000000..eb5f666b639a --- /dev/null +++ b/src/gui/vector/qgsdiagramwidget.h @@ -0,0 +1,78 @@ +/*************************************************************************** + qgsdiagramwidget.h + Container widget for diagram layers + ------------------- + begin : September 2024 + copyright : (C) Germán Carrillo + email : german at opengis dot ch + + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSDIAGRAMWIDGET_H +#define QGSDIAGRAMWIDGET_H + +// We don't want to expose this in the public API +#define SIP_NO_FILE + +#include +#include "ui_qgsdiagramwidget.h" + +#include + +/** + * \ingroup gui + * \class QgsDiagramWidget + * + * \since QGIS 3.40 + */ +class GUI_EXPORT QgsDiagramWidget : public QgsMapLayerConfigWidget, private Ui::QgsDiagramWidget +{ + Q_OBJECT + + public: + //! constructor + QgsDiagramWidget( QgsVectorLayer *layer, QgsMapCanvas *canvas, QWidget *parent = nullptr ); + + /** + * Updates the widget to reflect the layer's current diagram settings. + */ + void syncToOwnLayer(); + + public slots: + //! Saves the labeling configuration and immediately updates the map canvas to reflect the changes + void apply() override; + + signals: + //! Emitted when an auxiliary field is created + void auxiliaryFieldCreated(); + + private slots: + void mDiagramTypeComboBox_currentIndexChanged( int index ); + void showEngineConfigDialog(); + + private: + + enum Mode + { + ModeNone, + ModePie, + ModeText, + ModeHistogram, + ModeStackedBar, + ModeStacked + }; + + QgsVectorLayer *mLayer = nullptr; + QgsMapCanvas *mCanvas = nullptr; + + QWidget *mWidget = nullptr; +}; + +#endif // QGSDIAGRAMWIDGET_H diff --git a/src/gui/vector/qgsstackeddiagramproperties.cpp b/src/gui/vector/qgsstackeddiagramproperties.cpp index 8bdac8ff0b3d..5db0aa5140c5 100644 --- a/src/gui/vector/qgsstackeddiagramproperties.cpp +++ b/src/gui/vector/qgsstackeddiagramproperties.cpp @@ -19,40 +19,37 @@ #include "diagram/qgspiediagram.h" #include "diagram/qgstextdiagram.h" #include "diagram/qgsstackedbardiagram.h" +#include "diagram/qgsstackeddiagram.h" -#include "qgsapplication.h" +#include "qgsgui.h" #include "qgsdiagramproperties.h" #include "qgslabelengineconfigdialog.h" #include "qgsproject.h" -#include "qgsstackeddiagram.h" #include "qgsstackeddiagramproperties.h" #include "qgsvectorlayer.h" +#include "qgshelp.h" QgsStackedDiagramProperties::QgsStackedDiagramProperties( QgsVectorLayer *layer, QWidget *parent, QgsMapCanvas *canvas ) - : QWidget{parent} + : QgsPanelWidget( parent ) + , mLayer( layer ) , mMapCanvas( canvas ) { - mLayer = layer; if ( !layer ) { return; } setupUi( this ); - connect( mDiagramTypeComboBox, static_cast( &QComboBox::currentIndexChanged ), this, &QgsStackedDiagramProperties::mDiagramTypeComboBox_currentIndexChanged ); - connect( mEngineSettingsButton, &QPushButton::clicked, this, &QgsStackedDiagramProperties::mEngineSettingsButton_clicked ); + + connect( mSubDiagramsView, &QAbstractItemView::doubleClicked, this, static_cast( &QgsStackedDiagramProperties::editSubDiagram ) ); + connect( mAddSubDiagramButton, &QPushButton::clicked, this, &QgsStackedDiagramProperties::addSubDiagram ); + connect( mEditSubDiagramButton, &QAbstractButton::clicked, this, static_cast( &QgsStackedDiagramProperties::editSubDiagram ) ); connect( mRemoveSubDiagramButton, &QPushButton::clicked, this, &QgsStackedDiagramProperties::removeSubDiagram ); - connect( mSubDiagramsTabWidget->tabBar(), &QTabBar::tabMoved, this, &QgsStackedDiagramProperties::mSubDiagramsTabWidget_tabMoved ); // Initialize stacked diagram controls - QIcon icon = QgsApplication::getThemeIcon( QStringLiteral( "diagramNone.svg" ) ); - mDiagramTypeComboBox->addItem( icon, tr( "No Diagrams" ), "None" ); - mDiagramTypeComboBox->addItem( tr( "Single diagram" ), QgsDiagramLayerSettings::Single ); - mDiagramTypeComboBox->addItem( tr( "Stacked diagrams" ), QgsDiagramLayerSettings::Stacked ); mStackedDiagramModeComboBox->addItem( tr( "Horizontal" ), QgsDiagramSettings::Horizontal ); mStackedDiagramModeComboBox->addItem( tr( "Vertical" ), QgsDiagramSettings::Vertical ); - mRemoveSubDiagramButton->setEnabled( false ); mStackedDiagramSpacingSpinBox->setClearValue( 0 ); mStackedDiagramSpacingUnitComboBox->setUnits( { Qgis::RenderUnit::Millimeters, @@ -62,37 +59,117 @@ QgsStackedDiagramProperties::QgsStackedDiagramProperties( QgsVectorLayer *layer, Qgis::RenderUnit::Points, Qgis::RenderUnit::Inches } ); - // Add default subdiagram tab - addSubDiagram(); + connect( mStackedDiagramModeComboBox, qOverload< int >( &QComboBox::currentIndexChanged ), this, &QgsStackedDiagramProperties::widgetChanged ); + connect( mStackedDiagramSpacingSpinBox, qOverload< double >( &QgsDoubleSpinBox::valueChanged ), this, &QgsStackedDiagramProperties::widgetChanged ); + connect( mStackedDiagramSpacingUnitComboBox, &QgsUnitSelectionWidget::changed, this, &QgsStackedDiagramProperties::widgetChanged ); + + mModel = new QgsStackedDiagramPropertiesModel(); + mSubDiagramsView->setModel( mModel ); + + connect( mModel, &QAbstractItemModel::dataChanged, this, &QgsStackedDiagramProperties::widgetChanged ); + connect( mModel, &QAbstractItemModel::rowsInserted, this, &QgsStackedDiagramProperties::widgetChanged ); + connect( mModel, &QAbstractItemModel::rowsRemoved, this, &QgsStackedDiagramProperties::widgetChanged ); syncToLayer(); } void QgsStackedDiagramProperties::addSubDiagram() { - QgsDiagramProperties *gui = new QgsDiagramProperties( mLayer, this, mMapCanvas ); - gui->layout()->setContentsMargins( 6, 6, 6, 6 ); - connect( gui, &QgsDiagramProperties::auxiliaryFieldCreated, this, &QgsStackedDiagramProperties::auxiliaryFieldCreated ); + // Create a single category renderer by default + std::unique_ptr< QgsDiagramRenderer > renderer; + std::unique_ptr< QgsSingleCategoryDiagramRenderer > dr = std::make_unique< QgsSingleCategoryDiagramRenderer >(); + renderer = std::move( dr ); - mSubDiagramsTabWidget->addTab( gui, tr( "Diagram %1" ).arg( mSubDiagramsTabWidget->count() + 1 ) ); + QItemSelectionModel *sel = mSubDiagramsView->selectionModel(); + const QModelIndex index = sel->currentIndex(); - mRemoveSubDiagramButton->setEnabled( mSubDiagramsTabWidget->count() > 2 ); + if ( index.isValid() ) + { + // add after this subDiagram + const QModelIndex currentIndex = mSubDiagramsView->selectionModel()->currentIndex(); + mModel->insertSubDiagram( currentIndex.row() + 1, renderer.release() ); + const QModelIndex newIndex = mModel->index( currentIndex.row() + 1, 0 ); + mSubDiagramsView->selectionModel()->setCurrentIndex( newIndex, QItemSelectionModel::ClearAndSelect ); + } + else + { + // append to root + appendSubDiagram( renderer.release() ); + } + editSubDiagram(); } -void QgsStackedDiagramProperties::removeSubDiagram() +void QgsStackedDiagramProperties::appendSubDiagram( QgsDiagramRenderer *dr ) { - if ( mSubDiagramsTabWidget->count() > 2 ) - { - const int index = mSubDiagramsTabWidget->currentIndex(); - delete mSubDiagramsTabWidget->widget( index ); + const int rows = mModel->rowCount(); + mModel->insertSubDiagram( rows, dr ); + const QModelIndex newIndex = mModel->index( rows, 0 ); + mSubDiagramsView->selectionModel()->setCurrentIndex( newIndex, QItemSelectionModel::ClearAndSelect ); +} - mRemoveSubDiagramButton->setEnabled( mSubDiagramsTabWidget->count() > 2 ); +void QgsStackedDiagramProperties::editSubDiagram() +{ + editSubDiagram( mSubDiagramsView->selectionModel()->currentIndex() ); +} + +void QgsStackedDiagramProperties::editSubDiagram( const QModelIndex &index ) +{ + if ( !index.isValid() ) + return; - for ( int i = index; i < mSubDiagramsTabWidget->count(); i++ ) + QgsDiagramRenderer *renderer = mModel->subDiagramForIndex( index ); + QgsDiagramLayerSettings dls = mModel->diagramLayerSettings(); + QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ); + + if ( panel && panel->dockMode() ) + { + QgsDiagramProperties *widget = new QgsDiagramProperties( mLayer, this, mMapCanvas ); + widget->setPanelTitle( tr( "Edit Sub Diagram" ) ); + widget->layout()->setContentsMargins( 0, 0, 0, 0 ); + widget->syncToRenderer( renderer ); + widget->syncToSettings( &dls ); + if ( !couldBeFirstSubDiagram( index ) ) { - mSubDiagramsTabWidget->setTabText( i, tr( "Diagram %1" ).arg( i + 1 ) ); + widget->setAllowedToEditDiagramLayerSettings( false ); } + + connect( widget, &QgsDiagramProperties::auxiliaryFieldCreated, this, &QgsStackedDiagramProperties::auxiliaryFieldCreated ); + connect( widget, &QgsPanelWidget::panelAccepted, this, &QgsStackedDiagramProperties::subDiagramWidgetPanelAccepted ); + connect( widget, &QgsDiagramProperties::widgetChanged, this, &QgsStackedDiagramProperties::liveUpdateSubDiagramFromPanel ); + openPanel( widget ); + return; } + + QgsStackedDiagramPropertiesDialog dlg( mLayer, this, mMapCanvas ); + dlg.syncToRenderer( renderer ); + dlg.syncToSettings( &dls ); + if ( !couldBeFirstSubDiagram( index ) ) + { + dlg.setAllowedToEditDiagramLayerSettings( false ); + } + + if ( dlg.exec() ) + { + const QModelIndex index = mSubDiagramsView->selectionModel()->currentIndex(); + if ( dlg.isAllowedToEditDiagramLayerSettings() ) + mModel->updateDiagramLayerSettings( dlg.diagramLayerSettings() ); + + // This call will emit dataChanged, which in turns triggers widgetChanged() + mModel->updateSubDiagram( index, dlg.renderer() ); + } +} + +void QgsStackedDiagramProperties::removeSubDiagram() +{ + const QItemSelection sel = mSubDiagramsView->selectionModel()->selection(); + const auto constSel = sel; + for ( const QItemSelectionRange &range : constSel ) + { + if ( range.isValid() ) + mModel->removeRows( range.top(), range.bottom() - range.top() + 1, range.parent() ); + } + // make sure that the selection is gone + mSubDiagramsView->selectionModel()->clear(); } void QgsStackedDiagramProperties::syncToLayer() @@ -101,222 +178,372 @@ void QgsStackedDiagramProperties::syncToLayer() if ( dr && dr->diagram() ) { - if ( dr->rendererName() == QStringLiteral( "Stacked" ) ) + const QList settingList = dr->diagramSettings(); + mStackedDiagramModeComboBox->setCurrentIndex( settingList.at( 0 ).stackedDiagramMode ); + mStackedDiagramSpacingSpinBox->setValue( settingList.at( 0 ).stackedDiagramSpacing() ); + mStackedDiagramSpacingUnitComboBox->setUnit( settingList.at( 0 ).stackedDiagramSpacingUnit() ); + + if ( dr->rendererName() == QLatin1String( "Stacked" ) ) { - mDiagramTypeComboBox->blockSignals( true ); - mDiagramTypeComboBox->setCurrentIndex( mDiagramTypeComboBox->findData( QgsDiagramLayerSettings::Stacked ) ); - mDiagramTypeComboBox->blockSignals( false ); - //force a refresh of widget status to match diagram type - mDiagramTypeComboBox_currentIndexChanged( mDiagramTypeComboBox->currentIndex() ); - - const QList settingList = dr->diagramSettings(); - mStackedDiagramModeComboBox->setCurrentIndex( settingList.at( 0 ).stackedDiagramMode ); - mStackedDiagramSpacingSpinBox->setValue( settingList.at( 0 ).stackedDiagramSpacing() ); - mStackedDiagramSpacingUnitComboBox->setUnit( settingList.at( 0 ).stackedDiagramSpacingUnit() ); - - // Create/remove as many tabs as necessary const QgsStackedDiagramRenderer *stackedDiagramRenderer = static_cast< const QgsStackedDiagramRenderer * >( dr ); - const int rendererCount = stackedDiagramRenderer->rendererCount(); - while ( mSubDiagramsTabWidget->count() < rendererCount ) + const auto renderers = stackedDiagramRenderer->renderers(); + for ( const auto &renderer : renderers ) { - addSubDiagram(); - } - while ( mSubDiagramsTabWidget->count() > rendererCount ) - { - mSubDiagramsTabWidget->setCurrentIndex( mSubDiagramsTabWidget->count() - 1 ); - removeSubDiagram(); - } - - // Call subdiagrams' syncToLayer with the corresponding rendering object - for ( int i = 0; i < mSubDiagramsTabWidget->count(); i++ ) - { - QgsDiagramProperties *diagramProperties = static_cast( mSubDiagramsTabWidget->widget( i ) ); - diagramProperties->syncToLayer( stackedDiagramRenderer->renderer( i ) ); + appendSubDiagram( renderer ); } } - else // Single diagram + else { - mDiagramTypeComboBox->blockSignals( true ); - mDiagramTypeComboBox->setCurrentIndex( mDiagramTypeComboBox->findData( QgsDiagramLayerSettings::Single ) ); - mDiagramTypeComboBox->blockSignals( false ); - //force a refresh of widget status to match diagram type - mDiagramTypeComboBox_currentIndexChanged( mDiagramTypeComboBox->currentIndex() ); - - // Delegate to single diagram's syncToLayer - // If the diagram name is unknown, the single diagram will choose a default one (pie) - static_cast( mSubDiagramsTabWidget->widget( 0 ) )->syncToLayer(); + // Take this single renderer as the first stacked renderer + appendSubDiagram( dr->clone() ); } - } - else // No Diagram - { - mDiagramTypeComboBox->blockSignals( true ); - mDiagramTypeComboBox->setCurrentIndex( 0 ); - mDiagramTypeComboBox->blockSignals( false ); - //force a refresh of widget status to match diagram type - mDiagramTypeComboBox_currentIndexChanged( mDiagramTypeComboBox->currentIndex() ); - - // Delegate to first diagram's syncToLayer - // It will add required reasonable defaults - static_cast( mSubDiagramsTabWidget->widget( 0 ) )->syncToLayer(); + + const QgsDiagramLayerSettings *dls = mLayer->diagramLayerSettings(); + mModel->updateDiagramLayerSettings( *dls ); } } -void QgsStackedDiagramProperties::mSubDiagramsTabWidget_tabMoved( int from, int to ) +void QgsStackedDiagramProperties::apply() { - Q_UNUSED( from ) - Q_UNUSED( to ) - for ( int i = 0; i < mSubDiagramsTabWidget->count(); i++ ) + std::unique_ptr< QgsDiagramSettings> ds = std::make_unique< QgsDiagramSettings >(); + ds->stackedDiagramMode = static_cast( mStackedDiagramModeComboBox->currentData().toInt() ); + ds->setStackedDiagramSpacingUnit( mStackedDiagramSpacingUnitComboBox->unit() ); + ds->setStackedDiagramSpacing( mStackedDiagramSpacingSpinBox->value() ); + + // Create diagram renderer for the StackedDiagram + QgsStackedDiagramRenderer *dr = new QgsStackedDiagramRenderer(); + dr->setDiagram( new QgsStackedDiagram() ); + + // Get DiagramSettings from each subdiagram + const QList< QgsDiagramRenderer *> renderers = mModel->subRenderers(); + for ( const auto &renderer : renderers ) { - mSubDiagramsTabWidget->setTabText( i, tr( "Diagram %1" ).arg( i + 1 ) ); + const QList< QgsDiagramSettings > ds1 = renderer->diagramSettings(); + if ( !ds1.isEmpty() ) + { + ds->categoryAttributes += ds1.at( 0 ).categoryAttributes; + ds->categoryLabels += ds1.at( 0 ).categoryLabels; + ds->categoryColors += ds1.at( 0 ).categoryColors; + } + dr->addRenderer( renderer ); } + + dr->setDiagramSettings( *ds ); + mLayer->setDiagramRenderer( dr ); + + // Get DiagramLayerSettings from the model + QgsDiagramLayerSettings dls = mModel->diagramLayerSettings(); + mLayer->setDiagramLayerSettings( dls ); + + // refresh + QgsProject::instance()->setDirty( true ); + mLayer->triggerRepaint(); } -void QgsStackedDiagramProperties::apply() +bool QgsStackedDiagramProperties::couldBeFirstSubDiagram( const QModelIndex &index ) const { - if ( mDiagramTypeComboBox->currentData( Qt::UserRole ) == "None" ) - { - std::unique_ptr< QgsDiagramRenderer > renderer; - mLayer->setDiagramRenderer( renderer.release() ); + if ( !index.isValid() ) + return false; - QgsDiagramLayerSettings dls; - mLayer->setDiagramLayerSettings( dls ); + if ( mModel->rowCount() == 1 ) + return true; - // refresh - QgsProject::instance()->setDirty( true ); - mLayer->triggerRepaint(); - } - else if ( mDiagramTypeComboBox->currentData( Qt::UserRole ) == QgsDiagramLayerSettings::Single ) - { - static_cast( mSubDiagramsTabWidget->widget( 0 ) )->apply(); - } - else // Stacked diagram + // Is there any enabled subdiagram before our index.row()? + // If so, ours cannot be the first diagram. + const QList< QgsDiagramRenderer * > renderers = mModel->subRenderers(); + + for ( int i = 0; i < index.row(); i++ ) { - // Create diagram settings for the StackedDiagram - std::unique_ptr< QgsDiagramSettings> ds = std::make_unique< QgsDiagramSettings >(); - ds->stackedDiagramMode = static_cast( mStackedDiagramModeComboBox->currentData().toInt() ); - ds->setStackedDiagramSpacingUnit( mStackedDiagramSpacingUnitComboBox->unit() ); - ds->setStackedDiagramSpacing( mStackedDiagramSpacingSpinBox->value() ); - - // Create diagram renderer for the StackedDiagram - QgsStackedDiagramRenderer *dr = new QgsStackedDiagramRenderer(); - dr->setDiagram( new QgsStackedDiagram() ); - - // Get DiagramSettings from each subdiagram - for ( int i = 0; i < mSubDiagramsTabWidget->count(); i++ ) + const auto &renderer = renderers.at( i ); + const QList< QgsDiagramSettings > ds = renderer->diagramSettings(); + if ( !ds.isEmpty() && ds.at( 0 ).enabled ) { - QgsDiagramProperties *diagramProperties = static_cast( mSubDiagramsTabWidget->widget( i ) ); - std::unique_ptr< QgsDiagramSettings > ds1 = diagramProperties->createDiagramSettings(); - ds->categoryAttributes += ds1->categoryAttributes; - ds->categoryLabels += ds1->categoryLabels; - ds->categoryColors += ds1->categoryColors; + // First enabled subdiagram found, and we know our row is after. + // Therefore, return false to disallow showing DLS settings for it. + return false; + } + } + // Either our row is the first subdiagram enabled or it's disabled, + // but there are no enabled ones before. So, ours could be the first + // enabled one after being edited. + // Therefore, we should allow DLS settings on its corresponding widget. + return true; +} - std::unique_ptr< QgsDiagramRenderer > dr1 = diagramProperties->createRendererBaseInfo( *ds1 ); +void QgsStackedDiagramProperties::subDiagramWidgetPanelAccepted( QgsPanelWidget *panel ) +{ + QgsDiagramProperties *widget = qobject_cast( panel ); - std::unique_ptr< QgsDiagram > diagram; + std::unique_ptr< QgsDiagramRenderer > renderer = widget->createRenderer(); - if ( diagramProperties->mDiagramType == DIAGRAM_NAME_TEXT ) - { - diagram = std::make_unique< QgsTextDiagram >(); - } - else if ( diagramProperties->mDiagramType == DIAGRAM_NAME_PIE ) - { - diagram = std::make_unique< QgsPieDiagram >(); - } - else if ( diagramProperties->mDiagramType == DIAGRAM_NAME_STACKED_BAR ) - { - diagram = std::make_unique< QgsStackedBarDiagram >(); - } - else // DIAGRAM_NAME_HISTOGRAM - { - diagram = std::make_unique< QgsHistogramDiagram >(); - } + const QModelIndex index = mSubDiagramsView->selectionModel()->currentIndex(); + if ( widget->isAllowedToEditDiagramLayerSettings() ) + mModel->updateDiagramLayerSettings( widget->createDiagramLayerSettings() ); - dr1->setDiagram( diagram.release() ); - dr->addRenderer( dr1.release() ); - } + mModel->updateSubDiagram( index, renderer.release() ); +} - dr->setDiagramSettings( *ds ); - mLayer->setDiagramRenderer( dr ); +void QgsStackedDiagramProperties::liveUpdateSubDiagramFromPanel() +{ + subDiagramWidgetPanelAccepted( qobject_cast( sender() ) ); +} - // Create DiagramLayerSettings from first diagram - QgsDiagramProperties *firstDiagramProperties = static_cast< QgsDiagramProperties * >( mSubDiagramsTabWidget->widget( 0 ) ); - QgsDiagramLayerSettings dls = firstDiagramProperties->createDiagramLayerSettings(); - mLayer->setDiagramLayerSettings( dls ); +//// - // refresh - QgsProject::instance()->setDirty( true ); - mLayer->triggerRepaint(); - } +#include "qgsvscrollarea.h" + +QgsStackedDiagramPropertiesDialog::QgsStackedDiagramPropertiesDialog( QgsVectorLayer *layer, QWidget *parent, QgsMapCanvas *mapCanvas ) + : QDialog( parent ) +{ + +#ifdef Q_OS_MAC + setWindowModality( Qt::WindowModal ); +#endif + + QVBoxLayout *layout = new QVBoxLayout( this ); + QgsVScrollArea *scrollArea = new QgsVScrollArea( this ); + scrollArea->setFrameShape( QFrame::NoFrame ); + layout->addWidget( scrollArea ); + + buttonBox = new QDialogButtonBox( QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::Ok ); + mPropsWidget = new QgsDiagramProperties( layer, this, mapCanvas ); + mPropsWidget->setDockMode( false ); + + scrollArea->setWidget( mPropsWidget ); + layout->addWidget( buttonBox ); + this->setWindowTitle( "Edit Sub Diagram" ); + QgsGui::enableAutoGeometryRestore( this ); + + connect( buttonBox, &QDialogButtonBox::accepted, this, &QgsStackedDiagramPropertiesDialog::accept ); + connect( buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject ); + connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsStackedDiagramPropertiesDialog::showHelp ); } -void QgsStackedDiagramProperties::mDiagramTypeComboBox_currentIndexChanged( int index ) +void QgsStackedDiagramPropertiesDialog::syncToRenderer( const QgsDiagramRenderer *dr ) const { - if ( index == 0 ) // No diagram - { - mDiagramsFrame->setEnabled( false ); - mStackedDiagramSettingsFrame->hide(); + mPropsWidget->syncToRenderer( dr ); +} - // Hide tabs other than the first one - for ( int i = 0; i < mSubDiagramsTabWidget->count(); i++ ) - { - if ( i < 1 ) - continue; +void QgsStackedDiagramPropertiesDialog::syncToSettings( const QgsDiagramLayerSettings *dls ) const +{ + mPropsWidget->syncToSettings( dls ); +} - mSubDiagramsTabWidget->setTabVisible( i, false ); +void QgsStackedDiagramPropertiesDialog::accept() +{ + // Get renderer and diagram layer settings from widget + mRenderer = mPropsWidget->createRenderer(); + mDiagramLayerSettings = mPropsWidget->createDiagramLayerSettings(); + QDialog::accept(); +} + +QgsDiagramRenderer *QgsStackedDiagramPropertiesDialog::renderer() +{ + return mRenderer.release(); +} + +QgsDiagramLayerSettings QgsStackedDiagramPropertiesDialog::diagramLayerSettings() const +{ + return mDiagramLayerSettings; +} + +void QgsStackedDiagramPropertiesDialog::setAllowedToEditDiagramLayerSettings( bool allowed ) const +{ + mPropsWidget->setAllowedToEditDiagramLayerSettings( allowed ); +} + +bool QgsStackedDiagramPropertiesDialog::isAllowedToEditDiagramLayerSettings() const +{ + return mPropsWidget->isAllowedToEditDiagramLayerSettings(); +} + +void QgsStackedDiagramPropertiesDialog::showHelp() +{ + QgsHelp::openHelp( QStringLiteral( "working_with_vector/vector_properties.html#diagrams-properties" ) ); +} + +//// + +QgsStackedDiagramPropertiesModel::QgsStackedDiagramPropertiesModel( QObject *parent ) + : QAbstractTableModel( parent ) +{ +} + +Qt::ItemFlags QgsStackedDiagramPropertiesModel::flags( const QModelIndex &index ) const +{ + const Qt::ItemFlag checkable = ( index.column() == 0 ? Qt::ItemIsUserCheckable : Qt::NoItemFlags ); + + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | checkable; +} + +QVariant QgsStackedDiagramPropertiesModel::data( const QModelIndex &index, int role ) const +{ + if ( !index.isValid() ) + return QVariant(); + + QgsDiagramRenderer *dr = subDiagramForIndex( index ); + + if ( role == Qt::DisplayRole || role == Qt::ToolTipRole ) + { + switch ( index.column() ) + { + case 1: + return ( !dr || !dr->diagram() ) ? tr( "(no diagram)" ) : dr->diagram()->diagramName(); + case 2: + if ( !dr ) + { + return tr( "(no renderer)" ); + } + else + { + if ( dr->rendererName() == QLatin1String( "SingleCategory" ) ) + return tr( "Fixed" ); + else if ( dr->rendererName() == QLatin1String( "LinearlyInterpolated" ) ) + return tr( "Scaled" ); + else + return tr( "Unknown" ); + } + case 3: + if ( dr && dr->diagram() && !dr->diagramSettings().isEmpty() ) + { + if ( DIAGRAM_NAME_HISTOGRAM == dr->diagram()->diagramName() || DIAGRAM_NAME_STACKED_BAR == dr->diagram()->diagramName() ) + { + switch ( dr->diagramSettings().at( 0 ).diagramOrientation ) + { + case QgsDiagramSettings::Left: + return tr( "Left" ); + case QgsDiagramSettings::Right: + return tr( "Right" ); + case QgsDiagramSettings::Up: + return tr( "Up" ); + case QgsDiagramSettings::Down: + return tr( "Down" ); + } + } + } + return QVariant(); + case 0: + default: + return QVariant(); } } - else if ( index == 1 ) // Single + else if ( role == Qt::TextAlignmentRole ) { - mDiagramsFrame->setEnabled( true ); - mStackedDiagramSettingsFrame->hide(); - - // Hide tabs other than the first one - for ( int i = 0; i < mSubDiagramsTabWidget->count(); i++ ) - { - if ( i < 1 ) - continue; + return index.column() == 0 ? static_cast( Qt::AlignCenter ) : static_cast( Qt::AlignLeft ); + } + else if ( role == Qt::CheckStateRole ) + { + if ( index.column() != 0 ) + return QVariant(); - mSubDiagramsTabWidget->setTabVisible( i, false ); - } + return ( dr && !dr->diagramSettings().isEmpty() && dr->diagramSettings().at( 0 ).enabled ) ? Qt::Checked : Qt::Unchecked; } - else // Stacked + else { - mDiagramsFrame->setEnabled( true ); - mStackedDiagramSettingsFrame->show(); + return QVariant(); + } +} - // Create the second tab or show all hidden tabs - if ( mSubDiagramsTabWidget->count() == 1 ) - { - // Add second subdiagram tab - addSubDiagram(); - } - else +QVariant QgsStackedDiagramPropertiesModel::headerData( int section, Qt::Orientation orientation, int role ) const +{ + if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= 0 && section < 4 ) + { + QStringList lst; + lst << tr( "Enabled" ) << tr( "Diagram type" ) << tr( "Size" ) << tr( "Orientation" ); + return lst[section]; + } + + return QVariant(); +} + +int QgsStackedDiagramPropertiesModel::rowCount( const QModelIndex & ) const +{ + return mRenderers.size(); +} + +int QgsStackedDiagramPropertiesModel::columnCount( const QModelIndex & ) const +{ + return 4; +} + +bool QgsStackedDiagramPropertiesModel::setData( const QModelIndex &index, const QVariant &value, int role ) +{ + if ( !index.isValid() ) + return false; + + QgsDiagramRenderer *dr = subDiagramForIndex( index ); + + if ( role == Qt::CheckStateRole ) + { + if ( dr && !dr->diagramSettings().isEmpty() ) { - for ( int i = 0; i < mSubDiagramsTabWidget->count(); i++ ) - { - if ( i < 1 ) - continue; + QgsDiagramSettings ds = dr->diagramSettings().at( 0 ); + ds.enabled = ( value.toInt() == Qt::Checked ); - mSubDiagramsTabWidget->setTabVisible( i, true ); + if ( dr->rendererName() == QLatin1String( "SingleCategory" ) ) + { + QgsSingleCategoryDiagramRenderer *dsr = static_cast< QgsSingleCategoryDiagramRenderer * >( dr ); + dsr->setDiagramSettings( ds ); } + else + { + QgsLinearlyInterpolatedDiagramRenderer *dlir = static_cast< QgsLinearlyInterpolatedDiagramRenderer * >( dr ); + dlir->setDiagramSettings( ds ); + } + + emit dataChanged( index, index ); + return true; } } + return false; } -void QgsStackedDiagramProperties::mEngineSettingsButton_clicked() +bool QgsStackedDiagramPropertiesModel::removeRows( int row, int count, const QModelIndex &parent ) { - QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ); - if ( panel && panel->dockMode() ) - { - QgsLabelEngineConfigWidget *widget = new QgsLabelEngineConfigWidget( mMapCanvas ); - connect( widget, &QgsLabelEngineConfigWidget::widgetChanged, widget, &QgsLabelEngineConfigWidget::apply ); - panel->openPanel( widget ); - } - else - { - QgsLabelEngineConfigDialog dialog( mMapCanvas, this ); - dialog.exec(); - // reactivate button's window - activateWindow(); - } + if ( row < 0 || row >= mRenderers.size() ) + return false; + + beginRemoveRows( parent, row, row + count - 1 ); + while ( count-- ) + mRenderers.removeAt( row ); + endRemoveRows(); + + return true; +} + +QgsDiagramRenderer *QgsStackedDiagramPropertiesModel::subDiagramForIndex( const QModelIndex &index ) const +{ + if ( index.isValid() ) + return mRenderers.at( index.row() ); + return nullptr; +} + +void QgsStackedDiagramPropertiesModel::insertSubDiagram( const int index, QgsDiagramRenderer *newSubDiagram ) +{ + beginInsertRows( QModelIndex(), index, index ); + mRenderers.insert( index, newSubDiagram ); + endInsertRows(); +} + +void QgsStackedDiagramPropertiesModel::updateSubDiagram( const QModelIndex &index, QgsDiagramRenderer *dr ) +{ + mRenderers.replace( index.row(), dr ); + emit dataChanged( index, index ); +} + +QList< QgsDiagramRenderer *> QgsStackedDiagramPropertiesModel::subRenderers() const +{ + QList subRenderers; + subRenderers = mRenderers; + return subRenderers; +} + +void QgsStackedDiagramPropertiesModel::updateDiagramLayerSettings( QgsDiagramLayerSettings dls ) +{ + mDiagramLayerSettings = dls; +} + +QgsDiagramLayerSettings QgsStackedDiagramPropertiesModel::diagramLayerSettings() const +{ + return mDiagramLayerSettings; } diff --git a/src/gui/vector/qgsstackeddiagramproperties.h b/src/gui/vector/qgsstackeddiagramproperties.h index 29cc4c0ae52a..d80339150bb6 100644 --- a/src/gui/vector/qgsstackeddiagramproperties.h +++ b/src/gui/vector/qgsstackeddiagramproperties.h @@ -22,14 +22,71 @@ #define SIP_NO_FILE #include "qgis_gui.h" +#include "qgsdiagramrenderer.h" #include "ui_qgsstackeddiagrampropertiesbase.h" #include #include +#include class QgsVectorLayer; class QgsMapCanvas; class QgsDiagramProperties; +class QgsDiagramRenderer; + + +/** + * \ingroup gui + * \brief Model for sub diagrams in a stacked diagram view. + * + * \note This class is not a part of public API + * \since QGIS 3.40 + */ +class GUI_EXPORT QgsStackedDiagramPropertiesModel : public QAbstractTableModel +{ + Q_OBJECT + + public: + //! constructor + QgsStackedDiagramPropertiesModel( QObject *parent = nullptr ); + + Qt::ItemFlags flags( const QModelIndex &index ) const override; + QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override; + QVariant headerData( int section, Qt::Orientation orientation, + int role = Qt::DisplayRole ) const override; + int rowCount( const QModelIndex & = QModelIndex() ) const override; + int columnCount( const QModelIndex & = QModelIndex() ) const override; + + // editing support + bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ) override; + bool removeRows( int row, int count, const QModelIndex &parent = QModelIndex() ) override; + + // new methods + + //! Returns the diagram renderer at the specified index + QgsDiagramRenderer *subDiagramForIndex( const QModelIndex &index ) const; + + //! Inserts a new diagram at the specified position + void insertSubDiagram( const int index, QgsDiagramRenderer *newSubDiagram ); + //! Replaces the diagram located at \a index by \a dr + void updateSubDiagram( const QModelIndex &index, QgsDiagramRenderer *dr ); + + //! Returns the list of diagram renderers from the model + QList< QgsDiagramRenderer *> subRenderers() const; + + //! Returns the diagram layer settings from the model + QgsDiagramLayerSettings diagramLayerSettings() const; + + /** + * Sets the diagram layer settings for the model. + * @param dls DiagramLayerSettings to be set. + */ + void updateDiagramLayerSettings( QgsDiagramLayerSettings dls ); + + protected: + QList< QgsDiagramRenderer *> mRenderers; + QgsDiagramLayerSettings mDiagramLayerSettings; +}; /** @@ -38,7 +95,7 @@ class QgsDiagramProperties; * * \since QGIS 3.40 */ -class GUI_EXPORT QgsStackedDiagramProperties : public QWidget, private Ui::QgsStackedDiagramPropertiesBase +class GUI_EXPORT QgsStackedDiagramProperties : public QgsPanelWidget, private Ui::QgsStackedDiagramPropertiesBase { Q_OBJECT @@ -55,27 +112,127 @@ class GUI_EXPORT QgsStackedDiagramProperties : public QWidget, private Ui::QgsSt public slots: void apply(); - void mDiagramTypeComboBox_currentIndexChanged( int index ); - void mSubDiagramsTabWidget_tabMoved( int from, int to ); - void mEngineSettingsButton_clicked(); private slots: /** - * Adds a diagram tab to the current QgsStackedDiagramProperties. + * Adds a diagram to the current QgsStackedDiagramProperties. */ void addSubDiagram(); /** - * Removes a diagram tab from the current QgsStackedDiagramProperties. - * Diagram tabs are removed only if the tab count is greater than 2. - * Tab texts are adjusted after tab removal, to keep sequential order. + * Appends a diagram to the current QgsStackedDiagramProperties. + * @param dr Diagram renderer to be appended. + */ + void appendSubDiagram( QgsDiagramRenderer *dr ); + + /** + * Edits the properties of the current diagram. + */ + void editSubDiagram(); + + /** + * Edits the properties of a diagram located at a given \a index. + * @param index Model index where the diagram is located. + */ + void editSubDiagram( const QModelIndex &index ); + + /** + * Removes a diagram from the current QgsStackedDiagramProperties. */ void removeSubDiagram(); private: QgsVectorLayer *mLayer = nullptr; QgsMapCanvas *mMapCanvas = nullptr; + + QgsStackedDiagramPropertiesModel *mModel = nullptr; + + /** + * Determines whether the subdiagram in the given \a index may be + * the first sub diagram in the stacked diagram. This includes the + * first enabled sub diagram, as well as disabled sub diagrams that, + * after being edited, can become the first enabled one. + * @param index Model index where the sub diagram is located. + */ + bool couldBeFirstSubDiagram( const QModelIndex &index ) const; + + private slots: + void subDiagramWidgetPanelAccepted( QgsPanelWidget *panel ); + void liveUpdateSubDiagramFromPanel(); +}; + +/** + * \ingroup gui + * \class QgsStackedDiagramPropertiesDialog + * \brief Dialog for editing sub diagrams + * + * \note This class is not a part of public API + * \since QGIS 3.40 + */ +class GUI_EXPORT QgsStackedDiagramPropertiesDialog : public QDialog +{ + Q_OBJECT + + public: + + /** + * Constructor for QgsStackedDiagramPropertiesDialog + * \param layer source vector layer + * \param parent parent widget + * \param mapCanvas map canvas + */ + QgsStackedDiagramPropertiesDialog( QgsVectorLayer *layer, QWidget *parent = nullptr, QgsMapCanvas *mapCanvas = nullptr ); + + /** + * Delegates to the diagram properties widget to sync with the given renderer. + * @param dr Diagram Renderer to be used for the sync. + */ + void syncToRenderer( const QgsDiagramRenderer *dr ) const; + + /** + * Delegates to the diagram properties widget to sync with the given diagram layer settings. + * @param dls Diagram Layer Settings to be used for the sync. + */ + void syncToSettings( const QgsDiagramLayerSettings *dls ) const; + + /** + * Gets a renderer object built from the diagram properties widget. + */ + QgsDiagramRenderer *renderer(); + + /** + * Gets diagram layer settings built from the diagram properties widget. + */ + QgsDiagramLayerSettings diagramLayerSettings() const; + + /** + * Delegates to the main widget to set whether the widget should show + * diagram layer settings to be edited. + * @param allowed Whether the main widget should be allowed to edit diagram layer settings. + */ + void setAllowedToEditDiagramLayerSettings( bool allowed ) const; + + /** + * Returns whether the main widget is allowed to edit diagram layer settings. + */ + bool isAllowedToEditDiagramLayerSettings() const; + + public slots: + + /** + * Applies changes from the widget to the internal renderer and diagram layer settings. + */ + void accept() override; + + private slots: + void showHelp(); + + private: + QgsDiagramProperties *mPropsWidget = nullptr; + std::unique_ptr< QgsDiagramRenderer > mRenderer; + QgsDiagramLayerSettings mDiagramLayerSettings; + QDialogButtonBox *buttonBox = nullptr; }; #endif // QGSSTACKEDDIAGRAMPROPERTIES_H diff --git a/src/gui/vector/qgsvectorlayerproperties.cpp b/src/gui/vector/qgsvectorlayerproperties.cpp index 6cfa361a87bd..ead248999eaa 100644 --- a/src/gui/vector/qgsvectorlayerproperties.cpp +++ b/src/gui/vector/qgsvectorlayerproperties.cpp @@ -26,7 +26,7 @@ #include "qgsapplication.h" #include "qgsattributeactiondialog.h" #include "qgsdatumtransformdialog.h" -#include "qgsstackeddiagramproperties.h" +#include "qgsdiagramwidget.h" #include "qgssourcefieldsproperties.h" #include "qgsattributesformproperties.h" #include "qgslabelingwidget.h" @@ -280,6 +280,15 @@ QgsVectorLayerProperties::QgsVectorLayerProperties( mSelectionSymbolButton->setEnabled( false ); mRadioDefaultSelectionColor->setChecked( true ); + // Diagram tab, before the syncToLayer + QVBoxLayout *diagLayout = new QVBoxLayout( mDiagramFrame ); + diagLayout->setContentsMargins( 0, 0, 0, 0 ); + diagramPropertiesDialog = new QgsDiagramWidget( mLayer, mCanvas, mDiagramFrame ); + diagramPropertiesDialog->layout()->setContentsMargins( 0, 0, 0, 0 ); + connect( diagramPropertiesDialog, &QgsDiagramWidget::auxiliaryFieldCreated, this, [ = ] { updateAuxiliaryStoragePage(); } ); + diagLayout->addWidget( diagramPropertiesDialog ); + mDiagramFrame->setLayout( diagLayout ); + syncToLayer(); if ( mLayer->dataProvider() ) @@ -333,14 +342,6 @@ QgsVectorLayerProperties::QgsVectorLayerProperties( mOldJoins = mLayer->vectorJoins(); - QVBoxLayout *diagLayout = new QVBoxLayout( mDiagramFrame ); - diagLayout->setContentsMargins( 0, 0, 0, 0 ); - diagramPropertiesDialog = new QgsStackedDiagramProperties( mLayer, mDiagramFrame, mCanvas ); - diagramPropertiesDialog->layout()->setContentsMargins( 0, 0, 0, 0 ); - connect( diagramPropertiesDialog, &QgsStackedDiagramProperties::auxiliaryFieldCreated, this, [ = ] { updateAuxiliaryStoragePage(); } ); - diagLayout->addWidget( diagramPropertiesDialog ); - mDiagramFrame->setLayout( diagLayout ); - // Legend tab mLegendWidget->setMapCanvas( mCanvas ); mLegendWidget->setLayer( mLayer ); @@ -728,7 +729,7 @@ void QgsVectorLayerProperties::syncToLayer() updateVariableEditor(); if ( diagramPropertiesDialog ) - diagramPropertiesDialog->syncToLayer(); + diagramPropertiesDialog->syncToOwnLayer(); // sync all plugin dialogs for ( QgsMapLayerConfigWidget *page : std::as_const( mConfigWidgets ) ) diff --git a/src/gui/vector/qgsvectorlayerproperties.h b/src/gui/vector/qgsvectorlayerproperties.h index 1b8c8b1c50a7..70cf23d77c04 100644 --- a/src/gui/vector/qgsvectorlayerproperties.h +++ b/src/gui/vector/qgsvectorlayerproperties.h @@ -33,7 +33,7 @@ class QgsMapLayer; class QgsAttributeActionDialog; class QgsVectorLayer; class QgsLabelingWidget; -class QgsStackedDiagramProperties; +class QgsDiagramWidget; class QgsSourceFieldsProperties; class QgsAttributesFormProperties; class QgsRendererPropertiesDialog; @@ -172,7 +172,7 @@ class GUI_EXPORT QgsVectorLayerProperties : public QgsLayerPropertiesDialog, pri //! Actions dialog. If apply is pressed, the actions are stored for later use QgsAttributeActionDialog *mActionDialog = nullptr; //! Diagram dialog. If apply is pressed, options are applied to vector's diagrams - QgsStackedDiagramProperties *diagramPropertiesDialog = nullptr; + QgsDiagramWidget *diagramPropertiesDialog = nullptr; //! SourceFields dialog. If apply is pressed, options are applied to vector's diagrams QgsSourceFieldsProperties *mSourceFieldsPropertiesDialog = nullptr; //! AttributesForm dialog. If apply is pressed, options are applied to vector's diagrams diff --git a/src/ui/qgsdiagrampropertiesbase.ui b/src/ui/qgsdiagrampropertiesbase.ui index 26af8901123d..316255721547 100644 --- a/src/ui/qgsdiagrampropertiesbase.ui +++ b/src/ui/qgsdiagrampropertiesbase.ui @@ -10,7 +10,7 @@ 491 - + 0 @@ -51,13 +51,83 @@ + + + + 0 + + + + 20 + 20 + + + + true + + + + + :/images/themes/default/propertyicons/symbology.svg:/images/themes/default/propertyicons/symbology.svg + + + + + + + + + :/images/themes/default/propertyicons/render.svg:/images/themes/default/propertyicons/render.svg + + + + + + + + + :/images/themes/default/propertyicons/transparency.svg:/images/themes/default/propertyicons/transparency.svg + + + + + + + + + :/images/themes/default/propertyicons/labelplacement.svg:/images/themes/default/propertyicons/labelplacement.svg + + + + + + + + + :/images/themes/default/propertyicons/action.svg:/images/themes/default/propertyicons/action.svg + + + + + + + + + :/images/themes/default/legend.svg:/images/themes/default/legend.svg + + + + + + + - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -78,7 +148,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal false @@ -97,10 +167,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -204,10 +274,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -251,7 +321,7 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true @@ -261,11 +331,11 @@ 0 0 - 642 - 421 + 646 + 373 - + 0 @@ -289,8 +359,14 @@ + + + 0 + 0 + + - QAbstractItemView::ExtendedSelection + QAbstractItemView::SelectionMode::ExtendedSelection 0 @@ -317,11 +393,11 @@ - + - Qt::Vertical + Qt::Orientation::Vertical @@ -388,7 +464,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -411,6 +487,12 @@ + + + 0 + 0 + + Drag and drop to reorder @@ -418,13 +500,13 @@ true - QAbstractItemView::InternalMove + QAbstractItemView::DragDropMode::InternalMove - Qt::TargetMoveAction + Qt::DropAction::TargetMoveAction - QAbstractItemView::ExtendedSelection + QAbstractItemView::SelectionMode::ExtendedSelection 0 @@ -493,7 +575,7 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true @@ -502,12 +584,12 @@ 0 - 0 - 630 - 535 + -298 + 632 + 671 - + 0 @@ -591,7 +673,7 @@ - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus @@ -696,7 +778,7 @@ - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus @@ -756,7 +838,7 @@ - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus @@ -839,154 +921,172 @@ labelrenderinggroup - - - - - + + + + + + 0 + 0 + + + + Controls how diagrams are drawn on top of each other. Diagrams with a higher z-index are drawn above diagrams and labels with a lower z-index. + + + -9999999.000000000000000 + + + 9999999.000000000000000 + + + + + + + Diagram z-index + + + + + + + Scale dependent visibility + + + true + + + + 9 + + + 9 + + + + + + + + + + + + + + + + + + Always show all diagrams, even when they overlap with each other or other map labels + + + Show all diagrams + + + true + + + + + + + + + true - - - - - + 0 0 - Controls how diagrams are drawn on top of each other. Diagrams with a higher z-index are drawn above diagrams and labels with a lower z-index. + Controls whether specific diagrams should be shown - - -9999999.000000000000000 - - - 9999999.000000000000000 + + Show diagram - - + + - Diagram z-index + - - - - - - true - - - - 0 - 0 - - - - Controls whether specific diagrams should be shown - - - Show diagram - - - - - - - - - - - - - - Qt::Vertical - - - - - - - - 0 - 0 - - - - Controls whether specific diagrams should always be rendered, even when they overlap other diagrams or map labels - - - Always show - - - - - - - - - - - - - - Qt::Horizontal - - - - 195 - 20 - - - - - - - - - - Scale dependent visibility - - - true + + + + Qt::Orientation::Vertical - - - 9 - - - 9 - - - - - - - + + + + + 0 + 0 + + - Always show all diagrams, even when they overlap with each other or other map labels + Controls whether specific diagrams should always be rendered, even when they overlap other diagrams or map labels - Show all diagrams + Always show - - true + + + + + + + + + + Qt::Orientation::Horizontal + + + + 195 + 20 + + + + + + + + + + true + + + + color: rgb(220, 125, 0); + + + z-index and show-all-diagrams settings can be configured in the first enabled sub diagram of the stacked diagram. + + + true + + + + - Qt::Vertical + Qt::Orientation::Vertical @@ -1029,7 +1129,7 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true @@ -1039,11 +1139,11 @@ 0 0 - 642 - 421 + 646 + 373 - + 0 @@ -1095,14 +1195,14 @@ - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus - Qt::Vertical + Qt::Orientation::Vertical @@ -1115,10 +1215,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -1141,6 +1241,9 @@ true + + 0 + @@ -1165,10 +1268,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -1330,7 +1433,7 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true @@ -1340,11 +1443,11 @@ 0 0 - 642 - 421 + 632 + 447 - + 0 @@ -1357,320 +1460,19 @@ 0 - - - - Coordinates - - - - - - - 0 - 0 - - - - X - - - - - - - - - - - - - - - 0 - 0 - - - - Y - - - - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - + + + + + 0 + 0 + - - - - - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - 0 - 0 - - - - Above line - - - true - - - - - - - Below line - - - - - - - - 0 - 0 - - - - On line - - - - - - - Line orientation dependent position - - - - - - - - - - - - Priority - - - labelplacementgroup - - - - 8 - - - 8 - - - - - Low - - - - - - - 10 - - - Qt::Horizontal - - - false - - - false - - - QSlider::TicksBelow - - - 1 - - - - - - - High - - - - - - - - - - - - - - - - - Obstacles - - - labelplacementgroup - - - - 8 - - - 8 - - - - - Discourage diagrams and labels from covering features - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - QFrame::NoFrame - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Distance - - - - - - - - - - 0 - 0 - - - - - - - - - - - - - - - - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Sunken + QFrame::Shadow::Sunken 0 @@ -1715,7 +1517,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1773,7 +1575,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1825,7 +1627,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1868,6 +1670,337 @@ + + + + + true + + + + color: rgb(220, 125, 0); + + + Placement settings can be configured in the first enabled sub diagram of the stacked diagram. + + + true + + + + + + + QFrame::Shape::NoFrame + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Distance + + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + Above line + + + true + + + + + + + Below line + + + + + + + + 0 + 0 + + + + On line + + + + + + + Line orientation dependent position + + + + + + + + + + + + Coordinates + + + + + + + 0 + 0 + + + + X + + + + + + + + + + + + + + + 0 + 0 + + + + Y + + + + + + + + + + + + + + Qt::Orientation::Horizontal + + + + 0 + 20 + + + + + + + + + + + Priority + + + labelplacementgroup + + + + 8 + + + 8 + + + + + Low + + + + + + + 10 + + + Qt::Orientation::Horizontal + + + false + + + false + + + QSlider::TickPosition::TicksBelow + + + 1 + + + + + + + High + + + + + + + + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + Obstacles + + + labelplacementgroup + + + + 8 + + + 8 + + + + + + 0 + 0 + + + + Discourage diagrams and labels from covering features + + + true + + + + + + + + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Expanding + + + + 40 + 20 + + + + + + + @@ -1901,7 +2034,7 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true @@ -1911,11 +2044,11 @@ 0 0 - 642 - 421 + 646 + 373 - + 0 @@ -1931,10 +2064,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -2005,10 +2138,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -2064,7 +2197,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2080,7 +2213,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -2120,7 +2253,7 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame true @@ -2130,11 +2263,11 @@ 0 0 - 341 - 81 + 646 + 373 - + @@ -2157,7 +2290,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2172,7 +2305,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -2200,18 +2333,6 @@ - - QgsScrollArea - QScrollArea -
qgsscrollarea.h
- 1 -
- - QgsCollapsibleGroupBox - QGroupBox -
qgscollapsiblegroupbox.h
- 1 -
QgsDoubleSpinBox QDoubleSpinBox @@ -2230,31 +2351,38 @@ 1 - QgsPropertyOverrideButton - QToolButton -
qgspropertyoverridebutton.h
+ QgsScrollArea + QScrollArea +
qgsscrollarea.h
+ 1
- QgsFieldExpressionWidget + QgsScaleRangeWidget QWidget -
qgsfieldexpressionwidget.h
- 1 +
qgsscalerangewidget.h
- QgsOpacityWidget + QgsEffectStackCompactWidget QWidget -
qgsopacitywidget.h
+
qgseffectstackpropertieswidget.h
1
- QgsFontButton + QgsPropertyOverrideButton QToolButton -
qgsfontbutton.h
+
qgspropertyoverridebutton.h
- QgsScaleRangeWidget + QgsCollapsibleGroupBox + QGroupBox +
qgscollapsiblegroupbox.h
+ 1 +
+ + QgsFieldExpressionWidget QWidget -
qgsscalerangewidget.h
+
qgsfieldexpressionwidget.h
+ 1
QgsSymbolButton @@ -2262,11 +2390,16 @@
qgssymbolbutton.h
- QgsEffectStackCompactWidget + QgsOpacityWidget QWidget -
qgseffectstackpropertieswidget.h
+
qgsopacitywidget.h
1
+ + QgsFontButton + QToolButton +
qgsfontbutton.h
+
mDiagramTypeComboBox @@ -2393,35 +2526,51 @@ + + mOptionsTab + currentChanged(int) + mDiagramStackedWidget + setCurrentIndex(int) + + + 343 + 56 + + + 364 + 286 + + + mIncreaseSmallDiagramsCheck toggled(bool) - mIncreaseMinimumSizeSpinBox + mIncreaseMinimumSizeLabel setEnabled(bool) - 248 - 341 + 296 + 330 - 589 - 342 + 395 + 332 mIncreaseSmallDiagramsCheck toggled(bool) - mIncreaseMinimumSizeLabel + mIncreaseMinimumSizeSpinBox setEnabled(bool) - 248 - 341 + 296 + 330 - 476 - 342 + 680 + 332 diff --git a/src/ui/qgsdiagramwidget.ui b/src/ui/qgsdiagramwidget.ui new file mode 100644 index 000000000000..b6f803546756 --- /dev/null +++ b/src/ui/qgsdiagramwidget.ui @@ -0,0 +1,59 @@ + + + QgsDiagramWidget + + + + 0 + 0 + 842 + 472 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + true + + + Automated placement settings (applies to all layers) + + + + :/images/themes/default/mIconAutoPlacementSettings.svg:/images/themes/default/mIconAutoPlacementSettings.svg + + + + + + + + + + + + + + + + + + diff --git a/src/ui/qgsstackeddiagrampropertiesbase.ui b/src/ui/qgsstackeddiagrampropertiesbase.ui index 9761615c8a28..df2cbfcedc4c 100644 --- a/src/ui/qgsstackeddiagrampropertiesbase.ui +++ b/src/ui/qgsstackeddiagrampropertiesbase.ui @@ -11,25 +11,106 @@
+ + 0 + + + 0 + + + 0 + + + 0 + - + + + QAbstractItemView::SelectionMode::SingleSelection + + + false + + + false + + + 57 + + + + + - + + + + 0 + 0 + + + + Add subdiagram to the stacked diagram + + + + + + + :/images/themes/default/symbologyAdd.svg:/images/themes/default/symbologyAdd.svg + + - - - true + + + Edit subdiagram from the stacked diagram + + + + + + + :/images/themes/default/symbologyEdit.svg:/images/themes/default/symbologyEdit.svg + + + + + + + + 0 + 0 + - Automated placement settings (apply to all layers) + Remove subdiagram from the stacked diagram + + + - :/images/themes/default/mIconAutoPlacementSettings.svg:/images/themes/default/mIconAutoPlacementSettings.svg + :/images/themes/default/symbologyRemove.svg:/images/themes/default/symbologyRemove.svg + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Expanding + + + + 40 + 20 + + + + @@ -41,80 +122,40 @@ QFrame::Shadow::Raised
- - + + - - - - 0 - 0 - - - - Add subdiagram to the stacked diagram - + - Add subdiagram - - - - :/images/themes/default/symbologyAdd.svg:/images/themes/default/symbologyAdd.svg + Diagram spacing - - - - 0 - 0 - - - - Remove subdiagram from the stacked diagram - - - Remove subdiagram - - - - :/images/themes/default/symbologyRemove.svg:/images/themes/default/symbologyRemove.svg + + + + 70 + 0 + - - - Qt::Orientation::Horizontal - - - QSizePolicy::Policy::Expanding - - + + - 40 - 20 + 120 + 0 - - - - - - - - - - Stacked Diagram Mode + + - - - - + Qt::Orientation::Horizontal @@ -128,40 +169,20 @@ - - + + - + - Diagram spacing + Stacked diagram mode - - - - 70 - 0 - - - - - - - - - 120 - 0 - - - - - - + - + Qt::Orientation::Horizontal @@ -178,43 +199,6 @@
- - - - QFrame::Shape::NoFrame - - - QFrame::Shadow::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - -1 - - - true - - - - - -
diff --git a/tests/code_layout/acceptable_missing_doc.py b/tests/code_layout/acceptable_missing_doc.py index 3ae65bc61dbb..c4c96cdae63d 100644 --- a/tests/code_layout/acceptable_missing_doc.py +++ b/tests/code_layout/acceptable_missing_doc.py @@ -286,7 +286,8 @@ "QgsEffectStack": ["QgsEffectStack(const QgsEffectStack &other)"], "QgsRelationReferenceWidget": ["setRelation(const QgsRelation &relation, bool allowNullValue)", "CanvasExtent", "setOpenFormButtonVisible(bool openFormButtonVisible)", "QgsRelationReferenceWidget(QWidget *parent)", "setReadOnlySelector(bool readOnly)", "setRelationEditable(bool editable)", "init()", "setAllowMapIdentification(bool allowMapIdentification)", "setEmbedForm(bool display)"], "QgsDiagramProperties": ["mAttributesTreeWidget_itemDoubleClicked(QTreeWidgetItem *item, int column)", "scalingTypeChanged()", "mAddCategoryPushButton_clicked()", "mEngineSettingsButton_clicked()", "apply()", "mRemoveCategoryPushButton_clicked()", "showSizeLegendDialog()", "QgsDiagramProperties(QgsVectorLayer *layer, QWidget *parent, QgsMapCanvas *canvas)", "mDiagramAttributesTreeWidget_itemDoubleClicked(QTreeWidgetItem *item, int column)", "mDiagramStackedWidget_currentChanged(int index)", "mDiagramTypeComboBox_currentIndexChanged(int index)", "mFindMaximumValueButton_clicked()", "showAddAttributeExpressionDialog()", "auxiliaryFieldCreated()", "updatePlacementWidgets()"], - "QgsStackedDiagramProperties": ["mDiagramTypeComboBox_currentIndexChanged(int index)", "apply()", "mEngineSettingsButton_clicked()", "mSubDiagramsTabWidget_tabMoved(int from, int to)", "auxiliaryFieldCreated()", "QgsStackedDiagramProperties(QgsVectorLayer *layer, QWidget *parent, QgsMapCanvas *canvas)"], + "QgsStackedDiagramProperties": ["apply()", "auxiliaryFieldCreated()", "QgsStackedDiagramProperties(QgsVectorLayer *layer, QWidget *parent, QgsMapCanvas *canvas)"], + "QgsDiagramWidget": ["mDiagramTypeComboBox_currentIndexChanged(int index)", "showEngineConfigDialog()"], "QgsFeatureListView": ["repaintRequested()", "repaintRequested(const QModelIndexList &indexes)"], "QgsGradientFillSymbolLayerWidget": ["setGradientSpread(int index)", "setColor(const QColor &color)", "setCoordinateMode(int index)", "setColor2(const QColor &color)", "setGradientType(int index)"], "QgsFillSymbol": ["setAngle(double angle) const"], diff --git a/tests/src/core/testqgsstackeddiagram.cpp b/tests/src/core/testqgsstackeddiagram.cpp index 7a50fd48b497..3a6080b8884b 100644 --- a/tests/src/core/testqgsstackeddiagram.cpp +++ b/tests/src/core/testqgsstackeddiagram.cpp @@ -191,6 +191,96 @@ class TestQgsStackedDiagram : public QgsTest QGSVERIFYRENDERMAPSETTINGSCHECK( "stackedhistograms", "stackedhistograms", *mMapSettings, 200, 15 ); } + void testDisabledSubDiagram() + { + // Histogram 1 (disabled) + QgsDiagramSettings ds1; + QColor col1 = Qt::blue; + QColor col2 = Qt::red; + QColor col3 = Qt::yellow; + QColor col4 = Qt::green; + col1.setAlphaF( 0.5 ); + col2.setAlphaF( 0.5 ); + col3.setAlphaF( 0.5 ); + col4.setAlphaF( 0.5 ); + ds1.categoryColors = QList() << col1 << col2 << col3 << col4; + ds1.categoryAttributes = QList() << QStringLiteral( "\"maennlich_ab_65\"" ) << QStringLiteral( "\"maennlich_18_64\"" ) << QStringLiteral( "\"maennlich_6_17\"" ) << QStringLiteral( "\"maennlich_unter_6\"" ); //#spellok + ds1.minimumScale = -1; + ds1.maximumScale = -1; + ds1.minimumSize = 0; + ds1.penColor = Qt::black; + ds1.penWidth = .5; + ds1.scaleByArea = true; + ds1.sizeType = Qgis::RenderUnit::Millimeters; + ds1.rotationOffset = 0; + ds1.diagramOrientation = QgsDiagramSettings::Left; + ds1.enabled = false; + + QgsLinearlyInterpolatedDiagramRenderer *dr1 = new QgsLinearlyInterpolatedDiagramRenderer(); + dr1->setDiagram( new QgsHistogramDiagram() ); + dr1->setDiagramSettings( ds1 ); + dr1->setLowerValue( 0.0 ); + dr1->setLowerSize( QSizeF( 0.0, 0.0 ) ); + dr1->setUpperValue( 15000 ); + dr1->setUpperSize( QSizeF( 20, 20 ) ); + //dr1->setClassificationField( QStringLiteral( "max(\"maennlich_18_64\", \"maennlich_ab_65\", \"maennlich_6_17\", \"maennlich_unter_6\")" ) ); //#spellok + + // Histogram 2 + QgsDiagramSettings ds2; + col1 = Qt::blue; + col2 = Qt::red; + col3 = Qt::yellow; + col4 = Qt::green; + col1.setAlphaF( 0.5 ); + col2.setAlphaF( 0.5 ); + col3.setAlphaF( 0.5 ); + col4.setAlphaF( 0.5 ); + ds2.categoryColors = QList() << col1 << col2 << col3 << col4; + ds2.categoryAttributes = QList() << QStringLiteral( "\"weiblich_ab_65\"" ) << QStringLiteral( "\"weiblich_18_64\"" ) << QStringLiteral( "\"weiblich_6_17\"" ) << QStringLiteral( "\"weiblich_unter_6\"" ); //#spellok + ds2.minimumScale = -1; + ds2.maximumScale = -1; + ds2.minimumSize = 0; + ds2.penColor = Qt::black; + ds2.penWidth = .5; + ds2.scaleByArea = true; + ds2.sizeType = Qgis::RenderUnit::Millimeters; + ds2.rotationOffset = 0; + ds2.diagramOrientation = QgsDiagramSettings::Right; + + QgsLinearlyInterpolatedDiagramRenderer *dr2 = new QgsLinearlyInterpolatedDiagramRenderer(); + dr2->setDiagram( new QgsHistogramDiagram() ); + dr2->setDiagramSettings( ds2 ); + dr2->setLowerValue( 0.0 ); + dr2->setLowerSize( QSizeF( 0.0, 0.0 ) ); + dr2->setUpperValue( 15000 ); + dr2->setUpperSize( QSizeF( 20, 20 ) ); + //dr2->setClassificationField( QStringLiteral( "max(\"weiblich_unter_6\", \"weiblich_6_17\", \"weiblich_18_64\", \"weiblich_ab_65\")" ) ); //#spellok + + QgsDiagramSettings ds; + ds.stackedDiagramMode = QgsDiagramSettings::Horizontal; + ds.categoryAttributes = ds1.categoryAttributes + ds2.categoryAttributes; + ds.setStackedDiagramSpacingUnit( Qgis::RenderUnit::Pixels ); + ds.setStackedDiagramSpacing( 0 ); + + QgsStackedDiagramRenderer *dr = new QgsStackedDiagramRenderer(); + dr->setDiagram( new QgsStackedDiagram() ); + dr->setDiagramSettings( ds ); + dr->addRenderer( dr1 ); + dr->addRenderer( dr2 ); + mPointsLayer->setDiagramRenderer( dr ); + + QgsDiagramLayerSettings dls = QgsDiagramLayerSettings(); + dls.setPlacement( QgsDiagramLayerSettings::OverPoint ); + dls.setShowAllDiagrams( true ); + mPointsLayer->setDiagramLayerSettings( dls ); + + const QgsRectangle extent( 9.7, 53.5, 9.95, 53.6 ); + mMapSettings->setExtent( extent ); + mMapSettings->setFlag( Qgis::MapSettingsFlag::ForceVectorOutput ); + mMapSettings->setOutputDpi( 96 ); + QGSVERIFYRENDERMAPSETTINGSCHECK( "disabledsubdiagram", "disabledsubdiagram", *mMapSettings, 200, 15 ); + } + void testVerticallyStackedHistograms() { // Histogram 1 diff --git a/tests/testdata/control_images/stackeddiagrams/expected_disabledsubdiagram/expected_disabledsubdiagram.png b/tests/testdata/control_images/stackeddiagrams/expected_disabledsubdiagram/expected_disabledsubdiagram.png new file mode 100644 index 000000000000..cf388a653596 Binary files /dev/null and b/tests/testdata/control_images/stackeddiagrams/expected_disabledsubdiagram/expected_disabledsubdiagram.png differ