26#include <QDragEnterEvent>
27#include <QGraphicsLineItem>
31#include "moc_qgslayoutruler.cpp"
36const int QgsLayoutRuler::VALID_SCALE_MULTIPLES[] = { 1, 2, 5 };
37const int QgsLayoutRuler::VALID_SCALE_MAGNITUDES[] = { 1, 10, 100, 1000, 10000 };
41 , mOrientation( orientation )
43 setMouseTracking(
true );
47 mRulerFontMetrics = std::make_unique<QFontMetrics>( mRulerFont );
52 mScaleMinPixelsWidth = mRulerFontMetrics->boundingRect( QStringLiteral(
"000" ) ).width() * 2.5;
54 mRulerMinSize = mRulerFontMetrics->height() * 1.5;
56 mMinPixelsPerDivision = mRulerMinSize / 4;
58 if ( mMinPixelsPerDivision < 2 )
59 mMinPixelsPerDivision = 2;
61 mPixelsBetweenLineAndText = mRulerMinSize / 10;
62 mTextBaseline = mRulerMinSize / 1.667;
63 mMinSpacingVerticalLabels = mRulerMinSize / 5;
65 const double guideMarkerSize = mRulerFontMetrics->horizontalAdvance(
'*' );
66 mDragGuideTolerance = guideMarkerSize;
67 switch ( mOrientation )
70 mGuideMarker << QPoint( -guideMarkerSize / 2, mRulerMinSize - guideMarkerSize ) << QPoint( 0, mRulerMinSize ) << QPoint( guideMarkerSize / 2, mRulerMinSize - guideMarkerSize );
74 mGuideMarker << QPoint( mRulerMinSize - guideMarkerSize, -guideMarkerSize / 2 ) << QPoint( mRulerMinSize, 0 ) << QPoint( mRulerMinSize - guideMarkerSize, guideMarkerSize / 2 );
81 return QSize( mRulerMinSize, mRulerMinSize );
87 if ( !mView || !mView->currentLayout() )
92 QgsLayout *layout = mView->currentLayout();
95 drawGuideMarkers( &p, layout );
97 const QTransform t = mTransform.inverted();
98 p.setFont( mRulerFont );
100 QBrush brush = p.brush();
101 QColor color = brush.color();
102 color.setAlphaF( 0.7 );
103 brush.setColor( color );
107 color.setAlphaF( 0.7 );
108 pen.setColor( color );
114 const int mmDisplay = optimumScale( mScaleMinPixelsWidth, magnitude, multiple );
117 const int numSmallDivisions = optimumNumberDivisions( mmDisplay, multiple );
119 switch ( mOrientation )
129 const double startX = t.map( QPointF( 0, 0 ) ).x();
130 const double endX = t.map( QPointF( width(), 0 ) ).x();
133 double markerPos = ( std::floor( startX / mmDisplay ) + 1 ) * mmDisplay;
136 drawSmallDivisions( &p, markerPos, numSmallDivisions, -mmDisplay );
138 while ( markerPos <= endX )
140 const double pixelCoord = mTransform.map( QPointF( markerPos, 0 ) ).x();
143 p.drawLine( pixelCoord, 0, pixelCoord, mRulerMinSize );
144 p.drawText( QPointF( pixelCoord + mPixelsBetweenLineAndText, mTextBaseline ), QLocale().toString( markerPos ) );
147 drawSmallDivisions( &p, markerPos, numSmallDivisions, mmDisplay, endX );
149 markerPos += mmDisplay;
160 const double startY = t.map( QPointF( 0, 0 ) ).y();
161 const double endY = t.map( QPointF( 0, height() ) ).y();
167 double currentPageY = 0;
168 for (
int page = 0; page < layout->
pageCollection()->pageCount(); ++page )
170 if ( currentY < startY )
173 currentPageY = currentY;
178 if ( currentY > endY )
184 double beforePageCoord = -mmDisplay;
185 const double firstPageY = mTransform.map( QPointF( 0, 0 ) ).y();
188 while ( beforePageCoord > startY )
190 const double pixelCoord = mTransform.map( QPointF( 0, beforePageCoord ) ).y();
191 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
193 const QString label = QLocale().toString( beforePageCoord );
194 const int labelSize = mRulerFontMetrics->boundingRect( label ).width();
197 if ( pixelCoord + labelSize + 8 < firstPageY )
199 drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
203 drawSmallDivisions( &p, beforePageCoord, numSmallDivisions, mmDisplay );
205 beforePageCoord -= mmDisplay;
209 drawSmallDivisions( &p, beforePageCoord + mmDisplay, numSmallDivisions, -mmDisplay, startY );
212 double nextPageStartPos = 0;
213 int nextPageStartPixel = 0;
215 for (
int i = startPage; i <= endPage; ++i )
217 double pageCoord = 0;
220 double totalCoord = currentPageY;
227 nextPageStartPixel = mTransform.map( QPointF( 0, nextPageStartPos ) ).y();
232 nextPageStartPos = 0;
233 nextPageStartPixel = 0;
236 while ( ( totalCoord < nextPageStartPos ) || ( ( nextPageStartPos == 0 ) && ( totalCoord <= endY ) ) )
238 const double pixelCoord = mTransform.map( QPointF( 0, totalCoord ) ).y();
239 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
241 const QString label = QLocale().toString( pageCoord );
242 const int labelSize = mRulerFontMetrics->boundingRect( label ).width();
245 if ( ( pixelCoord + labelSize + 8 < nextPageStartPixel )
246 || ( nextPageStartPixel == 0 ) )
248 drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
252 drawSmallDivisions( &p, totalCoord, numSmallDivisions, mmDisplay, nextPageStartPos );
254 pageCoord += mmDisplay;
255 totalCoord += mmDisplay;
268void QgsLayoutRuler::drawMarkerPos( QPainter *painter )
271 painter->setPen( QColor( Qt::red ) );
272 switch ( mOrientation )
276 painter->drawLine( mMarkerPos.x(), 0, mMarkerPos.x(), mRulerMinSize );
281 painter->drawLine( 0, mMarkerPos.y(), mRulerMinSize, mMarkerPos.y() );
287void QgsLayoutRuler::drawGuideMarkers( QPainter *p,
QgsLayout *layout )
289 const QList<QgsLayoutItemPage *> visiblePages = mView->visiblePages();
290 const QList<QgsLayoutGuide *> guides = layout->
guides().
guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
291 const QgsScopedQPainterState painterState( p );
292 p->setRenderHint( QPainter::Antialiasing,
true );
293 p->setPen( Qt::NoPen );
294 const auto constGuides = guides;
295 for ( QgsLayoutGuide *guide : constGuides )
297 if ( visiblePages.contains( guide->page() ) )
299 if ( guide == mHoverGuide )
301 p->setBrush( QBrush( QColor( 255, 0, 0, 225 ) ) );
305 p->setBrush( QBrush( QColor( 255, 0, 0, 150 ) ) );
308 switch ( mOrientation )
311 point = QPointF( guide->layoutPosition(), 0 );
315 point = QPointF( 0, guide->layoutPosition() );
318 drawGuideAtPos( p, convertLayoutPointToLocal( point ) );
323void QgsLayoutRuler::drawGuideAtPos( QPainter *painter, QPoint pos )
325 switch ( mOrientation )
329 painter->translate( pos.x(), 0 );
330 painter->drawPolygon( mGuideMarker );
331 painter->translate( -pos.x(), 0 );
336 painter->translate( 0, pos.y() );
337 painter->drawPolygon( mGuideMarker );
338 painter->translate( 0, -pos.y() );
344void QgsLayoutRuler::createTemporaryGuideItem()
346 if ( !mView->currentLayout() )
350 mGuideItem =
new QGraphicsLineItem();
353 QPen linePen( Qt::DotLine );
354 linePen.setColor( QColor( 255, 0, 0, 150 ) );
355 linePen.setWidthF( 0 );
356 mGuideItem->setPen( linePen );
358 mView->currentLayout()->addItem( mGuideItem );
361QPointF QgsLayoutRuler::convertLocalPointToLayout( QPoint localPoint )
const
363 const QPoint viewPoint = mView->mapFromGlobal( mapToGlobal( localPoint ) );
364 return mView->mapToScene( viewPoint );
367QPoint QgsLayoutRuler::convertLayoutPointToLocal( QPointF layoutPoint )
const
369 const QPoint viewPoint = mView->mapFromScene( layoutPoint );
370 return mapFromGlobal( mView->mapToGlobal( viewPoint ) );
373QgsLayoutGuide *QgsLayoutRuler::guideAtPoint( QPoint localPoint )
const
375 if ( !mView->currentLayout() )
378 const QPointF layoutPoint = convertLocalPointToLayout( localPoint );
379 const QList<QgsLayoutItemPage *> visiblePages = mView->visiblePages();
380 const QList<QgsLayoutGuide *> guides = mView->currentLayout()->guides().guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
381 QgsLayoutGuide *closestGuide =
nullptr;
382 double minDelta = std::numeric_limits<double>::max();
383 const auto constGuides = guides;
384 for ( QgsLayoutGuide *guide : constGuides )
386 if ( visiblePages.contains( guide->page() ) )
388 double currentDelta = 0;
389 switch ( mOrientation )
392 currentDelta = std::fabs( layoutPoint.x() - guide->layoutPosition() );
396 currentDelta = std::fabs( layoutPoint.y() - guide->layoutPosition() );
399 if ( currentDelta < minDelta )
401 minDelta = currentDelta;
402 closestGuide = guide;
407 if ( minDelta * mView->transform().m11() <= mDragGuideTolerance )
417void QgsLayoutRuler::drawRotatedText( QPainter *painter, QPointF pos,
const QString &text )
419 const QgsScopedQPainterState painterState( painter );
420 painter->translate( pos.x(), pos.y() );
421 painter->rotate( 270 );
422 painter->drawText( 0, 0, text );
425void QgsLayoutRuler::drawSmallDivisions( QPainter *painter,
double startPos,
int numDivisions,
double rulerScale,
double maxPos )
427 if ( numDivisions == 0 )
431 double smallMarkerPos = startPos;
432 const double smallDivisionSpacing = rulerScale / numDivisions;
434 double pixelCoord = 0.0;
437 for (
int i = 0; i < numDivisions; ++i )
439 smallMarkerPos += smallDivisionSpacing;
441 if ( maxPos > 0 && smallMarkerPos > maxPos )
448 switch ( mOrientation )
452 pixelCoord = mTransform.map( QPointF( smallMarkerPos, 0 ) ).x();
457 pixelCoord = mTransform.map( QPointF( 0, smallMarkerPos ) ).y();
464 if ( ( numDivisions == 10 && i == 4 ) || ( numDivisions == 4 && i == 1 ) )
467 lineSize = mRulerMinSize / 1.5;
471 lineSize = mRulerMinSize / 1.25;
475 switch ( mOrientation )
479 painter->drawLine( pixelCoord, lineSize, pixelCoord, mRulerMinSize );
484 painter->drawLine( lineSize, pixelCoord, mRulerMinSize, pixelCoord );
491int QgsLayoutRuler::optimumScale(
double minPixelDiff,
int &magnitude,
int &multiple )
496 for (
unsigned int magnitudeCandidate = 0; magnitudeCandidate <
COUNT_VALID_MAGNITUDES; ++magnitudeCandidate )
498 for (
unsigned int multipleCandidate = 0; multipleCandidate <
COUNT_VALID_MULTIPLES; ++multipleCandidate )
500 const int candidateScale = VALID_SCALE_MULTIPLES[multipleCandidate] * VALID_SCALE_MAGNITUDES[magnitudeCandidate];
502 const double pixelDiff = mTransform.map( QPointF( candidateScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
503 if ( pixelDiff > minPixelDiff )
506 magnitude = VALID_SCALE_MAGNITUDES[magnitudeCandidate];
507 multiple = VALID_SCALE_MULTIPLES[multipleCandidate];
508 return candidateScale;
516int QgsLayoutRuler::optimumNumberDivisions(
double rulerScale,
int scaleMultiple )
519 const double largeDivisionSize = mTransform.map( QPointF( rulerScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
522 QList<int> validSmallDivisions;
523 switch ( scaleMultiple )
528 validSmallDivisions << 10 << 5 << 2;
533 validSmallDivisions << 10 << 4 << 2;
538 validSmallDivisions << 10 << 5;
543 QList<int>::iterator divisions_it;
544 for ( divisions_it = validSmallDivisions.begin(); divisions_it != validSmallDivisions.end(); ++divisions_it )
547 const double candidateSize = largeDivisionSize / ( *divisions_it );
549 if ( candidateSize >= mMinPixelsPerDivision )
552 return ( *divisions_it );
563 mTransform = transform;
580 mMarkerPos = mView->mapFromScene( position );
586 mMarkerPos =
event->pos();
589 if ( !mView->currentLayout() )
593 if ( mCreatingGuide || mDraggingGuide )
596 displayPos = convertLocalPointToLayout( event->pos() );
598 if ( mCreatingGuide )
600 QgsLayout *layout = mView->currentLayout();
606 QPen linePen = mGuideItem->pen();
610 linePen.setColor( QColor( 255, 0, 0, 150 ) );
614 linePen.setColor( QColor( 255, 0, 0, 225 ) );
616 mGuideItem->setPen( linePen );
617 switch ( mOrientation )
622 mGuideItem->setLine( page->scenePos().x(), displayPos.y(), page->scenePos().x() + page->rect().width(), displayPos.y() );
623 displayPos.setX( 0 );
629 mGuideItem->setLine( displayPos.x(), page->scenePos().y(), displayPos.x(), page->scenePos().y() + page->rect().height() );
630 displayPos.setY( 0 );
638 switch ( mOrientation )
642 mView->currentLayout()->guides().setGuideLayoutPosition( mDraggingGuide, displayPos.x() );
643 displayPos.setY( 0 );
648 mView->currentLayout()->guides().setGuideLayoutPosition( mDraggingGuide, displayPos.y() );
649 displayPos.setX( 0 );
658 mHoverGuide = guideAtPoint( event->pos() );
661 setCursor( mOrientation == Qt::Vertical ? Qt::SplitVCursor : Qt::SplitHCursor );
665 setCursor( Qt::ArrowCursor );
669 displayPos = mTransform.inverted().map( event->pos() );
670 switch ( mOrientation )
675 displayPos.setY( 0 );
681 displayPos.setX( 0 );
691 if ( !mView->currentLayout() )
694 if ( event->button() == Qt::LeftButton )
696 mDraggingGuide = guideAtPoint( event->pos() );
697 if ( !mDraggingGuide )
700 if ( mView->currentLayout()->pageCollection()->pageCount() > 0 )
702 mCreatingGuide =
true;
703 createTemporaryGuideItem();
708 mDraggingGuideOldPosition = mDraggingGuide->layoutPosition();
710 switch ( mOrientation )
714 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitHCursor : Qt::SplitVCursor );
718 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitVCursor : Qt::SplitHCursor );
726 if ( !mView->currentLayout() )
729 if ( event->button() == Qt::LeftButton )
731 if ( mDraggingGuide )
733 QApplication::restoreOverrideCursor();
735 const QPointF layoutPoint = convertLocalPointToLayout( event->pos() );
739 bool deleteGuide =
false;
740 switch ( mDraggingGuide->orientation() )
743 if ( layoutPoint.y() < page->scenePos().y() || layoutPoint.y() > page->scenePos().y() + page->rect().height() )
748 if ( layoutPoint.x() < page->scenePos().x() || layoutPoint.x() > page->scenePos().x() + page->rect().width() )
755 mView->currentLayout()->guides().removeGuide( mDraggingGuide );
757 mDraggingGuide =
nullptr;
761 mCreatingGuide =
false;
762 QApplication::restoreOverrideCursor();
764 mGuideItem =
nullptr;
767 switch ( mOrientation )
771 if ( event->pos().y() <= height() )
777 if ( event->pos().x() <= width() )
783 QgsLayout *layout = mView->currentLayout();
786 const QPointF scenePos = convertLocalPointToLayout( event->pos() );
791 std::unique_ptr<QgsLayoutGuide> guide;
792 switch ( mOrientation )
808 mView->currentLayout()->guides().addGuide( guide.release() );
811 else if ( event->button() == Qt::RightButton )
813 if ( mCreatingGuide || mDraggingGuide )
815 QApplication::restoreOverrideCursor();
817 mGuideItem =
nullptr;
818 mCreatingGuide =
false;
819 if ( mDraggingGuide )
821 mDraggingGuide->setLayoutPosition( mDraggingGuideOldPosition );
823 mDraggingGuide =
nullptr;
826 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