QGIS API Documentation 4.1.0-Master (fd797d02722)
Loading...
Searching...
No Matches
qgsprocessingoutputdestinationwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsprocessingmatrixparameterdialog.cpp
3 ------------------------------------
4 Date : February 2019
5 Copyright : (C) 2019 Nyall Dawson
6 Email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
18#include "qgsapplication.h"
20#include "qgsdatasourceuri.h"
23#include "qgsfileutils.h"
28#include "qgssettings.h"
29
30#include <QCheckBox>
31#include <QFileDialog>
32#include <QInputDialog>
33#include <QLocale>
34#include <QMenu>
35#include <QString>
36#include <QTextCodec>
37#include <QUrl>
38
39#include "moc_qgsprocessingoutputdestinationwidget.cpp"
40
41using namespace Qt::StringLiterals;
42
44
45QgsProcessingLayerOutputDestinationWidget::QgsProcessingLayerOutputDestinationWidget( const QgsProcessingDestinationParameter *param, bool defaultSelection, QWidget *parent )
46 : QWidget( parent )
47 , mParameter( param )
48 , mDefaultSelection( defaultSelection )
49{
50 Q_ASSERT( mParameter );
51
52 setupUi( this );
53
54 mActionTemporaryOutputIcon = new QAction( QgsApplication::getThemeIcon( u"/mActionCreateMemory.svg"_s ), tr( "Temporary Output" ), this );
55
56 leText->setClearButtonEnabled( false );
57
58 if ( !( mParameter->type() == QgsProcessingParameterFolderDestination::typeName() || mParameter->type() == QgsProcessingParameterFileDestination::typeName() ) )
59 {
60 leText->addAction( mActionTemporaryOutputIcon, QLineEdit::LeadingPosition );
61 }
62
63 connect( leText, &QLineEdit::textChanged, this, &QgsProcessingLayerOutputDestinationWidget::textChanged );
64
65 mMenu = new QMenu( this );
66 connect( mMenu, &QMenu::aboutToShow, this, &QgsProcessingLayerOutputDestinationWidget::menuAboutToShow );
67 mSelectButton->setMenu( mMenu );
68 mSelectButton->setPopupMode( QToolButton::InstantPopup );
69
70 QgsSettings settings;
71 mEncoding = QgsProcessingUtils::resolveDefaultEncoding( settings.value( u"/Processing/encoding"_s, u"System"_s ).toString() );
72 settings.setValue( u"/Processing/encoding"_s, mEncoding );
73
74 if ( !mParameter->defaultValueForGui().isValid() )
75 {
76 // no default value -- we default to either skipping the output or a temporary output, depending on the createByDefault value
77 if ( mParameter->flags() & Qgis::ProcessingParameterFlag::Optional && !mParameter->createByDefault() )
78 setValue( QVariant() );
79 else
81 }
82 else
83 {
84 setValue( mParameter->defaultValueForGui() );
85 }
86
87 setToolTip( mParameter->toolTip() );
88
89 setAcceptDrops( true );
90 leText->setAcceptDrops( false );
91}
92
93bool QgsProcessingLayerOutputDestinationWidget::outputIsSkipped() const
94{
95 return leText->text().isEmpty() && !mUseTemporary;
96}
97
98void QgsProcessingLayerOutputDestinationWidget::setValue( const QVariant &value )
99{
100 const bool prevSkip = outputIsSkipped();
101 mUseRemapping = false;
102 if ( !value.isValid() || ( value.userType() == QMetaType::Type::QString && couldBeTemporaryLayerName( value.toString() ) ) )
103 {
104 if ( mParameter->flags() & Qgis::ProcessingParameterFlag::Optional )
105 {
106 skipOutput();
107 emit destinationChanged();
108 }
109 else
110 {
111 saveToTemporary( value.toString() );
112 }
113 }
114 else
115 {
116 if ( value.toString() == "memory:"_L1 || value.toString() == QgsProcessing::TEMPORARY_OUTPUT )
117 {
118 saveToTemporary();
119 if ( !consideredEqualTemporaryOutputValues( mPreviousValueString, variantToString( value ) ) )
120 {
121 emit destinationChanged();
122 }
123 }
124 else if ( value.userType() == qMetaTypeId<QgsProcessingOutputLayerDefinition>() )
125 {
127 if ( def.sink.staticValue().toString() == "memory:"_L1 || def.sink.staticValue().toString() == QgsProcessing::TEMPORARY_OUTPUT || def.sink.staticValue().toString().isEmpty() )
128 {
129 saveToTemporary( def.destinationName );
130 }
131 else
132 {
133 leText->setText( def.sink.staticValue().toString() );
134 if ( prevSkip )
135 emit skipOutputChanged( false );
136 }
137 mUseRemapping = def.useRemapping();
138 mRemapDefinition = def.remappingDefinition();
139 mEncoding = def.createOptions.value( u"fileEncoding"_s ).toString();
140 }
141 else
142 {
143 leText->setText( value.toString() );
144 if ( prevSkip )
145 emit skipOutputChanged( false );
146 }
147 }
148
149 mPreviousValueString = variantToString( value );
150}
151
152QVariant QgsProcessingLayerOutputDestinationWidget::value() const
153{
154 QgsSettings settings;
155 QString key;
156 if ( mUseTemporary && mParameter->type() == QgsProcessingParameterFeatureSink::typeName() )
157 {
159 }
160 else if ( mUseTemporary && !mDefaultSelection )
161 {
163 }
164 else
165 {
166 key = leText->text();
167 }
168
169 if ( key.isEmpty() && mParameter->flags() & Qgis::ProcessingParameterFlag::Optional )
170 return QVariant();
171
172 QString provider;
173 QString uri;
174 if ( !key.isEmpty()
176 && !key.startsWith( "memory:"_L1 )
177 && !key.startsWith( "ogr:"_L1 )
178 && !key.startsWith( "postgres:"_L1 )
179 && !key.startsWith( "postgis:"_L1 )
180 && !QgsProcessingUtils::decodeProviderKeyAndUri( key, provider, uri ) )
181 {
182 // output should be a file path
183 QString folder = QFileInfo( key ).path();
184 if ( folder == '.' )
185 {
186 // output name does not include a folder - use default
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 ) )
190 {
191 QgsDebugError( u"Can't create output folder '%1'"_s.arg( defaultFolder ) );
192 }
193 key = destDir.filePath( key );
194 }
195 }
196
197 if ( mParameter->type() == QgsProcessingParameterFolderDestination::typeName() )
198 return key;
199 else if ( mParameter->type() == QgsProcessingParameterFileDestination::typeName() )
200 return key;
201
203 value.createOptions.insert( u"fileEncoding"_s, mEncoding );
204 if ( mUseRemapping )
205 value.setRemappingDefinition( mRemapDefinition );
206
207 // this marks named temporary layer
208 if ( key == QgsProcessing::TEMPORARY_OUTPUT && couldBeTemporaryLayerName( leText->text() ) )
209 {
210 const QString memoryLayerName = memoryProviderLayerName( leText->text() );
211 value.destinationName = memoryLayerName.isEmpty() ? leText->text() : memoryLayerName;
212 }
213
214
215 if ( !mFormat.isEmpty() )
216 value.setFormat( mFormat );
217
218 return value;
219}
220
221void QgsProcessingLayerOutputDestinationWidget::setWidgetContext( const QgsProcessingParameterWidgetContext &context )
222{
223 mBrowserModel = context.browserModel();
224}
225
226void QgsProcessingLayerOutputDestinationWidget::setContext( QgsProcessingContext *context )
227{
228 mContext = context;
229}
230
231void QgsProcessingLayerOutputDestinationWidget::registerProcessingParametersGenerator( QgsProcessingParametersGenerator *generator )
232{
233 mParametersGenerator = generator;
234}
235
236void QgsProcessingLayerOutputDestinationWidget::addOpenAfterRunningOption()
237{
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 );
243
244 connect( this, &QgsProcessingLayerOutputDestinationWidget::skipOutputChanged, this, [this]( bool skipped ) {
245 bool enabled = !skipped;
246 mOpenAfterRunningCheck->setEnabled( enabled );
247 mOpenAfterRunningCheck->setChecked( enabled );
248 } );
249}
250
251bool QgsProcessingLayerOutputDestinationWidget::openAfterRunning() const
252{
253 return mOpenAfterRunningCheck && mOpenAfterRunningCheck->isChecked();
254}
255
256void QgsProcessingLayerOutputDestinationWidget::menuAboutToShow()
257{
258 mMenu->clear();
259
260 if ( !mDefaultSelection )
261 {
262 if ( mParameter->flags() & Qgis::ProcessingParameterFlag::Optional )
263 {
264 QAction *actionSkipOutput = new QAction( tr( "Skip Output" ), this );
265 connect( actionSkipOutput, &QAction::triggered, this, &QgsProcessingLayerOutputDestinationWidget::skipOutput );
266 mMenu->addAction( actionSkipOutput );
267 }
268
269 QAction *actionSaveToTemp = nullptr;
270 if ( mParameter->type() == QgsProcessingParameterFeatureSink::typeName() && mParameter->supportsNonFileBasedOutput() )
271 {
272 // use memory layers for temporary layers if supported
273 actionSaveToTemp = new QAction( tr( "Create Temporary Layer" ), this );
274 }
275 else if ( mParameter->type() == QgsProcessingParameterFolderDestination::typeName() )
276 {
277 actionSaveToTemp = new QAction( tr( "Save to a Temporary Directory" ), this );
278 }
279 else
280 {
281 actionSaveToTemp = new QAction( tr( "Save to a Temporary File" ), this );
282 }
283
284 connect( actionSaveToTemp, &QAction::triggered, this, [this]() { saveToTemporary(); } );
285 mMenu->addAction( actionSaveToTemp );
286 }
287
288 QAction *actionSaveToFile = nullptr;
289 if ( mParameter->type() == QgsProcessingParameterFolderDestination::typeName() )
290 {
291 actionSaveToFile = new QAction( tr( "Save to Directory…" ), this );
292 connect( actionSaveToFile, &QAction::triggered, this, &QgsProcessingLayerOutputDestinationWidget::selectDirectory );
293 }
294 else
295 {
296 actionSaveToFile = new QAction( tr( "Save to File…" ), this );
297 connect( actionSaveToFile, &QAction::triggered, this, &QgsProcessingLayerOutputDestinationWidget::selectFile );
298 }
299 mMenu->addAction( actionSaveToFile );
300
301 if ( mParameter->type() == QgsProcessingParameterFeatureSink::typeName() && mParameter->supportsNonFileBasedOutput() )
302 {
303 QAction *actionSaveToGpkg = new QAction( tr( "Save to GeoPackage…" ), this );
304 connect( actionSaveToGpkg, &QAction::triggered, this, &QgsProcessingLayerOutputDestinationWidget::saveToGeopackage );
305 mMenu->addAction( actionSaveToGpkg );
306
307 QAction *actionSaveToDatabase = new QAction( tr( "Save to Database Table…" ), this );
308 connect( actionSaveToDatabase, &QAction::triggered, this, &QgsProcessingLayerOutputDestinationWidget::saveToDatabase );
309 mMenu->addAction( actionSaveToDatabase );
310
311 if ( mParameter->algorithm() && qgis::down_cast<const QgsProcessingParameterFeatureSink *>( mParameter )->supportsAppend() )
312 {
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 );
317 if ( mUseRemapping )
318 {
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() );
322 } );
323 mMenu->addAction( editMappingAction );
324 }
325 }
326 }
327
328 if ( mParameter->type() == QgsProcessingParameterFeatureSink::typeName() )
329 {
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 );
334 }
335}
336
337void QgsProcessingLayerOutputDestinationWidget::skipOutput()
338{
340 leText->setPlaceholderText( tr( "[Skip output]" ) );
341 leText->removeAction( mActionTemporaryOutputIcon );
342 leText->clear();
343 mPreviousValueString.clear();
344 mUseTemporary = false;
345 mUseRemapping = false;
346
347 emit skipOutputChanged( true );
348}
349
350void QgsProcessingLayerOutputDestinationWidget::setupPlaceholderText()
351{
352 if ( mParameter->type() == QgsProcessingParameterFeatureSink::typeName() && mParameter->supportsNonFileBasedOutput() )
353 {
354 leText->setPlaceholderText( tr( "[Create temporary layer]" ) );
355 }
356 else if ( mParameter->type() == QgsProcessingParameterFolderDestination::typeName() )
357 {
358 leText->setPlaceholderText( tr( "[Save to temporary folder]" ) );
359 }
360 else
361 {
362 leText->setPlaceholderText( tr( "[Save to temporary file]" ) );
363 }
364}
365
366void QgsProcessingLayerOutputDestinationWidget::saveToTemporary( const QString &name )
367{
368 const bool prevSkip = outputIsSkipped();
369
370 // special case, destination has not changed (empty string to empty string) but we need to show the temporary icon
371 if ( prevSkip && name.isEmpty() )
372 {
373 leText->addAction( mActionTemporaryOutputIcon, QLineEdit::LeadingPosition );
374 }
375
376 setupPlaceholderText();
377
378 if ( name.isEmpty() )
379 leText->clear();
380
381 if ( mUseTemporary && leText->text() == name )
382 return;
383
384 leText->setText( name );
385
386 mUseTemporary = true;
387 mUseRemapping = false;
388 if ( prevSkip )
389 emit skipOutputChanged( false );
390}
391
392void QgsProcessingLayerOutputDestinationWidget::selectDirectory()
393{
394 QString lastDir = leText->text();
395 QgsSettings settings;
396 if ( lastDir.isEmpty() )
397 lastDir = settings.value( u"/Processing/LastOutputPath"_s, QDir::homePath() ).toString();
398
399 const QString dirName = QFileDialog::getExistingDirectory( this, tr( "Select Directory" ), lastDir, QFileDialog::Options() );
400 if ( !dirName.isEmpty() )
401 {
402 leText->setText( QDir::toNativeSeparators( dirName ) );
403 settings.setValue( u"/Processing/LastOutputPath"_s, dirName );
404 mUseTemporary = false;
405 mUseRemapping = false;
406 emit skipOutputChanged( false );
407 }
408}
409
410void QgsProcessingLayerOutputDestinationWidget::selectFile()
411{
412 const QString fileFilter = mParameter->createFileFilter();
413
414 QgsSettings settings;
415
416 QString lastExtPath;
417 QString lastExt;
418 QString lastFormatPath;
419 QString lastFormat;
420 if ( mParameter->type() == QgsProcessingParameterFeatureSink::typeName() || mParameter->type() == QgsProcessingParameterVectorDestination::typeName() )
421 {
422 lastExtPath = u"/Processing/LastVectorOutputExt"_s;
423 lastExt = settings.value( lastExtPath, u".%1"_s.arg( mParameter->defaultFileExtension() ) ).toString();
424 }
425 else if ( mParameter->type() == QgsProcessingParameterRasterDestination::typeName() )
426 {
427 const QgsProcessingParameterRasterDestination *dest = dynamic_cast<const QgsProcessingParameterRasterDestination *>( mParameter );
428 Q_ASSERT( dest );
429 lastFormatPath = u"/Processing/LastRasterOutputFormat"_s;
430 lastFormat = settings.value( lastFormatPath, dest->defaultFileFormat() ).toString();
431 lastExtPath = u"/Processing/LastRasterOutputExt"_s;
432 lastExt = settings.value( lastExtPath, u".%1"_s.arg( mParameter->defaultFileExtension() ) ).toString();
433 }
434 else if ( mParameter->type() == QgsProcessingParameterPointCloudDestination::typeName() )
435 {
436 lastExtPath = u"/Processing/LastPointCloudOutputExt"_s;
437 lastExt = settings.value( lastExtPath, u".%1"_s.arg( mParameter->defaultFileExtension() ) ).toString();
438 }
439 else if ( mParameter->type() == QgsProcessingParameterVectorTileDestination::typeName() )
440 {
441 lastExtPath = u"/Processing/LastVectorTileOutputExt"_s;
442 lastExt = settings.value( lastExtPath, u".%1"_s.arg( mParameter->defaultFileExtension() ) ).toString();
443 }
444
445 // get default filter
446 const QStringList filters = fileFilter.split( u";;"_s );
447 QString lastFilter;
448 if ( !lastFormat.isEmpty() && !lastExt.isEmpty() )
449 {
450 // If we have both the format and extension, use in priority the filter
451 // that associates both, in case of multiple extensions per format.
452 for ( const QString &f : filters )
453 {
454 // "%1 - %2 " pattern from QgsProcessingParameterRasterDestination::createFileFilter()
455 if ( f.contains( u"%1 - %2 "_s.arg( lastFormat, lastExt ), Qt::CaseInsensitive ) )
456 {
457 lastFilter = f;
458 break;
459 }
460 }
461 }
462 if ( lastFilter.isEmpty() && !lastFormat.isEmpty() )
463 {
464 for ( const QString &f : filters )
465 {
466 if ( f.contains( lastFormat, Qt::CaseInsensitive ) )
467 {
468 lastFilter = f;
469 break;
470 }
471 }
472 }
473 if ( lastFilter.isEmpty() && !lastExt.isEmpty() )
474 {
475 for ( const QString &f : filters )
476 {
477 if ( f.contains( u"*.%1"_s.arg( lastExt ), Qt::CaseInsensitive ) )
478 {
479 lastFilter = f;
480 break;
481 }
482 }
483 }
484
485 QString path;
486 if ( settings.contains( u"/Processing/LastOutputPath"_s ) )
487 path = settings.value( u"/Processing/LastOutputPath"_s ).toString();
488 else
489 path = settings.value( u"/Processing/Configuration/OUTPUTS_FOLDER"_s ).toString();
490
491 const bool dontConfirmOverwrite = mParameter->metadata().value( u"widget_wrapper"_s ).toMap().value( u"dontconfirmoverwrite"_s, false ).toBool();
492
493 QString filename
494 = QFileDialog::getSaveFileName( this, tr( "Save file" ), path, fileFilter, &lastFilter, dontConfirmOverwrite ? QFileDialog::Options( QFileDialog::DontConfirmOverwrite ) : QFileDialog::Options() );
495 if ( !filename.isEmpty() )
496 {
497 mUseTemporary = false;
498 mUseRemapping = false;
499 if ( mParameter->type() == QgsProcessingParameterRasterDestination::typeName() )
500 {
501 const QgsProcessingParameterRasterDestination *rasterParam = static_cast<const QgsProcessingParameterRasterDestination *>( mParameter );
502 const QList<QPair<QString, QString>> formatAndExtensions = rasterParam->supportedOutputRasterLayerFormatAndExtensions();
503 Q_ASSERT( formatAndExtensions.size() + 1 == filters.size() ); // filters have one more entry for all files
504 int idxFilter = 0;
505 for ( const QString &f : filters )
506 {
507 if ( f == lastFilter )
508 {
509 mFormat = formatAndExtensions[idxFilter].first;
510 break;
511 }
512 ++idxFilter;
513 }
514 }
515 filename = QgsFileUtils::addExtensionFromFilter( filename, lastFilter );
516
517 leText->setText( filename );
518
519 settings.setValue( u"/Processing/LastOutputPath"_s, QFileInfo( filename ).path() );
520 if ( !lastFormatPath.isEmpty() && !mFormat.isEmpty() )
521 settings.setValue( lastFormatPath, mFormat );
522 if ( !lastExtPath.isEmpty() )
523 settings.setValue( lastExtPath, QFileInfo( filename ).suffix().toLower() );
524
525 emit skipOutputChanged( false );
526 }
527 // return dialog focus on Mac
528 activateWindow();
529 raise();
530}
531
532void QgsProcessingLayerOutputDestinationWidget::saveToGeopackage()
533{
534 QgsSettings settings;
535 QString lastPath = settings.value( u"/Processing/LastOutputPath"_s, QString() ).toString();
536 if ( lastPath.isEmpty() )
537 lastPath = settings.value( u"/Processing/Configuration/OUTPUTS_FOLDER"_s, QString() ).toString();
538
539 QString filename = QFileDialog::getSaveFileName( this, tr( "Save to GeoPackage" ), lastPath, tr( "GeoPackage files (*.gpkg);;All files (*.*)" ), nullptr, QFileDialog::DontConfirmOverwrite );
540 // return dialog focus on Mac
541 activateWindow();
542 raise();
543
544 if ( filename.isEmpty() )
545 return;
546
547 const QString layerName = QInputDialog::getText( this, tr( "Save to GeoPackage" ), tr( "Layer name" ), QLineEdit::Normal, mParameter->name().toLower() );
548 if ( layerName.isEmpty() )
549 return;
550
551 mUseTemporary = false;
552 mUseRemapping = false;
553
554 filename = QgsFileUtils::ensureFileNameHasExtension( filename, QStringList() << u"gpkg"_s );
555
556 settings.setValue( u"/Processing/LastOutputPath"_s, QFileInfo( filename ).path() );
557
559 uri.setTable( layerName );
560 uri.setDatabase( filename );
561
562 QString geomColumn;
563 if ( const QgsProcessingParameterFeatureSink *sink = dynamic_cast<const QgsProcessingParameterFeatureSink *>( mParameter ) )
564 {
565 if ( sink->hasGeometry() )
566 geomColumn = u"geom"_s;
567 }
568 uri.setGeometryColumn( geomColumn );
569
570 leText->setText( u"ogr:%1"_s.arg( uri.uri() ) );
571
572 emit skipOutputChanged( false );
573}
574
575void QgsProcessingLayerOutputDestinationWidget::saveToDatabase()
576{
577 if ( QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ) )
578 {
579 QgsNewDatabaseTableNameWidget *widget = new QgsNewDatabaseTableNameWidget( mBrowserModel, QStringList() << u"postgres"_s << u"mssql"_s << u"ogr"_s << u"hana"_s << u"spatialite"_s << u"oracle"_s, this );
580 widget->setPanelTitle( tr( "Save “%1” to Database Table" ).arg( mParameter->description() ) );
581 widget->setAcceptButtonVisible( true );
582
583 panel->openPanel( widget );
584
585 auto changed = [this, widget] {
586 mUseTemporary = false;
587 mUseRemapping = false;
588
589 QString geomColumn;
590 if ( const QgsProcessingParameterFeatureSink *sink = dynamic_cast<const QgsProcessingParameterFeatureSink *>( mParameter ) )
591 {
592 if ( sink->hasGeometry() )
593 geomColumn = widget->dataProviderKey() == "oracle"_L1 ? u"GEOM"_s : u"geom"_s;
594 }
595
596 if ( widget->dataProviderKey() == "ogr"_L1 )
597 {
599 uri.setTable( widget->table() );
600 uri.setDatabase( widget->schema() );
601 uri.setGeometryColumn( geomColumn );
602 leText->setText( u"ogr:%1"_s.arg( uri.uri() ) );
603 }
604 else
605 {
606 QgsDataSourceUri uri( widget->uri() );
607 uri.setGeometryColumn( geomColumn );
608 leText->setText( QgsProcessingUtils::encodeProviderKeyAndUri( widget->dataProviderKey(), uri.uri() ) );
609 }
610
611 emit skipOutputChanged( false );
612 };
613
614 connect( widget, &QgsNewDatabaseTableNameWidget::tableNameChanged, this, [changed] { changed(); } );
615 connect( widget, &QgsNewDatabaseTableNameWidget::schemaNameChanged, this, [changed] { changed(); } );
616 connect( widget, &QgsNewDatabaseTableNameWidget::validationChanged, this, [changed] { changed(); } );
617 connect( widget, &QgsNewDatabaseTableNameWidget::providerKeyChanged, this, [changed] { changed(); } );
618 connect( widget, &QgsNewDatabaseTableNameWidget::accepted, this, [changed, widget] {
619 changed();
620 widget->acceptPanel();
621 } );
622 }
623}
624
625void QgsProcessingLayerOutputDestinationWidget::appendToLayer()
626{
627 if ( QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ) )
628 {
630 widget->setPanelTitle( tr( "Append \"%1\" to Layer" ).arg( mParameter->description() ) );
631
632 panel->openPanel( widget );
633
634 connect( widget, &QgsDataSourceSelectWidget::itemTriggered, this, [widget]( const QgsMimeDataUtils::Uri & ) { widget->acceptPanel(); } );
635 connect( widget, &QgsPanelWidget::panelAccepted, this, [this, widget]() {
636 if ( widget->uri().uri.isEmpty() )
637 setValue( QVariant() );
638 else
639 {
640 // get fields for destination
641 auto dest = std::make_unique<QgsVectorLayer>( widget->uri().uri, QString(), widget->uri().providerKey );
642 if ( widget->uri().providerKey == "ogr"_L1 )
643 setAppendDestination( widget->uri().uri, dest->fields() );
644 else
645 setAppendDestination( QgsProcessingUtils::encodeProviderKeyAndUri( widget->uri().providerKey, widget->uri().uri ), dest->fields() );
646 }
647 } );
648 }
649}
650
651
652void QgsProcessingLayerOutputDestinationWidget::setAppendDestination( const QString &uri, const QgsFields &destFields )
653{
654 const QgsProcessingAlgorithm *alg = mParameter->algorithm();
655 QVariantMap props;
656 if ( mParametersGenerator )
657 props = mParametersGenerator->createProcessingParameters();
658 props.insert( mParameter->name(), uri );
659
660 const QgsProcessingAlgorithm::VectorProperties outputProps = alg->sinkProperties( mParameter->name(), props, *mContext, QMap<QString, QgsProcessingAlgorithm::VectorProperties>() );
662 {
663 if ( QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this ) )
664 {
665 // get mapping from fields output by algorithm to destination fields
666 QgsFieldMappingWidget *widget = new QgsFieldMappingWidget( nullptr, outputProps.fields, destFields );
667 widget->setPanelTitle( tr( "Append \"%1\" to Layer" ).arg( mParameter->description() ) );
668 if ( !mRemapDefinition.fieldMap().isEmpty() )
669 widget->setFieldPropertyMap( mRemapDefinition.fieldMap() );
670
671 panel->openPanel( widget );
672
673 connect( widget, &QgsPanelWidget::panelAccepted, this, [this, outputProps, widget, destFields, uri]() {
676 remap.setSourceCrs( outputProps.crs );
677 remap.setFieldMap( widget->fieldPropertyMap() );
678 remap.setDestinationFields( destFields );
679 def.setRemappingDefinition( remap );
680 setValue( def );
681 } );
682 }
683 }
684}
685
686void QgsProcessingLayerOutputDestinationWidget::selectEncoding()
687{
688 QgsEncodingSelectionDialog dialog( this, tr( "File encoding" ), mEncoding );
689 if ( dialog.exec() )
690 {
691 mEncoding = QgsProcessingUtils::resolveDefaultEncoding( dialog.encoding() );
692
693 QgsSettings settings;
694 settings.setValue( u"/Processing/encoding"_s, mEncoding );
695
696 emit destinationChanged();
697 }
698}
699
700void QgsProcessingLayerOutputDestinationWidget::textChanged( const QString &text )
701{
702 // since the text changed we can't use outputIsSkipped() so we check against the previous value
703 const bool prevSkip = !mUseTemporary && mPreviousValueString.isEmpty();
704 if ( prevSkip )
705 {
706 setupPlaceholderText();
707 emit skipOutputChanged( false );
708 }
709
710 mUseRemapping = false;
711
712 if ( couldBeTemporaryLayerName( text ) || text == "memory:"_L1 )
713 {
714 leText->addAction( mActionTemporaryOutputIcon, QLineEdit::LeadingPosition );
715 mUseTemporary = true;
716 }
717 else
718 {
719 leText->removeAction( mActionTemporaryOutputIcon );
720 mUseTemporary = false;
721 }
722
723 // emit destinationChanged only if the value actually changed
724 if ( mPreviousValueString != text )
725 {
726 emit destinationChanged();
727 }
728
729 mPreviousValueString = text;
730}
731
732
733QString QgsProcessingLayerOutputDestinationWidget::mimeDataToPath( const QMimeData *data )
734{
736 for ( const QgsMimeDataUtils::Uri &u : uriList )
737 {
738 if ( ( mParameter->type() == QgsProcessingParameterFeatureSink::typeName()
740 || mParameter->type() == QgsProcessingParameterFileDestination::typeName() )
741 && u.layerType == "vector"_L1
742 && u.providerKey == "ogr"_L1 )
743 {
744 return u.uri;
745 }
746 else if ( ( mParameter->type() == QgsProcessingParameterRasterDestination::typeName() || mParameter->type() == QgsProcessingParameterFileDestination::typeName() )
747 && u.layerType == "raster"_L1
748 && u.providerKey == "gdal"_L1 )
749 {
750 return u.uri;
751 }
752 else if ( ( mParameter->type() == QgsProcessingParameterPointCloudDestination::typeName() || mParameter->type() == QgsProcessingParameterFileDestination::typeName() )
753 && u.layerType == "pointcloud"_L1
754 && ( u.providerKey == "ept"_L1 || u.providerKey == "pdal"_L1 ) )
755 {
756 return u.uri;
757 }
758#if 0
759 else if ( ( mParameter->type() == QgsProcessingParameterMeshDestination::typeName()
760 || mParameter->type() == QgsProcessingParameterFileDestination::typeName() )
761 && u.layerType == "mesh"_L1 && u.providerKey == "mdal"_L1 )
762 return u.uri;
763
764#endif
765 else if ( mParameter->type() == QgsProcessingParameterFolderDestination::typeName() && u.layerType == "directory"_L1 )
766 {
767 return u.uri;
768 }
769 }
770 if ( !uriList.isEmpty() )
771 return QString();
772
773 // files dragged from file explorer, outside of QGIS
774 QStringList rawPaths;
775 if ( data->hasUrls() )
776 {
777 const QList<QUrl> urls = data->urls();
778 rawPaths.reserve( urls.count() );
779 for ( const QUrl &url : urls )
780 {
781 const QString local = url.toLocalFile();
782 if ( !rawPaths.contains( local ) )
783 rawPaths.append( local );
784 }
785 }
786 if ( !data->text().isEmpty() && !rawPaths.contains( data->text() ) )
787 rawPaths.append( data->text() );
788
789 for ( const QString &path : std::as_const( rawPaths ) )
790 {
791 QFileInfo file( path );
792 if ( file.isFile() && ( mParameter->type() == QgsProcessingParameterFeatureSink::typeName() || mParameter->type() == QgsProcessingParameterVectorDestination::typeName() || mParameter->type() == QgsProcessingParameterRasterDestination::typeName() || mParameter->type() == QgsProcessingParameterVectorDestination::typeName() || mParameter->type() == QgsProcessingParameterFileDestination::typeName() || mParameter->type() == QgsProcessingParameterPointCloudDestination::typeName() ) )
793 {
794 // TODO - we should check to see if it's a valid extension for the parameter, but that's non-trivial
795 return path;
796 }
797 else if ( file.isDir() && ( mParameter->type() == QgsProcessingParameterFolderDestination::typeName() ) )
798 return path;
799 }
800
801 return QString();
802}
803
804void QgsProcessingLayerOutputDestinationWidget::dragEnterEvent( QDragEnterEvent *event )
805{
806 if ( !( event->possibleActions() & Qt::CopyAction ) )
807 return;
808
809 const QString path = mimeDataToPath( event->mimeData() );
810 if ( !path.isEmpty() )
811 {
812 // dragged an acceptable path, phew
813 event->setDropAction( Qt::CopyAction );
814 event->accept();
815 leText->setHighlighted( true );
816 }
817}
818
819void QgsProcessingLayerOutputDestinationWidget::dragLeaveEvent( QDragLeaveEvent *event )
820{
821 QWidget::dragLeaveEvent( event );
822 if ( leText->isHighlighted() )
823 {
824 event->accept();
825 leText->setHighlighted( false );
826 }
827}
828
829void QgsProcessingLayerOutputDestinationWidget::dropEvent( QDropEvent *event )
830{
831 if ( !( event->possibleActions() & Qt::CopyAction ) )
832 return;
833
834 const QString path = mimeDataToPath( event->mimeData() );
835 if ( !path.isEmpty() )
836 {
837 // dropped an acceptable path, phew
838 setFocus( Qt::MouseFocusReason );
839 event->setDropAction( Qt::CopyAction );
840 event->accept();
841 setValue( path );
842 }
843 leText->setHighlighted( false );
844}
845
846QString QgsProcessingLayerOutputDestinationWidget::memoryProviderLayerName( const QString &value ) const
847{
848 if ( value == "memory:"_L1 )
849 return QString();
850
851 QString provider;
852 QString uri;
853 bool hasProviderAndUri = QgsProcessingUtils::decodeProviderKeyAndUri( value, provider, uri );
854
855 if ( hasProviderAndUri && provider == "memory"_L1 )
856 {
857 return uri;
858 }
859
860 return QString();
861}
862
863bool QgsProcessingLayerOutputDestinationWidget::couldBeTemporaryLayerName( const QString &value ) const
864{
865 if ( value == QgsProcessing::TEMPORARY_OUTPUT || mParameter->type() != QgsProcessingParameterFeatureSink::typeName() || !mParameter->supportsNonFileBasedOutput() )
866 return false;
867
868 if ( value.isEmpty() )
869 return true;
870
871 if ( value == "memory:"_L1 )
872 return false;
873
874 QString provider;
875 QString uri;
876 const bool hasProviderAndUri = QgsProcessingUtils::decodeProviderKeyAndUri( value, provider, uri );
877
878 if ( provider == "memory"_L1 )
879 return true;
880
881 if ( hasProviderAndUri )
882 return false;
883
884 if ( QFileInfo( value ).isAbsolute() || !QFileInfo( value ).suffix().isEmpty() )
885 return false;
886
887 return true;
888}
889
890bool QgsProcessingLayerOutputDestinationWidget::consideredEqualTemporaryOutputValues( const QString &val1, const QString &val2 ) const
891{
892 if ( ( val1 == "memory:"_L1 && val2 == QgsProcessing::TEMPORARY_OUTPUT ) || ( val1 == QgsProcessing::TEMPORARY_OUTPUT && val2 == "memory:"_L1 ) || val1 == val2 )
893 return true;
894
895 return false;
896}
897
898QString QgsProcessingLayerOutputDestinationWidget::variantToString( const QVariant &value ) const
899{
900 if ( value.userType() == qMetaTypeId<QgsProcessingOutputLayerDefinition>() )
901 {
902 return value.value<QgsProcessingOutputLayerDefinition>().sink.staticValue().toString();
903 }
904 else
905 {
906 return value.toString();
907 }
908}
909
@ Available
Properties are available.
Definition qgis.h:3826
@ Vector
Vector layer.
Definition qgis.h:207
@ Optional
Parameter is optional.
Definition qgis.h:3949
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
Embeds the browser view to select an existing data source.
void itemTriggered(const QgsMimeDataUtils::Uri &uri)
Emitted when an item is triggered, e.g.
QgsMimeDataUtils::Uri uri() const
Returns the (possibly invalid) uri of the selected data source.
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.
Creates a mapping from one set of QgsFields to another, for each set of "destination" fields an expre...
QMap< QString, QgsProperty > fieldPropertyMap() const
Returns a map of destination field name to QgsProperty definition for field value,...
void setFieldPropertyMap(const QMap< QString, QgsProperty > &map)
Sets a map of destination field name to QgsProperty definition for field value.
Container of fields for a vector layer.
Definition qgsfields.h:46
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)
A widget which embeds the browser view to select a DB schema and a new table name.
QString table() const
Returns the current name of the new table.
QString dataProviderKey() const
Returns the currently selected data item provider key.
void setAcceptButtonVisible(bool visible)
Sets whether the optional "Ok"/accept button should be visible.
void providerKeyChanged(const QString &providerKey)
This signal is emitted when the selects a data provider or a schema name that has a different data pr...
QString schema() const
Returns the currently selected schema or file path (in case of filesystem-based DBs like spatialite o...
void tableNameChanged(const QString &tableName)
This signal is emitted when the user enters a table name.
void accepted()
Emitted when the OK/accept button is clicked.
void validationChanged(bool isValid)
This signal is emitted whenever the validation status of the widget changes.
QString uri() const
Returns the (possibly blank) string representation of the new table data source URI.
void schemaNameChanged(const QString &schemaName)
This signal is emitted when the user selects a schema (or file path for filesystem-based DBs like spa...
Base class for any widget that can be shown as an inline panel.
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
void acceptPanel()
Accept the panel.
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
void setPanelTitle(const QString &panelTitle)
Set the title of the panel when shown in the interface.
Abstract base class for processing algorithms.
virtual QgsProcessingAlgorithm::VectorProperties sinkProperties(const QString &sink, const QVariantMap &parameters, 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.
Definition qgssettings.h:68
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.
Definition qgis.h:7187
#define QgsDebugError(str)
Definition qgslogger.h:71
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.