31 #include <QFileDialog>
32 #include <QInputDialog>
38 QgsProcessingLayerOutputDestinationWidget::QgsProcessingLayerOutputDestinationWidget(
const QgsProcessingDestinationParameter *param,
bool defaultSelection, QWidget *parent )
41 , mDefaultSelection( defaultSelection )
43 Q_ASSERT( mParameter );
47 leText->setClearButtonEnabled(
false );
49 connect( leText, &QLineEdit::textEdited,
this, &QgsProcessingLayerOutputDestinationWidget::textChanged );
51 mMenu =
new QMenu(
this );
52 connect( mMenu, &QMenu::aboutToShow,
this, &QgsProcessingLayerOutputDestinationWidget::menuAboutToShow );
53 mSelectButton->setMenu( mMenu );
54 mSelectButton->setPopupMode( QToolButton::InstantPopup );
57 mEncoding = settings.
value( QStringLiteral(
"/Processing/encoding" ), QStringLiteral(
"System" ) ).toString();
59 if ( !mParameter->defaultValueForGui().isValid() )
63 setValue( QVariant() );
69 setValue( mParameter->defaultValueForGui() );
72 setToolTip( mParameter->toolTip() );
74 setAcceptDrops(
true );
75 leText->setAcceptDrops(
false );
78 bool QgsProcessingLayerOutputDestinationWidget::outputIsSkipped()
const
80 return leText->text().isEmpty() && !mUseTemporary;
83 void QgsProcessingLayerOutputDestinationWidget::setValue(
const QVariant &value )
85 const bool prevSkip = outputIsSkipped();
86 mUseRemapping =
false;
87 if ( !value.isValid() || ( value.type() == QVariant::String && value.toString().isEmpty() ) )
109 const QVariant prev = QgsProcessingLayerOutputDestinationWidget::value();
111 mUseTemporary =
false;
113 emit skipOutputChanged(
false );
114 if ( prev != QgsProcessingLayerOutputDestinationWidget::value() )
115 emit destinationChanged();
119 mEncoding = def.
createOptions.value( QStringLiteral(
"fileEncoding" ) ).toString();
123 const QVariant prev = QgsProcessingLayerOutputDestinationWidget::value();
124 leText->setText( value.toString() );
125 mUseTemporary =
false;
127 emit skipOutputChanged(
false );
131 if ( prev.toString() != QgsProcessingLayerOutputDestinationWidget::value().toString() )
132 emit destinationChanged();
138 emit destinationChanged();
144 QVariant QgsProcessingLayerOutputDestinationWidget::value()
const
152 else if ( mUseTemporary && !mDefaultSelection )
158 key = leText->text();
167 && !key.startsWith( QLatin1String(
"memory:" ) )
168 && !key.startsWith( QLatin1String(
"ogr:" ) )
169 && !key.startsWith( QLatin1String(
"postgres:" ) )
170 && !key.startsWith( QLatin1String(
"postgis:" ) )
174 QString folder = QFileInfo( key ).path();
178 QString defaultFolder = settings.
value( QStringLiteral(
"/Processing/Configuration/OUTPUTS_FOLDER" ) ).toString();
179 key = QDir( defaultFolder ).filePath( key );
189 value.createOptions.insert( QStringLiteral(
"fileEncoding" ), mEncoding );
191 value.setRemappingDefinition( mRemapDefinition );
207 mParametersGenerator = generator;
210 void QgsProcessingLayerOutputDestinationWidget::addOpenAfterRunningOption()
212 Q_ASSERT( mOpenAfterRunningCheck ==
nullptr );
213 mOpenAfterRunningCheck =
new QCheckBox( tr(
"Open output file after running algorithm" ) );
214 mOpenAfterRunningCheck->setChecked( !outputIsSkipped() );
215 mOpenAfterRunningCheck->setEnabled( !outputIsSkipped() );
216 gridLayout->addWidget( mOpenAfterRunningCheck, 1, 0, 1, 2 );
218 connect(
this, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged,
this, [ = ](
bool skipped )
220 bool enabled = !skipped;
221 mOpenAfterRunningCheck->setEnabled( enabled );
222 mOpenAfterRunningCheck->setChecked( enabled );
226 bool QgsProcessingLayerOutputDestinationWidget::openAfterRunning()
const
228 return mOpenAfterRunningCheck && mOpenAfterRunningCheck->isChecked();
231 void QgsProcessingLayerOutputDestinationWidget::menuAboutToShow()
235 if ( !mDefaultSelection )
239 QAction *actionSkipOutput =
new QAction( tr(
"Skip Output" ),
this );
240 connect( actionSkipOutput, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::skipOutput );
241 mMenu->addAction( actionSkipOutput );
244 QAction *actionSaveToTemp =
nullptr;
248 actionSaveToTemp =
new QAction( tr(
"Create Temporary Layer" ),
this );
252 actionSaveToTemp =
new QAction( tr(
"Save to a Temporary Directory" ),
this );
256 actionSaveToTemp =
new QAction( tr(
"Save to a Temporary File" ),
this );
259 connect( actionSaveToTemp, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::saveToTemporary );
260 mMenu->addAction( actionSaveToTemp );
263 QAction *actionSaveToFile =
nullptr;
266 actionSaveToFile =
new QAction( tr(
"Save to Directory…" ),
this );
267 connect( actionSaveToFile, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::selectDirectory );
271 actionSaveToFile =
new QAction( tr(
"Save to File…" ),
this );
272 connect( actionSaveToFile, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::selectFile );
274 mMenu->addAction( actionSaveToFile );
278 QAction *actionSaveToGpkg =
new QAction( tr(
"Save to GeoPackage…" ),
this );
279 connect( actionSaveToGpkg, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::saveToGeopackage );
280 mMenu->addAction( actionSaveToGpkg );
282 QAction *actionSaveToDatabase =
new QAction( tr(
"Save to Database Table…" ),
this );
283 connect( actionSaveToDatabase, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::saveToDatabase );
284 mMenu->addAction( actionSaveToDatabase );
286 if ( mParameter->algorithm() && qgis::down_cast< const QgsProcessingParameterFeatureSink * >( mParameter )->supportsAppend() )
288 mMenu->addSeparator();
289 QAction *actionAppendToLayer =
new QAction( tr(
"Append to Layer…" ),
this );
290 connect( actionAppendToLayer, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::appendToLayer );
291 mMenu->addAction( actionAppendToLayer );
294 QAction *editMappingAction =
new QAction( tr(
"Edit Field Mapping…" ),
this );
295 connect( editMappingAction, &QAction::triggered,
this, [ = ]
297 setAppendDestination( value().value< QgsProcessingOutputLayerDefinition >().sink.staticValue().toString(), mRemapDefinition.destinationFields() );
299 mMenu->addAction( editMappingAction );
306 mMenu->addSeparator();
307 QAction *actionSetEncoding =
new QAction( tr(
"Change File Encoding (%1)…" ).arg( mEncoding ),
this );
308 connect( actionSetEncoding, &QAction::triggered,
this, &QgsProcessingLayerOutputDestinationWidget::selectEncoding );
309 mMenu->addAction( actionSetEncoding );
313 void QgsProcessingLayerOutputDestinationWidget::skipOutput()
315 leText->setPlaceholderText( tr(
"[Skip output]" ) );
317 mUseTemporary =
false;
318 mUseRemapping =
false;
320 emit skipOutputChanged(
true );
321 emit destinationChanged();
324 void QgsProcessingLayerOutputDestinationWidget::saveToTemporary()
326 const bool prevSkip = outputIsSkipped();
330 leText->setPlaceholderText( tr(
"[Create temporary layer]" ) );
334 leText->setPlaceholderText( tr(
"[Save to temporary folder]" ) );
338 leText->setPlaceholderText( tr(
"[Save to temporary file]" ) );
345 mUseTemporary =
true;
346 mUseRemapping =
false;
348 emit skipOutputChanged(
false );
349 emit destinationChanged();
352 void QgsProcessingLayerOutputDestinationWidget::selectDirectory()
354 QString lastDir = leText->text();
356 if ( lastDir.isEmpty() )
357 lastDir = settings.
value( QStringLiteral(
"/Processing/LastOutputPath" ), QDir::homePath() ).toString();
359 const QString dirName = QFileDialog::getExistingDirectory(
this, tr(
"Select Directory" ), lastDir, QFileDialog::Options() );
360 if ( !dirName.isEmpty() )
362 leText->setText( QDir::toNativeSeparators( dirName ) );
363 settings.
setValue( QStringLiteral(
"/Processing/LastOutputPath" ), dirName );
364 mUseTemporary =
false;
365 mUseRemapping =
false;
366 emit skipOutputChanged(
false );
367 emit destinationChanged();
371 void QgsProcessingLayerOutputDestinationWidget::selectFile()
373 const QString fileFilter = mParameter->createFileFilter();
381 lastExtPath = QStringLiteral(
"/Processing/LastVectorOutputExt" );
382 lastExt = settings.
value( lastExtPath, QStringLiteral(
".%1" ).arg( mParameter->defaultFileExtension() ) ).toString() ;
386 lastExtPath = QStringLiteral(
"/Processing/LastRasterOutputExt" );
387 lastExt = settings.
value( lastExtPath, QStringLiteral(
".%1" ).arg( mParameter->defaultFileExtension() ) ).toString();
391 lastExtPath = QStringLiteral(
"/Processing/LastPointCloudOutputExt" );
392 lastExt = settings.
value( lastExtPath, QStringLiteral(
".%1" ).arg( mParameter->defaultFileExtension() ) ).toString();
396 const QStringList filters = fileFilter.split( QStringLiteral(
";;" ) );
398 for (
const QString &f : filters )
400 if ( f.contains( QStringLiteral(
"*.%1" ).arg( lastExt ), Qt::CaseInsensitive ) )
408 if ( settings.
contains( QStringLiteral(
"/Processing/LastOutputPath" ) ) )
409 path = settings.
value( QStringLiteral(
"/Processing/LastOutputPath" ) ).toString();
411 path = settings.
value( QStringLiteral(
"/Processing/Configuration/OUTPUTS_FOLDER" ) ).toString();
413 const bool dontConfirmOverwrite = mParameter->metadata().value( QStringLiteral(
"widget_wrapper" ) ).toMap().value( QStringLiteral(
"dontconfirmoverwrite" ),
false ).toBool();
415 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save file" ), path, fileFilter, &lastFilter, dontConfirmOverwrite ? QFileDialog::Options( QFileDialog::DontConfirmOverwrite ) : QFileDialog::Options() );
416 if ( !filename.isEmpty() )
418 mUseTemporary =
false;
419 mUseRemapping =
false;
422 leText->setText( filename );
423 settings.
setValue( QStringLiteral(
"/Processing/LastOutputPath" ), QFileInfo( filename ).path() );
424 if ( !lastExtPath.isEmpty() )
425 settings.
setValue( lastExtPath, QFileInfo( filename ).suffix().toLower() );
427 emit skipOutputChanged(
false );
428 emit destinationChanged();
432 void QgsProcessingLayerOutputDestinationWidget::saveToGeopackage()
435 QString lastPath = settings.
value( QStringLiteral(
"/Processing/LastOutputPath" ), QString() ).toString();
436 if ( lastPath.isEmpty() )
437 lastPath = settings.
value( QStringLiteral(
"/Processing/Configuration/OUTPUTS_FOLDER" ), QString() ).toString();
439 QString filename = QFileDialog::getSaveFileName(
this, tr(
"Save to GeoPackage" ), lastPath, tr(
"GeoPackage files (*.gpkg);;All files (*.*)" ),
nullptr, QFileDialog::DontConfirmOverwrite );
441 if ( filename.isEmpty() )
444 const QString layerName = QInputDialog::getText(
this, tr(
"Save to GeoPackage" ), tr(
"Layer name" ), QLineEdit::Normal, mParameter->name().toLower() );
445 if ( layerName.isEmpty() )
448 mUseTemporary =
false;
449 mUseRemapping =
false;
453 settings.
setValue( QStringLiteral(
"/Processing/LastOutputPath" ), QFileInfo( filename ).path() );
462 if ( sink->hasGeometry() )
463 geomColumn = QStringLiteral(
"geom" );
467 leText->setText( QStringLiteral(
"ogr:%1" ).arg( uri.
uri() ) );
469 emit skipOutputChanged(
false );
470 emit destinationChanged();
473 void QgsProcessingLayerOutputDestinationWidget::saveToDatabase()
478 QgsNewDatabaseTableNameWidget *widget =
new QgsNewDatabaseTableNameWidget( mBrowserModel, QStringList() << QStringLiteral(
"postgres" )
479 << QStringLiteral(
"mssql" )
480 << QStringLiteral(
"ogr" )
481 << QStringLiteral(
"hana" )
482 << QStringLiteral(
"spatialite" )
483 << QStringLiteral(
"oracle" ),
this );
484 widget->setPanelTitle( tr(
"Save “%1” to Database Table" ).arg( mParameter->description() ) );
485 widget->setAcceptButtonVisible(
true );
487 panel->openPanel( widget );
491 mUseTemporary =
false;
492 mUseRemapping =
false;
497 if ( sink->hasGeometry() )
498 geomColumn = widget->dataProviderKey() == QLatin1String(
"oracle" ) ? QStringLiteral(
"GEOM" ) : QStringLiteral(
"geom" );
501 if ( widget->dataProviderKey() == QLatin1String(
"ogr" ) )
507 leText->setText( QStringLiteral(
"ogr:%1" ).arg( uri.
uri() ) );
516 emit skipOutputChanged(
false );
517 emit destinationChanged();
527 widget->acceptPanel();
532 void QgsProcessingLayerOutputDestinationWidget::appendToLayer()
537 widget->
setPanelTitle( tr(
"Append \"%1\" to Layer" ).arg( mParameter->description() ) );
539 panel->openPanel( widget );
547 if ( widget->
uri().
uri.isEmpty() )
548 setValue( QVariant() );
552 std::unique_ptr< QgsVectorLayer > dest = std::make_unique< QgsVectorLayer >( widget->
uri().
uri, QString(), widget->
uri().
providerKey );
554 setAppendDestination( widget->
uri().
uri, dest->fields() );
563 void QgsProcessingLayerOutputDestinationWidget::setAppendDestination(
const QString &uri,
const QgsFields &destFields )
567 if ( mParametersGenerator )
568 props = mParametersGenerator->createProcessingParameters();
569 props.insert( mParameter->name(), uri );
578 widget->
setPanelTitle( tr(
"Append \"%1\" to Layer" ).arg( mParameter->description() ) );
579 if ( !mRemapDefinition.fieldMap().isEmpty() )
582 panel->openPanel( widget );
598 void QgsProcessingLayerOutputDestinationWidget::selectEncoding()
603 mEncoding = dialog.encoding();
605 settings.
setValue( QStringLiteral(
"/Processing/encoding" ), mEncoding );
606 emit destinationChanged();
610 void QgsProcessingLayerOutputDestinationWidget::textChanged(
const QString &text )
612 mUseTemporary = text.isEmpty();
613 mUseRemapping =
false;
614 emit destinationChanged();
618 QString QgsProcessingLayerOutputDestinationWidget::mimeDataToPath(
const QMimeData *data )
626 && u.layerType == QLatin1String(
"vector" ) && u.providerKey == QLatin1String(
"ogr" ) )
632 && u.layerType == QLatin1String(
"raster" ) && u.providerKey == QLatin1String(
"gdal" ) )
638 && u.layerType == QLatin1String(
"pointcloud" ) && ( u.providerKey == QLatin1String(
"ept" ) || u.providerKey == QLatin1String(
"pdal" ) ) )
645 && u.layerType == QLatin1String(
"mesh" ) && u.providerKey == QLatin1String(
"mdal" ) )
650 && u.layerType == QLatin1String(
"directory" ) )
655 if ( !uriList.isEmpty() )
659 QStringList rawPaths;
660 if ( data->hasUrls() )
662 const QList< QUrl > urls = data->urls();
663 rawPaths.reserve( urls.count() );
664 for (
const QUrl &url : urls )
666 const QString local = url.toLocalFile();
667 if ( !rawPaths.contains( local ) )
668 rawPaths.append( local );
671 if ( !data->text().isEmpty() && !rawPaths.contains( data->text() ) )
672 rawPaths.append( data->text() );
674 for (
const QString &path : std::as_const( rawPaths ) )
676 QFileInfo file( path );
694 void QgsProcessingLayerOutputDestinationWidget::dragEnterEvent( QDragEnterEvent *event )
696 if ( !( event->possibleActions() & Qt::CopyAction ) )
699 const QString path = mimeDataToPath( event->mimeData() );
700 if ( !path.isEmpty() )
703 event->setDropAction( Qt::CopyAction );
705 leText->setHighlighted(
true );
709 void QgsProcessingLayerOutputDestinationWidget::dragLeaveEvent( QDragLeaveEvent *event )
711 QWidget::dragLeaveEvent( event );
712 if ( leText->isHighlighted() )
715 leText->setHighlighted(
false );
719 void QgsProcessingLayerOutputDestinationWidget::dropEvent( QDropEvent *event )
721 if ( !( event->possibleActions() & Qt::CopyAction ) )
724 const QString path = mimeDataToPath( event->mimeData() );
725 if ( !path.isEmpty() )
728 setFocus( Qt::MouseFocusReason );
729 event->setDropAction( Qt::CopyAction );
733 leText->setHighlighted(
false );