26#include <QDragEnterEvent>
27#include <QGraphicsLineItem>
32#include "moc_qgslayoutruler.cpp"
34using namespace Qt::StringLiterals;
39const int QgsLayoutRuler::VALID_SCALE_MULTIPLES[] = { 1, 2, 5 };
40const int QgsLayoutRuler::VALID_SCALE_MAGNITUDES[] = { 1, 10, 100, 1000, 10000 };
44 , mOrientation( orientation )
46 setMouseTracking(
true );
50 mRulerFontMetrics = std::make_unique<QFontMetrics>( mRulerFont );
55 mScaleMinPixelsWidth = mRulerFontMetrics->boundingRect( u
"000"_s ).width() * 2.5;
57 mRulerMinSize = mRulerFontMetrics->height() * 1.5;
59 mMinPixelsPerDivision = mRulerMinSize / 4;
61 if ( mMinPixelsPerDivision < 2 )
62 mMinPixelsPerDivision = 2;
64 mPixelsBetweenLineAndText = mRulerMinSize / 10;
65 mTextBaseline = mRulerMinSize / 1.667;
66 mMinSpacingVerticalLabels = mRulerMinSize / 5;
68 const double guideMarkerSize = mRulerFontMetrics->horizontalAdvance(
'*' );
69 mDragGuideTolerance = guideMarkerSize;
70 switch ( mOrientation )
73 mGuideMarker << QPoint( -guideMarkerSize / 2, mRulerMinSize - guideMarkerSize ) << QPoint( 0, mRulerMinSize ) << QPoint( guideMarkerSize / 2, mRulerMinSize - guideMarkerSize );
77 mGuideMarker << QPoint( mRulerMinSize - guideMarkerSize, -guideMarkerSize / 2 ) << QPoint( mRulerMinSize, 0 ) << QPoint( mRulerMinSize - guideMarkerSize, guideMarkerSize / 2 );
84 return QSize( mRulerMinSize, mRulerMinSize );
90 if ( !mView || !mView->currentLayout() )
95 QgsLayout *layout = mView->currentLayout();
98 drawGuideMarkers( &p, layout );
100 const QTransform t = mTransform.inverted();
101 p.setFont( mRulerFont );
103 QBrush brush = p.brush();
104 QColor color = brush.color();
105 color.setAlphaF( 0.7 );
106 brush.setColor( color );
110 color.setAlphaF( 0.7 );
111 pen.setColor( color );
117 const int mmDisplay = optimumScale( mScaleMinPixelsWidth, magnitude, multiple );
120 const int numSmallDivisions = optimumNumberDivisions( mmDisplay, multiple );
122 switch ( mOrientation )
132 const double startX = t.map( QPointF( 0, 0 ) ).x();
133 const double endX = t.map( QPointF( width(), 0 ) ).x();
136 double markerPos = ( std::floor( startX / mmDisplay ) + 1 ) * mmDisplay;
139 drawSmallDivisions( &p, markerPos, numSmallDivisions, -mmDisplay );
141 while ( markerPos <= endX )
143 const double pixelCoord = mTransform.map( QPointF( markerPos, 0 ) ).x();
146 p.drawLine( pixelCoord, 0, pixelCoord, mRulerMinSize );
147 p.drawText( QPointF( pixelCoord + mPixelsBetweenLineAndText, mTextBaseline ), QLocale().toString( markerPos ) );
150 drawSmallDivisions( &p, markerPos, numSmallDivisions, mmDisplay, endX );
152 markerPos += mmDisplay;
163 const double startY = t.map( QPointF( 0, 0 ) ).y();
164 const double endY = t.map( QPointF( 0, height() ) ).y();
170 double currentPageY = 0;
171 for (
int page = 0; page < layout->
pageCollection()->pageCount(); ++page )
173 if ( currentY < startY )
176 currentPageY = currentY;
181 if ( currentY > endY )
187 double beforePageCoord = -mmDisplay;
188 const double firstPageY = mTransform.map( QPointF( 0, 0 ) ).y();
191 while ( beforePageCoord > startY )
193 const double pixelCoord = mTransform.map( QPointF( 0, beforePageCoord ) ).y();
194 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
196 const QString label = QLocale().toString( beforePageCoord );
197 const int labelSize = mRulerFontMetrics->boundingRect( label ).width();
200 if ( pixelCoord + labelSize + 8 < firstPageY )
202 drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
206 drawSmallDivisions( &p, beforePageCoord, numSmallDivisions, mmDisplay );
208 beforePageCoord -= mmDisplay;
212 drawSmallDivisions( &p, beforePageCoord + mmDisplay, numSmallDivisions, -mmDisplay, startY );
215 double nextPageStartPos = 0;
216 int nextPageStartPixel = 0;
218 for (
int i = startPage; i <= endPage; ++i )
220 double pageCoord = 0;
223 double totalCoord = currentPageY;
230 nextPageStartPixel = mTransform.map( QPointF( 0, nextPageStartPos ) ).y();
235 nextPageStartPos = 0;
236 nextPageStartPixel = 0;
239 while ( ( totalCoord < nextPageStartPos ) || ( ( nextPageStartPos == 0 ) && ( totalCoord <= endY ) ) )
241 const double pixelCoord = mTransform.map( QPointF( 0, totalCoord ) ).y();
242 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
244 const QString label = QLocale().toString( pageCoord );
245 const int labelSize = mRulerFontMetrics->boundingRect( label ).width();
248 if ( ( pixelCoord + labelSize + 8 < nextPageStartPixel )
249 || ( nextPageStartPixel == 0 ) )
251 drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
255 drawSmallDivisions( &p, totalCoord, numSmallDivisions, mmDisplay, nextPageStartPos );
257 pageCoord += mmDisplay;
258 totalCoord += mmDisplay;
271void QgsLayoutRuler::drawMarkerPos( QPainter *painter )
274 painter->setPen( QColor( Qt::red ) );
275 switch ( mOrientation )
279 painter->drawLine( mMarkerPos.x(), 0, mMarkerPos.x(), mRulerMinSize );
284 painter->drawLine( 0, mMarkerPos.y(), mRulerMinSize, mMarkerPos.y() );
290void QgsLayoutRuler::drawGuideMarkers( QPainter *p,
QgsLayout *layout )
292 const QList<QgsLayoutItemPage *> visiblePages = mView->visiblePages();
293 const QList<QgsLayoutGuide *> guides = layout->
guides().
guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
294 const QgsScopedQPainterState painterState( p );
295 p->setRenderHint( QPainter::Antialiasing,
true );
296 p->setPen( Qt::NoPen );
297 const auto constGuides = guides;
298 for ( QgsLayoutGuide *guide : constGuides )
300 if ( visiblePages.contains( guide->page() ) )
302 if ( guide == mHoverGuide )
304 p->setBrush( QBrush( QColor( 255, 0, 0, 225 ) ) );
308 p->setBrush( QBrush( QColor( 255, 0, 0, 150 ) ) );
311 switch ( mOrientation )
314 point = QPointF( guide->layoutPosition(), 0 );
318 point = QPointF( 0, guide->layoutPosition() );
321 drawGuideAtPos( p, convertLayoutPointToLocal( point ) );
326void QgsLayoutRuler::drawGuideAtPos( QPainter *painter, QPoint pos )
328 switch ( mOrientation )
332 painter->translate( pos.x(), 0 );
333 painter->drawPolygon( mGuideMarker );
334 painter->translate( -pos.x(), 0 );
339 painter->translate( 0, pos.y() );
340 painter->drawPolygon( mGuideMarker );
341 painter->translate( 0, -pos.y() );
347void QgsLayoutRuler::createTemporaryGuideItem()
349 if ( !mView->currentLayout() )
353 mGuideItem =
new QGraphicsLineItem();
356 QPen linePen( Qt::DotLine );
357 linePen.setColor( QColor( 255, 0, 0, 150 ) );
358 linePen.setWidthF( 0 );
359 mGuideItem->setPen( linePen );
361 mView->currentLayout()->addItem( mGuideItem );
364QPointF QgsLayoutRuler::convertLocalPointToLayout( QPoint localPoint )
const
366 const QPoint viewPoint = mView->mapFromGlobal( mapToGlobal( localPoint ) );
367 return mView->mapToScene( viewPoint );
370QPoint QgsLayoutRuler::convertLayoutPointToLocal( QPointF layoutPoint )
const
372 const QPoint viewPoint = mView->mapFromScene( layoutPoint );
373 return mapFromGlobal( mView->mapToGlobal( viewPoint ) );
376QgsLayoutGuide *QgsLayoutRuler::guideAtPoint( QPoint localPoint )
const
378 if ( !mView->currentLayout() )
381 const QPointF layoutPoint = convertLocalPointToLayout( localPoint );
382 const QList<QgsLayoutItemPage *> visiblePages = mView->visiblePages();
383 const QList<QgsLayoutGuide *> guides = mView->currentLayout()->guides().guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
384 QgsLayoutGuide *closestGuide =
nullptr;
385 double minDelta = std::numeric_limits<double>::max();
386 const auto constGuides = guides;
387 for ( QgsLayoutGuide *guide : constGuides )
389 if ( visiblePages.contains( guide->page() ) )
391 double currentDelta = 0;
392 switch ( mOrientation )
395 currentDelta = std::fabs( layoutPoint.x() - guide->layoutPosition() );
399 currentDelta = std::fabs( layoutPoint.y() - guide->layoutPosition() );
402 if ( currentDelta < minDelta )
404 minDelta = currentDelta;
405 closestGuide = guide;
410 if ( minDelta * mView->transform().m11() <= mDragGuideTolerance )
420void QgsLayoutRuler::drawRotatedText( QPainter *painter, QPointF pos,
const QString &text )
422 const QgsScopedQPainterState painterState( painter );
423 painter->translate( pos.x(), pos.y() );
424 painter->rotate( 270 );
425 painter->drawText( 0, 0, text );
428void QgsLayoutRuler::drawSmallDivisions( QPainter *painter,
double startPos,
int numDivisions,
double rulerScale,
double maxPos )
430 if ( numDivisions == 0 )
434 double smallMarkerPos = startPos;
435 const double smallDivisionSpacing = rulerScale / numDivisions;
437 double pixelCoord = 0.0;
440 for (
int i = 0; i < numDivisions; ++i )
442 smallMarkerPos += smallDivisionSpacing;
444 if ( maxPos > 0 && smallMarkerPos > maxPos )
451 switch ( mOrientation )
455 pixelCoord = mTransform.map( QPointF( smallMarkerPos, 0 ) ).x();
460 pixelCoord = mTransform.map( QPointF( 0, smallMarkerPos ) ).y();
467 if ( ( numDivisions == 10 && i == 4 ) || ( numDivisions == 4 && i == 1 ) )
470 lineSize = mRulerMinSize / 1.5;
474 lineSize = mRulerMinSize / 1.25;
478 switch ( mOrientation )
482 painter->drawLine( pixelCoord, lineSize, pixelCoord, mRulerMinSize );
487 painter->drawLine( lineSize, pixelCoord, mRulerMinSize, pixelCoord );
494int QgsLayoutRuler::optimumScale(
double minPixelDiff,
int &magnitude,
int &multiple )
499 for (
unsigned int magnitudeCandidate = 0; magnitudeCandidate <
COUNT_VALID_MAGNITUDES; ++magnitudeCandidate )
501 for (
unsigned int multipleCandidate = 0; multipleCandidate <
COUNT_VALID_MULTIPLES; ++multipleCandidate )
503 const int candidateScale = VALID_SCALE_MULTIPLES[multipleCandidate] * VALID_SCALE_MAGNITUDES[magnitudeCandidate];
505 const double pixelDiff = mTransform.map( QPointF( candidateScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
506 if ( pixelDiff > minPixelDiff )
509 magnitude = VALID_SCALE_MAGNITUDES[magnitudeCandidate];
510 multiple = VALID_SCALE_MULTIPLES[multipleCandidate];
511 return candidateScale;
519int QgsLayoutRuler::optimumNumberDivisions(
double rulerScale,
int scaleMultiple )
522 const double largeDivisionSize = mTransform.map( QPointF( rulerScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
525 QList<int> validSmallDivisions;
526 switch ( scaleMultiple )
531 validSmallDivisions << 10 << 5 << 2;
536 validSmallDivisions << 10 << 4 << 2;
541 validSmallDivisions << 10 << 5;
546 QList<int>::iterator divisions_it;
547 for ( divisions_it = validSmallDivisions.begin(); divisions_it != validSmallDivisions.end(); ++divisions_it )
550 const double candidateSize = largeDivisionSize / ( *divisions_it );
552 if ( candidateSize >= mMinPixelsPerDivision )
555 return ( *divisions_it );
566 mTransform = transform;
583 mMarkerPos = mView->mapFromScene( position );
589 mMarkerPos =
event->pos();
592 if ( !mView->currentLayout() )
596 if ( mCreatingGuide || mDraggingGuide )
599 displayPos = convertLocalPointToLayout( event->pos() );
601 if ( mCreatingGuide )
603 QgsLayout *layout = mView->currentLayout();
609 QPen linePen = mGuideItem->pen();
613 linePen.setColor( QColor( 255, 0, 0, 150 ) );
617 linePen.setColor( QColor( 255, 0, 0, 225 ) );
619 mGuideItem->setPen( linePen );
620 switch ( mOrientation )
625 mGuideItem->setLine( page->scenePos().x(), displayPos.y(), page->scenePos().x() + page->rect().width(), displayPos.y() );
626 displayPos.setX( 0 );
632 mGuideItem->setLine( displayPos.x(), page->scenePos().y(), displayPos.x(), page->scenePos().y() + page->rect().height() );
633 displayPos.setY( 0 );
641 switch ( mOrientation )
645 mView->currentLayout()->guides().setGuideLayoutPosition( mDraggingGuide, displayPos.x() );
646 displayPos.setY( 0 );
651 mView->currentLayout()->guides().setGuideLayoutPosition( mDraggingGuide, displayPos.y() );
652 displayPos.setX( 0 );
661 mHoverGuide = guideAtPoint( event->pos() );
664 setCursor( mOrientation == Qt::Vertical ? Qt::SplitVCursor : Qt::SplitHCursor );
668 setCursor( Qt::ArrowCursor );
672 displayPos = mTransform.inverted().map( event->pos() );
673 switch ( mOrientation )
678 displayPos.setY( 0 );
684 displayPos.setX( 0 );
694 if ( !mView->currentLayout() )
697 if ( event->button() == Qt::LeftButton )
699 mDraggingGuide = guideAtPoint( event->pos() );
700 if ( !mDraggingGuide )
703 if ( mView->currentLayout()->pageCollection()->pageCount() > 0 )
705 mCreatingGuide =
true;
706 createTemporaryGuideItem();
711 mDraggingGuideOldPosition = mDraggingGuide->layoutPosition();
713 switch ( mOrientation )
717 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitHCursor : Qt::SplitVCursor );
721 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitVCursor : Qt::SplitHCursor );
729 if ( !mView->currentLayout() )
732 if ( event->button() == Qt::LeftButton )
734 if ( mDraggingGuide )
736 QApplication::restoreOverrideCursor();
738 const QPointF layoutPoint = convertLocalPointToLayout( event->pos() );
742 bool deleteGuide =
false;
743 switch ( mDraggingGuide->orientation() )
746 if ( layoutPoint.y() < page->scenePos().y() || layoutPoint.y() > page->scenePos().y() + page->rect().height() )
751 if ( layoutPoint.x() < page->scenePos().x() || layoutPoint.x() > page->scenePos().x() + page->rect().width() )
758 mView->currentLayout()->guides().removeGuide( mDraggingGuide );
760 mDraggingGuide =
nullptr;
764 mCreatingGuide =
false;
765 QApplication::restoreOverrideCursor();
767 mGuideItem =
nullptr;
770 switch ( mOrientation )
774 if ( event->pos().y() <= height() )
780 if ( event->pos().x() <= width() )
786 QgsLayout *layout = mView->currentLayout();
789 const QPointF scenePos = convertLocalPointToLayout( event->pos() );
794 std::unique_ptr<QgsLayoutGuide> guide;
795 switch ( mOrientation )
811 mView->currentLayout()->guides().addGuide( guide.release() );
814 else if ( event->button() == Qt::RightButton )
816 if ( mCreatingGuide || mDraggingGuide )
818 QApplication::restoreOverrideCursor();
820 mGuideItem =
nullptr;
821 mCreatingGuide =
false;
822 if ( mDraggingGuide )
824 mDraggingGuide->setLayoutPosition( mDraggingGuideOldPosition );
826 mDraggingGuide =
nullptr;
829 mMenu->popup( event->globalPos() );
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.
Item representing the paper in a layout.
int page() const
Returns the page the item is currently on, with the first page returning 0.
Provides a method of storing measurements for use in QGIS layouts using a variety of different measur...
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.
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 position)
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.
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.
@ ZGuide
Z-value for page guides.
Qgis::LayoutUnit units() const
Returns the native units for the layout.
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