mirror of
https://github.com/qgis/QGIS.git
synced 2025-06-19 00:02:48 -04:00
Restore rulers in layout designer
This commit is contained in:
parent
06976d0ec2
commit
28cd9addab
@ -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
|
||||
|
90
python/gui/layout/qgslayoutruler.sip
Normal file
90
python/gui/layout/qgslayoutruler.sip
Normal 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 *
|
||||
************************************************************************/
|
@ -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 );
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
453
src/gui/layout/qgslayoutruler.cpp
Normal file
453
src/gui/layout/qgslayoutruler.cpp
Normal 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 );
|
||||
}
|
133
src/gui/layout/qgslayoutruler.h
Normal file
133
src/gui/layout/qgslayoutruler.h
Normal 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
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
};
|
||||
|
@ -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 )
|
||||
|
@ -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 )
|
||||
|
@ -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 )
|
||||
|
@ -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 )
|
||||
|
Loading…
x
Reference in New Issue
Block a user