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