32#include <QInputDialog>
39#include "moc_qgsprocessingoutputdestinationwidget.cpp"
41using namespace Qt::StringLiterals;
45QgsProcessingLayerOutputDestinationWidget::QgsProcessingLayerOutputDestinationWidget(
const QgsProcessingDestinationParameter *param,
bool defaultSelection, QWidget *parent )
48 , mDefaultSelection( defaultSelection )
50 Q_ASSERT( mParameter );
54 mActionTemporaryOutputIcon =
new QAction(
QgsApplication::getThemeIcon( u
"/mActionCreateMemory.svg"_s ), tr(
"Temporary Output" ),
this );
56 leText->setClearButtonEnabled(
false );
60 leText->addAction( mActionTemporaryOutputIcon, QLineEdit::LeadingPosition );
63 connect( leText, &QLineEdit::textChanged,
this, &QgsProcessingLayerOutputDestinationWidget::textChanged );
65 mMenu =
new QMenu(
this );
66 connect( mMenu, &QMenu::aboutToShow,
this, &QgsProcessingLayerOutputDestinationWidget::menuAboutToShow );
67 mSelectButton->setMenu( mMenu );
68 mSelectButton->setPopupMode( QToolButton::InstantPopup );
72 settings.
setValue( u
"/Processing/encoding"_s, mEncoding );
74 if ( !mParameter->defaultValueForGui().isValid() )
78 setValue( QVariant() );
84 setValue( mParameter->defaultValueForGui() );
87 setToolTip( mParameter->toolTip() );
89 setAcceptDrops(
true );
90 leText->setAcceptDrops(
false );
93bool QgsProcessingLayerOutputDestinationWidget::outputIsSkipped()
const
95 return leText->text().isEmpty() && !mUseTemporary;
98void QgsProcessingLayerOutputDestinationWidget::setValue(
const QVariant &value )
100 const bool prevSkip = outputIsSkipped();
101 mUseRemapping =
false;
102 if ( !value.isValid() || ( value.userType() == QMetaType::Type::QString && couldBeTemporaryLayerName( value.toString() ) ) )
107 emit destinationChanged();
111 saveToTemporary( value.toString() );
119 if ( !consideredEqualTemporaryOutputValues( mPreviousValueString, variantToString( value ) ) )
121 emit destinationChanged();
124 else if ( value.userType() == qMetaTypeId<QgsProcessingOutputLayerDefinition>() )
135 emit skipOutputChanged(
false );
139 mEncoding = def.
createOptions.value( u
"fileEncoding"_s ).toString();
143 leText->setText( value.toString() );
145 emit skipOutputChanged(
false );
149 mPreviousValueString = variantToString( value );
152QVariant QgsProcessingLayerOutputDestinationWidget::value()
const
160 else if ( mUseTemporary && !mDefaultSelection )
166 key = leText->text();
176 && !key.startsWith(
"memory:"_L1 )
177 && !key.startsWith(
"ogr:"_L1 )
178 && !key.startsWith(
"postgres:"_L1 )
179 && !key.startsWith(
"postgis:"_L1 )
183 QString folder = QFileInfo( key ).path();
187 QString defaultFolder = settings.
value( u
"/Processing/Configuration/OUTPUTS_FOLDER"_s, u
"%1/processing"_s.arg( QDir::homePath() ) ).toString();
188 QDir destDir( defaultFolder );
189 if ( !destDir.exists() && !QDir().mkpath( defaultFolder ) )
191 QgsDebugError( u
"Can't create output folder '%1'"_s.arg( defaultFolder ) );
193 key = destDir.filePath( key );
203 value.createOptions.insert( u
"fileEncoding"_s, mEncoding );
205 value.setRemappingDefinition( mRemapDefinition );
210 const QString memoryLayerName = memoryProviderLayerName( leText->text() );
211 value.destinationName = memoryLayerName.isEmpty() ? leText->text() : memoryLayerName;
215 if ( !mFormat.isEmpty() )
216 value.setFormat( mFormat );
233 mParametersGenerator = generator;
236void QgsProcessingLayerOutputDestinationWidget::addOpenAfterRunningOption()
238 Q_ASSERT( mOpenAfterRunningCheck ==
nullptr );
239 mOpenAfterRunningCheck =
new QCheckBox( tr(
"Open output file after running algorithm" ) );
240 mOpenAfterRunningCheck->setChecked( !outputIsSkipped() );
241 mOpenAfterRunningCheck->setEnabled( !outputIsSkipped() );
242 gridLayout->addWidget( mOpenAfterRunningCheck, 1, 0, 1, 2 );
244 connect(
this, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged,
this, [
this](
bool skipped ) {
245 bool enabled = !skipped;
246 mOpenAfterRunningCheck->setEnabled( enabled );
247 mOpenAfterRunningCheck->setChecked( enabled );
251bool QgsProcessingLayerOutputDestinationWidget::openAfterRunning()
const
253 return mOpenAfterRunningCheck && mOpenAfterRunningCheck->isChecked();
256void QgsProcessingLayerOutputDestinationWidget::menuAboutToShow()
260 if ( !mDefaultSelection )
264 QAction *actionSkipOutput =
new QAction( tr(
"Skip Output" ),
this );
265 connect( actionSkipOutput, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::skipOutput );
266 mMenu->addAction( actionSkipOutput );
269 QAction *actionSaveToTemp =
nullptr;
273 actionSaveToTemp =
new QAction( tr(
"Create Temporary Layer" ),
this );
277 actionSaveToTemp =
new QAction( tr(
"Save to a Temporary Directory" ),
this );
281 actionSaveToTemp =
new QAction( tr(
"Save to a Temporary File" ),
this );
284 connect( actionSaveToTemp, &QAction::triggered,
this, [
this]() { saveToTemporary(); } );
285 mMenu->addAction( actionSaveToTemp );
288 QAction *actionSaveToFile =
nullptr;
291 actionSaveToFile =
new QAction( tr(
"Save to Directory…" ),
this );
292 connect( actionSaveToFile, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::selectDirectory );
296 actionSaveToFile =
new QAction( tr(
"Save to File…" ),
this );
297 connect( actionSaveToFile, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::selectFile );
299 mMenu->addAction( actionSaveToFile );
303 QAction *actionSaveToGpkg =
new QAction( tr(
"Save to GeoPackage…" ),
this );
304 connect( actionSaveToGpkg, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::saveToGeopackage );
305 mMenu->addAction( actionSaveToGpkg );
307 QAction *actionSaveToDatabase =
new QAction( tr(
"Save to Database Table…" ),
this );
308 connect( actionSaveToDatabase, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::saveToDatabase );
309 mMenu->addAction( actionSaveToDatabase );
311 if ( mParameter->algorithm() && qgis::down_cast<const QgsProcessingParameterFeatureSink *>( mParameter )->supportsAppend() )
313 mMenu->addSeparator();
314 QAction *actionAppendToLayer =
new QAction( tr(
"Append to Layer…" ),
this );
315 connect( actionAppendToLayer, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::appendToLayer );
316 mMenu->addAction( actionAppendToLayer );
319 QAction *editMappingAction =
new QAction( tr(
"Edit Field Mapping…" ),
this );
320 connect( editMappingAction, &QAction::triggered,
this, [
this] {
321 setAppendDestination( value().value<QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), mRemapDefinition.destinationFields() );
323 mMenu->addAction( editMappingAction );
330 mMenu->addSeparator();
331 QAction *actionSetEncoding =
new QAction( tr(
"Change File Encoding (%1)…" ).arg( mEncoding ),
this );
332 connect( actionSetEncoding, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::selectEncoding );
333 mMenu->addAction( actionSetEncoding );
337void QgsProcessingLayerOutputDestinationWidget::skipOutput()
340 leText->setPlaceholderText( tr(
"[Skip output]" ) );
341 leText->removeAction( mActionTemporaryOutputIcon );
343 mPreviousValueString.clear();
344 mUseTemporary =
false;
345 mUseRemapping =
false;
347 emit skipOutputChanged(
true );
350void QgsProcessingLayerOutputDestinationWidget::setupPlaceholderText()
354 leText->setPlaceholderText( tr(
"[Create temporary layer]" ) );
358 leText->setPlaceholderText( tr(
"[Save to temporary folder]" ) );
362 leText->setPlaceholderText( tr(
"[Save to temporary file]" ) );
366void QgsProcessingLayerOutputDestinationWidget::saveToTemporary(
const QString &name )
368 const bool prevSkip = outputIsSkipped();
371 if ( prevSkip && name.isEmpty() )
373 leText->addAction( mActionTemporaryOutputIcon, QLineEdit::LeadingPosition );
376 setupPlaceholderText();
378 if ( name.isEmpty() )
381 if ( mUseTemporary && leText->text() == name )
384 leText->setText( name );
386 mUseTemporary =
true;
387 mUseRemapping =
false;
389 emit skipOutputChanged(
false );
392void QgsProcessingLayerOutputDestinationWidget::selectDirectory()
394 QString lastDir = leText->text();
396 if ( lastDir.isEmpty() )
397 lastDir = settings.
value( u
"/Processing/LastOutputPath"_s, QDir::homePath() ).toString();
399 const QString dirName = QFileDialog::getExistingDirectory(
this, tr(
"Select Directory" ), lastDir, QFileDialog::Options() );
400 if ( !dirName.isEmpty() )
402 leText->setText( QDir::toNativeSeparators( dirName ) );
403 settings.
setValue( u
"/Processing/LastOutputPath"_s, dirName );
404 mUseTemporary =
false;
405 mUseRemapping =
false;
406 emit skipOutputChanged(
false );
410void QgsProcessingLayerOutputDestinationWidget::selectFile()
412 const QString fileFilter = mParameter->createFileFilter();
418 QString lastFormatPath;
422 lastExtPath = u
"/Processing/LastVectorOutputExt"_s;
423 lastExt = settings.
value( lastExtPath, u
".%1"_s.arg( mParameter->defaultFileExtension() ) ).toString();
429 lastFormatPath = u
"/Processing/LastRasterOutputFormat"_s;
434 lastExtPath = u
"/Processing/LastPointCloudOutputExt"_s;
435 lastExt = settings.
value( lastExtPath, u
".%1"_s.arg( mParameter->defaultFileExtension() ) ).toString();
439 lastExtPath = u
"/Processing/LastVectorTileOutputExt"_s;
440 lastExt = settings.
value( lastExtPath, u
".%1"_s.arg( mParameter->defaultFileExtension() ) ).toString();
444 const QStringList filters = fileFilter.split( u
";;"_s );
446 for (
const QString &f : filters )
448 if ( !lastFormat.isEmpty() && f.contains( lastFormat, Qt::CaseInsensitive ) )
453 else if ( !lastExt.isEmpty() && f.contains( u
"*.%1"_s.arg( lastExt ), Qt::CaseInsensitive ) )
461 if ( settings.
contains( u
"/Processing/LastOutputPath"_s ) )
462 path = settings.
value( u
"/Processing/LastOutputPath"_s ).toString();
464 path = settings.
value( u
"/Processing/Configuration/OUTPUTS_FOLDER"_s ).toString();
466 const bool dontConfirmOverwrite = mParameter->metadata().value( u
"widget_wrapper"_s ).toMap().value( u
"dontconfirmoverwrite"_s,
false ).toBool();
469 = QFileDialog::getSaveFileName(
this, tr(
"Save file" ), path, fileFilter, &lastFilter, dontConfirmOverwrite ? QFileDialog::Options( QFileDialog::DontConfirmOverwrite ) : QFileDialog::Options() );
470 if ( !filename.isEmpty() )
472 mUseTemporary =
false;
473 mUseRemapping =
false;
478 Q_ASSERT( formatAndExtensions.size() + 1 == filters.size() );
480 for (
const QString &f : filters )
482 if ( f == lastFilter )
484 mFormat = formatAndExtensions[idxFilter].first;
492 leText->setText( filename );
494 settings.
setValue( u
"/Processing/LastOutputPath"_s, QFileInfo( filename ).path() );
495 if ( !lastFormatPath.isEmpty() && !mFormat.isEmpty() )
496 settings.
setValue( lastFormatPath, mFormat );
497 else if ( !lastExtPath.isEmpty() )
498 settings.
setValue( lastExtPath, QFileInfo( filename ).suffix().toLower() );
500 emit skipOutputChanged(
false );
507void QgsProcessingLayerOutputDestinationWidget::saveToGeopackage()
510 QString lastPath = settings.
value( u
"/Processing/LastOutputPath"_s, QString() ).toString();
511 if ( lastPath.isEmpty() )
512 lastPath = settings.
value( u
"/Processing/Configuration/OUTPUTS_FOLDER"_s, QString() ).toString();
514 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save to GeoPackage" ), lastPath, tr(
"GeoPackage files (*.gpkg);;All files (*.*)" ),
nullptr, QFileDialog::DontConfirmOverwrite );
519 if ( filename.isEmpty() )
522 const QString layerName = QInputDialog::getText(
this, tr(
"Save to GeoPackage" ), tr(
"Layer name" ), QLineEdit::Normal, mParameter->name().toLower() );
523 if ( layerName.isEmpty() )
526 mUseTemporary =
false;
527 mUseRemapping =
false;
531 settings.
setValue( u
"/Processing/LastOutputPath"_s, QFileInfo( filename ).path() );
540 if ( sink->hasGeometry() )
541 geomColumn = u
"geom"_s;
545 leText->setText( u
"ogr:%1"_s.arg( uri.
uri() ) );
547 emit skipOutputChanged(
false );
550void QgsProcessingLayerOutputDestinationWidget::saveToDatabase()
555 widget->
setPanelTitle( tr(
"Save “%1” to Database Table" ).arg( mParameter->description() ) );
558 panel->openPanel( widget );
560 auto changed = [
this, widget] {
561 mUseTemporary =
false;
562 mUseRemapping =
false;
567 if ( sink->hasGeometry() )
568 geomColumn = widget->
dataProviderKey() ==
"oracle"_L1 ? u
"GEOM"_s : u
"geom"_s;
577 leText->setText( u
"ogr:%1"_s.arg( uri.
uri() ) );
586 emit skipOutputChanged(
false );
600void QgsProcessingLayerOutputDestinationWidget::appendToLayer()
605 widget->
setPanelTitle( tr(
"Append \"%1\" to Layer" ).arg( mParameter->description() ) );
607 panel->openPanel( widget );
611 if ( widget->
uri().
uri.isEmpty() )
612 setValue( QVariant() );
616 auto dest = std::make_unique<QgsVectorLayer>( widget->
uri().
uri, QString(), widget->
uri().
providerKey );
618 setAppendDestination( widget->
uri().
uri, dest->fields() );
627void QgsProcessingLayerOutputDestinationWidget::setAppendDestination(
const QString &uri,
const QgsFields &destFields )
631 if ( mParametersGenerator )
632 props = mParametersGenerator->createProcessingParameters();
633 props.insert( mParameter->name(), uri );
642 widget->
setPanelTitle( tr(
"Append \"%1\" to Layer" ).arg( mParameter->description() ) );
643 if ( !mRemapDefinition.fieldMap().isEmpty() )
646 panel->openPanel( widget );
661void QgsProcessingLayerOutputDestinationWidget::selectEncoding()
669 settings.
setValue( u
"/Processing/encoding"_s, mEncoding );
671 emit destinationChanged();
675void QgsProcessingLayerOutputDestinationWidget::textChanged(
const QString &text )
678 const bool prevSkip = !mUseTemporary && mPreviousValueString.isEmpty();
681 setupPlaceholderText();
682 emit skipOutputChanged(
false );
685 mUseRemapping =
false;
687 if ( couldBeTemporaryLayerName( text ) || text ==
"memory:"_L1 )
689 leText->addAction( mActionTemporaryOutputIcon, QLineEdit::LeadingPosition );
690 mUseTemporary =
true;
694 leText->removeAction( mActionTemporaryOutputIcon );
695 mUseTemporary =
false;
699 if ( mPreviousValueString != text )
701 emit destinationChanged();
704 mPreviousValueString = text;
708QString QgsProcessingLayerOutputDestinationWidget::mimeDataToPath(
const QMimeData *data )
716 && u.layerType ==
"vector"_L1
717 && u.providerKey ==
"ogr"_L1 )
722 && u.layerType ==
"raster"_L1
723 && u.providerKey ==
"gdal"_L1 )
728 && u.layerType ==
"pointcloud"_L1
729 && ( u.providerKey ==
"ept"_L1 || u.providerKey ==
"pdal"_L1 ) )
734 else if ( ( mParameter->type() == QgsProcessingParameterMeshDestination::typeName()
736 && u.layerType ==
"mesh"_L1 && u.providerKey ==
"mdal"_L1 )
745 if ( !uriList.isEmpty() )
749 QStringList rawPaths;
750 if ( data->hasUrls() )
752 const QList<QUrl> urls = data->urls();
753 rawPaths.reserve( urls.count() );
754 for (
const QUrl &url : urls )
756 const QString local = url.toLocalFile();
757 if ( !rawPaths.contains( local ) )
758 rawPaths.append( local );
761 if ( !data->text().isEmpty() && !rawPaths.contains( data->text() ) )
762 rawPaths.append( data->text() );
764 for (
const QString &path : std::as_const( rawPaths ) )
766 QFileInfo file( path );
779void QgsProcessingLayerOutputDestinationWidget::dragEnterEvent( QDragEnterEvent *event )
781 if ( !( event->possibleActions() & Qt::CopyAction ) )
784 const QString path = mimeDataToPath( event->mimeData() );
785 if ( !path.isEmpty() )
788 event->setDropAction( Qt::CopyAction );
790 leText->setHighlighted(
true );
794void QgsProcessingLayerOutputDestinationWidget::dragLeaveEvent( QDragLeaveEvent *event )
796 QWidget::dragLeaveEvent( event );
797 if ( leText->isHighlighted() )
800 leText->setHighlighted(
false );
804void QgsProcessingLayerOutputDestinationWidget::dropEvent( QDropEvent *event )
806 if ( !( event->possibleActions() & Qt::CopyAction ) )
809 const QString path = mimeDataToPath( event->mimeData() );
810 if ( !path.isEmpty() )
813 setFocus( Qt::MouseFocusReason );
814 event->setDropAction( Qt::CopyAction );
818 leText->setHighlighted(
false );
821QString QgsProcessingLayerOutputDestinationWidget::memoryProviderLayerName(
const QString &value )
const
823 if ( value ==
"memory:"_L1 )
830 if ( hasProviderAndUri && provider ==
"memory"_L1 )
838bool QgsProcessingLayerOutputDestinationWidget::couldBeTemporaryLayerName(
const QString &value )
const
843 if ( value.isEmpty() )
846 if ( value ==
"memory:"_L1 )
853 if ( provider ==
"memory"_L1 )
856 if ( hasProviderAndUri )
859 if ( QFileInfo( value ).isAbsolute() || !QFileInfo( value ).suffix().isEmpty() )
865bool QgsProcessingLayerOutputDestinationWidget::consideredEqualTemporaryOutputValues(
const QString &val1,
const QString &val2 )
const
873QString QgsProcessingLayerOutputDestinationWidget::variantToString(
const QVariant &value )
const
875 if ( value.userType() == qMetaTypeId<QgsProcessingOutputLayerDefinition>() )
881 return value.toString();
@ Available
Properties are available.
@ Optional
Parameter is optional.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
Stores the component parts of a data source URI (e.g.
void setTable(const QString &table)
Sets table to table.
void setGeometryColumn(const QString &geometryColumn)
Sets geometry column name to geometryColumn.
QString uri(bool expandAuthConfig=true) const
Returns the complete URI as a string.
void setDatabase(const QString &database)
Sets the URI database name.
A dialog which presents the user with a choice of file encodings.
Container of fields for a vector layer.
static QString ensureFileNameHasExtension(const QString &fileName, const QStringList &extensions)
Ensures that a fileName ends with an extension from the provided list of extensions.
static QString addExtensionFromFilter(const QString &fileName, const QString &filter)
Ensures that a fileName ends with an extension from the specified filter string.
QList< QgsMimeDataUtils::Uri > UriList
static UriList decodeUriList(const QMimeData *data)
Abstract base class for processing algorithms.
virtual QgsProcessingAlgorithm::VectorProperties sinkProperties(const QString &sink, const QVariantMap ¶meters, QgsProcessingContext &context, const QMap< QString, QgsProcessingAlgorithm::VectorProperties > &sourceProperties) const
Returns the vector properties which will be used for the sink with matching name.
Contains information about the context in which a processing algorithm is executed.
Base class for all parameter definitions which represent file or layer destinations,...
Encapsulates settings relating to a feature sink or output raster layer for a processing algorithm.
QgsProperty sink
Sink/layer definition.
bool useRemapping() const
Returns true if the output uses a remapping definition.
QgsRemappingSinkDefinition remappingDefinition() const
Returns the output remapping definition, if useRemapping() is true.
QString destinationName
Name to use for sink if it's to be loaded into a destination project.
QVariantMap createOptions
Map of optional sink/layer creation options, which are passed to the underlying provider when creatin...
void setRemappingDefinition(const QgsRemappingSinkDefinition &definition)
Sets the remapping definition to use when adding features to the output layer.
A feature sink output for processing algorithms.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
A raster layer destination parameter, for specifying the destination path for a raster layer created ...
QString defaultFileFormat() const
Returns the default file format for destination file paths associated with this parameter.
static QString typeName()
Returns the type name for the parameter class.
virtual QList< QPair< QString, QString > > supportedOutputRasterLayerFormatAndExtensions() const
Returns a list of (format, file extension) supported by this provider.
static QString typeName()
Returns the type name for the parameter class.
static QString typeName()
Returns the type name for the parameter class.
Contains settings which reflect the context in which a Processing parameter widget is shown.
QgsBrowserGuiModel * browserModel() const
Returns the browser model associated with the widget.
An interface for objects which can create sets of parameter values for processing algorithms.
static QString encodeProviderKeyAndUri(const QString &providerKey, const QString &uri)
Encodes a provider key and layer uri to a single string, for use with decodeProviderKeyAndUri().
static QString resolveDefaultEncoding(const QString &defaultEncoding="System")
Returns the default encoding.
static bool decodeProviderKeyAndUri(const QString &string, QString &providerKey, QString &uri)
Decodes a provider key and layer uri from an encoded string, for use with encodeProviderKeyAndUri().
static const QString TEMPORARY_OUTPUT
Constant used to indicate that a Processing algorithm output should be a temporary layer/file.
QVariant staticValue() const
Returns the current static value for the property.
Defines the parameters used to remap features when creating a QgsRemappingProxyFeatureSink.
void setFieldMap(const QMap< QString, QgsProperty > &map)
Sets the field mapping, which defines how to map the values from incoming features to destination fie...
void setDestinationFields(const QgsFields &fields)
Sets the fields for the destination sink.
void setSourceCrs(const QgsCoordinateReferenceSystem &source)
Sets the source crs used for reprojecting incoming features to the sink's destination CRS.
Stores settings for use within QGIS.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
bool contains(const QString &key, QgsSettings::Section section=QgsSettings::NoSection) const
Returns true if there exists a setting called key; returns false otherwise.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
RAII signal blocking class.
#define QgsDebugError(str)
QString uri
Identifier of the data source recognized by its providerKey.
QString providerKey
For "vector" / "raster" type: provider id.
Properties of a vector source or sink used in an algorithm.
QgsCoordinateReferenceSystem crs
Coordinate Reference System.
Qgis::ProcessingPropertyAvailability availability
Availability of the properties. By default properties are not available.