QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
•All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
qgsprocessingalgorithmdialogbase.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsprocessingalgorithmdialogbase.cpp
3 ------------------------------------
4 Date : November 2017
5 Copyright : (C) 2017 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#include "qgssettings.h"
18#include "qgshelp.h"
19#include "qgsmessagebar.h"
20#include "qgsgui.h"
23#include "qgstaskmanager.h"
25#include "qgsstringutils.h"
26#include "qgsapplication.h"
27#include "qgspanelwidget.h"
28#include "qgsjsonutils.h"
29#include "qgsunittypes.h"
30#include <QToolButton>
31#include <QDesktopServices>
32#include <QScrollBar>
33#include <QApplication>
34#include <QClipboard>
35#include <QFileDialog>
36#include <QMimeData>
37#include <QMenu>
38#include <nlohmann/json.hpp>
39
40
42
43QgsProcessingAlgorithmDialogFeedback::QgsProcessingAlgorithmDialogFeedback()
44 : QgsProcessingFeedback( false )
45{}
46
47void QgsProcessingAlgorithmDialogFeedback::setProgressText( const QString &text )
48{
50 emit progressTextChanged( text );
51}
52
53void QgsProcessingAlgorithmDialogFeedback::reportError( const QString &error, bool fatalError )
54{
55 QgsProcessingFeedback::reportError( error, fatalError );
56 emit errorReported( error, fatalError );
57}
58
59void QgsProcessingAlgorithmDialogFeedback::pushWarning( const QString &warning )
60{
62 emit warningPushed( warning );
63}
64
65void QgsProcessingAlgorithmDialogFeedback::pushInfo( const QString &info )
66{
68 emit infoPushed( info );
69}
70
71void QgsProcessingAlgorithmDialogFeedback::pushCommandInfo( const QString &info )
72{
74 emit commandInfoPushed( info );
75}
76
77void QgsProcessingAlgorithmDialogFeedback::pushDebugInfo( const QString &info )
78{
80 emit debugInfoPushed( info );
81}
82
83void QgsProcessingAlgorithmDialogFeedback::pushConsoleInfo( const QString &info )
84{
86 emit consoleInfoPushed( info );
87}
88
89//
90// QgsProcessingAlgorithmDialogBase
91//
92
93QgsProcessingAlgorithmDialogBase::QgsProcessingAlgorithmDialogBase( QWidget *parent, Qt::WindowFlags flags, DialogMode mode )
94 : QDialog( parent, flags )
95 , mMode( mode )
96{
97 setupUi( this );
98
99 //don't collapse parameters panel
100 splitter->setCollapsible( 0, false );
101
102 // add collapse button to splitter
103 QSplitterHandle *splitterHandle = splitter->handle( 1 );
104 QVBoxLayout *handleLayout = new QVBoxLayout();
105 handleLayout->setContentsMargins( 0, 0, 0, 0 );
106 mButtonCollapse = new QToolButton( splitterHandle );
107 mButtonCollapse->setAutoRaise( true );
108 mButtonCollapse->setFixedSize( 12, 12 );
109 mButtonCollapse->setCursor( Qt::ArrowCursor );
110 handleLayout->addWidget( mButtonCollapse );
111 handleLayout->addStretch();
112 splitterHandle->setLayout( handleLayout );
113
115
116 const QgsSettings settings;
117 splitter->restoreState( settings.value( QStringLiteral( "/Processing/dialogBaseSplitter" ), QByteArray() ).toByteArray() );
118 mSplitterState = splitter->saveState();
119 splitterChanged( 0, 0 );
120
121 // Rename OK button to Run
122 mButtonRun = mButtonBox->button( QDialogButtonBox::Ok );
123 mButtonRun->setText( tr( "Run" ) );
124
125 // Rename Yes button. Yes is used to ensure same position of Run and Change Parameters with respect to Close button.
126 mButtonChangeParameters = mButtonBox->button( QDialogButtonBox::Yes );
127 mButtonChangeParameters->setText( tr( "Change Parameters" ) );
128
129 buttonCancel->setEnabled( false );
130 mButtonClose = mButtonBox->button( QDialogButtonBox::Close );
131
132 switch ( mMode )
133 {
134 case DialogMode::Single:
135 {
136 mAdvancedButton = new QPushButton( tr( "Advanced" ) );
137 mAdvancedMenu = new QMenu( this );
138 mAdvancedButton->setMenu( mAdvancedMenu );
139
140 mContextSettingsAction = new QAction( tr( "Algorithm Settings…" ), mAdvancedMenu );
141 mContextSettingsAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/propertyicons/settings.svg" ) ) );
142 mAdvancedMenu->addAction( mContextSettingsAction );
143
144 connect( mContextSettingsAction, &QAction::triggered, this, [this]
145 {
146 if ( QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( mMainWidget ) )
147 {
148 mTabWidget->setCurrentIndex( 0 );
149
150 if ( !mContextOptionsWidget )
151 {
152 mContextOptionsWidget = new QgsProcessingContextOptionsWidget();
153 mContextOptionsWidget->setFromContext( processingContext() );
154 panel->openPanel( mContextOptionsWidget );
155
156 connect( mContextOptionsWidget, &QgsPanelWidget::widgetChanged, this, [ = ]
157 {
158 mOverrideDefaultContextSettings = true;
159 mGeometryCheck = mContextOptionsWidget->invalidGeometryCheck();
160 mDistanceUnits = mContextOptionsWidget->distanceUnit();
161 mAreaUnits = mContextOptionsWidget->areaUnit();
162 mTemporaryFolderOverride = mContextOptionsWidget->temporaryFolder();
163 mMaximumThreads = mContextOptionsWidget->maximumThreads();
164 } );
165 }
166 }
167 } );
168 mAdvancedMenu->addSeparator();
169
170 QAction *copyAsPythonCommand = new QAction( tr( "Copy as Python Command" ), mAdvancedMenu );
171 copyAsPythonCommand->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconPythonFile.svg" ) ) );
172
173 mAdvancedMenu->addAction( copyAsPythonCommand );
174 connect( copyAsPythonCommand, &QAction::triggered, this, [this]
175 {
176 if ( const QgsProcessingAlgorithm *alg = algorithm() )
177 {
178 QgsProcessingContext *context = processingContext();
179 if ( !context )
180 return;
181
182 const QString command = alg->asPythonCommand( createProcessingParameters(), *context );
183 QMimeData *m = new QMimeData();
184 m->setText( command );
185 QClipboard *cb = QApplication::clipboard();
186
187#ifdef Q_OS_LINUX
188 cb->setMimeData( m, QClipboard::Selection );
189#endif
190 cb->setMimeData( m, QClipboard::Clipboard );
191 }
192 } );
193
194 mCopyAsQgisProcessCommand = new QAction( tr( "Copy as qgis_process Command" ), mAdvancedMenu );
195 mCopyAsQgisProcessCommand->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionTerminal.svg" ) ) );
196 mAdvancedMenu->addAction( mCopyAsQgisProcessCommand );
197
198 connect( mCopyAsQgisProcessCommand, &QAction::triggered, this, [this]
199 {
200 if ( const QgsProcessingAlgorithm *alg = algorithm() )
201 {
202 QgsProcessingContext *context = processingContext();
203 if ( !context )
204 return;
205
206 bool ok = false;
207 const QString command = alg->asQgisProcessCommand( createProcessingParameters(), *context, ok );
208 if ( ! ok )
209 {
210 mMessageBar->pushMessage( tr( "Current settings cannot be specified as arguments to qgis_process (Pipe parameters as JSON to qgis_process instead)" ), Qgis::MessageLevel::Warning );
211 }
212 else
213 {
214 QMimeData *m = new QMimeData();
215 m->setText( command );
216 QClipboard *cb = QApplication::clipboard();
217
218#ifdef Q_OS_LINUX
219 cb->setMimeData( m, QClipboard::Selection );
220#endif
221 cb->setMimeData( m, QClipboard::Clipboard );
222 }
223 }
224 } );
225
226 mAdvancedMenu->addSeparator();
227
228 QAction *copyAsJson = new QAction( tr( "Copy as JSON" ), mAdvancedMenu );
229 copyAsJson->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionEditCopy.svg" ) ) );
230
231 mAdvancedMenu->addAction( copyAsJson );
232 connect( copyAsJson, &QAction::triggered, this, [this]
233 {
234 if ( const QgsProcessingAlgorithm *alg = algorithm() )
235 {
236 QgsProcessingContext *context = processingContext();
237 if ( !context )
238 return;
239
240 const QVariantMap properties = alg->asMap( createProcessingParameters(), *context );
241 const QString json = QString::fromStdString( QgsJsonUtils::jsonFromVariant( properties ).dump( 2 ) );
242
243 QMimeData *m = new QMimeData();
244 m->setText( json );
245 QClipboard *cb = QApplication::clipboard();
246
247#ifdef Q_OS_LINUX
248 cb->setMimeData( m, QClipboard::Selection );
249#endif
250 cb->setMimeData( m, QClipboard::Clipboard );
251 }
252 } );
253
254 mPasteJsonAction = new QAction( tr( "Paste Settings" ), mAdvancedMenu );
255 mPasteJsonAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionEditPaste.svg" ) ) );
256
257 mAdvancedMenu->addAction( mPasteJsonAction );
258 connect( mPasteJsonAction, &QAction::triggered, this, [this]
259 {
260 const QString text = QApplication::clipboard()->text();
261 if ( text.isEmpty() )
262 return;
263
264 const QVariantMap parameterValues = QgsJsonUtils::parseJson( text ).toMap().value( QStringLiteral( "inputs" ) ).toMap();
265 if ( parameterValues.isEmpty() )
266 return;
267
268 bool ok = false;
269 QString error;
270 const QVariantMap preparedValues = QgsProcessingUtils::preprocessQgisProcessParameters( parameterValues, ok, error );
271
272 setParameters( preparedValues );
273 } );
274
275 mButtonBox->addButton( mAdvancedButton, QDialogButtonBox::ResetRole );
276 break;
277 }
278
279 case DialogMode::Batch:
280 break;
281 }
282
283 if ( mAdvancedMenu )
284 {
285 connect( mAdvancedMenu, &QMenu::aboutToShow, this, [ = ]
286 {
287 mCopyAsQgisProcessCommand->setEnabled( algorithm()
289 mPasteJsonAction->setEnabled( !QApplication::clipboard()->text().isEmpty() );
290 } );
291 }
292
293 connect( mButtonRun, &QPushButton::clicked, this, &QgsProcessingAlgorithmDialogBase::runAlgorithm );
294 connect( mButtonChangeParameters, &QPushButton::clicked, this, &QgsProcessingAlgorithmDialogBase::showParameters );
295 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsProcessingAlgorithmDialogBase::closeClicked );
296 connect( mButtonBox, &QDialogButtonBox::helpRequested, this, &QgsProcessingAlgorithmDialogBase::openHelp );
297 connect( mButtonCollapse, &QToolButton::clicked, this, &QgsProcessingAlgorithmDialogBase::toggleCollapsed );
298 connect( splitter, &QSplitter::splitterMoved, this, &QgsProcessingAlgorithmDialogBase::splitterChanged );
299
300 connect( mButtonSaveLog, &QToolButton::clicked, this, &QgsProcessingAlgorithmDialogBase::saveLog );
301 connect( mButtonCopyLog, &QToolButton::clicked, this, &QgsProcessingAlgorithmDialogBase::copyLogToClipboard );
302 connect( mButtonClearLog, &QToolButton::clicked, this, &QgsProcessingAlgorithmDialogBase::clearLog );
303
304 connect( mTabWidget, &QTabWidget::currentChanged, this, &QgsProcessingAlgorithmDialogBase::mTabWidget_currentChanged );
305
306 mMessageBar = new QgsMessageBar();
307 mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
308 verticalLayout->insertWidget( 0, mMessageBar );
309
310 connect( QgsApplication::taskManager(), &QgsTaskManager::taskTriggered, this, &QgsProcessingAlgorithmDialogBase::taskTriggered );
311}
312
313QgsProcessingAlgorithmDialogBase::~QgsProcessingAlgorithmDialogBase() = default;
314
315void QgsProcessingAlgorithmDialogBase::setParameters( const QVariantMap & )
316{}
317
318void QgsProcessingAlgorithmDialogBase::setAlgorithm( QgsProcessingAlgorithm *algorithm )
319{
320 mAlgorithm.reset( algorithm );
321 QString title;
323 {
324 title = QgsStringUtils::capitalize( mAlgorithm->displayName(), Qgis::Capitalization::TitleCase );
325 }
326 else
327 {
328 title = mAlgorithm->displayName();
329 }
330 setWindowTitle( title );
331
332 const QString algHelp = formatHelp( algorithm );
333 if ( algHelp.isEmpty() )
334 textShortHelp->hide();
335 else
336 {
337 textShortHelp->document()->setDefaultStyleSheet( QStringLiteral( ".summary { margin-left: 10px; margin-right: 10px; }\n"
338 "h2 { color: #555555; padding-bottom: 15px; }\n"
339 "a { text - decoration: none; color: #3498db; font-weight: bold; }\n"
340 "p { color: #666666; }\n"
341 "b { color: #333333; }\n"
342 "dl dd { margin - bottom: 5px; }" ) );
343 textShortHelp->setHtml( algHelp );
344 connect( textShortHelp, &QTextBrowser::anchorClicked, this, &QgsProcessingAlgorithmDialogBase::linkClicked );
345 textShortHelp->show();
346 }
347
348 if ( algorithm->helpUrl().isEmpty() && ( !algorithm->provider() || algorithm->provider()->helpId().isEmpty() ) )
349 {
350 mButtonBox->removeButton( mButtonBox->button( QDialogButtonBox::Help ) );
351 }
352
353 const QString warning = algorithm->provider() ? algorithm->provider()->warningMessage() : QString();
354 if ( !warning.isEmpty() )
355 {
356 mMessageBar->pushMessage( warning, Qgis::MessageLevel::Warning );
357 }
358}
359
361{
362 return mAlgorithm.get();
363}
364
365void QgsProcessingAlgorithmDialogBase::setMainWidget( QgsPanelWidget *widget )
366{
367 if ( mMainWidget )
368 {
369 mMainWidget->deleteLater();
370 }
371
372 mPanelStack->setMainPanel( widget );
373 widget->setDockMode( true );
374
375 mMainWidget = widget;
376 connect( mMainWidget, &QgsPanelWidget::panelAccepted, this, &QDialog::reject );
377}
378
379QgsPanelWidget *QgsProcessingAlgorithmDialogBase::mainWidget()
380{
381 return mMainWidget;
382}
383
384void QgsProcessingAlgorithmDialogBase::saveLogToFile( const QString &path, const LogFormat format )
385{
386 QFile logFile( path );
387 if ( !logFile.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
388 {
389 return;
390 }
391 QTextStream fout( &logFile );
392
393 switch ( format )
394 {
395 case FormatPlainText:
396 fout << txtLog->toPlainText();
397 break;
398
399 case FormatHtml:
400 fout << txtLog->toHtml();
401 break;
402 }
403}
404
405QgsProcessingFeedback *QgsProcessingAlgorithmDialogBase::createFeedback()
406{
407 auto feedback = std::make_unique< QgsProcessingAlgorithmDialogFeedback >();
408 connect( feedback.get(), &QgsProcessingFeedback::progressChanged, this, &QgsProcessingAlgorithmDialogBase::setPercentage );
409 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::commandInfoPushed, this, &QgsProcessingAlgorithmDialogBase::pushCommandInfo );
410 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::consoleInfoPushed, this, &QgsProcessingAlgorithmDialogBase::pushConsoleInfo );
411 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::debugInfoPushed, this, &QgsProcessingAlgorithmDialogBase::pushDebugInfo );
412 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::errorReported, this, &QgsProcessingAlgorithmDialogBase::reportError );
413 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::warningPushed, this, &QgsProcessingAlgorithmDialogBase::pushWarning );
414 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::infoPushed, this, &QgsProcessingAlgorithmDialogBase::pushInfo );
415 connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::progressTextChanged, this, &QgsProcessingAlgorithmDialogBase::setProgressText );
416 connect( buttonCancel, &QPushButton::clicked, feedback.get(), &QgsProcessingFeedback::cancel );
417 return feedback.release();
418}
419
420QDialogButtonBox *QgsProcessingAlgorithmDialogBase::buttonBox()
421{
422 return mButtonBox;
423}
424
425QTabWidget *QgsProcessingAlgorithmDialogBase::tabWidget()
426{
427 return mTabWidget;
428}
429
430void QgsProcessingAlgorithmDialogBase::showLog()
431{
432 mTabWidget->setCurrentIndex( 1 );
433}
434
435void QgsProcessingAlgorithmDialogBase::showParameters()
436{
437 mTabWidget->setCurrentIndex( 0 );
438}
439
440QPushButton *QgsProcessingAlgorithmDialogBase::runButton()
441{
442 return mButtonRun;
443}
444
445QPushButton *QgsProcessingAlgorithmDialogBase::cancelButton()
446{
447 return buttonCancel;
448}
449
450QPushButton *QgsProcessingAlgorithmDialogBase::changeParametersButton()
451{
452 return mButtonChangeParameters;
453}
454
455void QgsProcessingAlgorithmDialogBase::clearProgress()
456{
457 progressBar->setMaximum( 0 );
458}
459
460void QgsProcessingAlgorithmDialogBase::setExecuted( bool executed )
461{
462 mExecuted = executed;
463}
464
465void QgsProcessingAlgorithmDialogBase::setExecutedAnyResult( bool executedAnyResult )
466{
467 mExecutedAnyResult = executedAnyResult;
468}
469
470void QgsProcessingAlgorithmDialogBase::setResults( const QVariantMap &results )
471{
472 mResults = results;
473}
474
475void QgsProcessingAlgorithmDialogBase::finished( bool, const QVariantMap &, QgsProcessingContext &, QgsProcessingFeedback * )
476{
477
478}
479
480void QgsProcessingAlgorithmDialogBase::openHelp()
481{
482 QUrl algHelp = mAlgorithm->helpUrl();
483 if ( algHelp.isEmpty() && mAlgorithm->provider() )
484 {
485 algHelp = QgsHelp::helpUrl( QStringLiteral( "processing_algs/%1/%2.html#%3" ).arg( mAlgorithm->provider()->helpId(), mAlgorithm->groupId(), QStringLiteral( "%1%2" ).arg( mAlgorithm->provider()->helpId() ).arg( mAlgorithm->name() ) ) );
486 }
487
488 if ( !algHelp.isEmpty() )
489 QDesktopServices::openUrl( algHelp );
490}
491
492void QgsProcessingAlgorithmDialogBase::toggleCollapsed()
493{
494 if ( mHelpCollapsed )
495 {
496 splitter->restoreState( mSplitterState );
497 mButtonCollapse->setArrowType( Qt::RightArrow );
498 }
499 else
500 {
501 mSplitterState = splitter->saveState();
502 splitter->setSizes( QList<int>() << 1 << 0 );
503 mButtonCollapse->setArrowType( Qt::LeftArrow );
504 }
505 mHelpCollapsed = !mHelpCollapsed;
506}
507
508void QgsProcessingAlgorithmDialogBase::splitterChanged( int, int )
509{
510 if ( splitter->sizes().at( 1 ) == 0 )
511 {
512 mHelpCollapsed = true;
513 mButtonCollapse->setArrowType( Qt::LeftArrow );
514 }
515 else
516 {
517 mHelpCollapsed = false;
518 mButtonCollapse->setArrowType( Qt::RightArrow );
519 }
520}
521
522void QgsProcessingAlgorithmDialogBase::mTabWidget_currentChanged( int )
523{
524 updateRunButtonVisibility();
525}
526
527void QgsProcessingAlgorithmDialogBase::linkClicked( const QUrl &url )
528{
529 QDesktopServices::openUrl( url.toString() );
530}
531
532void QgsProcessingAlgorithmDialogBase::algExecuted( bool successful, const QVariantMap & )
533{
534 mAlgorithmTask = nullptr;
535
536 if ( !successful )
537 {
538 // show dialog to display errors
539 show();
540 raise();
541 setWindowState( ( windowState() & ~Qt::WindowMinimized ) | Qt::WindowActive );
542 activateWindow();
543 showLog();
544 }
545 else
546 {
547 if ( isFinalized() && successful )
548 {
549 progressBar->setFormat( tr( "Complete" ) );
550 }
551
552 // delete dialog if closed
553 if ( isFinalized() && !isVisible() )
554 {
555 deleteLater();
556 }
557 }
558}
559
560void QgsProcessingAlgorithmDialogBase::taskTriggered( QgsTask *task )
561{
562 if ( task == mAlgorithmTask )
563 {
564 show();
565 raise();
566 setWindowState( ( windowState() & ~Qt::WindowMinimized ) | Qt::WindowActive );
567 activateWindow();
568 showLog();
569 }
570}
571
572void QgsProcessingAlgorithmDialogBase::closeClicked()
573{
574 reject();
575 close();
576}
577
578QgsProcessingContext::LogLevel QgsProcessingAlgorithmDialogBase::logLevel() const
579{
580 return mLogLevel;
581}
582
583void QgsProcessingAlgorithmDialogBase::setLogLevel( QgsProcessingContext::LogLevel level )
584{
585 mLogLevel = level;
586}
587
588void QgsProcessingAlgorithmDialogBase::reportError( const QString &error, bool fatalError )
589{
590 setInfo( error, true );
591 if ( fatalError )
592 resetGui();
593 showLog();
594 processEvents();
595}
596
597void QgsProcessingAlgorithmDialogBase::pushWarning( const QString &warning )
598{
599 setInfo( warning, false, true, true );
600 processEvents();
601}
602
603void QgsProcessingAlgorithmDialogBase::pushInfo( const QString &info )
604{
605 setInfo( info );
606 processEvents();
607}
608
609void QgsProcessingAlgorithmDialogBase::pushCommandInfo( const QString &command )
610{
611 txtLog->append( QStringLiteral( "<code>%1<code>" ).arg( formatStringForLog( command.toHtmlEscaped() ) ) );
612 scrollToBottomOfLog();
613 processEvents();
614}
615
616void QgsProcessingAlgorithmDialogBase::pushDebugInfo( const QString &message )
617{
618 txtLog->append( QStringLiteral( "<span style=\"color:#777\">%1</span>" ).arg( formatStringForLog( message.toHtmlEscaped() ) ) );
619 scrollToBottomOfLog();
620 processEvents();
621}
622
623void QgsProcessingAlgorithmDialogBase::pushConsoleInfo( const QString &info )
624{
625 txtLog->append( QStringLiteral( "<code style=\"color:#777\">%1</code>" ).arg( formatStringForLog( info.toHtmlEscaped() ) ) );
626 scrollToBottomOfLog();
627 processEvents();
628}
629
630QDialog *QgsProcessingAlgorithmDialogBase::createProgressDialog()
631{
632 QgsProcessingAlgorithmProgressDialog *dialog = new QgsProcessingAlgorithmProgressDialog( this );
633 dialog->setWindowModality( Qt::ApplicationModal );
634 dialog->setWindowTitle( windowTitle() );
635 dialog->setGeometry( geometry() ); // match size/position to this dialog
636 connect( progressBar, &QProgressBar::valueChanged, dialog->progressBar(), &QProgressBar::setValue );
637 connect( dialog->cancelButton(), &QPushButton::clicked, buttonCancel, &QPushButton::click );
638 dialog->logTextEdit()->setHtml( txtLog->toHtml() );
639 connect( txtLog, &QTextEdit::textChanged, dialog, [this, dialog]()
640 {
641 dialog->logTextEdit()->setHtml( txtLog->toHtml() );
642 QScrollBar *sb = dialog->logTextEdit()->verticalScrollBar();
643 sb->setValue( sb->maximum() );
644 } );
645 return dialog;
646}
647
648void QgsProcessingAlgorithmDialogBase::clearLog()
649{
650 txtLog->clear();
651}
652
653void QgsProcessingAlgorithmDialogBase::saveLog()
654{
655 QgsSettings settings;
656 const QString lastUsedDir = settings.value( QStringLiteral( "/Processing/lastUsedLogDirectory" ), QDir::homePath() ).toString();
657
658 QString filter;
659 const QString txtExt = tr( "Text files" ) + QStringLiteral( " (*.txt *.TXT)" );
660 const QString htmlExt = tr( "HTML files" ) + QStringLiteral( " (*.html *.HTML)" );
661
662 const QString path = QFileDialog::getSaveFileName( this, tr( "Save Log to File" ), lastUsedDir, txtExt + ";;" + htmlExt, &filter );
663 if ( path.isEmpty() )
664 {
665 return;
666 }
667
668 settings.setValue( QStringLiteral( "/Processing/lastUsedLogDirectory" ), QFileInfo( path ).path() );
669
670 LogFormat format = FormatPlainText;
671 if ( filter == htmlExt )
672 {
673 format = FormatHtml;
674 }
675 saveLogToFile( path, format );
676}
677
678void QgsProcessingAlgorithmDialogBase::copyLogToClipboard()
679{
680 QMimeData *m = new QMimeData();
681 m->setText( txtLog->toPlainText() );
682 m->setHtml( txtLog->toHtml() );
683 QClipboard *cb = QApplication::clipboard();
684
685#ifdef Q_OS_LINUX
686 cb->setMimeData( m, QClipboard::Selection );
687#endif
688 cb->setMimeData( m, QClipboard::Clipboard );
689}
690
691void QgsProcessingAlgorithmDialogBase::closeEvent( QCloseEvent *e )
692{
693 if ( !mHelpCollapsed )
694 {
695 QgsSettings settings;
696 settings.setValue( QStringLiteral( "/Processing/dialogBaseSplitter" ), splitter->saveState() );
697 }
698
699 QDialog::closeEvent( e );
700
701 if ( !mAlgorithmTask && isFinalized() )
702 {
703 // when running a background task, the dialog is kept around and deleted only when the task
704 // completes. But if not running a task, we auto cleanup (later - gotta give callers a chance
705 // to retrieve results and execution status).
706 deleteLater();
707 }
708}
709
710void QgsProcessingAlgorithmDialogBase::runAlgorithm()
711{
712
713}
714
715void QgsProcessingAlgorithmDialogBase::setPercentage( double percent )
716{
717 // delay setting maximum progress value until we know algorithm reports progress
718 if ( progressBar->maximum() == 0 )
719 progressBar->setMaximum( 100 );
720 progressBar->setValue( percent );
721 processEvents();
722}
723
724void QgsProcessingAlgorithmDialogBase::setProgressText( const QString &text )
725{
726 lblProgress->setText( text );
727 setInfo( text, false );
728 scrollToBottomOfLog();
729 processEvents();
730}
731
732QString QgsProcessingAlgorithmDialogBase::formatHelp( QgsProcessingAlgorithm *algorithm )
733{
734 const QString text = algorithm->shortHelpString();
735 if ( !text.isEmpty() )
736 {
737 const QStringList paragraphs = text.split( '\n' );
738 QString help;
739 for ( const QString &paragraph : paragraphs )
740 {
741 help += QStringLiteral( "<p>%1</p>" ).arg( paragraph );
742 }
743 return QStringLiteral( "<h2>%1</h2>%2" ).arg( algorithm->displayName(), help );
744 }
745 else if ( !algorithm->shortDescription().isEmpty() )
746 {
747 return QStringLiteral( "<h2>%1</h2><p>%2</p>" ).arg( algorithm->displayName(), algorithm->shortDescription() );
748 }
749 else
750 return QString();
751}
752
753void QgsProcessingAlgorithmDialogBase::processEvents()
754{
755 if ( mAlgorithmTask )
756 {
757 // no need to call this - the algorithm is running in a thread.
758 // in fact, calling it causes a crash on Windows when the algorithm
759 // is running in a background thread... unfortunately we need something
760 // like this for non-threadable algorithms, otherwise there's no chance
761 // for users to hit cancel or see progress updates...
762 return;
763 }
764
765 // So that we get a chance of hitting the Abort button
766#ifdef Q_OS_LINUX
767 // One iteration is actually enough on Windows to get good interactivity
768 // whereas on Linux we must allow for far more iterations.
769 // For safety limit the number of iterations
770 int nIters = 0;
771 while ( ++nIters < 100 )
772#endif
773 {
774 QCoreApplication::processEvents();
775 }
776}
777
778void QgsProcessingAlgorithmDialogBase::scrollToBottomOfLog()
779{
780 QScrollBar *sb = txtLog->verticalScrollBar();
781 sb->setValue( sb->maximum() );
782}
783
784void QgsProcessingAlgorithmDialogBase::resetGui()
785{
786 lblProgress->clear();
787 progressBar->setMaximum( 100 );
788 progressBar->setValue( 0 );
789 mButtonRun->setEnabled( true );
790 mButtonChangeParameters->setEnabled( true );
791 mButtonClose->setEnabled( true );
792 if ( mMainWidget )
793 {
794 mMainWidget->setEnabled( true );
795 }
796 updateRunButtonVisibility();
797 resetAdditionalGui();
798}
799
800void QgsProcessingAlgorithmDialogBase::updateRunButtonVisibility()
801{
802 // Activate run button if current tab is Parameters
803 const bool runButtonVisible = mTabWidget->currentIndex() == 0;
804 mButtonRun->setVisible( runButtonVisible );
805 if ( runButtonVisible )
806 progressBar->resetFormat();
807 mButtonChangeParameters->setVisible( !runButtonVisible && mExecutedAnyResult && mButtonChangeParameters->isEnabled() );
808}
809
810void QgsProcessingAlgorithmDialogBase::resetAdditionalGui()
811{
812
813}
814
815void QgsProcessingAlgorithmDialogBase::blockControlsWhileRunning()
816{
817 mButtonRun->setEnabled( false );
818 mButtonChangeParameters->setEnabled( false );
819 if ( mMainWidget )
820 {
821 mMainWidget->setEnabled( false );
822 }
823 blockAdditionalControlsWhileRunning();
824}
825
826void QgsProcessingAlgorithmDialogBase::blockAdditionalControlsWhileRunning()
827{
828
829}
830
831QgsMessageBar *QgsProcessingAlgorithmDialogBase::messageBar()
832{
833 return mMessageBar;
834}
835
836void QgsProcessingAlgorithmDialogBase::hideShortHelp()
837{
838 textShortHelp->setVisible( false );
839}
840
841void QgsProcessingAlgorithmDialogBase::setCurrentTask( QgsProcessingAlgRunnerTask *task )
842{
843 mAlgorithmTask = task;
844 connect( mAlgorithmTask, &QgsProcessingAlgRunnerTask::executed, this, &QgsProcessingAlgorithmDialogBase::algExecuted );
845 QgsApplication::taskManager()->addTask( mAlgorithmTask );
846}
847
848QString QgsProcessingAlgorithmDialogBase::formatStringForLog( const QString &string )
849{
850 QString s = string;
851 s.replace( '\n', QLatin1String( "<br>" ) );
852 return s;
853}
854
855bool QgsProcessingAlgorithmDialogBase::isFinalized()
856{
857 return true;
858}
859
860void QgsProcessingAlgorithmDialogBase::applyContextOverrides( QgsProcessingContext *context )
861{
862 if ( !context )
863 return;
864
865 context->setLogLevel( logLevel() );
866
867 if ( mOverrideDefaultContextSettings )
868 {
869 context->setInvalidGeometryCheck( mGeometryCheck );
870 context->setDistanceUnit( mDistanceUnits );
871 context->setAreaUnit( mAreaUnits );
872 context->setTemporaryFolder( mTemporaryFolderOverride );
873 context->setMaximumThreads( mMaximumThreads );
874 }
875}
876
877void QgsProcessingAlgorithmDialogBase::setInfo( const QString &message, bool isError, bool escapeHtml, bool isWarning )
878{
879 constexpr int MESSAGE_COUNT_LIMIT = 10000;
880 // Avoid logging too many messages, which might blow memory.
881 if ( mMessageLoggedCount == MESSAGE_COUNT_LIMIT )
882 return;
883 ++mMessageLoggedCount;
884
885 // note -- we have to wrap the message in a span block, or QTextEdit::append sometimes gets confused
886 // and varies between treating it as a HTML string or a plain text string! (see https://github.com/qgis/QGIS/issues/37934)
887 if ( mMessageLoggedCount == MESSAGE_COUNT_LIMIT )
888 txtLog->append( QStringLiteral( "<span style=\"color:red\">%1</span>" ).arg( tr( "Message log truncated" ) ) );
889 else if ( isError || isWarning )
890 txtLog->append( QStringLiteral( "<span style=\"color:%1\">%2</span>" ).arg( isError ? QStringLiteral( "red" ) : QStringLiteral( "#b85a20" ), escapeHtml ? formatStringForLog( message.toHtmlEscaped() ) : formatStringForLog( message ) ) );
891 else if ( escapeHtml )
892 txtLog->append( QStringLiteral( "<span>%1</span" ).arg( formatStringForLog( message.toHtmlEscaped() ) ) );
893 else
894 txtLog->append( QStringLiteral( "<span>%1</span>" ).arg( formatStringForLog( message ) ) );
895 scrollToBottomOfLog();
896 processEvents();
897}
898
899void QgsProcessingAlgorithmDialogBase::reject()
900{
901 if ( !mAlgorithmTask && isFinalized() )
902 {
903 setAttribute( Qt::WA_DeleteOnClose );
904 }
905 QDialog::reject();
906}
907
908//
909// QgsProcessingAlgorithmProgressDialog
910//
911
912QgsProcessingAlgorithmProgressDialog::QgsProcessingAlgorithmProgressDialog( QWidget *parent )
913 : QDialog( parent )
914{
915 setupUi( this );
916}
917
918QProgressBar *QgsProcessingAlgorithmProgressDialog::progressBar()
919{
920 return mProgressBar;
921}
922
923QPushButton *QgsProcessingAlgorithmProgressDialog::cancelButton()
924{
925 return mButtonBox->button( QDialogButtonBox::Cancel );
926}
927
928QTextEdit *QgsProcessingAlgorithmProgressDialog::logTextEdit()
929{
930 return mTxtLog;
931}
932
933void QgsProcessingAlgorithmProgressDialog::reject()
934{
935
936}
937
938
939//
940// QgsProcessingContextOptionsWidget
941//
942
943QgsProcessingContextOptionsWidget::QgsProcessingContextOptionsWidget( QWidget *parent )
944 : QgsPanelWidget( parent )
945{
946 setupUi( this );
947 setPanelTitle( tr( "Algorithm Settings" ) );
948
949 mComboInvalidFeatureFiltering->addItem( tr( "Do not Filter (Better Performance)" ), QgsFeatureRequest::GeometryNoCheck );
950 mComboInvalidFeatureFiltering->addItem( tr( "Skip (Ignore) Features with Invalid Geometries" ), QgsFeatureRequest::GeometrySkipInvalid );
951 mComboInvalidFeatureFiltering->addItem( tr( "Stop Algorithm Execution When a Geometry is Invalid" ), QgsFeatureRequest::GeometryAbortOnInvalid );
952
953 mTemporaryFolderWidget->setDialogTitle( tr( "Select Temporary Directory" ) );
954 mTemporaryFolderWidget->setStorageMode( QgsFileWidget::GetDirectory );
955 mTemporaryFolderWidget->lineEdit()->setPlaceholderText( tr( "Default" ) );
956
957 mDistanceUnitsCombo->addItem( tr( "Default" ), QVariant::fromValue( Qgis::DistanceUnit::Unknown ) );
958 for ( Qgis::DistanceUnit unit :
959 {
960 Qgis::DistanceUnit::Meters,
961 Qgis::DistanceUnit::Kilometers,
962 Qgis::DistanceUnit::Centimeters,
963 Qgis::DistanceUnit::Millimeters,
964 Qgis::DistanceUnit::Feet,
965 Qgis::DistanceUnit::Miles,
966 Qgis::DistanceUnit::NauticalMiles,
967 Qgis::DistanceUnit::Yards,
969 Qgis::DistanceUnit::Degrees,
970 } )
971 {
972 QString title;
974 {
976 }
977 else
978 {
979 title = QgsUnitTypes::toString( unit );
980 }
981
982 mDistanceUnitsCombo->addItem( title, QVariant::fromValue( unit ) );
983 }
984
985 mAreaUnitsCombo->addItem( tr( "Default" ), QVariant::fromValue( Qgis::AreaUnit::Unknown ) );
986 for ( Qgis::AreaUnit unit :
987 {
988 Qgis::AreaUnit::SquareMeters,
989 Qgis::AreaUnit::Hectares,
990 Qgis::AreaUnit::SquareKilometers,
991 Qgis::AreaUnit::SquareCentimeters,
992 Qgis::AreaUnit::SquareMillimeters,
993 Qgis::AreaUnit::SquareFeet,
994 Qgis::AreaUnit::SquareMiles,
995 Qgis::AreaUnit::SquareNauticalMiles,
996 Qgis::AreaUnit::SquareYards,
998 Qgis::AreaUnit::Acres,
999 Qgis::AreaUnit::SquareDegrees,
1000 } )
1001 {
1002 QString title;
1004 {
1006 }
1007 else
1008 {
1009 title = QgsUnitTypes::toString( unit );
1010 }
1011
1012 mAreaUnitsCombo->addItem( title, QVariant::fromValue( unit ) );
1013 }
1014
1015 mThreadsSpinBox->setRange( 1, QThread::idealThreadCount() );
1016
1017 connect( mComboInvalidFeatureFiltering, qOverload< int >( &QComboBox::currentIndexChanged ), this, &QgsPanelWidget::widgetChanged );
1018 connect( mDistanceUnitsCombo, qOverload< int >( &QComboBox::currentIndexChanged ), this, &QgsPanelWidget::widgetChanged );
1019 connect( mAreaUnitsCombo, qOverload< int >( &QComboBox::currentIndexChanged ), this, &QgsPanelWidget::widgetChanged );
1020 connect( mTemporaryFolderWidget, &QgsFileWidget::fileChanged, this, &QgsPanelWidget::widgetChanged );
1021 connect( mThreadsSpinBox, qOverload< int >( &QSpinBox::valueChanged ), this, &QgsPanelWidget::widgetChanged );
1022}
1023
1024void QgsProcessingContextOptionsWidget::setFromContext( const QgsProcessingContext *context )
1025{
1026 whileBlocking( mComboInvalidFeatureFiltering )->setCurrentIndex( mComboInvalidFeatureFiltering->findData( static_cast< int >( context->invalidGeometryCheck() ) ) );
1027 whileBlocking( mDistanceUnitsCombo )->setCurrentIndex( mDistanceUnitsCombo->findData( QVariant::fromValue( context->distanceUnit() ) ) );
1028 whileBlocking( mAreaUnitsCombo )->setCurrentIndex( mAreaUnitsCombo->findData( QVariant::fromValue( context->areaUnit() ) ) );
1029 whileBlocking( mTemporaryFolderWidget )->setFilePath( context->temporaryFolder() );
1030 whileBlocking( mThreadsSpinBox )->setValue( context->maximumThreads() );
1031}
1032
1033QgsFeatureRequest::InvalidGeometryCheck QgsProcessingContextOptionsWidget::invalidGeometryCheck() const
1034{
1035 return static_cast< QgsFeatureRequest::InvalidGeometryCheck >( mComboInvalidFeatureFiltering->currentData().toInt() );
1036}
1037
1038Qgis::DistanceUnit QgsProcessingContextOptionsWidget::distanceUnit() const
1039{
1040 return mDistanceUnitsCombo->currentData().value< Qgis::DistanceUnit >();
1041}
1042
1043Qgis::AreaUnit QgsProcessingContextOptionsWidget::areaUnit() const
1044{
1045 return mAreaUnitsCombo->currentData().value< Qgis::AreaUnit >();
1046}
1047
1048QString QgsProcessingContextOptionsWidget::temporaryFolder()
1049{
1050 return mTemporaryFolderWidget->filePath();
1051}
1052
1053int QgsProcessingContextOptionsWidget::maximumThreads() const
1054{
1055 return mThreadsSpinBox->value();
1056}
1057
DistanceUnit
Units of distance.
Definition: qgis.h:3310
@ Inches
Inches (since QGIS 3.32)
AreaUnit
Units of area.
Definition: qgis.h:3348
@ SquareInches
Square inches (since QGIS 3.32)
@ TitleCase
Simple title case conversion - does not fully grammatically parse the text and uses simple rules only...
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsTaskManager * taskManager()
Returns the application's task manager, used for managing application wide background task handling.
InvalidGeometryCheck
Handling of features with invalid geometries.
@ GeometryNoCheck
No invalid geometry checking.
@ GeometryAbortOnInvalid
Close iterator on encountering any features with invalid geometry. This requires a slow geometry vali...
@ GeometrySkipInvalid
Skip any features with invalid geometry. This requires a slow geometry validity check for every featu...
void progressChanged(double progress)
Emitted when the feedback object reports a progress change.
void cancel()
Tells the internal routines that the current operation should be canceled. This should be run by the ...
Definition: qgsfeedback.h:108
@ GetDirectory
Select a directory.
Definition: qgsfilewidget.h:69
void fileChanged(const QString &path)
Emitted whenever the current file or directory path is changed.
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
Definition: qgsgui.cpp:193
@ HigDialogTitleIsTitleCase
Dialog titles should be title case.
Definition: qgsgui.h:249
static QgsGui::HigFlags higFlags()
Returns the platform's HIG flags.
Definition: qgsgui.cpp:212
static QUrl helpUrl(const QString &key)
Returns URI of the help topic for the given key.
Definition: qgshelp.cpp:43
static QVariant parseJson(const std::string &jsonString)
Converts JSON jsonString to a QVariant, in case of parsing error an invalid QVariant is returned and ...
static json jsonFromVariant(const QVariant &v)
Converts a QVariant v to a json object.
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:61
Base class for any widget that can be shown as a inline panel.
void panelAccepted(QgsPanelWidget *panel)
Emitted when the panel is accepted by the user.
void widgetChanged()
Emitted when the widget state changes.
static QgsPanelWidget * findParentPanel(QWidget *widget)
Traces through the parents of a widget to find if it is contained within a QgsPanelWidget widget.
virtual void setDockMode(bool dockMode)
Set the widget in dock mode which tells the widget to emit panel widgets and not open dialogs.
QgsTask task which runs a QgsProcessingAlgorithm in a background task.
void executed(bool successful, const QVariantMap &results)
Emitted when the algorithm has finished execution.
Abstract base class for processing algorithms.
virtual QString helpUrl() const
Returns a url pointing to the algorithm's help page.
virtual QString shortHelpString() const
Returns a localised short helper string for the algorithm.
virtual QString shortDescription() const
Returns an optional translated short description of the algorithm.
@ FlagNotAvailableInStandaloneTool
Algorithm should not be available from the standalone "qgis_process" tool. Used to flag algorithms wh...
@ FlagDisplayNameIsLiteral
Algorithm's display name is a static literal string, and should not be translated or automatically fo...
virtual QString displayName() const =0
Returns the translated algorithm name, which should be used for any user-visible display of the algor...
QgsProcessingProvider * provider() const
Returns the provider to which this algorithm belongs.
Contains information about the context in which a processing algorithm is executed.
LogLevel
Logging level for algorithms to use when pushing feedback messages.
Qgis::AreaUnit areaUnit() const
Returns the area unit to use for area calculations.
void setMaximumThreads(int threads)
Sets the (optional) number of threads to use when running algorithms.
void setDistanceUnit(Qgis::DistanceUnit unit)
Sets the unit to use for distance calculations.
void setAreaUnit(Qgis::AreaUnit areaUnit)
Sets the unit to use for area calculations.
Qgis::DistanceUnit distanceUnit() const
Returns the distance unit to use for distance calculations.
void setLogLevel(LogLevel level)
Sets the logging level for algorithms to use when pushing feedback messages to users.
void setInvalidGeometryCheck(QgsFeatureRequest::InvalidGeometryCheck check)
Sets the behavior used for checking invalid geometries in input layers.
QgsFeatureRequest::InvalidGeometryCheck invalidGeometryCheck() const
Returns the behavior used for checking invalid geometries in input layers.
void setTemporaryFolder(const QString &folder)
Sets the (optional) temporary folder to use when running algorithms.
QString temporaryFolder() const
Returns the (optional) temporary folder to use when running algorithms.
int maximumThreads() const
Returns the (optional) number of threads to use when running algorithms.
QgsProcessingAlgorithm::Flags flags() const override
Returns the flags indicating how and when the algorithm operates and should be exposed to users.
Base class for providing feedback from a processing algorithm.
virtual void pushCommandInfo(const QString &info)
Pushes an informational message containing a command from the algorithm.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
virtual void pushWarning(const QString &warning)
Pushes a warning informational message from the algorithm.
virtual void pushDebugInfo(const QString &info)
Pushes an informational message containing debugging helpers from the algorithm.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
virtual void pushConsoleInfo(const QString &info)
Pushes a console feedback message from the algorithm.
virtual void setProgressText(const QString &text)
Sets a progress report text string.
virtual QString helpId() const
Returns the provider help id string, used for creating QgsHelp urls for algorithms belong to this pro...
virtual QString warningMessage() const
Returns an optional warning message to show users when running algorithms from this provider.
static QVariantMap preprocessQgisProcessParameters(const QVariantMap &parameters, bool &ok, QString &error)
Pre-processes a set of parameter values for the qgis_process command.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:63
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
static QString capitalize(const QString &string, Qgis::Capitalization capitalization)
Converts a string by applying capitalization rules to the string.
long addTask(QgsTask *task, int priority=0)
Adds a task to the manager.
void taskTriggered(QgsTask *task)
Emitted when a task is triggered.
Abstract base class for long running background tasks.
static Q_INVOKABLE QString toString(Qgis::DistanceUnit unit)
Returns a translated string representing a distance unit.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into allowing algorithms to be written in pure substantial changes are required in order to port existing x Processing algorithms for QGIS x The most significant changes are outlined not GeoAlgorithm For algorithms which operate on features one by consider subclassing the QgsProcessingFeatureBasedAlgorithm class This class allows much of the boilerplate code for looping over features from a vector layer to be bypassed and instead requires implementation of a processFeature method Ensure that your algorithm(or algorithm 's parent class) implements the new pure virtual createInstance(self) call
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:3914