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   double guideMarkerSize = mRulerFontMetrics->horizontalAdvance( 
'*' );
 
   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;
 
  164       for ( 
int page = 0; page < layout->
pageCollection()->pageCount(); ++page )
 
  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->boundingRect( label ).width();
 
  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->boundingRect( label ).width();
 
  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 ) );
 
  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();
 
  379   const auto constGuides = guides;
 
  382     if ( visiblePages.contains( guide->page() ) )
 
  384       double currentDelta = 0;
 
  385       switch ( mOrientation )
 
  388           currentDelta = std::fabs( layoutPoint.x() - guide->layoutPosition() );
 
  392           currentDelta = std::fabs( layoutPoint.y() - guide->layoutPosition() );
 
  395       if ( currentDelta < minDelta )
 
  397         minDelta = currentDelta;
 
  398         closestGuide = guide;
 
  403   if ( minDelta * mView->transform().m11() <= mDragGuideTolerance )
 
  413 void QgsLayoutRuler::drawRotatedText( QPainter *painter, QPointF pos, 
const QString &text )
 
  416   painter->translate( pos.x(), pos.y() );
 
  417   painter->rotate( 270 );
 
  418   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() );
 
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