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 ) << QPoint( guideMarkerSize / 2, mRulerMinSize - guideMarkerSize );
69 mGuideMarker << QPoint( mRulerMinSize - guideMarkerSize, -guideMarkerSize / 2 ) << QPoint( mRulerMinSize, 0 ) << QPoint( mRulerMinSize - guideMarkerSize, guideMarkerSize / 2 );
76 return QSize( mRulerMinSize, mRulerMinSize );
90 drawGuideMarkers( &p, layout );
92 const QTransform t = mTransform.inverted();
93 p.setFont( mRulerFont );
95 QBrush brush = p.brush();
96 QColor color = brush.color();
97 color.setAlphaF( 0.7 );
98 brush.setColor( color );
102 color.setAlphaF( 0.7 );
103 pen.setColor( color );
109 const int mmDisplay = optimumScale( mScaleMinPixelsWidth, magnitude, multiple );
112 const int numSmallDivisions = optimumNumberDivisions( mmDisplay, multiple );
114 switch ( mOrientation )
124 const double startX = t.map( QPointF( 0, 0 ) ).x();
125 const double endX = t.map( QPointF( width(), 0 ) ).x();
128 double markerPos = ( std::floor( startX / mmDisplay ) + 1 ) * mmDisplay;
131 drawSmallDivisions( &p, markerPos, numSmallDivisions, -mmDisplay );
133 while ( markerPos <= endX )
135 const double pixelCoord = mTransform.map( QPointF( markerPos, 0 ) ).x();
138 p.drawLine( pixelCoord, 0, pixelCoord, mRulerMinSize );
139 p.drawText( QPointF( pixelCoord + mPixelsBetweenLineAndText, mTextBaseline ), QLocale().toString( markerPos ) );
142 drawSmallDivisions( &p, markerPos, numSmallDivisions, mmDisplay, endX );
144 markerPos += mmDisplay;
155 const double startY = t.map( QPointF( 0, 0 ) ).y();
156 const double endY = t.map( QPointF( 0, height() ) ).y();
162 double currentPageY = 0;
163 for (
int page = 0; page < layout->
pageCollection()->pageCount(); ++page )
165 if ( currentY < startY )
168 currentPageY = currentY;
173 if ( currentY > endY )
179 double beforePageCoord = -mmDisplay;
180 const double firstPageY = mTransform.map( QPointF( 0, 0 ) ).y();
183 while ( beforePageCoord > startY )
185 const double pixelCoord = mTransform.map( QPointF( 0, beforePageCoord ) ).y();
186 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
188 const QString label = QLocale().toString( beforePageCoord );
189 const int labelSize = mRulerFontMetrics->boundingRect( label ).width();
192 if ( pixelCoord + labelSize + 8 < firstPageY )
194 drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
198 drawSmallDivisions( &p, beforePageCoord, numSmallDivisions, mmDisplay );
200 beforePageCoord -= mmDisplay;
204 drawSmallDivisions( &p, beforePageCoord + mmDisplay, numSmallDivisions, -mmDisplay, startY );
207 double nextPageStartPos = 0;
208 int nextPageStartPixel = 0;
210 for (
int i = startPage; i <= endPage; ++i )
212 double pageCoord = 0;
215 double totalCoord = currentPageY;
222 nextPageStartPixel = mTransform.map( QPointF( 0, nextPageStartPos ) ).y();
227 nextPageStartPos = 0;
228 nextPageStartPixel = 0;
231 while ( ( totalCoord < nextPageStartPos ) || ( ( nextPageStartPos == 0 ) && ( totalCoord <= endY ) ) )
233 const double pixelCoord = mTransform.map( QPointF( 0, totalCoord ) ).y();
234 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
236 const QString label = QLocale().toString( pageCoord );
237 const int labelSize = mRulerFontMetrics->boundingRect( label ).width();
240 if ( ( pixelCoord + labelSize + 8 < nextPageStartPixel )
241 || ( nextPageStartPixel == 0 ) )
243 drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
247 drawSmallDivisions( &p, totalCoord, numSmallDivisions, mmDisplay, nextPageStartPos );
249 pageCoord += mmDisplay;
250 totalCoord += mmDisplay;
263void QgsLayoutRuler::drawMarkerPos( QPainter *painter )
266 painter->setPen( QColor( Qt::red ) );
267 switch ( mOrientation )
271 painter->drawLine( mMarkerPos.x(), 0, mMarkerPos.x(), mRulerMinSize );
276 painter->drawLine( 0, mMarkerPos.y(), mRulerMinSize, mMarkerPos.y() );
282void QgsLayoutRuler::drawGuideMarkers( QPainter *p,
QgsLayout *layout )
284 const QList<QgsLayoutItemPage *> visiblePages = mView->
visiblePages();
285 const QList<QgsLayoutGuide *> guides = layout->
guides().
guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
287 p->setRenderHint( QPainter::Antialiasing,
true );
288 p->setPen( Qt::NoPen );
289 const auto constGuides = guides;
292 if ( visiblePages.contains( guide->page() ) )
294 if ( guide == mHoverGuide )
296 p->setBrush( QBrush( QColor( 255, 0, 0, 225 ) ) );
300 p->setBrush( QBrush( QColor( 255, 0, 0, 150 ) ) );
303 switch ( mOrientation )
306 point = QPointF( guide->layoutPosition(), 0 );
310 point = QPointF( 0, guide->layoutPosition() );
313 drawGuideAtPos( p, convertLayoutPointToLocal( point ) );
318void QgsLayoutRuler::drawGuideAtPos( QPainter *painter, QPoint pos )
320 switch ( mOrientation )
324 painter->translate( pos.x(), 0 );
325 painter->drawPolygon( mGuideMarker );
326 painter->translate( -pos.x(), 0 );
331 painter->translate( 0, pos.y() );
332 painter->drawPolygon( mGuideMarker );
333 painter->translate( 0, -pos.y() );
339void QgsLayoutRuler::createTemporaryGuideItem()
345 mGuideItem =
new QGraphicsLineItem();
348 QPen linePen( Qt::DotLine );
349 linePen.setColor( QColor( 255, 0, 0, 150 ) );
350 linePen.setWidthF( 0 );
351 mGuideItem->setPen( linePen );
356QPointF QgsLayoutRuler::convertLocalPointToLayout( QPoint localPoint )
const
358 const QPoint viewPoint = mView->mapFromGlobal( mapToGlobal( localPoint ) );
359 return mView->mapToScene( viewPoint );
362QPoint QgsLayoutRuler::convertLayoutPointToLocal( QPointF layoutPoint )
const
364 const QPoint viewPoint = mView->mapFromScene( layoutPoint );
365 return mapFromGlobal( mView->mapToGlobal( viewPoint ) );
368QgsLayoutGuide *QgsLayoutRuler::guideAtPoint( QPoint localPoint )
const
373 const QPointF layoutPoint = convertLocalPointToLayout( localPoint );
374 const QList<QgsLayoutItemPage *> visiblePages = mView->
visiblePages();
375 const QList<QgsLayoutGuide *> guides = mView->
currentLayout()->
guides().
guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
377 double minDelta = std::numeric_limits<double>::max();
378 const auto constGuides = guides;
381 if ( visiblePages.contains( guide->page() ) )
383 double currentDelta = 0;
384 switch ( mOrientation )
387 currentDelta = std::fabs( layoutPoint.x() - guide->layoutPosition() );
391 currentDelta = std::fabs( layoutPoint.y() - guide->layoutPosition() );
394 if ( currentDelta < minDelta )
396 minDelta = currentDelta;
397 closestGuide = guide;
402 if ( minDelta * mView->transform().m11() <= mDragGuideTolerance )
412void QgsLayoutRuler::drawRotatedText( QPainter *painter, QPointF pos,
const QString &text )
415 painter->translate( pos.x(), pos.y() );
416 painter->rotate( 270 );
417 painter->drawText( 0, 0, text );
420void QgsLayoutRuler::drawSmallDivisions( QPainter *painter,
double startPos,
int numDivisions,
double rulerScale,
double maxPos )
422 if ( numDivisions == 0 )
426 double smallMarkerPos = startPos;
427 const double smallDivisionSpacing = rulerScale / numDivisions;
429 double pixelCoord = 0.0;
432 for (
int i = 0; i < numDivisions; ++i )
434 smallMarkerPos += smallDivisionSpacing;
436 if ( maxPos > 0 && smallMarkerPos > maxPos )
443 switch ( mOrientation )
447 pixelCoord = mTransform.map( QPointF( smallMarkerPos, 0 ) ).x();
452 pixelCoord = mTransform.map( QPointF( 0, smallMarkerPos ) ).y();
459 if ( ( numDivisions == 10 && i == 4 ) || ( numDivisions == 4 && i == 1 ) )
462 lineSize = mRulerMinSize / 1.5;
466 lineSize = mRulerMinSize / 1.25;
470 switch ( mOrientation )
474 painter->drawLine( pixelCoord, lineSize, pixelCoord, mRulerMinSize );
479 painter->drawLine( lineSize, pixelCoord, mRulerMinSize, pixelCoord );
486int QgsLayoutRuler::optimumScale(
double minPixelDiff,
int &magnitude,
int &multiple )
491 for (
unsigned int magnitudeCandidate = 0; magnitudeCandidate <
COUNT_VALID_MAGNITUDES; ++magnitudeCandidate )
493 for (
unsigned int multipleCandidate = 0; multipleCandidate <
COUNT_VALID_MULTIPLES; ++multipleCandidate )
495 const int candidateScale = VALID_SCALE_MULTIPLES[multipleCandidate] * VALID_SCALE_MAGNITUDES[magnitudeCandidate];
497 const double pixelDiff = mTransform.map( QPointF( candidateScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
498 if ( pixelDiff > minPixelDiff )
501 magnitude = VALID_SCALE_MAGNITUDES[magnitudeCandidate];
502 multiple = VALID_SCALE_MULTIPLES[multipleCandidate];
503 return candidateScale;
511int QgsLayoutRuler::optimumNumberDivisions(
double rulerScale,
int scaleMultiple )
514 const double largeDivisionSize = mTransform.map( QPointF( rulerScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
517 QList<int> validSmallDivisions;
518 switch ( scaleMultiple )
523 validSmallDivisions << 10 << 5 << 2;
528 validSmallDivisions << 10 << 4 << 2;
533 validSmallDivisions << 10 << 5;
538 QList<int>::iterator divisions_it;
539 for ( divisions_it = validSmallDivisions.begin(); divisions_it != validSmallDivisions.end(); ++divisions_it )
542 const double candidateSize = largeDivisionSize / ( *divisions_it );
544 if ( candidateSize >= mMinPixelsPerDivision )
547 return ( *divisions_it );
558 mTransform = transform;
575 mMarkerPos = mView->mapFromScene( position );
581 mMarkerPos =
event->pos();
588 if ( mCreatingGuide || mDraggingGuide )
591 displayPos = convertLocalPointToLayout( event->pos() );
593 if ( mCreatingGuide )
601 QPen linePen = mGuideItem->pen();
605 linePen.setColor( QColor( 255, 0, 0, 150 ) );
609 linePen.setColor( QColor( 255, 0, 0, 225 ) );
611 mGuideItem->setPen( linePen );
612 switch ( mOrientation )
617 mGuideItem->setLine( page->scenePos().x(), displayPos.y(), page->scenePos().x() + page->rect().width(), displayPos.y() );
618 displayPos.setX( 0 );
624 mGuideItem->setLine( displayPos.x(), page->scenePos().y(), displayPos.x(), page->scenePos().y() + page->rect().height() );
625 displayPos.setY( 0 );
633 switch ( mOrientation )
638 displayPos.setY( 0 );
644 displayPos.setX( 0 );
653 mHoverGuide = guideAtPoint( event->pos() );
656 setCursor( mOrientation == Qt::Vertical ? Qt::SplitVCursor : Qt::SplitHCursor );
660 setCursor( Qt::ArrowCursor );
664 displayPos = mTransform.inverted().map( event->pos() );
665 switch ( mOrientation )
670 displayPos.setY( 0 );
676 displayPos.setX( 0 );
689 if ( event->button() == Qt::LeftButton )
691 mDraggingGuide = guideAtPoint( event->pos() );
692 if ( !mDraggingGuide )
697 mCreatingGuide =
true;
698 createTemporaryGuideItem();
705 switch ( mOrientation )
709 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitHCursor : Qt::SplitVCursor );
713 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitVCursor : Qt::SplitHCursor );
724 if ( event->button() == Qt::LeftButton )
726 if ( mDraggingGuide )
728 QApplication::restoreOverrideCursor();
730 const QPointF layoutPoint = convertLocalPointToLayout( event->pos() );
734 bool deleteGuide =
false;
738 if ( layoutPoint.y() < page->scenePos().y() || layoutPoint.y() > page->scenePos().y() + page->rect().height() )
743 if ( layoutPoint.x() < page->scenePos().x() || layoutPoint.x() > page->scenePos().x() + page->rect().width() )
752 mDraggingGuide =
nullptr;
756 mCreatingGuide =
false;
757 QApplication::restoreOverrideCursor();
759 mGuideItem =
nullptr;
762 switch ( mOrientation )
766 if ( event->pos().y() <= height() )
772 if ( event->pos().x() <= width() )
781 const QPointF scenePos = convertLocalPointToLayout( event->pos() );
786 std::unique_ptr<QgsLayoutGuide> guide;
787 switch ( mOrientation )
806 else if ( event->button() == Qt::RightButton )
808 if ( mCreatingGuide || mDraggingGuide )
810 QApplication::restoreOverrideCursor();
812 mGuideItem =
nullptr;
813 mCreatingGuide =
false;
814 if ( mDraggingGuide )
818 mDraggingGuide =
nullptr;
821 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