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 );
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 )
313 drawGuideAtPos( p, convertLayoutPointToLocal( point ) );
319 void QgsLayoutRuler::drawGuideAtPos( QPainter *painter, QPoint pos )
321 switch ( mOrientation )
325 painter->translate( pos.x(), 0 );
326 painter->drawPolygon( mGuideMarker );
327 painter->translate( -pos.x(), 0 );
332 painter->translate( 0, pos.y() );
333 painter->drawPolygon( mGuideMarker );
334 painter->translate( 0, -pos.y() );
340 void QgsLayoutRuler::createTemporaryGuideItem()
346 mGuideItem =
new QGraphicsLineItem();
349 QPen linePen( Qt::DotLine );
350 linePen.setColor( QColor( 255, 0, 0, 150 ) );
351 linePen.setWidthF( 0 );
352 mGuideItem->setPen( linePen );
357 QPointF QgsLayoutRuler::convertLocalPointToLayout( QPoint localPoint )
const 359 QPoint viewPoint = mView->mapFromGlobal( mapToGlobal( localPoint ) );
360 return mView->mapToScene( viewPoint );
363 QPoint QgsLayoutRuler::convertLayoutPointToLocal( QPointF layoutPoint )
const 365 QPoint viewPoint = mView->mapFromScene( layoutPoint );
366 return mapFromGlobal( mView->mapToGlobal( viewPoint ) );
369 QgsLayoutGuide *QgsLayoutRuler::guideAtPoint( QPoint localPoint )
const 374 QPointF layoutPoint = convertLocalPointToLayout( localPoint );
375 QList< QgsLayoutItemPage * > visiblePages = mView->
visiblePages();
376 QList< QgsLayoutGuide * > guides = mView->
currentLayout()->
guides().
guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
378 double minDelta = std::numeric_limits<double>::max();
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 )
412 void 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 );
421 void QgsLayoutRuler::drawSmallDivisions( QPainter *painter,
double startPos,
int numDivisions,
double rulerScale,
double maxPos )
423 if ( numDivisions == 0 )
427 double smallMarkerPos = startPos;
428 double smallDivisionSpacing = rulerScale / numDivisions;
430 double pixelCoord = 0.0;
433 for (
int i = 0; i < numDivisions; ++i )
435 smallMarkerPos += smallDivisionSpacing;
437 if ( maxPos > 0 && smallMarkerPos > maxPos )
444 switch ( mOrientation )
448 pixelCoord = mTransform.map( QPointF( smallMarkerPos, 0 ) ).x();
453 pixelCoord = mTransform.map( QPointF( 0, smallMarkerPos ) ).y();
460 if ( ( numDivisions == 10 && i == 4 ) || ( numDivisions == 4 && i == 1 ) )
463 lineSize = mRulerMinSize / 1.5;
467 lineSize = mRulerMinSize / 1.25;
471 switch ( mOrientation )
475 painter->drawLine( pixelCoord, lineSize, pixelCoord, mRulerMinSize );
480 painter->drawLine( lineSize, pixelCoord, mRulerMinSize, pixelCoord );
487 int QgsLayoutRuler::optimumScale(
double minPixelDiff,
int &magnitude,
int &multiple )
492 for (
unsigned int magnitudeCandidate = 0; magnitudeCandidate <
COUNT_VALID_MAGNITUDES; ++magnitudeCandidate )
494 for (
unsigned int multipleCandidate = 0; multipleCandidate <
COUNT_VALID_MULTIPLES; ++multipleCandidate )
496 int candidateScale = VALID_SCALE_MULTIPLES[multipleCandidate] * VALID_SCALE_MAGNITUDES[magnitudeCandidate];
498 double pixelDiff = mTransform.map( QPointF( candidateScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
499 if ( pixelDiff > minPixelDiff )
502 magnitude = VALID_SCALE_MAGNITUDES[magnitudeCandidate];
503 multiple = VALID_SCALE_MULTIPLES[multipleCandidate];
504 return candidateScale;
512 int QgsLayoutRuler::optimumNumberDivisions(
double rulerScale,
int scaleMultiple )
515 double largeDivisionSize = mTransform.map( QPointF( rulerScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
518 QList<int> validSmallDivisions;
519 switch ( scaleMultiple )
524 validSmallDivisions << 10 << 5 << 2;
529 validSmallDivisions << 10 << 4 << 2;
534 validSmallDivisions << 10 << 5;
539 QList<int>::iterator divisions_it;
540 for ( divisions_it = validSmallDivisions.begin(); divisions_it != validSmallDivisions.end(); ++divisions_it )
543 double candidateSize = largeDivisionSize / ( *divisions_it );
545 if ( candidateSize >= mMinPixelsPerDivision )
548 return ( *divisions_it );
559 mTransform = transform;
576 mMarkerPos = mView->mapFromScene( position );
582 mMarkerPos =
event->pos();
589 if ( mCreatingGuide || mDraggingGuide )
592 displayPos = convertLocalPointToLayout( event->pos() );
594 if ( mCreatingGuide )
602 QPen linePen = mGuideItem->pen();
606 linePen.setColor( QColor( 255, 0, 0, 150 ) );
610 linePen.setColor( QColor( 255, 0, 0, 225 ) );
612 mGuideItem->setPen( linePen );
613 switch ( mOrientation )
618 mGuideItem->setLine( page->scenePos().x(), displayPos.y(), page->scenePos().x() + page->rect().width(), displayPos.y() );
619 displayPos.setX( 0 );
625 mGuideItem->setLine( displayPos.x(), page->scenePos().y(), displayPos.x(), page->scenePos().y() + page->rect().height() );
626 displayPos.setY( 0 );
634 switch ( mOrientation )
639 displayPos.setY( 0 );
645 displayPos.setX( 0 );
654 mHoverGuide = guideAtPoint( event->pos() );
657 setCursor( mOrientation == Qt::Vertical ? Qt::SplitVCursor : Qt::SplitHCursor );
661 setCursor( Qt::ArrowCursor );
665 displayPos = mTransform.inverted().map( event->pos() );
666 switch ( mOrientation )
671 displayPos.setY( 0 );
677 displayPos.setX( 0 );
690 if ( event->button() == Qt::LeftButton )
692 mDraggingGuide = guideAtPoint( event->pos() );
693 if ( !mDraggingGuide )
698 mCreatingGuide =
true;
699 createTemporaryGuideItem();
702 switch ( mOrientation )
706 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitHCursor : Qt::SplitVCursor );
710 QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitVCursor : Qt::SplitHCursor );
721 if ( event->button() == Qt::LeftButton )
723 if ( mDraggingGuide )
725 QApplication::restoreOverrideCursor();
727 QPointF layoutPoint = convertLocalPointToLayout( event->pos() );
731 bool deleteGuide =
false;
735 if ( layoutPoint.y() < page->scenePos().y() || layoutPoint.y() > page->scenePos().y() + page->rect().height() )
740 if ( layoutPoint.x() < page->scenePos().x() || layoutPoint.x() > page->scenePos().x() + page->rect().width() )
749 mDraggingGuide =
nullptr;
753 mCreatingGuide =
false;
754 QApplication::restoreOverrideCursor();
756 mGuideItem =
nullptr;
759 switch ( mOrientation )
763 if ( event->pos().y() <= height() )
769 if ( event->pos().x() <= width() )
778 QPointF scenePos = convertLocalPointToLayout( event->pos() );
783 std::unique_ptr< QgsLayoutGuide > guide;
784 switch ( mOrientation )
803 else if ( event->button() == Qt::RightButton )
806 mMenu->popup( event->globalPos() );
int pageCount() const
Returns the number of pages in the collection.
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.
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. ...
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.
const unsigned int COUNT_VALID_MAGNITUDES
void mousePressEvent(QMouseEvent *event) override
void cursorPosChanged(QPointF)
Is emitted when mouse cursor coordinates change.
void addGuide(QgsLayoutGuide *guide)
Adds a guide to the collection.
void cursorPosChanged(QPointF layoutPoint)
Is 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.
QgsLayoutItemPage * pageAtPoint(QPointF point) const
Returns the page at a specified point (in layout coordinates).
void setContextMenu(QMenu *menu)
Sets a context menu to show when right clicking occurs on the ruler.
void setSceneTransform(const QTransform &transform)
Sets the current scene transform.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
QPointF positionOnPage(QPointF point) const
Returns the position within a page of a point in the layout (in layout units).
int pageNumberForPoint(QPointF point) const
Returns the page number corresponding to a point in the layout (in layout units). ...
void setLayoutView(QgsLayoutView *view)
Sets the current layout view to synchronize the ruler with.
void mouseMoveEvent(QMouseEvent *event) override
QList< QgsLayoutGuide * > guides()
Returns a list of all guides contained in the collection.
Qt::Orientation orientation() const
Returns the guide's orientation.
QgsLayoutItemPage * page()
Returns the page the guide is contained within.
QList< QgsLayoutItemPage * > visiblePages() const
Returns a list of page items which are currently visible in the view.
void mouseReleaseEvent(QMouseEvent *event) override
QgsUnitTypes::LayoutUnit units() const
Returns the native units for the layout.
double layoutPosition() const
Returns the guide's position in absolute layout units.
double spaceBetweenPages() const
Returns the space between pages, in layout units.
void paintEvent(QPaintEvent *event) override
void removeGuide(QgsLayoutGuide *guide)
Removes the specified guide, and deletes it.
Item representing the paper in a layout.