Skip to content
Permalink
Browse files

[FEATURE] New decoration type for showing composer map extents

Adds a new (disabled by default) decoration item for showing
the extents of composer maps in the main canvas. When enabled,
the extents of all maps within all composers will be shown
using a lightly dotted border. The border symbol is configurable
for improved legibility against different map backgrounds.

This is useful when you're tweaking the positioning of map elements
such as labels, and need to know what the actual visible region
of composer maps are.
  • Loading branch information
nyalldawson committed Jun 1, 2017
1 parent 5077e12 commit 7921de326417b3f94bc1c24341a09612b07abb30
@@ -24,6 +24,8 @@ SET(QGIS_APP_SRCS
qgsdecorationitem.cpp
qgsdecorationcopyright.cpp
qgsdecorationcopyrightdialog.cpp
qgsdecorationlayoutextent.cpp
qgsdecorationlayoutextentdialog.cpp
qgsdecorationnortharrow.cpp
qgsdecorationnortharrowdialog.cpp
qgsdecorationscalebar.cpp
@@ -209,6 +211,8 @@ SET (QGIS_APP_MOC_HDRS
qgsdecorationitem.h
qgsdecorationcopyright.h
qgsdecorationcopyrightdialog.h
qgsdecorationlayoutextent.h
qgsdecorationlayoutextentdialog.h
qgsdecorationnortharrow.h
qgsdecorationnortharrowdialog.h
qgsdecorationscalebar.h
@@ -161,6 +161,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "qgsdecorationnortharrow.h"
#include "qgsdecorationscalebar.h"
#include "qgsdecorationgrid.h"
#include "qgsdecorationlayoutextent.h"
#include "qgsencodingfiledialog.h"
#include "qgserror.h"
#include "qgserrordialog.h"
@@ -3527,11 +3528,15 @@ void QgisApp::createDecorations()
QgsDecorationGrid *mDecorationGrid = new QgsDecorationGrid( this );
connect( mActionDecorationGrid, &QAction::triggered, mDecorationGrid, &QgsDecorationGrid::run );

QgsDecorationLayoutExtent *decorationLayoutExtent = new QgsDecorationLayoutExtent( this );
connect( mActionDecorationLayoutExtent, &QAction::triggered, decorationLayoutExtent, &QgsDecorationLayoutExtent::run );

// add the decorations in a particular order so they are rendered in that order
addDecorationItem( mDecorationGrid );
addDecorationItem( mDecorationCopyright );
addDecorationItem( mDecorationNorthArrow );
addDecorationItem( mDecorationScaleBar );
addDecorationItem( decorationLayoutExtent );
connect( mMapCanvas, &QgsMapCanvas::renderComplete, this, &QgisApp::renderDecorationItems );
connect( this, &QgisApp::newProject, this, &QgisApp::projectReadDecorationItems );
connect( this, &QgisApp::projectRead, this, &QgisApp::projectReadDecorationItems );
@@ -61,6 +61,8 @@ class APP_EXPORT QgsDecorationItem : public QObject, public QgsMapDecoration
*/
void setPlacement( Placement placement ) { mPlacement = placement; }

QString name() const { return mName; }

signals:
void toggled( bool t );

@@ -73,14 +75,13 @@ class APP_EXPORT QgsDecorationItem : public QObject, public QgsMapDecoration
//! Show the dialog box
virtual void run() {}

virtual void setName( const char *name );
virtual QString name() { return mName; }

//! Redraws the decoration
void update();

protected:

void setName( const char *name );

//! True if decoration item has to be displayed
bool mEnabled;

@@ -0,0 +1,153 @@
/***************************************************************************
qgsdecorationlayoutextent.cpp
----------------------
begin : May 2017
copyright : (C) 2017 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 "qgsdecorationlayoutextent.h"
#include "qgsdecorationlayoutextentdialog.h"

#include "qgslayoutmanager.h"
#include "qgscomposition.h"
#include "qgscomposermap.h"
#include "qgsgeometry.h"
#include "qgscsexception.h"
#include "qgslinesymbollayer.h"

#include "qgisapp.h"
#include "qgsapplication.h"
#include "qgslogger.h"
#include "qgsmapcanvas.h"
#include "qgsproject.h"
#include "qgssymbollayerutils.h"
#include "qgsreadwritecontext.h"

#include <QPainter>

QgsDecorationLayoutExtent::QgsDecorationLayoutExtent( QObject *parent )
: QgsDecorationItem( parent )
{
mPlacement = BottomRight;
mMarginUnit = QgsUnitTypes::RenderMillimeters;

setName( "Layout Extent" );
projectRead();
}

void QgsDecorationLayoutExtent::projectRead()
{
QgsDecorationItem::projectRead();

QDomDocument doc;
QDomElement elem;
QgsReadWriteContext rwContext;
rwContext.setPathResolver( QgsProject::instance()->pathResolver() );
QString xml = QgsProject::instance()->readEntry( mNameConfig, QStringLiteral( "/Symbol" ) );
mSymbol.reset( nullptr );
if ( !xml.isEmpty() )
{
doc.setContent( xml );
elem = doc.documentElement();
mSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( elem, rwContext ) );
}
if ( ! mSymbol )
{
mSymbol.reset( new QgsFillSymbol() );
QgsSimpleLineSymbolLayer *layer = new QgsSimpleLineSymbolLayer( QColor( 0, 0, 0, 100 ), 0, Qt::DashLine );
mSymbol->changeSymbolLayer( 0, layer );
}
}

void QgsDecorationLayoutExtent::saveToProject()
{
QgsDecorationItem::saveToProject();
// write symbol info to xml
QDomDocument doc;
QDomElement elem;
QgsReadWriteContext rwContext;
rwContext.setPathResolver( QgsProject::instance()->pathResolver() );
if ( mSymbol )
{
elem = QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "Symbol" ), mSymbol.get(), doc, rwContext );
doc.appendChild( elem );
// FIXME this works, but XML will not be valid as < is replaced by &lt;
QgsProject::instance()->writeEntry( mNameConfig, QStringLiteral( "/Symbol" ), doc.toString() );
}
}

void QgsDecorationLayoutExtent::run()
{
QgsDecorationLayoutExtentDialog dlg( *this, QgisApp::instance() );
dlg.exec();
}


void QgsDecorationLayoutExtent::render( const QgsMapSettings &mapSettings, QgsRenderContext &context )
{
if ( !enabled() )
return;

if ( !mSymbol )
return;

context.painter()->save();
context.painter()->setRenderHint( QPainter::Antialiasing, true );
mSymbol->startRender( context );

const QgsMapToPixel &m2p = mapSettings.mapToPixel();
QTransform transform = m2p.transform();

Q_FOREACH ( QgsComposition *composition, QgsProject::instance()->layoutManager()->compositions() )
{
Q_FOREACH ( const QgsComposerMap *map, composition->composerMapItems() )
{
QPolygonF extent = map->visibleExtentPolygon();
QgsGeometry g = QgsGeometry::fromQPolygonF( extent );

if ( map->crs() !=
mapSettings.destinationCrs() )
{
// reproject extent
QgsCoordinateTransform ct( map->crs(),
mapSettings.destinationCrs() );
g = g.densifyByCount( 20 );
try
{
g.transform( ct );
}
catch ( QgsCsException & )
{
}
}

g.transform( transform );
extent = g.asQPolygonF();
mSymbol->renderPolygon( extent, nullptr, nullptr, context );
}
}
mSymbol->stopRender( context );
context.painter()->restore();
}

QgsFillSymbol *QgsDecorationLayoutExtent::symbol() const
{
return mSymbol.get();
}

void QgsDecorationLayoutExtent::setSymbol( QgsFillSymbol *symbol )
{
mSymbol.reset( symbol );
}

@@ -0,0 +1,60 @@
/***************************************************************************
qgsdecorationlayoutextent.h
-------------------
begin : May 2017
copyright : (C) 2017 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 QGSDECORATIONLAYOUTEXTENT_H
#define QGSDECORATIONLAYOUTEXTENT_H

#include "qgsdecorationitem.h"

#include <QColor>
#include <QFont>
#include <QObject>
#include "qgis_app.h"
#include "qgssymbol.h"
#include <memory>

class QgsDecorationLayoutExtentDialog;

class APP_EXPORT QgsDecorationLayoutExtent : public QgsDecorationItem
{
Q_OBJECT
public:

//! Constructor
QgsDecorationLayoutExtent( QObject *parent = nullptr );

QgsFillSymbol *symbol() const;
void setSymbol( QgsFillSymbol *symbol SIP_TRANSFER );

public slots:
//! set values on the gui when a project is read or the gui first loaded
void projectRead() override;
//! save values to the project
void saveToProject() override;

//! Show the dialog box
void run() override;
//! render the copyright label
void render( const QgsMapSettings &mapSettings, QgsRenderContext &context ) override;

private:
std::unique_ptr< QgsFillSymbol > mSymbol;

friend class QgsDecorationLayoutExtentDialog;
};

#endif //QGSDECORATIONLAYOUTEXTENT_H
@@ -0,0 +1,110 @@
/***************************************************************************
qgsdecorationlayoutextentdialog.cpp
----------------------------
begin : May 2017
copyright : (C) 2017 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 "qgsdecorationlayoutextentdialog.h"

#include "qgsdecorationlayoutextent.h"

#include "qgslogger.h"
#include "qgshelp.h"
#include "qgsstyle.h"
#include "qgssymbol.h"
#include "qgssymbolselectordialog.h"
#include "qgisapp.h"
#include "qgsguiutils.h"
#include "qgssettings.h"

QgsDecorationLayoutExtentDialog::QgsDecorationLayoutExtentDialog( QgsDecorationLayoutExtent &deco, QWidget *parent )
: QDialog( parent )
, mDeco( deco )
{
setupUi( this );

QgsSettings settings;
restoreGeometry( settings.value( "/Windows/DecorationLayoutExtent/geometry" ).toByteArray() );

updateGuiElements();
connect( buttonBox->button( QDialogButtonBox::Apply ), &QAbstractButton::clicked, this, &QgsDecorationLayoutExtentDialog::apply );
connect( mSymbolButton, &QPushButton::clicked, this, &QgsDecorationLayoutExtentDialog::changeSymbol );
}

void QgsDecorationLayoutExtentDialog::updateGuiElements()
{
grpEnable->setChecked( mDeco.enabled() );

if ( mDeco.symbol() )
{
mSymbol.reset( static_cast<QgsFillSymbol *>( mDeco.symbol()->clone() ) );
QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mSymbol.get(), mSymbolButton->iconSize() );
mSymbolButton->setIcon( icon );
}
}

void QgsDecorationLayoutExtentDialog::updateDecoFromGui()
{
mDeco.setEnabled( grpEnable->isChecked() );

if ( mSymbol )
{
mDeco.setSymbol( mSymbol->clone() );
}
}

QgsDecorationLayoutExtentDialog::~QgsDecorationLayoutExtentDialog()
{
QgsSettings settings;
settings.setValue( QStringLiteral( "/Windows/DecorationLayoutExtent/geometry" ), saveGeometry() );
}

void QgsDecorationLayoutExtentDialog::on_buttonBox_accepted()
{
apply();
accept();
}

void QgsDecorationLayoutExtentDialog::apply()
{
updateDecoFromGui();
mDeco.update();
}

void QgsDecorationLayoutExtentDialog::on_buttonBox_rejected()
{
reject();
}

void QgsDecorationLayoutExtentDialog::changeSymbol()
{
if ( !mSymbol )
return;

QgsFillSymbol *symbol = mSymbol->clone();
QgsSymbolSelectorDialog dlg( symbol, QgsStyle::defaultStyle(), nullptr, this );
if ( dlg.exec() == QDialog::Rejected )
{
delete symbol;
}
else
{
mSymbol.reset( symbol );
if ( mSymbol )
{
QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mSymbol.get(), mSymbolButton->iconSize() );
mSymbolButton->setIcon( icon );
}
}
}

0 comments on commit 7921de3

Please sign in to comment.
You can’t perform that action at this time.