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