QGIS API Documentation  3.6.0-Noosa (5873452)
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:460
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.
QgsMasterLayoutInterface * layoutByName(const QString &name) const
Returns the layout with a matching name, or nullptr if no matching layouts were found.
virtual QgsMasterLayoutInterface * clone() const =0
Creates a clone of the layout.
static std::unique_ptr< QgsPrintLayout > createLayoutFromCompositionXml(const QDomElement &composerElement, QgsProject *project)
createLayoutFromCompositionXml is a factory that creates layout instances from a QGIS 2...
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...
QList< QgsPrintLayout *> printLayouts() const
Returns a list of all print layouts contained in the manager.
void layoutAboutToBeAdded(const QString &name)
Emitted when a layout is about to be added to the manager.
QgsLayoutManager(QgsProject *project=nullptr)
Constructor for QgsLayoutManager.
QgsPathResolver pathResolver() const
Returns path resolver object with considering whether the project uses absolute or relative paths and...
QDomElement writeXml(QDomDocument &doc) const
Returns a DOM element representing the state of the manager.
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.
void clear()
Removes and deletes all layouts from the manager.
void nameChanged(const QString &name)
Emitted when the layout&#39;s name is changed.
virtual QString name() const =0
Returns the layout&#39;s name.
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...
bool removeLayout(QgsMasterLayoutInterface *layout)
Removes a layout from the manager.
Print layout, a QgsLayout subclass for static or atlas-based layouts.
QList< QgsMasterLayoutInterface *> layouts() const
Returns a list of all layouts contained in the manager.
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.