Restore rulers in layout designer

This commit is contained in:
Nyall Dawson 2017-07-11 14:21:35 +10:00
parent 06976d0ec2
commit 28cd9addab
14 changed files with 781 additions and 9 deletions

View File

@ -278,6 +278,7 @@
%Include layertree/qgslayertreeviewdefaultactions.sip
%Include layout/qgslayoutdesignerinterface.sip
%Include layout/qgslayoutitemguiregistry.sip
%Include layout/qgslayoutruler.sip
%Include layout/qgslayoutview.sip
%Include layout/qgslayoutviewtool.sip
%Include layout/qgslayoutviewtooladditem.sip

View File

@ -0,0 +1,90 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/layout/qgslayoutruler.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsLayoutRuler: QWidget
{
%Docstring
A custom ruler widget for use with QgsLayoutView, displaying the
current zoom and position of the visible layout and for interacting
with guides in a layout.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgslayoutruler.h"
%End
public:
explicit QgsLayoutRuler( QWidget *parent /TransferThis/ = 0, Qt::Orientation orientation = Qt::Horizontal );
%Docstring
Constructor for QgsLayoutRuler, with the specified ``parent`` widget and ``orientation``.
%End
virtual QSize minimumSizeHint() const;
void setSceneTransform( const QTransform &transform );
%Docstring
Sets the current scene ``transform``. This is usually the transform set for a view
showing the associated scene, in order to synchronize the view's display of
the scene with the rulers.
%End
QgsLayoutView *layoutView();
%Docstring
Returns the current layout view associated with the ruler.
.. seealso:: setLayoutView()
:rtype: QgsLayoutView
%End
void setLayoutView( QgsLayoutView *view );
%Docstring
Sets the current layout ``view`` to synchronize the ruler with.
.. seealso:: layoutView()
%End
int rulerSize() const;
%Docstring
Returns the ruler size (either the height of a horizontal ruler or the
width of a vertical rule).
:rtype: int
%End
public slots:
void setCursorPosition( QPointF position );
%Docstring
Updates the ``position`` of the marker showing the current mouse position within
the view.
``position`` is in layout coordinates.
%End
protected:
virtual void paintEvent( QPaintEvent *event );
virtual void mouseMoveEvent( QMouseEvent *event );
signals:
void cursorPosChanged( QPointF );
%Docstring
Is emitted when mouse cursor coordinates change
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/layout/qgslayoutruler.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -78,6 +78,18 @@ class QgsLayoutView: QGraphicsView
Sets the zoom ``level`` for the view, where a zoom level of 1.0 corresponds to 100%.
%End
void setHorizontalRuler( QgsLayoutRuler *ruler );
%Docstring
Sets a horizontal ``ruler`` to synchronize with the view state.
.. seealso:: setVerticalRuler()
%End
void setVerticalRuler( QgsLayoutRuler *ruler );
%Docstring
Sets a vertical ``ruler`` to synchronize with the view state.
.. seealso:: setHorizontalRuler()
%End
public slots:
void zoomFull();
@ -122,6 +134,14 @@ class QgsLayoutView: QGraphicsView
void emitZoomLevelChanged();
void updateRulers();
%Docstring
Updates associated rulers after view extent or zoom has changed.
This should be called after calling any of the QGraphicsView
base class methods which alter the view's zoom level or extent,
i.e. QGraphicsView.fitInView().
%End
signals:
void layoutSet( QgsLayout *layout );

View File

@ -28,6 +28,7 @@
#include "qgslayoutviewtoolselect.h"
#include "qgsgui.h"
#include "qgslayoutitemguiregistry.h"
#include "qgslayoutruler.h"
#include <QShortcut>
#include <QComboBox>
#include <QLineEdit>
@ -88,11 +89,22 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
centralWidget()->layout()->setSpacing( 0 );
centralWidget()->layout()->setMargin( 0 );
centralWidget()->layout()->setContentsMargins( 0, 0, 0, 0 );
mHorizontalRuler = new QgsLayoutRuler( nullptr, Qt::Horizontal );
mVerticalRuler = new QgsLayoutRuler( nullptr, Qt::Vertical );
mRulerLayoutFix = new QWidget();
mRulerLayoutFix->setAttribute( Qt::WA_NoMousePropagation );
mRulerLayoutFix->setBackgroundRole( QPalette::Window );
mRulerLayoutFix->setFixedSize( mVerticalRuler->rulerSize(), mHorizontalRuler->rulerSize() );
viewLayout->addWidget( mRulerLayoutFix, 0, 0 );
viewLayout->addWidget( mHorizontalRuler, 0, 1 );
viewLayout->addWidget( mVerticalRuler, 1, 0 );
mView = new QgsLayoutView();
//mView->setMapCanvas( mQgis->mapCanvas() );
mView->setContentsMargins( 0, 0, 0, 0 );
//mView->setHorizontalRuler( mHorizontalRuler );
//mView->setVerticalRuler( mVerticalRuler );
mView->setHorizontalRuler( mHorizontalRuler );
mView->setVerticalRuler( mVerticalRuler );
viewLayout->addWidget( mView, 1, 1 );
//view does not accept focus via tab
mView->setFocusPolicy( Qt::ClickFocus );
@ -186,6 +198,9 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
mView->setFocus();
connect( mView, &QgsLayoutView::zoomLevelChanged, this, &QgsLayoutDesignerDialog::updateStatusZoom );
connect( mView, &QgsLayoutView::cursorPosChanged, this, &QgsLayoutDesignerDialog::updateStatusCursorPos );
//also listen out for position updates from the horizontal/vertical rulers
connect( mHorizontalRuler, &QgsLayoutRuler::cursorPosChanged, this, &QgsLayoutDesignerDialog::updateStatusCursorPos );
connect( mVerticalRuler, &QgsLayoutRuler::cursorPosChanged, this, &QgsLayoutDesignerDialog::updateStatusCursorPos );
restoreWindowState();
}

View File

@ -26,6 +26,7 @@ class QgsLayoutViewToolAddItem;
class QgsLayoutViewToolPan;
class QgsLayoutViewToolZoom;
class QgsLayoutViewToolSelect;
class QgsLayoutRuler;
class QComboBox;
class QSlider;
class QLabel;
@ -133,6 +134,9 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
QActionGroup *mToolsActionGroup = nullptr;
QgsLayoutView *mView = nullptr;
QgsLayoutRuler *mHorizontalRuler = nullptr;
QgsLayoutRuler *mVerticalRuler = nullptr;
QWidget *mRulerLayoutFix = nullptr;
//! Combobox in status bar which shows/adjusts current zoom level
QComboBox *mStatusZoomCombo = nullptr;

View File

@ -159,6 +159,7 @@ SET(QGIS_GUI_SRCS
layertree/qgslayertreeviewdefaultactions.cpp
layout/qgslayoutitemguiregistry.cpp
layout/qgslayoutruler.cpp
layout/qgslayoutview.cpp
layout/qgslayoutviewmouseevent.cpp
layout/qgslayoutviewrubberband.cpp
@ -634,6 +635,7 @@ SET(QGIS_GUI_MOC_HDRS
layout/qgslayoutdesignerinterface.h
layout/qgslayoutitemguiregistry.h
layout/qgslayoutruler.h
layout/qgslayoutview.h
layout/qgslayoutviewtool.h
layout/qgslayoutviewtooladditem.h

View File

@ -0,0 +1,453 @@
/***************************************************************************
qgslayoutruler.cpp
------------------
Date : July 2017
Copyright : (C) 2017 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 "qgslayoutruler.h"
#include "qgslayout.h"
#include "qgis.h"
#include "qgslayoutview.h"
#include <QDragEnterEvent>
#include <QGraphicsLineItem>
#include <QPainter>
#include <cmath>
const int RULER_FONT_SIZE = 8;
const unsigned int COUNT_VALID_MULTIPLES = 3;
const unsigned int COUNT_VALID_MAGNITUDES = 5;
const int QgsLayoutRuler::VALID_SCALE_MULTIPLES[] = {1, 2, 5};
const int QgsLayoutRuler::VALID_SCALE_MAGNITUDES[] = {1, 10, 100, 1000, 10000};
QgsLayoutRuler::QgsLayoutRuler( QWidget *parent, Qt::Orientation orientation )
: QWidget( parent )
, mOrientation( orientation )
{
setMouseTracking( true );
//calculate minimum size required for ruler text
mRulerFont.setPointSize( RULER_FONT_SIZE );
mRulerFontMetrics.reset( new QFontMetrics( mRulerFont ) );
//calculate ruler sizes and marker separations
//minimum gap required between major ticks is 3 digits * 250%, based on appearance
mScaleMinPixelsWidth = mRulerFontMetrics->width( QStringLiteral( "000" ) ) * 2.5;
//minimum ruler height is twice the font height in pixels
mRulerMinSize = mRulerFontMetrics->height() * 1.5;
mMinPixelsPerDivision = mRulerMinSize / 4;
//each small division must be at least 2 pixels apart
if ( mMinPixelsPerDivision < 2 )
mMinPixelsPerDivision = 2;
mPixelsBetweenLineAndText = mRulerMinSize / 10;
mTextBaseline = mRulerMinSize / 1.667;
mMinSpacingVerticalLabels = mRulerMinSize / 5;
}
QSize QgsLayoutRuler::minimumSizeHint() const
{
return QSize( mRulerMinSize, mRulerMinSize );
}
void QgsLayoutRuler::paintEvent( QPaintEvent *event )
{
Q_UNUSED( event );
if ( !mView || !mView->currentLayout() )
{
return;
}
#if 0
QgsLayout *layout = mView->currentLayout();
#endif
QPainter p( this );
QTransform t = mTransform.inverted();
p.setFont( mRulerFont );
// keep same default color, but lower opacity a tad
QBrush brush = p.brush();
QColor color = brush.color();
color.setAlphaF( 0.7 );
brush.setColor( color );
p.setBrush( brush );
QPen pen = p.pen();
color = pen.color();
color.setAlphaF( 0.7 );
pen.setColor( color );
p.setPen( pen );
//find optimum scale for ruler (size of numbered divisions)
int magnitude = 1;
int multiple = 1;
int mmDisplay = optimumScale( mScaleMinPixelsWidth, magnitude, multiple );
//find optimum number of small divisions
int numSmallDivisions = optimumNumberDivisions( mmDisplay, multiple );
switch ( mOrientation )
{
case Qt::Horizontal:
{
if ( qgsDoubleNear( width(), 0 ) )
{
return;
}
//start x-coordinate
double startX = t.map( QPointF( 0, 0 ) ).x();
double endX = t.map( QPointF( width(), 0 ) ).x();
//start marker position in mm
double markerPos = ( floor( startX / mmDisplay ) + 1 ) * mmDisplay;
//draw minor ticks marks which occur before first major tick
drawSmallDivisions( &p, markerPos, numSmallDivisions, -mmDisplay );
while ( markerPos <= endX )
{
double pixelCoord = mTransform.map( QPointF( markerPos, 0 ) ).x();
//draw large division and text
p.drawLine( pixelCoord, 0, pixelCoord, mRulerMinSize );
p.drawText( QPointF( pixelCoord + mPixelsBetweenLineAndText, mTextBaseline ), QString::number( markerPos ) );
//draw small divisions
drawSmallDivisions( &p, markerPos, numSmallDivisions, mmDisplay, endX );
markerPos += mmDisplay;
}
break;
}
case Qt::Vertical:
{
if ( qgsDoubleNear( height(), 0 ) )
{
return;
}
double startY = t.map( QPointF( 0, 0 ) ).y(); //start position in mm (total including space between pages)
double endY = t.map( QPointF( 0, height() ) ).y(); //stop position in mm (total including space between pages)
#if 0 // TODO
int startPage = ( int )( startY / ( layout->paperHeight() + layout->spaceBetweenPages() ) );
#endif
int startPage = 0;
if ( startPage < 0 )
{
startPage = 0;
}
if ( startY < 0 )
{
double beforePageCoord = -mmDisplay;
double firstPageY = mTransform.map( QPointF( 0, 0 ) ).y();
//draw negative rulers which fall before first page
while ( beforePageCoord > startY )
{
double pixelCoord = mTransform.map( QPointF( 0, beforePageCoord ) ).y();
p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
//calc size of label
QString label = QString::number( beforePageCoord );
int labelSize = mRulerFontMetrics->width( label );
//draw label only if it fits in before start of next page
if ( pixelCoord + labelSize + 8 < firstPageY )
{
drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
}
//draw small divisions
drawSmallDivisions( &p, beforePageCoord, numSmallDivisions, mmDisplay );
beforePageCoord -= mmDisplay;
}
//draw minor ticks marks which occur before first major tick
drawSmallDivisions( &p, beforePageCoord + mmDisplay, numSmallDivisions, -mmDisplay, startY );
}
#if 0 //TODO
int endPage = ( int )( endY / ( layout->paperHeight() + layout->spaceBetweenPages() ) );
if ( endPage > ( mLayout->numPages() - 1 ) )
{
endPage = mLayout->numPages() - 1;
}
#endif
int endPage = 0;
double nextPageStartPos = 0;
int nextPageStartPixel = 0;
for ( int i = startPage; i <= endPage; ++i )
{
double pageCoord = 0; //page coordinate in mm
//total (composition) coordinate in mm, including space between pages
#if 0 //TODO
double totalCoord = i * ( layout->paperHeight() + layout->spaceBetweenPages() );
//position of next page
if ( i < endPage )
{
//not the last page
nextPageStartPos = ( i + 1 ) * ( layout->paperHeight() + layout->spaceBetweenPages() );
nextPageStartPixel = mTransform.map( QPointF( 0, nextPageStartPos ) ).y();
}
else
{
//is the last page
nextPageStartPos = 0;
nextPageStartPixel = 0;
}
#endif
double totalCoord = 0;
while ( ( totalCoord < nextPageStartPos ) || ( ( nextPageStartPos == 0 ) && ( totalCoord <= endY ) ) )
{
double pixelCoord = mTransform.map( QPointF( 0, totalCoord ) ).y();
p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
//calc size of label
QString label = QString::number( pageCoord );
int labelSize = mRulerFontMetrics->width( label );
//draw label only if it fits in before start of next page
if ( ( pixelCoord + labelSize + 8 < nextPageStartPixel )
|| ( nextPageStartPixel == 0 ) )
{
drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
}
//draw small divisions
drawSmallDivisions( &p, totalCoord, numSmallDivisions, mmDisplay, nextPageStartPos );
pageCoord += mmDisplay;
totalCoord += mmDisplay;
}
}
break;
}
}
//draw current marker pos
drawMarkerPos( &p );
}
void QgsLayoutRuler::drawMarkerPos( QPainter *painter )
{
//draw current marker pos in red
painter->setPen( QColor( Qt::red ) );
switch ( mOrientation )
{
case Qt::Horizontal:
{
painter->drawLine( mMarkerPos.x(), 0, mMarkerPos.x(), mRulerMinSize );
break;
}
case Qt::Vertical:
{
painter->drawLine( 0, mMarkerPos.y(), mRulerMinSize, mMarkerPos.y() );
break;
}
}
}
void QgsLayoutRuler::drawRotatedText( QPainter *painter, QPointF pos, const QString &text )
{
painter->save();
painter->translate( pos.x(), pos.y() );
painter->rotate( 270 );
painter->drawText( 0, 0, text );
painter->restore();
}
void QgsLayoutRuler::drawSmallDivisions( QPainter *painter, double startPos, int numDivisions, double rulerScale, double maxPos )
{
if ( numDivisions == 0 )
return;
//draw small divisions starting at startPos (in mm)
double smallMarkerPos = startPos;
double smallDivisionSpacing = rulerScale / numDivisions;
double pixelCoord;
//draw numDivisions small divisions
for ( int i = 0; i < numDivisions; ++i )
{
smallMarkerPos += smallDivisionSpacing;
if ( maxPos > 0 && smallMarkerPos > maxPos )
{
//stop drawing current division position is past maxPos
return;
}
//calculate pixelCoordinate of the current division
switch ( mOrientation )
{
case Qt::Horizontal:
{
pixelCoord = mTransform.map( QPointF( smallMarkerPos, 0 ) ).x();
break;
}
case Qt::Vertical:
{
pixelCoord = mTransform.map( QPointF( 0, smallMarkerPos ) ).y();
break;
}
}
//calculate height of small division line
double lineSize;
if ( ( numDivisions == 10 && i == 4 ) || ( numDivisions == 4 && i == 1 ) )
{
//if drawing the 5th line of 10 or drawing the 2nd line of 4, then draw it slightly longer
lineSize = mRulerMinSize / 1.5;
}
else
{
lineSize = mRulerMinSize / 1.25;
}
//draw either horizontal or vertical line depending on ruler direction
switch ( mOrientation )
{
case Qt::Horizontal:
{
painter->drawLine( pixelCoord, lineSize, pixelCoord, mRulerMinSize );
break;
}
case Qt::Vertical:
{
painter->drawLine( lineSize, pixelCoord, mRulerMinSize, pixelCoord );
break;
}
}
}
}
int QgsLayoutRuler::optimumScale( double minPixelDiff, int &magnitude, int &multiple )
{
//find optimal ruler display scale
//loop through magnitudes and multiples to find optimum scale
for ( unsigned int magnitudeCandidate = 0; magnitudeCandidate < COUNT_VALID_MAGNITUDES; ++magnitudeCandidate )
{
for ( unsigned int multipleCandidate = 0; multipleCandidate < COUNT_VALID_MULTIPLES; ++multipleCandidate )
{
int candidateScale = VALID_SCALE_MULTIPLES[multipleCandidate] * VALID_SCALE_MAGNITUDES[magnitudeCandidate];
//find pixel size for each step using this candidate scale
double pixelDiff = mTransform.map( QPointF( candidateScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
if ( pixelDiff > minPixelDiff )
{
//found the optimum major scale
magnitude = VALID_SCALE_MAGNITUDES[magnitudeCandidate];
multiple = VALID_SCALE_MULTIPLES[multipleCandidate];
return candidateScale;
}
}
}
return 100000;
}
int QgsLayoutRuler::optimumNumberDivisions( double rulerScale, int scaleMultiple )
{
//calculate size in pixels of each marked ruler unit
double largeDivisionSize = mTransform.map( QPointF( rulerScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
//now calculate optimum small tick scale, depending on marked ruler units
QList<int> validSmallDivisions;
switch ( scaleMultiple )
{
case 1:
//numbers increase by 1 increment each time, e.g., 1, 2, 3 or 10, 20, 30
//so we can draw either 10, 5 or 2 small ticks and have each fall on a nice value
validSmallDivisions << 10 << 5 << 2;
break;
case 2:
//numbers increase by 2 increments each time, e.g., 2, 4, 6 or 20, 40, 60
//so we can draw either 10, 4 or 2 small ticks and have each fall on a nice value
validSmallDivisions << 10 << 4 << 2;
break;
case 5:
//numbers increase by 5 increments each time, e.g., 5, 10, 15 or 100, 500, 1000
//so we can draw either 10 or 5 small ticks and have each fall on a nice value
validSmallDivisions << 10 << 5;
break;
}
//calculate the most number of small divisions we can draw without them being too close to each other
QList<int>::iterator divisions_it;
for ( divisions_it = validSmallDivisions.begin(); divisions_it != validSmallDivisions.end(); ++divisions_it )
{
//find pixel size for this small division
double candidateSize = largeDivisionSize / ( *divisions_it );
//check if this separation is more then allowed min separation
if ( candidateSize >= mMinPixelsPerDivision )
{
//found a good candidate, return it
return ( *divisions_it );
}
}
//unable to find a good candidate
return 0;
}
void QgsLayoutRuler::setSceneTransform( const QTransform &transform )
{
#if 0
QString debug = QString::number( transform.dx() ) + ',' + QString::number( transform.dy() ) + ','
+ QString::number( transform.m11() ) + ',' + QString::number( transform.m22() );
#endif
mTransform = transform;
update();
}
void QgsLayoutRuler::setLayoutView( QgsLayoutView *view )
{
mView = view;
connect( mView, &QgsLayoutView::cursorPosChanged, this, &QgsLayoutRuler::setCursorPosition );
}
void QgsLayoutRuler::setCursorPosition( QPointF position )
{
mMarkerPos = mView->mapFromScene( position );
update();
}
void QgsLayoutRuler::mouseMoveEvent( QMouseEvent *event )
{
mMarkerPos = event->posF();
update();
//update cursor position in status bar
QPointF displayPos = mTransform.inverted().map( event->posF() );
switch ( mOrientation )
{
case Qt::Horizontal:
{
//mouse is over a horizontal ruler, so don't show a y coordinate
displayPos.setY( 0 );
break;
}
case Qt::Vertical:
{
//mouse is over a vertical ruler, so don't show an x coordinate
displayPos.setX( 0 );
break;
}
}
emit cursorPosChanged( displayPos );
}

View File

@ -0,0 +1,133 @@
/***************************************************************************
qgslayoutruler.h
----------------
Date : July 2017
Copyright : (C) 2017 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 QGSLAYOUTRULER_H
#define QGSLAYOUTRULER_H
#include "qgscomposeritem.h"
#include <QWidget>
#include "qgis_gui.h"
class QgsLayout;
class QGraphicsLineItem;
class QgsLayoutView;
/**
* \ingroup gui
* A custom ruler widget for use with QgsLayoutView, displaying the
* current zoom and position of the visible layout and for interacting
* with guides in a layout.
*
* \since QGIS 3.0
*/
class GUI_EXPORT QgsLayoutRuler: public QWidget
{
Q_OBJECT
public:
/**
* Constructor for QgsLayoutRuler, with the specified \a parent widget and \a orientation.
*/
explicit QgsLayoutRuler( QWidget *parent SIP_TRANSFERTHIS = nullptr, Qt::Orientation orientation = Qt::Horizontal );
QSize minimumSizeHint() const override;
/**
* Sets the current scene \a transform. This is usually the transform set for a view
* showing the associated scene, in order to synchronize the view's display of
* the scene with the rulers.
*/
void setSceneTransform( const QTransform &transform );
/**
* Returns the current layout view associated with the ruler.
* \see setLayoutView()
*/
QgsLayoutView *layoutView() { return mView; }
/**
* Sets the current layout \a view to synchronize the ruler with.
* \see layoutView()
*/
void setLayoutView( QgsLayoutView *view );
/**
* Returns the ruler size (either the height of a horizontal ruler or the
* width of a vertical rule).
*/
int rulerSize() const { return mRulerMinSize; }
public slots:
/**
* Updates the \a position of the marker showing the current mouse position within
* the view.
* \a position is in layout coordinates.
*/
void setCursorPosition( QPointF position );
protected:
void paintEvent( QPaintEvent *event ) override;
void mouseMoveEvent( QMouseEvent *event ) override;
private:
static const int VALID_SCALE_MULTIPLES[];
static const int VALID_SCALE_MAGNITUDES[];
Qt::Orientation mOrientation = Qt::Horizontal;
QgsLayoutView *mView = nullptr;
QTransform mTransform;
QPointF mMarkerPos;
QFont mRulerFont;
std::unique_ptr< QFontMetrics > mRulerFontMetrics;
double mScaleMinPixelsWidth = 0.0;
int mRulerMinSize;
int mMinPixelsPerDivision;
int mPixelsBetweenLineAndText;
int mTextBaseline;
int mMinSpacingVerticalLabels;
//! Calculates the optimum labeled units for ruler so that labels are a good distance apart
int optimumScale( double minPixelDiff, int &magnitude, int &multiple );
/**
* Calculate the number of small divisions for each ruler unit, ensuring that they
* are sufficiently spaced.
*/
int optimumNumberDivisions( double rulerScale, int scaleMultiple );
//! Draws vertical text on a painter
void drawRotatedText( QPainter *painter, QPointF pos, const QString &text );
/**
* Draws small ruler divisions.
* Starting at startPos in mm, for numDivisions divisions, with major division spacing of rulerScale (in mm)
* Stop drawing if position exceeds maxPos
*/
void drawSmallDivisions( QPainter *painter, double startPos, int numDivisions, double rulerScale, double maxPos = 0 );
//! Draw current marker pos on ruler
void drawMarkerPos( QPainter *painter );
signals:
//! Is emitted when mouse cursor coordinates change
void cursorPosChanged( QPointF );
};
#endif // QGSLAYOUTRULER_H

View File

@ -22,6 +22,7 @@
#include "qgslayoutviewtooltemporarykeypan.h"
#include "qgslayoutviewtooltemporarykeyzoom.h"
#include "qgslayoutviewtooltemporarymousepan.h"
#include "qgslayoutruler.h"
#include "qgssettings.h"
#include "qgsrectangle.h"
#include "qgsapplication.h"
@ -96,6 +97,7 @@ void QgsLayoutView::scaleSafe( double scale )
scale = qBound( MIN_VIEW_SCALE, scale, MAX_VIEW_SCALE );
setTransform( QTransform::fromScale( scale, scale ) );
emit zoomLevelChanged();
updateRulers();
}
void QgsLayoutView::setZoomLevel( double level )
@ -109,6 +111,21 @@ void QgsLayoutView::setZoomLevel( double level )
double scale = qBound( MIN_VIEW_SCALE, level * dpi / 25.4, MAX_VIEW_SCALE );
setTransform( QTransform::fromScale( scale, scale ) );
emit zoomLevelChanged();
updateRulers();
}
void QgsLayoutView::setHorizontalRuler( QgsLayoutRuler *ruler )
{
mHorizontalRuler = ruler;
ruler->setLayoutView( this );
updateRulers();
}
void QgsLayoutView::setVerticalRuler( QgsLayoutRuler *ruler )
{
mVerticalRuler = ruler;
ruler->setLayoutView( this );
updateRulers();
}
void QgsLayoutView::zoomFull()
@ -136,6 +153,7 @@ void QgsLayoutView::zoomWidth()
fitInView( targetRect, Qt::KeepAspectRatio );
emit zoomLevelChanged();
updateRulers();
}
void QgsLayoutView::zoomIn()
@ -287,6 +305,18 @@ void QgsLayoutView::resizeEvent( QResizeEvent *event )
emit zoomLevelChanged();
}
void QgsLayoutView::updateRulers()
{
if ( mHorizontalRuler )
{
mHorizontalRuler->setSceneTransform( viewportTransform() );
}
if ( mVerticalRuler )
{
mVerticalRuler->setSceneTransform( viewportTransform() );
}
}
void QgsLayoutView::wheelZoom( QWheelEvent *event )
{
//get mouse wheel zoom behavior settings
@ -328,11 +358,4 @@ void QgsLayoutView::wheelZoom( QWheelEvent *event )
{
scaleSafe( 1 / zoomFactor );
}
//update layout for new zoom
emit zoomLevelChanged();
#if 0 // TODO
updateRulers();
#endif
update();
}

View File

@ -28,6 +28,7 @@ class QgsLayoutViewTool;
class QgsLayoutViewToolTemporaryKeyPan;
class QgsLayoutViewToolTemporaryKeyZoom;
class QgsLayoutViewToolTemporaryMousePan;
class QgsLayoutRuler;
/**
* \ingroup gui
@ -99,6 +100,18 @@ class GUI_EXPORT QgsLayoutView: public QGraphicsView
*/
void setZoomLevel( double level );
/**
* Sets a horizontal \a ruler to synchronize with the view state.
* \see setVerticalRuler()
*/
void setHorizontalRuler( QgsLayoutRuler *ruler );
/**
* Sets a vertical \a ruler to synchronize with the view state.
* \see setHorizontalRuler()
*/
void setVerticalRuler( QgsLayoutRuler *ruler );
public slots:
/**
@ -152,6 +165,14 @@ class GUI_EXPORT QgsLayoutView: public QGraphicsView
// methods also adds noise to the API.
void emitZoomLevelChanged();
/**
* Updates associated rulers after view extent or zoom has changed.
* This should be called after calling any of the QGraphicsView
* base class methods which alter the view's zoom level or extent,
* i.e. QGraphicsView::fitInView().
*/
void updateRulers();
signals:
/**
@ -189,6 +210,8 @@ class GUI_EXPORT QgsLayoutView: public QGraphicsView
void keyReleaseEvent( QKeyEvent *event ) override;
void resizeEvent( QResizeEvent *event ) override;
private slots:
private:
//! Zoom layout from a mouse wheel event
@ -202,6 +225,9 @@ class GUI_EXPORT QgsLayoutView: public QGraphicsView
QPoint mMouseCurrentXY;
QgsLayoutRuler *mHorizontalRuler = nullptr;
QgsLayoutRuler *mVerticalRuler = nullptr;
friend class TestQgsLayoutView;
};

View File

@ -48,6 +48,7 @@ void QgsLayoutViewToolPan::layoutMoveEvent( QgsLayoutViewMouseEvent *event )
view()->horizontalScrollBar()->setValue( view()->horizontalScrollBar()->value() - ( event->x() - mLastMousePos.x() ) );
view()->verticalScrollBar()->setValue( view()->verticalScrollBar()->value() - ( event->y() - mLastMousePos.y() ) );
mLastMousePos = event->pos();
view()->updateRulers();
}
void QgsLayoutViewToolPan::layoutReleaseEvent( QgsLayoutViewMouseEvent *event )

View File

@ -29,6 +29,7 @@ void QgsLayoutViewToolTemporaryKeyPan::layoutMoveEvent( QgsLayoutViewMouseEvent
view()->horizontalScrollBar()->setValue( view()->horizontalScrollBar()->value() - ( event->x() - mLastMousePos.x() ) );
view()->verticalScrollBar()->setValue( view()->verticalScrollBar()->value() - ( event->y() - mLastMousePos.y() ) );
mLastMousePos = event->pos();
view()->updateRulers();
}
void QgsLayoutViewToolTemporaryKeyPan::keyReleaseEvent( QKeyEvent *event )

View File

@ -29,6 +29,7 @@ void QgsLayoutViewToolTemporaryMousePan::layoutMoveEvent( QgsLayoutViewMouseEven
view()->horizontalScrollBar()->setValue( view()->horizontalScrollBar()->value() - ( event->x() - mLastMousePos.x() ) );
view()->verticalScrollBar()->setValue( view()->verticalScrollBar()->value() - ( event->y() - mLastMousePos.y() ) );
mLastMousePos = event->pos();
view()->updateRulers();
}
void QgsLayoutViewToolTemporaryMousePan::layoutReleaseEvent( QgsLayoutViewMouseEvent *event )

View File

@ -55,6 +55,7 @@ void QgsLayoutViewToolZoom::layoutPressEvent( QgsLayoutViewMouseEvent *event )
//zoom view to fit desired bounds
view()->fitInView( boundsRect, Qt::KeepAspectRatio );
view()->emitZoomLevelChanged();
view()->updateRulers();
}
else
{
@ -101,6 +102,7 @@ void QgsLayoutViewToolZoom::layoutReleaseEvent( QgsLayoutViewMouseEvent *event )
//zoom view to fit desired bounds
view()->fitInView( newBoundsRect, Qt::KeepAspectRatio );
view()->emitZoomLevelChanged();
view()->updateRulers();
}
void QgsLayoutViewToolZoom::keyPressEvent( QKeyEvent *event )