QGIS API Documentation  3.0.2-Girona (307d082)
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 <QToolButton>
26 #include <QDesktopServices>
27 #include <QScrollBar>
28 
30 
31 QgsProcessingAlgorithmDialogFeedback::QgsProcessingAlgorithmDialogFeedback()
33 {
34 }
35 
36 void QgsProcessingAlgorithmDialogFeedback::setProgressText( const QString &text )
37 {
38  emit progressTextChanged( text );
39 }
40 
41 void QgsProcessingAlgorithmDialogFeedback::reportError( const QString &error, bool fatalError )
42 {
43  emit errorReported( error, fatalError );
44 }
45 
46 void QgsProcessingAlgorithmDialogFeedback::pushInfo( const QString &info )
47 {
48  emit infoPushed( info );
49 }
50 
51 void QgsProcessingAlgorithmDialogFeedback::pushCommandInfo( const QString &info )
52 {
53  emit commandInfoPushed( info );
54 }
55 
56 void QgsProcessingAlgorithmDialogFeedback::pushDebugInfo( const QString &info )
57 {
58  emit debugInfoPushed( info );
59 }
60 
61 void QgsProcessingAlgorithmDialogFeedback::pushConsoleInfo( const QString &info )
62 {
63  emit consoleInfoPushed( info );
64 }
65 
66 //
67 // QgsProcessingAlgorithmDialogBase
68 //
69 
70 QgsProcessingAlgorithmDialogBase::QgsProcessingAlgorithmDialogBase( QWidget *parent, Qt::WindowFlags flags )
71  : QDialog( parent, flags )
72 {
73  setupUi( this );
74 
75  //don't collapse parameters panel
76  splitter->setCollapsible( 0, false );
77 
78  // add collapse button to splitter
79  QSplitterHandle *splitterHandle = splitter->handle( 1 );
80  QVBoxLayout *handleLayout = new QVBoxLayout();
81  handleLayout->setContentsMargins( 0, 0, 0, 0 );
82  mButtonCollapse = new QToolButton( splitterHandle );
83  mButtonCollapse->setAutoRaise( true );
84  mButtonCollapse->setFixedSize( 12, 12 );
85  mButtonCollapse->setCursor( Qt::ArrowCursor );
86  handleLayout->addWidget( mButtonCollapse );
87  handleLayout->addStretch();
88  splitterHandle->setLayout( handleLayout );
89 
91 
92  QgsSettings settings;
93  splitter->restoreState( settings.value( QStringLiteral( "/Processing/dialogBaseSplitter" ), QByteArray() ).toByteArray() );
94  mSplitterState = splitter->saveState();
95  splitterChanged( 0, 0 );
96 
97  connect( mButtonBox, &QDialogButtonBox::rejected, this, &QgsProcessingAlgorithmDialogBase::closeClicked );
98  connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsProcessingAlgorithmDialogBase::accept );
99 
100  // Rename OK button to Run
101  mButtonRun = mButtonBox->button( QDialogButtonBox::Ok );
102  mButtonRun->setText( tr( "Run" ) );
103 
104  buttonCancel->setEnabled( false );
105  mButtonClose = mButtonBox->button( QDialogButtonBox::Close );
106 
107  connect( mButtonBox, &QDialogButtonBox::helpRequested, this, &QgsProcessingAlgorithmDialogBase::openHelp );
108  connect( mButtonCollapse, &QToolButton::clicked, this, &QgsProcessingAlgorithmDialogBase::toggleCollapsed );
109  connect( splitter, &QSplitter::splitterMoved, this, &QgsProcessingAlgorithmDialogBase::splitterChanged );
110 
111  mMessageBar = new QgsMessageBar();
112  mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
113  verticalLayout->insertWidget( 0, mMessageBar );
114 
115  connect( QgsApplication::taskManager(), &QgsTaskManager::taskTriggered, this, &QgsProcessingAlgorithmDialogBase::taskTriggered );
116 }
117 
118 void QgsProcessingAlgorithmDialogBase::setAlgorithm( QgsProcessingAlgorithm *algorithm )
119 {
120  mAlgorithm = algorithm;
121  setWindowTitle( mAlgorithm->displayName() );
122 
123  QString algHelp = formatHelp( algorithm );
124  if ( algHelp.isEmpty() )
125  textShortHelp->hide();
126  else
127  {
128  textShortHelp->document()->setDefaultStyleSheet( QStringLiteral( ".summary { margin-left: 10px; margin-right: 10px; }\n"
129  "h2 { color: #555555; padding-bottom: 15px; }\n"
130  "a { text - decoration: none; color: #3498db; font-weight: bold; }\n"
131  "p { color: #666666; }\n"
132  "b { color: #333333; }\n"
133  "dl dd { margin - bottom: 5px; }" ) );
134  textShortHelp->setHtml( algHelp );
135  connect( textShortHelp, &QTextBrowser::anchorClicked, this, &QgsProcessingAlgorithmDialogBase::linkClicked );
136  }
137 
138  if ( !( algorithm->flags() & QgsProcessingAlgorithm::FlagNoThreading ) )
139  mButtonRun->setText( tr( "Run in Background" ) );
140 }
141 
142 QgsProcessingAlgorithm *QgsProcessingAlgorithmDialogBase::algorithm()
143 {
144  return mAlgorithm;
145 }
146 
147 void QgsProcessingAlgorithmDialogBase::setMainWidget( QWidget *widget )
148 {
149  if ( mMainWidget )
150  {
151  mMainWidget->deleteLater();
152  }
153 
154  mMainWidget = widget;
155  mTabWidget->widget( 0 )->layout()->addWidget( mMainWidget );
156 }
157 
158 QWidget *QgsProcessingAlgorithmDialogBase::mainWidget()
159 {
160  return mMainWidget;
161 }
162 
163 QVariantMap QgsProcessingAlgorithmDialogBase::getParameterValues() const
164 {
165  return QVariantMap();
166 }
167 
168 QgsProcessingFeedback *QgsProcessingAlgorithmDialogBase::createFeedback()
169 {
170  auto feedback = qgis::make_unique< QgsProcessingAlgorithmDialogFeedback >();
171  connect( feedback.get(), &QgsProcessingFeedback::progressChanged, this, &QgsProcessingAlgorithmDialogBase::setPercentage );
172  connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::commandInfoPushed, this, &QgsProcessingAlgorithmDialogBase::pushCommandInfo );
173  connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::consoleInfoPushed, this, &QgsProcessingAlgorithmDialogBase::pushConsoleInfo );
174  connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::debugInfoPushed, this, &QgsProcessingAlgorithmDialogBase::pushDebugInfo );
175  connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::errorReported, this, &QgsProcessingAlgorithmDialogBase::reportError );
176  connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::infoPushed, this, &QgsProcessingAlgorithmDialogBase::pushInfo );
177  connect( feedback.get(), &QgsProcessingAlgorithmDialogFeedback::progressTextChanged, this, &QgsProcessingAlgorithmDialogBase::setProgressText );
178  connect( buttonCancel, &QPushButton::clicked, feedback.get(), &QgsProcessingFeedback::cancel );
179  return feedback.release();
180 }
181 
182 QDialogButtonBox *QgsProcessingAlgorithmDialogBase::buttonBox()
183 {
184  return mButtonBox;
185 }
186 
187 QTabWidget *QgsProcessingAlgorithmDialogBase::tabWidget()
188 {
189  return mTabWidget;
190 }
191 
192 void QgsProcessingAlgorithmDialogBase::showLog()
193 {
194  mTabWidget->setCurrentIndex( 1 );
195 }
196 
197 QPushButton *QgsProcessingAlgorithmDialogBase::runButton()
198 {
199  return mButtonRun;
200 }
201 
202 QPushButton *QgsProcessingAlgorithmDialogBase::cancelButton()
203 {
204  return buttonCancel;
205 }
206 
207 void QgsProcessingAlgorithmDialogBase::clearProgress()
208 {
209  progressBar->setMaximum( 0 );
210 }
211 
212 void QgsProcessingAlgorithmDialogBase::setExecuted( bool executed )
213 {
214  mExecuted = executed;
215 }
216 
217 void QgsProcessingAlgorithmDialogBase::setResults( const QVariantMap &results )
218 {
219  mResults = results;
220 }
221 
222 void QgsProcessingAlgorithmDialogBase::finished( bool, const QVariantMap &, QgsProcessingContext &, QgsProcessingFeedback * )
223 {
224 
225 }
226 
227 void QgsProcessingAlgorithmDialogBase::accept()
228 {
229 }
230 
231 void QgsProcessingAlgorithmDialogBase::openHelp()
232 {
233  QUrl algHelp = mAlgorithm->helpUrl();
234  if ( algHelp.isEmpty() )
235  {
236  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() ) ) );
237  }
238 
239  if ( !algHelp.isEmpty() )
240  QDesktopServices::openUrl( algHelp );
241 }
242 
243 void QgsProcessingAlgorithmDialogBase::toggleCollapsed()
244 {
245  if ( mHelpCollapsed )
246  {
247  splitter->restoreState( mSplitterState );
248  mButtonCollapse->setArrowType( Qt::RightArrow );
249  }
250  else
251  {
252  mSplitterState = splitter->saveState();
253  splitter->setSizes( QList<int>() << 1 << 0 );
254  mButtonCollapse->setArrowType( Qt::LeftArrow );
255  }
256  mHelpCollapsed = !mHelpCollapsed;
257 }
258 
259 void QgsProcessingAlgorithmDialogBase::splitterChanged( int, int )
260 {
261  if ( splitter->sizes().at( 1 ) == 0 )
262  {
263  mHelpCollapsed = true;
264  mButtonCollapse->setArrowType( Qt::LeftArrow );
265  }
266  else
267  {
268  mHelpCollapsed = false;
269  mButtonCollapse->setArrowType( Qt::RightArrow );
270  }
271 }
272 
273 void QgsProcessingAlgorithmDialogBase::linkClicked( const QUrl &url )
274 {
275  QDesktopServices::openUrl( url.toString() );
276 }
277 
278 void QgsProcessingAlgorithmDialogBase::algExecuted( bool successful, const QVariantMap & )
279 {
280  mAlgorithmTask = nullptr;
281 
282  if ( !successful )
283  {
284  // show dialog to display errors
285  show();
286  raise();
287  setWindowState( ( windowState() & ~Qt::WindowMinimized ) | Qt::WindowActive );
288  activateWindow();
289  showLog();
290  }
291  else
292  {
293  // delete dialog if closed
294  if ( !isVisible() )
295  {
296  deleteLater();
297  }
298  }
299 }
300 
301 void QgsProcessingAlgorithmDialogBase::taskTriggered( QgsTask *task )
302 {
303  if ( task == mAlgorithmTask )
304  {
305  show();
306  raise();
307  setWindowState( ( windowState() & ~Qt::WindowMinimized ) | Qt::WindowActive );
308  activateWindow();
309  showLog();
310  }
311 }
312 
313 void QgsProcessingAlgorithmDialogBase::closeClicked()
314 {
315  reject();
316  close();
317 }
318 
319 void QgsProcessingAlgorithmDialogBase::reportError( const QString &error, bool fatalError )
320 {
321  setInfo( error, true );
322  if ( fatalError )
323  resetGui();
324  showLog();
325  processEvents();
326 }
327 
328 void QgsProcessingAlgorithmDialogBase::pushInfo( const QString &info )
329 {
330  setInfo( info );
331  processEvents();
332 }
333 
334 void QgsProcessingAlgorithmDialogBase::pushCommandInfo( const QString &command )
335 {
336  txtLog->append( QStringLiteral( "<code>%1<code>" ).arg( formatStringForLog( command.toHtmlEscaped() ) ) );
337  scrollToBottomOfLog();
338  processEvents();
339 }
340 
341 void QgsProcessingAlgorithmDialogBase::pushDebugInfo( const QString &message )
342 {
343  txtLog->append( QStringLiteral( "<span style=\"color:blue\">%1</span>" ).arg( formatStringForLog( message.toHtmlEscaped() ) ) );
344  scrollToBottomOfLog();
345  processEvents();
346 }
347 
348 void QgsProcessingAlgorithmDialogBase::pushConsoleInfo( const QString &info )
349 {
350  txtLog->append( QStringLiteral( "<code><span style=\"color:blue\">%1</darkgray></code>" ).arg( formatStringForLog( info.toHtmlEscaped() ) ) );
351  scrollToBottomOfLog();
352  processEvents();
353 }
354 
355 QDialog *QgsProcessingAlgorithmDialogBase::createProgressDialog()
356 {
357  QgsProcessingAlgorithmProgressDialog *dialog = new QgsProcessingAlgorithmProgressDialog( this );
358  dialog->setWindowModality( Qt::ApplicationModal );
359  dialog->setWindowTitle( windowTitle() );
360  connect( progressBar, &QProgressBar::valueChanged, dialog->progressBar(), &QProgressBar::setValue );
361  connect( dialog->cancelButton(), &QPushButton::clicked, buttonCancel, &QPushButton::click );
362  dialog->logTextEdit()->setHtml( txtLog->toHtml() );
363  connect( txtLog, &QTextEdit::textChanged, dialog, [this, dialog]()
364  {
365  dialog->logTextEdit()->setHtml( txtLog->toHtml() );
366  QScrollBar *sb = dialog->logTextEdit()->verticalScrollBar();
367  sb->setValue( sb->maximum() );
368  } );
369  return dialog;
370 }
371 
372 void QgsProcessingAlgorithmDialogBase::closeEvent( QCloseEvent *e )
373 {
374  QDialog::closeEvent( e );
375 
376  if ( !mAlgorithmTask )
377  {
378  // when running a background task, the dialog is kept around and deleted only when the task
379  // completes. But if not running a task, we auto cleanup (later - gotta give callers a chance
380  // to retrieve results and execution status).
381  deleteLater();
382  }
383 }
384 
385 void QgsProcessingAlgorithmDialogBase::setPercentage( double percent )
386 {
387  // delay setting maximum progress value until we know algorithm reports progress
388  if ( progressBar->maximum() == 0 )
389  progressBar->setMaximum( 100 );
390  progressBar->setValue( percent );
391  processEvents();
392 }
393 
394 void QgsProcessingAlgorithmDialogBase::setProgressText( const QString &text )
395 {
396  lblProgress->setText( text );
397  setInfo( text, false );
398  scrollToBottomOfLog();
399  processEvents();
400 }
401 
402 QString QgsProcessingAlgorithmDialogBase::formatHelp( QgsProcessingAlgorithm *algorithm )
403 {
404  QString text = algorithm->shortHelpString();
405  if ( !text.isEmpty() )
406  {
407  QStringList paragraphs = text.split( '\n' );
408  QString help;
409  for ( const QString &paragraph : paragraphs )
410  {
411  help += QStringLiteral( "<p>%1</p>" ).arg( paragraph );
412  }
413  return QStringLiteral( "<h2>%1</h2>%2" ).arg( algorithm->displayName(), help );
414  }
415  else
416  return QString();
417 }
418 
419 void QgsProcessingAlgorithmDialogBase::processEvents()
420 {
421  if ( mAlgorithmTask )
422  {
423  // no need to call this - the algorithm is running in a thread.
424  // in fact, calling it causes a crash on Windows when the algorithm
425  // is running in a background thread... unfortunately we need something
426  // like this for non-threadable algorithms, otherwise there's no chance
427  // for users to hit cancel or see progress updates...
428  return;
429  }
430 
431  // So that we get a chance of hitting the Abort button
432 #ifdef Q_OS_LINUX
433  // For some reason on Windows hasPendingEvents() always return true,
434  // but one iteration is actually enough on Windows to get good interactivity
435  // whereas on Linux we must allow for far more iterations.
436  // For safety limit the number of iterations
437  int nIters = 0;
438  while ( QCoreApplication::hasPendingEvents() && ++nIters < 100 )
439 #endif
440  {
441  QCoreApplication::processEvents();
442  }
443 }
444 
445 void QgsProcessingAlgorithmDialogBase::scrollToBottomOfLog()
446 {
447  QScrollBar *sb = txtLog->verticalScrollBar();
448  sb->setValue( sb->maximum() );
449 }
450 
451 void QgsProcessingAlgorithmDialogBase::resetGui()
452 {
453  lblProgress->clear();
454  progressBar->setMaximum( 100 );
455  progressBar->setValue( 0 );
456  mButtonRun->setEnabled( true );
457  mButtonClose->setEnabled( true );
458 }
459 
460 QgsMessageBar *QgsProcessingAlgorithmDialogBase::messageBar()
461 {
462  return mMessageBar;
463 }
464 
465 void QgsProcessingAlgorithmDialogBase::hideShortHelp()
466 {
467  textShortHelp->setVisible( false );
468 }
469 
470 void QgsProcessingAlgorithmDialogBase::setCurrentTask( QgsProcessingAlgRunnerTask *task )
471 {
472  mAlgorithmTask = task;
473  connect( mAlgorithmTask, &QgsProcessingAlgRunnerTask::executed, this, &QgsProcessingAlgorithmDialogBase::algExecuted );
474  QgsApplication::taskManager()->addTask( mAlgorithmTask );
475 }
476 
477 QString QgsProcessingAlgorithmDialogBase::formatStringForLog( const QString &string )
478 {
479  QString s = string;
480  s.replace( '\n', QStringLiteral( "<br>" ) );
481  return s;
482 }
483 
484 void QgsProcessingAlgorithmDialogBase::setInfo( const QString &message, bool isError, bool escapeHtml )
485 {
486  if ( isError )
487  txtLog->append( QStringLiteral( "<span style=\"color:red\">%1</span>" ).arg( formatStringForLog( message ) ) );
488  else if ( escapeHtml )
489  txtLog->append( formatStringForLog( message.toHtmlEscaped() ) );
490  else
491  txtLog->append( formatStringForLog( message ) );
492  scrollToBottomOfLog();
493  processEvents();
494 }
495 
496 //
497 // QgsProcessingAlgorithmProgressDialog
498 //
499 
500 QgsProcessingAlgorithmProgressDialog::QgsProcessingAlgorithmProgressDialog( QWidget *parent )
501  : QDialog( parent )
502 {
503  setupUi( this );
505 }
506 
507 QProgressBar *QgsProcessingAlgorithmProgressDialog::progressBar()
508 {
509  return mProgressBar;
510 }
511 
512 QPushButton *QgsProcessingAlgorithmProgressDialog::cancelButton()
513 {
514  return mButtonBox->button( QDialogButtonBox::Cancel );
515 }
516 
517 QTextEdit *QgsProcessingAlgorithmProgressDialog::logTextEdit()
518 {
519  return mTxtLog;
520 }
521 
522 void QgsProcessingAlgorithmProgressDialog::reject()
523 {
524 
525 }
526 
527 
528 
Base class for providing feedback from a processing algorithm.
void taskTriggered(QgsTask *task)
Emitted when a task is triggered.
void cancel()
Tells the internal routines that the current operation should be canceled. This should be run by the ...
Definition: qgsfeedback.h:85
This class is a composition of two QSettings instances:
Definition: qgssettings.h:57
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:45
virtual Flags flags() const
Returns the flags indicating how and when the algorithm operates and should be exposed to users...
static QgsGui * instance()
Returns a pointer to the singleton instance.
Definition: qgsgui.cpp:35
virtual QString shortHelpString() const
Returns a localised short helper string for the algorithm.
Abstract base class for processing algorithms.
static QUrl helpUrl(const QString &key)
Returns URI of the help topic for the given key.
Definition: qgshelp.cpp:40
static QgsTaskManager * taskManager()
Returns the application&#39;s task manager, used for managing application wide background task handling...
void progressChanged(double progress)
Emitted when the feedback object reports a progress change.
Algorithm is not thread safe and cannot be run in a background thread, e.g. for algorithms which mani...
long addTask(QgsTask *task, int priority=0)
Adds a task to the manager.
Abstract base class for long running background tasks.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), const Section section=NoSection) const
Returns the value for setting key.
virtual QString displayName() const =0
Returns the translated algorithm name, which should be used for any user-visible display of the algor...
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:76
QgsTask task which runs a QgsProcessingAlgorithm in a background task.
Contains information about the context in which a processing algorithm is executed.
void executed(bool successful, const QVariantMap &results)
Emitted when the algorithm has finished execution.