21#include <QDragEnterEvent> 
   22#include <QGraphicsLineItem> 
   30const int QgsLayoutRuler::VALID_SCALE_MULTIPLES[] = {1, 2, 5};
 
   31const 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  const 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  const 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  const int mmDisplay = optimumScale( mScaleMinPixelsWidth, magnitude, multiple );
 
  113  const int numSmallDivisions = optimumNumberDivisions( mmDisplay, multiple );
 
  115  switch ( mOrientation )
 
  125      const double startX = t.map( QPointF( 0, 0 ) ).x();
 
  126      const 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        const double pixelCoord = mTransform.map( QPointF( markerPos, 0 ) ).x();
 
  139        p.drawLine( pixelCoord, 0, pixelCoord, mRulerMinSize );
 
  140        p.drawText( QPointF( pixelCoord + mPixelsBetweenLineAndText, mTextBaseline ), QLocale().toString( markerPos ) );
 
  143        drawSmallDivisions( &p, markerPos, numSmallDivisions, mmDisplay, endX );
 
  145        markerPos += mmDisplay;
 
  156      const double startY = t.map( QPointF( 0, 0 ) ).y(); 
 
  157      const 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        const double firstPageY = mTransform.map( QPointF( 0, 0 ) ).y();
 
  184        while ( beforePageCoord > startY )
 
  186          const double pixelCoord = mTransform.map( QPointF( 0, beforePageCoord ) ).y();
 
  187          p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
 
  189          const QString label = QLocale().toString( beforePageCoord );
 
  190          const 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          const double pixelCoord = mTransform.map( QPointF( 0, totalCoord ) ).y();
 
  235          p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
 
  237          const QString label = QLocale().toString( pageCoord );
 
  238          const 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;
 
  264void 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() );
 
  283void QgsLayoutRuler::drawGuideMarkers( QPainter *p, 
QgsLayout *layout )
 
  285  const QList< QgsLayoutItemPage * > visiblePages = mView->
visiblePages();
 
  286  const 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 ) );
 
  319void 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() );
 
  340void 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 );
 
  357QPointF QgsLayoutRuler::convertLocalPointToLayout( QPoint localPoint )
 const 
  359  const QPoint viewPoint = mView->mapFromGlobal( mapToGlobal( localPoint ) );
 
  360  return  mView->mapToScene( viewPoint );
 
  363QPoint QgsLayoutRuler::convertLayoutPointToLocal( QPointF layoutPoint )
 const 
  365  const QPoint viewPoint = mView->mapFromScene( layoutPoint );
 
  366  return mapFromGlobal( mView->mapToGlobal( viewPoint ) );
 
  369QgsLayoutGuide *QgsLayoutRuler::guideAtPoint( QPoint localPoint )
 const 
  374  const QPointF layoutPoint = convertLocalPointToLayout( localPoint );
 
  375  const QList< QgsLayoutItemPage * > visiblePages = mView->
visiblePages();
 
  376  const 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 )
 
  413void 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 );
 
  421void QgsLayoutRuler::drawSmallDivisions( QPainter *painter, 
double startPos, 
int numDivisions, 
double rulerScale, 
double maxPos )
 
  423  if ( numDivisions == 0 )
 
  427  double smallMarkerPos = startPos;
 
  428  const 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 );
 
  487int 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      const int candidateScale = VALID_SCALE_MULTIPLES[multipleCandidate] * VALID_SCALE_MAGNITUDES[magnitudeCandidate];
 
  498      const 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;
 
  512int QgsLayoutRuler::optimumNumberDivisions( 
double rulerScale, 
int scaleMultiple )
 
  515  const 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    const 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();
 
  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      const 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      const QPointF scenePos = convertLocalPointToLayout( event->pos() );
 
  787      std::unique_ptr< QgsLayoutGuide > guide;
 
  788      switch ( mOrientation )
 
  807  else if ( event->button() == Qt::RightButton )
 
  809    if ( mCreatingGuide || mDraggingGuide )
 
  811      QApplication::restoreOverrideCursor();
 
  813      mGuideItem = 
nullptr;
 
  814      mCreatingGuide = 
false;
 
  815      if ( mDraggingGuide )
 
  819      mDraggingGuide = 
nullptr;
 
  822      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.
 
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.
 
@ 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