Skip to content

Commit

Permalink
[FEATURE] New decoration type for showing composer map extents
Browse files Browse the repository at this point in the history
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 7921de3
Show file tree
Hide file tree
Showing 9 changed files with 547 additions and 4 deletions.
4 changes: 4 additions & 0 deletions src/app/CMakeLists.txt
Expand Up @@ -24,6 +24,8 @@ SET(QGIS_APP_SRCS
qgsdecorationitem.cpp
qgsdecorationcopyright.cpp
qgsdecorationcopyrightdialog.cpp
qgsdecorationlayoutextent.cpp
qgsdecorationlayoutextentdialog.cpp
qgsdecorationnortharrow.cpp
qgsdecorationnortharrowdialog.cpp
qgsdecorationscalebar.cpp
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/app/qgisapp.cpp
Expand Up @@ -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"
Expand Down Expand Up @@ -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 );
Expand Down
7 changes: 4 additions & 3 deletions src/app/qgsdecorationitem.h
Expand Up @@ -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 );

Expand All @@ -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;

Expand Down
153 changes: 153 additions & 0 deletions src/app/qgsdecorationlayoutextent.cpp
@@ -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 );
}

60 changes: 60 additions & 0 deletions src/app/qgsdecorationlayoutextent.h
@@ -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
110 changes: 110 additions & 0 deletions src/app/qgsdecorationlayoutextentdialog.cpp
@@ -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.