Workspace 6.21.5
Providing User Feedback During Execution

Tutorial contents


Tutorial goals

Unless an operation is performing something trivial, it is often useful to give the user some feedback about what the operation is doing. Textual output can be given to inform the user about some intermediate result, or a notification of progress on terms of percent complete could also prove useful. This tutorial shows how to do both of these things.


Logging text from a workspace operation

Workspace operations often need to output text to record intermediate result or inform to the user about what the operation is doing. While C++ and other languages generally provide their own language-specific way to output text to the console, these mechanisms typically offer only very basic capabilities. In particular, they are generally inadequate for handling text logged simultaneously from different threads, often resulting in the text being jumbled together or even worse, causing corruption.

The Operation base class provides a logLine() as well as a logText() function for logging text safely:

void logLine(const String& msg);
void logText(const String& msg);

The usual place where logLine() or logText() would be called is from within an operation's execute() function. For example:

bool MyOperation::execute()
{
logLine("Starting my operation's execution.");
// ... Do some processing here ...
logLine("Finished my operation's execution.");
return true;
}

Note that logLine() automatically appends a carriage return at the end of the message you supply whereas logText() does not. Rather than thinking of your log messages as individual lines, it is recommended that you think of them in terms of paragraphs and let whatever is presenting the logged text to the user decide where line breaks should occur. You should use new line characters to indicate that the block of text being logged has now logically finished (an exception to this would be if you are outputting pre-formatted text). Also, if your operation logs any text in its execute() function, then it should also ensure that the last thing it logs is a new line character (or use logLine()) This ensures that its text is separated from any text subsequently logged by another operation or some other part of the application.

Message Categories: Info, debug, warnings and errors

Operations often need to log warning and error messages, preferably in a way that brings them to the attention of the user. However, the text being logged could be getting sent to any number of destinations, such as to a file, the console or to a special-purpose widget in a GUI application. It will be up to the user or application author to make these warning and error messages stand out.

For this reason, logLine() and logText() can also be used with a message category parameter:

void logLine(MessageCategory &category, const String& msg);
void logText(MessageCategory &category, const String& msg);

where the message category can be LOG_INFO, LOG_DEBUG, LOG_VERBOSE, LOG_WARNING or LOG_ERROR

To make it easier for users and application authors to highlight warning and error messages, it is recommended that you always use an appropriate message category:

bool MyOperation::execute()
{
// ... Something occurred that we want to warn the user about
logLine(LOG_WARNING, "Encountered a potentially undesirable condition.");
// ... Do some more processing ...
logLine(LOG_INFO, "Did some more processing.");
// ... Oops, encountered a fatal problem
logLine(LOG_ERROR, "Encountered a fatal situation, so aborting.");
return false;
}

Where does logged text go?

It is well and good to provide log text from your workspace operation, but it is helpful to have some awareness of where it might end up being sent. In most cases, the text will be forwarded to a logging stream set up by the application automatically when the operation is added to a workflow. This logging stream could be to a console, a file on disk or even a logging window in a GUI application. For the most part, it should not really matter where the logged text is being sent, but in some circumstances the destination might buffer the text so that it does not get shown immediately. Quite often, these types of logging destinations will only flush the buffer when they encounter a carriage return. This is worth noting, since it could potentially be an issue if you are attempting to debug your operation and never get to see the last block of logged text because it didn't end with a carriage return. Thus, it is generally wise to output your entire log message in one go and end it with a carriage return. Other than that, you should not need to care greatly where your logged text might go. Leave that decision up to the controlling application.


Reporting progress

While in some circumstances it is useful to indicate progress by logging text messages, sometimes text is not the most appropriate form. For things like GUI applications, progress bar widgets need to receive an integer value. To facilitate this, the Operation base class provides a setProgress() function for notifying interested parties about the progress and a getProgress() function to retrieve the last progress value that was set:

void setProgress(int progress);
int getProgress() const;

The progress parameter to setProgress() should be in the range 0 to 100. When the execute() function is entered, getProgress() will return -1 to indicate that no progress has been set yet. Once setProgress() has been called, getProgress() will return that value until the next call to setProgress(). Calling setProgress() with the same value as the current progress (ie what getProgress() would return) will have no effect - no clients will be notified of the call.

Operations don't have to report their progress if they don't want to. If they don't call setProgress() then their progress will be deemed "indeterminate" for the duration of the call.

The appropriate way to report progress is demonstrated most easily for an operation that executes a loop a set number of times:

bool MyOperation::execute()
{
// This operation will report progress, so always start by setting
// the progress to zero
setProgress(0);
int numIterations = 2000;
double range = numIterations; // So we use floating point division, not integer
for (int i = 0; i != numIterations; ++i)
{
//For any long-running execute() implementation, should call getTerminateExecution()
//to check if the operation has been asked to terminate execution.
if (getTerminateExecution())
{
logLine(LOG_WARNING, tr("Execution was asked to terminate"));
return false;
}
setProgress(100 * (i / range));
// ... Do some processing for this iteration
}
// It is optional whether you explicitly set the progress
// upon completion, but for completeness it is advisable
setProgress(100);
return true;
}
double i
Definition: opencljuliaset.cpp:45

Summary of important points

This tutorial has introduced you to the essentials of providing feedback during an operation's execution. The main points to remember are the following:

  • Instead of using your language's own output facilities, use the logLine() or logText() function to record all text output.
  • Operations can optionally report their own percent complete progress by calling setProgress() and retrieve the currently set progress by calling getProgress() . A progress value of -1 means that the progress has not been set, which usually means that the operation does not report its progress.