Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[feature] When a group is selected in the layer tree, the layer
styling dock now shows an option to control the rendering
appearance of the entire group

By selecting "Render Layers as a Group", the whole set of layers
present in the group will be rendered as a flattened object during
map renders. This means that the opacity and blend mode assigned
to the group will apply to the flattened version of all the
child layers, instead of applying to each layer one-by-one.

This allows for styling results such as groups of layers which
obscure each other, but overall are rendered as semi-opaque.
  • Loading branch information
nyalldawson committed Nov 25, 2021
1 parent 027928a commit f91ace8
Show file tree
Hide file tree
Showing 5 changed files with 330 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/app/CMakeLists.txt
Expand Up @@ -45,6 +45,7 @@ set(QGIS_APP_SRCS
qgsfeatureaction.cpp
qgslayercapabilitiesmodel.cpp
qgslayernotesmanager.cpp
qgslayertreegrouppropertieswidget.cpp
qgslayertreeviewindicatorprovider.cpp
qgslayertreeviewembeddedindicator.cpp
qgslayertreeviewfilterindicator.cpp
Expand Down
2 changes: 2 additions & 0 deletions src/app/qgisapp.cpp
Expand Up @@ -252,6 +252,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "qgsdiagramproperties.h"
#include "qgslayerdefinition.h"
#include "qgslayertree.h"
#include "qgslayertreegrouppropertieswidget.h"
#include "qgslayertreemapcanvasbridge.h"
#include "qgslayertreemodel.h"
#include "qgslayertreemodellegendnode.h"
Expand Down Expand Up @@ -1428,6 +1429,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
#endif
registerMapLayerPropertiesFactory( new QgsPointCloudElevationPropertiesWidgetFactory( this ) );
registerMapLayerPropertiesFactory( new QgsAnnotationItemPropertiesWidgetFactory( this ) );
registerMapLayerPropertiesFactory( new QgsLayerTreeGroupPropertiesWidgetFactory( this ) );

activateDeactivateLayerRelatedActions( nullptr ); // after members were created

Expand Down
154 changes: 154 additions & 0 deletions src/app/qgslayertreegrouppropertieswidget.cpp
@@ -0,0 +1,154 @@
/***************************************************************************
qgslayertreegrouppropertieswidget.cpp
---------------------
begin : Nobember 2021
copyright : (C) 2021 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* 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 "qgslayertreegrouppropertieswidget.h"
#include "qgsstyle.h"
#include "qgsapplication.h"
#include "qgsmaplayer.h"
#include "qgsgui.h"
#include "qgspainteffect.h"
#include "qgsmapcanvas.h"

#include <QStackedWidget>
#include <QHBoxLayout>
#include <QLabel>

QgsLayerTreeGroupPropertiesWidget::QgsLayerTreeGroupPropertiesWidget( QgsMapCanvas *canvas, QWidget *parent )
: QgsMapLayerConfigWidget( nullptr, canvas, parent )
{
setupUi( this );

connect( mOpacityWidget, &QgsOpacityWidget::opacityChanged, this, &QgsLayerTreeGroupPropertiesWidget::onLayerPropertyChanged );
connect( mBlendModeComboBox, qOverload< int >( &QgsBlendModeComboBox::currentIndexChanged ), this, &QgsLayerTreeGroupPropertiesWidget::onLayerPropertyChanged );
// connect( mEffectWidget, &QgsEffectStackCompactWidget::changed, this, &QgsLayerTreeGroupPropertiesWidget::onLayerPropertyChanged );
connect( mRenderAsGroupCheckBox, &QGroupBox::toggled, this, &QgsLayerTreeGroupPropertiesWidget::onLayerPropertyChanged );

setDockMode( true );
}

QgsLayerTreeGroupPropertiesWidget::~QgsLayerTreeGroupPropertiesWidget() = default;

void QgsLayerTreeGroupPropertiesWidget::syncToLayer( QgsMapLayer * )
{
}

void QgsLayerTreeGroupPropertiesWidget::setMapLayerConfigWidgetContext( const QgsMapLayerConfigWidgetContext &context )
{
QgsMapLayerConfigWidget::setMapLayerConfigWidgetContext( context );
mLayerTreeGroup = context.layerTreeGroup();

mBlockLayerUpdates = true;
if ( QgsGroupLayer *groupLayer = mLayerTreeGroup->groupLayer() )
{
mRenderAsGroupCheckBox->setChecked( true );
mBlendModeComboBox->setBlendMode( groupLayer->blendMode() );
mOpacityWidget->setOpacity( groupLayer->opacity() );
if ( groupLayer->paintEffect() )
{
mPaintEffect.reset( groupLayer->paintEffect()->clone() );
//mEffectWidget->setPaintEffect( mPaintEffect.get() );
}
}
else
{
mRenderAsGroupCheckBox->setChecked( false );
}
mBlockLayerUpdates = false;
}

void QgsLayerTreeGroupPropertiesWidget::setDockMode( bool dockMode )
{
QgsMapLayerConfigWidget::setDockMode( dockMode );
}

void QgsLayerTreeGroupPropertiesWidget::apply()
{
if ( !mLayerTreeGroup )
return;

QgsGroupLayer *groupLayer = mLayerTreeGroup->groupLayer();
if ( groupLayer && !mRenderAsGroupCheckBox->isChecked() )
{
mLayerTreeGroup->setGroupLayer( nullptr );
QgsProject::instance()->removeMapLayer( groupLayer );
groupLayer = nullptr;
}
else if ( !groupLayer && mRenderAsGroupCheckBox->isChecked() )
{
QgsGroupLayer::LayerOptions options( QgsProject::instance()->transformContext() );
groupLayer = mLayerTreeGroup->convertToGroupLayer( options );
QgsProject::instance()->addMapLayer( groupLayer, false );
}

if ( groupLayer )
{
// set the blend mode and opacity for the layer
groupLayer->setBlendMode( mBlendModeComboBox->blendMode() );
groupLayer->setOpacity( mOpacityWidget->opacity() );
if ( mPaintEffect )
groupLayer->setPaintEffect( mPaintEffect->clone() );
groupLayer->triggerRepaint();
}
else if ( mMapLayerConfigWidgetContext.mapCanvas() )
{
mMapLayerConfigWidgetContext.mapCanvas()->refresh();
}
}

void QgsLayerTreeGroupPropertiesWidget::onLayerPropertyChanged()
{
if ( mBlockLayerUpdates )
return;

emit widgetChanged();
}


//
// QgsLayerTreeGroupPropertiesWidgetFactory
//

QgsLayerTreeGroupPropertiesWidgetFactory::QgsLayerTreeGroupPropertiesWidgetFactory( QObject *parent )
: QObject( parent )
{
setIcon( QgsApplication::getThemeIcon( QStringLiteral( "propertyicons/symbology.svg" ) ) );
setTitle( tr( "Group" ) );
}

QgsMapLayerConfigWidget *QgsLayerTreeGroupPropertiesWidgetFactory::createWidget( QgsMapLayer *, QgsMapCanvas *canvas, bool, QWidget *parent ) const
{
return new QgsLayerTreeGroupPropertiesWidget( canvas, parent );
}

bool QgsLayerTreeGroupPropertiesWidgetFactory::supportLayerPropertiesDialog() const
{
return false;
}

bool QgsLayerTreeGroupPropertiesWidgetFactory::supportsStyleDock() const
{
return true;
}

bool QgsLayerTreeGroupPropertiesWidgetFactory::supportsLayer( QgsMapLayer * ) const
{
return false;
}

bool QgsLayerTreeGroupPropertiesWidgetFactory::supportsLayerTreeGroup( QgsLayerTreeGroup * ) const
{
return true;
}

71 changes: 71 additions & 0 deletions src/app/qgslayertreegrouppropertieswidget.h
@@ -0,0 +1,71 @@
/***************************************************************************
qgslayertreegrouppropertieswidget.h
---------------------
begin : November 2021
copyright : (C) 2021 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* 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 QGSLAYERTREEGROUPPROPERTIESWIDGET_H
#define QGSLAYERTREEGROUPPROPERTIESWIDGET_H

#include "qgsmaplayerconfigwidget.h"
#include "qgsmaplayerconfigwidgetfactory.h"
#include "ui_qgslayertreegrouppropertieswidgetbase.h"
#include <QPointer>

class QgsAnnotationLayer;
class QgsAnnotationItemBaseWidget;
class QStackedWidget;

class QgsLayerTreeGroupPropertiesWidget : public QgsMapLayerConfigWidget, public Ui::QgsLayerTreeGroupPropertiesWidgetBase
{
Q_OBJECT
public:

QgsLayerTreeGroupPropertiesWidget( QgsMapCanvas *canvas, QWidget *parent );
~QgsLayerTreeGroupPropertiesWidget() override;

void syncToLayer( QgsMapLayer *layer ) override;
void setMapLayerConfigWidgetContext( const QgsMapLayerConfigWidgetContext &context ) override;
void setDockMode( bool dockMode ) override;

public slots:
void apply() override;

private slots:

void onLayerPropertyChanged();
private:

QPointer< QgsLayerTreeGroup > mLayerTreeGroup;
bool mBlockLayerUpdates = false;

std::unique_ptr< QgsPaintEffect > mPaintEffect;

};


class QgsLayerTreeGroupPropertiesWidgetFactory : public QObject, public QgsMapLayerConfigWidgetFactory
{
Q_OBJECT
public:
explicit QgsLayerTreeGroupPropertiesWidgetFactory( QObject *parent = nullptr );

QgsMapLayerConfigWidget *createWidget( QgsMapLayer *layer, QgsMapCanvas *canvas, bool dockWidget, QWidget *parent ) const override;
bool supportLayerPropertiesDialog() const override;
bool supportsStyleDock() const override;
bool supportsLayer( QgsMapLayer *layer ) const override;
bool supportsLayerTreeGroup( QgsLayerTreeGroup *group ) const override;
};



#endif // QGSLAYERTREEGROUPPROPERTIESWIDGET_H
102 changes: 102 additions & 0 deletions src/ui/qgslayertreegrouppropertieswidgetbase.ui
@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsLayerTreeGroupPropertiesWidgetBase</class>
<widget class="QWidget" name="QgsLayerTreeGroupPropertiesWidgetBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>424</width>
<height>702</height>
</rect>
</property>
<property name="windowTitle">
<string>Annotation Item Properties</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="mRenderAsGroupCheckBox">
<property name="title">
<string>Render Layers as a Group</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="lblTransparency">
<property name="text">
<string>Opacity</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="lblBlend">
<property name="text">
<string>Blending mode</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QgsBlendModeComboBox" name="mBlendModeComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>4</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QgsOpacityWidget" name="mOpacityWidget" native="true">
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsBlendModeComboBox</class>
<extends>QComboBox</extends>
<header>qgsblendmodecombobox.h</header>
</customwidget>
<customwidget>
<class>QgsOpacityWidget</class>
<extends>QWidget</extends>
<header>qgsopacitywidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

0 comments on commit f91ace8

Please sign in to comment.