Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Allow embedded SVG symbols via base 64 encoding #7433

Merged
merged 5 commits into from
Jul 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions python/gui/auto_generated/qgssvgsourcelineedit.sip.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgssvgsourcelineedit.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/




class QgsSvgSourceLineEdit : QWidget
{
%Docstring
A line edit widget with toolbutton for setting an SVG image path.

.. versionadded:: 3.4
%End

%TypeHeaderCode
#include "qgssvgsourcelineedit.h"
%End
public:

QgsSvgSourceLineEdit( QWidget *parent /TransferThis/ = 0 );
%Docstring
Constructor for QgsSvgSourceLineEdit, with the specified ``parent`` widget.
%End

QString source() const;
%Docstring
Returns the current SVG source.

.. seealso:: :py:func:`setSource`

.. seealso:: :py:func:`sourceChanged`
%End

void setLastPathSettingsKey( const QString &key );
%Docstring
Sets a specific settings ``key`` to use when storing the last
used path for the SVG source.
%End

public slots:

void setSource( const QString &source );
%Docstring
Sets a new ``source`` to show in the widget.

.. seealso:: :py:func:`source`

.. seealso:: :py:func:`sourceChanged`
%End

signals:

void sourceChanged( const QString &source );
%Docstring
Emitted whenever the SVG source is changed in the widget.
%End

};

/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/qgssvgsourcelineedit.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
1 change: 1 addition & 0 deletions python/gui/gui_auto.sip
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@
%Include auto_generated/qgsstatusbar.sip
%Include auto_generated/qgssublayersdialog.sip
%Include auto_generated/qgssubstitutionlistwidget.sip
%Include auto_generated/qgssvgsourcelineedit.sip
%Include auto_generated/qgssymbolbutton.sip
%Include auto_generated/qgstablewidgetbase.sip
%Include auto_generated/qgstabwidget.sip
Expand Down
3 changes: 3 additions & 0 deletions src/app/qgsdecorationnortharrow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ void QgsDecorationNorthArrow::run()

QString QgsDecorationNorthArrow::svgPath()
{
if ( mSvgPath.startsWith( QStringLiteral( "base64:" ), Qt::CaseInsensitive ) )
return mSvgPath;

if ( !mSvgPath.isEmpty() )
{
QString resolvedPath = QgsSymbolLayerUtils::svgSymbolNameToPath( mSvgPath, QgsProject::instance()->pathResolver() );
Expand Down
7 changes: 7 additions & 0 deletions src/core/symbology/qgssvgcache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,13 @@ QByteArray QgsSvgCache::getImageData( const QString &path ) const
}
}

// maybe it's an embedded base64 string
if ( path.startsWith( QLatin1String( "base64:" ), Qt::CaseInsensitive ) )
{
QByteArray base64 = path.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
return QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );
}

// maybe it's a url...
if ( !path.contains( QLatin1String( "://" ) ) ) // otherwise short, relative SVG paths might be considered URLs
{
Expand Down
2 changes: 2 additions & 0 deletions src/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ SET(QGIS_GUI_SRCS
qgssubstitutionlistwidget.cpp
qgssqlcomposerdialog.cpp
qgsstatusbar.cpp
qgssvgsourcelineedit.cpp
qgssymbolbutton.cpp
qgstablewidgetbase.cpp
qgstabwidget.cpp
Expand Down Expand Up @@ -526,6 +527,7 @@ SET(QGIS_GUI_MOC_HDRS
qgsstatusbar.h
qgssublayersdialog.h
qgssubstitutionlistwidget.h
qgssvgsourcelineedit.h
qgssymbolbutton.h
qgstablewidgetbase.h
qgstabwidget.h
Expand Down
212 changes: 212 additions & 0 deletions src/gui/qgssvgsourcelineedit.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/***************************************************************************
qgssvgsourcelineedit.cpp
-----------------------
begin : July 2018
copyright : (C) 2018 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgssvgsourcelineedit.h"
#include "qgssettings.h"
#include <QMenu>
#include <QLineEdit>
#include <QToolButton>
#include <QHBoxLayout>
#include <QFileDialog>
#include <QInputDialog>

QgsSvgSourceLineEdit::QgsSvgSourceLineEdit( QWidget *parent )
: QWidget( parent )
{
QHBoxLayout *layout = new QHBoxLayout( this );
mFileLineEdit = new QLineEdit( this );
mFileToolButton = new QToolButton( this );
mFileToolButton->setText( tr( "…" ) );
layout->addWidget( mFileLineEdit, 1 );
layout->addWidget( mFileToolButton );
setLayout( layout );

QMenu *sourceMenu = new QMenu( mFileToolButton );

QAction *selectFileAction = new QAction( tr( "Select File…" ), sourceMenu );
connect( selectFileAction, &QAction::triggered, this, &QgsSvgSourceLineEdit::selectFile );
sourceMenu->addAction( selectFileAction );

QAction *embedFileAction = new QAction( tr( "Embed File…" ), sourceMenu );
connect( embedFileAction, &QAction::triggered, this, &QgsSvgSourceLineEdit::embedFile );
sourceMenu->addAction( embedFileAction );

QAction *extractFileAction = new QAction( tr( "Extract Embedded File…" ), sourceMenu );
connect( extractFileAction, &QAction::triggered, this, &QgsSvgSourceLineEdit::extractFile );
sourceMenu->addAction( extractFileAction );

connect( sourceMenu, &QMenu::aboutToShow, this, [this, extractFileAction]
{
extractFileAction->setEnabled( mFileLineEdit->text().startsWith( QStringLiteral( "base64:" ), Qt::CaseInsensitive ) );
} );

QAction *enterUrlAction = new QAction( tr( "From URL…" ), sourceMenu );
connect( enterUrlAction, &QAction::triggered, this, &QgsSvgSourceLineEdit::selectUrl );
sourceMenu->addAction( enterUrlAction );

mFileToolButton->setMenu( sourceMenu );
mFileToolButton->setPopupMode( QToolButton::MenuButtonPopup );
connect( mFileToolButton, &QToolButton::clicked, this, &QgsSvgSourceLineEdit::selectFile );

connect( mFileLineEdit, &QLineEdit::textEdited, this, &QgsSvgSourceLineEdit::mFileLineEdit_textEdited );
connect( mFileLineEdit, &QLineEdit::editingFinished, this, &QgsSvgSourceLineEdit::mFileLineEdit_editingFinished );
}

QString QgsSvgSourceLineEdit::source() const
{
return mFileLineEdit->text();
}

void QgsSvgSourceLineEdit::setLastPathSettingsKey( const QString &key )
{
mLastPathKey = key;
}

void QgsSvgSourceLineEdit::setSource( const QString &source )
{
if ( source == mFileLineEdit->text() )
return;

mFileLineEdit->setText( source );
emit sourceChanged( source );
}

void QgsSvgSourceLineEdit::selectFile()
{
QgsSettings s;
QString file = QFileDialog::getOpenFileName( nullptr,
tr( "Select SVG file" ),
defaultPath(),
tr( "SVG files" ) + " (*.svg)" );
QFileInfo fi( file );
if ( file.isEmpty() || !fi.exists() || file == source() )
{
return;
}
mFileLineEdit->setText( file );
s.setValue( settingsKey(), fi.absolutePath() );
emit sourceChanged( mFileLineEdit->text() );
}

void QgsSvgSourceLineEdit::selectUrl()
{
bool ok = false;
const QString path = QInputDialog::getText( this, tr( "SVG From URL" ), tr( "Enter SVG URL" ), QLineEdit::Normal, mFileLineEdit->text(), &ok );
if ( ok && path != source() )
{
mFileLineEdit->setText( path );
emit sourceChanged( mFileLineEdit->text() );
}
}

void QgsSvgSourceLineEdit::embedFile()
{
QgsSettings s;
QString file = QFileDialog::getOpenFileName( nullptr,
tr( "Embed SVG File" ),
defaultPath(),
tr( "SVG files" ) + " (*.svg)" );
QFileInfo fi( file );
if ( file.isEmpty() || !fi.exists() )
{
return;
}

s.setValue( settingsKey(), fi.absolutePath() );

// encode file as base64
QFile fileSource( file );
if ( !fileSource.open( QIODevice::ReadOnly ) )
{
return;
}

QByteArray blob = fileSource.readAll();
QByteArray encoded = blob.toBase64();

QString path( encoded );
path.prepend( QLatin1String( "base64:" ) );
if ( path == source() )
return;

mFileLineEdit->setText( path );
emit sourceChanged( mFileLineEdit->text() );
}

void QgsSvgSourceLineEdit::extractFile()
{
QgsSettings s;
QString file = QFileDialog::getSaveFileName( nullptr,
tr( "Extract SVG File" ),
defaultPath(),
tr( "SVG files" ) + " (*.svg)" );
if ( file.isEmpty() )
{
return;
}

QFileInfo fi( file );
s.setValue( settingsKey(), fi.absolutePath() );

// decode current base64 embedded file
QString path = mFileLineEdit->text().trimmed();
if ( path.startsWith( QLatin1String( "base64:" ), Qt::CaseInsensitive ) )
{
QByteArray base64 = path.mid( 7 ).toLocal8Bit(); // strip 'base64:' prefix
QByteArray decoded = QByteArray::fromBase64( base64, QByteArray::OmitTrailingEquals );

QFile fileOut( file );
fileOut.open( QIODevice::WriteOnly );
fileOut.write( decoded );
fileOut.close();
}
}

void QgsSvgSourceLineEdit::mFileLineEdit_textEdited( const QString &text )
{
if ( !QFileInfo::exists( text ) )
{
return;
}
emit sourceChanged( text );
}

void QgsSvgSourceLineEdit::mFileLineEdit_editingFinished()
{
if ( !QFileInfo::exists( mFileLineEdit->text() ) )
{
QUrl url( mFileLineEdit->text() );
if ( !url.isValid() )
{
return;
}
}

emit sourceChanged( mFileLineEdit->text() );
}

QString QgsSvgSourceLineEdit::defaultPath() const
{
if ( QFileInfo::exists( source() ) )
return source();

return QgsSettings().value( settingsKey(), QDir::homePath() ).toString();
}

QString QgsSvgSourceLineEdit::settingsKey() const
{
return mLastPathKey.isEmpty() ? QStringLiteral( "/UI/lastSVGDir" ) : mLastPathKey;
}

Loading