QGIS API Documentation 3.27.0-Master (f261cc1f8b)
qgslayoutguidecollection.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayoutguidecollection.cpp
3 ----------------------------
4 begin : July 2017
5 copyright : (C) 2017 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8/***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
18#include "qgslayout.h"
19#include "qgsproject.h"
20#include "qgsreadwritecontext.h"
22#include "qgslayoutundostack.h"
23#include <QGraphicsLineItem>
24
25
26//
27// QgsLayoutGuide
28//
29
30QgsLayoutGuide::QgsLayoutGuide( Qt::Orientation orientation, QgsLayoutMeasurement position, QgsLayoutItemPage *page )
31 : QObject( nullptr )
32 , mOrientation( orientation )
33 , mPosition( position )
34 , mPage( page )
35{}
36
38{
39 if ( mLayout && mLineItem )
40 {
41 mLayout->removeItem( mLineItem );
42 delete mLineItem;
43 }
44}
45
47{
48 return mPosition;
49}
50
52{
53 mPosition = position;
54 update();
55 emit positionChanged();
56}
57
59{
60 return mPage;
61}
62
64{
65 mPage = page;
66 update();
67}
68
70{
71 if ( !mLayout || !mLineItem )
72 return;
73
74 // first find matching page
75 if ( !mPage )
76 {
77 mLineItem->hide();
78 return;
79 }
80
81 double layoutPos = mLayout->convertToLayoutUnits( mPosition );
82 bool showGuide = mLayout->guides().visible();
83 switch ( mOrientation )
84 {
85 case Qt::Horizontal:
86 if ( layoutPos > mPage->rect().height() )
87 {
88 mLineItem->hide();
89 }
90 else
91 {
92 mLineItem->setLine( 0, layoutPos + mPage->y(), mPage->rect().width(), layoutPos + mPage->y() );
93 mLineItem->setVisible( showGuide );
94 }
95
96 break;
97
98 case Qt::Vertical:
99 if ( layoutPos > mPage->rect().width() )
100 {
101 mLineItem->hide();
102 }
103 else
104 {
105 mLineItem->setLine( layoutPos, mPage->y(), layoutPos, mPage->y() + mPage->rect().height() );
106 mLineItem->setVisible( showGuide );
107 }
108
109 break;
110 }
111}
112
113QGraphicsLineItem *QgsLayoutGuide::item()
114{
115 return mLineItem;
116}
117
119{
120 if ( !mLineItem )
121 return -999;
122
123 switch ( mOrientation )
124 {
125 case Qt::Horizontal:
126 return mLineItem->mapToScene( mLineItem->line().p1() ).y();
127
128 case Qt::Vertical:
129 return mLineItem->mapToScene( mLineItem->line().p1() ).x();
130 }
131 return -999; // avoid warning
132}
133
135{
136 if ( !mLayout )
137 return;
138
139 double p = 0;
140 switch ( mOrientation )
141 {
142 case Qt::Horizontal:
143 p = mPage->mapFromScene( QPointF( 0, position ) ).y();
144 break;
145
146 case Qt::Vertical:
147 p = mPage->mapFromScene( QPointF( position, 0 ) ).x();
148 break;
149 }
150 mPosition = mLayout->convertFromLayoutUnits( p, mPosition.units() );
151 update();
152 emit positionChanged();
153}
154
156{
157 return mLayout;
158}
159
161{
162 mLayout = layout;
163
164 if ( !mLineItem )
165 {
166 mLineItem = new QGraphicsLineItem();
167 mLineItem->hide();
168 mLineItem->setZValue( QgsLayout::ZGuide );
169 QPen linePen( Qt::DotLine );
170 linePen.setColor( Qt::red );
171 // use a pen width of 0, since this activates a cosmetic pen
172 // which doesn't scale with the layout and keeps a constant size
173 linePen.setWidthF( 0 );
174 mLineItem->setPen( linePen );
175 }
176
177 mLayout->addItem( mLineItem );
178 update();
179}
180
181Qt::Orientation QgsLayoutGuide::orientation() const
182{
183 return mOrientation;
184}
185
186
187
188//
189// QgsLayoutGuideCollection
190//
191
193 : QAbstractTableModel( layout )
194 , mLayout( layout )
195 , mPageCollection( pageCollection )
196{
197 QFont f;
198 mHeaderSize = QFontMetrics( f ).boundingRect( QStringLiteral( "XX" ) ).width();
199
200 connect( mPageCollection, &QgsLayoutPageCollection::pageAboutToBeRemoved, this, &QgsLayoutGuideCollection::pageAboutToBeRemoved );
201}
202
204{
205 qDeleteAll( mGuides );
206}
207
209{
210 return mLayout;
211}
212
213int QgsLayoutGuideCollection::rowCount( const QModelIndex & ) const
214{
215 return mGuides.count();
216}
217
218int QgsLayoutGuideCollection::columnCount( const QModelIndex &parent ) const
219{
220 if ( parent.isValid() )
221 return 0;
222
223 return 2;
224}
225
226QVariant QgsLayoutGuideCollection::data( const QModelIndex &index, int role ) const
227{
228 if ( !index.isValid() )
229 return QVariant();
230
231 if ( index.row() >= mGuides.count() || index.row() < 0 )
232 return QVariant();
233
234 QgsLayoutGuide *guide = mGuides.at( index.row() );
235 switch ( role )
236 {
237 case Qt::DisplayRole:
238 case Qt::EditRole:
239 {
240 if ( index.column() == 0 )
241 return guide->position().length();
242 else
244 }
245
246 case OrientationRole:
247 return guide->orientation();
248
249 case PositionRole:
250 return guide->position().length();
251
252 case UnitsRole:
253 return guide->position().units();
254
255 case PageRole:
256 return mPageCollection->pageNumber( guide->page() );
257
259 return guide->layoutPosition();
260
261 default:
262 return QVariant();
263 }
264}
265
266bool QgsLayoutGuideCollection::setData( const QModelIndex &index, const QVariant &value, int role )
267{
268 if ( !index.isValid() )
269 return false;
270
271 if ( index.row() >= mGuides.count() || index.row() < 0 )
272 return false;
273
274 QgsLayoutGuide *guide = mGuides.at( index.row() );
275
276 switch ( role )
277 {
278 case Qt::EditRole:
279 {
280 bool ok = false;
281 double newPos = value.toDouble( &ok );
282 if ( !ok )
283 return false;
284
285 QgsLayoutMeasurement m = guide->position();
286 m.setLength( newPos );
287 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Move Guide" ), Move + index.row() );
288 whileBlocking( guide )->setPosition( m );
289 guide->update();
290 mLayout->undoStack()->endCommand();
291 emit dataChanged( index, index, QVector<int>() << role );
292 return true;
293 }
294 case PositionRole:
295 {
296 bool ok = false;
297 double newPos = value.toDouble( &ok );
298 if ( !ok )
299 return false;
300
301 QgsLayoutMeasurement m = guide->position();
302 if ( qgsDoubleNear( m.length(), newPos ) )
303 return true;
304
305 m.setLength( newPos );
306 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Move Guide" ), Move + index.row() );
307 whileBlocking( guide )->setPosition( m );
308 guide->update();
309 mLayout->undoStack()->endCommand();
310 emit dataChanged( index, index, QVector<int>() << role );
311 return true;
312 }
313
315 {
316 bool ok = false;
317 double newPos = value.toDouble( &ok );
318 if ( !ok )
319 return false;
320
321 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Move Guide" ), Move + index.row() );
322 whileBlocking( guide )->setLayoutPosition( newPos );
323 mLayout->undoStack()->endCommand();
324 emit dataChanged( index, index, QVector<int>() << role );
325 return true;
326 }
327
328 case UnitsRole:
329 {
330 bool ok = false;
331 int units = value.toInt( &ok );
332 if ( !ok )
333 return false;
334
335 QgsLayoutMeasurement m = guide->position();
336 m.setUnits( static_cast< QgsUnitTypes::LayoutUnit >( units ) );
337 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Move Guide" ), Move + index.row() );
338 whileBlocking( guide )->setPosition( m );
339 guide->update();
340 mLayout->undoStack()->endCommand();
341 emit dataChanged( index, index, QVector<int>() << role );
342 return true;
343 }
344 }
345
346 return false;
347}
348
349Qt::ItemFlags QgsLayoutGuideCollection::flags( const QModelIndex &index ) const
350{
351 if ( !index.isValid() )
352 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
353 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
354}
355
356QVariant QgsLayoutGuideCollection::headerData( int section, Qt::Orientation orientation, int role ) const
357{
358 if ( role == Qt::DisplayRole )
359 return QVariant();
360 else if ( role == Qt::SizeHintRole )
361 {
362 return QSize( mHeaderSize, mHeaderSize );
363 }
364 return QAbstractTableModel::headerData( section, orientation, role );
365}
366
367bool QgsLayoutGuideCollection::removeRows( int row, int count, const QModelIndex &parent )
368{
369 if ( parent.isValid() )
370 return false;
371
372 if ( !mBlockUndoCommands )
373 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Remove Guide(s)" ), Remove + row );
374 beginRemoveRows( parent, row, row + count - 1 );
375 for ( int i = 0; i < count; ++ i )
376 {
377 delete mGuides.takeAt( row );
378 }
379 endRemoveRows();
380 if ( !mBlockUndoCommands )
381 mLayout->undoStack()->endCommand();
382 return true;
383}
384
386{
387 if ( guide->layout() != mLayout )
388 guide->setLayout( mLayout );
389
390 if ( !mBlockUndoCommands )
391 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Create Guide" ) );
392 beginInsertRows( QModelIndex(), mGuides.count(), mGuides.count() );
393 mGuides.append( guide );
394 endInsertRows();
395 if ( !mBlockUndoCommands )
396 mLayout->undoStack()->endCommand();
397
398 QModelIndex index = createIndex( mGuides.length() - 1, 0 );
399 connect( guide, &QgsLayoutGuide::positionChanged, this, [ this, index ]
400 {
401 emit dataChanged( index, index );
402 } );
403}
404
406{
407 int row = mGuides.indexOf( guide );
408 if ( row < 0 )
409 return;
410
411 removeRow( row );
412}
413
415{
416 int row = mGuides.indexOf( guide );
417 if ( row < 0 )
418 return;
419
420 setData( index( row, 0 ), position, LayoutPositionRole );
421}
422
424{
425 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Clear Guides" ) );
426 beginResetModel();
427 qDeleteAll( mGuides );
428 mGuides.clear();
429 endResetModel();
430 mLayout->undoStack()->endCommand();
431}
432
434{
435 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Apply Guides" ) );
436 mBlockUndoCommands = true;
437 QgsLayoutItemPage *page = mPageCollection->page( sourcePage );
438 // remove other page's guides
439 const auto constMGuides = mGuides;
440 for ( QgsLayoutGuide *guide : constMGuides )
441 {
442 if ( guide->page() != page )
443 removeGuide( guide );
444 }
445
446 // remaining guides belong to source page - clone them to other pages
447 for ( QgsLayoutGuide *guide : std::as_const( mGuides ) )
448 {
449 for ( int p = 0; p < mPageCollection->pageCount(); ++p )
450 {
451 if ( p == sourcePage )
452 continue;
453
454 std::unique_ptr< QgsLayoutGuide> newGuide( new QgsLayoutGuide( guide->orientation(), guide->position(), mPageCollection->page( p ) ) );
455 newGuide->setLayout( mLayout );
456 if ( newGuide->item()->isVisible() )
457 {
458 // if invisible, new guide is outside of page bounds
459 addGuide( newGuide.release() );
460 }
461 }
462 }
463 mLayout->undoStack()->endCommand();
464 mBlockUndoCommands = false;
465}
466
468{
469 const auto constMGuides = mGuides;
470 for ( QgsLayoutGuide *guide : constMGuides )
471 {
472 guide->update();
473 }
474}
475
476QList<QgsLayoutGuide *> QgsLayoutGuideCollection::guides()
477{
478 return mGuides;
479}
480
481QList<QgsLayoutGuide *> QgsLayoutGuideCollection::guides( Qt::Orientation orientation, int page )
482{
483 QList<QgsLayoutGuide *> res;
484 const auto constMGuides = mGuides;
485 for ( QgsLayoutGuide *guide : constMGuides )
486 {
487 if ( guide->orientation() == orientation && guide->item()->isVisible() &&
488 ( page < 0 || mPageCollection->page( page ) == guide->page() ) )
489 res << guide;
490 }
491 return res;
492}
493
494QList<QgsLayoutGuide *> QgsLayoutGuideCollection::guidesOnPage( int page )
495{
496 QList<QgsLayoutGuide *> res;
497 const auto constMGuides = mGuides;
498 for ( QgsLayoutGuide *guide : constMGuides )
499 {
500 if ( mPageCollection->page( page ) == guide->page() )
501 res << guide;
502 }
503 return res;
504}
505
507{
508 return mGuidesVisible;
509}
510
512{
513 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Change Guide Visibility" ) );
514 mGuidesVisible = visible;
515 mLayout->undoStack()->endCommand();
516 update();
517}
518
519void QgsLayoutGuideCollection::pageAboutToBeRemoved( int pageNumber )
520{
521 mBlockUndoCommands = true;
522 const auto constGuidesOnPage = guidesOnPage( pageNumber );
523 for ( QgsLayoutGuide *guide : constGuidesOnPage )
524 {
525 removeGuide( guide );
526 }
527 mBlockUndoCommands = false;
528}
529
530bool QgsLayoutGuideCollection::writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext & ) const
531{
532 QDomElement element = document.createElement( QStringLiteral( "GuideCollection" ) );
533 element.setAttribute( QStringLiteral( "visible" ), mGuidesVisible );
534 const auto constMGuides = mGuides;
535 for ( QgsLayoutGuide *guide : constMGuides )
536 {
537 QDomElement guideElement = document.createElement( QStringLiteral( "Guide" ) );
538 guideElement.setAttribute( QStringLiteral( "orientation" ), guide->orientation() );
539 guideElement.setAttribute( QStringLiteral( "page" ), mPageCollection->pageNumber( guide->page() ) );
540 guideElement.setAttribute( QStringLiteral( "position" ), guide->position().length() );
541 guideElement.setAttribute( QStringLiteral( "units" ), QgsUnitTypes::encodeUnit( guide->position().units() ) );
542 element.appendChild( guideElement );
543 }
544
545 parentElement.appendChild( element );
546 return true;
547}
548
549bool QgsLayoutGuideCollection::readXml( const QDomElement &e, const QDomDocument &, const QgsReadWriteContext & )
550{
551 QDomElement element = e;
552 if ( element.nodeName() != QLatin1String( "GuideCollection" ) )
553 {
554 element = element.firstChildElement( QStringLiteral( "GuideCollection" ) );
555 }
556
557 if ( element.nodeName() != QLatin1String( "GuideCollection" ) )
558 {
559 return false;
560 }
561
562 mBlockUndoCommands = true;
563 beginResetModel();
564 qDeleteAll( mGuides );
565 mGuides.clear();
566
567 mGuidesVisible = element.attribute( QStringLiteral( "visible" ), QStringLiteral( "0" ) ) != QLatin1String( "0" );
568 QDomNodeList guideNodeList = element.elementsByTagName( QStringLiteral( "Guide" ) );
569 for ( int i = 0; i < guideNodeList.size(); ++i )
570 {
571 QDomElement element = guideNodeList.at( i ).toElement();
572 Qt::Orientation orientation = static_cast< Qt::Orientation >( element.attribute( QStringLiteral( "orientation" ), QStringLiteral( "1" ) ).toInt() );
573 double pos = element.attribute( QStringLiteral( "position" ), QStringLiteral( "0" ) ).toDouble();
574 QgsUnitTypes::LayoutUnit unit = QgsUnitTypes::decodeLayoutUnit( element.attribute( QStringLiteral( "units" ) ) );
575 int page = element.attribute( QStringLiteral( "page" ), QStringLiteral( "0" ) ).toInt();
576 std::unique_ptr< QgsLayoutGuide > guide( new QgsLayoutGuide( orientation, QgsLayoutMeasurement( pos, unit ), mPageCollection->page( page ) ) );
577 guide->update();
578 addGuide( guide.release() );
579 }
580
581 endResetModel();
582 mBlockUndoCommands = false;
583 return true;
584}
585
586//
587// QgsLayoutGuideProxyModel
588//
589
590QgsLayoutGuideProxyModel::QgsLayoutGuideProxyModel( QObject *parent, Qt::Orientation orientation, int page )
591 : QSortFilterProxyModel( parent )
592 , mOrientation( orientation )
593 , mPage( page )
594{
595 setDynamicSortFilter( true );
596 sort( 0 );
597}
598
600{
601 mPage = page;
602 invalidateFilter();
603}
604
605bool QgsLayoutGuideProxyModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
606{
607 QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
608 Qt::Orientation orientation = static_cast< Qt::Orientation>( sourceModel()->data( index, QgsLayoutGuideCollection::OrientationRole ).toInt() );
609 if ( orientation != mOrientation )
610 return false;
611
612 int page = sourceModel()->data( index, QgsLayoutGuideCollection::PageRole ).toInt();
613 return page == mPage;
614}
615
616bool QgsLayoutGuideProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
617{
618 double leftPos = sourceModel()->data( left, QgsLayoutGuideCollection::LayoutPositionRole ).toDouble();
619 double rightPos = sourceModel()->data( right, QgsLayoutGuideCollection::LayoutPositionRole ).toDouble();
620 return leftPos < rightPos;
621}
bool writeXml(QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores the collection's state in a DOM element.
int columnCount(const QModelIndex &) const override
void addGuide(QgsLayoutGuide *guide)
Adds a guide to the collection.
bool setData(const QModelIndex &index, const QVariant &value, int role) override
QgsLayout * layout() override
Returns the layout the object belongs to.
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
QgsLayoutGuideCollection(QgsLayout *layout, QgsLayoutPageCollection *pageCollection)
Constructor for QgsLayoutGuideCollection belonging to the specified layout, and linked to the specifi...
bool readXml(const QDomElement &collectionElement, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets the collection's state from a DOM element.
QVariant data(const QModelIndex &index, int role) const override
void applyGuidesToAllOtherPages(int sourcePage)
Resets all other pages' guides to match the guides from the specified sourcePage.
void removeGuide(QgsLayoutGuide *guide)
Removes the specified guide, and deletes it.
QList< QgsLayoutGuide * > guidesOnPage(int page)
Returns the list of guides contained on a matching page.
bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex()) override
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.
int rowCount(const QModelIndex &) const override
void clear()
Removes all guides from the collection.
Qt::ItemFlags flags(const QModelIndex &index) const override
bool visible() const
Returns true if the guide lines should be drawn.
@ OrientationRole
Guide orientation role.
@ PositionRole
Guide position role.
@ LayoutPositionRole
Guide position in layout coordinates.
@ UnitsRole
Guide position units role.
void update()
Updates the position (and visibility) of all guide line items.
void setVisible(bool visible)
Sets whether the guide lines should be visible.
void setPage(int page)
Sets the current page for filtering matching guides.
QgsLayoutGuideProxyModel(QObject *parent, Qt::Orientation orientation, int page)
Constructor for QgsLayoutGuideProxyModel, filtered to guides of the specified orientation and page on...
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
Contains the configuration for a single snap guide used by a layout.
QgsLayoutMeasurement position() const
Returns the guide's position within the page.
QgsLayoutItemPage * page()
Returns the page the guide is contained within.
Qt::Orientation orientation() const
Returns the guide's orientation.
QgsLayout * layout() const
Returns the layout the guide belongs to.
void setLayout(QgsLayout *layout)
Sets the layout the guide belongs to.
void setLayoutPosition(double position)
Sets the guide's position in absolute layout units.
void setPage(QgsLayoutItemPage *page)
Sets the page the guide is contained within.
void setPosition(QgsLayoutMeasurement position)
Sets the guide's position within the page.
QgsLayoutGuide(Qt::Orientation orientation, QgsLayoutMeasurement position, QgsLayoutItemPage *page)
Constructor for a new guide with the specified orientation and initial position.
double layoutPosition() const
Returns the guide's position in absolute layout units.
void positionChanged()
Emitted when the guide's position is changed.
void update()
Updates the position of the guide's line item.
QGraphicsLineItem * item()
Returns the guide's line item.
Item representing the paper in a layout.
int page() const
Returns the page the item is currently on, with the first page returning 0.
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
void setLength(const double length)
Sets the length of the measurement.
double length() const
Returns the length of the measurement.
QgsUnitTypes::LayoutUnit units() const
Returns the units for the measurement.
void setUnits(const QgsUnitTypes::LayoutUnit units)
Sets the units for the measurement.
A manager for a collection of pages in a layout.
void pageAboutToBeRemoved(int pageNumber)
Emitted just before a page is removed from the collection.
int pageCount() const
Returns the number of pages in the collection.
QgsLayoutItemPage * page(int pageNumber)
Returns a specific page (by pageNumber) from the collection.
int pageNumber(QgsLayoutItemPage *page) const
Returns the page number for the specified page, or -1 if the page is not contained in the collection.
void endCommand()
Saves final state of an object and pushes the active command to the undo history.
void beginCommand(QgsLayoutUndoObjectInterface *object, const QString &commandText, int id=0)
Begins a new undo command for the specified object.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:51
@ ZGuide
Z-value for page guides.
Definition: qgslayout.h:62
QgsLayoutUndoStack * undoStack()
Returns a pointer to the layout's undo stack, which manages undo/redo states for the layout and it's ...
Definition: qgslayout.cpp:686
The class is used as a container of context for various read/write operations on other objects.
LayoutUnit
Layout measurement units.
Definition: qgsunittypes.h:182
static Q_INVOKABLE QString encodeUnit(QgsUnitTypes::DistanceUnit unit)
Encodes a distance unit to a string.
static Q_INVOKABLE QString toAbbreviatedString(QgsUnitTypes::DistanceUnit unit)
Returns a translated abbreviation representing a distance unit.
static Q_INVOKABLE QgsUnitTypes::LayoutUnit decodeLayoutUnit(const QString &string, bool *ok=nullptr)
Decodes a layout unit from a string.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2396
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:2322