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->boundingRect( QStringLiteral(
"000" ) ).width() * 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 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
60 double guideMarkerSize = mRulerFontMetrics->width( QStringLiteral(
"*" ) );
62 double guideMarkerSize = mRulerFontMetrics->horizontalAdvance(
'*' );
64 mDragGuideTolerance = guideMarkerSize;
65 switch ( mOrientation )
68 mGuideMarker << QPoint( -guideMarkerSize / 2, mRulerMinSize - guideMarkerSize ) << QPoint( 0, mRulerMinSize ) <<
69 QPoint( guideMarkerSize / 2, mRulerMinSize - guideMarkerSize );
73 mGuideMarker << QPoint( mRulerMinSize - guideMarkerSize, -guideMarkerSize / 2 ) << QPoint( mRulerMinSize, 0 ) <<
74 QPoint( mRulerMinSize - guideMarkerSize, guideMarkerSize / 2 );
81 return QSize( mRulerMinSize, mRulerMinSize );
95 drawGuideMarkers( &p, layout );
97 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 int mmDisplay = optimumScale( mScaleMinPixelsWidth, magnitude, multiple );
117 int numSmallDivisions = optimumNumberDivisions( mmDisplay, multiple );
119 switch ( mOrientation )
129 double startX = t.map( QPointF( 0, 0 ) ).x();
130 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 double pixelCoord = mTransform.map( QPointF( markerPos, 0 ) ).x();
143 p.drawLine( pixelCoord, 0, pixelCoord, mRulerMinSize );
144 p.drawText( QPointF( pixelCoord + mPixelsBetweenLineAndText, mTextBaseline ), QString::number( markerPos ) );
147 drawSmallDivisions( &p, markerPos, numSmallDivisions, mmDisplay, endX );
149 markerPos += mmDisplay;
160 double startY = t.map( QPointF( 0, 0 ) ).y();
161 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 double firstPageY = mTransform.map( QPointF( 0, 0 ) ).y();
188 while ( beforePageCoord > startY )
190 double pixelCoord = mTransform.map( QPointF( 0, beforePageCoord ) ).y();
191 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
193 QString label = QString::number( beforePageCoord );
194 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 double pixelCoord = mTransform.map( QPointF( 0, totalCoord ) ).y();
239 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
241 QString label = QString::number( pageCoord );
242 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;
268 void 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() );
287 void QgsLayoutRuler::drawGuideMarkers( QPainter *p,
QgsLayout *layout )
289 QList< QgsLayoutItemPage * > visiblePages = mView->
visiblePages();
290 QList< QgsLayoutGuide * > guides = layout->
guides().
guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
292 p->setRenderHint( QPainter::Antialiasing,
true );
293 p->setPen( Qt::NoPen );
294 const auto constGuides = guides;
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 ) );
323 void 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() );
344 void QgsLayoutRuler::createTemporaryGuideItem()
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 );
361 QPointF QgsLayoutRuler::convertLocalPointToLayout( QPoint localPoint )
const
363 QPoint viewPoint = mView->mapFromGlobal( mapToGlobal( localPoint ) );
364 return mView->mapToScene( viewPoint );
367 QPoint QgsLayoutRuler::convertLayoutPointToLocal( QPointF layoutPoint )
const
369 QPoint viewPoint = mView->mapFromScene( layoutPoint );
370 return mapFromGlobal( mView->mapToGlobal( viewPoint ) );
373 QgsLayoutGuide *QgsLayoutRuler::guideAtPoint( QPoint localPoint )
const
378 QPointF layoutPoint = convertLocalPointToLayout( localPoint );
379 QList< QgsLayoutItemPage * > visiblePages = mView->
visiblePages();
380 QList< QgsLayoutGuide * > guides = mView->
currentLayout()->
guides().
guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
382 double minDelta = std::numeric_limits<double>::max();
383 const auto constGuides = guides;
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 )
417 void QgsLayoutRuler::drawRotatedText( QPainter *painter, QPointF pos,
const QString &text )
420 painter->translate( pos.x(), pos.y() );
421 painter->rotate( 270 );
422 painter->drawText( 0, 0, text );
425 void QgsLayoutRuler::drawSmallDivisions( QPainter *painter,
double startPos,
int numDivisions,
double rulerScale,
double maxPos )
427 if ( numDivisions == 0 )
431 double smallMarkerPos = startPos;
432 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 );
491 int 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 int candidateScale = VALID_SCALE_MULTIPLES[multipleCandidate] * VALID_SCALE_MAGNITUDES[magnitudeCandidate];
502 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;
516 int QgsLayoutRuler::optimumNumberDivisions(
double rulerScale,
int scaleMultiple )
519 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 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();
593 if ( mCreatingGuide || mDraggingGuide )
596 displayPos = convertLocalPointToLayout( event->pos() );
598 if ( mCreatingGuide )
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 )
643 displayPos.setY( 0 );
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 );
694 if ( event->button() == Qt::LeftButton )
696 mDraggingGuide = guideAtPoint( event->pos() );
697 if ( !mDraggingGuide )
702 mCreatingGuide =
true;
703 createTemporaryGuideItem();
706 switch ( mOrientation )
710 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitHCursor : Qt::SplitVCursor );
714 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitVCursor : Qt::SplitHCursor );
725 if ( event->button() == Qt::LeftButton )
727 if ( mDraggingGuide )
729 QApplication::restoreOverrideCursor();
731 QPointF layoutPoint = convertLocalPointToLayout( event->pos() );
735 bool deleteGuide =
false;
739 if ( layoutPoint.y() < page->scenePos().y() || layoutPoint.y() > page->scenePos().y() + page->rect().height() )
744 if ( layoutPoint.x() < page->scenePos().x() || layoutPoint.x() > page->scenePos().x() + page->rect().width() )
753 mDraggingGuide =
nullptr;
757 mCreatingGuide =
false;
758 QApplication::restoreOverrideCursor();
760 mGuideItem =
nullptr;
763 switch ( mOrientation )
767 if ( event->pos().y() <= height() )
773 if ( event->pos().x() <= width() )
782 QPointF scenePos = convertLocalPointToLayout( event->pos() );
787 std::unique_ptr< QgsLayoutGuide > guide;
788 switch ( mOrientation )
807 else if ( event->button() == Qt::RightButton )
810 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.
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)
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.
QgsUnitTypes::LayoutUnit units() const
Returns the native units for the layout.
@ ZGuide
Z-value for page guides.
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