Skip to content
Permalink
Browse files

[processing] Show a modal progress dialog when running algorithms

which cannot run in background tasks

This is not fantastic UX, but we have lots of constraints here:

- The algorithm dialog itself cannot be made modal. There's child
widgets (such as the point and extent parameter widgets) which
interact with the main QGIS window.
- There is no reliable way in Qt to make a dialog modal after
it's shown (e.g. make it modal only when the algorithm is
running). Trust me - I've tried everything, and all approaches
break with some corner case.
- For non-background algorithms, we must have processEvents calls
in order to show the algorithm feedback and progress to users,
and detect cancel button clicks. Yet these processEvents calls
means that users can interact with other parts of QGIS, e.g.
removing layers from a project, and other operations which
could cause the algorithm to crash. So we MUST have some modal
dialog in order to block interactions outside of allowing
the cancel button clicks/progress repainting.

I've tried many approaches, but this is the only one which
works reliably...
  • Loading branch information
nyalldawson committed Jan 9, 2018
1 parent 2eb68de commit 240c52a4c03860907b85de488ed81e574abaa244
@@ -133,6 +133,12 @@ Sets a progress text message.
void pushConsoleInfo( const QString &info );
%Docstring
Pushes a console info string to the dialog's log.
%End

QDialog *createProgressDialog();
%Docstring
Creates a modal progress dialog showing progress and log messages
from this dialog.
%End

protected:
@@ -210,6 +216,7 @@ Called when the algorithm has finished executing.
};



/************************************************************************
* This file has been generated automatically from *
* *
@@ -63,6 +63,7 @@ class AlgorithmDialog(QgsProcessingAlgorithmDialogBase):

def __init__(self, alg):
super().__init__()
self.feedback_dialog = None

self.setAlgorithm(alg)
self.setMainWidget(self.getParametersPanel(alg, self))
@@ -196,12 +197,6 @@ def accept(self):

self.clearProgress()
self.setProgressText(self.tr('Processing algorithm...'))
# Make sure the Log tab is visible before executing the algorithm
try:
self.showLog()
self.repaint()
except:
pass

self.setInfo(
self.tr('<b>Algorithm \'{0}\' starting...</b>').format(self.algorithm().displayName()), escapeHtml=False)
@@ -215,6 +210,13 @@ def accept(self):
start_time = time.time()

if self.iterateParam:
# Make sure the Log tab is visible before executing the algorithm
try:
self.showLog()
self.repaint()
except:
pass

self.cancelButton().setEnabled(self.algorithm().flags() & QgsProcessingAlgorithm.FlagCanCancel)
if executeIterating(self.algorithm(), parameters, self.iterateParam, context, feedback):
feedback.pushInfo(
@@ -240,15 +242,25 @@ def on_complete(ok, results):
self.tr('Execution failed after {0:0.2f} seconds'.format(time.time() - start_time)))
feedback.pushInfo('')

if self.feedback_dialog is not None:
self.feedback_dialog.close()
self.feedback_dialog.deleteLater()
self.feedback_dialog = None

self.cancelButton().setEnabled(False)

self.finish(ok, results, context, feedback)

if self.algorithm().flags() & QgsProcessingAlgorithm.FlagCanRunInBackground:
# Make sure the Log tab is visible before executing the algorithm
self.showLog()

task = QgsProcessingAlgRunnerTask(self.algorithm(), parameters, context, feedback)
task.executed.connect(on_complete)
QgsApplication.taskManager().addTask(task)
else:
self.feedback_dialog = self.createProgressDialog()
self.feedback_dialog.show()
ok, results = execute(self.algorithm(), parameters, context, feedback)
on_complete(ok, results)

@@ -306,6 +306,23 @@ void QgsProcessingAlgorithmDialogBase::pushConsoleInfo( const QString &info )
processEvents();
}

QDialog *QgsProcessingAlgorithmDialogBase::createProgressDialog()
{
QgsProcessingAlgorithmProgressDialog *dialog = new QgsProcessingAlgorithmProgressDialog( this );
dialog->setWindowModality( Qt::ApplicationModal );
dialog->setWindowTitle( windowTitle() );
connect( progressBar, &QProgressBar::valueChanged, dialog->progressBar(), &QProgressBar::setValue );
connect( dialog->cancelButton(), &QPushButton::clicked, buttonCancel, &QPushButton::click );
dialog->logTextEdit()->setHtml( txtLog->toHtml() );
connect( txtLog, &QTextEdit::textChanged, dialog, [this, dialog]()
{
dialog->logTextEdit()->setHtml( txtLog->toHtml() );
QScrollBar *sb = dialog->logTextEdit()->verticalScrollBar();
sb->setValue( sb->maximum() );
} );
return dialog;
}

void QgsProcessingAlgorithmDialogBase::setPercentage( double percent )
{
// delay setting maximum progress value until we know algorithm reports progress
@@ -393,4 +410,37 @@ void QgsProcessingAlgorithmDialogBase::setInfo( const QString &message, bool isE
processEvents();
}

//
// QgsProcessingAlgorithmProgressDialog
//

QgsProcessingAlgorithmProgressDialog::QgsProcessingAlgorithmProgressDialog( QWidget *parent )
: QDialog( parent )
{
setWindowFlags( Qt::Dialog ); // hide close button
setupUi( this );
}

QProgressBar *QgsProcessingAlgorithmProgressDialog::progressBar()
{
return mProgressBar;
}

QPushButton *QgsProcessingAlgorithmProgressDialog::cancelButton()
{
return mButtonBox->button( QDialogButtonBox::Cancel );
}

QTextEdit *QgsProcessingAlgorithmProgressDialog::logTextEdit()
{
return mTxtLog;
}

void QgsProcessingAlgorithmProgressDialog::reject()
{

}



///@endcond
@@ -19,6 +19,7 @@
#include "qgis.h"
#include "qgis_gui.h"
#include "ui_qgsprocessingalgorithmdialogbase.h"
#include "ui_qgsprocessingalgorithmprogressdialogbase.h"
#include "processing/qgsprocessingcontext.h"
#include "processing/qgsprocessingfeedback.h"

@@ -180,6 +181,12 @@ class GUI_EXPORT QgsProcessingAlgorithmDialogBase : public QDialog, private Ui::
*/
void pushConsoleInfo( const QString &info );

/**
* Creates a modal progress dialog showing progress and log messages
* from this dialog.
*/
QDialog *createProgressDialog();

protected:

/**
@@ -271,11 +278,53 @@ class GUI_EXPORT QgsProcessingAlgorithmDialogBase : public QDialog, private Ui::
bool mHelpCollapsed = false;

QString formatHelp( QgsProcessingAlgorithm *algorithm );
void processEvents();
void scrollToBottomOfLog();
void processEvents();

};

#ifndef SIP_RUN

/**
* \ingroup gui
* \brief A modal dialog for showing algorithm progress and log messages.
* \note This is not considered stable API and may change in future QGIS versions.
* \since QGIS 3.0
*/
class QgsProcessingAlgorithmProgressDialog : public QDialog, private Ui::QgsProcessingProgressDialogBase
{
Q_OBJECT

public:

/**
* Constructor for QgsProcessingAlgorithmProgressDialog.
*/
QgsProcessingAlgorithmProgressDialog( QWidget *parent = nullptr );

/**
* Returns the dialog's progress bar.
*/
QProgressBar *progressBar();

/**
* Returns the dialog's cancel button.
*/
QPushButton *cancelButton();

/**
* Returns the dialog's text log.
*/
QTextEdit *logTextEdit();

public slots:

void reject() override;

};

#endif

///@endcond

#endif // QGSPROCESSINGALGORITHMDIALOGBASE_H
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsProcessingProgressDialogBase</class>
<widget class="QDialog" name="QgsProcessingProgressDialogBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1603</width>
<height>1239</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTextEdit" name="mTxtLog">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QProgressBar" name="mProgressBar">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="mButtonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

0 comments on commit 240c52a

Please sign in to comment.
You can’t perform that action at this time.