QGIS API Documentation 4.3.0-Master (eb72899e5dc)
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#include "qgspainting.h"
26
27#include <QDragEnterEvent>
28#include <QGraphicsLineItem>
29#include <QMenu>
30#include <QPainter>
31#include <QString>
32
33#include "moc_qgslayoutruler.cpp"
34
35using namespace Qt::StringLiterals;
36
37const int RULER_FONT_SIZE = 8;
38const unsigned int COUNT_VALID_MULTIPLES = 3;
39const unsigned int COUNT_VALID_MAGNITUDES = 5;
40const int QgsLayoutRuler::VALID_SCALE_MULTIPLES[] = { 1, 2, 5 };
41const int QgsLayoutRuler::VALID_SCALE_MAGNITUDES[] = { 1, 10, 100, 1000, 10000 };
42
43QgsLayoutRuler::QgsLayoutRuler( QWidget *parent, Qt::Orientation orientation )
44 : QWidget( parent )
45 , mOrientation( orientation )
46{
47 setMouseTracking( true );
48
49 //calculate minimum size required for ruler text
50 mRulerFont.setPointSize( RULER_FONT_SIZE );
51 mRulerFontMetrics = std::make_unique<QFontMetrics>( mRulerFont );
52
53 //calculate ruler sizes and marker separations
54
55 //minimum gap required between major ticks is 3 digits * 250%, based on appearance
56 mScaleMinPixelsWidth = mRulerFontMetrics->boundingRect( u"000"_s ).width() * 2.5;
57 //minimum ruler height is twice the font height in pixels
58 mRulerMinSize = mRulerFontMetrics->height() * 1.5;
59
60 mMinPixelsPerDivision = mRulerMinSize / 4;
61 //each small division must be at least 2 pixels apart
62 if ( mMinPixelsPerDivision < 2 )
63 mMinPixelsPerDivision = 2;
64
65 mPixelsBetweenLineAndText = mRulerMinSize / 10;
66 mTextBaseline = mRulerMinSize / 1.667;
67 mMinSpacingVerticalLabels = mRulerMinSize / 5;
68
69 const double guideMarkerSize = mRulerFontMetrics->horizontalAdvance( '*' );
70 mDragGuideTolerance = guideMarkerSize;
71 switch ( mOrientation )
72 {
73 case Qt::Horizontal:
74 mGuideMarker << QPoint( -guideMarkerSize / 2, mRulerMinSize - guideMarkerSize ) << QPoint( 0, mRulerMinSize ) << QPoint( guideMarkerSize / 2, mRulerMinSize - guideMarkerSize );
75 break;
76
77 case Qt::Vertical:
78 mGuideMarker << QPoint( mRulerMinSize - guideMarkerSize, -guideMarkerSize / 2 ) << QPoint( mRulerMinSize, 0 ) << QPoint( mRulerMinSize - guideMarkerSize, guideMarkerSize / 2 );
79 break;
80 }
81}
82
84{
85 return QSize( mRulerMinSize, mRulerMinSize );
86}
87
88void QgsLayoutRuler::paintEvent( QPaintEvent *event )
89{
90 Q_UNUSED( event )
91 if ( !mView || !mView->currentLayout() )
92 {
93 return;
94 }
95
96 QgsLayout *layout = mView->currentLayout();
97 QPainter p( this );
98
99 drawGuideMarkers( &p, layout );
100
101 const QTransform t = mTransform.inverted();
102 p.setFont( mRulerFont );
103 // keep same default color, but lower opacity a tad
104 QBrush brush = p.brush();
105 QColor color = brush.color();
106 color.setAlphaF( 0.7 );
107 brush.setColor( color );
108 p.setBrush( brush );
109 QPen pen = p.pen();
110 color = pen.color();
111 color.setAlphaF( 0.7 );
112 pen.setColor( color );
113 p.setPen( pen );
114
115 //find optimum scale for ruler (size of numbered divisions)
116 int magnitude = 1;
117 int multiple = 1;
118 const int mmDisplay = optimumScale( mScaleMinPixelsWidth, magnitude, multiple );
119
120 //find optimum number of small divisions
121 const int numSmallDivisions = optimumNumberDivisions( mmDisplay, multiple );
122
123 switch ( mOrientation )
124 {
125 case Qt::Horizontal:
126 {
127 if ( qgsDoubleNear( width(), 0 ) )
128 {
129 return;
130 }
131
132 //start x-coordinate
133 const double startX = t.map( QPointF( 0, 0 ) ).x();
134 const double endX = t.map( QPointF( width(), 0 ) ).x();
135
136 //start marker position in mm
137 double markerPos = ( std::floor( startX / mmDisplay ) + 1 ) * mmDisplay;
138
139 //draw minor ticks marks which occur before first major tick
140 drawSmallDivisions( &p, markerPos, numSmallDivisions, -mmDisplay );
141
142 while ( markerPos <= endX )
143 {
144 const double pixelCoord = mTransform.map( QPointF( markerPos, 0 ) ).x();
145
146 //draw large division and text
147 p.drawLine( pixelCoord, 0, pixelCoord, mRulerMinSize );
148 p.drawText( QPointF( pixelCoord + mPixelsBetweenLineAndText, mTextBaseline ), QLocale().toString( markerPos ) );
149
150 //draw small divisions
151 drawSmallDivisions( &p, markerPos, numSmallDivisions, mmDisplay, endX );
152
153 markerPos += mmDisplay;
154 }
155 break;
156 }
157 case Qt::Vertical:
158 {
159 if ( qgsDoubleNear( height(), 0 ) )
160 {
161 return;
162 }
163
164 const double startY = t.map( QPointF( 0, 0 ) ).y(); //start position in mm (total including space between pages)
165 const double endY = t.map( QPointF( 0, height() ) ).y(); //stop position in mm (total including space between pages)
166
167 // work out start page
168 int startPage = 0;
169 int endPage = 0;
170 double currentY = 0;
171 double currentPageY = 0;
172 for ( int page = 0; page < layout->pageCollection()->pageCount(); ++page )
173 {
174 if ( currentY < startY )
175 {
176 startPage = page;
177 currentPageY = currentY;
178 }
179 endPage = page;
180
181 currentY += layout->pageCollection()->page( startPage )->rect().height() + layout->pageCollection()->spaceBetweenPages();
182 if ( currentY > endY )
183 break;
184 }
185
186 if ( startY < 0 )
187 {
188 double beforePageCoord = -mmDisplay;
189 const double firstPageY = mTransform.map( QPointF( 0, 0 ) ).y();
190
191 //draw negative rulers which fall before first page
192 while ( beforePageCoord > startY )
193 {
194 const double pixelCoord = mTransform.map( QPointF( 0, beforePageCoord ) ).y();
195 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
196 //calc size of label
197 const QString label = QLocale().toString( beforePageCoord );
198 const int labelSize = mRulerFontMetrics->boundingRect( label ).width();
199
200 //draw label only if it fits in before start of next page
201 if ( pixelCoord + labelSize + 8 < firstPageY )
202 {
203 drawRotatedText( &p, QPointF( mTextBaseline, pixelCoord + mMinSpacingVerticalLabels + labelSize ), label );
204 }
205
206 //draw small divisions
207 drawSmallDivisions( &p, beforePageCoord, numSmallDivisions, mmDisplay );
208
209 beforePageCoord -= mmDisplay;
210 }
211
212 //draw minor ticks marks which occur before first major tick
213 drawSmallDivisions( &p, beforePageCoord + mmDisplay, numSmallDivisions, -mmDisplay, startY );
214 }
215
216 double nextPageStartPos = 0;
217 int nextPageStartPixel = 0;
218
219 for ( int i = startPage; i <= endPage; ++i )
220 {
221 double pageCoord = 0; //page coordinate in mm
222 //total (composition) coordinate in mm, including space between pages
223
224 double totalCoord = currentPageY;
225
226 //position of next page
227 if ( i < endPage )
228 {
229 //not the last page
230 nextPageStartPos = currentPageY + layout->pageCollection()->page( i )->rect().height() + layout->pageCollection()->spaceBetweenPages();
231 nextPageStartPixel = mTransform.map( QPointF( 0, nextPageStartPos ) ).y();
232 }
233 else
234 {
235 //is the last page
236 nextPageStartPos = 0;
237 nextPageStartPixel = 0;
238 }
239
240 while ( ( totalCoord < nextPageStartPos ) || ( ( nextPageStartPos == 0 ) && ( totalCoord <= endY ) ) )
241 {
242 const double pixelCoord = mTransform.map( QPointF( 0, totalCoord ) ).y();
243 p.drawLine( 0, pixelCoord, mRulerMinSize, pixelCoord );
244 //calc size of label
245 const QString label = QLocale().toString( pageCoord );
246 const int labelSize = mRulerFontMetrics->boundingRect( label ).width();
247
248 //draw label only if it fits in before start of next page
249 if ( ( pixelCoord + labelSize + 8 < nextPageStartPixel ) || ( 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:51
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:7725
const unsigned int COUNT_VALID_MULTIPLES
const unsigned int COUNT_VALID_MAGNITUDES
const int RULER_FONT_SIZE