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