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 ) || ( nextPageStartPixel == 0 ) )
250 drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
254 drawSmallDivisions( &p, totalCoord, numSmallDivisions, mmDisplay, nextPageStartPos );
256 pageCoord += mmDisplay;
257 totalCoord += mmDisplay;
270void QgsLayoutRuler::drawMarkerPos( QPainter *painter )
273 painter->setPen( QColor( Qt::red ) );
274 switch ( mOrientation )
278 painter->drawLine( mMarkerPos.x(), 0, mMarkerPos.x(), mRulerMinSize );
283 painter->drawLine( 0, mMarkerPos.y(), mRulerMinSize, mMarkerPos.y() );
289void QgsLayoutRuler::drawGuideMarkers( QPainter *p,
QgsLayout *layout )
291 const QList<QgsLayoutItemPage *> visiblePages = mView->visiblePages();
292 const QList<QgsLayoutGuide *> guides = layout->
guides().
guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
293 const QgsScopedQPainterState painterState( p );
294 p->setRenderHint( QPainter::Antialiasing,
true );
295 p->setPen( Qt::NoPen );
296 const auto constGuides = guides;
297 for ( QgsLayoutGuide *guide : constGuides )
299 if ( visiblePages.contains( guide->page() ) )
301 if ( guide == mHoverGuide )
303 p->setBrush( QBrush( QColor( 255, 0, 0, 225 ) ) );
307 p->setBrush( QBrush( QColor( 255, 0, 0, 150 ) ) );
310 switch ( mOrientation )
313 point = QPointF( guide->layoutPosition(), 0 );
317 point = QPointF( 0, guide->layoutPosition() );
320 drawGuideAtPos( p, convertLayoutPointToLocal( point ) );
325void QgsLayoutRuler::drawGuideAtPos( QPainter *painter, QPoint pos )
327 switch ( mOrientation )
331 painter->translate( pos.x(), 0 );
332 painter->drawPolygon( mGuideMarker );
333 painter->translate( -pos.x(), 0 );
338 painter->translate( 0, pos.y() );
339 painter->drawPolygon( mGuideMarker );
340 painter->translate( 0, -pos.y() );
346void QgsLayoutRuler::createTemporaryGuideItem()
348 if ( !mView->currentLayout() )
352 mGuideItem =
new QGraphicsLineItem();
355 QPen linePen( Qt::DotLine );
356 linePen.setColor( QColor( 255, 0, 0, 150 ) );
357 linePen.setWidthF( 0 );
358 mGuideItem->setPen( linePen );
360 mView->currentLayout()->addItem( mGuideItem );
363QPointF QgsLayoutRuler::convertLocalPointToLayout( QPoint localPoint )
const
365 const QPoint viewPoint = mView->mapFromGlobal( mapToGlobal( localPoint ) );
366 return mView->mapToScene( viewPoint );
369QPoint QgsLayoutRuler::convertLayoutPointToLocal( QPointF layoutPoint )
const
371 const QPoint viewPoint = mView->mapFromScene( layoutPoint );
372 return mapFromGlobal( mView->mapToGlobal( viewPoint ) );
375QgsLayoutGuide *QgsLayoutRuler::guideAtPoint( QPoint localPoint )
const
377 if ( !mView->currentLayout() )
380 const QPointF layoutPoint = convertLocalPointToLayout( localPoint );
381 const QList<QgsLayoutItemPage *> visiblePages = mView->visiblePages();
382 const QList<QgsLayoutGuide *> guides = mView->currentLayout()->guides().guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
383 QgsLayoutGuide *closestGuide =
nullptr;
384 double minDelta = std::numeric_limits<double>::max();
385 const auto constGuides = guides;
386 for ( QgsLayoutGuide *guide : constGuides )
388 if ( visiblePages.contains( guide->page() ) )
390 double currentDelta = 0;
391 switch ( mOrientation )
394 currentDelta = std::fabs( layoutPoint.x() - guide->layoutPosition() );
398 currentDelta = std::fabs( layoutPoint.y() - guide->layoutPosition() );
401 if ( currentDelta < minDelta )
403 minDelta = currentDelta;
404 closestGuide = guide;
409 if ( minDelta * mView->transform().m11() <= mDragGuideTolerance )
419void QgsLayoutRuler::drawRotatedText( QPainter *painter, QPointF pos,
const QString &text )
421 const QgsScopedQPainterState painterState( painter );
422 painter->translate( pos.x(), pos.y() );
423 painter->rotate( 270 );
424 painter->drawText( 0, 0, text );
427void QgsLayoutRuler::drawSmallDivisions( QPainter *painter,
double startPos,
int numDivisions,
double rulerScale,
double maxPos )
429 if ( numDivisions == 0 )
433 double smallMarkerPos = startPos;
434 const double smallDivisionSpacing = rulerScale / numDivisions;
436 double pixelCoord = 0.0;
439 for (
int i = 0; i < numDivisions; ++i )
441 smallMarkerPos += smallDivisionSpacing;
443 if ( maxPos > 0 && smallMarkerPos > maxPos )
450 switch ( mOrientation )
454 pixelCoord = mTransform.map( QPointF( smallMarkerPos, 0 ) ).x();
459 pixelCoord = mTransform.map( QPointF( 0, smallMarkerPos ) ).y();
466 if ( ( numDivisions == 10 && i == 4 ) || ( numDivisions == 4 && i == 1 ) )
469 lineSize = mRulerMinSize / 1.5;
473 lineSize = mRulerMinSize / 1.25;
477 switch ( mOrientation )
481 painter->drawLine( pixelCoord, lineSize, pixelCoord, mRulerMinSize );
486 painter->drawLine( lineSize, pixelCoord, mRulerMinSize, pixelCoord );
493int QgsLayoutRuler::optimumScale(
double minPixelDiff,
int &magnitude,
int &multiple )
498 for (
unsigned int magnitudeCandidate = 0; magnitudeCandidate <
COUNT_VALID_MAGNITUDES; ++magnitudeCandidate )
500 for (
unsigned int multipleCandidate = 0; multipleCandidate <
COUNT_VALID_MULTIPLES; ++multipleCandidate )
502 const int candidateScale = VALID_SCALE_MULTIPLES[multipleCandidate] * VALID_SCALE_MAGNITUDES[magnitudeCandidate];
504 const double pixelDiff = mTransform.map( QPointF( candidateScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
505 if ( pixelDiff > minPixelDiff )
508 magnitude = VALID_SCALE_MAGNITUDES[magnitudeCandidate];
509 multiple = VALID_SCALE_MULTIPLES[multipleCandidate];
510 return candidateScale;
518int QgsLayoutRuler::optimumNumberDivisions(
double rulerScale,
int scaleMultiple )
521 const double largeDivisionSize = mTransform.map( QPointF( rulerScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
524 QList<int> validSmallDivisions;
525 switch ( scaleMultiple )
530 validSmallDivisions << 10 << 5 << 2;
535 validSmallDivisions << 10 << 4 << 2;
540 validSmallDivisions << 10 << 5;
545 QList<int>::iterator divisions_it;
546 for ( divisions_it = validSmallDivisions.begin(); divisions_it != validSmallDivisions.end(); ++divisions_it )
549 const double candidateSize = largeDivisionSize / ( *divisions_it );
551 if ( candidateSize >= mMinPixelsPerDivision )
554 return ( *divisions_it );
565 mTransform = transform;
582 mMarkerPos = mView->mapFromScene( position );
588 mMarkerPos =
event->pos();
591 if ( !mView->currentLayout() )
595 if ( mCreatingGuide || mDraggingGuide )
598 displayPos = convertLocalPointToLayout( event->pos() );
600 if ( mCreatingGuide )
602 QgsLayout *layout = mView->currentLayout();
608 QPen linePen = mGuideItem->pen();
612 linePen.setColor( QColor( 255, 0, 0, 150 ) );
616 linePen.setColor( QColor( 255, 0, 0, 225 ) );
618 mGuideItem->setPen( linePen );
619 switch ( mOrientation )
624 mGuideItem->setLine( page->scenePos().x(), displayPos.y(), page->scenePos().x() + page->rect().width(), displayPos.y() );
625 displayPos.setX( 0 );
631 mGuideItem->setLine( displayPos.x(), page->scenePos().y(), displayPos.x(), page->scenePos().y() + page->rect().height() );
632 displayPos.setY( 0 );
640 switch ( mOrientation )
644 mView->currentLayout()->guides().setGuideLayoutPosition( mDraggingGuide, displayPos.x() );
645 displayPos.setY( 0 );
650 mView->currentLayout()->guides().setGuideLayoutPosition( mDraggingGuide, displayPos.y() );
651 displayPos.setX( 0 );
660 mHoverGuide = guideAtPoint( event->pos() );
663 setCursor( mOrientation == Qt::Vertical ? Qt::SplitVCursor : Qt::SplitHCursor );
667 setCursor( Qt::ArrowCursor );
671 displayPos = mTransform.inverted().map( event->pos() );
672 switch ( mOrientation )
677 displayPos.setY( 0 );
683 displayPos.setX( 0 );
693 if ( !mView->currentLayout() )
696 if ( event->button() == Qt::LeftButton )
698 mDraggingGuide = guideAtPoint( event->pos() );
699 if ( !mDraggingGuide )
702 if ( mView->currentLayout()->pageCollection()->pageCount() > 0 )
704 mCreatingGuide =
true;
705 createTemporaryGuideItem();
710 mDraggingGuideOldPosition = mDraggingGuide->layoutPosition();
712 switch ( mOrientation )
716 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitHCursor : Qt::SplitVCursor );
720 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitVCursor : Qt::SplitHCursor );
728 if ( !mView->currentLayout() )
731 if ( event->button() == Qt::LeftButton )
733 if ( mDraggingGuide )
735 QApplication::restoreOverrideCursor();
737 const QPointF layoutPoint = convertLocalPointToLayout( event->pos() );
741 bool deleteGuide =
false;
742 switch ( mDraggingGuide->orientation() )
745 if ( layoutPoint.y() < page->scenePos().y() || layoutPoint.y() > page->scenePos().y() + page->rect().height() )
750 if ( layoutPoint.x() < page->scenePos().x() || layoutPoint.x() > page->scenePos().x() + page->rect().width() )
757 mView->currentLayout()->guides().removeGuide( mDraggingGuide );
759 mDraggingGuide =
nullptr;
763 mCreatingGuide =
false;
764 QApplication::restoreOverrideCursor();
766 mGuideItem =
nullptr;
769 switch ( mOrientation )
773 if ( event->pos().y() <= height() )
779 if ( event->pos().x() <= width() )
785 QgsLayout *layout = mView->currentLayout();
788 const QPointF scenePos = convertLocalPointToLayout( event->pos() );
793 std::unique_ptr<QgsLayoutGuide> guide;
794 switch ( mOrientation )
810 mView->currentLayout()->guides().addGuide( guide.release() );
813 else if ( event->button() == Qt::RightButton )
815 if ( mCreatingGuide || mDraggingGuide )
817 QApplication::restoreOverrideCursor();
819 mGuideItem =
nullptr;
820 mCreatingGuide =
false;
821 if ( mDraggingGuide )
823 mDraggingGuide->setLayoutPosition( mDraggingGuideOldPosition );
825 mDraggingGuide =
nullptr;
828 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