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