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 );