QGIS API Documentation 3.99.0-Master (21b3aa880ba)
Loading...
Searching...
No Matches
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
19#include "qgslayout.h"
21#include "qgslayoutundostack.h"
22#include "qgsreadwritecontext.h"
23#include "qgsunittypes.h"
24
25#include <QGraphicsLineItem>
26
27#include "moc_qgslayoutguidecollection.cpp"
28
29//
30// QgsLayoutGuide
31//
32
34 : QObject( nullptr )
35 , mOrientation( orientation )
36 , mPosition( position )
37 , mPage( page )
38{}
39
41{
42 if ( mLayout && mLineItem )
43 {
44 mLayout->removeItem( mLineItem );
45 delete mLineItem;
46 }
47}
48
50{
51 return mPosition;
52}
53
60
62{
63 return mPage;
64}
65
67{
68 mPage = page;
69 update();
70}
71
73{
74 if ( !mLayout || !mLineItem )
75 return;
76
77 // first find matching page
78 if ( !mPage )
79 {
80 mLineItem->hide();
81 return;
82 }
83
84 double layoutPos = mLayout->convertToLayoutUnits( mPosition );
85 bool showGuide = mLayout->guides().visible();
86 switch ( mOrientation )
87 {
88 case Qt::Horizontal:
89 if ( layoutPos > mPage->rect().height() )
90 {
91 mLineItem->hide();
92 }
93 else
94 {
95 mLineItem->setLine( 0, layoutPos + mPage->y(), mPage->rect().width(), layoutPos + mPage->y() );
96 mLineItem->setVisible( showGuide );
97 }
98
99 break;
100
101 case Qt::Vertical:
102 if ( layoutPos > mPage->rect().width() )
103 {
104 mLineItem->hide();
105 }
106 else
107 {
108 mLineItem->setLine( layoutPos, mPage->y(), layoutPos, mPage->y() + mPage->rect().height() );
109 mLineItem->setVisible( showGuide );
110 }
111
112 break;
113 }
114}
115
116QGraphicsLineItem *QgsLayoutGuide::item()
117{
118 return mLineItem;
119}
120
122{
123 if ( !mLineItem )
124 return -999;
125
126 switch ( mOrientation )
127 {
128 case Qt::Horizontal:
129 return mLineItem->mapToScene( mLineItem->line().p1() ).y();
130
131 case Qt::Vertical:
132 return mLineItem->mapToScene( mLineItem->line().p1() ).x();
133 }
134 return -999; // avoid warning
135}
136
138{
139 if ( !mLayout )
140 return;
141
142 double p = 0;
143 switch ( mOrientation )
144 {
145 case Qt::Horizontal:
146 p = mPage->mapFromScene( QPointF( 0, position ) ).y();
147 break;
148
149 case Qt::Vertical:
150 p = mPage->mapFromScene( QPointF( position, 0 ) ).x();
151 break;
152 }
153 mPosition = mLayout->convertFromLayoutUnits( p, mPosition.units() );
154 update();
155 emit positionChanged();
156}
157
159{
160 return mLayout;
161}
162
164{
165 mLayout = layout;
166
167 if ( !mLineItem )
168 {
169 mLineItem = new QGraphicsLineItem();
170 mLineItem->hide();
171 mLineItem->setZValue( QgsLayout::ZGuide );
172 QPen linePen( Qt::DotLine );
173 linePen.setColor( Qt::red );
174 // use a pen width of 0, since this activates a cosmetic pen
175 // which doesn't scale with the layout and keeps a constant size
176 linePen.setWidthF( 0 );
177 mLineItem->setPen( linePen );
178 }
179
180 mLayout->addItem( mLineItem );
181 update();
182}
183
184Qt::Orientation QgsLayoutGuide::orientation() const
185{
186 return mOrientation;
187}
188
189
190
191//
192// QgsLayoutGuideCollection
193//
194
196 : QAbstractTableModel( layout )
197 , mLayout( layout )
198 , mPageCollection( pageCollection )
199{
200 QFont f;
201 mHeaderSize = QFontMetrics( f ).boundingRect( QStringLiteral( "XX" ) ).width();
202
203 connect( mPageCollection, &QgsLayoutPageCollection::pageAboutToBeRemoved, this, &QgsLayoutGuideCollection::pageAboutToBeRemoved );
204}
205
207{
208 qDeleteAll( mGuides );
209}
210
212{
213 return mLayout;
214}
215
216int QgsLayoutGuideCollection::rowCount( const QModelIndex & ) const
217{
218 return mGuides.count();
219}
220
221int QgsLayoutGuideCollection::columnCount( const QModelIndex &parent ) const
222{
223 if ( parent.isValid() )
224 return 0;
225
226 return 2;
227}
228
229QVariant QgsLayoutGuideCollection::data( const QModelIndex &index, int role ) const
230{
231 if ( !index.isValid() )
232 return QVariant();
233
234 if ( index.row() >= mGuides.count() || index.row() < 0 )
235 return QVariant();
236
237 QgsLayoutGuide *guide = mGuides.at( index.row() );
238 switch ( role )
239 {
240 case Qt::DisplayRole:
241 case Qt::EditRole:
242 {
243 if ( index.column() == 0 )
244 return guide->position().length();
245 else
247 }
248
249 case static_cast< int >( CustomRole::Orientation ):
250 return QVariant::fromValue( guide->orientation() );
251
252 case static_cast< int >( CustomRole::Position ):
253 return guide->position().length();
254
255 case static_cast< int >( CustomRole::Units ):
256 return static_cast< int >( guide->position().units() );
257
258 case static_cast< int >( CustomRole::Page ):
259 return mPageCollection->pageNumber( guide->page() );
260
261 case static_cast< int >( CustomRole::LayoutPosition ):
262 return guide->layoutPosition();
263
264 default:
265 return QVariant();
266 }
267}
268
269bool QgsLayoutGuideCollection::setData( const QModelIndex &index, const QVariant &value, int role )
270{
271 if ( !index.isValid() )
272 return false;
273
274 if ( index.row() >= mGuides.count() || index.row() < 0 )
275 return false;
276
277 QgsLayoutGuide *guide = mGuides.at( index.row() );
278
279 switch ( role )
280 {
281 case Qt::EditRole:
282 {
283 bool ok = false;
284 double newPos = value.toDouble( &ok );
285 if ( !ok )
286 return false;
287
288 QgsLayoutMeasurement m = guide->position();
289 m.setLength( newPos );
290 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Move Guide" ), Move + index.row() );
291 whileBlocking( guide )->setPosition( m );
292 guide->update();
293 mLayout->undoStack()->endCommand();
294 emit dataChanged( index, index, QVector<int>() << role );
295 return true;
296 }
297 case static_cast< int >( CustomRole::Position ):
298 {
299 bool ok = false;
300 double newPos = value.toDouble( &ok );
301 if ( !ok )
302 return false;
303
304 QgsLayoutMeasurement m = guide->position();
305 if ( qgsDoubleNear( m.length(), newPos ) )
306 return true;
307
308 m.setLength( newPos );
309 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Move Guide" ), Move + index.row() );
310 whileBlocking( guide )->setPosition( m );
311 guide->update();
312 mLayout->undoStack()->endCommand();
313 emit dataChanged( index, index, QVector<int>() << role );
314 return true;
315 }
316
317 case static_cast< int >( CustomRole::LayoutPosition ):
318 {
319 bool ok = false;
320 double newPos = value.toDouble( &ok );
321 if ( !ok )
322 return false;
323
324 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Move Guide" ), Move + index.row() );
325 whileBlocking( guide )->setLayoutPosition( newPos );
326 mLayout->undoStack()->endCommand();
327 emit dataChanged( index, index, QVector<int>() << role );
328 return true;
329 }
330
331 case static_cast< int >( CustomRole::Units ):
332 {
333 bool ok = false;
334 int units = value.toInt( &ok );
335 if ( !ok )
336 return false;
337
338 QgsLayoutMeasurement m = guide->position();
339 m.setUnits( static_cast< Qgis::LayoutUnit >( units ) );
340 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Move Guide" ), Move + index.row() );
341 whileBlocking( guide )->setPosition( m );
342 guide->update();
343 mLayout->undoStack()->endCommand();
344 emit dataChanged( index, index, QVector<int>() << role );
345 return true;
346 }
347
348 default:
349 break;
350 }
351
352 return false;
353}
354
355Qt::ItemFlags QgsLayoutGuideCollection::flags( const QModelIndex &index ) const
356{
357 if ( !index.isValid() )
358 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
359 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
360}
361
362QVariant QgsLayoutGuideCollection::headerData( int section, Qt::Orientation orientation, int role ) const
363{
364 if ( role == Qt::DisplayRole )
365 return QVariant();
366 else if ( role == Qt::SizeHintRole )
367 {
368 return QSize( mHeaderSize, mHeaderSize );
369 }
370 return QAbstractTableModel::headerData( section, orientation, role );
371}
372
373bool QgsLayoutGuideCollection::removeRows( int row, int count, const QModelIndex &parent )
374{
375 if ( parent.isValid() )
376 return false;
377
378 if ( !mBlockUndoCommands )
379 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Remove Guide(s)" ), Remove + row );
380 beginRemoveRows( parent, row, row + count - 1 );
381 for ( int i = 0; i < count; ++ i )
382 {
383 delete mGuides.takeAt( row );
384 }
385 endRemoveRows();
386 if ( !mBlockUndoCommands )
387 mLayout->undoStack()->endCommand();
388 return true;
389}
390
392{
393 if ( guide->layout() != mLayout )
394 guide->setLayout( mLayout );
395
396 if ( !mBlockUndoCommands )
397 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Create Guide" ) );
398 beginInsertRows( QModelIndex(), mGuides.count(), mGuides.count() );
399 mGuides.append( guide );
400 endInsertRows();
401 if ( !mBlockUndoCommands )
402 mLayout->undoStack()->endCommand();
403
404 QModelIndex index = createIndex( mGuides.length() - 1, 0 );
405 connect( guide, &QgsLayoutGuide::positionChanged, this, [ this, index ]
406 {
407 emit dataChanged( index, index );
408 } );
409}
410
412{
413 int row = mGuides.indexOf( guide );
414 if ( row < 0 )
415 return;
416
417 removeRow( row );
418}
419
421{
422 int row = mGuides.indexOf( guide );
423 if ( row < 0 )
424 return;
425
426 setData( index( row, 0 ), position, static_cast< int >( CustomRole::LayoutPosition ) );
427}
428
430{
431 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Clear Guides" ) );
432 beginResetModel();
433 qDeleteAll( mGuides );
434 mGuides.clear();
435 endResetModel();
436 mLayout->undoStack()->endCommand();
437}
438
440{
441 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Apply Guides" ) );
442 mBlockUndoCommands = true;
443 QgsLayoutItemPage *page = mPageCollection->page( sourcePage );
444 // remove other page's guides
445 const auto constMGuides = mGuides;
446 for ( QgsLayoutGuide *guide : constMGuides )
447 {
448 if ( guide->page() != page )
449 removeGuide( guide );
450 }
451
452 // remaining guides belong to source page - clone them to other pages
453 const auto constMGuidesNew = mGuides;
454 for ( QgsLayoutGuide *guide : constMGuidesNew )
455 {
456 for ( int p = 0; p < mPageCollection->pageCount(); ++p )
457 {
458 if ( p == sourcePage )
459 continue;
460
461 auto newGuide = std::make_unique<QgsLayoutGuide>( guide->orientation(), guide->position(), mPageCollection->page( p ) );
462 newGuide->setLayout( mLayout );
463 if ( newGuide->item()->isVisible() )
464 {
465 // if invisible, new guide is outside of page bounds
466 addGuide( newGuide.release() );
467 }
468 }
469 }
470 mLayout->undoStack()->endCommand();
471 mBlockUndoCommands = false;
472}
473
475{
476 const auto constMGuides = mGuides;
477 for ( QgsLayoutGuide *guide : constMGuides )
478 {
479 guide->update();
480 }
481}
482
483QList<QgsLayoutGuide *> QgsLayoutGuideCollection::guides()
484{
485 return mGuides;
486}
487
488QList<QgsLayoutGuide *> QgsLayoutGuideCollection::guides( Qt::Orientation orientation, int page )
489{
490 QList<QgsLayoutGuide *> res;
491 const auto constMGuides = mGuides;
492 for ( QgsLayoutGuide *guide : constMGuides )
493 {
494 if ( guide->orientation() == orientation && guide->item()->isVisible() &&
495 ( page < 0 || mPageCollection->page( page ) == guide->page() ) )
496 res << guide;
497 }
498 return res;
499}
500
501QList<QgsLayoutGuide *> QgsLayoutGuideCollection::guidesOnPage( int page )
502{
503 QList<QgsLayoutGuide *> res;
504 const auto constMGuides = mGuides;
505 for ( QgsLayoutGuide *guide : constMGuides )
506 {
507 if ( mPageCollection->page( page ) == guide->page() )
508 res << guide;
509 }
510 return res;
511}
512
514{
515 return mGuidesVisible;
516}
517
519{
520 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Change Guide Visibility" ) );
521 mGuidesVisible = visible;
522 mLayout->undoStack()->endCommand();
523 update();
524}
525
526void QgsLayoutGuideCollection::pageAboutToBeRemoved( int pageNumber )
527{
528 mBlockUndoCommands = true;
529 const auto constGuidesOnPage = guidesOnPage( pageNumber );
530 for ( QgsLayoutGuide *guide : constGuidesOnPage )
531 {
532 removeGuide( guide );
533 }
534 mBlockUndoCommands = false;
535}
536
537bool QgsLayoutGuideCollection::writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext & ) const
538{
539 QDomElement element = document.createElement( QStringLiteral( "GuideCollection" ) );
540 element.setAttribute( QStringLiteral( "visible" ), mGuidesVisible );
541 const auto constMGuides = mGuides;
542 for ( QgsLayoutGuide *guide : constMGuides )
543 {
544 QDomElement guideElement = document.createElement( QStringLiteral( "Guide" ) );
545 guideElement.setAttribute( QStringLiteral( "orientation" ), guide->orientation() );
546 guideElement.setAttribute( QStringLiteral( "page" ), mPageCollection->pageNumber( guide->page() ) );
547 guideElement.setAttribute( QStringLiteral( "position" ), guide->position().length() );
548 guideElement.setAttribute( QStringLiteral( "units" ), QgsUnitTypes::encodeUnit( guide->position().units() ) );
549 element.appendChild( guideElement );
550 }
551
552 parentElement.appendChild( element );
553 return true;
554}
555
556bool QgsLayoutGuideCollection::readXml( const QDomElement &e, const QDomDocument &, const QgsReadWriteContext & )
557{
558 QDomElement element = e;
559 if ( element.nodeName() != QLatin1String( "GuideCollection" ) )
560 {
561 element = element.firstChildElement( QStringLiteral( "GuideCollection" ) );
562 }
563
564 if ( element.nodeName() != QLatin1String( "GuideCollection" ) )
565 {
566 return false;
567 }
568
569 mBlockUndoCommands = true;
570 beginResetModel();
571 qDeleteAll( mGuides );
572 mGuides.clear();
573
574 mGuidesVisible = element.attribute( QStringLiteral( "visible" ), QStringLiteral( "0" ) ) != QLatin1String( "0" );
575 QDomNodeList guideNodeList = element.elementsByTagName( QStringLiteral( "Guide" ) );
576 for ( int i = 0; i < guideNodeList.size(); ++i )
577 {
578 QDomElement element = guideNodeList.at( i ).toElement();
579 Qt::Orientation orientation = static_cast< Qt::Orientation >( element.attribute( QStringLiteral( "orientation" ), QStringLiteral( "1" ) ).toInt() );
580 double pos = element.attribute( QStringLiteral( "position" ), QStringLiteral( "0" ) ).toDouble();
581 Qgis::LayoutUnit unit = QgsUnitTypes::decodeLayoutUnit( element.attribute( QStringLiteral( "units" ) ) );
582 int page = element.attribute( QStringLiteral( "page" ), QStringLiteral( "0" ) ).toInt();
583 auto guide = std::make_unique<QgsLayoutGuide>( orientation, QgsLayoutMeasurement( pos, unit ), mPageCollection->page( page ) );
584 guide->update();
585 addGuide( guide.release() );
586 }
587
588 endResetModel();
589 mBlockUndoCommands = false;
590 return true;
591}
592
593//
594// QgsLayoutGuideProxyModel
595//
596
597QgsLayoutGuideProxyModel::QgsLayoutGuideProxyModel( QObject *parent, Qt::Orientation orientation, int page )
598 : QSortFilterProxyModel( parent )
599 , mOrientation( orientation )
600 , mPage( page )
601{
602 setDynamicSortFilter( true );
603 sort( 0 );
604}
605
607{
608 mPage = page;
609 invalidateFilter();
610}
611
612bool QgsLayoutGuideProxyModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
613{
614 QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
615 const Qt::Orientation orientation = static_cast< Qt::Orientation>( sourceModel()->data( index, static_cast< int >( QgsLayoutGuideCollection::CustomRole::Orientation ) ).value< Qt::Orientation >() );
616 if ( orientation != mOrientation )
617 return false;
618
619 int page = sourceModel()->data( index, static_cast< int >( QgsLayoutGuideCollection::CustomRole::Page ) ).toInt();
620 return page == mPage;
621}
622
623bool QgsLayoutGuideProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
624{
625 double leftPos = sourceModel()->data( left, static_cast< int >( QgsLayoutGuideCollection::CustomRole::LayoutPosition ) ).toDouble();
626 double rightPos = sourceModel()->data( right, static_cast< int >( QgsLayoutGuideCollection::CustomRole::LayoutPosition ) ).toDouble();
627 return leftPos < rightPos;
628}
LayoutUnit
Layout measurement units.
Definition qgis.h:5203
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.
@ LayoutPosition
Guide position in layout coordinates.
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.
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.
Provides a method of storing measurements for use in QGIS layouts using a variety of different measur...
void setLength(const double length)
Sets the length of the measurement.
Qgis::LayoutUnit units() const
Returns the units for the measurement.
void setUnits(const Qgis::LayoutUnit units)
Sets the units for the measurement.
double length() const
Returns the length of 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.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition qgslayout.h:50
@ ZGuide
Z-value for page guides.
Definition qgslayout.h:61
A container for the context for various read/write operations on objects.
static Q_INVOKABLE QString toAbbreviatedString(Qgis::DistanceUnit unit)
Returns a translated abbreviation representing a distance unit.
static Q_INVOKABLE Qgis::LayoutUnit decodeLayoutUnit(const QString &string, bool *ok=nullptr)
Decodes a layout unit from a string.
static Q_INVOKABLE QString encodeUnit(Qgis::DistanceUnit unit)
Encodes a distance unit to 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:6607
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:6511