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