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() );
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
double layoutPosition() const
Returns the guide's position in absolute layout units.
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)
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.
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.