27#include <QDragEnterEvent>
28#include <QGraphicsLineItem>
33#include "moc_qgslayoutruler.cpp"
35using namespace Qt::StringLiterals;
40const int QgsLayoutRuler::VALID_SCALE_MULTIPLES[] = { 1, 2, 5 };
41const int QgsLayoutRuler::VALID_SCALE_MAGNITUDES[] = { 1, 10, 100, 1000, 10000 };
45 , mOrientation( orientation )
47 setMouseTracking(
true );
51 mRulerFontMetrics = std::make_unique<QFontMetrics>( mRulerFont );
56 mScaleMinPixelsWidth = mRulerFontMetrics->boundingRect( u
"000"_s ).width() * 2.5;
58 mRulerMinSize = mRulerFontMetrics->height() * 1.5;
60 mMinPixelsPerDivision = mRulerMinSize / 4;
62 if ( mMinPixelsPerDivision < 2 )
63 mMinPixelsPerDivision = 2;
65 mPixelsBetweenLineAndText = mRulerMinSize / 10;
66 mTextBaseline = mRulerMinSize / 1.667;
67 mMinSpacingVerticalLabels = mRulerMinSize / 5;
69 const double guideMarkerSize = mRulerFontMetrics->horizontalAdvance(
'*' );
70 mDragGuideTolerance = guideMarkerSize;
71 switch ( mOrientation )
74 mGuideMarker << QPoint( -guideMarkerSize / 2, mRulerMinSize - guideMarkerSize ) << QPoint( 0, mRulerMinSize ) << QPoint( guideMarkerSize / 2, mRulerMinSize - guideMarkerSize );
78 mGuideMarker << QPoint( mRulerMinSize - guideMarkerSize, -guideMarkerSize / 2 ) << QPoint( mRulerMinSize, 0 ) << QPoint( mRulerMinSize - guideMarkerSize, guideMarkerSize / 2 );
85 return QSize( mRulerMinSize, mRulerMinSize );
91 if ( !mView || !mView->currentLayout() )
96 QgsLayout *layout = mView->currentLayout();
99 drawGuideMarkers( &p, layout );
101 const QTransform t = mTransform.inverted();
102 p.setFont( mRulerFont );
104 QBrush brush = p.brush();
105 QColor color = brush.color();
106 color.setAlphaF( 0.7 );
107 brush.setColor( color );
111 color.setAlphaF( 0.7 );
112 pen.setColor( color );
118 const int mmDisplay = optimumScale( mScaleMinPixelsWidth, magnitude, multiple );
121 const int numSmallDivisions = optimumNumberDivisions( mmDisplay, multiple );
123 switch ( mOrientation )
133 const double startX = t.map( QPointF( 0, 0 ) ).x();
134 const double endX = t.map( QPointF( width(), 0 ) ).x();
137 double markerPos = ( std::floor( startX / mmDisplay ) + 1 ) * mmDisplay;
140 drawSmallDivisions( &p, markerPos, numSmallDivisions, -mmDisplay );
142 while ( markerPos <= endX )
144 const double pixelCoord = mTransform.map( QPointF( markerPos, 0 ) ).x();
147 p.drawLine( pixelCoord, 0, pixelCoord, mRulerMinSize );
148 p.drawText( QPointF( pixelCoord + mPixelsBetweenLineAndText, mTextBaseline ), QLocale().toString( markerPos ) );
151 drawSmallDivisions( &p, markerPos, numSmallDivisions, mmDisplay, endX );
153 markerPos += mmDisplay;
164 const double startY = t.map( QPointF( 0, 0 ) ).y();
165 const double endY = t.map( QPointF( 0, height() ) ).y();
171 double currentPageY = 0;
172 for (
int page = 0; page < layout->
pageCollection()->pageCount(); ++page )
174 if ( currentY < startY )
177 currentPageY = currentY;
182 if ( currentY > endY )
188 double beforePageCoord = -mmDisplay;
189 const double firstPageY = mTransform.map( QPointF( 0, 0 ) ).y();
192 while ( beforePageCoord > startY )
194 const double pixelCoord = mTransform.map( QPointF( 0, beforePageCoord ) ).y();
195 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
197 const QString label = QLocale().toString( beforePageCoord );
198 const int labelSize = mRulerFontMetrics->boundingRect( label ).width();
201 if ( pixelCoord + labelSize + 8 < firstPageY )
203 drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
207 drawSmallDivisions( &p, beforePageCoord, numSmallDivisions, mmDisplay );
209 beforePageCoord -= mmDisplay;
213 drawSmallDivisions( &p, beforePageCoord + mmDisplay, numSmallDivisions, -mmDisplay, startY );
216 double nextPageStartPos = 0;
217 int nextPageStartPixel = 0;
219 for (
int i = startPage; i <= endPage; ++i )
221 double pageCoord = 0;
224 double totalCoord = currentPageY;
231 nextPageStartPixel = mTransform.map( QPointF( 0, nextPageStartPos ) ).y();
236 nextPageStartPos = 0;
237 nextPageStartPixel = 0;
240 while ( ( totalCoord < nextPageStartPos ) || ( ( nextPageStartPos == 0 ) && ( totalCoord <= endY ) ) )
242 const double pixelCoord = mTransform.map( QPointF( 0, totalCoord ) ).y();
243 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
245 const QString label = QLocale().toString( pageCoord );
246 const int labelSize = mRulerFontMetrics->boundingRect( label ).width();
249 if ( ( pixelCoord + labelSize + 8 < nextPageStartPixel ) || ( 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