QGIS API Documentation  3.4.15-Madeira (e83d02e274)
qgslayoutmanager.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayoutmanager.cpp
3  --------------------
4  Date : January 2017
5  Copyright : (C) 2017 Nyall Dawson
6  Email : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgslayoutmanager.h"
17 #include "qgslayout.h"
18 #include "qgsproject.h"
19 #include "qgslogger.h"
20 #include "qgslayoutundostack.h"
21 #include "qgsprintlayout.h"
22 #include "qgsreport.h"
24 #include "qgsreadwritecontext.h"
25 
27  : QObject( project )
28  , mProject( project )
29 {
30 
31 }
32 
34 {
35  clear();
36 }
37 
39 {
40  if ( !layout || mLayouts.contains( layout ) )
41  return false;
42 
43  // check for duplicate name
44  for ( QgsMasterLayoutInterface *l : qgis::as_const( mLayouts ) )
45  {
46  if ( l->name() == layout->name() )
47  {
48  delete layout;
49  return false;
50  }
51  }
52 
53  // ugly, but unavoidable for interfaces...
54  if ( QgsPrintLayout *l = dynamic_cast< QgsPrintLayout * >( layout ) )
55  {
56  connect( l, &QgsPrintLayout::nameChanged, this, [this, l]( const QString & newName )
57  {
58  emit layoutRenamed( l, newName );
59  } );
60  }
61  else if ( QgsReport *r = dynamic_cast< QgsReport * >( layout ) )
62  {
63  connect( r, &QgsReport::nameChanged, this, [this, r]( const QString & newName )
64  {
65  emit layoutRenamed( r, newName );
66  } );
67  }
68 
69  emit layoutAboutToBeAdded( layout->name() );
70  mLayouts << layout;
71  emit layoutAdded( layout->name() );
72  mProject->setDirty( true );
73  return true;
74 }
75 
77 {
78  if ( !layout )
79  return false;
80 
81  if ( !mLayouts.contains( layout ) )
82  return false;
83 
84  QString name = layout->name();
85  emit layoutAboutToBeRemoved( name );
86  mLayouts.removeAll( layout );
87  delete layout;
88  emit layoutRemoved( name );
89  mProject->setDirty( true );
90  return true;
91 }
92 
94 {
95  const QList< QgsMasterLayoutInterface * > layouts = mLayouts;
96  for ( QgsMasterLayoutInterface *l : layouts )
97  {
98  removeLayout( l );
99  }
100 }
101 
102 QList<QgsMasterLayoutInterface *> QgsLayoutManager::layouts() const
103 {
104  return mLayouts;
105 }
106 
107 QList<QgsPrintLayout *> QgsLayoutManager::printLayouts() const
108 {
109  QList<QgsPrintLayout *> result;
110  const QList<QgsMasterLayoutInterface *> _layouts( mLayouts );
111  result.reserve( _layouts.size() );
112  for ( const auto &layout : _layouts )
113  {
114  QgsPrintLayout *_item( dynamic_cast<QgsPrintLayout *>( layout ) );
115  if ( _item )
116  result.push_back( _item );
117  }
118  return result;
119 }
120 
122 {
123  for ( QgsMasterLayoutInterface *l : mLayouts )
124  {
125  if ( l->name() == name )
126  return l;
127  }
128  return nullptr;
129 }
130 
131 bool QgsLayoutManager::readXml( const QDomElement &element, const QDomDocument &doc )
132 {
133  clear();
134 
135  QDomElement layoutsElem = element;
136  if ( element.tagName() != QStringLiteral( "Layouts" ) )
137  {
138  layoutsElem = element.firstChildElement( QStringLiteral( "Layouts" ) );
139  }
140  if ( layoutsElem.isNull() )
141  {
142  // handle legacy projects
143  layoutsElem = doc.documentElement();
144  }
145 
146  //restore each composer
147  bool result = true;
148  QDomNodeList composerNodes = element.elementsByTagName( QStringLiteral( "Composer" ) );
149  for ( int i = 0; i < composerNodes.size(); ++i )
150  {
151  // This legacy title is the Composer "title" (that can be overridden by the Composition "name")
152  QString legacyTitle = composerNodes.at( i ).toElement().attribute( QStringLiteral( "title" ) );
153  // Convert compositions to layouts
154  QDomNodeList compositionNodes = composerNodes.at( i ).toElement().elementsByTagName( QStringLiteral( "Composition" ) );
155  for ( int j = 0; j < compositionNodes.size(); ++j )
156  {
157  std::unique_ptr< QgsPrintLayout > l( QgsCompositionConverter::createLayoutFromCompositionXml( compositionNodes.at( j ).toElement(), mProject ) );
158  if ( l )
159  {
160  if ( l->name().isEmpty() )
161  l->setName( legacyTitle );
162 
163  // some 2.x projects could end in a state where they had duplicated layout names. This is strictly forbidden in 3.x
164  // so check for duplicate name in layouts already added
165  int id = 2;
166  bool isDuplicateName = false;
167  QString originalName = l->name();
168  do
169  {
170  isDuplicateName = false;
171  for ( QgsMasterLayoutInterface *layout : qgis::as_const( mLayouts ) )
172  {
173  if ( l->name() == layout->name() )
174  {
175  isDuplicateName = true;
176  break;
177  }
178  }
179  if ( isDuplicateName )
180  {
181  l->setName( QStringLiteral( "%1 %2" ).arg( originalName ).arg( id ) );
182  id++;
183  }
184  }
185  while ( isDuplicateName );
186 
187  bool added = addLayout( l.release() );
188  result = added && result;
189  }
190  }
191  }
192 
193  QgsReadWriteContext context;
194  context.setPathResolver( mProject->pathResolver() );
195 
196  // restore layouts
197  const QDomNodeList layoutNodes = layoutsElem.childNodes();
198  for ( int i = 0; i < layoutNodes.size(); ++i )
199  {
200  if ( layoutNodes.at( i ).nodeName() != QStringLiteral( "Layout" ) )
201  continue;
202 
203  std::unique_ptr< QgsPrintLayout > l = qgis::make_unique< QgsPrintLayout >( mProject );
204  l->undoStack()->blockCommands( true );
205  if ( !l->readLayoutXml( layoutNodes.at( i ).toElement(), doc, context ) )
206  {
207  result = false;
208  continue;
209  }
210  l->undoStack()->blockCommands( false );
211  if ( addLayout( l.get() ) )
212  {
213  ( void )l.release(); // ownership was transferred successfully
214  }
215  else
216  {
217  result = false;
218  }
219  }
220  //reports
221  const QDomNodeList reportNodes = element.elementsByTagName( QStringLiteral( "Report" ) );
222  for ( int i = 0; i < reportNodes.size(); ++i )
223  {
224  std::unique_ptr< QgsReport > r = qgis::make_unique< QgsReport >( mProject );
225  if ( !r->readLayoutXml( reportNodes.at( i ).toElement(), doc, context ) )
226  {
227  result = false;
228  continue;
229  }
230  if ( addLayout( r.get() ) )
231  {
232  ( void )r.release(); // ownership was transferred successfully
233  }
234  else
235  {
236  result = false;
237  }
238  }
239  return result;
240 }
241 
242 QDomElement QgsLayoutManager::writeXml( QDomDocument &doc ) const
243 {
244  QDomElement layoutsElem = doc.createElement( QStringLiteral( "Layouts" ) );
245 
246  QgsReadWriteContext context;
247  context.setPathResolver( mProject->pathResolver() );
248  for ( QgsMasterLayoutInterface *l : mLayouts )
249  {
250  QDomElement layoutElem = l->writeLayoutXml( doc, context );
251  layoutsElem.appendChild( layoutElem );
252  }
253  return layoutsElem;
254 }
255 
257 {
258  if ( !layout )
259  return nullptr;
260 
261  std::unique_ptr< QgsMasterLayoutInterface > newLayout( layout->clone() );
262  if ( !newLayout )
263  {
264  return nullptr;
265  }
266 
267  newLayout->setName( newName );
268  QgsMasterLayoutInterface *l = newLayout.get();
269  if ( !addLayout( newLayout.release() ) )
270  {
271  return nullptr;
272  }
273  else
274  {
275  return l;
276  }
277 }
278 
280 {
281  QStringList names;
282  names.reserve( mLayouts.size() );
283  for ( QgsMasterLayoutInterface *l : mLayouts )
284  {
285  names << l->name();
286  }
287  QString name;
288  int id = 1;
289  while ( name.isEmpty() || names.contains( name ) )
290  {
291  switch ( type )
292  {
294  name = tr( "Layout %1" ).arg( id );
295  break;
297  name = tr( "Report %1" ).arg( id );
298  break;
299  }
300  id++;
301  }
302  return name;
303 }
304 
void setDirty(bool b=true)
Flag the project as dirty (modified).
Definition: qgsproject.cpp:441
The class is used as a container of context for various read/write operations on other objects...
void layoutAboutToBeRemoved(const QString &name)
Emitted when a layout is about to be removed from the manager.
void setPathResolver(const QgsPathResolver &resolver)
Sets up path resolver for conversion between relative and absolute paths.
virtual QgsMasterLayoutInterface * clone() const =0
Creates a clone of the layout.
QString generateUniqueTitle(QgsMasterLayoutInterface::Type type=QgsMasterLayoutInterface::PrintLayout) const
Generates a unique title for a new layout of the specified type, which does not clash with any alread...
static std::unique_ptr< QgsPrintLayout > createLayoutFromCompositionXml(const QDomElement &composerElement, QgsProject *project)
createLayoutFromCompositionXml is a factory that creates layout instances from a QGIS 2...
QList< QgsPrintLayout * > printLayouts() const
Returns a list of all print layouts contained in the manager.
virtual void setName(const QString &name)=0
Sets the layout&#39;s name.
bool readXml(const QDomElement &element, const QDomDocument &doc)
Reads the manager&#39;s state from a DOM element, restoring all layouts present in the XML document...
void layoutAboutToBeAdded(const QString &name)
Emitted when a layout is about to be added to the manager.
QgsLayoutManager(QgsProject *project=nullptr)
Constructor for QgsLayoutManager.
QList< QgsMasterLayoutInterface * > layouts() const
Returns a list of all layouts contained in the manager.
QgsPathResolver pathResolver() const
Returns path resolver object with considering whether the project uses absolute or relative paths and...
Reads and writes project states.
Definition: qgsproject.h:89
void layoutRemoved(const QString &name)
Emitted when a layout was removed from the manager.
void layoutAdded(const QString &name)
Emitted when a layout has been added to the manager.
void layoutRenamed(QgsMasterLayoutInterface *layout, const QString &newName)
Emitted when a layout is renamed.
QDomElement writeXml(QDomDocument &doc) const
Returns a DOM element representing the state of the manager.
void clear()
Removes and deletes all layouts from the manager.
void nameChanged(const QString &name)
Emitted when the layout&#39;s name is changed.
QgsMasterLayoutInterface * layoutByName(const QString &name) const
Returns the layout with a matching name, or nullptr if no matching layouts were found.
virtual QString name() const =0
Returns the layout&#39;s name.
bool removeLayout(QgsMasterLayoutInterface *layout)
Removes a layout from the manager.
Print layout, a QgsLayout subclass for static or atlas-based layouts.
Interface for master layout type objects, such as print layouts and reports.
QgsMasterLayoutInterface * duplicateLayout(const QgsMasterLayoutInterface *layout, const QString &newName)
Duplicates an existing layout from the manager.
~QgsLayoutManager() override
Individual print layout (QgsPrintLayout)
bool addLayout(QgsMasterLayoutInterface *layout)
Adds a layout to the manager.