QGIS/python/plugins/processing/gui/DestinationSelectionPanel.py
Nyall Dawson 6b55300fbc If a feature sink parameter is optional and not set, don't create the sink
This adds a lot of flexibility to algorithms, as it makes output
sinks truely optional. For instance, the various "Extract by..."
algorithms could add a new optional sink for features which
'fail' the extraction criteria. This effectively allows these
algorithms to become feature 'routers', directing features onto
other parts of a model depending on whether they pass or fail
the test.

But in this situation we don't always care about these failing
features, and we don't want to force them to always be fetched
from the provider. By making the outputs truely optional,
the algorithm can tweak its logic to either fetch all features
and send them to the correct output, or only fetch
matching features from the provider in the first place (a big
speed boost).
2017-06-09 14:22:29 +10:00

274 lines
12 KiB
Python

# -*- coding: utf-8 -*-
"""
***************************************************************************
OutputSelectionPanel.py
---------------------
Date : August 2012
Copyright : (C) 2012 by Victor Olaya
Email : volayaf 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. *
* *
***************************************************************************
"""
from builtins import str
__author__ = 'Victor Olaya'
__date__ = 'August 2012'
__copyright__ = '(C) 2012, Victor Olaya'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import re
import os
from qgis.PyQt import uic
from qgis.PyQt.QtCore import QCoreApplication
from qgis.PyQt.QtWidgets import QDialog, QMenu, QAction, QFileDialog
from qgis.PyQt.QtGui import QCursor
from qgis.gui import QgsEncodingFileDialog, QgsExpressionBuilderDialog
from qgis.core import (QgsDataSourceUri,
QgsCredentials,
QgsExpression,
QgsSettings,
QgsProcessingParameterFeatureSink,
QgsProcessingOutputLayerDefinition,
QgsProcessingParameterDefinition)
from processing.core.ProcessingConfig import ProcessingConfig
from processing.core.outputs import OutputVector
from processing.core.outputs import OutputDirectory
from processing.tools.dataobjects import createContext
from processing.gui.PostgisTableSelector import PostgisTableSelector
from processing.gui.ParameterGuiUtils import getFileFilter
pluginPath = os.path.split(os.path.dirname(__file__))[0]
WIDGET, BASE = uic.loadUiType(
os.path.join(pluginPath, 'ui', 'widgetBaseSelector.ui'))
class DestinationSelectionPanel(BASE, WIDGET):
SAVE_TO_TEMP_FILE = QCoreApplication.translate(
'DestinationSelectionPanel', '[Save to temporary file]')
SAVE_TO_TEMP_LAYER = QCoreApplication.translate(
'DestinationSelectionPanel', '[Create temporary layer]')
SKIP_OUTPUT = QCoreApplication.translate(
'DestinationSelectionPanel', '[Skip output]')
def __init__(self, parameter, alg):
super(DestinationSelectionPanel, self).__init__(None)
self.setupUi(self)
self.parameter = parameter
self.alg = alg
settings = QgsSettings()
self.encoding = settings.value('/Processing/encoding', 'System')
self.use_temporary = True
if hasattr(self.leText, 'setPlaceholderText'):
if parameter.flags() & QgsProcessingParameterDefinition.FlagOptional and parameter.defaultValue() is None:
self.leText.setPlaceholderText(self.SKIP_OUTPUT)
self.use_temporary = False
elif isinstance(self.parameter, QgsProcessingParameterFeatureSink) \
and alg.provider().supportsNonFileBasedOutput():
# use memory layers for temporary files if supported
self.leText.setPlaceholderText(self.SAVE_TO_TEMP_LAYER)
else:
self.leText.setPlaceholderText(self.SAVE_TO_TEMP_FILE)
self.btnSelect.clicked.connect(self.selectOutput)
self.leText.textEdited.connect(self.textChanged)
def textChanged(self):
self.use_temporary = False
def skipOutput(self):
self.leText.setPlaceholderText(self.SKIP_OUTPUT)
self.use_temporary = False
def selectOutput(self):
if isinstance(self.parameter, OutputDirectory):
self.selectDirectory()
else:
popupMenu = QMenu()
if isinstance(self.parameter, QgsProcessingParameterFeatureSink) \
and self.parameter.flags() & QgsProcessingParameterDefinition.FlagOptional:
actionSkipOutput = QAction(
self.tr('Skip output'), self.btnSelect)
actionSkipOutput.triggered.connect(self.skipOutput)
popupMenu.addAction(actionSkipOutput)
if isinstance(self.parameter, QgsProcessingParameterFeatureSink) \
and self.alg.provider().supportsNonFileBasedOutput():
# use memory layers for temporary layers if supported
actionSaveToTemp = QAction(
self.tr('Create temporary layer'), self.btnSelect)
else:
actionSaveToTemp = QAction(
self.tr('Save to a temporary file'), self.btnSelect)
actionSaveToTemp.triggered.connect(self.saveToTemporary)
popupMenu.addAction(actionSaveToTemp)
actionSaveToFile = QAction(
self.tr('Save to file...'), self.btnSelect)
actionSaveToFile.triggered.connect(self.selectFile)
popupMenu.addAction(actionSaveToFile)
actionShowExpressionsBuilder = QAction(
self.tr('Use expression...'), self.btnSelect)
actionShowExpressionsBuilder.triggered.connect(self.showExpressionsBuilder)
popupMenu.addAction(actionShowExpressionsBuilder)
if isinstance(self.parameter, QgsProcessingParameterFeatureSink) \
and self.alg.provider().supportsNonFileBasedOutput():
actionSaveToSpatialite = QAction(
self.tr('Save to Spatialite table...'), self.btnSelect)
actionSaveToSpatialite.triggered.connect(self.saveToSpatialite)
popupMenu.addAction(actionSaveToSpatialite)
actionSaveToPostGIS = QAction(
self.tr('Save to PostGIS table...'), self.btnSelect)
actionSaveToPostGIS.triggered.connect(self.saveToPostGIS)
settings = QgsSettings()
settings.beginGroup('/PostgreSQL/connections/')
names = settings.childGroups()
settings.endGroup()
actionSaveToPostGIS.setEnabled(bool(names))
popupMenu.addAction(actionSaveToPostGIS)
popupMenu.exec_(QCursor.pos())
def showExpressionsBuilder(self):
context = self.alg.createExpressionContext({}, createContext())
dlg = QgsExpressionBuilderDialog(None, self.leText.text(), self, 'generic',
context)
dlg.setWindowTitle(self.tr('Expression based output'))
if dlg.exec_() == QDialog.Accepted:
expression = QgsExpression(dlg.expressionText())
self.leText.setText(expression.evaluate(context))
def saveToTemporary(self):
if isinstance(self.parameter, QgsProcessingParameterFeatureSink) and self.alg.provider().supportsNonFileBasedOutput():
self.leText.setPlaceholderText(self.SAVE_TO_TEMP_LAYER)
else:
self.leText.setPlaceholderText(self.SAVE_TO_TEMP_FILE)
self.leText.setText('')
self.use_temporary = True
def saveToPostGIS(self):
dlg = PostgisTableSelector(self, self.parameter.name().lower())
dlg.exec_()
if dlg.connection:
self.use_temporary = False
settings = QgsSettings()
mySettings = '/PostgreSQL/connections/' + dlg.connection
dbname = settings.value(mySettings + '/database')
user = settings.value(mySettings + '/username')
host = settings.value(mySettings + '/host')
port = settings.value(mySettings + '/port')
password = settings.value(mySettings + '/password')
uri = QgsDataSourceUri()
uri.setConnection(host, str(port), dbname, user, password)
uri.setDataSource(dlg.schema, dlg.table,
"the_geom" if isinstance(self.parameter, QgsProcessingParameterFeatureSink) and self.parameter.hasGeometry() else None)
connInfo = uri.connectionInfo()
(success, user, passwd) = QgsCredentials.instance().get(connInfo, None, None)
if success:
QgsCredentials.instance().put(connInfo, user, passwd)
self.leText.setText("postgis:" + uri.uri())
def saveToSpatialite(self):
fileFilter = 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)
if fileDialog.exec_() == QDialog.Accepted:
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)
settings.setValue('/Processing/LastOutputPath',
os.path.dirname(fileName))
settings.setValue('/Processing/encoding', self.encoding)
uri = QgsDataSourceUri()
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)
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 file'), path, fileFilter, self.encoding)
fileDialog.setFileMode(QFileDialog.AnyFile)
fileDialog.setAcceptMode(QFileDialog.AcceptSave)
fileDialog.setOption(QFileDialog.DontConfirmOverwrite, False)
if fileDialog.exec_() == QDialog.Accepted:
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)
self.leText.setText(fileName)
settings.setValue('/Processing/LastOutputPath',
os.path.dirname(fileName))
settings.setValue('/Processing/encoding', self.encoding)
def selectDirectory(self):
lastDir = ''
dirName = QFileDialog.getExistingDirectory(self, self.tr('Select directory'),
lastDir, QFileDialog.ShowDirsOnly)
self.leText.setText(dirName)
def getValue(self):
key = None
if self.use_temporary and isinstance(self.parameter, QgsProcessingParameterFeatureSink):
key = 'memory:'
else:
key = self.leText.text()
if not key and self.parameter.flags() & QgsProcessingParameterDefinition.FlagOptional:
return None
value = QgsProcessingOutputLayerDefinition(key)
value.createOptions = {'fileEncoding': self.encoding}
return value