16#include "moc_qgslayoutruler.cpp" 
   22#include <QDragEnterEvent> 
   23#include <QGraphicsLineItem> 
   31const int QgsLayoutRuler::VALID_SCALE_MULTIPLES[] = { 1, 2, 5 };
 
   32const int QgsLayoutRuler::VALID_SCALE_MAGNITUDES[] = { 1, 10, 100, 1000, 10000 };
 
   36  , mOrientation( orientation )
 
   38  setMouseTracking( 
true );
 
   42  mRulerFontMetrics.reset( 
new QFontMetrics( mRulerFont ) );
 
   47  mScaleMinPixelsWidth = mRulerFontMetrics->boundingRect( QStringLiteral( 
"000" ) ).width() * 2.5;
 
   49  mRulerMinSize = mRulerFontMetrics->height() * 1.5;
 
   51  mMinPixelsPerDivision = mRulerMinSize / 4;
 
   53  if ( mMinPixelsPerDivision < 2 )
 
   54    mMinPixelsPerDivision = 2;
 
   56  mPixelsBetweenLineAndText = mRulerMinSize / 10;
 
   57  mTextBaseline = mRulerMinSize / 1.667;
 
   58  mMinSpacingVerticalLabels = mRulerMinSize / 5;
 
   60  const double guideMarkerSize = mRulerFontMetrics->horizontalAdvance( 
'*' );
 
   61  mDragGuideTolerance = guideMarkerSize;
 
   62  switch ( mOrientation )
 
   65      mGuideMarker << QPoint( -guideMarkerSize / 2, mRulerMinSize - guideMarkerSize ) << QPoint( 0, mRulerMinSize ) << QPoint( guideMarkerSize / 2, mRulerMinSize - guideMarkerSize );
 
   69      mGuideMarker << QPoint( mRulerMinSize - guideMarkerSize, -guideMarkerSize / 2 ) << QPoint( mRulerMinSize, 0 ) << QPoint( mRulerMinSize - guideMarkerSize, guideMarkerSize / 2 );
 
 
   76  return QSize( mRulerMinSize, mRulerMinSize );
 
 
   90  drawGuideMarkers( &p, layout );
 
   92  const QTransform t = mTransform.inverted();
 
   93  p.setFont( mRulerFont );
 
   95  QBrush brush = p.brush();
 
   96  QColor color = brush.color();
 
   97  color.setAlphaF( 0.7 );
 
   98  brush.setColor( color );
 
  102  color.setAlphaF( 0.7 );
 
  103  pen.setColor( color );
 
  109  const int mmDisplay = optimumScale( mScaleMinPixelsWidth, magnitude, multiple );
 
  112  const int numSmallDivisions = optimumNumberDivisions( mmDisplay, multiple );
 
  114  switch ( mOrientation )
 
  124      const double startX = t.map( QPointF( 0, 0 ) ).x();
 
  125      const double endX = t.map( QPointF( width(), 0 ) ).x();
 
  128      double markerPos = ( std::floor( startX / mmDisplay ) + 1 ) * mmDisplay;
 
  131      drawSmallDivisions( &p, markerPos, numSmallDivisions, -mmDisplay );
 
  133      while ( markerPos <= endX )
 
  135        const double pixelCoord = mTransform.map( QPointF( markerPos, 0 ) ).x();
 
  138        p.drawLine( pixelCoord, 0, pixelCoord, mRulerMinSize );
 
  139        p.drawText( QPointF( pixelCoord + mPixelsBetweenLineAndText, mTextBaseline ), QLocale().toString( markerPos ) );
 
  142        drawSmallDivisions( &p, markerPos, numSmallDivisions, mmDisplay, endX );
 
  144        markerPos += mmDisplay;
 
  155      const double startY = t.map( QPointF( 0, 0 ) ).y();      
 
  156      const double endY = t.map( QPointF( 0, height() ) ).y(); 
 
  162      double currentPageY = 0;
 
  163      for ( 
int page = 0; page < layout->
pageCollection()->pageCount(); ++page )
 
  165        if ( currentY < startY )
 
  168          currentPageY = currentY;
 
  173        if ( currentY > endY )
 
  179        double beforePageCoord = -mmDisplay;
 
  180        const double firstPageY = mTransform.map( QPointF( 0, 0 ) ).y();
 
  183        while ( beforePageCoord > startY )
 
  185          const double pixelCoord = mTransform.map( QPointF( 0, beforePageCoord ) ).y();
 
  186          p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
 
  188          const QString label = QLocale().toString( beforePageCoord );
 
  189          const int labelSize = mRulerFontMetrics->boundingRect( label ).width();
 
  192          if ( pixelCoord + labelSize + 8 < firstPageY )
 
  194            drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
 
  198          drawSmallDivisions( &p, beforePageCoord, numSmallDivisions, mmDisplay );
 
  200          beforePageCoord -= mmDisplay;
 
  204        drawSmallDivisions( &p, beforePageCoord + mmDisplay, numSmallDivisions, -mmDisplay, startY );
 
  207      double nextPageStartPos = 0;
 
  208      int nextPageStartPixel = 0;
 
  210      for ( 
int i = startPage; i <= endPage; ++i )
 
  212        double pageCoord = 0; 
 
  215        double totalCoord = currentPageY;
 
  222          nextPageStartPixel = mTransform.map( QPointF( 0, nextPageStartPos ) ).y();
 
  227          nextPageStartPos = 0;
 
  228          nextPageStartPixel = 0;
 
  231        while ( ( totalCoord < nextPageStartPos ) || ( ( nextPageStartPos == 0 ) && ( totalCoord <= endY ) ) )
 
  233          const double pixelCoord = mTransform.map( QPointF( 0, totalCoord ) ).y();
 
  234          p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
 
  236          const QString label = QLocale().toString( pageCoord );
 
  237          const int labelSize = mRulerFontMetrics->boundingRect( label ).width();
 
  240          if ( ( pixelCoord + labelSize + 8 < nextPageStartPixel )
 
  241               || ( nextPageStartPixel == 0 ) )
 
  243            drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
 
  247          drawSmallDivisions( &p, totalCoord, numSmallDivisions, mmDisplay, nextPageStartPos );
 
  249          pageCoord += mmDisplay;
 
  250          totalCoord += mmDisplay;
 
 
  263void QgsLayoutRuler::drawMarkerPos( QPainter *painter )
 
  266  painter->setPen( QColor( Qt::red ) );
 
  267  switch ( mOrientation )
 
  271      painter->drawLine( mMarkerPos.x(), 0, mMarkerPos.x(), mRulerMinSize );
 
  276      painter->drawLine( 0, mMarkerPos.y(), mRulerMinSize, mMarkerPos.y() );
 
  282void QgsLayoutRuler::drawGuideMarkers( QPainter *p, 
QgsLayout *layout )
 
  284  const QList<QgsLayoutItemPage *> visiblePages = mView->
visiblePages();
 
  285  const QList<QgsLayoutGuide *> guides = layout->
guides().
guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
 
  287  p->setRenderHint( QPainter::Antialiasing, 
true );
 
  288  p->setPen( Qt::NoPen );
 
  289  const auto constGuides = guides;
 
  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 )
 
  306          point = QPointF( guide->layoutPosition(), 0 );
 
  310          point = QPointF( 0, guide->layoutPosition() );
 
  313      drawGuideAtPos( p, convertLayoutPointToLocal( point ) );
 
  318void QgsLayoutRuler::drawGuideAtPos( QPainter *painter, QPoint pos )
 
  320  switch ( mOrientation )
 
  324      painter->translate( pos.x(), 0 );
 
  325      painter->drawPolygon( mGuideMarker );
 
  326      painter->translate( -pos.x(), 0 );
 
  331      painter->translate( 0, pos.y() );
 
  332      painter->drawPolygon( mGuideMarker );
 
  333      painter->translate( 0, -pos.y() );
 
  339void QgsLayoutRuler::createTemporaryGuideItem()
 
  345  mGuideItem = 
new QGraphicsLineItem();
 
  348  QPen linePen( Qt::DotLine );
 
  349  linePen.setColor( QColor( 255, 0, 0, 150 ) );
 
  350  linePen.setWidthF( 0 );
 
  351  mGuideItem->setPen( linePen );
 
  356QPointF QgsLayoutRuler::convertLocalPointToLayout( QPoint localPoint )
 const 
  358  const QPoint viewPoint = mView->mapFromGlobal( mapToGlobal( localPoint ) );
 
  359  return mView->mapToScene( viewPoint );
 
  362QPoint QgsLayoutRuler::convertLayoutPointToLocal( QPointF layoutPoint )
 const 
  364  const QPoint viewPoint = mView->mapFromScene( layoutPoint );
 
  365  return mapFromGlobal( mView->mapToGlobal( viewPoint ) );
 
  368QgsLayoutGuide *QgsLayoutRuler::guideAtPoint( QPoint localPoint )
 const 
  373  const QPointF layoutPoint = convertLocalPointToLayout( localPoint );
 
  374  const QList<QgsLayoutItemPage *> visiblePages = mView->
visiblePages();
 
  375  const QList<QgsLayoutGuide *> guides = mView->
currentLayout()->
guides().
guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
 
  377  double minDelta = std::numeric_limits<double>::max();
 
  378  const auto constGuides = guides;
 
  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 )
 
  412void 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 );
 
  420void QgsLayoutRuler::drawSmallDivisions( QPainter *painter, 
double startPos, 
int numDivisions, 
double rulerScale, 
double maxPos )
 
  422  if ( numDivisions == 0 )
 
  426  double smallMarkerPos = startPos;
 
  427  const double smallDivisionSpacing = rulerScale / numDivisions;
 
  429  double pixelCoord = 0.0;
 
  432  for ( 
int i = 0; i < numDivisions; ++i )
 
  434    smallMarkerPos += smallDivisionSpacing;
 
  436    if ( maxPos > 0 && smallMarkerPos > maxPos )
 
  443    switch ( mOrientation )
 
  447        pixelCoord = mTransform.map( QPointF( smallMarkerPos, 0 ) ).x();
 
  452        pixelCoord = mTransform.map( QPointF( 0, smallMarkerPos ) ).y();
 
  459    if ( ( numDivisions == 10 && i == 4 ) || ( numDivisions == 4 && i == 1 ) )
 
  462      lineSize = mRulerMinSize / 1.5;
 
  466      lineSize = mRulerMinSize / 1.25;
 
  470    switch ( mOrientation )
 
  474        painter->drawLine( pixelCoord, lineSize, pixelCoord, mRulerMinSize );
 
  479        painter->drawLine( lineSize, pixelCoord, mRulerMinSize, pixelCoord );
 
  486int QgsLayoutRuler::optimumScale( 
double minPixelDiff, 
int &magnitude, 
int &multiple )
 
  491  for ( 
unsigned int magnitudeCandidate = 0; magnitudeCandidate < 
COUNT_VALID_MAGNITUDES; ++magnitudeCandidate )
 
  493    for ( 
unsigned int multipleCandidate = 0; multipleCandidate < 
COUNT_VALID_MULTIPLES; ++multipleCandidate )
 
  495      const int candidateScale = VALID_SCALE_MULTIPLES[multipleCandidate] * VALID_SCALE_MAGNITUDES[magnitudeCandidate];
 
  497      const double pixelDiff = mTransform.map( QPointF( candidateScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
 
  498      if ( pixelDiff > minPixelDiff )
 
  501        magnitude = VALID_SCALE_MAGNITUDES[magnitudeCandidate];
 
  502        multiple = VALID_SCALE_MULTIPLES[multipleCandidate];
 
  503        return candidateScale;
 
  511int QgsLayoutRuler::optimumNumberDivisions( 
double rulerScale, 
int scaleMultiple )
 
  514  const double largeDivisionSize = mTransform.map( QPointF( rulerScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
 
  517  QList<int> validSmallDivisions;
 
  518  switch ( scaleMultiple )
 
  523      validSmallDivisions << 10 << 5 << 2;
 
  528      validSmallDivisions << 10 << 4 << 2;
 
  533      validSmallDivisions << 10 << 5;
 
  538  QList<int>::iterator divisions_it;
 
  539  for ( divisions_it = validSmallDivisions.begin(); divisions_it != validSmallDivisions.end(); ++divisions_it )
 
  542    const double candidateSize = largeDivisionSize / ( *divisions_it );
 
  544    if ( candidateSize >= mMinPixelsPerDivision )
 
  547      return ( *divisions_it );
 
  558  mTransform = transform;
 
 
  575  mMarkerPos = mView->mapFromScene( position );
 
 
  581  mMarkerPos = 
event->pos();
 
  588  if ( mCreatingGuide || mDraggingGuide )
 
  591    displayPos = convertLocalPointToLayout( event->pos() );
 
  593    if ( mCreatingGuide )
 
  601      QPen linePen = mGuideItem->pen();
 
  605        linePen.setColor( QColor( 255, 0, 0, 150 ) );
 
  609        linePen.setColor( QColor( 255, 0, 0, 225 ) );
 
  611      mGuideItem->setPen( linePen );
 
  612      switch ( mOrientation )
 
  617          mGuideItem->setLine( page->scenePos().x(), displayPos.y(), page->scenePos().x() + page->rect().width(), displayPos.y() );
 
  618          displayPos.setX( 0 );
 
  624          mGuideItem->setLine( displayPos.x(), page->scenePos().y(), displayPos.x(), page->scenePos().y() + page->rect().height() );
 
  625          displayPos.setY( 0 );
 
  633      switch ( mOrientation )
 
  638          displayPos.setY( 0 );
 
  644          displayPos.setX( 0 );
 
  653    mHoverGuide = guideAtPoint( event->pos() );
 
  656      setCursor( mOrientation == Qt::Vertical ? Qt::SplitVCursor : Qt::SplitHCursor );
 
  660      setCursor( Qt::ArrowCursor );
 
  664    displayPos = mTransform.inverted().map( event->pos() );
 
  665    switch ( mOrientation )
 
  670        displayPos.setY( 0 );
 
  676        displayPos.setX( 0 );
 
 
  689  if ( event->button() == Qt::LeftButton )
 
  691    mDraggingGuide = guideAtPoint( event->pos() );
 
  692    if ( !mDraggingGuide )
 
  697        mCreatingGuide = 
true;
 
  698        createTemporaryGuideItem();
 
  705    switch ( mOrientation )
 
  709        QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitHCursor : Qt::SplitVCursor );
 
  713        QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitVCursor : Qt::SplitHCursor );
 
 
  724  if ( event->button() == Qt::LeftButton )
 
  726    if ( mDraggingGuide )
 
  728      QApplication::restoreOverrideCursor();
 
  730      const QPointF layoutPoint = convertLocalPointToLayout( event->pos() );
 
  734      bool deleteGuide = 
false;
 
  738          if ( layoutPoint.y() < page->scenePos().y() || layoutPoint.y() > page->scenePos().y() + page->rect().height() )
 
  743          if ( layoutPoint.x() < page->scenePos().x() || layoutPoint.x() > page->scenePos().x() + page->rect().width() )
 
  752      mDraggingGuide = 
nullptr;
 
  756      mCreatingGuide = 
false;
 
  757      QApplication::restoreOverrideCursor();
 
  759      mGuideItem = 
nullptr;
 
  762      switch ( mOrientation )
 
  766          if ( event->pos().y() <= height() )
 
  772          if ( event->pos().x() <= width() )
 
  781      const QPointF scenePos = convertLocalPointToLayout( event->pos() );
 
  786      std::unique_ptr<QgsLayoutGuide> guide;
 
  787      switch ( mOrientation )
 
  806  else if ( event->button() == Qt::RightButton )
 
  808    if ( mCreatingGuide || mDraggingGuide )
 
  810      QApplication::restoreOverrideCursor();
 
  812      mGuideItem = 
nullptr;
 
  813      mCreatingGuide = 
false;
 
  814      if ( mDraggingGuide )
 
  818      mDraggingGuide = 
nullptr;
 
  821      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.
 
void setLayoutPosition(double position)
Sets the guide's position in absolute layout units.
 
double layoutPosition() const
Returns the guide's position in absolute layout units.
 
Item representing the paper in a layout.
 
Provides a method of storing measurements for use in QGIS layouts using a variety of different measur...
 
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 position)
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.
 
@ ZGuide
Z-value for page guides.
 
Qgis::LayoutUnit units() const
Returns the native units for the layout.
 
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