Skip to content

Commit

Permalink
Merge pull request #5686 from nyalldawson/encoding
Browse files Browse the repository at this point in the history
[processing] Don't use crappy Qt file picker dialog
  • Loading branch information
nyalldawson committed Nov 21, 2017
2 parents d62b4e9 + b97c6a8 commit b058df7
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 39 deletions.
40 changes: 40 additions & 0 deletions python/gui/qgsencodingfiledialog.sip
Expand Up @@ -49,6 +49,46 @@ Returns true if the user clicked 'Cancel All'

};

class QgsEncodingSelectionDialog: QDialog
{
%Docstring
A dialog which presents the user with a choice of file encodings.
.. versionadded:: 3.0
*
%End

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

QgsEncodingSelectionDialog( QWidget *parent /TransferThis/ = 0,
const QString &caption = QString(), const QString &encoding = QString(),
Qt::WindowFlags flags = Qt::WindowFlags() );
%Docstring
Constructor for QgsEncodingSelectionDialog.

If ``caption`` is set, it will be used as the caption within the dialog.

The ``encoding`` argument can be used to specify the encoding initially selected in the dialog.
%End

QString encoding() const;
%Docstring
Returns the encoding selected within the dialog.
.. seealso:: setEncoding()
:rtype: str
%End

void setEncoding( const QString &encoding );
%Docstring
Sets the ``encoding`` selected within the dialog.
see encoding()
%End

};


/************************************************************************
* This file has been generated automatically from *
* *
Expand Down
91 changes: 52 additions & 39 deletions python/plugins/processing/gui/DestinationSelectionPanel.py
Expand Up @@ -33,12 +33,13 @@
from qgis.PyQt.QtCore import QCoreApplication, QDir
from qgis.PyQt.QtWidgets import QDialog, QMenu, QAction, QFileDialog
from qgis.PyQt.QtGui import QCursor
from qgis.gui import QgsEncodingFileDialog, QgsExpressionBuilderDialog
from qgis.gui import QgsEncodingSelectionDialog
from qgis.core import (QgsDataSourceUri,
QgsCredentials,
QgsExpression,
QgsSettings,
QgsProcessingParameterFeatureSink,
QgsProcessingParameterRasterDestination,
QgsProcessingOutputLayerDefinition,
QgsProcessingParameterDefinition,
QgsProcessingParameterFileDestination,
Expand Down Expand Up @@ -138,6 +139,11 @@ def selectOutput(self):
actionSaveToPostGIS.setEnabled(bool(names))
popupMenu.addAction(actionSaveToPostGIS)

actionSetEncoding = QAction(
self.tr('Change file encoding ({})...').format(self.encoding), self.btnSelect)
actionSetEncoding.triggered.connect(self.selectEncoding)
popupMenu.addAction(actionSetEncoding)

popupMenu.exec_(QCursor.pos())

def saveToTemporary(self):
Expand Down Expand Up @@ -172,71 +178,78 @@ def saveToPostGIS(self):
self.leText.setText("postgis:" + uri.uri())

def saveToSpatialite(self):
fileFilter = self.tr('SpatiaLite files (*.sqlite)', 'OutputFile')
file_filter = self.tr('SpatiaLite files (*.sqlite)', 'OutputFile')

settings = QgsSettings()
if settings.contains('/Processing/LastOutputPath'):
path = settings.value('/Processing/LastOutputPath')
else:
path = ProcessingConfig.getSetting(ProcessingConfig.OUTPUT_FOLDER)

fileDialog = QgsEncodingFileDialog(
self, self.tr('Save SpatiaLite'), path, fileFilter, self.encoding)
fileDialog.setFileMode(QFileDialog.AnyFile)
fileDialog.setAcceptMode(QFileDialog.AcceptSave)
fileDialog.setOption(QFileDialog.DontConfirmOverwrite, True)
filename, filter = QFileDialog.getSaveFileName(self, self.tr("Save file"), path,
file_filter, options=QFileDialog.DontConfirmOverwrite)

if fileDialog.exec_() == QDialog.Accepted:
if filename is not None:
self.use_temporary = False
files = fileDialog.selectedFiles()
self.encoding = str(fileDialog.encoding())
fileName = str(files[0])
selectedFileFilter = str(fileDialog.selectedNameFilter())
if not fileName.lower().endswith(
tuple(re.findall("\\*(\\.[a-z]{1,10})", fileFilter))):
ext = re.search("\\*(\\.[a-z]{1,10})", selectedFileFilter)
if ext:
fileName += ext.group(1)
if not filename.lower().endswith('.sqlite'):
filename += '.sqlite'
settings.setValue('/Processing/LastOutputPath',
os.path.dirname(fileName))
settings.setValue('/Processing/encoding', self.encoding)
os.path.dirname(filename))

uri = QgsDataSourceUri()
uri.setDatabase(fileName)
uri.setDatabase(filename)
uri.setDataSource('', self.parameter.name().lower(),
'the_geom' if isinstance(self.parameter, QgsProcessingParameterFeatureSink) and self.parameter.hasGeometry() else None)
self.leText.setText("spatialite:" + uri.uri())

def selectFile(self):
fileFilter = getFileFilter(self.parameter)

file_filter = getFileFilter(self.parameter)
settings = QgsSettings()
if isinstance(self.parameter, QgsProcessingParameterFeatureSink):
last_ext_path = '/Processing/LastVectorOutputExt'
last_ext = settings.value(last_ext_path, '.gpkg')
elif isinstance(self.parameter, QgsProcessingParameterRasterDestination):
last_ext_path = '/Processing/LastRasterOutputExt'
last_ext = settings.value(last_ext_path, '.tif')
else:
last_ext_path = None
last_ext = None

# get default filter
filters = file_filter.split(';;')
try:
last_filter = [f for f in filters if '*{}'.format(last_ext) in f.lower()][0]
except:
last_filter = None

if settings.contains('/Processing/LastOutputPath'):
path = settings.value('/Processing/LastOutputPath')
else:
path = ProcessingConfig.getSetting(ProcessingConfig.OUTPUT_FOLDER)

fileDialog = QgsEncodingFileDialog(
self, self.tr('Save file'), path, fileFilter, self.encoding)
fileDialog.setFileMode(QFileDialog.AnyFile)
fileDialog.setAcceptMode(QFileDialog.AcceptSave)
fileDialog.setOption(QFileDialog.DontConfirmOverwrite, False)

if fileDialog.exec_() == QDialog.Accepted:
filename, filter = QFileDialog.getSaveFileName(self, self.tr("Save file"), path,
file_filter, last_filter)
if filename:
self.use_temporary = False
files = fileDialog.selectedFiles()
self.encoding = str(fileDialog.encoding())
fileName = str(files[0])
selectedFileFilter = str(fileDialog.selectedNameFilter())
if not fileName.lower().endswith(
tuple(re.findall("\\*(\\.[a-z]{1,10})", fileFilter))):
ext = re.search("\\*(\\.[a-z]{1,10})", selectedFileFilter)
if not filename.lower().endswith(
tuple(re.findall("\\*(\\.[a-z]{1,10})", file_filter))):
ext = re.search("\\*(\\.[a-z]{1,10})", filter)
if ext:
fileName += ext.group(1)
self.leText.setText(fileName)
filename += ext.group(1)
self.leText.setText(filename)
settings.setValue('/Processing/LastOutputPath',
os.path.dirname(fileName))
os.path.dirname(filename))
if not last_ext_path is None:
settings.setValue(last_ext_path, os.path.splitext(filename)[1].lower())

def selectEncoding(self):
dialog = QgsEncodingSelectionDialog(
self, self.tr('File encoding'), self.encoding)
if dialog.exec_() == QDialog.Accepted:
self.encoding = dialog.encoding()
settings = QgsSettings()
settings.setValue('/Processing/encoding', self.encoding)
dialog.deleteLater()

def selectDirectory(self):
lastDir = self.leText.text()
Expand Down
62 changes: 62 additions & 0 deletions src/gui/qgsencodingfiledialog.cpp
Expand Up @@ -24,6 +24,7 @@
#include <QLabel>
#include <QLayout>
#include <QTextCodec>
#include <QDialogButtonBox>

QgsEncodingFileDialog::QgsEncodingFileDialog( QWidget *parent,
const QString &caption, const QString &directory,
Expand Down Expand Up @@ -102,3 +103,64 @@ void QgsEncodingFileDialog::pbnCancelAll_clicked()
// Now, continue as the user clicked the cancel button
reject();
}

QgsEncodingSelectionDialog::QgsEncodingSelectionDialog( QWidget *parent, const QString &caption, const QString &encoding, Qt::WindowFlags flags )
: QDialog( parent, flags )
{
QString c = caption;
if ( c.isEmpty() )
c = tr( "Encoding" );

setWindowTitle( tr( "Select Encoding" ) );

QVBoxLayout *layout = new QVBoxLayout();
layout->setMargin( 6 );

mEncodingComboBox = new QComboBox( this );
QLabel *l = new QLabel( c, this );

QHBoxLayout *hLayout = new QHBoxLayout();
hLayout->addWidget( l );
hLayout->addWidget( mEncodingComboBox, 1 );
layout->addLayout( hLayout );

QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
Qt::Horizontal, this );
buttonBox->button( QDialogButtonBox::Ok )->setDefault( true );
connect( buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
connect( buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
layout->addWidget( buttonBox );
setLayout( layout );

mEncodingComboBox->addItem( tr( "System" ) );
mEncodingComboBox->addItems( QgsVectorDataProvider::availableEncodings() );

// Use default encoding if none supplied
QString enc = encoding;
if ( encoding.isEmpty() )
{
QgsSettings settings;
enc = settings.value( QStringLiteral( "UI/encoding" ), "System" ).toString();
}

setEncoding( enc );
}

QString QgsEncodingSelectionDialog::encoding() const
{
return mEncodingComboBox->currentText();
}

void QgsEncodingSelectionDialog::setEncoding( const QString &encoding )
{
// The specified decoding is added if not existing alread, and then set current.
// This should select it.

int encindex = mEncodingComboBox->findText( encoding );
if ( encindex < 0 )
{
mEncodingComboBox->insertItem( 0, encoding );
encindex = 0;
}
mEncodingComboBox->setCurrentIndex( encindex );
}
41 changes: 41 additions & 0 deletions src/gui/qgsencodingfiledialog.h
Expand Up @@ -61,4 +61,45 @@ class GUI_EXPORT QgsEncodingFileDialog: public QFileDialog
bool mCancelAll;
};

/**
* \ingroup gui
* A dialog which presents the user with a choice of file encodings.
* \since QGIS 3.0
**/
class GUI_EXPORT QgsEncodingSelectionDialog: public QDialog
{
Q_OBJECT

public:

/**
* Constructor for QgsEncodingSelectionDialog.
*
* If \a caption is set, it will be used as the caption within the dialog.
*
* The \a encoding argument can be used to specify the encoding initially selected in the dialog.
*/
QgsEncodingSelectionDialog( QWidget *parent SIP_TRANSFERTHIS = nullptr,
const QString &caption = QString(), const QString &encoding = QString(),
Qt::WindowFlags flags = Qt::WindowFlags() );

/**
* Returns the encoding selected within the dialog.
* \see setEncoding()
*/
QString encoding() const;

/**
* Sets the \a encoding selected within the dialog.
* see encoding()
*/
void setEncoding( const QString &encoding );

private:

QComboBox *mEncodingComboBox = nullptr;

};


#endif
1 change: 1 addition & 0 deletions tests/src/python/CMakeLists.txt
Expand Up @@ -48,6 +48,7 @@ ADD_PYTHON_TEST(PyQgsDelimitedTextProvider test_qgsdelimitedtextprovider.py)
ADD_PYTHON_TEST(PyQgsDistanceArea test_qgsdistancearea.py)
ADD_PYTHON_TEST(PyQgsEditWidgets test_qgseditwidgets.py)
ADD_PYTHON_TEST(PyQgsEllipsoidUtils test_qgsellipsoidutils.py)
ADD_PYTHON_TEST(PyQgsEncodingSelectionDialog test_qgsencodingselectiondialog.py)
ADD_PYTHON_TEST(PyQgsExpression test_qgsexpression.py)
ADD_PYTHON_TEST(PyQgsExpressionBuilderWidget test_qgsexpressionbuilderwidget.py)
ADD_PYTHON_TEST(PyQgsExpressionLineEdit test_qgsexpressionlineedit.py)
Expand Down
38 changes: 38 additions & 0 deletions tests/src/python/test_qgsencodingselectiondialog.py
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsEncodingSelectionDialog
.. note:: 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.
"""
__author__ = 'Nyall Dawson'
__date__ = '21/11/2017'
__copyright__ = 'Copyright 2017, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'

import qgis # NOQA

from qgis.gui import QgsEncodingSelectionDialog

from qgis.testing import start_app, unittest

start_app()


class TestQgsEncodingSelectionDialog(unittest.TestCase):

def testGettersSetters(self):
""" test dialog getters/setters """
dlg = qgis.gui.QgsEncodingSelectionDialog(encoding='UTF-16')
self.assertEqual(dlg.encoding(), 'UTF-16')
dlg.setEncoding('UTF-8')
self.assertEqual(dlg.encoding(), 'UTF-8')
# custom encoding option
dlg.setEncoding('trisolarian')
self.assertEqual(dlg.encoding(), 'trisolarian')


if __name__ == '__main__':
unittest.main()

0 comments on commit b058df7

Please sign in to comment.