QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
qgslayoutruler.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayoutruler.cpp
3  ------------------
4  Date : July 2017
5  Copyright : (C) 2017 Nyall Dawson
6  Email : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 #include "qgslayoutruler.h"
16 #include "qgslayout.h"
17 #include "qgis.h"
18 #include "qgslayoutview.h"
19 #include "qgslogger.h"
21 #include <QDragEnterEvent>
22 #include <QGraphicsLineItem>
23 #include <QPainter>
24 #include <QMenu>
25 #include <cmath>
26 
27 const int RULER_FONT_SIZE = 8;
28 const unsigned int COUNT_VALID_MULTIPLES = 3;
29 const unsigned int COUNT_VALID_MAGNITUDES = 5;
30 const int QgsLayoutRuler::VALID_SCALE_MULTIPLES[] = {1, 2, 5};
31 const int QgsLayoutRuler::VALID_SCALE_MAGNITUDES[] = {1, 10, 100, 1000, 10000};
32 
33 QgsLayoutRuler::QgsLayoutRuler( QWidget *parent, Qt::Orientation orientation )
34  : QWidget( parent )
35  , mOrientation( orientation )
36 {
37  setMouseTracking( true );
38 
39  //calculate minimum size required for ruler text
40  mRulerFont.setPointSize( RULER_FONT_SIZE );
41  mRulerFontMetrics.reset( new QFontMetrics( mRulerFont ) );
42 
43  //calculate ruler sizes and marker separations
44 
45  //minimum gap required between major ticks is 3 digits * 250%, based on appearance
46  mScaleMinPixelsWidth = mRulerFontMetrics->boundingRect( QStringLiteral( "000" ) ).width() * 2.5;
47  //minimum ruler height is twice the font height in pixels
48  mRulerMinSize = mRulerFontMetrics->height() * 1.5;
49 
50  mMinPixelsPerDivision = mRulerMinSize / 4;
51  //each small division must be at least 2 pixels apart
52  if ( mMinPixelsPerDivision < 2 )
53  mMinPixelsPerDivision = 2;
54 
55  mPixelsBetweenLineAndText = mRulerMinSize / 10;
56  mTextBaseline = mRulerMinSize / 1.667;
57  mMinSpacingVerticalLabels = mRulerMinSize / 5;
58 
59 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
60  double guideMarkerSize = mRulerFontMetrics->width( QStringLiteral( "*" ) );
61 #else
62  double guideMarkerSize = mRulerFontMetrics->horizontalAdvance( '*' );
63 #endif
64  mDragGuideTolerance = guideMarkerSize;
65  switch ( mOrientation )
66  {
67  case Qt::Horizontal:
68  mGuideMarker << QPoint( -guideMarkerSize / 2, mRulerMinSize - guideMarkerSize ) << QPoint( 0, mRulerMinSize ) <<
69  QPoint( guideMarkerSize / 2, mRulerMinSize - guideMarkerSize );
70  break;
71 
72  case Qt::Vertical:
73  mGuideMarker << QPoint( mRulerMinSize - guideMarkerSize, -guideMarkerSize / 2 ) << QPoint( mRulerMinSize, 0 ) <<
74  QPoint( mRulerMinSize - guideMarkerSize, guideMarkerSize / 2 );
75  break;
76  }
77 }
78 
80 {
81  return QSize( mRulerMinSize, mRulerMinSize );
82 }
83 
84 void QgsLayoutRuler::paintEvent( QPaintEvent *event )
85 {
86  Q_UNUSED( event )
87  if ( !mView || !mView->currentLayout() )
88  {
89  return;
90  }
91 
92  QgsLayout *layout = mView->currentLayout();
93  QPainter p( this );
94 
95  drawGuideMarkers( &p, layout );
96 
97  QTransform t = mTransform.inverted();
98  p.setFont( mRulerFont );
99  // keep same default color, but lower opacity a tad
100  QBrush brush = p.brush();
101  QColor color = brush.color();
102  color.setAlphaF( 0.7 );
103  brush.setColor( color );
104  p.setBrush( brush );
105  QPen pen = p.pen();
106  color = pen.color();
107  color.setAlphaF( 0.7 );
108  pen.setColor( color );
109  p.setPen( pen );
110 
111  //find optimum scale for ruler (size of numbered divisions)
112  int magnitude = 1;
113  int multiple = 1;
114  int mmDisplay = optimumScale( mScaleMinPixelsWidth, magnitude, multiple );
115 
116  //find optimum number of small divisions
117  int numSmallDivisions = optimumNumberDivisions( mmDisplay, multiple );
118 
119  switch ( mOrientation )
120  {
121  case Qt::Horizontal:
122  {
123  if ( qgsDoubleNear( width(), 0 ) )
124  {
125  return;
126  }
127 
128  //start x-coordinate
129  double startX = t.map( QPointF( 0, 0 ) ).x();
130  double endX = t.map( QPointF( width(), 0 ) ).x();
131 
132  //start marker position in mm
133  double markerPos = ( std::floor( startX / mmDisplay ) + 1 ) * mmDisplay;
134 
135  //draw minor ticks marks which occur before first major tick
136  drawSmallDivisions( &p, markerPos, numSmallDivisions, -mmDisplay );
137 
138  while ( markerPos <= endX )
139  {
140  double pixelCoord = mTransform.map( QPointF( markerPos, 0 ) ).x();
141 
142  //draw large division and text
143  p.drawLine( pixelCoord, 0, pixelCoord, mRulerMinSize );
144  p.drawText( QPointF( pixelCoord + mPixelsBetweenLineAndText, mTextBaseline ), QString::number( markerPos ) );
145 
146  //draw small divisions
147  drawSmallDivisions( &p, markerPos, numSmallDivisions, mmDisplay, endX );
148 
149  markerPos += mmDisplay;
150  }
151  break;
152  }
153  case Qt::Vertical:
154  {
155  if ( qgsDoubleNear( height(), 0 ) )
156  {
157  return;
158  }
159 
160  double startY = t.map( QPointF( 0, 0 ) ).y(); //start position in mm (total including space between pages)
161  double endY = t.map( QPointF( 0, height() ) ).y(); //stop position in mm (total including space between pages)
162 
163  // work out start page
164  int startPage = 0;
165  int endPage = 0;
166  double currentY = 0;
167  double currentPageY = 0;
168  for ( int page = 0; page < layout->pageCollection()->pageCount(); ++page )
169  {
170  if ( currentY < startY )
171  {
172  startPage = page;
173  currentPageY = currentY;
174  }
175  endPage = page;
176 
177  currentY += layout->pageCollection()->page( startPage )->rect().height() + layout->pageCollection()->spaceBetweenPages();
178  if ( currentY > endY )
179  break;
180  }
181 
182  if ( startY < 0 )
183  {
184  double beforePageCoord = -mmDisplay;
185  double firstPageY = mTransform.map( QPointF( 0, 0 ) ).y();
186 
187  //draw negative rulers which fall before first page
188  while ( beforePageCoord > startY )
189  {
190  double pixelCoord = mTransform.map( QPointF( 0, beforePageCoord ) ).y();
191  p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
192  //calc size of label
193  QString label = QString::number( beforePageCoord );
194  int labelSize = mRulerFontMetrics->boundingRect( label ).width();
195 
196  //draw label only if it fits in before start of next page
197  if ( pixelCoord + labelSize + 8 < firstPageY )
198  {
199  drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
200  }
201 
202  //draw small divisions
203  drawSmallDivisions( &p, beforePageCoord, numSmallDivisions, mmDisplay );
204 
205  beforePageCoord -= mmDisplay;
206  }
207 
208  //draw minor ticks marks which occur before first major tick
209  drawSmallDivisions( &p, beforePageCoord + mmDisplay, numSmallDivisions, -mmDisplay, startY );
210  }
211 
212  double nextPageStartPos = 0;
213  int nextPageStartPixel = 0;
214 
215  for ( int i = startPage; i <= endPage; ++i )
216  {
217  double pageCoord = 0; //page coordinate in mm
218  //total (composition) coordinate in mm, including space between pages
219 
220  double totalCoord = currentPageY;
221 
222  //position of next page
223  if ( i < endPage )
224  {
225  //not the last page
226  nextPageStartPos = currentPageY + layout->pageCollection()->page( i )->rect().height() + layout->pageCollection()->spaceBetweenPages();
227  nextPageStartPixel = mTransform.map( QPointF( 0, nextPageStartPos ) ).y();
228  }
229  else
230  {
231  //is the last page
232  nextPageStartPos = 0;
233  nextPageStartPixel = 0;
234  }
235 
236  while ( ( totalCoord < nextPageStartPos ) || ( ( nextPageStartPos == 0 ) && ( totalCoord <= endY ) ) )
237  {
238  double pixelCoord = mTransform.map( QPointF( 0, totalCoord ) ).y();
239  p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
240  //calc size of label
241  QString label = QString::number( pageCoord );
242  int labelSize = mRulerFontMetrics->boundingRect( label ).width();
243 
244  //draw label only if it fits in before start of next page
245  if ( ( pixelCoord + labelSize + 8 < nextPageStartPixel )
246  || ( nextPageStartPixel == 0 ) )
247  {
248  drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
249  }
250 
251  //draw small divisions
252  drawSmallDivisions( &p, totalCoord, numSmallDivisions, mmDisplay, nextPageStartPos );
253 
254  pageCoord += mmDisplay;
255  totalCoord += mmDisplay;
256  }
257  if ( i < endPage )
258  currentPageY += layout->pageCollection()->page( i )->rect().height() + layout->pageCollection()->spaceBetweenPages();
259  }
260  break;
261  }
262  }
263 
264  //draw current marker pos
265  drawMarkerPos( &p );
266 }
267 
268 void QgsLayoutRuler::drawMarkerPos( QPainter *painter )
269 {
270  //draw current marker pos in red
271  painter->setPen( QColor( Qt::red ) );
272  switch ( mOrientation )
273  {
274  case Qt::Horizontal:
275  {
276  painter->drawLine( mMarkerPos.x(), 0, mMarkerPos.x(), mRulerMinSize );
277  break;
278  }
279  case Qt::Vertical:
280  {
281  painter->drawLine( 0, mMarkerPos.y(), mRulerMinSize, mMarkerPos.y() );
282  break;
283  }
284  }
285 }
286 
287 void QgsLayoutRuler::drawGuideMarkers( QPainter *p, QgsLayout *layout )
288 {
289  QList< QgsLayoutItemPage * > visiblePages = mView->visiblePages();
290  QList< QgsLayoutGuide * > guides = layout->guides().guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
291  QgsScopedQPainterState painterState( p );
292  p->setRenderHint( QPainter::Antialiasing, true );
293  p->setPen( Qt::NoPen );
294  const auto constGuides = guides;
295  for ( QgsLayoutGuide *guide : constGuides )
296  {
297  if ( visiblePages.contains( guide->page() ) )
298  {
299  if ( guide == mHoverGuide )
300  {
301  p->setBrush( QBrush( QColor( 255, 0, 0, 225 ) ) );
302  }
303  else
304  {
305  p->setBrush( QBrush( QColor( 255, 0, 0, 150 ) ) );
306  }
307  QPointF point;
308  switch ( mOrientation )
309  {
310  case Qt::Horizontal:
311  point = QPointF( guide->layoutPosition(), 0 );
312  break;
313 
314  case Qt::Vertical:
315  point = QPointF( 0, guide->layoutPosition() );
316  break;
317  }
318  drawGuideAtPos( p, convertLayoutPointToLocal( point ) );
319  }
320  }
321 }
322 
323 void QgsLayoutRuler::drawGuideAtPos( QPainter *painter, QPoint pos )
324 {
325  switch ( mOrientation )
326  {
327  case Qt::Horizontal:
328  {
329  painter->translate( pos.x(), 0 );
330  painter->drawPolygon( mGuideMarker );
331  painter->translate( -pos.x(), 0 );
332  break;
333  }
334  case Qt::Vertical:
335  {
336  painter->translate( 0, pos.y() );
337  painter->drawPolygon( mGuideMarker );
338  painter->translate( 0, -pos.y() );
339  break;
340  }
341  }
342 }
343 
344 void QgsLayoutRuler::createTemporaryGuideItem()
345 {
346  if ( !mView->currentLayout() )
347  return;
348 
349  delete mGuideItem;
350  mGuideItem = new QGraphicsLineItem();
351 
352  mGuideItem->setZValue( QgsLayout::ZGuide );
353  QPen linePen( Qt::DotLine );
354  linePen.setColor( QColor( 255, 0, 0, 150 ) );
355  linePen.setWidthF( 0 );
356  mGuideItem->setPen( linePen );
357 
358  mView->currentLayout()->addItem( mGuideItem );
359 }
360 
361 QPointF QgsLayoutRuler::convertLocalPointToLayout( QPoint localPoint ) const
362 {
363  QPoint viewPoint = mView->mapFromGlobal( mapToGlobal( localPoint ) );
364  return mView->mapToScene( viewPoint );
365 }
366 
367 QPoint QgsLayoutRuler::convertLayoutPointToLocal( QPointF layoutPoint ) const
368 {
369  QPoint viewPoint = mView->mapFromScene( layoutPoint );
370  return mapFromGlobal( mView->mapToGlobal( viewPoint ) );
371 }
372 
373 QgsLayoutGuide *QgsLayoutRuler::guideAtPoint( QPoint localPoint ) const
374 {
375  if ( !mView->currentLayout() )
376  return nullptr;
377 
378  QPointF layoutPoint = convertLocalPointToLayout( localPoint );
379  QList< QgsLayoutItemPage * > visiblePages = mView->visiblePages();
380  QList< QgsLayoutGuide * > guides = mView->currentLayout()->guides().guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
381  QgsLayoutGuide *closestGuide = nullptr;
382  double minDelta = std::numeric_limits<double>::max();
383  const auto constGuides = guides;
384  for ( QgsLayoutGuide *guide : constGuides )
385  {
386  if ( visiblePages.contains( guide->page() ) )
387  {
388  double currentDelta = 0;
389  switch ( mOrientation )
390  {
391  case Qt::Horizontal:
392  currentDelta = std::fabs( layoutPoint.x() - guide->layoutPosition() );
393  break;
394 
395  case Qt::Vertical:
396  currentDelta = std::fabs( layoutPoint.y() - guide->layoutPosition() );
397  break;
398  }
399  if ( currentDelta < minDelta )
400  {
401  minDelta = currentDelta;
402  closestGuide = guide;
403  }
404  }
405  }
406 
407  if ( minDelta * mView->transform().m11() <= mDragGuideTolerance )
408  {
409  return closestGuide;
410  }
411  else
412  {
413  return nullptr;
414  }
415 }
416 
417 void QgsLayoutRuler::drawRotatedText( QPainter *painter, QPointF pos, const QString &text )
418 {
419  QgsScopedQPainterState painterState( painter );
420  painter->translate( pos.x(), pos.y() );
421  painter->rotate( 270 );
422  painter->drawText( 0, 0, text );
423 }
424 
425 void QgsLayoutRuler::drawSmallDivisions( QPainter *painter, double startPos, int numDivisions, double rulerScale, double maxPos )
426 {
427  if ( numDivisions == 0 )
428  return;
429 
430  //draw small divisions starting at startPos (in mm)
431  double smallMarkerPos = startPos;
432  double smallDivisionSpacing = rulerScale / numDivisions;
433 
434  double pixelCoord = 0.0;
435 
436  //draw numDivisions small divisions
437  for ( int i = 0; i < numDivisions; ++i )
438  {
439  smallMarkerPos += smallDivisionSpacing;
440 
441  if ( maxPos > 0 && smallMarkerPos > maxPos )
442  {
443  //stop drawing current division position is past maxPos
444  return;
445  }
446 
447  //calculate pixelCoordinate of the current division
448  switch ( mOrientation )
449  {
450  case Qt::Horizontal:
451  {
452  pixelCoord = mTransform.map( QPointF( smallMarkerPos, 0 ) ).x();
453  break;
454  }
455  case Qt::Vertical:
456  {
457  pixelCoord = mTransform.map( QPointF( 0, smallMarkerPos ) ).y();
458  break;
459  }
460  }
461 
462  //calculate height of small division line
463  double lineSize;
464  if ( ( numDivisions == 10 && i == 4 ) || ( numDivisions == 4 && i == 1 ) )
465  {
466  //if drawing the 5th line of 10 or drawing the 2nd line of 4, then draw it slightly longer
467  lineSize = mRulerMinSize / 1.5;
468  }
469  else
470  {
471  lineSize = mRulerMinSize / 1.25;
472  }
473 
474  //draw either horizontal or vertical line depending on ruler direction
475  switch ( mOrientation )
476  {
477  case Qt::Horizontal:
478  {
479  painter->drawLine( pixelCoord, lineSize, pixelCoord, mRulerMinSize );
480  break;
481  }
482  case Qt::Vertical:
483  {
484  painter->drawLine( lineSize, pixelCoord, mRulerMinSize, pixelCoord );
485  break;
486  }
487  }
488  }
489 }
490 
491 int QgsLayoutRuler::optimumScale( double minPixelDiff, int &magnitude, int &multiple )
492 {
493  //find optimal ruler display scale
494 
495  //loop through magnitudes and multiples to find optimum scale
496  for ( unsigned int magnitudeCandidate = 0; magnitudeCandidate < COUNT_VALID_MAGNITUDES; ++magnitudeCandidate )
497  {
498  for ( unsigned int multipleCandidate = 0; multipleCandidate < COUNT_VALID_MULTIPLES; ++multipleCandidate )
499  {
500  int candidateScale = VALID_SCALE_MULTIPLES[multipleCandidate] * VALID_SCALE_MAGNITUDES[magnitudeCandidate];
501  //find pixel size for each step using this candidate scale
502  double pixelDiff = mTransform.map( QPointF( candidateScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
503  if ( pixelDiff > minPixelDiff )
504  {
505  //found the optimum major scale
506  magnitude = VALID_SCALE_MAGNITUDES[magnitudeCandidate];
507  multiple = VALID_SCALE_MULTIPLES[multipleCandidate];
508  return candidateScale;
509  }
510  }
511  }
512 
513  return 100000;
514 }
515 
516 int QgsLayoutRuler::optimumNumberDivisions( double rulerScale, int scaleMultiple )
517 {
518  //calculate size in pixels of each marked ruler unit
519  double largeDivisionSize = mTransform.map( QPointF( rulerScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
520 
521  //now calculate optimum small tick scale, depending on marked ruler units
522  QList<int> validSmallDivisions;
523  switch ( scaleMultiple )
524  {
525  case 1:
526  //numbers increase by 1 increment each time, e.g., 1, 2, 3 or 10, 20, 30
527  //so we can draw either 10, 5 or 2 small ticks and have each fall on a nice value
528  validSmallDivisions << 10 << 5 << 2;
529  break;
530  case 2:
531  //numbers increase by 2 increments each time, e.g., 2, 4, 6 or 20, 40, 60
532  //so we can draw either 10, 4 or 2 small ticks and have each fall on a nice value
533  validSmallDivisions << 10 << 4 << 2;
534  break;
535  case 5:
536  //numbers increase by 5 increments each time, e.g., 5, 10, 15 or 100, 500, 1000
537  //so we can draw either 10 or 5 small ticks and have each fall on a nice value
538  validSmallDivisions << 10 << 5;
539  break;
540  }
541 
542  //calculate the most number of small divisions we can draw without them being too close to each other
543  QList<int>::iterator divisions_it;
544  for ( divisions_it = validSmallDivisions.begin(); divisions_it != validSmallDivisions.end(); ++divisions_it )
545  {
546  //find pixel size for this small division
547  double candidateSize = largeDivisionSize / ( *divisions_it );
548  //check if this separation is more then allowed min separation
549  if ( candidateSize >= mMinPixelsPerDivision )
550  {
551  //found a good candidate, return it
552  return ( *divisions_it );
553  }
554  }
555 
556  //unable to find a good candidate
557  return 0;
558 }
559 
560 
561 void QgsLayoutRuler::setSceneTransform( const QTransform &transform )
562 {
563  mTransform = transform;
564  update();
565 }
566 
568 {
569  mView = view;
571 }
572 
574 {
575  mMenu = menu;
576 }
577 
578 void QgsLayoutRuler::setCursorPosition( QPointF position )
579 {
580  mMarkerPos = mView->mapFromScene( position );
581  update();
582 }
583 
584 void QgsLayoutRuler::mouseMoveEvent( QMouseEvent *event )
585 {
586  mMarkerPos = event->pos();
587  update();
588 
589  if ( !mView->currentLayout() )
590  return;
591 
592  QPointF displayPos;
593  if ( mCreatingGuide || mDraggingGuide )
594  {
595  // event -> layout coordinates
596  displayPos = convertLocalPointToLayout( event->pos() );
597 
598  if ( mCreatingGuide )
599  {
600  QgsLayout *layout = mView->currentLayout();
601  int pageNo = layout->pageCollection()->pageNumberForPoint( displayPos );
602  QgsLayoutItemPage *page = layout->pageCollection()->page( pageNo );
603  if ( !page )
604  return;
605 
606  QPen linePen = mGuideItem->pen();
607  // if guide preview is outside a page draw it a lot fainter, to indicate it's invalid
608  if ( !layout->pageCollection()->pageAtPoint( displayPos ) )
609  {
610  linePen.setColor( QColor( 255, 0, 0, 150 ) );
611  }
612  else
613  {
614  linePen.setColor( QColor( 255, 0, 0, 225 ) );
615  }
616  mGuideItem->setPen( linePen );
617  switch ( mOrientation )
618  {
619  case Qt::Horizontal:
620  {
621  //mouse is creating a horizontal ruler, so don't show x coordinate
622  mGuideItem->setLine( page->scenePos().x(), displayPos.y(), page->scenePos().x() + page->rect().width(), displayPos.y() );
623  displayPos.setX( 0 );
624  break;
625  }
626  case Qt::Vertical:
627  {
628  //mouse is creating a vertical ruler, so don't show a y coordinate
629  mGuideItem->setLine( displayPos.x(), page->scenePos().y(), displayPos.x(), page->scenePos().y() + page->rect().height() );
630  displayPos.setY( 0 );
631  break;
632  }
633  }
634  }
635  else
636  {
637  // dragging guide
638  switch ( mOrientation )
639  {
640  case Qt::Horizontal:
641  {
642  mView->currentLayout()->guides().setGuideLayoutPosition( mDraggingGuide, displayPos.x() );
643  displayPos.setY( 0 );
644  break;
645  }
646  case Qt::Vertical:
647  {
648  mView->currentLayout()->guides().setGuideLayoutPosition( mDraggingGuide, displayPos.y() );
649  displayPos.setX( 0 );
650  break;
651  }
652  }
653  }
654  }
655  else
656  {
657  // is cursor over a guide marker?
658  mHoverGuide = guideAtPoint( event->pos() );
659  if ( mHoverGuide )
660  {
661  setCursor( mOrientation == Qt::Vertical ? Qt::SplitVCursor : Qt::SplitHCursor );
662  }
663  else
664  {
665  setCursor( Qt::ArrowCursor );
666  }
667 
668  //update cursor position in status bar
669  displayPos = mTransform.inverted().map( event->pos() );
670  switch ( mOrientation )
671  {
672  case Qt::Horizontal:
673  {
674  //mouse is over a horizontal ruler, so don't show a y coordinate
675  displayPos.setY( 0 );
676  break;
677  }
678  case Qt::Vertical:
679  {
680  //mouse is over a vertical ruler, so don't show an x coordinate
681  displayPos.setX( 0 );
682  break;
683  }
684  }
685  }
686  emit cursorPosChanged( displayPos );
687 }
688 
689 void QgsLayoutRuler::mousePressEvent( QMouseEvent *event )
690 {
691  if ( !mView->currentLayout() )
692  return;
693 
694  if ( event->button() == Qt::LeftButton )
695  {
696  mDraggingGuide = guideAtPoint( event->pos() );
697  if ( !mDraggingGuide )
698  {
699  // if no guide at the point, then we're creating one
700  if ( mView->currentLayout()->pageCollection()->pageCount() > 0 )
701  {
702  mCreatingGuide = true;
703  createTemporaryGuideItem();
704  }
705  }
706  switch ( mOrientation )
707  {
708  case Qt::Horizontal:
709  {
710  QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitHCursor : Qt::SplitVCursor );
711  break;
712  }
713  case Qt::Vertical:
714  QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitVCursor : Qt::SplitHCursor );
715  break;
716  }
717  }
718 }
719 
720 void QgsLayoutRuler::mouseReleaseEvent( QMouseEvent *event )
721 {
722  if ( !mView->currentLayout() )
723  return;
724 
725  if ( event->button() == Qt::LeftButton )
726  {
727  if ( mDraggingGuide )
728  {
729  QApplication::restoreOverrideCursor();
730 
731  QPointF layoutPoint = convertLocalPointToLayout( event->pos() );
732 
733  // delete guide if it ends outside of page
734  QgsLayoutItemPage *page = mDraggingGuide->page();
735  bool deleteGuide = false;
736  switch ( mDraggingGuide->orientation() )
737  {
738  case Qt::Horizontal:
739  if ( layoutPoint.y() < page->scenePos().y() || layoutPoint.y() > page->scenePos().y() + page->rect().height() )
740  deleteGuide = true;
741  break;
742 
743  case Qt::Vertical:
744  if ( layoutPoint.x() < page->scenePos().x() || layoutPoint.x() > page->scenePos().x() + page->rect().width() )
745  deleteGuide = true;
746  break;
747  }
748 
749  if ( deleteGuide )
750  {
751  mView->currentLayout()->guides().removeGuide( mDraggingGuide );
752  }
753  mDraggingGuide = nullptr;
754  }
755  else
756  {
757  mCreatingGuide = false;
758  QApplication::restoreOverrideCursor();
759  delete mGuideItem;
760  mGuideItem = nullptr;
761 
762  // check that cursor left the ruler
763  switch ( mOrientation )
764  {
765  case Qt::Horizontal:
766  {
767  if ( event->pos().y() <= height() )
768  return;
769  break;
770  }
771  case Qt::Vertical:
772  {
773  if ( event->pos().x() <= width() )
774  return;
775  break;
776  }
777  }
778 
779  QgsLayout *layout = mView->currentLayout();
780 
781  // create guide
782  QPointF scenePos = convertLocalPointToLayout( event->pos() );
783  QgsLayoutItemPage *page = layout->pageCollection()->pageAtPoint( scenePos );
784  if ( !page )
785  return; // dragged outside of a page
786 
787  std::unique_ptr< QgsLayoutGuide > guide;
788  switch ( mOrientation )
789  {
790  case Qt::Horizontal:
791  {
792  //mouse is creating a horizontal guide
793  double posOnPage = layout->pageCollection()->positionOnPage( scenePos ).y();
794  guide.reset( new QgsLayoutGuide( Qt::Horizontal, QgsLayoutMeasurement( posOnPage, layout->units() ), page ) );
795  break;
796  }
797  case Qt::Vertical:
798  {
799  //mouse is creating a vertical guide
800  guide.reset( new QgsLayoutGuide( Qt::Vertical, QgsLayoutMeasurement( scenePos.x(), layout->units() ), page ) );
801  break;
802  }
803  }
804  mView->currentLayout()->guides().addGuide( guide.release() );
805  }
806  }
807  else if ( event->button() == Qt::RightButton )
808  {
809  if ( mMenu )
810  mMenu->popup( event->globalPos() );
811  }
812 }
QgsLayoutRuler::mouseMoveEvent
void mouseMoveEvent(QMouseEvent *event) override
Definition: qgslayoutruler.cpp:584
QgsLayoutRuler::paintEvent
void paintEvent(QPaintEvent *event) override
Definition: qgslayoutruler.cpp:84
QgsLayoutItemPage
Item representing the paper in a layout.
Definition: qgslayoutitempage.h:55
QgsLayoutGuideCollection::guides
QList< QgsLayoutGuide * > guides()
Returns a list of all guides contained in the collection.
Definition: qgslayoutguidecollection.cpp:475
QgsLayoutView::cursorPosChanged
void cursorPosChanged(QPointF layoutPoint)
Emitted when the mouse cursor coordinates change within the view.
QgsLayoutPageCollection::spaceBetweenPages
double spaceBetweenPages() const
Returns the space between pages, in layout units.
Definition: qgslayoutpagecollection.cpp:273
qgslayoutview.h
QgsLayoutView::visiblePages
QList< QgsLayoutItemPage * > visiblePages() const
Returns a list of page items which are currently visible in the view.
Definition: qgslayoutview.cpp:261
qgis.h
QgsLayout::units
QgsUnitTypes::LayoutUnit units() const
Returns the native units for the layout.
Definition: qgslayout.h:328
QgsLayoutPageCollection::pageAtPoint
QgsLayoutItemPage * pageAtPoint(QPointF point) const
Returns the page at a specified point (in layout coordinates).
Definition: qgslayoutpagecollection.cpp:207
QgsLayoutPageCollection::pageNumberForPoint
int pageNumberForPoint(QPointF point) const
Returns the page number corresponding to a point in the layout (in layout units).
Definition: qgslayoutpagecollection.cpp:155
QgsLayoutPageCollection::positionOnPage
QPointF positionOnPage(QPointF point) const
Returns the position within a page of a point in the layout (in layout units).
Definition: qgslayoutpagecollection.cpp:243
QgsLayoutGuideCollection::removeGuide
void removeGuide(QgsLayoutGuide *guide)
Removes the specified guide, and deletes it.
Definition: qgslayoutguidecollection.cpp:404
qgsDoubleNear
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:315
QgsLayoutRuler::mouseReleaseEvent
void mouseReleaseEvent(QMouseEvent *event) override
Definition: qgslayoutruler.cpp:720
QgsLayoutRuler::minimumSizeHint
QSize minimumSizeHint() const override
Definition: qgslayoutruler.cpp:79
QgsLayoutRuler::QgsLayoutRuler
QgsLayoutRuler(QWidget *parent=nullptr, Qt::Orientation orientation=Qt::Horizontal)
Constructor for QgsLayoutRuler, with the specified parent widget and orientation.
Definition: qgslayoutruler.cpp:33
QgsLayoutRuler::cursorPosChanged
void cursorPosChanged(QPointF)
Emitted when mouse cursor coordinates change.
QgsLayoutGuide::orientation
Qt::Orientation orientation() const
Returns the guide's orientation.
Definition: qgslayoutguidecollection.cpp:181
qgslayoutruler.h
QgsLayoutView::currentLayout
QgsLayout * currentLayout
Definition: qgslayoutview.h:63
QgsLayoutRuler::setLayoutView
void setLayoutView(QgsLayoutView *view)
Sets the current layout view to synchronize the ruler with.
Definition: qgslayoutruler.cpp:567
QgsLayoutPageCollection::page
QgsLayoutItemPage * page(int pageNumber)
Returns a specific page (by pageNumber) from the collection.
Definition: qgslayoutpagecollection.cpp:460
QgsLayoutGuideCollection::setGuideLayoutPosition
void setGuideLayoutPosition(QgsLayoutGuide *guide, double position)
Sets the absolute position (in layout coordinates) for guide within the layout.
Definition: qgslayoutguidecollection.cpp:413
QgsLayoutRuler::setCursorPosition
void setCursorPosition(QPointF position)
Updates the position of the marker showing the current mouse position within the view.
Definition: qgslayoutruler.cpp:578
QgsScopedQPainterState
Scoped object for saving and restoring a QPainter object's state.
Definition: qgsrendercontext.h:1120
QgsLayoutGuide
Contains the configuration for a single snap guide used by a layout.
Definition: qgslayoutguidecollection.h:43
qgslayout.h
QgsLayout::ZGuide
@ ZGuide
Z-value for page guides.
Definition: qgslayout.h:61
QgsLayoutRuler::mousePressEvent
void mousePressEvent(QMouseEvent *event) override
Definition: qgslayoutruler.cpp:689
QgsLayout::pageCollection
QgsLayoutPageCollection * pageCollection()
Returns a pointer to the layout's page collection, which stores and manages page items in the layout.
Definition: qgslayout.cpp:459
QgsLayout
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:50
COUNT_VALID_MULTIPLES
const unsigned int COUNT_VALID_MULTIPLES
Definition: qgslayoutruler.cpp:28
QgsLayoutView
A graphical widget to display and interact with QgsLayouts.
Definition: qgslayoutview.h:50
QgsLayout::guides
QgsLayoutGuideCollection & guides()
Returns a reference to the layout's guide collection, which manages page snap guides.
Definition: qgslayout.cpp:385
qgslayoutpagecollection.h
QgsLayoutRuler::setContextMenu
void setContextMenu(QMenu *menu)
Sets a context menu to show when right clicking occurs on the ruler.
Definition: qgslayoutruler.cpp:573
COUNT_VALID_MAGNITUDES
const unsigned int COUNT_VALID_MAGNITUDES
Definition: qgslayoutruler.cpp:29
QgsLayoutGuide::page
QgsLayoutItemPage * page()
Returns the page the guide is contained within.
Definition: qgslayoutguidecollection.cpp:58
QgsLayoutGuideCollection::addGuide
void addGuide(QgsLayoutGuide *guide)
Adds a guide to the collection.
Definition: qgslayoutguidecollection.cpp:385
qgslogger.h
QgsLayoutPageCollection::pageCount
int pageCount() const
Returns the number of pages in the collection.
Definition: qgslayoutpagecollection.cpp:455
QgsLayoutRuler::setSceneTransform
void setSceneTransform(const QTransform &transform)
Sets the current scene transform.
Definition: qgslayoutruler.cpp:561
QgsLayoutMeasurement
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
Definition: qgslayoutmeasurement.h:34
RULER_FONT_SIZE
const int RULER_FONT_SIZE
Definition: qgslayoutruler.cpp:27