Saturday, December 3, 2011

PunchClock 0.2.5 Released, JFace lessons

I finally got to a point where I thought enough of the nagging items with PunchClock were resolved that I would take the time to publish a new release. I am pretty proud of this release. There are no major new features but a lot of work went into making the code better, improving the infrastructure to make it a more rounded, solid product. For details on what was done, see  the PunchClock 0.2.5 release announcement.

 I've been personally using PunchClock versions daily to keep track of time at my work, I think I've pretty much gotten it to a stable, usable point. I don't see much new work going into it, unless I have some inspiration.


A couple of interesting lessons learned I think I'll share.

Utilizing JFace's built in ImageRegistry
I needed to add a custom error message to my Edit Session dialog. I did this using two labels. One that displays an icon (info, warning, error) and the other will have the message text. I tried using Label.setImage() for the label with the message text, however, the image "takes over" and no text is displayed, so I needed to keep them separate.

I know JFace must have the icons somewhere as they are used by the WizardPage status messages. It turns out they are available in the org.eclipse.jface.resource.JFaceResources.getImage() utility method. The icon images are available as constants of the org.eclipse.jface.dialogs.Dialog class.

Here is the setMessage() method from my custom EditSessionDialog class, it is called by a Listener that is attached to all the widgets in the form, listening on any change.

    /**
     * Updates the status message of this dialog with errors or other
     * information to display to the user.
     *
     * @param messageText
     *            - text to display
     * @param status
     *            - one of IMessageProvider constants
     */
    public void setMessage(String messageText, int status) {
        _logger.fine(messageText + ", status: " + status);
        Image messageImage = null;
        // based on status, display appropriate icon
        switch (status) {
        case IMessageProvider.INFORMATION:
            messageImage = JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_INFO);
            break;
        case IMessageProvider.WARNING:
            messageImage = JFaceResources
                    .getImage(Dialog.DLG_IMG_MESSAGE_WARNING);
            break;
        case IMessageProvider.ERROR:
            messageImage = JFaceResources
                    .getImage(Dialog.DLG_IMG_MESSAGE_ERROR);
            break;
        case IMessageProvider.NONE:
        default:
            messageImage = null;
            break;

        }
        // update the labels with new values
        _messageLabel.setText(messageText);
        _messageIcon.setImage(messageImage);
        _messageIcon.redraw();
        // resize widgets in dialog
        _messageIcon.pack(true);
        _messageLabel.pack(true);
    }



JFace ErrorDialog
As I transitioned all of the code in PunchClock away from writing out direct to the console, and instead used java.util.logging API, it became apparent that the way that exceptions were being handled was inconsistent, and in fact exceptions were getting thrown that were never handled. It seems that if there is no ExceptionHandler defined for the Window class, the exceptions don't get shown in the UI, though the program continues to run, leading to some unexpected results, such as clicking on a button and nothing seeming to happen.

So first step was to create a class that implements IExceptionHandler and then set it as the ExceptionHandler for my main window, using Window.setExceptionHandler(). This lets me catch all Throwables thrown and not caught in the PunchClock code.

Then I did find that JFace conveniently has a prebuilt dialog for displaying errors to the UI, which before I was able to do in one off cases using a custom built SWT dialog function. The example I took my queue from was here http://www.java2s.com/Code/Java/SWT-JFace-Eclipse/JFacesErrorDialogclass.htm

My example handleException method in my ExceptionHandler

/* (non-Javadoc)
     * @see org.eclipse.jface.window.Window.IExceptionHandler#handleException(java.lang.Throwable)
     */
    @Override
    public void handleException(Throwable arg0) {
        _logger.log(Level.SEVERE, "Uncaught exception from UI", arg0);
        // Create the required Status object
        Status status = new Status(IStatus.ERROR, "PunchClock", 0,
            "Unhandled Exception "+arg0.getMessage(), arg0);

        // Display the dialog
        ErrorDialog.openError(Display.getCurrent().getActiveShell(),
            "Error Occured", "Program Error. See PunchClock.log for more details.", status);
    }

It wasn't too clear what all these parameters do in terms of where they get used in the ErrorDialog. Here is an example of the error dialog produced by above code with a NullPointerException
The title of the dialog, and the first message line is from the openError() method, second and third parameters respectively, the fourth parameter, the Status object, controls the "Reason:" message, and if you include the actual Throwable in the status it gets displayed in the expandable Details box, although that is not required, you can just pass null.

Editor's note: stay tuned for updates to the punchclock wiki and FAQ pages as I round out documentation with all the updates from the latest releases.