Skip to content

Commit b058df7

Browse files
authored
Merge pull request #5686 from nyalldawson/encoding
[processing] Don't use crappy Qt file picker dialog
2 parents d62b4e9 + b97c6a8 commit b058df7

File tree

6 files changed

+234
-39
lines changed

6 files changed

+234
-39
lines changed

python/gui/qgsencodingfiledialog.sip

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,46 @@ Returns true if the user clicked 'Cancel All'
4949

5050
};
5151

52+
class QgsEncodingSelectionDialog: QDialog
53+
{
54+
%Docstring
55+
A dialog which presents the user with a choice of file encodings.
56+
.. versionadded:: 3.0
57+
*
58+
%End
59+
60+
%TypeHeaderCode
61+
#include "qgsencodingfiledialog.h"
62+
%End
63+
public:
64+
65+
QgsEncodingSelectionDialog( QWidget *parent /TransferThis/ = 0,
66+
const QString &caption = QString(), const QString &encoding = QString(),
67+
Qt::WindowFlags flags = Qt::WindowFlags() );
68+
%Docstring
69+
Constructor for QgsEncodingSelectionDialog.
70+
71+
If ``caption`` is set, it will be used as the caption within the dialog.
72+
73+
The ``encoding`` argument can be used to specify the encoding initially selected in the dialog.
74+
%End
75+
76+
QString encoding() const;
77+
%Docstring
78+
Returns the encoding selected within the dialog.
79+
.. seealso:: setEncoding()
80+
:rtype: str
81+
%End
82+
83+
void setEncoding( const QString &encoding );
84+
%Docstring
85+
Sets the ``encoding`` selected within the dialog.
86+
see encoding()
87+
%End
88+
89+
};
90+
91+
5292
/************************************************************************
5393
* This file has been generated automatically from *
5494
* *

python/plugins/processing/gui/DestinationSelectionPanel.py

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,13 @@
3333
from qgis.PyQt.QtCore import QCoreApplication, QDir
3434
from qgis.PyQt.QtWidgets import QDialog, QMenu, QAction, QFileDialog
3535
from qgis.PyQt.QtGui import QCursor
36-
from qgis.gui import QgsEncodingFileDialog, QgsExpressionBuilderDialog
36+
from qgis.gui import QgsEncodingSelectionDialog
3737
from qgis.core import (QgsDataSourceUri,
3838
QgsCredentials,
3939
QgsExpression,
4040
QgsSettings,
4141
QgsProcessingParameterFeatureSink,
42+
QgsProcessingParameterRasterDestination,
4243
QgsProcessingOutputLayerDefinition,
4344
QgsProcessingParameterDefinition,
4445
QgsProcessingParameterFileDestination,
@@ -138,6 +139,11 @@ def selectOutput(self):
138139
actionSaveToPostGIS.setEnabled(bool(names))
139140
popupMenu.addAction(actionSaveToPostGIS)
140141

142+
actionSetEncoding = QAction(
143+
self.tr('Change file encoding ({})...').format(self.encoding), self.btnSelect)
144+
actionSetEncoding.triggered.connect(self.selectEncoding)
145+
popupMenu.addAction(actionSetEncoding)
146+
141147
popupMenu.exec_(QCursor.pos())
142148

143149
def saveToTemporary(self):
@@ -172,71 +178,78 @@ def saveToPostGIS(self):
172178
self.leText.setText("postgis:" + uri.uri())
173179

174180
def saveToSpatialite(self):
175-
fileFilter = self.tr('SpatiaLite files (*.sqlite)', 'OutputFile')
181+
file_filter = self.tr('SpatiaLite files (*.sqlite)', 'OutputFile')
176182

177183
settings = QgsSettings()
178184
if settings.contains('/Processing/LastOutputPath'):
179185
path = settings.value('/Processing/LastOutputPath')
180186
else:
181187
path = ProcessingConfig.getSetting(ProcessingConfig.OUTPUT_FOLDER)
182188

183-
fileDialog = QgsEncodingFileDialog(
184-
self, self.tr('Save SpatiaLite'), path, fileFilter, self.encoding)
185-
fileDialog.setFileMode(QFileDialog.AnyFile)
186-
fileDialog.setAcceptMode(QFileDialog.AcceptSave)
187-
fileDialog.setOption(QFileDialog.DontConfirmOverwrite, True)
189+
filename, filter = QFileDialog.getSaveFileName(self, self.tr("Save file"), path,
190+
file_filter, options=QFileDialog.DontConfirmOverwrite)
188191

189-
if fileDialog.exec_() == QDialog.Accepted:
192+
if filename is not None:
190193
self.use_temporary = False
191-
files = fileDialog.selectedFiles()
192-
self.encoding = str(fileDialog.encoding())
193-
fileName = str(files[0])
194-
selectedFileFilter = str(fileDialog.selectedNameFilter())
195-
if not fileName.lower().endswith(
196-
tuple(re.findall("\\*(\\.[a-z]{1,10})", fileFilter))):
197-
ext = re.search("\\*(\\.[a-z]{1,10})", selectedFileFilter)
198-
if ext:
199-
fileName += ext.group(1)
194+
if not filename.lower().endswith('.sqlite'):
195+
filename += '.sqlite'
200196
settings.setValue('/Processing/LastOutputPath',
201-
os.path.dirname(fileName))
202-
settings.setValue('/Processing/encoding', self.encoding)
197+
os.path.dirname(filename))
203198

204199
uri = QgsDataSourceUri()
205-
uri.setDatabase(fileName)
200+
uri.setDatabase(filename)
206201
uri.setDataSource('', self.parameter.name().lower(),
207202
'the_geom' if isinstance(self.parameter, QgsProcessingParameterFeatureSink) and self.parameter.hasGeometry() else None)
208203
self.leText.setText("spatialite:" + uri.uri())
209204

210205
def selectFile(self):
211-
fileFilter = getFileFilter(self.parameter)
212-
206+
file_filter = getFileFilter(self.parameter)
213207
settings = QgsSettings()
208+
if isinstance(self.parameter, QgsProcessingParameterFeatureSink):
209+
last_ext_path = '/Processing/LastVectorOutputExt'
210+
last_ext = settings.value(last_ext_path, '.gpkg')
211+
elif isinstance(self.parameter, QgsProcessingParameterRasterDestination):
212+
last_ext_path = '/Processing/LastRasterOutputExt'
213+
last_ext = settings.value(last_ext_path, '.tif')
214+
else:
215+
last_ext_path = None
216+
last_ext = None
217+
218+
# get default filter
219+
filters = file_filter.split(';;')
220+
try:
221+
last_filter = [f for f in filters if '*{}'.format(last_ext) in f.lower()][0]
222+
except:
223+
last_filter = None
224+
214225
if settings.contains('/Processing/LastOutputPath'):
215226
path = settings.value('/Processing/LastOutputPath')
216227
else:
217228
path = ProcessingConfig.getSetting(ProcessingConfig.OUTPUT_FOLDER)
218229

219-
fileDialog = QgsEncodingFileDialog(
220-
self, self.tr('Save file'), path, fileFilter, self.encoding)
221-
fileDialog.setFileMode(QFileDialog.AnyFile)
222-
fileDialog.setAcceptMode(QFileDialog.AcceptSave)
223-
fileDialog.setOption(QFileDialog.DontConfirmOverwrite, False)
224-
225-
if fileDialog.exec_() == QDialog.Accepted:
230+
filename, filter = QFileDialog.getSaveFileName(self, self.tr("Save file"), path,
231+
file_filter, last_filter)
232+
if filename:
226233
self.use_temporary = False
227-
files = fileDialog.selectedFiles()
228-
self.encoding = str(fileDialog.encoding())
229-
fileName = str(files[0])
230-
selectedFileFilter = str(fileDialog.selectedNameFilter())
231-
if not fileName.lower().endswith(
232-
tuple(re.findall("\\*(\\.[a-z]{1,10})", fileFilter))):
233-
ext = re.search("\\*(\\.[a-z]{1,10})", selectedFileFilter)
234+
if not filename.lower().endswith(
235+
tuple(re.findall("\\*(\\.[a-z]{1,10})", file_filter))):
236+
ext = re.search("\\*(\\.[a-z]{1,10})", filter)
234237
if ext:
235-
fileName += ext.group(1)
236-
self.leText.setText(fileName)
238+
filename += ext.group(1)
239+
self.leText.setText(filename)
237240
settings.setValue('/Processing/LastOutputPath',
238-
os.path.dirname(fileName))
241+
os.path.dirname(filename))
242+
if not last_ext_path is None:
243+
settings.setValue(last_ext_path, os.path.splitext(filename)[1].lower())
244+
245+
def selectEncoding(self):
246+
dialog = QgsEncodingSelectionDialog(
247+
self, self.tr('File encoding'), self.encoding)
248+
if dialog.exec_() == QDialog.Accepted:
249+
self.encoding = dialog.encoding()
250+
settings = QgsSettings()
239251
settings.setValue('/Processing/encoding', self.encoding)
252+
dialog.deleteLater()
240253

241254
def selectDirectory(self):
242255
lastDir = self.leText.text()

src/gui/qgsencodingfiledialog.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <QLabel>
2525
#include <QLayout>
2626
#include <QTextCodec>
27+
#include <QDialogButtonBox>
2728

2829
QgsEncodingFileDialog::QgsEncodingFileDialog( QWidget *parent,
2930
const QString &caption, const QString &directory,
@@ -102,3 +103,64 @@ void QgsEncodingFileDialog::pbnCancelAll_clicked()
102103
// Now, continue as the user clicked the cancel button
103104
reject();
104105
}
106+
107+
QgsEncodingSelectionDialog::QgsEncodingSelectionDialog( QWidget *parent, const QString &caption, const QString &encoding, Qt::WindowFlags flags )
108+
: QDialog( parent, flags )
109+
{
110+
QString c = caption;
111+
if ( c.isEmpty() )
112+
c = tr( "Encoding" );
113+
114+
setWindowTitle( tr( "Select Encoding" ) );
115+
116+
QVBoxLayout *layout = new QVBoxLayout();
117+
layout->setMargin( 6 );
118+
119+
mEncodingComboBox = new QComboBox( this );
120+
QLabel *l = new QLabel( c, this );
121+
122+
QHBoxLayout *hLayout = new QHBoxLayout();
123+
hLayout->addWidget( l );
124+
hLayout->addWidget( mEncodingComboBox, 1 );
125+
layout->addLayout( hLayout );
126+
127+
QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
128+
Qt::Horizontal, this );
129+
buttonBox->button( QDialogButtonBox::Ok )->setDefault( true );
130+
connect( buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
131+
connect( buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
132+
layout->addWidget( buttonBox );
133+
setLayout( layout );
134+
135+
mEncodingComboBox->addItem( tr( "System" ) );
136+
mEncodingComboBox->addItems( QgsVectorDataProvider::availableEncodings() );
137+
138+
// Use default encoding if none supplied
139+
QString enc = encoding;
140+
if ( encoding.isEmpty() )
141+
{
142+
QgsSettings settings;
143+
enc = settings.value( QStringLiteral( "UI/encoding" ), "System" ).toString();
144+
}
145+
146+
setEncoding( enc );
147+
}
148+
149+
QString QgsEncodingSelectionDialog::encoding() const
150+
{
151+
return mEncodingComboBox->currentText();
152+
}
153+
154+
void QgsEncodingSelectionDialog::setEncoding( const QString &encoding )
155+
{
156+
// The specified decoding is added if not existing alread, and then set current.
157+
// This should select it.
158+
159+
int encindex = mEncodingComboBox->findText( encoding );
160+
if ( encindex < 0 )
161+
{
162+
mEncodingComboBox->insertItem( 0, encoding );
163+
encindex = 0;
164+
}
165+
mEncodingComboBox->setCurrentIndex( encindex );
166+
}

src/gui/qgsencodingfiledialog.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,45 @@ class GUI_EXPORT QgsEncodingFileDialog: public QFileDialog
6161
bool mCancelAll;
6262
};
6363

64+
/**
65+
* \ingroup gui
66+
* A dialog which presents the user with a choice of file encodings.
67+
* \since QGIS 3.0
68+
**/
69+
class GUI_EXPORT QgsEncodingSelectionDialog: public QDialog
70+
{
71+
Q_OBJECT
72+
73+
public:
74+
75+
/**
76+
* Constructor for QgsEncodingSelectionDialog.
77+
*
78+
* If \a caption is set, it will be used as the caption within the dialog.
79+
*
80+
* The \a encoding argument can be used to specify the encoding initially selected in the dialog.
81+
*/
82+
QgsEncodingSelectionDialog( QWidget *parent SIP_TRANSFERTHIS = nullptr,
83+
const QString &caption = QString(), const QString &encoding = QString(),
84+
Qt::WindowFlags flags = Qt::WindowFlags() );
85+
86+
/**
87+
* Returns the encoding selected within the dialog.
88+
* \see setEncoding()
89+
*/
90+
QString encoding() const;
91+
92+
/**
93+
* Sets the \a encoding selected within the dialog.
94+
* see encoding()
95+
*/
96+
void setEncoding( const QString &encoding );
97+
98+
private:
99+
100+
QComboBox *mEncodingComboBox = nullptr;
101+
102+
};
103+
104+
64105
#endif

tests/src/python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ ADD_PYTHON_TEST(PyQgsDelimitedTextProvider test_qgsdelimitedtextprovider.py)
4848
ADD_PYTHON_TEST(PyQgsDistanceArea test_qgsdistancearea.py)
4949
ADD_PYTHON_TEST(PyQgsEditWidgets test_qgseditwidgets.py)
5050
ADD_PYTHON_TEST(PyQgsEllipsoidUtils test_qgsellipsoidutils.py)
51+
ADD_PYTHON_TEST(PyQgsEncodingSelectionDialog test_qgsencodingselectiondialog.py)
5152
ADD_PYTHON_TEST(PyQgsExpression test_qgsexpression.py)
5253
ADD_PYTHON_TEST(PyQgsExpressionBuilderWidget test_qgsexpressionbuilderwidget.py)
5354
ADD_PYTHON_TEST(PyQgsExpressionLineEdit test_qgsexpressionlineedit.py)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# -*- coding: utf-8 -*-
2+
"""QGIS Unit tests for QgsEncodingSelectionDialog
3+
4+
.. note:: This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation; either version 2 of the License, or
7+
(at your option) any later version.
8+
"""
9+
__author__ = 'Nyall Dawson'
10+
__date__ = '21/11/2017'
11+
__copyright__ = 'Copyright 2017, The QGIS Project'
12+
# This will get replaced with a git SHA1 when you do a git archive
13+
__revision__ = '$Format:%H$'
14+
15+
import qgis # NOQA
16+
17+
from qgis.gui import QgsEncodingSelectionDialog
18+
19+
from qgis.testing import start_app, unittest
20+
21+
start_app()
22+
23+
24+
class TestQgsEncodingSelectionDialog(unittest.TestCase):
25+
26+
def testGettersSetters(self):
27+
""" test dialog getters/setters """
28+
dlg = qgis.gui.QgsEncodingSelectionDialog(encoding='UTF-16')
29+
self.assertEqual(dlg.encoding(), 'UTF-16')
30+
dlg.setEncoding('UTF-8')
31+
self.assertEqual(dlg.encoding(), 'UTF-8')
32+
# custom encoding option
33+
dlg.setEncoding('trisolarian')
34+
self.assertEqual(dlg.encoding(), 'trisolarian')
35+
36+
37+
if __name__ == '__main__':
38+
unittest.main()

0 commit comments

Comments
 (0)