QGIS API Documentation  3.2.0-Bonn (bc43194)
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 
30 QgsLayoutGuide::QgsLayoutGuide( Qt::Orientation orientation, const 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 
113 QGraphicsLineItem *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 
181 Qt::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 ).width( QStringLiteral( "XX" ) );
199 
200  connect( mPageCollection, &QgsLayoutPageCollection::pageAboutToBeRemoved, this, &QgsLayoutGuideCollection::pageAboutToBeRemoved );
201 }
202 
204 {
205  qDeleteAll( mGuides );
206 }
207 
209 {
210  return mLayout;
211 }
212 
213 int QgsLayoutGuideCollection::rowCount( const QModelIndex & ) const
214 {
215  return mGuides.count();
216 }
217 
218 int QgsLayoutGuideCollection::columnCount( const QModelIndex &parent ) const
219 {
220  if ( parent.isValid() )
221  return 0;
222 
223  return 2;
224 }
225 
226 QVariant 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
243  return QgsUnitTypes::toAbbreviatedString( guide->position().units() );
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 
258  case LayoutPositionRole:
259  return guide->layoutPosition();
260 
261  default:
262  return QVariant();
263  }
264 }
265 
266 bool 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 ( 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 
314  case LayoutPositionRole:
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 
349 Qt::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 
356 QVariant 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 
367 bool 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  guide->setLayout( mLayout );
388 
389  if ( !mBlockUndoCommands )
390  mLayout->undoStack()->beginCommand( mPageCollection, tr( "Create Guide" ) );
391  beginInsertRows( QModelIndex(), mGuides.count(), mGuides.count() );
392  mGuides.append( guide );
393  endInsertRows();
394  if ( !mBlockUndoCommands )
395  mLayout->undoStack()->endCommand();
396 
397  QModelIndex index = createIndex( mGuides.length() - 1, 0 );
398  connect( guide, &QgsLayoutGuide::positionChanged, this, [ this, index ]
399  {
400  emit dataChanged( index, index );
401  } );
402 }
403 
405 {
406  int row = mGuides.indexOf( guide );
407  if ( row < 0 )
408  return;
409 
410  removeRow( row );
411 }
412 
414 {
415  int row = mGuides.indexOf( guide );
416  if ( row < 0 )
417  return;
418 
419  setData( index( row, 0 ), position, LayoutPositionRole );
420 }
421 
423 {
424  mLayout->undoStack()->beginCommand( mPageCollection, tr( "Clear Guides" ) );
425  beginResetModel();
426  qDeleteAll( mGuides );
427  mGuides.clear();
428  endResetModel();
429  mLayout->undoStack()->endCommand();
430 }
431 
433 {
434  mLayout->undoStack()->beginCommand( mPageCollection, tr( "Apply Guides" ) );
435  mBlockUndoCommands = true;
436  QgsLayoutItemPage *page = mPageCollection->page( sourcePage );
437  // remove other page's guides
438  Q_FOREACH ( QgsLayoutGuide *guide, mGuides )
439  {
440  if ( guide->page() != page )
441  removeGuide( guide );
442  }
443 
444  // remaining guides belong to source page - clone them to other pages
445  Q_FOREACH ( QgsLayoutGuide *guide, mGuides )
446  {
447  for ( int p = 0; p < mPageCollection->pageCount(); ++p )
448  {
449  if ( p == sourcePage )
450  continue;
451 
452  std::unique_ptr< QgsLayoutGuide> newGuide( new QgsLayoutGuide( guide->orientation(), guide->position(), mPageCollection->page( p ) ) );
453  newGuide->setLayout( mLayout );
454  if ( newGuide->item()->isVisible() )
455  {
456  // if invisible, new guide is outside of page bounds
457  addGuide( newGuide.release() );
458  }
459  }
460  }
461  mLayout->undoStack()->endCommand();
462  mBlockUndoCommands = false;
463 }
464 
466 {
467  Q_FOREACH ( QgsLayoutGuide *guide, mGuides )
468  {
469  guide->update();
470  }
471 }
472 
473 QList<QgsLayoutGuide *> QgsLayoutGuideCollection::guides()
474 {
475  return mGuides;
476 }
477 
478 QList<QgsLayoutGuide *> QgsLayoutGuideCollection::guides( Qt::Orientation orientation, int page )
479 {
480  QList<QgsLayoutGuide *> res;
481  Q_FOREACH ( QgsLayoutGuide *guide, mGuides )
482  {
483  if ( guide->orientation() == orientation && guide->item()->isVisible() &&
484  ( page < 0 || mPageCollection->page( page ) == guide->page() ) )
485  res << guide;
486  }
487  return res;
488 }
489 
490 QList<QgsLayoutGuide *> QgsLayoutGuideCollection::guidesOnPage( int page )
491 {
492  QList<QgsLayoutGuide *> res;
493  Q_FOREACH ( QgsLayoutGuide *guide, mGuides )
494  {
495  if ( mPageCollection->page( page ) == guide->page() )
496  res << guide;
497  }
498  return res;
499 }
500 
502 {
503  return mGuidesVisible;
504 }
505 
507 {
508  mLayout->undoStack()->beginCommand( mPageCollection, tr( "Change Guide Visibility" ) );
509  mGuidesVisible = visible;
510  mLayout->undoStack()->endCommand();
511  update();
512 }
513 
514 void QgsLayoutGuideCollection::pageAboutToBeRemoved( int pageNumber )
515 {
516  mBlockUndoCommands = true;
517  Q_FOREACH ( QgsLayoutGuide *guide, guidesOnPage( pageNumber ) )
518  {
519  removeGuide( guide );
520  }
521  mBlockUndoCommands = false;
522 }
523 
524 bool QgsLayoutGuideCollection::writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext & ) const
525 {
526  QDomElement element = document.createElement( QStringLiteral( "GuideCollection" ) );
527  element.setAttribute( QStringLiteral( "visible" ), mGuidesVisible );
528  Q_FOREACH ( QgsLayoutGuide *guide, mGuides )
529  {
530  QDomElement guideElement = document.createElement( QStringLiteral( "Guide" ) );
531  guideElement.setAttribute( QStringLiteral( "orientation" ), guide->orientation() );
532  guideElement.setAttribute( QStringLiteral( "page" ), mPageCollection->pageNumber( guide->page() ) );
533  guideElement.setAttribute( QStringLiteral( "position" ), guide->position().length() );
534  guideElement.setAttribute( QStringLiteral( "units" ), QgsUnitTypes::encodeUnit( guide->position().units() ) );
535  element.appendChild( guideElement );
536  }
537 
538  parentElement.appendChild( element );
539  return true;
540 }
541 
542 bool QgsLayoutGuideCollection::readXml( const QDomElement &e, const QDomDocument &, const QgsReadWriteContext & )
543 {
544  QDomElement element = e;
545  if ( element.nodeName() != QStringLiteral( "GuideCollection" ) )
546  {
547  element = element.firstChildElement( QStringLiteral( "GuideCollection" ) );
548  }
549 
550  if ( element.nodeName() != QStringLiteral( "GuideCollection" ) )
551  {
552  return false;
553  }
554 
555  mBlockUndoCommands = true;
556  beginResetModel();
557  qDeleteAll( mGuides );
558  mGuides.clear();
559 
560  mGuidesVisible = element.attribute( QStringLiteral( "visible" ), QStringLiteral( "0" ) ) != QLatin1String( "0" );
561  QDomNodeList guideNodeList = element.elementsByTagName( QStringLiteral( "Guide" ) );
562  for ( int i = 0; i < guideNodeList.size(); ++i )
563  {
564  QDomElement element = guideNodeList.at( i ).toElement();
565  Qt::Orientation orientation = static_cast< Qt::Orientation >( element.attribute( QStringLiteral( "orientation" ), QStringLiteral( "1" ) ).toInt() );
566  double pos = element.attribute( QStringLiteral( "position" ), QStringLiteral( "0" ) ).toDouble();
567  QgsUnitTypes::LayoutUnit unit = QgsUnitTypes::decodeLayoutUnit( element.attribute( QStringLiteral( "units" ) ) );
568  int page = element.attribute( QStringLiteral( "page" ), QStringLiteral( "0" ) ).toInt();
569  std::unique_ptr< QgsLayoutGuide > guide( new QgsLayoutGuide( orientation, QgsLayoutMeasurement( pos, unit ), mPageCollection->page( page ) ) );
570  guide->update();
571  addGuide( guide.release() );
572  }
573 
574  endResetModel();
575  mBlockUndoCommands = false;
576  return true;
577 }
578 
579 //
580 // QgsLayoutGuideProxyModel
581 //
582 
583 QgsLayoutGuideProxyModel::QgsLayoutGuideProxyModel( QObject *parent, Qt::Orientation orientation, int page )
584  : QSortFilterProxyModel( parent )
585  , mOrientation( orientation )
586  , mPage( page )
587 {
588  setDynamicSortFilter( true );
589  sort( 0 );
590 }
591 
593 {
594  mPage = page;
595  invalidateFilter();
596 }
597 
598 bool QgsLayoutGuideProxyModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
599 {
600  QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
601  Qt::Orientation orientation = static_cast< Qt::Orientation>( sourceModel()->data( index, QgsLayoutGuideCollection::OrientationRole ).toInt() );
602  if ( orientation != mOrientation )
603  return false;
604 
605  int page = sourceModel()->data( index, QgsLayoutGuideCollection::PageRole ).toInt();
606  return page == mPage;
607 }
608 
609 bool QgsLayoutGuideProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
610 {
611  double leftPos = sourceModel()->data( left, QgsLayoutGuideCollection::LayoutPositionRole ).toDouble();
612  double rightPos = sourceModel()->data( right, QgsLayoutGuideCollection::LayoutPositionRole ).toDouble();
613  return leftPos < rightPos;
614 }
void update()
Updates the position of the guide&#39;s line item.
The class is used as a container of context for various read/write operations on other objects...
QgsUnitTypes::LayoutUnit units() const
Returns the units for the measurement.
void pageAboutToBeRemoved(int pageNumber)
Emitted just before a page is removed 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...
QgsLayoutMeasurement position() const
Returns the guide&#39;s position within the page.
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
QgsLayoutUndoStack * undoStack()
Returns a pointer to the layout&#39;s undo stack, which manages undo/redo states for the layout and it&#39;s ...
Definition: qgslayout.cpp:681
Contains the configuration for a single snap guide used by a layout.
bool setData(const QModelIndex &index, const QVariant &value, int role) override
void setPage(int page)
Sets the current page for filtering matching guides.
bool visible() const
Returns true if the guide lines should be drawn.
void setLayoutPosition(double position)
Sets the guide&#39;s position in absolute layout units.
void setLayout(QgsLayout *layout)
Sets the layout the guide belongs to.
double layoutPosition() const
Returns the guide&#39;s position in absolute layout units.
Qt::Orientation orientation() const
Returns the guide&#39;s orientation.
void clear()
Removes all guides from the collection.
void applyGuidesToAllOtherPages(int sourcePage)
Resets all other pages&#39; guides to match the guides from the specified sourcePage. ...
void positionChanged()
Emitted when the guide&#39;s position is changed.
QVariant data(const QModelIndex &index, int role) const override
QGraphicsLineItem * item()
Returns the guide&#39;s line item.
void addGuide(QgsLayoutGuide *guide)
Adds a guide to the collection.
static Q_INVOKABLE QString toAbbreviatedString(QgsUnitTypes::DistanceUnit unit)
Returns a translated abbreviation representing a distance unit.
bool writeXml(QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores the collection&#39;s state in a DOM element.
void endCommand()
Saves final state of an object and pushes the active command to the undo history. ...
QgsLayoutItemPage * page(int pageNumber)
Returns a specific page (by pageNumber) from the collection.
void setLength(const double length)
Sets the length of the measurement.
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
static Q_INVOKABLE QgsUnitTypes::LayoutUnit decodeLayoutUnit(const QString &string, bool *ok=nullptr)
Decodes a layout unit from a string.
Guide position in layout coordinates.
void setGuideLayoutPosition(QgsLayoutGuide *guide, double position)
Sets the absolute position (in layout coordinates) for guide within the layout.
QgsLayout * layout() const
Returns the layout the guide belongs to.
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:49
void setPage(QgsLayoutItemPage *page)
Sets the page the guide is contained within.
A manager for a collection of pages in a layout.
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
static Q_INVOKABLE QString encodeUnit(QgsUnitTypes::DistanceUnit unit)
Encodes a distance unit to a string.
bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex()) override
void beginCommand(QgsLayoutUndoObjectInterface *object, const QString &commandText, int id=0)
Begins a new undo command for the specified object.
int pageCount() const
Returns the number of pages in the collection.
int columnCount(const QModelIndex &) const override
void setUnits(const QgsUnitTypes::LayoutUnit units)
Sets the units for the measurement.
bool readXml(const QDomElement &collectionElement, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets the collection&#39;s state from a DOM element.
Z-value for page guides.
Definition: qgslayout.h:61
QgsLayoutGuideCollection(QgsLayout *layout, QgsLayoutPageCollection *pageCollection)
Constructor for QgsLayoutGuideCollection belonging to the specified layout, and linked to the specifi...
QList< QgsLayoutGuide *> guides()
Returns a list of all guides contained in the collection.
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:224
double length() const
Returns the length of the measurement.
QgsLayoutGuideProxyModel(QObject *parent, Qt::Orientation orientation, int page)
Constructor for QgsLayoutGuideProxyModel, filtered to guides of the specified orientation and page on...
QList< QgsLayoutGuide *> guidesOnPage(int page)
Returns the list of guides contained on a matching page.
void update()
Updates the position (and visibility) of all guide line items.
QgsLayout * layout() override
Returns the layout the object belongs to.
QgsLayoutItemPage * page()
Returns the page the guide is contained within.
void setPosition(const QgsLayoutMeasurement &position)
Sets the guide&#39;s position within the page.
QgsLayoutGuide(Qt::Orientation orientation, const QgsLayoutMeasurement &position, QgsLayoutItemPage *page)
Constructor for a new guide with the specified orientation and initial position.
LayoutUnit
Layout measurement units.
Definition: qgsunittypes.h:114
int rowCount(const QModelIndex &) const override
Qt::ItemFlags flags(const QModelIndex &index) const override
void setVisible(bool visible)
Sets whether the guide lines should be visible.
void removeGuide(QgsLayoutGuide *guide)
Removes the specified guide, and deletes it.
Item representing the paper in a layout.