Skip to content

Commit

Permalink
Restore ability to save layouts to templates and add items from template
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Dec 6, 2017
1 parent 59b6bf6 commit a4dea99
Show file tree
Hide file tree
Showing 8 changed files with 306 additions and 1 deletion.
22 changes: 22 additions & 0 deletions python/core/layout/qgslayout.sip
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,28 @@ class QgsLayout : QGraphicsScene, QgsExpressionContextGenerator, QgsLayoutUndoOb
:rtype: list of QgsLayoutMultiFrame
%End

bool saveAsTemplate( const QString &path, const QgsReadWriteContext &context ) const;
%Docstring
Saves the layout as a template at the given file ``path``.
Returns true if save was successful.
.. seealso:: loadFromTemplate()
:rtype: bool
%End

QList< QgsLayoutItem * > loadFromTemplate( const QDomDocument &document, const QgsReadWriteContext &context, bool clearExisting = true, bool *ok /Out/ = 0 );
%Docstring
Load a layout template ``document``.

By default this method will clear all items from the existing layout and real all layout
settings from the template. Setting ``clearExisting`` to false will only add new items
from the template, without overwriting the existing items or layout settings.

If ``ok`` is specified, it will be set to true if the load was successful.

Returns a list of loaded items.
:rtype: list of QgsLayoutItem
%End

QDomElement writeXml( QDomDocument &document, const QgsReadWriteContext &context ) const;
%Docstring
Returns the layout's state encapsulated in a DOM element.
Expand Down
84 changes: 84 additions & 0 deletions src/app/layout/qgslayoutdesignerdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include "qgslayoutmousehandles.h"
#include "qgslayoutmodel.h"
#include "qgslayoutitemslistview.h"
#include "qgsproject.h"
#include <QShortcut>
#include <QComboBox>
#include <QLineEdit>
Expand All @@ -52,6 +53,8 @@
#include <QLabel>
#include <QUndoView>
#include <QTreeView>
#include <QFileDialog>
#include <QMessageBox>

#ifdef ENABLE_MODELTEST
#include "modeltest.h"
Expand Down Expand Up @@ -299,6 +302,9 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
mActionPreviewProtanope->setActionGroup( previewGroup );
mActionPreviewDeuteranope->setActionGroup( previewGroup );

connect( mActionSaveAsTemplate, &QAction::triggered, this, &QgsLayoutDesignerDialog::saveAsTemplate );
connect( mActionLoadFromTemplate, &QAction::triggered, this, &QgsLayoutDesignerDialog::addItemsFromTemplate );

connect( mActionZoomIn, &QAction::triggered, mView, &QgsLayoutView::zoomIn );
connect( mActionZoomOut, &QAction::triggered, mView, &QgsLayoutView::zoomOut );
connect( mActionZoomAll, &QAction::triggered, mView, &QgsLayoutView::zoomFull );
Expand Down Expand Up @@ -1130,6 +1136,84 @@ void QgsLayoutDesignerDialog::undoRedoOccurredForItems( const QSet<QString> item
showItemOptions( focusItem );
}

void QgsLayoutDesignerDialog::saveAsTemplate()
{
//show file dialog
QgsSettings settings;
QString lastSaveDir = settings.value( QStringLiteral( "UI/lastComposerTemplateDir" ), QDir::homePath() ).toString();
#ifdef Q_OS_MAC
mQgis->activateWindow();
this->raise();
#endif
QString saveFileName = QFileDialog::getSaveFileName(
this,
tr( "Save template" ),
lastSaveDir,
tr( "Layout templates" ) + " (*.qpt *.QPT)" );
if ( saveFileName.isEmpty() )
return;

QFileInfo saveFileInfo( saveFileName );
//check if suffix has been added
if ( saveFileInfo.suffix().isEmpty() )
{
QString saveFileNameWithSuffix = saveFileName.append( ".qpt" );
saveFileInfo = QFileInfo( saveFileNameWithSuffix );
}
settings.setValue( QStringLiteral( "UI/lastComposerTemplateDir" ), saveFileInfo.absolutePath() );

QgsReadWriteContext context;
context.setPathResolver( QgsProject::instance()->pathResolver() );
if ( !currentLayout()->saveAsTemplate( saveFileName, context ) )
{
QMessageBox::warning( nullptr, tr( "Save template" ), tr( "Error creating template file." ) );
}
}

void QgsLayoutDesignerDialog::addItemsFromTemplate()
{
if ( !currentLayout() )
return;

QgsSettings settings;
QString openFileDir = settings.value( QStringLiteral( "UI/lastComposerTemplateDir" ), QDir::homePath() ).toString();
QString openFileString = QFileDialog::getOpenFileName( nullptr, tr( "Load template" ), openFileDir, tr( "Layout templates" ) + " (*.qpt *.QPT)" );

if ( openFileString.isEmpty() )
{
return; //canceled by the user
}

QFileInfo openFileInfo( openFileString );
settings.setValue( QStringLiteral( "UI/LastComposerTemplateDir" ), openFileInfo.absolutePath() );

QFile templateFile( openFileString );
if ( !templateFile.open( QIODevice::ReadOnly ) )
{
QMessageBox::warning( this, tr( "Load from template" ), tr( "Could not read template file." ) );
return;
}

QDomDocument templateDoc;
QgsReadWriteContext context;
context.setPathResolver( QgsProject::instance()->pathResolver() );
if ( templateDoc.setContent( &templateFile ) )
{
bool ok = false;
QList< QgsLayoutItem * > items = currentLayout()->loadFromTemplate( templateDoc, context, false, &ok );
if ( !ok )
{
QMessageBox::warning( this, tr( "Load from template" ), tr( "Could not read template file." ) );
return;
}
else
{
whileBlocking( currentLayout() )->deselectAll();
selectItems( items );
}
}
}

void QgsLayoutDesignerDialog::paste()
{
QPointF pt = mView->mapFromGlobal( QCursor::pos() );
Expand Down
2 changes: 2 additions & 0 deletions src/app/layout/qgslayoutdesignerdialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,8 @@ class QgsLayoutDesignerDialog: public QMainWindow, private Ui::QgsLayoutDesigner
void statusMessageReceived( const QString &message );
void dockVisibilityChanged( bool visible );
void undoRedoOccurredForItems( const QSet< QString > itemUuids );
void saveAsTemplate();
void addItemsFromTemplate();

private:

Expand Down
71 changes: 71 additions & 0 deletions src/core/layout/qgslayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,77 @@ QList<QgsLayoutMultiFrame *> QgsLayout::multiFrames() const
return mMultiFrames;
}

bool QgsLayout::saveAsTemplate( const QString &path, const QgsReadWriteContext &context ) const
{
QFile templateFile( path );
if ( !templateFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
{
return false;
}

QDomDocument saveDocument;
QDomElement elem = writeXml( saveDocument, context );
saveDocument.appendChild( elem );

if ( templateFile.write( saveDocument.toByteArray() ) == -1 )
return false;

return true;
}

QList< QgsLayoutItem * > QgsLayout::loadFromTemplate( const QDomDocument &document, const QgsReadWriteContext &context, bool clearExisting, bool *ok )
{
if ( ok )
*ok = false;

QList< QgsLayoutItem * > result;

if ( clearExisting )
{
clear();
}

QDomDocument doc = document;

// remove all uuid attributes since we don't want duplicates UUIDS
QDomNodeList composerItemsNodes = doc.elementsByTagName( QStringLiteral( "ComposerItem" ) );
for ( int i = 0; i < composerItemsNodes.count(); ++i )
{
QDomNode composerItemNode = composerItemsNodes.at( i );
if ( composerItemNode.isElement() )
{
composerItemNode.toElement().setAttribute( QStringLiteral( "templateUuid" ), composerItemNode.toElement().attribute( QStringLiteral( "uuid" ) ) );
composerItemNode.toElement().removeAttribute( QStringLiteral( "uuid" ) );
}
}

//read general settings
if ( clearExisting )
{
QDomElement layoutElem = doc.documentElement();
if ( layoutElem.isNull() )
{
return result;
}

bool loadOk = readXml( layoutElem, doc, context );
if ( !loadOk )
{
return result;
}
layoutItems( result );
}
else
{
result = addItemsFromXml( doc.documentElement(), doc, context );
}

if ( ok )
*ok = true;

return result;
}

QgsLayoutUndoStack *QgsLayout::undoStack()
{
return mUndoStack.get();
Expand Down
20 changes: 20 additions & 0 deletions src/core/layout/qgslayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,26 @@ class CORE_EXPORT QgsLayout : public QGraphicsScene, public QgsExpressionContext
*/
QList< QgsLayoutMultiFrame * > multiFrames() const;

/**
* Saves the layout as a template at the given file \a path.
* Returns true if save was successful.
* \see loadFromTemplate()
*/
bool saveAsTemplate( const QString &path, const QgsReadWriteContext &context ) const;

/**
* Load a layout template \a document.
*
* By default this method will clear all items from the existing layout and real all layout
* settings from the template. Setting \a clearExisting to false will only add new items
* from the template, without overwriting the existing items or layout settings.
*
* If \a ok is specified, it will be set to true if the load was successful.
*
* Returns a list of loaded items.
*/
QList< QgsLayoutItem * > loadFromTemplate( const QDomDocument &document, const QgsReadWriteContext &context, bool clearExisting = true, bool *ok SIP_OUT = nullptr );

/**
* Returns the layout's state encapsulated in a DOM element.
* \see readXml()
Expand Down
3 changes: 3 additions & 0 deletions src/gui/layout/qgslayoutview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,9 @@ void QgsLayoutView::deleteSelectedItems()

void QgsLayoutView::deleteItems( const QList<QgsLayoutItem *> &items )
{
if ( items.empty() )
return;

currentLayout()->undoStack()->beginMacro( tr( "Delete Items" ) );
//delete selected items
for ( QgsLayoutItem *item : items )
Expand Down
32 changes: 31 additions & 1 deletion src/ui/layout/qgslayoutdesignerbase.ui
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="mActionLoadFromTemplate"/>
<addaction name="mActionSaveAsTemplate"/>
</widget>
<widget class="QToolBar" name="mToolsToolbar">
<property name="windowTitle">
Expand All @@ -85,7 +87,7 @@
<x>0</x>
<y>0</y>
<width>1083</width>
<height>42</height>
<height>25</height>
</rect>
</property>
<widget class="QMenu" name="mLayoutMenu">
Expand All @@ -95,6 +97,9 @@
<addaction name="mActionLayoutProperties"/>
<addaction name="mActionAddPages"/>
<addaction name="separator"/>
<addaction name="mActionLoadFromTemplate"/>
<addaction name="mActionSaveAsTemplate"/>
<addaction name="separator"/>
<addaction name="mActionClose"/>
</widget>
<widget class="QMenu" name="mItemMenu">
Expand Down Expand Up @@ -1057,6 +1062,30 @@
<string>Ctrl+Shift+V</string>
</property>
</action>
<action name="mActionSaveAsTemplate">
<property name="icon">
<iconset resource="../../../images/images.qrc">
<normaloff>:/images/themes/default/mActionFileSaveAs.svg</normaloff>:/images/themes/default/mActionFileSaveAs.svg</iconset>
</property>
<property name="text">
<string>Save as &amp;Template...</string>
</property>
<property name="toolTip">
<string>Save as template</string>
</property>
</action>
<action name="mActionLoadFromTemplate">
<property name="icon">
<iconset resource="../../../images/images.qrc">
<normaloff>:/images/themes/default/mActionFileOpen.svg</normaloff>:/images/themes/default/mActionFileOpen.svg</iconset>
</property>
<property name="text">
<string>&amp;Add Items from Template...</string>
</property>
<property name="toolTip">
<string>Add items from template</string>
</property>
</action>
</widget>
<resources>
<include location="../../../images/images.qrc"/>
Expand Down Expand Up @@ -1086,6 +1115,7 @@
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
<include location="../../../images/images.qrc"/>
</resources>
<connections/>
</ui>
Loading

0 comments on commit a4dea99

Please sign in to comment.