Port duplicate layout functionality

This commit is contained in:
Nyall Dawson 2017-12-04 16:33:37 +10:00
parent a64a675830
commit 3a0b751698
8 changed files with 198 additions and 2 deletions

View File

@ -109,6 +109,14 @@ class QgsLayoutManager : QObject
:rtype: QgsComposition
%End
QgsLayout *duplicateLayout( const QgsLayout *layout, const QString &newName );
%Docstring
Duplicates an existing ``layout`` from the manager. The new
layout will automatically be stored in the manager.
Returns new the layout if duplication was successful.
:rtype: QgsLayout
%End
QString generateUniqueTitle() const;
%Docstring
Generates a unique title for a new composition, which does not

View File

@ -46,6 +46,7 @@
#include "qgslayoutmodel.h"
#include "qgslayoutitemslistview.h"
#include "qgsproject.h"
#include "qgsbusyindicatordialog.h"
#include <QShortcut>
#include <QComboBox>
#include <QLineEdit>
@ -311,6 +312,7 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
connect( mActionSaveAsTemplate, &QAction::triggered, this, &QgsLayoutDesignerDialog::saveAsTemplate );
connect( mActionLoadFromTemplate, &QAction::triggered, this, &QgsLayoutDesignerDialog::addItemsFromTemplate );
connect( mActionDuplicateLayout, &QAction::triggered, this, &QgsLayoutDesignerDialog::duplicate );
connect( mActionZoomIn, &QAction::triggered, mView, &QgsLayoutView::zoomIn );
connect( mActionZoomOut, &QAction::triggered, mView, &QgsLayoutView::zoomOut );
@ -1319,6 +1321,32 @@ void QgsLayoutDesignerDialog::addItemsFromTemplate()
}
}
void QgsLayoutDesignerDialog::duplicate()
{
QString newTitle;
if ( !QgisApp::instance()->uniqueLayoutTitle( this, newTitle, false, tr( "%1 copy" ).arg( currentLayout()->name() ) ) )
{
return;
}
// provide feedback, since loading of template into duplicate layout will be hidden
QDialog *dlg = new QgsBusyIndicatorDialog( tr( "Duplicating layout…" ) );
dlg->setStyleSheet( QgisApp::instance()->styleSheet() );
dlg->show();
QgsLayoutDesignerDialog *newDialog = QgisApp::instance()->duplicateLayout( currentLayout(), newTitle );
dlg->close();
delete dlg;
dlg = nullptr;
if ( !newDialog )
{
QMessageBox::warning( this, tr( "Duplicate layout" ),
tr( "Layout duplication failed." ) );
}
}
void QgsLayoutDesignerDialog::paste()
{
QPointF pt = mView->mapFromGlobal( QCursor::pos() );

View File

@ -268,6 +268,7 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
void undoRedoOccurredForItems( const QSet< QString > itemUuids );
void saveAsTemplate();
void addItemsFromTemplate();
void duplicate();
private:

View File

@ -205,6 +205,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "qgslayertreeutils.h"
#include "qgslayertreeview.h"
#include "qgslayertreeviewdefaultactions.h"
#include "qgslayout.h"
#include "qgslayoutcustomdrophandler.h"
#include "qgslayoutdesignerdialog.h"
#include "qgslayoutmanager.h"
@ -7333,6 +7334,71 @@ bool QgisApp::uniqueComposerTitle( QWidget *parent, QString &composerTitle, bool
return true;
}
bool QgisApp::uniqueLayoutTitle( QWidget *parent, QString &title, bool acceptEmpty, const QString &currentTitle )
{
if ( !parent )
{
parent = this;
}
bool ok = false;
bool titleValid = false;
QString newTitle = QString( currentTitle );
QString chooseMsg = tr( "Create unique print layout title" );
if ( acceptEmpty )
{
chooseMsg += '\n' + tr( "(title generated if left empty)" );
}
QString titleMsg = chooseMsg;
QStringList cNames;
cNames << newTitle;
#if 0 //TODO
Q_FOREACH ( QgsComposition *c, QgsProject::instance()->layoutManager()->compositions() )
{
cNames << c->name();
}
#endif
while ( !titleValid )
{
newTitle = QInputDialog::getText( parent,
tr( "Layout title" ),
titleMsg,
QLineEdit::Normal,
newTitle,
&ok );
if ( !ok )
{
return false;
}
if ( newTitle.isEmpty() )
{
if ( !acceptEmpty )
{
titleMsg = chooseMsg + "\n\n" + tr( "Title can not be empty!" );
}
else
{
titleValid = true;
newTitle = QgsProject::instance()->layoutManager()->generateUniqueTitle();
}
}
else if ( cNames.indexOf( newTitle, 1 ) >= 0 )
{
cNames[0] = QString(); // clear non-unique name
titleMsg = chooseMsg + "\n\n" + tr( "Title already exists!" );
}
else
{
titleValid = true;
}
}
title = newTitle;
return true;
}
QgsComposer *QgisApp::createNewComposer( QString title )
{
if ( title.isEmpty() )
@ -7441,6 +7507,21 @@ QgsComposer *QgisApp::duplicateComposer( QgsComposer *currentComposer, QString t
return newComposer;
}
QgsLayoutDesignerDialog *QgisApp::duplicateLayout( QgsLayout *layout, const QString &t )
{
QString title = t;
if ( title.isEmpty() )
{
// TODO: inject a bit of randomness in auto-titles?
title = tr( "%1 copy" ).arg( layout->name() );
}
QgsLayout *newLayout = QgsProject::instance()->layoutManager()->duplicateLayout( layout, title );
QgsLayoutDesignerDialog *dlg = openLayoutDesignerDialog( newLayout );
dlg->activate();
return dlg;
}
void QgisApp::deletePrintComposers()
{
QSet<QgsComposer *>::iterator it = mPrintComposers.begin();

View File

@ -361,6 +361,21 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
* \returns QString() if user cancels input dialog
*/
bool uniqueComposerTitle( QWidget *parent, QString &composerTitle, bool acceptEmpty, const QString &currentTitle = QString() );
/**
* Gets a unique title from user for new and duplicate layouts.
*
* The \a title argument will be filled with the new layout title.
*
* If \a acceptEmpty is true then empty titles will be acceptable (one will be generated).
*
* The \a currentTitle argument specifies a base name for initial title choice.
*
* \returns true if user did not cancel the dialog.
*/
bool uniqueLayoutTitle( QWidget *parent, QString &title, bool acceptEmpty, const QString &currentTitle = QString() );
//! Creates a new composer and returns a pointer to it
QgsComposer *createNewComposer( QString title = QString() );
@ -383,6 +398,14 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
*/
QgsComposer *duplicateComposer( QgsComposer *currentComposer, QString title = QString() );
/**
* Duplicates a \a layout and adds it to the current project.
*
* If \a title is set, it will be used as the title for the new layout. If it is not set,
* and auto-generated title will be used instead.
*/
QgsLayoutDesignerDialog *duplicateLayout( QgsLayout *layout, const QString &title = QString() );
//! Overloaded function used to sort menu entries alphabetically
QMenu *createPopupMenu() override;

View File

@ -14,6 +14,7 @@
***************************************************************************/
#include "qgslayoutmanager.h"
#include "qgslayout.h"
#include "qgsproject.h"
#include "qgslogger.h"
@ -186,6 +187,38 @@ QgsComposition *QgsLayoutManager::duplicateComposition( const QString &name, con
}
}
QgsLayout *QgsLayoutManager::duplicateLayout( const QgsLayout *layout, const QString &newName )
{
QDomDocument currentDoc;
QgsReadWriteContext context;
QDomElement elem = layout->writeXml( currentDoc, context );
currentDoc.appendChild( elem );
std::unique_ptr< QgsLayout > newLayout = qgis::make_unique< QgsLayout >( mProject );
bool ok = false;
newLayout->loadFromTemplate( currentDoc, context, true, &ok );
if ( !ok )
{
return nullptr;
}
newLayout->setName( newName );
#if 0 //TODO
if ( !addComposition( newComposition ) )
{
delete newComposition;
return nullptr;
}
else
{
return newComposition;
}
#endif
return newLayout.release();
}
QString QgsLayoutManager::generateUniqueTitle() const
{
QStringList names;

View File

@ -115,6 +115,13 @@ class CORE_EXPORT QgsLayoutManager : public QObject
*/
QgsComposition *duplicateComposition( const QString &name, const QString &newName );
/**
* Duplicates an existing \a layout from the manager. The new
* layout will automatically be stored in the manager.
* Returns new the layout if duplication was successful.
*/
QgsLayout *duplicateLayout( const QgsLayout *layout, const QString &newName );
/**
* Generates a unique title for a new composition, which does not
* clash with any already contained by the manager.

View File

@ -62,6 +62,7 @@
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="mActionDuplicateLayout"/>
<addaction name="mActionLoadFromTemplate"/>
<addaction name="mActionSaveAsTemplate"/>
</widget>
@ -94,6 +95,8 @@
<property name="title">
<string>&amp;Layout</string>
</property>
<addaction name="mActionDuplicateLayout"/>
<addaction name="separator"/>
<addaction name="mActionLayoutProperties"/>
<addaction name="mActionAddPages"/>
<addaction name="separator"/>
@ -1068,7 +1071,7 @@
<normaloff>:/images/themes/default/mActionFileSaveAs.svg</normaloff>:/images/themes/default/mActionFileSaveAs.svg</iconset>
</property>
<property name="text">
<string>Save as &amp;Template...</string>
<string>Save as &amp;Template</string>
</property>
<property name="toolTip">
<string>Save as template</string>
@ -1080,12 +1083,24 @@
<normaloff>:/images/themes/default/mActionFileOpen.svg</normaloff>:/images/themes/default/mActionFileOpen.svg</iconset>
</property>
<property name="text">
<string>&amp;Add Items from Template...</string>
<string>&amp;Add Items from Template</string>
</property>
<property name="toolTip">
<string>Add items from template</string>
</property>
</action>
<action name="mActionDuplicateLayout">
<property name="icon">
<iconset resource="../../../images/images.qrc">
<normaloff>:/images/themes/default/mActionDuplicateComposer.svg</normaloff>:/images/themes/default/mActionDuplicateComposer.svg</iconset>
</property>
<property name="text">
<string>&amp;Duplicate Layout…</string>
</property>
<property name="toolTip">
<string>Duplicate layout</string>
</property>
</action>
</widget>
<resources>
<include location="../../../images/images.qrc"/>