16#include "moc_qgslayoutruler.cpp"
22#include <QDragEnterEvent>
23#include <QGraphicsLineItem>
31const int QgsLayoutRuler::VALID_SCALE_MULTIPLES[] = {1, 2, 5};
32const int QgsLayoutRuler::VALID_SCALE_MAGNITUDES[] = {1, 10, 100, 1000, 10000};
36 , mOrientation( orientation )
38 setMouseTracking(
true );
42 mRulerFontMetrics.reset(
new QFontMetrics( mRulerFont ) );
47 mScaleMinPixelsWidth = mRulerFontMetrics->boundingRect( QStringLiteral(
"000" ) ).width() * 2.5;
49 mRulerMinSize = mRulerFontMetrics->height() * 1.5;
51 mMinPixelsPerDivision = mRulerMinSize / 4;
53 if ( mMinPixelsPerDivision < 2 )
54 mMinPixelsPerDivision = 2;
56 mPixelsBetweenLineAndText = mRulerMinSize / 10;
57 mTextBaseline = mRulerMinSize / 1.667;
58 mMinSpacingVerticalLabels = mRulerMinSize / 5;
60 const double guideMarkerSize = mRulerFontMetrics->horizontalAdvance(
'*' );
61 mDragGuideTolerance = guideMarkerSize;
62 switch ( mOrientation )
65 mGuideMarker << QPoint( -guideMarkerSize / 2, mRulerMinSize - guideMarkerSize ) << QPoint( 0, mRulerMinSize ) <<
66 QPoint( guideMarkerSize / 2, mRulerMinSize - guideMarkerSize );
70 mGuideMarker << QPoint( mRulerMinSize - guideMarkerSize, -guideMarkerSize / 2 ) << QPoint( mRulerMinSize, 0 ) <<
71 QPoint( mRulerMinSize - guideMarkerSize, guideMarkerSize / 2 );
78 return QSize( mRulerMinSize, mRulerMinSize );
92 drawGuideMarkers( &p, layout );
94 const QTransform t = mTransform.inverted();
95 p.setFont( mRulerFont );
97 QBrush brush = p.brush();
98 QColor color = brush.color();
99 color.setAlphaF( 0.7 );
100 brush.setColor( color );
104 color.setAlphaF( 0.7 );
105 pen.setColor( color );
111 const int mmDisplay = optimumScale( mScaleMinPixelsWidth, magnitude, multiple );
114 const int numSmallDivisions = optimumNumberDivisions( mmDisplay, multiple );
116 switch ( mOrientation )
126 const double startX = t.map( QPointF( 0, 0 ) ).x();
127 const double endX = t.map( QPointF( width(), 0 ) ).x();
130 double markerPos = ( std::floor( startX / mmDisplay ) + 1 ) * mmDisplay;
133 drawSmallDivisions( &p, markerPos, numSmallDivisions, -mmDisplay );
135 while ( markerPos <= endX )
137 const double pixelCoord = mTransform.map( QPointF( markerPos, 0 ) ).x();
140 p.drawLine( pixelCoord, 0, pixelCoord, mRulerMinSize );
141 p.drawText( QPointF( pixelCoord + mPixelsBetweenLineAndText, mTextBaseline ), QLocale().toString( markerPos ) );
144 drawSmallDivisions( &p, markerPos, numSmallDivisions, mmDisplay, endX );
146 markerPos += mmDisplay;
157 const double startY = t.map( QPointF( 0, 0 ) ).y();
158 const double endY = t.map( QPointF( 0, height() ) ).y();
164 double currentPageY = 0;
165 for (
int page = 0; page < layout->
pageCollection()->pageCount(); ++page )
167 if ( currentY < startY )
170 currentPageY = currentY;
175 if ( currentY > endY )
181 double beforePageCoord = -mmDisplay;
182 const double firstPageY = mTransform.map( QPointF( 0, 0 ) ).y();
185 while ( beforePageCoord > startY )
187 const double pixelCoord = mTransform.map( QPointF( 0, beforePageCoord ) ).y();
188 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
190 const QString label = QLocale().toString( beforePageCoord );
191 const int labelSize = mRulerFontMetrics->boundingRect( label ).width();
194 if ( pixelCoord + labelSize + 8 < firstPageY )
196 drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
200 drawSmallDivisions( &p, beforePageCoord, numSmallDivisions, mmDisplay );
202 beforePageCoord -= mmDisplay;
206 drawSmallDivisions( &p, beforePageCoord + mmDisplay, numSmallDivisions, -mmDisplay, startY );
209 double nextPageStartPos = 0;
210 int nextPageStartPixel = 0;
212 for (
int i = startPage; i <= endPage; ++i )
214 double pageCoord = 0;
217 double totalCoord = currentPageY;
224 nextPageStartPixel = mTransform.map( QPointF( 0, nextPageStartPos ) ).y();
229 nextPageStartPos = 0;
230 nextPageStartPixel = 0;
233 while ( ( totalCoord < nextPageStartPos ) || ( ( nextPageStartPos == 0 ) && ( totalCoord <= endY ) ) )
235 const double pixelCoord = mTransform.map( QPointF( 0, totalCoord ) ).y();
236 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
238 const QString label = QLocale().toString( pageCoord );
239 const int labelSize = mRulerFontMetrics->boundingRect( label ).width();
242 if ( ( pixelCoord + labelSize + 8 < nextPageStartPixel )
243 || ( nextPageStartPixel == 0 ) )
245 drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
249 drawSmallDivisions( &p, totalCoord, numSmallDivisions, mmDisplay, nextPageStartPos );
251 pageCoord += mmDisplay;
252 totalCoord += mmDisplay;
265void QgsLayoutRuler::drawMarkerPos( QPainter *painter )
268 painter->setPen( QColor( Qt::red ) );
269 switch ( mOrientation )
273 painter->drawLine( mMarkerPos.x(), 0, mMarkerPos.x(), mRulerMinSize );
278 painter->drawLine( 0, mMarkerPos.y(), mRulerMinSize, mMarkerPos.y() );
284void QgsLayoutRuler::drawGuideMarkers( QPainter *p,
QgsLayout *layout )
286 const QList< QgsLayoutItemPage * > visiblePages = mView->
visiblePages();
287 const QList< QgsLayoutGuide * > guides = layout->
guides().
guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
289 p->setRenderHint( QPainter::Antialiasing,
true );
290 p->setPen( Qt::NoPen );
291 const auto constGuides = guides;
294 if ( visiblePages.contains( guide->page() ) )
296 if ( guide == mHoverGuide )
298 p->setBrush( QBrush( QColor( 255, 0, 0, 225 ) ) );
302 p->setBrush( QBrush( QColor( 255, 0, 0, 150 ) ) );
305 switch ( mOrientation )
308 point = QPointF( guide->layoutPosition(), 0 );
312 point = QPointF( 0, guide->layoutPosition() );
315 drawGuideAtPos( p, convertLayoutPointToLocal( point ) );
320void QgsLayoutRuler::drawGuideAtPos( QPainter *painter, QPoint pos )
322 switch ( mOrientation )
326 painter->translate( pos.x(), 0 );
327 painter->drawPolygon( mGuideMarker );
328 painter->translate( -pos.x(), 0 );
333 painter->translate( 0, pos.y() );
334 painter->drawPolygon( mGuideMarker );
335 painter->translate( 0, -pos.y() );
341void QgsLayoutRuler::createTemporaryGuideItem()
347 mGuideItem =
new QGraphicsLineItem();
350 QPen linePen( Qt::DotLine );
351 linePen.setColor( QColor( 255, 0, 0, 150 ) );
352 linePen.setWidthF( 0 );
353 mGuideItem->setPen( linePen );
358QPointF QgsLayoutRuler::convertLocalPointToLayout( QPoint localPoint )
const
360 const QPoint viewPoint = mView->mapFromGlobal( mapToGlobal( localPoint ) );
361 return mView->mapToScene( viewPoint );
364QPoint QgsLayoutRuler::convertLayoutPointToLocal( QPointF layoutPoint )
const
366 const QPoint viewPoint = mView->mapFromScene( layoutPoint );
367 return mapFromGlobal( mView->mapToGlobal( viewPoint ) );
370QgsLayoutGuide *QgsLayoutRuler::guideAtPoint( QPoint localPoint )
const
375 const QPointF layoutPoint = convertLocalPointToLayout( localPoint );
376 const QList< QgsLayoutItemPage * > visiblePages = mView->
visiblePages();
377 const QList< QgsLayoutGuide * > guides = mView->
currentLayout()->
guides().
guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
379 double minDelta = std::numeric_limits<double>::max();
380 const auto constGuides = guides;
383 if ( visiblePages.contains( guide->page() ) )
385 double currentDelta = 0;
386 switch ( mOrientation )
389 currentDelta = std::fabs( layoutPoint.x() - guide->layoutPosition() );
393 currentDelta = std::fabs( layoutPoint.y() - guide->layoutPosition() );
396 if ( currentDelta < minDelta )
398 minDelta = currentDelta;
399 closestGuide = guide;
404 if ( minDelta * mView->transform().m11() <= mDragGuideTolerance )
414void QgsLayoutRuler::drawRotatedText( QPainter *painter, QPointF pos,
const QString &text )
417 painter->translate( pos.x(), pos.y() );
418 painter->rotate( 270 );
419 painter->drawText( 0, 0, text );
422void QgsLayoutRuler::drawSmallDivisions( QPainter *painter,
double startPos,
int numDivisions,
double rulerScale,
double maxPos )
424 if ( numDivisions == 0 )
428 double smallMarkerPos = startPos;
429 const double smallDivisionSpacing = rulerScale / numDivisions;
431 double pixelCoord = 0.0;
434 for (
int i = 0; i < numDivisions; ++i )
436 smallMarkerPos += smallDivisionSpacing;
438 if ( maxPos > 0 && smallMarkerPos > maxPos )
445 switch ( mOrientation )
449 pixelCoord = mTransform.map( QPointF( smallMarkerPos, 0 ) ).x();
454 pixelCoord = mTransform.map( QPointF( 0, smallMarkerPos ) ).y();
461 if ( ( numDivisions == 10 && i == 4 ) || ( numDivisions == 4 && i == 1 ) )
464 lineSize = mRulerMinSize / 1.5;
468 lineSize = mRulerMinSize / 1.25;
472 switch ( mOrientation )
476 painter->drawLine( pixelCoord, lineSize, pixelCoord, mRulerMinSize );
481 painter->drawLine( lineSize, pixelCoord, mRulerMinSize, pixelCoord );
488int QgsLayoutRuler::optimumScale(
double minPixelDiff,
int &magnitude,
int &multiple )
493 for (
unsigned int magnitudeCandidate = 0; magnitudeCandidate <
COUNT_VALID_MAGNITUDES; ++magnitudeCandidate )
495 for (
unsigned int multipleCandidate = 0; multipleCandidate <
COUNT_VALID_MULTIPLES; ++multipleCandidate )
497 const int candidateScale = VALID_SCALE_MULTIPLES[multipleCandidate] * VALID_SCALE_MAGNITUDES[magnitudeCandidate];
499 const double pixelDiff = mTransform.map( QPointF( candidateScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
500 if ( pixelDiff > minPixelDiff )
503 magnitude = VALID_SCALE_MAGNITUDES[magnitudeCandidate];
504 multiple = VALID_SCALE_MULTIPLES[multipleCandidate];
505 return candidateScale;
513int QgsLayoutRuler::optimumNumberDivisions(
double rulerScale,
int scaleMultiple )
516 const double largeDivisionSize = mTransform.map( QPointF( rulerScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
519 QList<int> validSmallDivisions;
520 switch ( scaleMultiple )
525 validSmallDivisions << 10 << 5 << 2;
530 validSmallDivisions << 10 << 4 << 2;
535 validSmallDivisions << 10 << 5;
540 QList<int>::iterator divisions_it;
541 for ( divisions_it = validSmallDivisions.begin(); divisions_it != validSmallDivisions.end(); ++divisions_it )
544 const double candidateSize = largeDivisionSize / ( *divisions_it );
546 if ( candidateSize >= mMinPixelsPerDivision )
549 return ( *divisions_it );
560 mTransform = transform;
577 mMarkerPos = mView->mapFromScene( position );
583 mMarkerPos =
event->pos();
590 if ( mCreatingGuide || mDraggingGuide )
593 displayPos = convertLocalPointToLayout( event->pos() );
595 if ( mCreatingGuide )
603 QPen linePen = mGuideItem->pen();
607 linePen.setColor( QColor( 255, 0, 0, 150 ) );
611 linePen.setColor( QColor( 255, 0, 0, 225 ) );
613 mGuideItem->setPen( linePen );
614 switch ( mOrientation )
619 mGuideItem->setLine( page->scenePos().x(), displayPos.y(), page->scenePos().x() + page->rect().width(), displayPos.y() );
620 displayPos.setX( 0 );
626 mGuideItem->setLine( displayPos.x(), page->scenePos().y(), displayPos.x(), page->scenePos().y() + page->rect().height() );
627 displayPos.setY( 0 );
635 switch ( mOrientation )
640 displayPos.setY( 0 );
646 displayPos.setX( 0 );
655 mHoverGuide = guideAtPoint( event->pos() );
658 setCursor( mOrientation == Qt::Vertical ? Qt::SplitVCursor : Qt::SplitHCursor );
662 setCursor( Qt::ArrowCursor );
666 displayPos = mTransform.inverted().map( event->pos() );
667 switch ( mOrientation )
672 displayPos.setY( 0 );
678 displayPos.setX( 0 );
691 if ( event->button() == Qt::LeftButton )
693 mDraggingGuide = guideAtPoint( event->pos() );
694 if ( !mDraggingGuide )
699 mCreatingGuide =
true;
700 createTemporaryGuideItem();
707 switch ( mOrientation )
711 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitHCursor : Qt::SplitVCursor );
715 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitVCursor : Qt::SplitHCursor );
726 if ( event->button() == Qt::LeftButton )
728 if ( mDraggingGuide )
730 QApplication::restoreOverrideCursor();
732 const QPointF layoutPoint = convertLocalPointToLayout( event->pos() );
736 bool deleteGuide =
false;
740 if ( layoutPoint.y() < page->scenePos().y() || layoutPoint.y() > page->scenePos().y() + page->rect().height() )
745 if ( layoutPoint.x() < page->scenePos().x() || layoutPoint.x() > page->scenePos().x() + page->rect().width() )
754 mDraggingGuide =
nullptr;
758 mCreatingGuide =
false;
759 QApplication::restoreOverrideCursor();
761 mGuideItem =
nullptr;
764 switch ( mOrientation )
768 if ( event->pos().y() <= height() )
774 if ( event->pos().x() <= width() )
783 const QPointF scenePos = convertLocalPointToLayout( event->pos() );
788 std::unique_ptr< QgsLayoutGuide > guide;
789 switch ( mOrientation )
808 else if ( event->button() == Qt::RightButton )
810 if ( mCreatingGuide || mDraggingGuide )
812 QApplication::restoreOverrideCursor();
814 mGuideItem =
nullptr;
815 mCreatingGuide =
false;
816 if ( mDraggingGuide )
820 mDraggingGuide =
nullptr;
823 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 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.
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.
@ ZGuide
Z-value for page guides.
Qgis::LayoutUnit units() const
Returns the native units for the layout.
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