Skip to content

Commit 0665072

Browse files
authored
[FEATURE] Locked aspect ratio state for Save as image/PDF" (#4880)
Sponsored by Andreas Neumann.
1 parent 08c06de commit 0665072

13 files changed

+427
-39
lines changed

images/images.qrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@
467467
<file>themes/default/transformed.svg</file>
468468
<file>themes/default/transp-background_8x8.png</file>
469469
<file>themes/default/unlocked.svg</file>
470+
<file>themes/default/unlockedGray.svg</file>
470471
<file>themes/default/user.svg</file>
471472
<file>flags/eu.png</file>
472473
<file>flags/bn.png</file>
Lines changed: 1 addition & 0 deletions
Loading

python/gui/gui_auto.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
%Include qgslistwidget.sip
125125
%Include qgslegendfilterbutton.sip
126126
%Include qgslimitedrandomcolorrampdialog.sip
127+
%Include qgsratiolockbutton.sip
127128
%Include qgslonglongvalidator.sip
128129
%Include qgsludialog.sip
129130
%Include qgsmanageconnectionsdialog.sip

python/gui/qgsextentgroupbox.sip

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,24 @@ class QgsExtentGroupBox : QgsCollapsibleGroupBox
163163
.. versionadded:: 3.0
164164
%End
165165

166+
void setRatio( QSize ratio );
167+
%Docstring
168+
Sets a fixed aspect ratio to be used when dragging extent onto the canvas.
169+
To unset a fixed aspect ratio, set the width and height to zero.
170+
\param ratio aspect ratio's width and height
171+
.. versionadded:: 3.0
172+
*
173+
%End
174+
175+
QSize ratio() const;
176+
%Docstring
177+
Returns the current fixed aspect ratio to be used when dragging extent onto the canvas.
178+
If the aspect ratio isn't fixed, the width and height will be set to zero.
179+
.. versionadded:: 3.0
180+
*
181+
:rtype: QSize
182+
%End
183+
166184
signals:
167185

168186
void extentChanged( const QgsRectangle &r );

python/gui/qgsratiolockbutton.sip

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/************************************************************************
2+
* This file has been generated automatically from *
3+
* *
4+
* src/gui/qgsratiolockbutton.h *
5+
* *
6+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
7+
************************************************************************/
8+
9+
10+
11+
12+
class QgsRatioLockButton : QToolButton
13+
{
14+
%Docstring
15+
A cross platform button subclass used to represent a locked / unlocked ratio state.
16+
.. versionadded:: 3.0
17+
%End
18+
19+
%TypeHeaderCode
20+
#include "qgsratiolockbutton.h"
21+
%End
22+
public:
23+
24+
QgsRatioLockButton( QWidget *parent /TransferThis/ = 0 );
25+
%Docstring
26+
Construct a new ratio lock button.
27+
Use ``parent`` to attach a parent QWidget to the button.
28+
%End
29+
30+
void setLocked( const bool locked );
31+
%Docstring
32+
Sets whether the button state is locked.
33+
\param locked locked state
34+
.. seealso:: locked
35+
%End
36+
37+
bool locked() const;
38+
%Docstring
39+
Returns whether the button state is locked.
40+
:return: true if the button state is locked.
41+
.. seealso:: setLocked
42+
:rtype: bool
43+
%End
44+
45+
signals:
46+
47+
void lockChanged( const bool locked );
48+
%Docstring
49+
Emitted whenever the lock state changes.
50+
%End
51+
52+
protected:
53+
54+
virtual void changeEvent( QEvent *e );
55+
56+
virtual void showEvent( QShowEvent *e );
57+
58+
virtual void resizeEvent( QResizeEvent *event );
59+
60+
61+
};
62+
63+
/************************************************************************
64+
* This file has been generated automatically from *
65+
* *
66+
* src/gui/qgsratiolockbutton.h *
67+
* *
68+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
69+
************************************************************************/

src/app/qgsmapsavedialog.cpp

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, QL
8181
connect( mOutputHeightSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsMapSaveDialog::updateOutputHeight );
8282
connect( mExtentGroupBox, &QgsExtentGroupBox::extentChanged, this, &QgsMapSaveDialog::updateExtent );
8383
connect( mScaleWidget, &QgsScaleWidget::scaleChanged, this, &QgsMapSaveDialog::updateScale );
84+
connect( mLockAspectRatio, &QgsRatioLockButton::lockChanged, this, &QgsMapSaveDialog::lockChanged );
8485

8586
updateOutputSize();
8687

@@ -126,25 +127,51 @@ void QgsMapSaveDialog::updateOutputWidth( int width )
126127
double scale = ( double )width / mSize.width();
127128
double adjustment = ( ( mExtent.width() * scale ) - mExtent.width() ) / 2;
128129

130+
mSize.setWidth( width );
131+
129132
mExtent.setXMinimum( mExtent.xMinimum() - adjustment );
130133
mExtent.setXMaximum( mExtent.xMaximum() + adjustment );
131134

132-
whileBlocking( mExtentGroupBox )->setOutputExtentFromUser( mExtent, mExtentGroupBox->currentCrs() );
135+
if ( mLockAspectRatio->locked() )
136+
{
137+
int height = width * mExtentGroupBox->ratio().height() / mExtentGroupBox->ratio().width();
138+
double scale = ( double )height / mSize.height();
139+
double adjustment = ( ( mExtent.height() * scale ) - mExtent.height() ) / 2;
133140

134-
mSize.setWidth( width );
141+
whileBlocking( mOutputHeightSpinBox )->setValue( height );
142+
mSize.setHeight( height );
143+
144+
mExtent.setYMinimum( mExtent.yMinimum() - adjustment );
145+
mExtent.setYMaximum( mExtent.yMaximum() + adjustment );
146+
}
147+
148+
whileBlocking( mExtentGroupBox )->setOutputExtentFromUser( mExtent, mExtentGroupBox->currentCrs() );
135149
}
136150

137151
void QgsMapSaveDialog::updateOutputHeight( int height )
138152
{
139153
double scale = ( double )height / mSize.height();
140154
double adjustment = ( ( mExtent.height() * scale ) - mExtent.height() ) / 2;
141155

156+
mSize.setHeight( height );
157+
142158
mExtent.setYMinimum( mExtent.yMinimum() - adjustment );
143159
mExtent.setYMaximum( mExtent.yMaximum() + adjustment );
144160

145-
whileBlocking( mExtentGroupBox )->setOutputExtentFromUser( mExtent, mExtentGroupBox->currentCrs() );
161+
if ( mLockAspectRatio->locked() )
162+
{
163+
int width = height * mExtentGroupBox->ratio().width() / mExtentGroupBox->ratio().height();
164+
double scale = ( double )width / mSize.width();
165+
double adjustment = ( ( mExtent.width() * scale ) - mExtent.width() ) / 2;
146166

147-
mSize.setHeight( height );
167+
whileBlocking( mOutputWidthSpinBox )->setValue( height );
168+
mSize.setWidth( width );
169+
170+
mExtent.setXMinimum( mExtent.xMinimum() - adjustment );
171+
mExtent.setXMaximum( mExtent.xMaximum() + adjustment );
172+
}
173+
174+
whileBlocking( mExtentGroupBox )->setOutputExtentFromUser( mExtent, mExtentGroupBox->currentCrs() );
148175
}
149176

150177
void QgsMapSaveDialog::updateExtent( const QgsRectangle &extent )
@@ -153,6 +180,11 @@ void QgsMapSaveDialog::updateExtent( const QgsRectangle &extent )
153180
mSize.setHeight( mSize.height() * extent.height() / mExtent.height() );
154181
mExtent = extent;
155182

183+
if ( mLockAspectRatio->locked() )
184+
{
185+
mExtentGroupBox->setRatio( QSize( mSize.width(), mSize.height() ) );
186+
}
187+
156188
updateOutputSize();
157189
}
158190

@@ -242,6 +274,18 @@ void QgsMapSaveDialog::applyMapSettings( QgsMapSettings &mapSettings )
242274
mapSettings.setExpressionContext( expressionContext );
243275
}
244276

277+
void QgsMapSaveDialog::lockChanged( const bool locked )
278+
{
279+
if ( locked )
280+
{
281+
mExtentGroupBox->setRatio( QSize( mOutputWidthSpinBox->value(), mOutputHeightSpinBox->value() ) );
282+
}
283+
else
284+
{
285+
mExtentGroupBox->setRatio( QSize( 0, 0 ) );
286+
}
287+
}
288+
245289
void QgsMapSaveDialog::accepted()
246290
{
247291
if ( mDialogType == Image )

src/app/qgsmapsavedialog.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class APP_EXPORT QgsMapSaveDialog: public QDialog, private Ui::QgsMapSaveDialog
7777

7878
private:
7979

80+
void lockChanged( const bool locked );
8081
void accepted();
8182

8283
void updateDpi( int dpi );

src/gui/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ SET(QGIS_GUI_SRCS
263263
qgslistwidget.cpp
264264
qgslegendfilterbutton.cpp
265265
qgslimitedrandomcolorrampdialog.cpp
266+
qgsratiolockbutton.cpp
266267
qgsludialog.cpp
267268
qgsmanageconnectionsdialog.cpp
268269
qgsmapcanvas.cpp
@@ -421,6 +422,7 @@ SET(QGIS_GUI_MOC_HDRS
421422
qgslistwidget.h
422423
qgslegendfilterbutton.h
423424
qgslimitedrandomcolorrampdialog.h
425+
qgsratiolockbutton.h
424426
qgslonglongvalidator.h
425427
qgsludialog.h
426428
qgsmanageconnectionsdialog.h

src/gui/qgsextentgroupbox.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ void QgsExtentGroupBox::setOutputExtentFromDrawOnCanvas()
277277
mMapToolPrevious = nullptr;
278278
} );
279279
}
280+
mMapToolExtent->setRatio( mRatio );
280281
mCanvas->setMapTool( mMapToolExtent.get() );
281282
window()->setVisible( false );
282283
}

src/gui/qgsextentgroupbox.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,19 @@ class GUI_EXPORT QgsExtentGroupBox : public QgsCollapsibleGroupBox, private Ui::
177177
*/
178178
void setOutputExtentFromDrawOnCanvas();
179179

180+
/** Sets a fixed aspect ratio to be used when dragging extent onto the canvas.
181+
* To unset a fixed aspect ratio, set the width and height to zero.
182+
* \param ratio aspect ratio's width and height
183+
* \since QGIS 3.0
184+
* */
185+
void setRatio( QSize ratio ) { mRatio = ratio; }
186+
187+
/** Returns the current fixed aspect ratio to be used when dragging extent onto the canvas.
188+
* If the aspect ratio isn't fixed, the width and height will be set to zero.
189+
* \since QGIS 3.0
190+
* */
191+
QSize ratio() const { return mRatio; }
192+
180193
signals:
181194

182195
/**
@@ -223,6 +236,7 @@ class GUI_EXPORT QgsExtentGroupBox : public QgsCollapsibleGroupBox, private Ui::
223236
std::unique_ptr< QgsMapToolExtent > mMapToolExtent;
224237
QPointer< QgsMapTool > mMapToolPrevious = nullptr;
225238
QgsMapCanvas *mCanvas = nullptr;
239+
QSize mRatio;
226240

227241
void setExtentToLayerExtent( const QString &layerId );
228242

src/gui/qgsratiolockbutton.cpp

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/***************************************************************************
2+
qgsratiolockbutton.cpp - Lock button
3+
--------------------------------------
4+
Date : July, 2017
5+
Copyright : (C) 2017 by Mathieu Pellerin
6+
Email : nirvn dot asia at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgsratiolockbutton.h"
17+
18+
#include <QApplication>
19+
#include <QMouseEvent>
20+
#include <QPainter>
21+
#include <QPushButton>
22+
#include <QWidget>
23+
24+
QgsRatioLockButton::QgsRatioLockButton( QWidget *parent )
25+
: QToolButton( parent )
26+
, mLocked( false )
27+
28+
{
29+
setMinimumSize( QSize( 24, 24 ) );
30+
setCheckable( true );
31+
setAutoRaise( true );
32+
connect( this, &QPushButton::clicked, this, &QgsRatioLockButton::buttonClicked );
33+
}
34+
35+
void QgsRatioLockButton::setLocked( const bool locked )
36+
{
37+
if ( mLocked != locked )
38+
buttonClicked();
39+
}
40+
41+
void QgsRatioLockButton::buttonClicked()
42+
{
43+
mLocked = !mLocked;
44+
setDown( mLocked );
45+
46+
emit lockChanged( mLocked );
47+
48+
drawButton();
49+
}
50+
51+
void QgsRatioLockButton::changeEvent( QEvent *e )
52+
{
53+
if ( e->type() == QEvent::EnabledChange )
54+
{
55+
drawButton();
56+
}
57+
QToolButton::changeEvent( e );
58+
}
59+
60+
void QgsRatioLockButton::showEvent( QShowEvent *e )
61+
{
62+
drawButton();
63+
QToolButton::showEvent( e );
64+
}
65+
66+
void QgsRatioLockButton::resizeEvent( QResizeEvent *event )
67+
{
68+
QToolButton::resizeEvent( event );
69+
drawButton();
70+
}
71+
72+
void QgsRatioLockButton::drawButton()
73+
{
74+
QSize currentIconSize;
75+
76+
#ifdef Q_OS_WIN
77+
currentIconSize = QSize( width() - 10, height() - 6 );
78+
#else
79+
currentIconSize = QSize( width() - 10, height() - 12 );
80+
#endif
81+
82+
if ( !currentIconSize.isValid() || currentIconSize.width() <= 0 || currentIconSize.height() <= 0 )
83+
{
84+
return;
85+
}
86+
87+
QPixmap pm;
88+
pm = QPixmap( currentIconSize );
89+
pm.fill( Qt::transparent );
90+
91+
QPainter painter;
92+
QPen pen = ( QColor( 136, 136, 136 ) );
93+
pen.setWidth( 2 );
94+
95+
painter.begin( &pm );
96+
painter.setPen( pen );
97+
98+
painter.drawLine( 1, 1, currentIconSize.width() / 2, 1 );
99+
painter.drawLine( currentIconSize.width() / 2, 1, currentIconSize.width() / 2, currentIconSize.height() / 2 - 13 );
100+
painter.drawLine( currentIconSize.width() / 2, currentIconSize.height() / 2 + 13, currentIconSize.width() / 2, currentIconSize.height() - 2 );
101+
painter.drawLine( currentIconSize.width() / 2, currentIconSize.height() - 2, 1, currentIconSize.height() - 2 );
102+
103+
QImage image( mLocked ? QStringLiteral( ":/images/themes/default/lockedGray.svg" ) : QStringLiteral( ":/images/themes/default/unlockedGray.svg" ) );
104+
painter.drawImage( QRectF( currentIconSize.width() / 2 - 8, currentIconSize.height() / 2 - 8, 16, 16 ), image, QRectF( 0, 0, 16, 16 ) );
105+
106+
painter.end();
107+
108+
setIconSize( currentIconSize );
109+
setIcon( pm );
110+
}

0 commit comments

Comments
 (0)