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