21 #include <QDragEnterEvent> 22 #include <QGraphicsLineItem> 30 const int QgsLayoutRuler::VALID_SCALE_MULTIPLES[] = {1, 2, 5};
31 const int QgsLayoutRuler::VALID_SCALE_MAGNITUDES[] = {1, 10, 100, 1000, 10000};
35 , mOrientation( orientation )
37 setMouseTracking(
true );
41 mRulerFontMetrics.reset(
new QFontMetrics( mRulerFont ) );
46 mScaleMinPixelsWidth = mRulerFontMetrics->width( QStringLiteral(
"000" ) ) * 2.5;
48 mRulerMinSize = mRulerFontMetrics->height() * 1.5;
50 mMinPixelsPerDivision = mRulerMinSize / 4;
52 if ( mMinPixelsPerDivision < 2 )
53 mMinPixelsPerDivision = 2;
55 mPixelsBetweenLineAndText = mRulerMinSize / 10;
56 mTextBaseline = mRulerMinSize / 1.667;
57 mMinSpacingVerticalLabels = mRulerMinSize / 5;
59 double guideMarkerSize = mRulerFontMetrics->width( QStringLiteral(
"*" ) );
60 mDragGuideTolerance = guideMarkerSize;
61 switch ( mOrientation )
64 mGuideMarker << QPoint( -guideMarkerSize / 2, mRulerMinSize - guideMarkerSize ) << QPoint( 0, mRulerMinSize ) <<
65 QPoint( guideMarkerSize / 2, mRulerMinSize - guideMarkerSize );
69 mGuideMarker << QPoint( mRulerMinSize - guideMarkerSize, -guideMarkerSize / 2 ) << QPoint( mRulerMinSize, 0 ) <<
70 QPoint( mRulerMinSize - guideMarkerSize, guideMarkerSize / 2 );
77 return QSize( mRulerMinSize, mRulerMinSize );
91 drawGuideMarkers( &p, layout );
93 QTransform t = mTransform.inverted();
94 p.setFont( mRulerFont );
96 QBrush brush = p.brush();
97 QColor color = brush.color();
98 color.setAlphaF( 0.7 );
99 brush.setColor( color );
103 color.setAlphaF( 0.7 );
104 pen.setColor( color );
110 int mmDisplay = optimumScale( mScaleMinPixelsWidth, magnitude, multiple );
113 int numSmallDivisions = optimumNumberDivisions( mmDisplay, multiple );
115 switch ( mOrientation )
125 double startX = t.map( QPointF( 0, 0 ) ).x();
126 double endX = t.map( QPointF( width(), 0 ) ).x();
129 double markerPos = ( std::floor( startX / mmDisplay ) + 1 ) * mmDisplay;
132 drawSmallDivisions( &p, markerPos, numSmallDivisions, -mmDisplay );
134 while ( markerPos <= endX )
136 double pixelCoord = mTransform.map( QPointF( markerPos, 0 ) ).x();
139 p.drawLine( pixelCoord, 0, pixelCoord, mRulerMinSize );
140 p.drawText( QPointF( pixelCoord + mPixelsBetweenLineAndText, mTextBaseline ), QString::number( markerPos ) );
143 drawSmallDivisions( &p, markerPos, numSmallDivisions, mmDisplay, endX );
145 markerPos += mmDisplay;
156 double startY = t.map( QPointF( 0, 0 ) ).y();
157 double endY = t.map( QPointF( 0, height() ) ).y();
163 double currentPageY = 0;
166 if ( currentY < startY )
169 currentPageY = currentY;
174 if ( currentY > endY )
180 double beforePageCoord = -mmDisplay;
181 double firstPageY = mTransform.map( QPointF( 0, 0 ) ).y();
184 while ( beforePageCoord > startY )
186 double pixelCoord = mTransform.map( QPointF( 0, beforePageCoord ) ).y();
187 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
189 QString label = QString::number( beforePageCoord );
190 int labelSize = mRulerFontMetrics->width( label );
193 if ( pixelCoord + labelSize + 8 < firstPageY )
195 drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
199 drawSmallDivisions( &p, beforePageCoord, numSmallDivisions, mmDisplay );
201 beforePageCoord -= mmDisplay;
205 drawSmallDivisions( &p, beforePageCoord + mmDisplay, numSmallDivisions, -mmDisplay, startY );
208 double nextPageStartPos = 0;
209 int nextPageStartPixel = 0;
211 for (
int i = startPage; i <= endPage; ++i )
213 double pageCoord = 0;
216 double totalCoord = currentPageY;
223 nextPageStartPixel = mTransform.map( QPointF( 0, nextPageStartPos ) ).y();
228 nextPageStartPos = 0;
229 nextPageStartPixel = 0;
232 while ( ( totalCoord < nextPageStartPos ) || ( ( nextPageStartPos == 0 ) && ( totalCoord <= endY ) ) )
234 double pixelCoord = mTransform.map( QPointF( 0, totalCoord ) ).y();
235 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
237 QString label = QString::number( pageCoord );
238 int labelSize = mRulerFontMetrics->width( label );
241 if ( ( pixelCoord + labelSize + 8 < nextPageStartPixel )
242 || ( nextPageStartPixel == 0 ) )
244 drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
248 drawSmallDivisions( &p, totalCoord, numSmallDivisions, mmDisplay, nextPageStartPos );
250 pageCoord += mmDisplay;
251 totalCoord += mmDisplay;
264 void QgsLayoutRuler::drawMarkerPos( QPainter *painter )
267 painter->setPen( QColor( Qt::red ) );
268 switch ( mOrientation )
272 painter->drawLine( mMarkerPos.x(), 0, mMarkerPos.x(), mRulerMinSize );
277 painter->drawLine( 0, mMarkerPos.y(), mRulerMinSize, mMarkerPos.y() );
283 void QgsLayoutRuler::drawGuideMarkers( QPainter *p,
QgsLayout *layout )
285 QList< QgsLayoutItemPage * > visiblePages = mView->
visiblePages();
286 QList< QgsLayoutGuide * > guides = layout->
guides().
guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
288 p->setRenderHint( QPainter::Antialiasing,
true );
289 p->setPen( Qt::NoPen );
290 const auto constGuides = guides;
293 if ( visiblePages.contains( guide->page() ) )
295 if ( guide == mHoverGuide )
297 p->setBrush( QBrush( QColor( 255, 0, 0, 225 ) ) );
301 p->setBrush( QBrush( QColor( 255, 0, 0, 150 ) ) );
304 switch ( mOrientation )
307 point = QPointF( guide->layoutPosition(), 0 );
311 point = QPointF( 0, guide->layoutPosition() );
314 drawGuideAtPos( p, convertLayoutPointToLocal( point ) );
320 void 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() );
341 void 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 );
358 QPointF QgsLayoutRuler::convertLocalPointToLayout( QPoint localPoint )
const 360 QPoint viewPoint = mView->mapFromGlobal( mapToGlobal( localPoint ) );
361 return mView->mapToScene( viewPoint );
364 QPoint QgsLayoutRuler::convertLayoutPointToLocal( QPointF layoutPoint )
const 366 QPoint viewPoint = mView->mapFromScene( layoutPoint );
367 return mapFromGlobal( mView->mapToGlobal( viewPoint ) );
370 QgsLayoutGuide *QgsLayoutRuler::guideAtPoint( QPoint localPoint )
const 375 QPointF layoutPoint = convertLocalPointToLayout( localPoint );
376 QList< QgsLayoutItemPage * > visiblePages = mView->
visiblePages();
377 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 )
414 void 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 );
423 void QgsLayoutRuler::drawSmallDivisions( QPainter *painter,
double startPos,
int numDivisions,
double rulerScale,
double maxPos )
425 if ( numDivisions == 0 )
429 double smallMarkerPos = startPos;
430 double smallDivisionSpacing = rulerScale / numDivisions;
432 double pixelCoord = 0.0;
435 for (
int i = 0; i < numDivisions; ++i )
437 smallMarkerPos += smallDivisionSpacing;
439 if ( maxPos > 0 && smallMarkerPos > maxPos )
446 switch ( mOrientation )
450 pixelCoord = mTransform.map( QPointF( smallMarkerPos, 0 ) ).x();
455 pixelCoord = mTransform.map( QPointF( 0, smallMarkerPos ) ).y();
462 if ( ( numDivisions == 10 && i == 4 ) || ( numDivisions == 4 && i == 1 ) )
465 lineSize = mRulerMinSize / 1.5;
469 lineSize = mRulerMinSize / 1.25;
473 switch ( mOrientation )
477 painter->drawLine( pixelCoord, lineSize, pixelCoord, mRulerMinSize );
482 painter->drawLine( lineSize, pixelCoord, mRulerMinSize, pixelCoord );
489 int QgsLayoutRuler::optimumScale(
double minPixelDiff,
int &magnitude,
int &multiple )
494 for (
unsigned int magnitudeCandidate = 0; magnitudeCandidate <
COUNT_VALID_MAGNITUDES; ++magnitudeCandidate )
496 for (
unsigned int multipleCandidate = 0; multipleCandidate <
COUNT_VALID_MULTIPLES; ++multipleCandidate )
498 int candidateScale = VALID_SCALE_MULTIPLES[multipleCandidate] * VALID_SCALE_MAGNITUDES[magnitudeCandidate];
500 double pixelDiff = mTransform.map( QPointF( candidateScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
501 if ( pixelDiff > minPixelDiff )
504 magnitude = VALID_SCALE_MAGNITUDES[magnitudeCandidate];
505 multiple = VALID_SCALE_MULTIPLES[multipleCandidate];
506 return candidateScale;
514 int QgsLayoutRuler::optimumNumberDivisions(
double rulerScale,
int scaleMultiple )
517 double largeDivisionSize = mTransform.map( QPointF( rulerScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
520 QList<int> validSmallDivisions;
521 switch ( scaleMultiple )
526 validSmallDivisions << 10 << 5 << 2;
531 validSmallDivisions << 10 << 4 << 2;
536 validSmallDivisions << 10 << 5;
541 QList<int>::iterator divisions_it;
542 for ( divisions_it = validSmallDivisions.begin(); divisions_it != validSmallDivisions.end(); ++divisions_it )
545 double candidateSize = largeDivisionSize / ( *divisions_it );
547 if ( candidateSize >= mMinPixelsPerDivision )
550 return ( *divisions_it );
561 mTransform = transform;
578 mMarkerPos = mView->mapFromScene( position );
584 mMarkerPos =
event->pos();
591 if ( mCreatingGuide || mDraggingGuide )
594 displayPos = convertLocalPointToLayout( event->pos() );
596 if ( mCreatingGuide )
604 QPen linePen = mGuideItem->pen();
608 linePen.setColor( QColor( 255, 0, 0, 150 ) );
612 linePen.setColor( QColor( 255, 0, 0, 225 ) );
614 mGuideItem->setPen( linePen );
615 switch ( mOrientation )
620 mGuideItem->setLine( page->scenePos().x(), displayPos.y(), page->scenePos().x() + page->rect().width(), displayPos.y() );
621 displayPos.setX( 0 );
627 mGuideItem->setLine( displayPos.x(), page->scenePos().y(), displayPos.x(), page->scenePos().y() + page->rect().height() );
628 displayPos.setY( 0 );
636 switch ( mOrientation )
641 displayPos.setY( 0 );
647 displayPos.setX( 0 );
656 mHoverGuide = guideAtPoint( event->pos() );
659 setCursor( mOrientation == Qt::Vertical ? Qt::SplitVCursor : Qt::SplitHCursor );
663 setCursor( Qt::ArrowCursor );
667 displayPos = mTransform.inverted().map( event->pos() );
668 switch ( mOrientation )
673 displayPos.setY( 0 );
679 displayPos.setX( 0 );
692 if ( event->button() == Qt::LeftButton )
694 mDraggingGuide = guideAtPoint( event->pos() );
695 if ( !mDraggingGuide )
700 mCreatingGuide =
true;
701 createTemporaryGuideItem();
704 switch ( mOrientation )
708 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitHCursor : Qt::SplitVCursor );
712 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitVCursor : Qt::SplitHCursor );
723 if ( event->button() == Qt::LeftButton )
725 if ( mDraggingGuide )
727 QApplication::restoreOverrideCursor();
729 QPointF layoutPoint = convertLocalPointToLayout( event->pos() );
733 bool deleteGuide =
false;
737 if ( layoutPoint.y() < page->scenePos().y() || layoutPoint.y() > page->scenePos().y() + page->rect().height() )
742 if ( layoutPoint.x() < page->scenePos().x() || layoutPoint.x() > page->scenePos().x() + page->rect().width() )
751 mDraggingGuide =
nullptr;
755 mCreatingGuide =
false;
756 QApplication::restoreOverrideCursor();
758 mGuideItem =
nullptr;
761 switch ( mOrientation )
765 if ( event->pos().y() <= height() )
771 if ( event->pos().x() <= width() )
780 QPointF scenePos = convertLocalPointToLayout( event->pos() );
785 std::unique_ptr< QgsLayoutGuide > guide;
786 switch ( mOrientation )
805 else if ( event->button() == Qt::RightButton )
808 mMenu->popup( event->globalPos() );
const unsigned int COUNT_VALID_MULTIPLES
QgsLayoutGuideCollection & guides()
Returns a reference to the layout's guide collection, which manages page snap guides.
A graphical widget to display and interact with QgsLayouts.
int pageNumberForPoint(QPointF point) const
Returns the page number corresponding to a point in the layout (in layout units). ...
Contains the configuration for a single snap guide used by a layout.
QgsLayoutRuler(QWidget *parent=nullptr, Qt::Orientation orientation=Qt::Horizontal)
Constructor for QgsLayoutRuler, with the specified parent widget and orientation. ...
QgsUnitTypes::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 int RULER_FONT_SIZE
void setCursorPosition(QPointF position)
Updates the position of the marker showing the current mouse position within the view.
Qt::Orientation orientation() const
Returns the guide's orientation.
const unsigned int COUNT_VALID_MAGNITUDES
void mousePressEvent(QMouseEvent *event) override
double spaceBetweenPages() const
Returns the space between pages, in layout units.
void cursorPosChanged(QPointF)
Emitted when mouse cursor coordinates change.
void addGuide(QgsLayoutGuide *guide)
Adds a guide to the collection.
void cursorPosChanged(QPointF layoutPoint)
Emitted when the mouse cursor coordinates change within the view.
QgsLayoutItemPage * page(int pageNumber)
Returns a specific page (by pageNumber) from the collection.
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout's page collection, which stores and manages page items in the layout...
QSize minimumSizeHint() const override
void setGuideLayoutPosition(QgsLayoutGuide *guide, double position)
Sets the absolute position (in layout coordinates) for guide within the layout.
void setContextMenu(QMenu *menu)
Sets a context menu to show when right clicking occurs on the ruler.
QgsLayoutItemPage * pageAtPoint(QPointF point) const
Returns the page at a specified point (in layout coordinates).
void setSceneTransform(const QTransform &transform)
Sets the current scene transform.
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.
void setLayoutView(QgsLayoutView *view)
Sets the current layout view to synchronize the ruler with.
void mouseMoveEvent(QMouseEvent *event) override
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).
QList< QgsLayoutGuide *> guides()
Returns a list of all guides contained in the collection.
QgsLayoutItemPage * page()
Returns the page the guide is contained within.
void mouseReleaseEvent(QMouseEvent *event) override
void paintEvent(QPaintEvent *event) override
void removeGuide(QgsLayoutGuide *guide)
Removes the specified guide, and deletes it.
Item representing the paper in a layout.