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