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(
56 tr(
"Temporary Output" ),
60 leText->setClearButtonEnabled(
false );
64 leText->addAction( mActionTemporaryOutputIcon, QLineEdit::LeadingPosition );
67 connect( leText, &QLineEdit::textChanged,
this, &QgsProcessingLayerOutputDestinationWidget::textChanged );
69 mMenu =
new QMenu(
this );
70 connect( mMenu, &QMenu::aboutToShow,
this, &QgsProcessingLayerOutputDestinationWidget::menuAboutToShow );
71 mSelectButton->setMenu( mMenu );
72 mSelectButton->setPopupMode( QToolButton::InstantPopup );
76 settings.
setValue( u
"/Processing/encoding"_s, mEncoding );
78 if ( !mParameter->defaultValueForGui().isValid() )
82 setValue( QVariant() );
88 setValue( mParameter->defaultValueForGui() );
91 setToolTip( mParameter->toolTip() );
93 setAcceptDrops(
true );
94 leText->setAcceptDrops(
false );
97bool QgsProcessingLayerOutputDestinationWidget::outputIsSkipped()
const
99 return leText->text().isEmpty() && !mUseTemporary;
102void QgsProcessingLayerOutputDestinationWidget::setValue(
const QVariant &value )
104 const bool prevSkip = outputIsSkipped();
105 mUseRemapping =
false;
106 if ( !value.isValid() || ( value.userType() == QMetaType::Type::QString && couldBeTemporaryLayerName( value.toString() ) ) )
111 emit destinationChanged();
115 saveToTemporary( value.toString() );
123 if ( !consideredEqualTemporaryOutputValues( mPreviousValueString, variantToString( value ) ) )
125 emit destinationChanged();
128 else if ( value.userType() == qMetaTypeId<QgsProcessingOutputLayerDefinition>() )
139 emit skipOutputChanged(
false );
143 mEncoding = def.
createOptions.value( u
"fileEncoding"_s ).toString();
147 leText->setText( value.toString() );
149 emit skipOutputChanged(
false );
153 mPreviousValueString = variantToString( value );
156QVariant QgsProcessingLayerOutputDestinationWidget::value()
const
164 else if ( mUseTemporary && !mDefaultSelection )
170 key = leText->text();
179 && !key.startsWith(
"memory:"_L1 )
180 && !key.startsWith(
"ogr:"_L1 )
181 && !key.startsWith(
"postgres:"_L1 )
182 && !key.startsWith(
"postgis:"_L1 )
186 QString folder = QFileInfo( key ).path();
190 QString defaultFolder = settings.
value( u
"/Processing/Configuration/OUTPUTS_FOLDER"_s, u
"%1/processing"_s.arg( QDir::homePath() ) ).toString();
191 QDir destDir( defaultFolder );
192 if ( !destDir.exists() && !QDir().mkpath( defaultFolder ) )
194 QgsDebugError( u
"Can't create output folder '%1'"_s.arg( defaultFolder ) );
196 key = destDir.filePath( key );
206 value.createOptions.insert( u
"fileEncoding"_s, mEncoding );
208 value.setRemappingDefinition( mRemapDefinition );
213 const QString memoryLayerName = memoryProviderLayerName( leText->text() );
214 value.destinationName = memoryLayerName.isEmpty() ? leText->text() : memoryLayerName;
218 if ( !mFormat.isEmpty() )
219 value.setFormat( mFormat );
236 mParametersGenerator = generator;
239void QgsProcessingLayerOutputDestinationWidget::addOpenAfterRunningOption()
241 Q_ASSERT( mOpenAfterRunningCheck ==
nullptr );
242 mOpenAfterRunningCheck =
new QCheckBox( tr(
"Open output file after running algorithm" ) );
243 mOpenAfterRunningCheck->setChecked( !outputIsSkipped() );
244 mOpenAfterRunningCheck->setEnabled( !outputIsSkipped() );
245 gridLayout->addWidget( mOpenAfterRunningCheck, 1, 0, 1, 2 );
247 connect(
this, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged,
this, [
this](
bool skipped ) {
248 bool enabled = !skipped;
249 mOpenAfterRunningCheck->setEnabled( enabled );
250 mOpenAfterRunningCheck->setChecked( enabled );
254bool QgsProcessingLayerOutputDestinationWidget::openAfterRunning()
const
256 return mOpenAfterRunningCheck && mOpenAfterRunningCheck->isChecked();
259void QgsProcessingLayerOutputDestinationWidget::menuAboutToShow()
263 if ( !mDefaultSelection )
267 QAction *actionSkipOutput =
new QAction( tr(
"Skip Output" ),
this );
268 connect( actionSkipOutput, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::skipOutput );
269 mMenu->addAction( actionSkipOutput );
272 QAction *actionSaveToTemp =
nullptr;
276 actionSaveToTemp =
new QAction( tr(
"Create Temporary Layer" ),
this );
280 actionSaveToTemp =
new QAction( tr(
"Save to a Temporary Directory" ),
this );
284 actionSaveToTemp =
new QAction( tr(
"Save to a Temporary File" ),
this );
287 connect( actionSaveToTemp, &QAction::triggered,
this, [
this]() { saveToTemporary(); } );
288 mMenu->addAction( actionSaveToTemp );
291 QAction *actionSaveToFile =
nullptr;
294 actionSaveToFile =
new QAction( tr(
"Save to Directory…" ),
this );
295 connect( actionSaveToFile, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::selectDirectory );
299 actionSaveToFile =
new QAction( tr(
"Save to File…" ),
this );
300 connect( actionSaveToFile, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::selectFile );
302 mMenu->addAction( actionSaveToFile );
306 QAction *actionSaveToGpkg =
new QAction( tr(
"Save to GeoPackage…" ),
this );
307 connect( actionSaveToGpkg, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::saveToGeopackage );
308 mMenu->addAction( actionSaveToGpkg );
310 QAction *actionSaveToDatabase =
new QAction( tr(
"Save to Database Table…" ),
this );
311 connect( actionSaveToDatabase, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::saveToDatabase );
312 mMenu->addAction( actionSaveToDatabase );
314 if ( mParameter->algorithm() && qgis::down_cast<const QgsProcessingParameterFeatureSink *>( mParameter )->supportsAppend() )
316 mMenu->addSeparator();
317 QAction *actionAppendToLayer =
new QAction( tr(
"Append to Layer…" ),
this );
318 connect( actionAppendToLayer, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::appendToLayer );
319 mMenu->addAction( actionAppendToLayer );
322 QAction *editMappingAction =
new QAction( tr(
"Edit Field Mapping…" ),
this );
323 connect( editMappingAction, &QAction::triggered,
this, [
this] {
324 setAppendDestination( value().value<QgsProcessingOutputLayerDefinition>().sink.staticValue().toString(), mRemapDefinition.destinationFields() );
326 mMenu->addAction( editMappingAction );
333 mMenu->addSeparator();
334 QAction *actionSetEncoding =
new QAction( tr(
"Change File Encoding (%1)…" ).arg( mEncoding ),
this );
335 connect( actionSetEncoding, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::selectEncoding );
336 mMenu->addAction( actionSetEncoding );
340void QgsProcessingLayerOutputDestinationWidget::skipOutput()
343 leText->setPlaceholderText( tr(
"[Skip output]" ) );
344 leText->removeAction( mActionTemporaryOutputIcon );
346 mPreviousValueString.clear();
347 mUseTemporary =
false;
348 mUseRemapping =
false;
350 emit skipOutputChanged(
true );
353void QgsProcessingLayerOutputDestinationWidget::setupPlaceholderText()
357 leText->setPlaceholderText( tr(
"[Create temporary layer]" ) );
361 leText->setPlaceholderText( tr(
"[Save to temporary folder]" ) );
365 leText->setPlaceholderText( tr(
"[Save to temporary file]" ) );
369void QgsProcessingLayerOutputDestinationWidget::saveToTemporary(
const QString &name )
371 const bool prevSkip = outputIsSkipped();
374 if ( prevSkip && name.isEmpty() )
376 leText->addAction( mActionTemporaryOutputIcon, QLineEdit::LeadingPosition );
379 setupPlaceholderText();
381 if ( name.isEmpty() )
384 if ( mUseTemporary && leText->text() == name )
387 leText->setText( name );
389 mUseTemporary =
true;
390 mUseRemapping =
false;
392 emit skipOutputChanged(
false );
395void QgsProcessingLayerOutputDestinationWidget::selectDirectory()
397 QString lastDir = leText->text();
399 if ( lastDir.isEmpty() )
400 lastDir = settings.
value( u
"/Processing/LastOutputPath"_s, QDir::homePath() ).toString();
402 const QString dirName = QFileDialog::getExistingDirectory(
this, tr(
"Select Directory" ), lastDir, QFileDialog::Options() );
403 if ( !dirName.isEmpty() )
405 leText->setText( QDir::toNativeSeparators( dirName ) );
406 settings.
setValue( u
"/Processing/LastOutputPath"_s, dirName );
407 mUseTemporary =
false;
408 mUseRemapping =
false;
409 emit skipOutputChanged(
false );
413void QgsProcessingLayerOutputDestinationWidget::selectFile()
415 const QString fileFilter = mParameter->createFileFilter();
421 QString lastFormatPath;
425 lastExtPath = u
"/Processing/LastVectorOutputExt"_s;
426 lastExt = settings.
value( lastExtPath, u
".%1"_s.arg( mParameter->defaultFileExtension() ) ).toString();
432 lastFormatPath = u
"/Processing/LastRasterOutputFormat"_s;
437 lastExtPath = u
"/Processing/LastPointCloudOutputExt"_s;
438 lastExt = settings.
value( lastExtPath, u
".%1"_s.arg( mParameter->defaultFileExtension() ) ).toString();
442 lastExtPath = u
"/Processing/LastVectorTileOutputExt"_s;
443 lastExt = settings.
value( lastExtPath, u
".%1"_s.arg( mParameter->defaultFileExtension() ) ).toString();
447 const QStringList filters = fileFilter.split( u
";;"_s );
449 for (
const QString &f : filters )
451 if ( !lastFormat.isEmpty() && f.contains( lastFormat, Qt::CaseInsensitive ) )
456 else if ( !lastExt.isEmpty() && f.contains( u
"*.%1"_s.arg( lastExt ), Qt::CaseInsensitive ) )
464 if ( settings.
contains( u
"/Processing/LastOutputPath"_s ) )
465 path = settings.
value( u
"/Processing/LastOutputPath"_s ).toString();
467 path = settings.
value( u
"/Processing/Configuration/OUTPUTS_FOLDER"_s ).toString();
469 const bool dontConfirmOverwrite = mParameter->metadata().value( u
"widget_wrapper"_s ).toMap().value( u
"dontconfirmoverwrite"_s,
false ).toBool();
471 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save file" ), path, fileFilter, &lastFilter, dontConfirmOverwrite ? QFileDialog::Options( QFileDialog::DontConfirmOverwrite ) : QFileDialog::Options() );
472 if ( !filename.isEmpty() )
474 mUseTemporary =
false;
475 mUseRemapping =
false;
480 Q_ASSERT( formatAndExtensions.size() + 1 == filters.size() );
482 for (
const QString &f : filters )
484 if ( f == lastFilter )
486 mFormat = formatAndExtensions[idxFilter].first;
494 leText->setText( filename );
496 settings.
setValue( u
"/Processing/LastOutputPath"_s, QFileInfo( filename ).path() );
497 if ( !lastFormatPath.isEmpty() && !mFormat.isEmpty() )
498 settings.
setValue( lastFormatPath, mFormat );
499 else if ( !lastExtPath.isEmpty() )
500 settings.
setValue( lastExtPath, QFileInfo( filename ).suffix().toLower() );
502 emit skipOutputChanged(
false );
509void QgsProcessingLayerOutputDestinationWidget::saveToGeopackage()
512 QString lastPath = settings.
value( u
"/Processing/LastOutputPath"_s, QString() ).toString();
513 if ( lastPath.isEmpty() )
514 lastPath = settings.
value( u
"/Processing/Configuration/OUTPUTS_FOLDER"_s, QString() ).toString();
516 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save to GeoPackage" ), lastPath, tr(
"GeoPackage files (*.gpkg);;All files (*.*)" ),
nullptr, QFileDialog::DontConfirmOverwrite );
521 if ( filename.isEmpty() )
524 const QString layerName = QInputDialog::getText(
this, tr(
"Save to GeoPackage" ), tr(
"Layer name" ), QLineEdit::Normal, mParameter->name().toLower() );
525 if ( layerName.isEmpty() )
528 mUseTemporary =
false;
529 mUseRemapping =
false;
533 settings.
setValue( u
"/Processing/LastOutputPath"_s, QFileInfo( filename ).path() );
542 if ( sink->hasGeometry() )
543 geomColumn = u
"geom"_s;
547 leText->setText( u
"ogr:%1"_s.arg( uri.
uri() ) );
549 emit skipOutputChanged(
false );
552void QgsProcessingLayerOutputDestinationWidget::saveToDatabase()
557 widget->
setPanelTitle( tr(
"Save “%1” to Database Table" ).arg( mParameter->description() ) );
560 panel->openPanel( widget );
562 auto changed = [
this, widget] {
563 mUseTemporary =
false;
564 mUseRemapping =
false;
569 if ( sink->hasGeometry() )
570 geomColumn = widget->
dataProviderKey() ==
"oracle"_L1 ? u
"GEOM"_s : u
"geom"_s;
579 leText->setText( u
"ogr:%1"_s.arg( uri.
uri() ) );
588 emit skipOutputChanged(
false );
602void QgsProcessingLayerOutputDestinationWidget::appendToLayer()
607 widget->
setPanelTitle( tr(
"Append \"%1\" to Layer" ).arg( mParameter->description() ) );
609 panel->openPanel( widget );
615 if ( widget->
uri().
uri.isEmpty() )
616 setValue( QVariant() );
620 auto dest = std::make_unique<QgsVectorLayer>( widget->
uri().
uri, QString(), widget->
uri().
providerKey );
622 setAppendDestination( widget->
uri().
uri, dest->fields() );
631void QgsProcessingLayerOutputDestinationWidget::setAppendDestination(
const QString &uri,
const QgsFields &destFields )
635 if ( mParametersGenerator )
636 props = mParametersGenerator->createProcessingParameters();
637 props.insert( mParameter->name(), uri );
646 widget->
setPanelTitle( tr(
"Append \"%1\" to Layer" ).arg( mParameter->description() ) );
647 if ( !mRemapDefinition.fieldMap().isEmpty() )
650 panel->openPanel( widget );
665void QgsProcessingLayerOutputDestinationWidget::selectEncoding()
673 settings.
setValue( u
"/Processing/encoding"_s, mEncoding );
675 emit destinationChanged();
679void QgsProcessingLayerOutputDestinationWidget::textChanged(
const QString &text )
682 const bool prevSkip = !mUseTemporary && mPreviousValueString.isEmpty();
685 setupPlaceholderText();
686 emit skipOutputChanged(
false );
689 mUseRemapping =
false;
691 if ( couldBeTemporaryLayerName( text ) || text ==
"memory:"_L1 )
693 leText->addAction( mActionTemporaryOutputIcon, QLineEdit::LeadingPosition );
694 mUseTemporary =
true;
698 leText->removeAction( mActionTemporaryOutputIcon );
699 mUseTemporary =
false;
703 if ( mPreviousValueString != text )
705 emit destinationChanged();
708 mPreviousValueString = text;
712QString QgsProcessingLayerOutputDestinationWidget::mimeDataToPath(
const QMimeData *data )
720 && u.layerType ==
"vector"_L1 && u.providerKey ==
"ogr"_L1 )
726 && u.layerType ==
"raster"_L1 && u.providerKey ==
"gdal"_L1 )
732 && u.layerType ==
"pointcloud"_L1 && ( u.providerKey ==
"ept"_L1 || u.providerKey ==
"pdal"_L1 ) )
737 else if ( ( mParameter->type() == QgsProcessingParameterMeshDestination::typeName()
739 && u.layerType ==
"mesh"_L1 && u.providerKey ==
"mdal"_L1 )
744 && u.layerType ==
"directory"_L1 )
749 if ( !uriList.isEmpty() )
753 QStringList rawPaths;
754 if ( data->hasUrls() )
756 const QList<QUrl> urls = data->urls();
757 rawPaths.reserve( urls.count() );
758 for (
const QUrl &url : urls )
760 const QString local = url.toLocalFile();
761 if ( !rawPaths.contains( local ) )
762 rawPaths.append( local );
765 if ( !data->text().isEmpty() && !rawPaths.contains( data->text() ) )
766 rawPaths.append( data->text() );
768 for (
const QString &path : std::as_const( rawPaths ) )
770 QFileInfo file( path );
783void QgsProcessingLayerOutputDestinationWidget::dragEnterEvent( QDragEnterEvent *event )
785 if ( !( event->possibleActions() & Qt::CopyAction ) )
788 const QString path = mimeDataToPath( event->mimeData() );
789 if ( !path.isEmpty() )
792 event->setDropAction( Qt::CopyAction );
794 leText->setHighlighted(
true );
798void QgsProcessingLayerOutputDestinationWidget::dragLeaveEvent( QDragLeaveEvent *event )
800 QWidget::dragLeaveEvent( event );
801 if ( leText->isHighlighted() )
804 leText->setHighlighted(
false );
808void QgsProcessingLayerOutputDestinationWidget::dropEvent( QDropEvent *event )
810 if ( !( event->possibleActions() & Qt::CopyAction ) )
813 const QString path = mimeDataToPath( event->mimeData() );
814 if ( !path.isEmpty() )
817 setFocus( Qt::MouseFocusReason );
818 event->setDropAction( Qt::CopyAction );
822 leText->setHighlighted(
false );
825QString QgsProcessingLayerOutputDestinationWidget::memoryProviderLayerName(
const QString &value )
const
827 if ( value ==
"memory:"_L1 )
834 if ( hasProviderAndUri && provider ==
"memory"_L1 )
842bool QgsProcessingLayerOutputDestinationWidget::couldBeTemporaryLayerName(
const QString &value )
const
847 if ( value.isEmpty() )
850 if ( value ==
"memory:"_L1 )
857 if ( provider ==
"memory"_L1 )
860 if ( hasProviderAndUri )
863 if ( QFileInfo( value ).isAbsolute() || !QFileInfo( value ).suffix().isEmpty() )
869bool QgsProcessingLayerOutputDestinationWidget::consideredEqualTemporaryOutputValues(
const QString &val1,
const QString &val2 )
const
877QString QgsProcessingLayerOutputDestinationWidget::variantToString(
const QVariant &value )
const
879 if ( value.userType() == qMetaTypeId<QgsProcessingOutputLayerDefinition>() )
885 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.