[FEATURE] Locked aspect ratio state for Save as image/PDF" (#4880)

Sponsored by Andreas Neumann.
This commit is contained in:
Mathieu Pellerin 2017-07-19 15:04:52 +07:00 committed by GitHub
parent 08c06def1b
commit 0665072d94
13 changed files with 427 additions and 39 deletions

View File

@ -467,6 +467,7 @@
<file>themes/default/transformed.svg</file>
<file>themes/default/transp-background_8x8.png</file>
<file>themes/default/unlocked.svg</file>
<file>themes/default/unlockedGray.svg</file>
<file>themes/default/user.svg</file>
<file>flags/eu.png</file>
<file>flags/bn.png</file>

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 0 16 16" width="16"><path d="M6 3.216c.665-.947 2.307-2.405 4.562-1.01 2.37 1.637 1.974 3.68-.04 6.93" fill="none" stroke="#888a85" stroke-width="3" stroke-linecap="square" stroke-linejoin="round"/><path d="M6 3.216C7 2 8.193.57 10.562 2.206c2.37 1.637 1.974 3.68-.04 6.93" fill="none" stroke="#eeeeec" stroke-linecap="square" stroke-linejoin="round"/><rect y="8.5" x="2.5" width="11" ry="1" rx=".846" height="7" fill="#666" stroke="#666" stroke-linecap="square"/></svg>

After

Width:  |  Height:  |  Size: 534 B

View File

@ -124,6 +124,7 @@
%Include qgslistwidget.sip
%Include qgslegendfilterbutton.sip
%Include qgslimitedrandomcolorrampdialog.sip
%Include qgsratiolockbutton.sip
%Include qgslonglongvalidator.sip
%Include qgsludialog.sip
%Include qgsmanageconnectionsdialog.sip

View File

@ -163,6 +163,24 @@ class QgsExtentGroupBox : QgsCollapsibleGroupBox
.. versionadded:: 3.0
%End
void setRatio( QSize ratio );
%Docstring
Sets a fixed aspect ratio to be used when dragging extent onto the canvas.
To unset a fixed aspect ratio, set the width and height to zero.
\param ratio aspect ratio's width and height
.. versionadded:: 3.0
*
%End
QSize ratio() const;
%Docstring
Returns the current fixed aspect ratio to be used when dragging extent onto the canvas.
If the aspect ratio isn't fixed, the width and height will be set to zero.
.. versionadded:: 3.0
*
:rtype: QSize
%End
signals:
void extentChanged( const QgsRectangle &r );

View File

@ -0,0 +1,69 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsratiolockbutton.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsRatioLockButton : QToolButton
{
%Docstring
A cross platform button subclass used to represent a locked / unlocked ratio state.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgsratiolockbutton.h"
%End
public:
QgsRatioLockButton( QWidget *parent /TransferThis/ = 0 );
%Docstring
Construct a new ratio lock button.
Use ``parent`` to attach a parent QWidget to the button.
%End
void setLocked( const bool locked );
%Docstring
Sets whether the button state is locked.
\param locked locked state
.. seealso:: locked
%End
bool locked() const;
%Docstring
Returns whether the button state is locked.
:return: true if the button state is locked.
.. seealso:: setLocked
:rtype: bool
%End
signals:
void lockChanged( const bool locked );
%Docstring
Emitted whenever the lock state changes.
%End
protected:
virtual void changeEvent( QEvent *e );
virtual void showEvent( QShowEvent *e );
virtual void resizeEvent( QResizeEvent *event );
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgsratiolockbutton.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -81,6 +81,7 @@ QgsMapSaveDialog::QgsMapSaveDialog( QWidget *parent, QgsMapCanvas *mapCanvas, QL
connect( mOutputHeightSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsMapSaveDialog::updateOutputHeight );
connect( mExtentGroupBox, &QgsExtentGroupBox::extentChanged, this, &QgsMapSaveDialog::updateExtent );
connect( mScaleWidget, &QgsScaleWidget::scaleChanged, this, &QgsMapSaveDialog::updateScale );
connect( mLockAspectRatio, &QgsRatioLockButton::lockChanged, this, &QgsMapSaveDialog::lockChanged );
updateOutputSize();
@ -126,12 +127,25 @@ void QgsMapSaveDialog::updateOutputWidth( int width )
double scale = ( double )width / mSize.width();
double adjustment = ( ( mExtent.width() * scale ) - mExtent.width() ) / 2;
mSize.setWidth( width );
mExtent.setXMinimum( mExtent.xMinimum() - adjustment );
mExtent.setXMaximum( mExtent.xMaximum() + adjustment );
whileBlocking( mExtentGroupBox )->setOutputExtentFromUser( mExtent, mExtentGroupBox->currentCrs() );
if ( mLockAspectRatio->locked() )
{
int height = width * mExtentGroupBox->ratio().height() / mExtentGroupBox->ratio().width();
double scale = ( double )height / mSize.height();
double adjustment = ( ( mExtent.height() * scale ) - mExtent.height() ) / 2;
mSize.setWidth( width );
whileBlocking( mOutputHeightSpinBox )->setValue( height );
mSize.setHeight( height );
mExtent.setYMinimum( mExtent.yMinimum() - adjustment );
mExtent.setYMaximum( mExtent.yMaximum() + adjustment );
}
whileBlocking( mExtentGroupBox )->setOutputExtentFromUser( mExtent, mExtentGroupBox->currentCrs() );
}
void QgsMapSaveDialog::updateOutputHeight( int height )
@ -139,12 +153,25 @@ void QgsMapSaveDialog::updateOutputHeight( int height )
double scale = ( double )height / mSize.height();
double adjustment = ( ( mExtent.height() * scale ) - mExtent.height() ) / 2;
mSize.setHeight( height );
mExtent.setYMinimum( mExtent.yMinimum() - adjustment );
mExtent.setYMaximum( mExtent.yMaximum() + adjustment );
whileBlocking( mExtentGroupBox )->setOutputExtentFromUser( mExtent, mExtentGroupBox->currentCrs() );
if ( mLockAspectRatio->locked() )
{
int width = height * mExtentGroupBox->ratio().width() / mExtentGroupBox->ratio().height();
double scale = ( double )width / mSize.width();
double adjustment = ( ( mExtent.width() * scale ) - mExtent.width() ) / 2;
mSize.setHeight( height );
whileBlocking( mOutputWidthSpinBox )->setValue( height );
mSize.setWidth( width );
mExtent.setXMinimum( mExtent.xMinimum() - adjustment );
mExtent.setXMaximum( mExtent.xMaximum() + adjustment );
}
whileBlocking( mExtentGroupBox )->setOutputExtentFromUser( mExtent, mExtentGroupBox->currentCrs() );
}
void QgsMapSaveDialog::updateExtent( const QgsRectangle &extent )
@ -153,6 +180,11 @@ void QgsMapSaveDialog::updateExtent( const QgsRectangle &extent )
mSize.setHeight( mSize.height() * extent.height() / mExtent.height() );
mExtent = extent;
if ( mLockAspectRatio->locked() )
{
mExtentGroupBox->setRatio( QSize( mSize.width(), mSize.height() ) );
}
updateOutputSize();
}
@ -242,6 +274,18 @@ void QgsMapSaveDialog::applyMapSettings( QgsMapSettings &mapSettings )
mapSettings.setExpressionContext( expressionContext );
}
void QgsMapSaveDialog::lockChanged( const bool locked )
{
if ( locked )
{
mExtentGroupBox->setRatio( QSize( mOutputWidthSpinBox->value(), mOutputHeightSpinBox->value() ) );
}
else
{
mExtentGroupBox->setRatio( QSize( 0, 0 ) );
}
}
void QgsMapSaveDialog::accepted()
{
if ( mDialogType == Image )

View File

@ -77,6 +77,7 @@ class APP_EXPORT QgsMapSaveDialog: public QDialog, private Ui::QgsMapSaveDialog
private:
void lockChanged( const bool locked );
void accepted();
void updateDpi( int dpi );

View File

@ -263,6 +263,7 @@ SET(QGIS_GUI_SRCS
qgslistwidget.cpp
qgslegendfilterbutton.cpp
qgslimitedrandomcolorrampdialog.cpp
qgsratiolockbutton.cpp
qgsludialog.cpp
qgsmanageconnectionsdialog.cpp
qgsmapcanvas.cpp
@ -421,6 +422,7 @@ SET(QGIS_GUI_MOC_HDRS
qgslistwidget.h
qgslegendfilterbutton.h
qgslimitedrandomcolorrampdialog.h
qgsratiolockbutton.h
qgslonglongvalidator.h
qgsludialog.h
qgsmanageconnectionsdialog.h

View File

@ -277,6 +277,7 @@ void QgsExtentGroupBox::setOutputExtentFromDrawOnCanvas()
mMapToolPrevious = nullptr;
} );
}
mMapToolExtent->setRatio( mRatio );
mCanvas->setMapTool( mMapToolExtent.get() );
window()->setVisible( false );
}

View File

@ -177,6 +177,19 @@ class GUI_EXPORT QgsExtentGroupBox : public QgsCollapsibleGroupBox, private Ui::
*/
void setOutputExtentFromDrawOnCanvas();
/** Sets a fixed aspect ratio to be used when dragging extent onto the canvas.
* To unset a fixed aspect ratio, set the width and height to zero.
* \param ratio aspect ratio's width and height
* \since QGIS 3.0
* */
void setRatio( QSize ratio ) { mRatio = ratio; }
/** Returns the current fixed aspect ratio to be used when dragging extent onto the canvas.
* If the aspect ratio isn't fixed, the width and height will be set to zero.
* \since QGIS 3.0
* */
QSize ratio() const { return mRatio; }
signals:
/**
@ -223,6 +236,7 @@ class GUI_EXPORT QgsExtentGroupBox : public QgsCollapsibleGroupBox, private Ui::
std::unique_ptr< QgsMapToolExtent > mMapToolExtent;
QPointer< QgsMapTool > mMapToolPrevious = nullptr;
QgsMapCanvas *mCanvas = nullptr;
QSize mRatio;
void setExtentToLayerExtent( const QString &layerId );

View File

@ -0,0 +1,110 @@
/***************************************************************************
qgsratiolockbutton.cpp - Lock button
--------------------------------------
Date : July, 2017
Copyright : (C) 2017 by Mathieu Pellerin
Email : nirvn dot asia 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 "qgsratiolockbutton.h"
#include <QApplication>
#include <QMouseEvent>
#include <QPainter>
#include <QPushButton>
#include <QWidget>
QgsRatioLockButton::QgsRatioLockButton( QWidget *parent )
: QToolButton( parent )
, mLocked( false )
{
setMinimumSize( QSize( 24, 24 ) );
setCheckable( true );
setAutoRaise( true );
connect( this, &QPushButton::clicked, this, &QgsRatioLockButton::buttonClicked );
}
void QgsRatioLockButton::setLocked( const bool locked )
{
if ( mLocked != locked )
buttonClicked();
}
void QgsRatioLockButton::buttonClicked()
{
mLocked = !mLocked;
setDown( mLocked );
emit lockChanged( mLocked );
drawButton();
}
void QgsRatioLockButton::changeEvent( QEvent *e )
{
if ( e->type() == QEvent::EnabledChange )
{
drawButton();
}
QToolButton::changeEvent( e );
}
void QgsRatioLockButton::showEvent( QShowEvent *e )
{
drawButton();
QToolButton::showEvent( e );
}
void QgsRatioLockButton::resizeEvent( QResizeEvent *event )
{
QToolButton::resizeEvent( event );
drawButton();
}
void QgsRatioLockButton::drawButton()
{
QSize currentIconSize;
#ifdef Q_OS_WIN
currentIconSize = QSize( width() - 10, height() - 6 );
#else
currentIconSize = QSize( width() - 10, height() - 12 );
#endif
if ( !currentIconSize.isValid() || currentIconSize.width() <= 0 || currentIconSize.height() <= 0 )
{
return;
}
QPixmap pm;
pm = QPixmap( currentIconSize );
pm.fill( Qt::transparent );
QPainter painter;
QPen pen = ( QColor( 136, 136, 136 ) );
pen.setWidth( 2 );
painter.begin( &pm );
painter.setPen( pen );
painter.drawLine( 1, 1, currentIconSize.width() / 2, 1 );
painter.drawLine( currentIconSize.width() / 2, 1, currentIconSize.width() / 2, currentIconSize.height() / 2 - 13 );
painter.drawLine( currentIconSize.width() / 2, currentIconSize.height() / 2 + 13, currentIconSize.width() / 2, currentIconSize.height() - 2 );
painter.drawLine( currentIconSize.width() / 2, currentIconSize.height() - 2, 1, currentIconSize.height() - 2 );
QImage image( mLocked ? QStringLiteral( ":/images/themes/default/lockedGray.svg" ) : QStringLiteral( ":/images/themes/default/unlockedGray.svg" ) );
painter.drawImage( QRectF( currentIconSize.width() / 2 - 8, currentIconSize.height() / 2 - 8, 16, 16 ), image, QRectF( 0, 0, 16, 16 ) );
painter.end();
setIconSize( currentIconSize );
setIcon( pm );
}

View File

@ -0,0 +1,78 @@
/***************************************************************************
qgsratiolockbutton.cpp - Lock button
--------------------------------------
Date : July, 2017
Copyright : (C) 2017 by Mathieu Pellerin
Email : nirvn dot asia 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 QGSLOCKBUTTON_H
#define QGSLOCKBUTTON_H
#include "qgsratiolockbutton.h"
#include <QToolButton>
#include "qgis_gui.h"
#include "qgis.h"
/** \ingroup gui
* \class QgsRatioLockButton
* A cross platform button subclass used to represent a locked / unlocked ratio state.
* \since QGIS 3.0
*/
class GUI_EXPORT QgsRatioLockButton : public QToolButton
{
Q_OBJECT
Q_PROPERTY( bool locked READ locked WRITE setLocked )
public:
/** Construct a new ratio lock button.
* Use \a parent to attach a parent QWidget to the button.
*/
QgsRatioLockButton( QWidget *parent SIP_TRANSFERTHIS = nullptr );
/** Sets whether the button state is locked.
* \param locked locked state
* \see locked
*/
void setLocked( const bool locked );
/** Returns whether the button state is locked.
* \returns true if the button state is locked.
* \see setLocked
*/
bool locked() const { return mLocked; }
signals:
/** Emitted whenever the lock state changes.
*/
void lockChanged( const bool locked );
protected:
void changeEvent( QEvent *e ) override;
void showEvent( QShowEvent *e ) override;
void resizeEvent( QResizeEvent *event ) override;
private:
void drawButton();
bool mLocked = false;
private slots:
void buttonClicked();
};
#endif

View File

@ -77,25 +77,6 @@ Rasterizing the map is recommended when such effects are used.</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QgsSpinBox" name="mOutputHeightSpinBox">
<property name="suffix">
<string> px</string>
</property>
<property name="prefix">
<string/>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>99999</number>
</property>
<property name="showClearButton" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
@ -103,24 +84,85 @@ Rasterizing the map is recommended when such effects are used.</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QgsSpinBox" name="mOutputWidthSpinBox">
<property name="suffix">
<string> px</string>
<item row="3" column="1" rowspan="2">
<layout class="QGridLayout" name="gridLayout_4">
<property name="horizontalSpacing">
<number>0</number>
</property>
<property name="prefix">
<string/>
<property name="verticalSpacing">
<number>6</number>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>99999</number>
</property>
<property name="showClearButton" stdset="0">
<bool>false</bool>
</property>
</widget>
<item row="0" column="0">
<widget class="QgsSpinBox" name="mOutputWidthSpinBox">
<property name="suffix">
<string> px</string>
</property>
<property name="prefix">
<string/>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>99999</number>
</property>
<property name="showClearButton" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="1" rowspan="2">
<layout class="QHBoxLayout">
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="QgsRatioLockButton" name="mLockAspectRatio">
<property name="leftMargin">
<number>13</number>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Lock aspect ratio (including while drawing extent onto canvas)</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QgsSpinBox" name="mOutputHeightSpinBox">
<property name="suffix">
<string> px</string>
</property>
<property name="prefix">
<string/>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>99999</number>
</property>
<property name="showClearButton" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
@ -206,6 +248,12 @@ Rasterizing the map is recommended when such effects are used.</string>
<header>qgsextentgroupbox.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsRatioLockButton</class>
<extends>QToolButton</extends>
<header>qgsratiolockbutton.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsSpinBox</class>
<extends>QSpinBox</extends>