QGIS API Documentation  3.27.0-Master (11ef3e5184)
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 <QToolButton>
30 #include <QDesktopServices>
31 #include <QScrollBar>
32 #include <QApplication>
33 #include <QClipboard>
34 #include <QFileDialog>
35 #include <QMimeData>
36 #include <QMenu>
37 #include <nlohmann/json.hpp>
38 
39 
41 
42 QgsProcessingAlgorithmDialogFeedback::QgsProcessingAlgorithmDialogFeedback()
43  : QgsProcessingFeedback( false )
44 {}
45 
46 void QgsProcessingAlgorithmDialogFeedback::setProgressText( const QString &text )
47 {
49  emit progressTextChanged( text );
50 }
51 
52 void QgsProcessingAlgorithmDialogFeedback::reportError( const QString &error, bool fatalError )
53 {
54  QgsProcessingFeedback::reportError( error, fatalError );
55  emit errorReported( error, fatalError );
56 }
57 
58 void QgsProcessingAlgorithmDialogFeedback::pushWarning( const QString &warning )
59 {
61  emit warningPushed( warning );
62 }
63 
64 void QgsProcessingAlgorithmDialogFeedback::pushInfo( const QString &info )
65 {
67  emit infoPushed( info );
68 }
69 
70 void QgsProcessingAlgorithmDialogFeedback::pushCommandInfo( const QString &info )
71 {
73  emit commandInfoPushed( info );
74 }
75 
76 void QgsProcessingAlgorithmDialogFeedback::pushDebugInfo( const QString &info )
77 {
79  emit debugInfoPushed( info );
80 }
81 
82 void QgsProcessingAlgorithmDialogFeedback::pushConsoleInfo( const QString &info )
83 {
85  emit consoleInfoPushed( info );
86 }
87 
88 //
89 // QgsProcessingAlgorithmDialogBase
90 //
91 
92 QgsProcessingAlgorithmDialogBase::QgsProcessingAlgorithmDialogBase( QWidget *parent, Qt::WindowFlags flags, DialogMode mode )
93  : QDialog( parent, flags )
94  , mMode( mode )
95 {
96  setupUi( this );
97 
98  //don't collapse parameters panel
99  splitter->setCollapsible( 0, false );
100 
101  // add collapse button to splitter
102  QSplitterHandle *splitterHandle = splitter->handle( 1 );
103  QVBoxLayout *handleLayout = new QVBoxLayout();
104  handleLayout->setContentsMargins( 0, 0, 0, 0 );
105  mButtonCollapse = new QToolButton( splitterHandle );
106  mButtonCollapse->setAutoRaise( true );
107  mButtonCollapse->setFixedSize( 12, 12 );
108  mButtonCollapse->setCursor( Qt::ArrowCursor );
109  handleLayout->addWidget( mButtonCollapse );
110  handleLayout->addStretch();
111  splitterHandle->setLayout( handleLayout );
112 
114 
115  const QgsSettings settings;
116  splitter->restoreState( settings.value( QStringLiteral( "/Processing/dialogBaseSplitter" ), QByteArray() ).toByteArray() );
117  mSplitterState = splitter->saveState();
118  splitterChanged( 0, 0 );
119 
120  // Rename OK button to Run
121  mButtonRun = mButtonBox->button( QDialogButtonBox::Ok );
122  mButtonRun->setText( tr( "Run" ) );
123 
124  // Rename Yes button. Yes is used to ensure same position of Run and Change Parameters with respect to Close button.
125  mButtonChangeParameters = mButtonBox->button( QDialogButtonBox::Yes );
126  mButtonChangeParameters->setText( tr( "Change Parameters" ) );
127 
128  buttonCancel->setEnabled( false );
129  mButtonClose = mButtonBox->button( QDialogButtonBox::Close );
130 
131  switch ( mMode )
132  {
133  case DialogMode::Single:
134  {
135  mAdvancedButton = new QPushButton( tr( "Advanced" ) );
136  mAdvancedMenu = new QMenu( this );
137  mAdvancedButton->setMenu( mAdvancedMenu );
138 
139  QAction *copyAsPythonCommand = new QAction( tr( "Copy as Python Command" ), mAdvancedMenu );
140  copyAsPythonCommand->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconPythonFile.svg" ) ) );
141 
142  mAdvancedMenu->addAction( copyAsPythonCommand );
143  connect( copyAsPythonCommand, &QAction::triggered, this, [this]
144  {
145  if ( const QgsProcessingAlgorithm *alg = algorithm() )
146  {
147  QgsProcessingContext *context = processingContext();
148  if ( !context )
149  return;
150 
151  const QString command = alg->asPythonCommand( createProcessingParameters(), *context );
152  QMimeData *m = new QMimeData();
153  m->setText( command );
154  QClipboard *cb = QApplication::clipboard();
155 
156 #ifdef Q_OS_LINUX
157  cb->setMimeData( m, QClipboard::Selection );
158 #endif
159  cb->setMimeData( m, QClipboard::Clipboard );
160  }
161  } );
162 
163  mCopyAsQgisProcessCommand = new QAction( tr( "Copy as qgis_process Command" ), mAdvancedMenu );
164  mCopyAsQgisProcessCommand->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionTerminal.svg" ) ) );
165  mAdvancedMenu->addAction( mCopyAsQgisProcessCommand );
166 
167  connect( mCopyAsQgisProcessCommand, &QAction::triggered, this, [this]
168  {
169  if ( const QgsProcessingAlgorithm *alg = algorithm() )
170  {
171  QgsProcessingContext *context = processingContext();
172  if ( !context )
173  return;
174 
175  bool ok = false;
176  const QString command = alg->asQgisProcessCommand( createProcessingParameters(), *context, ok );
177  if ( ! ok )
178  {
179  mMessageBar->pushMessage( tr( "Current settings cannot be specified as arguments to qgis_process (Pipe parameters as JSON to qgis_process instead)" ), Qgis::MessageLevel::Warning );
180  }
181  else
182  {
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 
195  mAdvancedMenu->addSeparator();
196 
197  QAction *copyAsJson = new QAction( tr( "Copy as JSON" ), mAdvancedMenu );
198  copyAsJson->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionEditCopy.svg" ) ) );
199 
200  mAdvancedMenu->addAction( copyAsJson );
201  connect( copyAsJson, &QAction::triggered, this, [this]
202  {
203  if ( const QgsProcessingAlgorithm *alg = algorithm() )
204  {
205  QgsProcessingContext *context = processingContext();
206  if ( !context )
207  return;
208 
209  const QVariantMap properties = alg->asMap( createProcessingParameters(), *context );
210  const QString json = QString::fromStdString( QgsJsonUtils::jsonFromVariant( properties ).dump( 2 ) );
211 
212  QMimeData *m = new QMimeData();
213  m->setText( json );
214  QClipboard *cb = QApplication::clipboard();
215 
216 #ifdef Q_OS_LINUX
217  cb->setMimeData( m, QClipboard::Selection );
218 #endif
219  cb->setMimeData( m, QClipboard::Clipboard );
220  }
221  } );
222 
223  mPasteJsonAction = new QAction( tr( "Paste Settings" ), mAdvancedMenu );
224  mPasteJsonAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionEditPaste.svg" ) ) );
225 
226  mAdvancedMenu->addAction( mPasteJsonAction );
227  connect( mPasteJsonAction, &QAction::triggered, this, [this]
228  {
229  const QString text = QApplication::clipboard()->text();
230  if ( text.isEmpty() )
231  return;
232 
233  const QVariantMap parameterValues = QgsJsonUtils::parseJson( text ).toMap().value( QStringLiteral( "inputs" ) ).toMap();
234  if ( parameterValues.isEmpty() )
235  return;
236 
237  setParameters( parameterValues );
238  } );
239 
240  mButtonBox->addButton( mAdvancedButton, QDialogButtonBox::ResetRole );
241  break;
242  }
243 
244  case DialogMode::Batch:
245  break;
246  }
247 
248  if ( mAdvancedMenu )
249  {
250  connect( mAdvancedMenu, &QMenu::aboutToShow, this, [ = ]
251  {
252  mCopyAsQgisProcessCommand->setEnabled( algorithm()
254  mPasteJsonAction->setEnabled( !QApplication::clipboard()->text().isEmpty() );
255  } );
256  }
257 
258  connect( mButtonRun, &QPushButton::clicked, this, &QgsProcessingAlgorithmDialogBase::runAlgorithm );
259  connect( mButtonChangeParameters, &QPushButton::clicked, this, &QgsProcessingAlgorithmDialogBase::showParameters );
260  connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsProcessingAlgorithmDialogBase::closeClicked );
261  connect( mButtonBox, &QDialogButtonBox::helpRequested, this, &QgsProcessingAlgorithmDialogBase::openHelp );
262  connect( mButtonCollapse, &QToolButton::clicked, this, &QgsProcessingAlgorithmDialogBase::toggleCollapsed );
263  connect( splitter, &QSplitter::splitterMoved, this, &QgsProcessingAlgorithmDialogBase::splitterChanged );
264 
265  connect( mButtonSaveLog, &QToolButton::clicked, this, &QgsProcessingAlgorithmDialogBase::saveLog );
266  connect( mButtonCopyLog, &QToolButton::clicked, this, &QgsProcessingAlgorithmDialogBase::copyLogToClipboard );
267  connect( mButtonClearLog, &QToolButton::clicked, this, &QgsProcessingAlgorithmDialogBase::clearLog );
268 
269  connect( mTabWidget, &QTabWidget::currentChanged, this, &QgsProcessingAlgorithmDialogBase::mTabWidget_currentChanged );
270 
271  mMessageBar = new QgsMessageBar();
272  mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
273  verticalLayout->insertWidget( 0, mMessageBar );
274 
275  connect( QgsApplication::taskManager(), &QgsTaskManager::taskTriggered, this, &QgsProcessingAlgorithmDialogBase::taskTriggered );
276 }
277 
278 QgsProcessingAlgorithmDialogBase::~QgsProcessingAlgorithmDialogBase() = default;
279 
280 void QgsProcessingAlgorithmDialogBase::setParameters( const QVariantMap & )
281 {}
282 
283 void QgsProcessingAlgorithmDialogBase::setAlgorithm( QgsProcessingAlgorithm *algorithm )
284 {
285  mAlgorithm.reset( algorithm );
286  QString title;
288  {
289  title = QgsStringUtils::capitalize( mAlgorithm->displayName(), Qgis::Capitalization::TitleCase );
290  }
291  else
292  {
293  title = mAlgorithm->displayName();
294  }
295  setWindowTitle( title );
296 
297  const QString algHelp = formatHelp( algorithm );
298  if ( algHelp.isEmpty() )
299  textShortHelp->hide();
300  else
301  {
302  textShortHelp->document()->setDefaultStyleSheet( QStringLiteral( ".summary { margin-left: 10px; margin-right: 10px; }\n"
303  "h2 { color: #555555; padding-bottom: 15px; }\n"
304  "a { text - decoration: none; color: #3498db; font-weight: bold; }\n"
305  "p { color: #666666; }\n"
306  "b { color: #333333; }\n"
307  "dl dd { margin - bottom: 5px; }" ) );
308  textShortHelp->setHtml( algHelp );
309  connect( textShortHelp, &QTextBrowser::anchorClicked, this, &QgsProcessingAlgorithmDialogBase::linkClicked );
310  textShortHelp->show();
311  }
312 
313  if ( algorithm->helpUrl().isEmpty() && algorithm->provider()->helpId().isEmpty() )
314  {
315  mButtonBox->removeButton( mButtonBox->button( QDialogButtonBox::Help ) );
316  }
317 
318  const QString warning = algorithm->provider()->warningMessage();
319  if ( !warning.isEmpty() )
320  {
321  mMessageBar->pushMessage( warning, Qgis::MessageLevel::Warning );
322  }
323 }
324 
326 {
327  return mAlgorithm.get();
328 }
329 
330 void QgsProcessingAlgorithmDialogBase::setMainWidget( QgsPanelWidget *widget )
331 {
332  if ( mMainWidget )
333  {
334  mMainWidget->deleteLater();
335  }
336 
337  mPanelStack->setMainPanel( widget );
338  widget->setDockMode( true );
339 
340  mMainWidget = widget;
341  connect( mMainWidget, &QgsPanelWidget::panelAccepted, this, &QDialog::reject );
342 }
343 
344 QgsPanelWidget *QgsProcessingAlgorithmDialogBase::mainWidget()
345 {
346  return mMainWidget;
347 }
348 
349 void QgsProcessingAlgorithmDialogBase::saveLogToFile( const QString &path, const LogFormat format )
350 {
351  QFile logFile( path );
352  if ( !logFile.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
353  {
354  return;
355  }
356  QTextStream fout( &logFile );
357 
358  switch ( format )
359  {
360  case FormatPlainText:
361  fout << txtLog->toPlainText();
362  break;
363 
364  case FormatHtml:
365  fout << txtLog->toHtml();
366  break;
367  }
368 }
369 
370 QgsProcessingFeedback *QgsProcessingAlgorithmDialogBase::createFeedback()
371 {
372  auto feedback = std::make_unique< QgsProcessingAlgorithmDialogFeedback >();
373  connect( feedback.get(), &QgsProcessingFeedback::progressChanged, this, &QgsProcessingAlgorithmDialogBase::setPercentage );
374  connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::commandInfoPushed, this, &QgsProcessingAlgorithmDialogBase::pushCommandInfo );
375  connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::consoleInfoPushed, this, &QgsProcessingAlgorithmDialogBase::pushConsoleInfo );
376  connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::debugInfoPushed, this, &QgsProcessingAlgorithmDialogBase::pushDebugInfo );
377  connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::errorReported, this, &QgsProcessingAlgorithmDialogBase::reportError );
378  connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::warningPushed, this, &QgsProcessingAlgorithmDialogBase::pushWarning );
379  connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::infoPushed, this, &QgsProcessingAlgorithmDialogBase::pushInfo );
380  connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::progressTextChanged, this, &QgsProcessingAlgorithmDialogBase::setProgressText );
381  connect( buttonCancel, &QPushButton::clicked, feedback.get(), &QgsProcessingFeedback::cancel );
382  return feedback.release();
383 }
384 
385 QDialogButtonBox *QgsProcessingAlgorithmDialogBase::buttonBox()
386 {
387  return mButtonBox;
388 }
389 
390 QTabWidget *QgsProcessingAlgorithmDialogBase::tabWidget()
391 {
392  return mTabWidget;
393 }
394 
395 void QgsProcessingAlgorithmDialogBase::showLog()
396 {
397  mTabWidget->setCurrentIndex( 1 );
398 }
399 
400 void QgsProcessingAlgorithmDialogBase::showParameters()
401 {
402  mTabWidget->setCurrentIndex( 0 );
403 }
404 
405 QPushButton *QgsProcessingAlgorithmDialogBase::runButton()
406 {
407  return mButtonRun;
408 }
409 
410 QPushButton *QgsProcessingAlgorithmDialogBase::cancelButton()
411 {
412  return buttonCancel;
413 }
414 
415 QPushButton *QgsProcessingAlgorithmDialogBase::changeParametersButton()
416 {
417  return mButtonChangeParameters;
418 }
419 
420 void QgsProcessingAlgorithmDialogBase::clearProgress()
421 {
422  progressBar->setMaximum( 0 );
423 }
424 
425 void QgsProcessingAlgorithmDialogBase::setExecuted( bool executed )
426 {
427  mExecuted = executed;
428 }
429 
430 void QgsProcessingAlgorithmDialogBase::setExecutedAnyResult( bool executedAnyResult )
431 {
432  mExecutedAnyResult = executedAnyResult;
433 }
434 
435 void QgsProcessingAlgorithmDialogBase::setResults( const QVariantMap &results )
436 {
437  mResults = results;
438 }
439 
440 void QgsProcessingAlgorithmDialogBase::finished( bool, const QVariantMap &, QgsProcessingContext &, QgsProcessingFeedback * )
441 {
442 
443 }
444 
445 void QgsProcessingAlgorithmDialogBase::openHelp()
446 {
447  QUrl algHelp = mAlgorithm->helpUrl();
448  if ( algHelp.isEmpty() )
449  {
450  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() ) ) );
451  }
452 
453  if ( !algHelp.isEmpty() )
454  QDesktopServices::openUrl( algHelp );
455 }
456 
457 void QgsProcessingAlgorithmDialogBase::toggleCollapsed()
458 {
459  if ( mHelpCollapsed )
460  {
461  splitter->restoreState( mSplitterState );
462  mButtonCollapse->setArrowType( Qt::RightArrow );
463  }
464  else
465  {
466  mSplitterState = splitter->saveState();
467  splitter->setSizes( QList<int>() << 1 << 0 );
468  mButtonCollapse->setArrowType( Qt::LeftArrow );
469  }
470  mHelpCollapsed = !mHelpCollapsed;
471 }
472 
473 void QgsProcessingAlgorithmDialogBase::splitterChanged( int, int )
474 {
475  if ( splitter->sizes().at( 1 ) == 0 )
476  {
477  mHelpCollapsed = true;
478  mButtonCollapse->setArrowType( Qt::LeftArrow );
479  }
480  else
481  {
482  mHelpCollapsed = false;
483  mButtonCollapse->setArrowType( Qt::RightArrow );
484  }
485 }
486 
487 void QgsProcessingAlgorithmDialogBase::mTabWidget_currentChanged( int )
488 {
489  updateRunButtonVisibility();
490 }
491 
492 void QgsProcessingAlgorithmDialogBase::linkClicked( const QUrl &url )
493 {
494  QDesktopServices::openUrl( url.toString() );
495 }
496 
497 void QgsProcessingAlgorithmDialogBase::algExecuted( bool successful, const QVariantMap & )
498 {
499  mAlgorithmTask = nullptr;
500 
501  if ( !successful )
502  {
503  // show dialog to display errors
504  show();
505  raise();
506  setWindowState( ( windowState() & ~Qt::WindowMinimized ) | Qt::WindowActive );
507  activateWindow();
508  showLog();
509  }
510  else
511  {
512  // delete dialog if closed
513  if ( isFinalized() && !isVisible() )
514  {
515  deleteLater();
516  }
517  }
518 }
519 
520 void QgsProcessingAlgorithmDialogBase::taskTriggered( QgsTask *task )
521 {
522  if ( task == mAlgorithmTask )
523  {
524  show();
525  raise();
526  setWindowState( ( windowState() & ~Qt::WindowMinimized ) | Qt::WindowActive );
527  activateWindow();
528  showLog();
529  }
530 }
531 
532 void QgsProcessingAlgorithmDialogBase::closeClicked()
533 {
534  reject();
535  close();
536 }
537 
538 QgsProcessingContext::LogLevel QgsProcessingAlgorithmDialogBase::logLevel() const
539 {
540  return mLogLevel;
541 }
542 
543 void QgsProcessingAlgorithmDialogBase::setLogLevel( QgsProcessingContext::LogLevel level )
544 {
545  mLogLevel = level;
546 }
547 
548 void QgsProcessingAlgorithmDialogBase::reportError( const QString &error, bool fatalError )
549 {
550  setInfo( error, true );
551  if ( fatalError )
552  resetGui();
553  showLog();
554  processEvents();
555 }
556 
557 void QgsProcessingAlgorithmDialogBase::pushWarning( const QString &warning )
558 {
559  setInfo( warning, false, true, true );
560  processEvents();
561 }
562 
563 void QgsProcessingAlgorithmDialogBase::pushInfo( const QString &info )
564 {
565  setInfo( info );
566  processEvents();
567 }
568 
569 void QgsProcessingAlgorithmDialogBase::pushCommandInfo( const QString &command )
570 {
571  txtLog->append( QStringLiteral( "<code>%1<code>" ).arg( formatStringForLog( command.toHtmlEscaped() ) ) );
572  scrollToBottomOfLog();
573  processEvents();
574 }
575 
576 void QgsProcessingAlgorithmDialogBase::pushDebugInfo( const QString &message )
577 {
578  txtLog->append( QStringLiteral( "<span style=\"color:#777\">%1</span>" ).arg( formatStringForLog( message.toHtmlEscaped() ) ) );
579  scrollToBottomOfLog();
580  processEvents();
581 }
582 
583 void QgsProcessingAlgorithmDialogBase::pushConsoleInfo( const QString &info )
584 {
585  txtLog->append( QStringLiteral( "<code style=\"color:#777\">%1</code>" ).arg( formatStringForLog( info.toHtmlEscaped() ) ) );
586  scrollToBottomOfLog();
587  processEvents();
588 }
589 
590 QDialog *QgsProcessingAlgorithmDialogBase::createProgressDialog()
591 {
592  QgsProcessingAlgorithmProgressDialog *dialog = new QgsProcessingAlgorithmProgressDialog( this );
593  dialog->setWindowModality( Qt::ApplicationModal );
594  dialog->setWindowTitle( windowTitle() );
595  dialog->setGeometry( geometry() ); // match size/position to this dialog
596  connect( progressBar, &QProgressBar::valueChanged, dialog->progressBar(), &QProgressBar::setValue );
597  connect( dialog->cancelButton(), &QPushButton::clicked, buttonCancel, &QPushButton::click );
598  dialog->logTextEdit()->setHtml( txtLog->toHtml() );
599  connect( txtLog, &QTextEdit::textChanged, dialog, [this, dialog]()
600  {
601  dialog->logTextEdit()->setHtml( txtLog->toHtml() );
602  QScrollBar *sb = dialog->logTextEdit()->verticalScrollBar();
603  sb->setValue( sb->maximum() );
604  } );
605  return dialog;
606 }
607 
608 void QgsProcessingAlgorithmDialogBase::clearLog()
609 {
610  txtLog->clear();
611 }
612 
613 void QgsProcessingAlgorithmDialogBase::saveLog()
614 {
615  QgsSettings settings;
616  const QString lastUsedDir = settings.value( QStringLiteral( "/Processing/lastUsedLogDirectory" ), QDir::homePath() ).toString();
617 
618  QString filter;
619  const QString txtExt = tr( "Text files" ) + QStringLiteral( " (*.txt *.TXT)" );
620  const QString htmlExt = tr( "HTML files" ) + QStringLiteral( " (*.html *.HTML)" );
621 
622  const QString path = QFileDialog::getSaveFileName( this, tr( "Save Log to File" ), lastUsedDir, txtExt + ";;" + htmlExt, &filter );
623  if ( path.isEmpty() )
624  {
625  return;
626  }
627 
628  settings.setValue( QStringLiteral( "/Processing/lastUsedLogDirectory" ), QFileInfo( path ).path() );
629 
630  LogFormat format = FormatPlainText;
631  if ( filter == htmlExt )
632  {
633  format = FormatHtml;
634  }
635  saveLogToFile( path, format );
636 }
637 
638 void QgsProcessingAlgorithmDialogBase::copyLogToClipboard()
639 {
640  QMimeData *m = new QMimeData();
641  m->setText( txtLog->toPlainText() );
642  m->setHtml( txtLog->toHtml() );
643  QClipboard *cb = QApplication::clipboard();
644 
645 #ifdef Q_OS_LINUX
646  cb->setMimeData( m, QClipboard::Selection );
647 #endif
648  cb->setMimeData( m, QClipboard::Clipboard );
649 }
650 
651 void QgsProcessingAlgorithmDialogBase::closeEvent( QCloseEvent *e )
652 {
653  if ( !mHelpCollapsed )
654  {
655  QgsSettings settings;
656  settings.setValue( QStringLiteral( "/Processing/dialogBaseSplitter" ), splitter->saveState() );
657  }
658 
659  QDialog::closeEvent( e );
660 
661  if ( !mAlgorithmTask && isFinalized() )
662  {
663  // when running a background task, the dialog is kept around and deleted only when the task
664  // completes. But if not running a task, we auto cleanup (later - gotta give callers a chance
665  // to retrieve results and execution status).
666  deleteLater();
667  }
668 }
669 
670 void QgsProcessingAlgorithmDialogBase::runAlgorithm()
671 {
672 
673 }
674 
675 void QgsProcessingAlgorithmDialogBase::setPercentage( double percent )
676 {
677  // delay setting maximum progress value until we know algorithm reports progress
678  if ( progressBar->maximum() == 0 )
679  progressBar->setMaximum( 100 );
680  progressBar->setValue( percent );
681  processEvents();
682 }
683 
684 void QgsProcessingAlgorithmDialogBase::setProgressText( const QString &text )
685 {
686  lblProgress->setText( text );
687  setInfo( text, false );
688  scrollToBottomOfLog();
689  processEvents();
690 }
691 
692 QString QgsProcessingAlgorithmDialogBase::formatHelp( QgsProcessingAlgorithm *algorithm )
693 {
694  const QString text = algorithm->shortHelpString();
695  if ( !text.isEmpty() )
696  {
697  const QStringList paragraphs = text.split( '\n' );
698  QString help;
699  for ( const QString &paragraph : paragraphs )
700  {
701  help += QStringLiteral( "<p>%1</p>" ).arg( paragraph );
702  }
703  return QStringLiteral( "<h2>%1</h2>%2" ).arg( algorithm->displayName(), help );
704  }
705  else if ( !algorithm->shortDescription().isEmpty() )
706  {
707  return QStringLiteral( "<h2>%1</h2><p>%2</p>" ).arg( algorithm->displayName(), algorithm->shortDescription() );
708  }
709  else
710  return QString();
711 }
712 
713 void QgsProcessingAlgorithmDialogBase::processEvents()
714 {
715  if ( mAlgorithmTask )
716  {
717  // no need to call this - the algorithm is running in a thread.
718  // in fact, calling it causes a crash on Windows when the algorithm
719  // is running in a background thread... unfortunately we need something
720  // like this for non-threadable algorithms, otherwise there's no chance
721  // for users to hit cancel or see progress updates...
722  return;
723  }
724 
725  // So that we get a chance of hitting the Abort button
726 #ifdef Q_OS_LINUX
727  // One iteration is actually enough on Windows to get good interactivity
728  // whereas on Linux we must allow for far more iterations.
729  // For safety limit the number of iterations
730  int nIters = 0;
731  while ( ++nIters < 100 )
732 #endif
733  {
734  QCoreApplication::processEvents();
735  }
736 }
737 
738 void QgsProcessingAlgorithmDialogBase::scrollToBottomOfLog()
739 {
740  QScrollBar *sb = txtLog->verticalScrollBar();
741  sb->setValue( sb->maximum() );
742 }
743 
744 void QgsProcessingAlgorithmDialogBase::resetGui()
745 {
746  lblProgress->clear();
747  progressBar->setMaximum( 100 );
748  progressBar->setValue( 0 );
749  mButtonRun->setEnabled( true );
750  mButtonChangeParameters->setEnabled( true );
751  mButtonClose->setEnabled( true );
752  if ( mMainWidget )
753  {
754  mMainWidget->setEnabled( true );
755  }
756  updateRunButtonVisibility();
757  resetAdditionalGui();
758 }
759 
760 void QgsProcessingAlgorithmDialogBase::updateRunButtonVisibility()
761 {
762  // Activate run button if current tab is Parameters
763  const bool runButtonVisible = mTabWidget->currentIndex() == 0;
764  mButtonRun->setVisible( runButtonVisible );
765  mButtonChangeParameters->setVisible( !runButtonVisible && mExecutedAnyResult && mButtonChangeParameters->isEnabled() );
766 }
767 
768 void QgsProcessingAlgorithmDialogBase::resetAdditionalGui()
769 {
770 
771 }
772 
773 void QgsProcessingAlgorithmDialogBase::blockControlsWhileRunning()
774 {
775  mButtonRun->setEnabled( false );
776  mButtonChangeParameters->setEnabled( false );
777  if ( mMainWidget )
778  {
779  mMainWidget->setEnabled( false );
780  }
781  blockAdditionalControlsWhileRunning();
782 }
783 
784 void QgsProcessingAlgorithmDialogBase::blockAdditionalControlsWhileRunning()
785 {
786 
787 }
788 
789 QgsMessageBar *QgsProcessingAlgorithmDialogBase::messageBar()
790 {
791  return mMessageBar;
792 }
793 
794 void QgsProcessingAlgorithmDialogBase::hideShortHelp()
795 {
796  textShortHelp->setVisible( false );
797 }
798 
799 void QgsProcessingAlgorithmDialogBase::setCurrentTask( QgsProcessingAlgRunnerTask *task )
800 {
801  mAlgorithmTask = task;
802  connect( mAlgorithmTask, &QgsProcessingAlgRunnerTask::executed, this, &QgsProcessingAlgorithmDialogBase::algExecuted );
803  QgsApplication::taskManager()->addTask( mAlgorithmTask );
804 }
805 
806 QString QgsProcessingAlgorithmDialogBase::formatStringForLog( const QString &string )
807 {
808  QString s = string;
809  s.replace( '\n', QLatin1String( "<br>" ) );
810  return s;
811 }
812 
813 bool QgsProcessingAlgorithmDialogBase::isFinalized()
814 {
815  return true;
816 }
817 
818 void QgsProcessingAlgorithmDialogBase::setInfo( const QString &message, bool isError, bool escapeHtml, bool isWarning )
819 {
820  constexpr int MESSAGE_COUNT_LIMIT = 10000;
821  // Avoid logging too many messages, which might blow memory.
822  if ( mMessageLoggedCount == MESSAGE_COUNT_LIMIT )
823  return;
824  ++mMessageLoggedCount;
825 
826  // note -- we have to wrap the message in a span block, or QTextEdit::append sometimes gets confused
827  // and varies between treating it as a HTML string or a plain text string! (see https://github.com/qgis/QGIS/issues/37934)
828  if ( mMessageLoggedCount == MESSAGE_COUNT_LIMIT )
829  txtLog->append( QStringLiteral( "<span style=\"color:red\">%1</span>" ).arg( tr( "Message log truncated" ) ) );
830  else if ( isError || isWarning )
831  txtLog->append( QStringLiteral( "<span style=\"color:%1\">%2</span>" ).arg( isError ? QStringLiteral( "red" ) : QStringLiteral( "#b85a20" ), escapeHtml ? formatStringForLog( message.toHtmlEscaped() ) : formatStringForLog( message ) ) );
832  else if ( escapeHtml )
833  txtLog->append( QStringLiteral( "<span>%1</span" ).arg( formatStringForLog( message.toHtmlEscaped() ) ) );
834  else
835  txtLog->append( QStringLiteral( "<span>%1</span>" ).arg( formatStringForLog( message ) ) );
836  scrollToBottomOfLog();
837  processEvents();
838 }
839 
840 void QgsProcessingAlgorithmDialogBase::reject()
841 {
842  if ( !mAlgorithmTask && isFinalized() )
843  {
844  setAttribute( Qt::WA_DeleteOnClose );
845  }
846  QDialog::reject();
847 }
848 
849 //
850 // QgsProcessingAlgorithmProgressDialog
851 //
852 
853 QgsProcessingAlgorithmProgressDialog::QgsProcessingAlgorithmProgressDialog( QWidget *parent )
854  : QDialog( parent )
855 {
856  setupUi( this );
857 }
858 
859 QProgressBar *QgsProcessingAlgorithmProgressDialog::progressBar()
860 {
861  return mProgressBar;
862 }
863 
864 QPushButton *QgsProcessingAlgorithmProgressDialog::cancelButton()
865 {
866  return mButtonBox->button( QDialogButtonBox::Cancel );
867 }
868 
869 QTextEdit *QgsProcessingAlgorithmProgressDialog::logTextEdit()
870 {
871  return mTxtLog;
872 }
873 
874 void QgsProcessingAlgorithmProgressDialog::reject()
875 {
876 
877 }
878 
879 
880 
@ 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.
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
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:178
@ HigDialogTitleIsTitleCase
Dialog titles should be title case.
Definition: qgsgui.h:235
static QgsGui::HigFlags higFlags()
Returns the platform's HIG flags.
Definition: qgsgui.cpp:197
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.
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.
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.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
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.
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