How do you report progress in your software ??

February 18, 2011

In order to improve usability of my RCP application I decided to manage all the “long” operations  using Jobs. This post will introduce several solutions to handle “composite” tasks progress reporting across a concrete example I faced this morning.

Image just to bring your eyes to my post and encourage you to read it, is it working ??

Image just to bring your eyes to my post and encourage you to read it, is it working ?? ;-)

Having already used the Job API I felt confident on the time needed to implement this. I was right about the time needed to use the Job’s API but I was wrong about the way to organize several related operations into several (or one ??) job(s).

Lets describe the context. The user is performing a “File->Open” action and want report on the progress of this action. Progress report is needed because opened files are big and a lot of computation has to be done on these files. This “File->Open” action is composed of 3 sequential “subtasks”: parsing, analyse and display. I am able to easily report accurate progress for each one of these tasks, but unfortunately I am not able then to have an accurate estimation of each task time in the global process composed of the 3 tasks.

Today I have 2 solutions in order to report what is happening behind the scene how many time is happening behind the scene (this IS really what the user cares !!!).

First solution: Use 3 distincts Jobs.

This solution can be easily implemented using IJobChangeListeners. A first Job is created for the parsing tasks. This Job is scheduled and thanks to Job listeners I am able to be notified when it’s completed in order to create and schedule the analyze Job. The same process is apply between the analyze Job and the display Job. This solution present the 3 tasks to the user with an accurate estimation time for EACH ONE of these tasks. As I mentioned before, this is the best I can do because I am not able to estimate each task time in the global process.  Here the new user may think that the global “File Open” action will be completed at the end of the first Job ….??!!!  Another drawback of this solution is that the user is prompted with 3 UI progress dialogs (all my Jobs are user Jobs so there is successively: “Parsing File dialog”, “Analyze File dialog” and “Display Dialog” ) for only one “File->Open” action.

Second solution: Use one main Job and 3 Submonitors inside this Job.

I came to this solution in order to try to fix the second drawback of the first one (3 UI dialogs for oen action). I’ll not step into implementation detail here but lets analyze the result. Why ? We have a unique UI progress dialog NOT accurate: we can clearly distinct the 3 stages stepping each one at a different speed. What is better: only one UI dialog not accurate or 2 “surprise” dialogs that appear after the first accurate one ?

An other solution would be (I didn’t find anyway to implement it for now):

Third solution (not sure this can be done .. any ideas ??): Have one Job with 3 Submonitors inside this Job but reporting the 3 Submonitors as 3 X 100 %. What I mean here is to have only one UI progress dialog called “Opening File” that will be “filled in from 0% to 100%” three times (one for each sub-task). This solution is the same as the first one but it will fix the problem of having 3 separated UI dialogs.

In all of these solution the end user “will” have on the first usage a “wrong” first estimation time …. Thus I would be interested to know how you handle such situations, so feel free to leave comments on this topic.


How drop GEF editors figures in the outside world

February 7, 2011

In a Gef editor I want to let the users drag and drop figures (== model objects) to an other custom view available in my tool’s perspective.

Adding a DragSource with my own drag transfer on my GEF editor figure canvas allows that. But as a side effect, and I don’t want this side effect, this disable the possibility to move the figures INSIDE the editor using drag and drop.

After investigations I found this post on eclipse forums. The solution is acceptable but not perfect. Thus I investigated deeper and came to the following pure SWT snippet that explains why we have this behavior: MouseMove events (the ones used by gef to support dragging INSIDE the editor) are no more fired once a drag source has been added:

import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class SwtTest {
public static void main(String[] args) {
final Display display = new Display();
final Shell shell = new Shell(display);

shell.addMouseMoveListener(new MouseMoveListener() {

@Override
public void mouseMove(MouseEvent e) {
System.out.println("Mouse move");
}
});
DragSourceListener dragListener = new DragSourceListener() {

public void dragFinished(DragSourceEvent event) {
System.out.println("dragFinished");

}

public void dragSetData(DragSourceEvent event) {
System.out.println("dragSetData");

}

public void dragStart(DragSourceEvent event) {
System.out.println("dragStart");
}
};

DragSource dragSource = new DragSource(shell, DND.DROP_COPY | DND.DROP_MOVE);
dragSource.addDragListener(dragListener);
dragSource.setTransfer(new Transfer[] { FileTransfer.getInstance() });

shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
}

I guess this is the normal behavior fro man SWT point of view.

As a side note I would be interested for a solution to this issue other that the one proposed on Eclipse forum consisting in activating my DragSource only if a given condition is met such as Shift is pressed (this is done in a DragSourceListener.dragStart method by setting event.doit to false.


Follow

Get every new post delivered to your Inbox.