Skip to content

Commit 3a0b751

Browse files
committed
Port duplicate layout functionality
1 parent a64a675 commit 3a0b751

File tree

8 files changed

+198
-2
lines changed

8 files changed

+198
-2
lines changed

python/core/composer/qgslayoutmanager.sip

+8
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,14 @@ class QgsLayoutManager : QObject
109109
:rtype: QgsComposition
110110
%End
111111

112+
QgsLayout *duplicateLayout( const QgsLayout *layout, const QString &newName );
113+
%Docstring
114+
Duplicates an existing ``layout`` from the manager. The new
115+
layout will automatically be stored in the manager.
116+
Returns new the layout if duplication was successful.
117+
:rtype: QgsLayout
118+
%End
119+
112120
QString generateUniqueTitle() const;
113121
%Docstring
114122
Generates a unique title for a new composition, which does not

src/app/layout/qgslayoutdesignerdialog.cpp

+28
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include "qgslayoutmodel.h"
4747
#include "qgslayoutitemslistview.h"
4848
#include "qgsproject.h"
49+
#include "qgsbusyindicatordialog.h"
4950
#include <QShortcut>
5051
#include <QComboBox>
5152
#include <QLineEdit>
@@ -311,6 +312,7 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
311312

312313
connect( mActionSaveAsTemplate, &QAction::triggered, this, &QgsLayoutDesignerDialog::saveAsTemplate );
313314
connect( mActionLoadFromTemplate, &QAction::triggered, this, &QgsLayoutDesignerDialog::addItemsFromTemplate );
315+
connect( mActionDuplicateLayout, &QAction::triggered, this, &QgsLayoutDesignerDialog::duplicate );
314316

315317
connect( mActionZoomIn, &QAction::triggered, mView, &QgsLayoutView::zoomIn );
316318
connect( mActionZoomOut, &QAction::triggered, mView, &QgsLayoutView::zoomOut );
@@ -1319,6 +1321,32 @@ void QgsLayoutDesignerDialog::addItemsFromTemplate()
13191321
}
13201322
}
13211323

1324+
void QgsLayoutDesignerDialog::duplicate()
1325+
{
1326+
QString newTitle;
1327+
if ( !QgisApp::instance()->uniqueLayoutTitle( this, newTitle, false, tr( "%1 copy" ).arg( currentLayout()->name() ) ) )
1328+
{
1329+
return;
1330+
}
1331+
1332+
// provide feedback, since loading of template into duplicate layout will be hidden
1333+
QDialog *dlg = new QgsBusyIndicatorDialog( tr( "Duplicating layout…" ) );
1334+
dlg->setStyleSheet( QgisApp::instance()->styleSheet() );
1335+
dlg->show();
1336+
1337+
QgsLayoutDesignerDialog *newDialog = QgisApp::instance()->duplicateLayout( currentLayout(), newTitle );
1338+
1339+
dlg->close();
1340+
delete dlg;
1341+
dlg = nullptr;
1342+
1343+
if ( !newDialog )
1344+
{
1345+
QMessageBox::warning( this, tr( "Duplicate layout" ),
1346+
tr( "Layout duplication failed." ) );
1347+
}
1348+
}
1349+
13221350
void QgsLayoutDesignerDialog::paste()
13231351
{
13241352
QPointF pt = mView->mapFromGlobal( QCursor::pos() );

src/app/layout/qgslayoutdesignerdialog.h

+1
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
268268
void undoRedoOccurredForItems( const QSet< QString > itemUuids );
269269
void saveAsTemplate();
270270
void addItemsFromTemplate();
271+
void duplicate();
271272

272273
private:
273274

src/app/qgisapp.cpp

+81
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
205205
#include "qgslayertreeutils.h"
206206
#include "qgslayertreeview.h"
207207
#include "qgslayertreeviewdefaultactions.h"
208+
#include "qgslayout.h"
208209
#include "qgslayoutcustomdrophandler.h"
209210
#include "qgslayoutdesignerdialog.h"
210211
#include "qgslayoutmanager.h"
@@ -7333,6 +7334,71 @@ bool QgisApp::uniqueComposerTitle( QWidget *parent, QString &composerTitle, bool
73337334
return true;
73347335
}
73357336

7337+
bool QgisApp::uniqueLayoutTitle( QWidget *parent, QString &title, bool acceptEmpty, const QString &currentTitle )
7338+
{
7339+
if ( !parent )
7340+
{
7341+
parent = this;
7342+
}
7343+
bool ok = false;
7344+
bool titleValid = false;
7345+
QString newTitle = QString( currentTitle );
7346+
QString chooseMsg = tr( "Create unique print layout title" );
7347+
if ( acceptEmpty )
7348+
{
7349+
chooseMsg += '\n' + tr( "(title generated if left empty)" );
7350+
}
7351+
QString titleMsg = chooseMsg;
7352+
7353+
QStringList cNames;
7354+
cNames << newTitle;
7355+
#if 0 //TODO
7356+
Q_FOREACH ( QgsComposition *c, QgsProject::instance()->layoutManager()->compositions() )
7357+
{
7358+
cNames << c->name();
7359+
}
7360+
#endif
7361+
while ( !titleValid )
7362+
{
7363+
newTitle = QInputDialog::getText( parent,
7364+
tr( "Layout title" ),
7365+
titleMsg,
7366+
QLineEdit::Normal,
7367+
newTitle,
7368+
&ok );
7369+
if ( !ok )
7370+
{
7371+
return false;
7372+
}
7373+
7374+
if ( newTitle.isEmpty() )
7375+
{
7376+
if ( !acceptEmpty )
7377+
{
7378+
titleMsg = chooseMsg + "\n\n" + tr( "Title can not be empty!" );
7379+
}
7380+
else
7381+
{
7382+
titleValid = true;
7383+
newTitle = QgsProject::instance()->layoutManager()->generateUniqueTitle();
7384+
}
7385+
}
7386+
else if ( cNames.indexOf( newTitle, 1 ) >= 0 )
7387+
{
7388+
cNames[0] = QString(); // clear non-unique name
7389+
titleMsg = chooseMsg + "\n\n" + tr( "Title already exists!" );
7390+
}
7391+
else
7392+
{
7393+
titleValid = true;
7394+
}
7395+
}
7396+
7397+
title = newTitle;
7398+
7399+
return true;
7400+
}
7401+
73367402
QgsComposer *QgisApp::createNewComposer( QString title )
73377403
{
73387404
if ( title.isEmpty() )
@@ -7441,6 +7507,21 @@ QgsComposer *QgisApp::duplicateComposer( QgsComposer *currentComposer, QString t
74417507
return newComposer;
74427508
}
74437509

7510+
QgsLayoutDesignerDialog *QgisApp::duplicateLayout( QgsLayout *layout, const QString &t )
7511+
{
7512+
QString title = t;
7513+
if ( title.isEmpty() )
7514+
{
7515+
// TODO: inject a bit of randomness in auto-titles?
7516+
title = tr( "%1 copy" ).arg( layout->name() );
7517+
}
7518+
7519+
QgsLayout *newLayout = QgsProject::instance()->layoutManager()->duplicateLayout( layout, title );
7520+
QgsLayoutDesignerDialog *dlg = openLayoutDesignerDialog( newLayout );
7521+
dlg->activate();
7522+
return dlg;
7523+
}
7524+
74447525
void QgisApp::deletePrintComposers()
74457526
{
74467527
QSet<QgsComposer *>::iterator it = mPrintComposers.begin();

src/app/qgisapp.h

+23
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,21 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
361361
* \returns QString() if user cancels input dialog
362362
*/
363363
bool uniqueComposerTitle( QWidget *parent, QString &composerTitle, bool acceptEmpty, const QString &currentTitle = QString() );
364+
365+
/**
366+
* Gets a unique title from user for new and duplicate layouts.
367+
*
368+
* The \a title argument will be filled with the new layout title.
369+
*
370+
* If \a acceptEmpty is true then empty titles will be acceptable (one will be generated).
371+
*
372+
* The \a currentTitle argument specifies a base name for initial title choice.
373+
*
374+
* \returns true if user did not cancel the dialog.
375+
*/
376+
bool uniqueLayoutTitle( QWidget *parent, QString &title, bool acceptEmpty, const QString &currentTitle = QString() );
377+
378+
364379
//! Creates a new composer and returns a pointer to it
365380
QgsComposer *createNewComposer( QString title = QString() );
366381

@@ -383,6 +398,14 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
383398
*/
384399
QgsComposer *duplicateComposer( QgsComposer *currentComposer, QString title = QString() );
385400

401+
/**
402+
* Duplicates a \a layout and adds it to the current project.
403+
*
404+
* If \a title is set, it will be used as the title for the new layout. If it is not set,
405+
* and auto-generated title will be used instead.
406+
*/
407+
QgsLayoutDesignerDialog *duplicateLayout( QgsLayout *layout, const QString &title = QString() );
408+
386409
//! Overloaded function used to sort menu entries alphabetically
387410
QMenu *createPopupMenu() override;
388411

src/core/composer/qgslayoutmanager.cpp

+33
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
***************************************************************************/
1515

1616
#include "qgslayoutmanager.h"
17+
#include "qgslayout.h"
1718
#include "qgsproject.h"
1819
#include "qgslogger.h"
1920

@@ -186,6 +187,38 @@ QgsComposition *QgsLayoutManager::duplicateComposition( const QString &name, con
186187
}
187188
}
188189

190+
QgsLayout *QgsLayoutManager::duplicateLayout( const QgsLayout *layout, const QString &newName )
191+
{
192+
QDomDocument currentDoc;
193+
194+
QgsReadWriteContext context;
195+
QDomElement elem = layout->writeXml( currentDoc, context );
196+
currentDoc.appendChild( elem );
197+
198+
std::unique_ptr< QgsLayout > newLayout = qgis::make_unique< QgsLayout >( mProject );
199+
bool ok = false;
200+
newLayout->loadFromTemplate( currentDoc, context, true, &ok );
201+
if ( !ok )
202+
{
203+
return nullptr;
204+
}
205+
206+
newLayout->setName( newName );
207+
#if 0 //TODO
208+
if ( !addComposition( newComposition ) )
209+
{
210+
delete newComposition;
211+
return nullptr;
212+
}
213+
else
214+
{
215+
return newComposition;
216+
}
217+
#endif
218+
219+
return newLayout.release();
220+
}
221+
189222
QString QgsLayoutManager::generateUniqueTitle() const
190223
{
191224
QStringList names;

src/core/composer/qgslayoutmanager.h

+7
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ class CORE_EXPORT QgsLayoutManager : public QObject
115115
*/
116116
QgsComposition *duplicateComposition( const QString &name, const QString &newName );
117117

118+
/**
119+
* Duplicates an existing \a layout from the manager. The new
120+
* layout will automatically be stored in the manager.
121+
* Returns new the layout if duplication was successful.
122+
*/
123+
QgsLayout *duplicateLayout( const QgsLayout *layout, const QString &newName );
124+
118125
/**
119126
* Generates a unique title for a new composition, which does not
120127
* clash with any already contained by the manager.

src/ui/layout/qgslayoutdesignerbase.ui

+17-2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
<attribute name="toolBarBreak">
6363
<bool>false</bool>
6464
</attribute>
65+
<addaction name="mActionDuplicateLayout"/>
6566
<addaction name="mActionLoadFromTemplate"/>
6667
<addaction name="mActionSaveAsTemplate"/>
6768
</widget>
@@ -94,6 +95,8 @@
9495
<property name="title">
9596
<string>&amp;Layout</string>
9697
</property>
98+
<addaction name="mActionDuplicateLayout"/>
99+
<addaction name="separator"/>
97100
<addaction name="mActionLayoutProperties"/>
98101
<addaction name="mActionAddPages"/>
99102
<addaction name="separator"/>
@@ -1068,7 +1071,7 @@
10681071
<normaloff>:/images/themes/default/mActionFileSaveAs.svg</normaloff>:/images/themes/default/mActionFileSaveAs.svg</iconset>
10691072
</property>
10701073
<property name="text">
1071-
<string>Save as &amp;Template...</string>
1074+
<string>Save as &amp;Template</string>
10721075
</property>
10731076
<property name="toolTip">
10741077
<string>Save as template</string>
@@ -1080,12 +1083,24 @@
10801083
<normaloff>:/images/themes/default/mActionFileOpen.svg</normaloff>:/images/themes/default/mActionFileOpen.svg</iconset>
10811084
</property>
10821085
<property name="text">
1083-
<string>&amp;Add Items from Template...</string>
1086+
<string>&amp;Add Items from Template</string>
10841087
</property>
10851088
<property name="toolTip">
10861089
<string>Add items from template</string>
10871090
</property>
10881091
</action>
1092+
<action name="mActionDuplicateLayout">
1093+
<property name="icon">
1094+
<iconset resource="../../../images/images.qrc">
1095+
<normaloff>:/images/themes/default/mActionDuplicateComposer.svg</normaloff>:/images/themes/default/mActionDuplicateComposer.svg</iconset>
1096+
</property>
1097+
<property name="text">
1098+
<string>&amp;Duplicate Layout…</string>
1099+
</property>
1100+
<property name="toolTip">
1101+
<string>Duplicate layout</string>
1102+
</property>
1103+
</action>
10891104
</widget>
10901105
<resources>
10911106
<include location="../../../images/images.qrc"/>

0 commit comments

Comments
 (0)