Skip to content

Commit 7921de3

Browse files
committed
[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.
1 parent 5077e12 commit 7921de3

9 files changed

+547
-4
lines changed

src/app/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ SET(QGIS_APP_SRCS
2424
qgsdecorationitem.cpp
2525
qgsdecorationcopyright.cpp
2626
qgsdecorationcopyrightdialog.cpp
27+
qgsdecorationlayoutextent.cpp
28+
qgsdecorationlayoutextentdialog.cpp
2729
qgsdecorationnortharrow.cpp
2830
qgsdecorationnortharrowdialog.cpp
2931
qgsdecorationscalebar.cpp
@@ -209,6 +211,8 @@ SET (QGIS_APP_MOC_HDRS
209211
qgsdecorationitem.h
210212
qgsdecorationcopyright.h
211213
qgsdecorationcopyrightdialog.h
214+
qgsdecorationlayoutextent.h
215+
qgsdecorationlayoutextentdialog.h
212216
qgsdecorationnortharrow.h
213217
qgsdecorationnortharrowdialog.h
214218
qgsdecorationscalebar.h

src/app/qgisapp.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
161161
#include "qgsdecorationnortharrow.h"
162162
#include "qgsdecorationscalebar.h"
163163
#include "qgsdecorationgrid.h"
164+
#include "qgsdecorationlayoutextent.h"
164165
#include "qgsencodingfiledialog.h"
165166
#include "qgserror.h"
166167
#include "qgserrordialog.h"
@@ -3527,11 +3528,15 @@ void QgisApp::createDecorations()
35273528
QgsDecorationGrid *mDecorationGrid = new QgsDecorationGrid( this );
35283529
connect( mActionDecorationGrid, &QAction::triggered, mDecorationGrid, &QgsDecorationGrid::run );
35293530

3531+
QgsDecorationLayoutExtent *decorationLayoutExtent = new QgsDecorationLayoutExtent( this );
3532+
connect( mActionDecorationLayoutExtent, &QAction::triggered, decorationLayoutExtent, &QgsDecorationLayoutExtent::run );
3533+
35303534
// add the decorations in a particular order so they are rendered in that order
35313535
addDecorationItem( mDecorationGrid );
35323536
addDecorationItem( mDecorationCopyright );
35333537
addDecorationItem( mDecorationNorthArrow );
35343538
addDecorationItem( mDecorationScaleBar );
3539+
addDecorationItem( decorationLayoutExtent );
35353540
connect( mMapCanvas, &QgsMapCanvas::renderComplete, this, &QgisApp::renderDecorationItems );
35363541
connect( this, &QgisApp::newProject, this, &QgisApp::projectReadDecorationItems );
35373542
connect( this, &QgisApp::projectRead, this, &QgisApp::projectReadDecorationItems );

src/app/qgsdecorationitem.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ class APP_EXPORT QgsDecorationItem : public QObject, public QgsMapDecoration
6161
*/
6262
void setPlacement( Placement placement ) { mPlacement = placement; }
6363

64+
QString name() const { return mName; }
65+
6466
signals:
6567
void toggled( bool t );
6668

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

76-
virtual void setName( const char *name );
77-
virtual QString name() { return mName; }
78-
7978
//! Redraws the decoration
8079
void update();
8180

8281
protected:
8382

83+
void setName( const char *name );
84+
8485
//! True if decoration item has to be displayed
8586
bool mEnabled;
8687

src/app/qgsdecorationlayoutextent.cpp

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/***************************************************************************
2+
qgsdecorationlayoutextent.cpp
3+
----------------------
4+
begin : May 2017
5+
copyright : (C) 2017 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
8+
***************************************************************************/
9+
10+
/***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************/
18+
19+
#include "qgsdecorationlayoutextent.h"
20+
#include "qgsdecorationlayoutextentdialog.h"
21+
22+
#include "qgslayoutmanager.h"
23+
#include "qgscomposition.h"
24+
#include "qgscomposermap.h"
25+
#include "qgsgeometry.h"
26+
#include "qgscsexception.h"
27+
#include "qgslinesymbollayer.h"
28+
29+
#include "qgisapp.h"
30+
#include "qgsapplication.h"
31+
#include "qgslogger.h"
32+
#include "qgsmapcanvas.h"
33+
#include "qgsproject.h"
34+
#include "qgssymbollayerutils.h"
35+
#include "qgsreadwritecontext.h"
36+
37+
#include <QPainter>
38+
39+
QgsDecorationLayoutExtent::QgsDecorationLayoutExtent( QObject *parent )
40+
: QgsDecorationItem( parent )
41+
{
42+
mPlacement = BottomRight;
43+
mMarginUnit = QgsUnitTypes::RenderMillimeters;
44+
45+
setName( "Layout Extent" );
46+
projectRead();
47+
}
48+
49+
void QgsDecorationLayoutExtent::projectRead()
50+
{
51+
QgsDecorationItem::projectRead();
52+
53+
QDomDocument doc;
54+
QDomElement elem;
55+
QgsReadWriteContext rwContext;
56+
rwContext.setPathResolver( QgsProject::instance()->pathResolver() );
57+
QString xml = QgsProject::instance()->readEntry( mNameConfig, QStringLiteral( "/Symbol" ) );
58+
mSymbol.reset( nullptr );
59+
if ( !xml.isEmpty() )
60+
{
61+
doc.setContent( xml );
62+
elem = doc.documentElement();
63+
mSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( elem, rwContext ) );
64+
}
65+
if ( ! mSymbol )
66+
{
67+
mSymbol.reset( new QgsFillSymbol() );
68+
QgsSimpleLineSymbolLayer *layer = new QgsSimpleLineSymbolLayer( QColor( 0, 0, 0, 100 ), 0, Qt::DashLine );
69+
mSymbol->changeSymbolLayer( 0, layer );
70+
}
71+
}
72+
73+
void QgsDecorationLayoutExtent::saveToProject()
74+
{
75+
QgsDecorationItem::saveToProject();
76+
// write symbol info to xml
77+
QDomDocument doc;
78+
QDomElement elem;
79+
QgsReadWriteContext rwContext;
80+
rwContext.setPathResolver( QgsProject::instance()->pathResolver() );
81+
if ( mSymbol )
82+
{
83+
elem = QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "Symbol" ), mSymbol.get(), doc, rwContext );
84+
doc.appendChild( elem );
85+
// FIXME this works, but XML will not be valid as < is replaced by &lt;
86+
QgsProject::instance()->writeEntry( mNameConfig, QStringLiteral( "/Symbol" ), doc.toString() );
87+
}
88+
}
89+
90+
void QgsDecorationLayoutExtent::run()
91+
{
92+
QgsDecorationLayoutExtentDialog dlg( *this, QgisApp::instance() );
93+
dlg.exec();
94+
}
95+
96+
97+
void QgsDecorationLayoutExtent::render( const QgsMapSettings &mapSettings, QgsRenderContext &context )
98+
{
99+
if ( !enabled() )
100+
return;
101+
102+
if ( !mSymbol )
103+
return;
104+
105+
context.painter()->save();
106+
context.painter()->setRenderHint( QPainter::Antialiasing, true );
107+
mSymbol->startRender( context );
108+
109+
const QgsMapToPixel &m2p = mapSettings.mapToPixel();
110+
QTransform transform = m2p.transform();
111+
112+
Q_FOREACH ( QgsComposition *composition, QgsProject::instance()->layoutManager()->compositions() )
113+
{
114+
Q_FOREACH ( const QgsComposerMap *map, composition->composerMapItems() )
115+
{
116+
QPolygonF extent = map->visibleExtentPolygon();
117+
QgsGeometry g = QgsGeometry::fromQPolygonF( extent );
118+
119+
if ( map->crs() !=
120+
mapSettings.destinationCrs() )
121+
{
122+
// reproject extent
123+
QgsCoordinateTransform ct( map->crs(),
124+
mapSettings.destinationCrs() );
125+
g = g.densifyByCount( 20 );
126+
try
127+
{
128+
g.transform( ct );
129+
}
130+
catch ( QgsCsException & )
131+
{
132+
}
133+
}
134+
135+
g.transform( transform );
136+
extent = g.asQPolygonF();
137+
mSymbol->renderPolygon( extent, nullptr, nullptr, context );
138+
}
139+
}
140+
mSymbol->stopRender( context );
141+
context.painter()->restore();
142+
}
143+
144+
QgsFillSymbol *QgsDecorationLayoutExtent::symbol() const
145+
{
146+
return mSymbol.get();
147+
}
148+
149+
void QgsDecorationLayoutExtent::setSymbol( QgsFillSymbol *symbol )
150+
{
151+
mSymbol.reset( symbol );
152+
}
153+

src/app/qgsdecorationlayoutextent.h

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/***************************************************************************
2+
qgsdecorationlayoutextent.h
3+
-------------------
4+
begin : May 2017
5+
copyright : (C) 2017 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
8+
***************************************************************************/
9+
10+
/***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************/
18+
#ifndef QGSDECORATIONLAYOUTEXTENT_H
19+
#define QGSDECORATIONLAYOUTEXTENT_H
20+
21+
#include "qgsdecorationitem.h"
22+
23+
#include <QColor>
24+
#include <QFont>
25+
#include <QObject>
26+
#include "qgis_app.h"
27+
#include "qgssymbol.h"
28+
#include <memory>
29+
30+
class QgsDecorationLayoutExtentDialog;
31+
32+
class APP_EXPORT QgsDecorationLayoutExtent : public QgsDecorationItem
33+
{
34+
Q_OBJECT
35+
public:
36+
37+
//! Constructor
38+
QgsDecorationLayoutExtent( QObject *parent = nullptr );
39+
40+
QgsFillSymbol *symbol() const;
41+
void setSymbol( QgsFillSymbol *symbol SIP_TRANSFER );
42+
43+
public slots:
44+
//! set values on the gui when a project is read or the gui first loaded
45+
void projectRead() override;
46+
//! save values to the project
47+
void saveToProject() override;
48+
49+
//! Show the dialog box
50+
void run() override;
51+
//! render the copyright label
52+
void render( const QgsMapSettings &mapSettings, QgsRenderContext &context ) override;
53+
54+
private:
55+
std::unique_ptr< QgsFillSymbol > mSymbol;
56+
57+
friend class QgsDecorationLayoutExtentDialog;
58+
};
59+
60+
#endif //QGSDECORATIONLAYOUTEXTENT_H
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/***************************************************************************
2+
qgsdecorationlayoutextentdialog.cpp
3+
----------------------------
4+
begin : May 2017
5+
copyright : (C) 2017 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
8+
***************************************************************************/
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#include "qgsdecorationlayoutextentdialog.h"
19+
20+
#include "qgsdecorationlayoutextent.h"
21+
22+
#include "qgslogger.h"
23+
#include "qgshelp.h"
24+
#include "qgsstyle.h"
25+
#include "qgssymbol.h"
26+
#include "qgssymbolselectordialog.h"
27+
#include "qgisapp.h"
28+
#include "qgsguiutils.h"
29+
#include "qgssettings.h"
30+
31+
QgsDecorationLayoutExtentDialog::QgsDecorationLayoutExtentDialog( QgsDecorationLayoutExtent &deco, QWidget *parent )
32+
: QDialog( parent )
33+
, mDeco( deco )
34+
{
35+
setupUi( this );
36+
37+
QgsSettings settings;
38+
restoreGeometry( settings.value( "/Windows/DecorationLayoutExtent/geometry" ).toByteArray() );
39+
40+
updateGuiElements();
41+
connect( buttonBox->button( QDialogButtonBox::Apply ), &QAbstractButton::clicked, this, &QgsDecorationLayoutExtentDialog::apply );
42+
connect( mSymbolButton, &QPushButton::clicked, this, &QgsDecorationLayoutExtentDialog::changeSymbol );
43+
}
44+
45+
void QgsDecorationLayoutExtentDialog::updateGuiElements()
46+
{
47+
grpEnable->setChecked( mDeco.enabled() );
48+
49+
if ( mDeco.symbol() )
50+
{
51+
mSymbol.reset( static_cast<QgsFillSymbol *>( mDeco.symbol()->clone() ) );
52+
QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mSymbol.get(), mSymbolButton->iconSize() );
53+
mSymbolButton->setIcon( icon );
54+
}
55+
}
56+
57+
void QgsDecorationLayoutExtentDialog::updateDecoFromGui()
58+
{
59+
mDeco.setEnabled( grpEnable->isChecked() );
60+
61+
if ( mSymbol )
62+
{
63+
mDeco.setSymbol( mSymbol->clone() );
64+
}
65+
}
66+
67+
QgsDecorationLayoutExtentDialog::~QgsDecorationLayoutExtentDialog()
68+
{
69+
QgsSettings settings;
70+
settings.setValue( QStringLiteral( "/Windows/DecorationLayoutExtent/geometry" ), saveGeometry() );
71+
}
72+
73+
void QgsDecorationLayoutExtentDialog::on_buttonBox_accepted()
74+
{
75+
apply();
76+
accept();
77+
}
78+
79+
void QgsDecorationLayoutExtentDialog::apply()
80+
{
81+
updateDecoFromGui();
82+
mDeco.update();
83+
}
84+
85+
void QgsDecorationLayoutExtentDialog::on_buttonBox_rejected()
86+
{
87+
reject();
88+
}
89+
90+
void QgsDecorationLayoutExtentDialog::changeSymbol()
91+
{
92+
if ( !mSymbol )
93+
return;
94+
95+
QgsFillSymbol *symbol = mSymbol->clone();
96+
QgsSymbolSelectorDialog dlg( symbol, QgsStyle::defaultStyle(), nullptr, this );
97+
if ( dlg.exec() == QDialog::Rejected )
98+
{
99+
delete symbol;
100+
}
101+
else
102+
{
103+
mSymbol.reset( symbol );
104+
if ( mSymbol )
105+
{
106+
QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mSymbol.get(), mSymbolButton->iconSize() );
107+
mSymbolButton->setIcon( icon );
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)