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