QGIS API Documentation  3.2.0-Bonn (bc43194)
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  for ( const auto &layout : _layouts )
112  {
113  QgsPrintLayout *_item( dynamic_cast<QgsPrintLayout *>( layout ) );
114  if ( _item )
115  result.push_back( _item );
116  }
117  return result;
118 }
119 
121 {
122  for ( QgsMasterLayoutInterface *l : mLayouts )
123  {
124  if ( l->name() == name )
125  return l;
126  }
127  return nullptr;
128 }
129 
130 bool QgsLayoutManager::readXml( const QDomElement &element, const QDomDocument &doc )
131 {
132  clear();
133 
134  QDomElement layoutsElem = element;
135  if ( element.tagName() != QStringLiteral( "Layouts" ) )
136  {
137  layoutsElem = element.firstChildElement( QStringLiteral( "Layouts" ) );
138  }
139  if ( layoutsElem.isNull() )
140  {
141  // handle legacy projects
142  layoutsElem = doc.documentElement();
143  }
144 
145  //restore each composer
146  bool result = true;
147  QDomNodeList composerNodes = element.elementsByTagName( QStringLiteral( "Composer" ) );
148  for ( int i = 0; i < composerNodes.size(); ++i )
149  {
150  // This legacy title is the Composer "title" (that can be overridden by the Composition "name")
151  QString legacyTitle = composerNodes.at( i ).toElement().attribute( QStringLiteral( "title" ) );
152  // Convert compositions to layouts
153  QDomNodeList compositionNodes = composerNodes.at( i ).toElement().elementsByTagName( QStringLiteral( "Composition" ) );
154  for ( int j = 0; j < compositionNodes.size(); ++j )
155  {
156  std::unique_ptr< QgsPrintLayout > l( QgsCompositionConverter::createLayoutFromCompositionXml( compositionNodes.at( j ).toElement(), mProject ) );
157  if ( l )
158  {
159  if ( l->name().isEmpty() )
160  l->setName( legacyTitle );
161 
162  // some 2.x projects could end in a state where they had duplicated layout names. This is strictly forbidden in 3.x
163  // so check for duplicate name in layouts already added
164  int id = 2;
165  bool isDuplicateName = false;
166  QString originalName = l->name();
167  do
168  {
169  isDuplicateName = false;
170  for ( QgsMasterLayoutInterface *layout : qgis::as_const( mLayouts ) )
171  {
172  if ( l->name() == layout->name() )
173  {
174  isDuplicateName = true;
175  break;
176  }
177  }
178  if ( isDuplicateName )
179  {
180  l->setName( QStringLiteral( "%1 %2" ).arg( originalName ).arg( id ) );
181  id++;
182  }
183  }
184  while ( isDuplicateName );
185 
186  bool added = addLayout( l.release() );
187  result = added && result;
188  }
189  }
190  }
191 
192  QgsReadWriteContext context;
193  context.setPathResolver( mProject->pathResolver() );
194 
195  // restore layouts
196  const QDomNodeList layoutNodes = layoutsElem.childNodes();
197  for ( int i = 0; i < layoutNodes.size(); ++i )
198  {
199  if ( layoutNodes.at( i ).nodeName() != QStringLiteral( "Layout" ) )
200  continue;
201 
202  std::unique_ptr< QgsPrintLayout > l = qgis::make_unique< QgsPrintLayout >( mProject );
203  l->undoStack()->blockCommands( true );
204  if ( !l->readLayoutXml( layoutNodes.at( i ).toElement(), doc, context ) )
205  {
206  result = false;
207  continue;
208  }
209  l->undoStack()->blockCommands( false );
210  if ( addLayout( l.get() ) )
211  {
212  ( void )l.release(); // ownership was transferred successfully
213  }
214  else
215  {
216  result = false;
217  }
218  }
219  //reports
220  const QDomNodeList reportNodes = element.elementsByTagName( QStringLiteral( "Report" ) );
221  for ( int i = 0; i < reportNodes.size(); ++i )
222  {
223  std::unique_ptr< QgsReport > r = qgis::make_unique< QgsReport >( mProject );
224  if ( !r->readLayoutXml( reportNodes.at( i ).toElement(), doc, context ) )
225  {
226  result = false;
227  continue;
228  }
229  if ( addLayout( r.get() ) )
230  {
231  ( void )r.release(); // ownership was transferred successfully
232  }
233  else
234  {
235  result = false;
236  }
237  }
238  return result;
239 }
240 
241 QDomElement QgsLayoutManager::writeXml( QDomDocument &doc ) const
242 {
243  QDomElement layoutsElem = doc.createElement( QStringLiteral( "Layouts" ) );
244 
245  QgsReadWriteContext context;
246  context.setPathResolver( mProject->pathResolver() );
247  for ( QgsMasterLayoutInterface *l : mLayouts )
248  {
249  QDomElement layoutElem = l->writeLayoutXml( doc, context );
250  layoutsElem.appendChild( layoutElem );
251  }
252  return layoutsElem;
253 }
254 
256 {
257  if ( !layout )
258  return nullptr;
259 
260  std::unique_ptr< QgsMasterLayoutInterface > newLayout( layout->clone() );
261  if ( !newLayout )
262  {
263  return nullptr;
264  }
265 
266  newLayout->setName( newName );
267  QgsMasterLayoutInterface *l = newLayout.get();
268  if ( !addLayout( newLayout.release() ) )
269  {
270  return nullptr;
271  }
272  else
273  {
274  return l;
275  }
276 }
277 
279 {
280  QStringList names;
281  for ( QgsMasterLayoutInterface *l : mLayouts )
282  {
283  names << l->name();
284  }
285  QString name;
286  int id = 1;
287  while ( name.isEmpty() || names.contains( name ) )
288  {
289  switch ( type )
290  {
292  name = tr( "Layout %1" ).arg( id );
293  break;
295  name = tr( "Report %1" ).arg( id );
296  break;
297  }
298  id++;
299  }
300  return name;
301 }
302 
void setDirty(bool b=true)
Flag the project as dirty (modified).
Definition: qgsproject.cpp:421
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:85
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.