QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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// QgsLayoutGuideCollection
195//
196
198 : QAbstractTableModel( layout )
199 , mLayout( layout )
200 , mPageCollection( pageCollection )
201{
202 QFont f;
203 mHeaderSize = QFontMetrics( f ).boundingRect( u"XX"_s ).width();
204
205 connect( mPageCollection, &QgsLayoutPageCollection::pageAboutToBeRemoved, this, &QgsLayoutGuideCollection::pageAboutToBeRemoved );
206}
207
209{
210 qDeleteAll( mGuides );
211}
212
214{
215 return mLayout;
216}
217
218int QgsLayoutGuideCollection::rowCount( const QModelIndex & ) const
219{
220 return mGuides.count();
221}
222
223int QgsLayoutGuideCollection::columnCount( const QModelIndex &parent ) const
224{
225 if ( parent.isValid() )
226 return 0;
227
228 return 2;
229}
230
231QVariant QgsLayoutGuideCollection::data( const QModelIndex &index, int role ) const
232{
233 if ( !index.isValid() )
234 return QVariant();
235
236 if ( index.row() >= mGuides.count() || index.row() < 0 )
237 return QVariant();
238
239 QgsLayoutGuide *guide = mGuides.at( index.row() );
240 switch ( role )
241 {
242 case Qt::DisplayRole:
243 case Qt::EditRole:
244 {
245 if ( index.column() == 0 )
246 return guide->position().length();
247 else
249 }
250
251 case static_cast< int >( CustomRole::Orientation ):
252 return QVariant::fromValue( guide->orientation() );
253
254 case static_cast< int >( CustomRole::Position ):
255 return guide->position().length();
256
257 case static_cast< int >( CustomRole::Units ):
258 return static_cast< int >( guide->position().units() );
259
260 case static_cast< int >( CustomRole::Page ):
261 return mPageCollection->pageNumber( guide->page() );
262
263 case static_cast< int >( CustomRole::LayoutPosition ):
264 return guide->layoutPosition();
265
266 default:
267 return QVariant();
268 }
269}
270
271bool QgsLayoutGuideCollection::setData( const QModelIndex &index, const QVariant &value, int role )
272{
273 if ( !index.isValid() )
274 return false;
275
276 if ( index.row() >= mGuides.count() || index.row() < 0 )
277 return false;
278
279 QgsLayoutGuide *guide = mGuides.at( index.row() );
280
281 switch ( role )
282 {
283 case Qt::EditRole:
284 {
285 bool ok = false;
286 double newPos = value.toDouble( &ok );
287 if ( !ok )
288 return false;
289
290 QgsLayoutMeasurement m = guide->position();
291 m.setLength( newPos );
292 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Move Guide" ), Move + index.row() );
293 whileBlocking( guide )->setPosition( m );
294 guide->update();
295 mLayout->undoStack()->endCommand();
296 emit dataChanged( index, index, QVector<int>() << role );
297 return true;
298 }
299 case static_cast< int >( CustomRole::Position ):
300 {
301 bool ok = false;
302 double newPos = value.toDouble( &ok );
303 if ( !ok )
304 return false;
305
306 QgsLayoutMeasurement m = guide->position();
307 if ( qgsDoubleNear( m.length(), newPos ) )
308 return true;
309
310 m.setLength( newPos );
311 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Move Guide" ), Move + index.row() );
312 whileBlocking( guide )->setPosition( m );
313 guide->update();
314 mLayout->undoStack()->endCommand();
315 emit dataChanged( index, index, QVector<int>() << role );
316 return true;
317 }
318
319 case static_cast< int >( CustomRole::LayoutPosition ):
320 {
321 bool ok = false;
322 double newPos = value.toDouble( &ok );
323 if ( !ok )
324 return false;
325
326 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Move Guide" ), Move + index.row() );
327 whileBlocking( guide )->setLayoutPosition( newPos );
328 mLayout->undoStack()->endCommand();
329 emit dataChanged( index, index, QVector<int>() << role );
330 return true;
331 }
332
333 case static_cast< int >( CustomRole::Units ):
334 {
335 bool ok = false;
336 int units = value.toInt( &ok );
337 if ( !ok )
338 return false;
339
340 QgsLayoutMeasurement m = guide->position();
341 m.setUnits( static_cast< Qgis::LayoutUnit >( units ) );
342 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Move Guide" ), Move + index.row() );
343 whileBlocking( guide )->setPosition( m );
344 guide->update();
345 mLayout->undoStack()->endCommand();
346 emit dataChanged( index, index, QVector<int>() << role );
347 return true;
348 }
349
350 default:
351 break;
352 }
353
354 return false;
355}
356
357Qt::ItemFlags QgsLayoutGuideCollection::flags( const QModelIndex &index ) const
358{
359 if ( !index.isValid() )
360 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
361 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
362}
363
364QVariant QgsLayoutGuideCollection::headerData( int section, Qt::Orientation orientation, int role ) const
365{
366 if ( role == Qt::DisplayRole )
367 return QVariant();
368 else if ( role == Qt::SizeHintRole )
369 {
370 return QSize( mHeaderSize, mHeaderSize );
371 }
372 return QAbstractTableModel::headerData( section, orientation, role );
373}
374
375bool QgsLayoutGuideCollection::removeRows( int row, int count, const QModelIndex &parent )
376{
377 if ( parent.isValid() )
378 return false;
379
380 if ( !mBlockUndoCommands )
381 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Remove Guide(s)" ), Remove + row );
382 beginRemoveRows( parent, row, row + count - 1 );
383 for ( int i = 0; i < count; ++i )
384 {
385 delete mGuides.takeAt( row );
386 }
387 endRemoveRows();
388 if ( !mBlockUndoCommands )
389 mLayout->undoStack()->endCommand();
390 return true;
391}
392
394{
395 if ( guide->layout() != mLayout )
396 guide->setLayout( mLayout );
397
398 if ( !mBlockUndoCommands )
399 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Create Guide" ) );
400 beginInsertRows( QModelIndex(), mGuides.count(), mGuides.count() );
401 mGuides.append( guide );
402 endInsertRows();
403 if ( !mBlockUndoCommands )
404 mLayout->undoStack()->endCommand();
405
406 QModelIndex index = createIndex( mGuides.length() - 1, 0 );
407 connect( guide, &QgsLayoutGuide::positionChanged, this, [this, index] { emit dataChanged( index, index ); } );
408}
409
411{
412 int row = mGuides.indexOf( guide );
413 if ( row < 0 )
414 return;
415
416 removeRow( row );
417}
418
420{
421 int row = mGuides.indexOf( guide );
422 if ( row < 0 )
423 return;
424
425 setData( index( row, 0 ), position, static_cast< int >( CustomRole::LayoutPosition ) );
426}
427
429{
430 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Clear Guides" ) );
431 beginResetModel();
432 qDeleteAll( mGuides );
433 mGuides.clear();
434 endResetModel();
435 mLayout->undoStack()->endCommand();
436}
437
439{
440 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Apply Guides" ) );
441 mBlockUndoCommands = true;
442 QgsLayoutItemPage *page = mPageCollection->page( sourcePage );
443 // remove other page's guides
444 const auto constMGuides = mGuides;
445 for ( QgsLayoutGuide *guide : constMGuides )
446 {
447 if ( guide->page() != page )
448 removeGuide( guide );
449 }
450
451 // remaining guides belong to source page - clone them to other pages
452 const auto constMGuidesNew = mGuides;
453 for ( QgsLayoutGuide *guide : constMGuidesNew )
454 {
455 for ( int p = 0; p < mPageCollection->pageCount(); ++p )
456 {
457 if ( p == sourcePage )
458 continue;
459
460 auto newGuide = std::make_unique<QgsLayoutGuide>( guide->orientation(), guide->position(), mPageCollection->page( p ) );
461 newGuide->setLayout( mLayout );
462 if ( newGuide->item()->isVisible() )
463 {
464 // if invisible, new guide is outside of page bounds
465 addGuide( newGuide.release() );
466 }
467 }
468 }
469 mLayout->undoStack()->endCommand();
470 mBlockUndoCommands = false;
471}
472
474{
475 const auto constMGuides = mGuides;
476 for ( QgsLayoutGuide *guide : constMGuides )
477 {
478 guide->update();
479 }
480}
481
482QList<QgsLayoutGuide *> QgsLayoutGuideCollection::guides()
483{
484 return mGuides;
485}
486
487QList<QgsLayoutGuide *> QgsLayoutGuideCollection::guides( Qt::Orientation orientation, int page )
488{
489 QList<QgsLayoutGuide *> res;
490 const auto constMGuides = mGuides;
491 for ( QgsLayoutGuide *guide : constMGuides )
492 {
493 if ( guide->orientation() == orientation && guide->item()->isVisible() && ( page < 0 || mPageCollection->page( page ) == guide->page() ) )
494 res << guide;
495 }
496 return res;
497}
498
499QList<QgsLayoutGuide *> QgsLayoutGuideCollection::guidesOnPage( int page )
500{
501 QList<QgsLayoutGuide *> res;
502 const auto constMGuides = mGuides;
503 for ( QgsLayoutGuide *guide : constMGuides )
504 {
505 if ( mPageCollection->page( page ) == guide->page() )
506 res << guide;
507 }
508 return res;
509}
510
512{
513 return mGuidesVisible;
514}
515
517{
518 mLayout->undoStack()->beginCommand( mPageCollection, tr( "Change Guide Visibility" ) );
519 mGuidesVisible = visible;
520 mLayout->undoStack()->endCommand();
521 update();
522}
523
524void QgsLayoutGuideCollection::pageAboutToBeRemoved( int pageNumber )
525{
526 mBlockUndoCommands = true;
527 const auto constGuidesOnPage = guidesOnPage( pageNumber );
528 for ( QgsLayoutGuide *guide : constGuidesOnPage )
529 {
530 removeGuide( guide );
531 }
532 mBlockUndoCommands = false;
533}
534
535bool QgsLayoutGuideCollection::writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext & ) const
536{
537 QDomElement element = document.createElement( u"GuideCollection"_s );
538 element.setAttribute( u"visible"_s, mGuidesVisible );
539 const auto constMGuides = mGuides;
540 for ( QgsLayoutGuide *guide : constMGuides )
541 {
542 QDomElement guideElement = document.createElement( u"Guide"_s );
543 guideElement.setAttribute( u"orientation"_s, guide->orientation() );
544 guideElement.setAttribute( u"page"_s, mPageCollection->pageNumber( guide->page() ) );
545 guideElement.setAttribute( u"position"_s, guide->position().length() );
546 guideElement.setAttribute( u"units"_s, QgsUnitTypes::encodeUnit( guide->position().units() ) );
547 element.appendChild( guideElement );
548 }
549
550 parentElement.appendChild( element );
551 return true;
552}
553
554bool QgsLayoutGuideCollection::readXml( const QDomElement &e, const QDomDocument &, const QgsReadWriteContext & )
555{
556 QDomElement element = e;
557 if ( element.nodeName() != "GuideCollection"_L1 )
558 {
559 element = element.firstChildElement( u"GuideCollection"_s );
560 }
561
562 if ( element.nodeName() != "GuideCollection"_L1 )
563 {
564 return false;
565 }
566
567 mBlockUndoCommands = true;
568 beginResetModel();
569 qDeleteAll( mGuides );
570 mGuides.clear();
571
572 mGuidesVisible = element.attribute( u"visible"_s, u"0"_s ) != "0"_L1;
573 QDomNodeList guideNodeList = element.elementsByTagName( u"Guide"_s );
574 for ( int i = 0; i < guideNodeList.size(); ++i )
575 {
576 QDomElement element = guideNodeList.at( i ).toElement();
577 Qt::Orientation orientation = static_cast< Qt::Orientation >( element.attribute( u"orientation"_s, u"1"_s ).toInt() );
578 double pos = element.attribute( u"position"_s, u"0"_s ).toDouble();
579 Qgis::LayoutUnit unit = QgsUnitTypes::decodeLayoutUnit( element.attribute( u"units"_s ) );
580 int page = element.attribute( u"page"_s, u"0"_s ).toInt();
581 auto guide = std::make_unique<QgsLayoutGuide>( orientation, QgsLayoutMeasurement( pos, unit ), mPageCollection->page( page ) );
582 guide->update();
583 addGuide( guide.release() );
584 }
585
586 endResetModel();
587 mBlockUndoCommands = false;
588 return true;
589}
590
591//
592// QgsLayoutGuideProxyModel
593//
594
595QgsLayoutGuideProxyModel::QgsLayoutGuideProxyModel( QObject *parent, Qt::Orientation orientation, int page )
596 : QSortFilterProxyModel( parent )
597 , mOrientation( orientation )
598 , mPage( page )
599{
600 setDynamicSortFilter( true );
601 sort( 0 );
602}
603
605{
606 mPage = page;
607 invalidateFilter();
608}
609
610bool QgsLayoutGuideProxyModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
611{
612 QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
613 const Qt::Orientation orientation = static_cast< Qt::Orientation>( sourceModel()->data( index, static_cast< int >( QgsLayoutGuideCollection::CustomRole::Orientation ) ).value< Qt::Orientation >() );
614 if ( orientation != mOrientation )
615 return false;
616
617 int page = sourceModel()->data( index, static_cast< int >( QgsLayoutGuideCollection::CustomRole::Page ) ).toInt();
618 return page == mPage;
619}
620
621bool QgsLayoutGuideProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
622{
623 double leftPos = sourceModel()->data( left, static_cast< int >( QgsLayoutGuideCollection::CustomRole::LayoutPosition ) ).toDouble();
624 double rightPos = sourceModel()->data( right, static_cast< int >( QgsLayoutGuideCollection::CustomRole::LayoutPosition ) ).toDouble();
625 return leftPos < rightPos;
626}
LayoutUnit
Layout measurement units.
Definition qgis.h:5360
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:60
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:6975
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:6880