21#include <QDragEnterEvent>
22#include <QGraphicsLineItem>
30const int QgsLayoutRuler::VALID_SCALE_MULTIPLES[] = {1, 2, 5};
31const int QgsLayoutRuler::VALID_SCALE_MAGNITUDES[] = {1, 10, 100, 1000, 10000};
35 , mOrientation( orientation )
37 setMouseTracking(
true );
41 mRulerFontMetrics.reset(
new QFontMetrics( mRulerFont ) );
46 mScaleMinPixelsWidth = mRulerFontMetrics->boundingRect( QStringLiteral(
"000" ) ).width() * 2.5;
48 mRulerMinSize = mRulerFontMetrics->height() * 1.5;
50 mMinPixelsPerDivision = mRulerMinSize / 4;
52 if ( mMinPixelsPerDivision < 2 )
53 mMinPixelsPerDivision = 2;
55 mPixelsBetweenLineAndText = mRulerMinSize / 10;
56 mTextBaseline = mRulerMinSize / 1.667;
57 mMinSpacingVerticalLabels = mRulerMinSize / 5;
59 const double guideMarkerSize = mRulerFontMetrics->horizontalAdvance(
'*' );
60 mDragGuideTolerance = guideMarkerSize;
61 switch ( mOrientation )
64 mGuideMarker << QPoint( -guideMarkerSize / 2, mRulerMinSize - guideMarkerSize ) << QPoint( 0, mRulerMinSize ) <<
65 QPoint( guideMarkerSize / 2, mRulerMinSize - guideMarkerSize );
69 mGuideMarker << QPoint( mRulerMinSize - guideMarkerSize, -guideMarkerSize / 2 ) << QPoint( mRulerMinSize, 0 ) <<
70 QPoint( mRulerMinSize - guideMarkerSize, guideMarkerSize / 2 );
77 return QSize( mRulerMinSize, mRulerMinSize );
91 drawGuideMarkers( &p, layout );
93 const QTransform t = mTransform.inverted();
94 p.setFont( mRulerFont );
96 QBrush brush = p.brush();
97 QColor color = brush.color();
98 color.setAlphaF( 0.7 );
99 brush.setColor( color );
103 color.setAlphaF( 0.7 );
104 pen.setColor( color );
110 const int mmDisplay = optimumScale( mScaleMinPixelsWidth, magnitude, multiple );
113 const int numSmallDivisions = optimumNumberDivisions( mmDisplay, multiple );
115 switch ( mOrientation )
125 const double startX = t.map( QPointF( 0, 0 ) ).x();
126 const double endX = t.map( QPointF( width(), 0 ) ).x();
129 double markerPos = ( std::floor( startX / mmDisplay ) + 1 ) * mmDisplay;
132 drawSmallDivisions( &p, markerPos, numSmallDivisions, -mmDisplay );
134 while ( markerPos <= endX )
136 const double pixelCoord = mTransform.map( QPointF( markerPos, 0 ) ).x();
139 p.drawLine( pixelCoord, 0, pixelCoord, mRulerMinSize );
140 p.drawText( QPointF( pixelCoord + mPixelsBetweenLineAndText, mTextBaseline ), QLocale().toString( markerPos ) );
143 drawSmallDivisions( &p, markerPos, numSmallDivisions, mmDisplay, endX );
145 markerPos += mmDisplay;
156 const double startY = t.map( QPointF( 0, 0 ) ).y();
157 const double endY = t.map( QPointF( 0, height() ) ).y();
163 double currentPageY = 0;
164 for (
int page = 0; page < layout->
pageCollection()->pageCount(); ++page )
166 if ( currentY < startY )
169 currentPageY = currentY;
174 if ( currentY > endY )
180 double beforePageCoord = -mmDisplay;
181 const double firstPageY = mTransform.map( QPointF( 0, 0 ) ).y();
184 while ( beforePageCoord > startY )
186 const double pixelCoord = mTransform.map( QPointF( 0, beforePageCoord ) ).y();
187 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
189 const QString label = QLocale().toString( beforePageCoord );
190 const int labelSize = mRulerFontMetrics->boundingRect( label ).width();
193 if ( pixelCoord + labelSize + 8 < firstPageY )
195 drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
199 drawSmallDivisions( &p, beforePageCoord, numSmallDivisions, mmDisplay );
201 beforePageCoord -= mmDisplay;
205 drawSmallDivisions( &p, beforePageCoord + mmDisplay, numSmallDivisions, -mmDisplay, startY );
208 double nextPageStartPos = 0;
209 int nextPageStartPixel = 0;
211 for (
int i = startPage; i <= endPage; ++i )
213 double pageCoord = 0;
216 double totalCoord = currentPageY;
223 nextPageStartPixel = mTransform.map( QPointF( 0, nextPageStartPos ) ).y();
228 nextPageStartPos = 0;
229 nextPageStartPixel = 0;
232 while ( ( totalCoord < nextPageStartPos ) || ( ( nextPageStartPos == 0 ) && ( totalCoord <= endY ) ) )
234 const double pixelCoord = mTransform.map( QPointF( 0, totalCoord ) ).y();
235 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
237 const QString label = QLocale().toString( pageCoord );
238 const int labelSize = mRulerFontMetrics->boundingRect( label ).width();
241 if ( ( pixelCoord + labelSize + 8 < nextPageStartPixel )
242 || ( nextPageStartPixel == 0 ) )
244 drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
248 drawSmallDivisions( &p, totalCoord, numSmallDivisions, mmDisplay, nextPageStartPos );
250 pageCoord += mmDisplay;
251 totalCoord += mmDisplay;
264void QgsLayoutRuler::drawMarkerPos( QPainter *painter )
267 painter->setPen( QColor( Qt::red ) );
268 switch ( mOrientation )
272 painter->drawLine( mMarkerPos.x(), 0, mMarkerPos.x(), mRulerMinSize );
277 painter->drawLine( 0, mMarkerPos.y(), mRulerMinSize, mMarkerPos.y() );
283void QgsLayoutRuler::drawGuideMarkers( QPainter *p,
QgsLayout *layout )
285 const QList< QgsLayoutItemPage * > visiblePages = mView->
visiblePages();
286 const QList< QgsLayoutGuide * > guides = layout->
guides().
guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
288 p->setRenderHint( QPainter::Antialiasing,
true );
289 p->setPen( Qt::NoPen );
290 const auto constGuides = guides;
293 if ( visiblePages.contains( guide->page() ) )
295 if ( guide == mHoverGuide )
297 p->setBrush( QBrush( QColor( 255, 0, 0, 225 ) ) );
301 p->setBrush( QBrush( QColor( 255, 0, 0, 150 ) ) );
304 switch ( mOrientation )
307 point = QPointF( guide->layoutPosition(), 0 );
311 point = QPointF( 0, guide->layoutPosition() );
314 drawGuideAtPos( p, convertLayoutPointToLocal( point ) );
319void QgsLayoutRuler::drawGuideAtPos( QPainter *painter, QPoint pos )
321 switch ( mOrientation )
325 painter->translate( pos.x(), 0 );
326 painter->drawPolygon( mGuideMarker );
327 painter->translate( -pos.x(), 0 );
332 painter->translate( 0, pos.y() );
333 painter->drawPolygon( mGuideMarker );
334 painter->translate( 0, -pos.y() );
340void QgsLayoutRuler::createTemporaryGuideItem()
346 mGuideItem =
new QGraphicsLineItem();
349 QPen linePen( Qt::DotLine );
350 linePen.setColor( QColor( 255, 0, 0, 150 ) );
351 linePen.setWidthF( 0 );
352 mGuideItem->setPen( linePen );
357QPointF QgsLayoutRuler::convertLocalPointToLayout( QPoint localPoint )
const
359 const QPoint viewPoint = mView->mapFromGlobal( mapToGlobal( localPoint ) );
360 return mView->mapToScene( viewPoint );
363QPoint QgsLayoutRuler::convertLayoutPointToLocal( QPointF layoutPoint )
const
365 const QPoint viewPoint = mView->mapFromScene( layoutPoint );
366 return mapFromGlobal( mView->mapToGlobal( viewPoint ) );
369QgsLayoutGuide *QgsLayoutRuler::guideAtPoint( QPoint localPoint )
const
374 const QPointF layoutPoint = convertLocalPointToLayout( localPoint );
375 const QList< QgsLayoutItemPage * > visiblePages = mView->
visiblePages();
376 const QList< QgsLayoutGuide * > guides = mView->
currentLayout()->
guides().
guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
378 double minDelta = std::numeric_limits<double>::max();
379 const auto constGuides = guides;
382 if ( visiblePages.contains( guide->page() ) )
384 double currentDelta = 0;
385 switch ( mOrientation )
388 currentDelta = std::fabs( layoutPoint.x() - guide->layoutPosition() );
392 currentDelta = std::fabs( layoutPoint.y() - guide->layoutPosition() );
395 if ( currentDelta < minDelta )
397 minDelta = currentDelta;
398 closestGuide = guide;
403 if ( minDelta * mView->transform().m11() <= mDragGuideTolerance )
413void QgsLayoutRuler::drawRotatedText( QPainter *painter, QPointF pos,
const QString &text )
416 painter->translate( pos.x(), pos.y() );
417 painter->rotate( 270 );
418 painter->drawText( 0, 0, text );
421void QgsLayoutRuler::drawSmallDivisions( QPainter *painter,
double startPos,
int numDivisions,
double rulerScale,
double maxPos )
423 if ( numDivisions == 0 )
427 double smallMarkerPos = startPos;
428 const double smallDivisionSpacing = rulerScale / numDivisions;
430 double pixelCoord = 0.0;
433 for (
int i = 0; i < numDivisions; ++i )
435 smallMarkerPos += smallDivisionSpacing;
437 if ( maxPos > 0 && smallMarkerPos > maxPos )
444 switch ( mOrientation )
448 pixelCoord = mTransform.map( QPointF( smallMarkerPos, 0 ) ).x();
453 pixelCoord = mTransform.map( QPointF( 0, smallMarkerPos ) ).y();
460 if ( ( numDivisions == 10 && i == 4 ) || ( numDivisions == 4 && i == 1 ) )
463 lineSize = mRulerMinSize / 1.5;
467 lineSize = mRulerMinSize / 1.25;
471 switch ( mOrientation )
475 painter->drawLine( pixelCoord, lineSize, pixelCoord, mRulerMinSize );
480 painter->drawLine( lineSize, pixelCoord, mRulerMinSize, pixelCoord );
487int QgsLayoutRuler::optimumScale(
double minPixelDiff,
int &magnitude,
int &multiple )
492 for (
unsigned int magnitudeCandidate = 0; magnitudeCandidate <
COUNT_VALID_MAGNITUDES; ++magnitudeCandidate )
494 for (
unsigned int multipleCandidate = 0; multipleCandidate <
COUNT_VALID_MULTIPLES; ++multipleCandidate )
496 const int candidateScale = VALID_SCALE_MULTIPLES[multipleCandidate] * VALID_SCALE_MAGNITUDES[magnitudeCandidate];
498 const double pixelDiff = mTransform.map( QPointF( candidateScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
499 if ( pixelDiff > minPixelDiff )
502 magnitude = VALID_SCALE_MAGNITUDES[magnitudeCandidate];
503 multiple = VALID_SCALE_MULTIPLES[multipleCandidate];
504 return candidateScale;
512int QgsLayoutRuler::optimumNumberDivisions(
double rulerScale,
int scaleMultiple )
515 const double largeDivisionSize = mTransform.map( QPointF( rulerScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
518 QList<int> validSmallDivisions;
519 switch ( scaleMultiple )
524 validSmallDivisions << 10 << 5 << 2;
529 validSmallDivisions << 10 << 4 << 2;
534 validSmallDivisions << 10 << 5;
539 QList<int>::iterator divisions_it;
540 for ( divisions_it = validSmallDivisions.begin(); divisions_it != validSmallDivisions.end(); ++divisions_it )
543 const double candidateSize = largeDivisionSize / ( *divisions_it );
545 if ( candidateSize >= mMinPixelsPerDivision )
548 return ( *divisions_it );
559 mTransform = transform;
576 mMarkerPos = mView->mapFromScene( position );
582 mMarkerPos =
event->pos();
589 if ( mCreatingGuide || mDraggingGuide )
592 displayPos = convertLocalPointToLayout( event->pos() );
594 if ( mCreatingGuide )
602 QPen linePen = mGuideItem->pen();
606 linePen.setColor( QColor( 255, 0, 0, 150 ) );
610 linePen.setColor( QColor( 255, 0, 0, 225 ) );
612 mGuideItem->setPen( linePen );
613 switch ( mOrientation )
618 mGuideItem->setLine( page->scenePos().x(), displayPos.y(), page->scenePos().x() + page->rect().width(), displayPos.y() );
619 displayPos.setX( 0 );
625 mGuideItem->setLine( displayPos.x(), page->scenePos().y(), displayPos.x(), page->scenePos().y() + page->rect().height() );
626 displayPos.setY( 0 );
634 switch ( mOrientation )
639 displayPos.setY( 0 );
645 displayPos.setX( 0 );
654 mHoverGuide = guideAtPoint( event->pos() );
657 setCursor( mOrientation == Qt::Vertical ? Qt::SplitVCursor : Qt::SplitHCursor );
661 setCursor( Qt::ArrowCursor );
665 displayPos = mTransform.inverted().map( event->pos() );
666 switch ( mOrientation )
671 displayPos.setY( 0 );
677 displayPos.setX( 0 );
690 if ( event->button() == Qt::LeftButton )
692 mDraggingGuide = guideAtPoint( event->pos() );
693 if ( !mDraggingGuide )
698 mCreatingGuide =
true;
699 createTemporaryGuideItem();
706 switch ( mOrientation )
710 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitHCursor : Qt::SplitVCursor );
714 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitVCursor : Qt::SplitHCursor );
725 if ( event->button() == Qt::LeftButton )
727 if ( mDraggingGuide )
729 QApplication::restoreOverrideCursor();
731 const QPointF layoutPoint = convertLocalPointToLayout( event->pos() );
735 bool deleteGuide =
false;
739 if ( layoutPoint.y() < page->scenePos().y() || layoutPoint.y() > page->scenePos().y() + page->rect().height() )
744 if ( layoutPoint.x() < page->scenePos().x() || layoutPoint.x() > page->scenePos().x() + page->rect().width() )
753 mDraggingGuide =
nullptr;
757 mCreatingGuide =
false;
758 QApplication::restoreOverrideCursor();
760 mGuideItem =
nullptr;
763 switch ( mOrientation )
767 if ( event->pos().y() <= height() )
773 if ( event->pos().x() <= width() )
782 const QPointF scenePos = convertLocalPointToLayout( event->pos() );
787 std::unique_ptr< QgsLayoutGuide > guide;
788 switch ( mOrientation )
807 else if ( event->button() == Qt::RightButton )
809 if ( mCreatingGuide || mDraggingGuide )
811 QApplication::restoreOverrideCursor();
813 mGuideItem =
nullptr;
814 mCreatingGuide =
false;
815 if ( mDraggingGuide )
819 mDraggingGuide =
nullptr;
822 mMenu->popup( event->globalPos() );
void addGuide(QgsLayoutGuide *guide)
Adds a guide to the collection.
void removeGuide(QgsLayoutGuide *guide)
Removes the specified guide, and deletes it.
void setGuideLayoutPosition(QgsLayoutGuide *guide, double position)
Sets the absolute position (in layout coordinates) for guide within the layout.
QList< QgsLayoutGuide * > guides()
Returns a list of all guides contained in the collection.
Contains the configuration for a single snap guide used by a layout.
QgsLayoutItemPage * page()
Returns the page the guide is contained within.
Qt::Orientation orientation() const
Returns the guide's orientation.
void setLayoutPosition(double position)
Sets the guide's position in absolute layout units.
double layoutPosition() const
Returns the guide's position in absolute layout units.
Item representing the paper in a layout.
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
int pageNumberForPoint(QPointF point) const
Returns the page number corresponding to a point in the layout (in layout units).
double spaceBetweenPages() const
Returns the space between pages, in layout units.
int pageCount() const
Returns the number of pages in the collection.
QPointF positionOnPage(QPointF point) const
Returns the position within a page of a point in the layout (in layout units).
QgsLayoutItemPage * page(int pageNumber)
Returns a specific page (by pageNumber) from the collection.
QgsLayoutItemPage * pageAtPoint(QPointF point) const
Returns the page at a specified point (in layout coordinates).
void cursorPosChanged(QPointF)
Emitted when mouse cursor coordinates change.
void paintEvent(QPaintEvent *event) override
void setContextMenu(QMenu *menu)
Sets a context menu to show when right clicking occurs on the ruler.
void setCursorPosition(QPointF position)
Updates the position of the marker showing the current mouse position within the view.
void setSceneTransform(const QTransform &transform)
Sets the current scene transform.
void mouseReleaseEvent(QMouseEvent *event) override
void mouseMoveEvent(QMouseEvent *event) override
QgsLayoutRuler(QWidget *parent=nullptr, Qt::Orientation orientation=Qt::Horizontal)
Constructor for QgsLayoutRuler, with the specified parent widget and orientation.
void setLayoutView(QgsLayoutView *view)
Sets the current layout view to synchronize the ruler with.
void mousePressEvent(QMouseEvent *event) override
QSize minimumSizeHint() const override
A graphical widget to display and interact with QgsLayouts.
void cursorPosChanged(QPointF layoutPoint)
Emitted when the mouse cursor coordinates change within the view.
QgsLayout * currentLayout
QList< QgsLayoutItemPage * > visiblePages() const
Returns a list of page items which are currently visible in the view.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout's page collection, which stores and manages page items in the layout.
QgsLayoutGuideCollection & guides()
Returns a reference to the layout's guide collection, which manages page snap guides.
QgsUnitTypes::LayoutUnit units() const
Returns the native units for the layout.
@ ZGuide
Z-value for page guides.
Scoped object for saving and restoring a QPainter object's state.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
const unsigned int COUNT_VALID_MULTIPLES
const unsigned int COUNT_VALID_MAGNITUDES
const int RULER_FONT_SIZE