QGIS API Documentation  3.14.0-Pi (9f7028fd23)
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  p->save();
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  p->restore();
322 }
323 
324 void QgsLayoutRuler::drawGuideAtPos( QPainter *painter, QPoint pos )
325 {
326  switch ( mOrientation )
327  {
328  case Qt::Horizontal:
329  {
330  painter->translate( pos.x(), 0 );
331  painter->drawPolygon( mGuideMarker );
332  painter->translate( -pos.x(), 0 );
333  break;
334  }
335  case Qt::Vertical:
336  {
337  painter->translate( 0, pos.y() );
338  painter->drawPolygon( mGuideMarker );
339  painter->translate( 0, -pos.y() );
340  break;
341  }
342  }
343 }
344 
345 void QgsLayoutRuler::createTemporaryGuideItem()
346 {
347  if ( !mView->currentLayout() )
348  return;
349 
350  delete mGuideItem;
351  mGuideItem = new QGraphicsLineItem();
352 
353  mGuideItem->setZValue( QgsLayout::ZGuide );
354  QPen linePen( Qt::DotLine );
355  linePen.setColor( QColor( 255, 0, 0, 150 ) );
356  linePen.setWidthF( 0 );
357  mGuideItem->setPen( linePen );
358 
359  mView->currentLayout()->addItem( mGuideItem );
360 }
361 
362 QPointF QgsLayoutRuler::convertLocalPointToLayout( QPoint localPoint ) const
363 {
364  QPoint viewPoint = mView->mapFromGlobal( mapToGlobal( localPoint ) );
365  return mView->mapToScene( viewPoint );
366 }
367 
368 QPoint QgsLayoutRuler::convertLayoutPointToLocal( QPointF layoutPoint ) const
369 {
370  QPoint viewPoint = mView->mapFromScene( layoutPoint );
371  return mapFromGlobal( mView->mapToGlobal( viewPoint ) );
372 }
373 
374 QgsLayoutGuide *QgsLayoutRuler::guideAtPoint( QPoint localPoint ) const
375 {
376  if ( !mView->currentLayout() )
377  return nullptr;
378 
379  QPointF layoutPoint = convertLocalPointToLayout( localPoint );
380  QList< QgsLayoutItemPage * > visiblePages = mView->visiblePages();
381  QList< QgsLayoutGuide * > guides = mView->currentLayout()->guides().guides( mOrientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal );
382  QgsLayoutGuide *closestGuide = nullptr;
383  double minDelta = std::numeric_limits<double>::max();
384  const auto constGuides = guides;
385  for ( QgsLayoutGuide *guide : constGuides )
386  {
387  if ( visiblePages.contains( guide->page() ) )
388  {
389  double currentDelta = 0;
390  switch ( mOrientation )
391  {
392  case Qt::Horizontal:
393  currentDelta = std::fabs( layoutPoint.x() - guide->layoutPosition() );
394  break;
395 
396  case Qt::Vertical:
397  currentDelta = std::fabs( layoutPoint.y() - guide->layoutPosition() );
398  break;
399  }
400  if ( currentDelta < minDelta )
401  {
402  minDelta = currentDelta;
403  closestGuide = guide;
404  }
405  }
406  }
407 
408  if ( minDelta * mView->transform().m11() <= mDragGuideTolerance )
409  {
410  return closestGuide;
411  }
412  else
413  {
414  return nullptr;
415  }
416 }
417 
418 void QgsLayoutRuler::drawRotatedText( QPainter *painter, QPointF pos, const QString &text )
419 {
420  painter->save();
421  painter->translate( pos.x(), pos.y() );
422  painter->rotate( 270 );
423  painter->drawText( 0, 0, text );
424  painter->restore();
425 }
426 
427 void QgsLayoutRuler::drawSmallDivisions( QPainter *painter, double startPos, int numDivisions, double rulerScale, double maxPos )
428 {
429  if ( numDivisions == 0 )
430  return;
431 
432  //draw small divisions starting at startPos (in mm)
433  double smallMarkerPos = startPos;
434  double smallDivisionSpacing = rulerScale / numDivisions;
435 
436  double pixelCoord = 0.0;
437 
438  //draw numDivisions small divisions
439  for ( int i = 0; i < numDivisions; ++i )
440  {
441  smallMarkerPos += smallDivisionSpacing;
442 
443  if ( maxPos > 0 && smallMarkerPos > maxPos )
444  {
445  //stop drawing current division position is past maxPos
446  return;
447  }
448 
449  //calculate pixelCoordinate of the current division
450  switch ( mOrientation )
451  {
452  case Qt::Horizontal:
453  {
454  pixelCoord = mTransform.map( QPointF( smallMarkerPos, 0 ) ).x();
455  break;
456  }
457  case Qt::Vertical:
458  {
459  pixelCoord = mTransform.map( QPointF( 0, smallMarkerPos ) ).y();
460  break;
461  }
462  }
463 
464  //calculate height of small division line
465  double lineSize;
466  if ( ( numDivisions == 10 && i == 4 ) || ( numDivisions == 4 && i == 1 ) )
467  {
468  //if drawing the 5th line of 10 or drawing the 2nd line of 4, then draw it slightly longer
469  lineSize = mRulerMinSize / 1.5;
470  }
471  else
472  {
473  lineSize = mRulerMinSize / 1.25;
474  }
475 
476  //draw either horizontal or vertical line depending on ruler direction
477  switch ( mOrientation )
478  {
479  case Qt::Horizontal:
480  {
481  painter->drawLine( pixelCoord, lineSize, pixelCoord, mRulerMinSize );
482  break;
483  }
484  case Qt::Vertical:
485  {
486  painter->drawLine( lineSize, pixelCoord, mRulerMinSize, pixelCoord );
487  break;
488  }
489  }
490  }
491 }
492 
493 int QgsLayoutRuler::optimumScale( double minPixelDiff, int &magnitude, int &multiple )
494 {
495  //find optimal ruler display scale
496 
497  //loop through magnitudes and multiples to find optimum scale
498  for ( unsigned int magnitudeCandidate = 0; magnitudeCandidate < COUNT_VALID_MAGNITUDES; ++magnitudeCandidate )
499  {
500  for ( unsigned int multipleCandidate = 0; multipleCandidate < COUNT_VALID_MULTIPLES; ++multipleCandidate )
501  {
502  int candidateScale = VALID_SCALE_MULTIPLES[multipleCandidate] * VALID_SCALE_MAGNITUDES[magnitudeCandidate];
503  //find pixel size for each step using this candidate scale
504  double pixelDiff = mTransform.map( QPointF( candidateScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
505  if ( pixelDiff > minPixelDiff )
506  {
507  //found the optimum major scale
508  magnitude = VALID_SCALE_MAGNITUDES[magnitudeCandidate];
509  multiple = VALID_SCALE_MULTIPLES[multipleCandidate];
510  return candidateScale;
511  }
512  }
513  }
514 
515  return 100000;
516 }
517 
518 int QgsLayoutRuler::optimumNumberDivisions( double rulerScale, int scaleMultiple )
519 {
520  //calculate size in pixels of each marked ruler unit
521  double largeDivisionSize = mTransform.map( QPointF( rulerScale, 0 ) ).x() - mTransform.map( QPointF( 0, 0 ) ).x();
522 
523  //now calculate optimum small tick scale, depending on marked ruler units
524  QList<int> validSmallDivisions;
525  switch ( scaleMultiple )
526  {
527  case 1:
528  //numbers increase by 1 increment each time, e.g., 1, 2, 3 or 10, 20, 30
529  //so we can draw either 10, 5 or 2 small ticks and have each fall on a nice value
530  validSmallDivisions << 10 << 5 << 2;
531  break;
532  case 2:
533  //numbers increase by 2 increments each time, e.g., 2, 4, 6 or 20, 40, 60
534  //so we can draw either 10, 4 or 2 small ticks and have each fall on a nice value
535  validSmallDivisions << 10 << 4 << 2;
536  break;
537  case 5:
538  //numbers increase by 5 increments each time, e.g., 5, 10, 15 or 100, 500, 1000
539  //so we can draw either 10 or 5 small ticks and have each fall on a nice value
540  validSmallDivisions << 10 << 5;
541  break;
542  }
543 
544  //calculate the most number of small divisions we can draw without them being too close to each other
545  QList<int>::iterator divisions_it;
546  for ( divisions_it = validSmallDivisions.begin(); divisions_it != validSmallDivisions.end(); ++divisions_it )
547  {
548  //find pixel size for this small division
549  double candidateSize = largeDivisionSize / ( *divisions_it );
550  //check if this separation is more then allowed min separation
551  if ( candidateSize >= mMinPixelsPerDivision )
552  {
553  //found a good candidate, return it
554  return ( *divisions_it );
555  }
556  }
557 
558  //unable to find a good candidate
559  return 0;
560 }
561 
562 
563 void QgsLayoutRuler::setSceneTransform( const QTransform &transform )
564 {
565  mTransform = transform;
566  update();
567 }
568 
570 {
571  mView = view;
573 }
574 
576 {
577  mMenu = menu;
578 }
579 
580 void QgsLayoutRuler::setCursorPosition( QPointF position )
581 {
582  mMarkerPos = mView->mapFromScene( position );
583  update();
584 }
585 
586 void QgsLayoutRuler::mouseMoveEvent( QMouseEvent *event )
587 {
588  mMarkerPos = event->pos();
589  update();
590 
591  if ( !mView->currentLayout() )
592  return;
593 
594  QPointF displayPos;
595  if ( mCreatingGuide || mDraggingGuide )
596  {
597  // event -> layout coordinates
598  displayPos = convertLocalPointToLayout( event->pos() );
599 
600  if ( mCreatingGuide )
601  {
602  QgsLayout *layout = mView->currentLayout();
603  int pageNo = layout->pageCollection()->pageNumberForPoint( displayPos );
604  QgsLayoutItemPage *page = layout->pageCollection()->page( pageNo );
605  if ( !page )
606  return;
607 
608  QPen linePen = mGuideItem->pen();
609  // if guide preview is outside a page draw it a lot fainter, to indicate it's invalid
610  if ( !layout->pageCollection()->pageAtPoint( displayPos ) )
611  {
612  linePen.setColor( QColor( 255, 0, 0, 150 ) );
613  }
614  else
615  {
616  linePen.setColor( QColor( 255, 0, 0, 225 ) );
617  }
618  mGuideItem->setPen( linePen );
619  switch ( mOrientation )
620  {
621  case Qt::Horizontal:
622  {
623  //mouse is creating a horizontal ruler, so don't show x coordinate
624  mGuideItem->setLine( page->scenePos().x(), displayPos.y(), page->scenePos().x() + page->rect().width(), displayPos.y() );
625  displayPos.setX( 0 );
626  break;
627  }
628  case Qt::Vertical:
629  {
630  //mouse is creating a vertical ruler, so don't show a y coordinate
631  mGuideItem->setLine( displayPos.x(), page->scenePos().y(), displayPos.x(), page->scenePos().y() + page->rect().height() );
632  displayPos.setY( 0 );
633  break;
634  }
635  }
636  }
637  else
638  {
639  // dragging guide
640  switch ( mOrientation )
641  {
642  case Qt::Horizontal:
643  {
644  mView->currentLayout()->guides().setGuideLayoutPosition( mDraggingGuide, displayPos.x() );
645  displayPos.setY( 0 );
646  break;
647  }
648  case Qt::Vertical:
649  {
650  mView->currentLayout()->guides().setGuideLayoutPosition( mDraggingGuide, displayPos.y() );
651  displayPos.setX( 0 );
652  break;
653  }
654  }
655  }
656  }
657  else
658  {
659  // is cursor over a guide marker?
660  mHoverGuide = guideAtPoint( event->pos() );
661  if ( mHoverGuide )
662  {
663  setCursor( mOrientation == Qt::Vertical ? Qt::SplitVCursor : Qt::SplitHCursor );
664  }
665  else
666  {
667  setCursor( Qt::ArrowCursor );
668  }
669 
670  //update cursor position in status bar
671  displayPos = mTransform.inverted().map( event->pos() );
672  switch ( mOrientation )
673  {
674  case Qt::Horizontal:
675  {
676  //mouse is over a horizontal ruler, so don't show a y coordinate
677  displayPos.setY( 0 );
678  break;
679  }
680  case Qt::Vertical:
681  {
682  //mouse is over a vertical ruler, so don't show an x coordinate
683  displayPos.setX( 0 );
684  break;
685  }
686  }
687  }
688  emit cursorPosChanged( displayPos );
689 }
690 
691 void QgsLayoutRuler::mousePressEvent( QMouseEvent *event )
692 {
693  if ( !mView->currentLayout() )
694  return;
695 
696  if ( event->button() == Qt::LeftButton )
697  {
698  mDraggingGuide = guideAtPoint( event->pos() );
699  if ( !mDraggingGuide )
700  {
701  // if no guide at the point, then we're creating one
702  if ( mView->currentLayout()->pageCollection()->pageCount() > 0 )
703  {
704  mCreatingGuide = true;
705  createTemporaryGuideItem();
706  }
707  }
708  switch ( mOrientation )
709  {
710  case Qt::Horizontal:
711  {
712  QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitHCursor : Qt::SplitVCursor );
713  break;
714  }
715  case Qt::Vertical:
716  QApplication::setOverrideCursor( mDraggingGuide ? Qt::SplitVCursor : Qt::SplitHCursor );
717  break;
718  }
719  }
720 }
721 
722 void QgsLayoutRuler::mouseReleaseEvent( QMouseEvent *event )
723 {
724  if ( !mView->currentLayout() )
725  return;
726 
727  if ( event->button() == Qt::LeftButton )
728  {
729  if ( mDraggingGuide )
730  {
731  QApplication::restoreOverrideCursor();
732 
733  QPointF layoutPoint = convertLocalPointToLayout( event->pos() );
734 
735  // delete guide if it ends outside of page
736  QgsLayoutItemPage *page = mDraggingGuide->page();
737  bool deleteGuide = false;
738  switch ( mDraggingGuide->orientation() )
739  {
740  case Qt::Horizontal:
741  if ( layoutPoint.y() < page->scenePos().y() || layoutPoint.y() > page->scenePos().y() + page->rect().height() )
742  deleteGuide = true;
743  break;
744 
745  case Qt::Vertical:
746  if ( layoutPoint.x() < page->scenePos().x() || layoutPoint.x() > page->scenePos().x() + page->rect().width() )
747  deleteGuide = true;
748  break;
749  }
750 
751  if ( deleteGuide )
752  {
753  mView->currentLayout()->guides().removeGuide( mDraggingGuide );
754  }
755  mDraggingGuide = nullptr;
756  }
757  else
758  {
759  mCreatingGuide = false;
760  QApplication::restoreOverrideCursor();
761  delete mGuideItem;
762  mGuideItem = nullptr;
763 
764  // check that cursor left the ruler
765  switch ( mOrientation )
766  {
767  case Qt::Horizontal:
768  {
769  if ( event->pos().y() <= height() )
770  return;
771  break;
772  }
773  case Qt::Vertical:
774  {
775  if ( event->pos().x() <= width() )
776  return;
777  break;
778  }
779  }
780 
781  QgsLayout *layout = mView->currentLayout();
782 
783  // create guide
784  QPointF scenePos = convertLocalPointToLayout( event->pos() );
785  QgsLayoutItemPage *page = layout->pageCollection()->pageAtPoint( scenePos );
786  if ( !page )
787  return; // dragged outside of a page
788 
789  std::unique_ptr< QgsLayoutGuide > guide;
790  switch ( mOrientation )
791  {
792  case Qt::Horizontal:
793  {
794  //mouse is creating a horizontal guide
795  double posOnPage = layout->pageCollection()->positionOnPage( scenePos ).y();
796  guide.reset( new QgsLayoutGuide( Qt::Horizontal, QgsLayoutMeasurement( posOnPage, layout->units() ), page ) );
797  break;
798  }
799  case Qt::Vertical:
800  {
801  //mouse is creating a vertical guide
802  guide.reset( new QgsLayoutGuide( Qt::Vertical, QgsLayoutMeasurement( scenePos.x(), layout->units() ), page ) );
803  break;
804  }
805  }
806  mView->currentLayout()->guides().addGuide( guide.release() );
807  }
808  }
809  else if ( event->button() == Qt::RightButton )
810  {
811  if ( mMenu )
812  mMenu->popup( event->globalPos() );
813  }
814 }
QgsLayoutRuler::mouseMoveEvent
void mouseMoveEvent(QMouseEvent *event) override
Definition: qgslayoutruler.cpp:586
QgsLayoutRuler::paintEvent
void paintEvent(QPaintEvent *event) override
Definition: qgslayoutruler.cpp:84
QgsLayoutItemPage
Item representing the paper in a layout.
Definition: qgslayoutitempage.h:54
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
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:458
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:722
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
QgsLayoutRuler::setLayoutView
void setLayoutView(QgsLayoutView *view)
Sets the current layout view to synchronize the ruler with.
Definition: qgslayoutruler.cpp:569
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:580
QgsLayoutGuide
Contains the configuration for a single snap guide used by a layout.
Definition: qgslayoutguidecollection.h:42
qgslayout.h
QgsLayoutView::currentLayout
QgsLayout currentLayout
Definition: qgslayoutview.h:63
QgsLayout::ZGuide
@ ZGuide
Z-value for page guides.
Definition: qgslayout.h:61
QgsLayoutRuler::mousePressEvent
void mousePressEvent(QMouseEvent *event) override
Definition: qgslayoutruler.cpp:691
QgsLayout
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
COUNT_VALID_MULTIPLES
const unsigned int COUNT_VALID_MULTIPLES
Definition: qgslayoutruler.cpp:28
QgsLayoutView
Definition: qgslayoutview.h:49
qgslayoutpagecollection.h
QgsLayoutRuler::setContextMenu
void setContextMenu(QMenu *menu)
Sets a context menu to show when right clicking occurs on the ruler.
Definition: qgslayoutruler.cpp:575
COUNT_VALID_MAGNITUDES
const unsigned int COUNT_VALID_MAGNITUDES
Definition: qgslayoutruler.cpp:29
QgsLayout::guides
QgsLayoutGuideCollection & guides()
Returns a reference to the layout's guide collection, which manages page snap guides.
Definition: qgslayout.cpp:384
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:563
QgsLayoutMeasurement
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
Definition: qgslayoutmeasurement.h:33
RULER_FONT_SIZE
const int RULER_FONT_SIZE
Definition: qgslayoutruler.cpp:27