es.deusto.weblab.client.experiments.xilinx.ui.XilinxExperiment.java Source code

Java tutorial

Introduction

Here is the source code for es.deusto.weblab.client.experiments.xilinx.ui.XilinxExperiment.java

Source

/*
* Copyright (C) 2005 onwards University of Deusto
* All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution.
*
* This software consists of contributions made by many individuals, 
* listed below:
*
* Author: Pablo Ordua <pablo@ordunya.com>
*         Luis Rodriguez <luis.rodriguez@opendeusto.es>
*
*/
package es.deusto.weblab.client.experiments.xilinx.ui;

import java.util.Vector;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

import es.deusto.weblab.client.comm.exceptions.CommException;
import es.deusto.weblab.client.configuration.IConfigurationRetriever;
import es.deusto.weblab.client.dto.experiments.Command;
import es.deusto.weblab.client.dto.experiments.ResponseCommand;
import es.deusto.weblab.client.experiments.xilinx.XilinxCreatorFactory;
import es.deusto.weblab.client.lab.comm.UploadStructure;
import es.deusto.weblab.client.lab.comm.callbacks.IResponseCommandCallback;
import es.deusto.weblab.client.lab.experiments.ExperimentBase;
import es.deusto.weblab.client.lab.experiments.IBoardBaseController;
import es.deusto.weblab.client.ui.widgets.IWlActionListener;
import es.deusto.weblab.client.ui.widgets.WlButton.IWlButtonUsed;
import es.deusto.weblab.client.ui.widgets.WlClockActivator;
import es.deusto.weblab.client.ui.widgets.WlPredictiveProgressBar;
import es.deusto.weblab.client.ui.widgets.WlPredictiveProgressBar.IProgressBarListener;
import es.deusto.weblab.client.ui.widgets.WlPredictiveProgressBar.IProgressBarTextUpdater;
import es.deusto.weblab.client.ui.widgets.WlPredictiveProgressBar.TextProgressBarTextUpdater;
import es.deusto.weblab.client.ui.widgets.WlSwitch;
import es.deusto.weblab.client.ui.widgets.WlTimedButton;
import es.deusto.weblab.client.ui.widgets.WlTimer;
import es.deusto.weblab.client.ui.widgets.WlTimer.IWlTimerFinishedCallback;
import es.deusto.weblab.client.ui.widgets.WlWaitingLabel;
import es.deusto.weblab.client.ui.widgets.WlWebcam;

public class XilinxExperiment extends ExperimentBase {

    /******************
    * UIBINDER RELATED
    ******************/

    interface WlDeustoXilinxBasedBoardUiBinder extends UiBinder<Widget, XilinxExperiment> {
    }

    private static final WlDeustoXilinxBasedBoardUiBinder uiBinder = GWT
            .create(WlDeustoXilinxBasedBoardUiBinder.class);

    private static final String DEFAULT_XILINX_WEBCAM_IMAGE_URL = GWT.getModuleBaseURL() + "/waiting_url_image.jpg";

    private final int DEFAULT_EXPECTED_PROGRAMMING_TIME = 25000;
    private final int DEFAULT_EXPECTED_SYNTHESIZING_TIME = 120000;

    private static final int IS_READY_QUERY_TIMER = 1000;
    private static final String STATE_SYNTHESIZING_ERROR = "synthesizing_error";
    private static final String STATE_AWAITING_CODE = "awaiting_code";
    private static final String STATE_NOT_READY = "not_ready";
    private static final String STATE_SYNTHESIZING = "synthesizing";
    private static final String STATE_PROGRAMMING = "programming";
    private static final String STATE_READY = "ready";
    private static final String STATE_FAILED = "failed";
    private static final String STATE_NOT_ALLOWED = "not_allowed";

    // The state the experiment is currently in. Must be a string within the states list.
    private String currentState;

    public static class Style {
        public static final String TIME_REMAINING = "wl-time_remaining";
        public static final String CLOCK_ACTIVATION_PANEL = "wl-clock_activation_panel";
    }

    private static final boolean DEBUG_ENABLED = false;

    @UiField
    public VerticalPanel verticalPanel;
    @UiField
    VerticalPanel widget;
    @UiField
    VerticalPanel innerVerticalPanel;
    @UiField
    HorizontalPanel uploadStructurePanel;

    @UiField
    Button uploadButton;
    @UiField
    Label selectProgram;

    @UiField
    HorizontalPanel timerMessagesPanel;
    @UiField
    WlWaitingLabel messages;
    @UiField
    WlClockActivator clockActivator;

    @UiField
    HorizontalPanel switchesRow;
    @UiField
    HorizontalPanel buttonsRow;
    @UiField
    HorizontalPanel webcamPanel;

    @UiField
    WlPredictiveProgressBar progressBar;

    //@UiField(provided=true)
    private UploadStructure uploadStructure;

    private WlWebcam webcam;

    @UiField(provided = true)
    WlTimer timer;

    private Timer readyTimer;
    private boolean deviceReady;
    private int expectedProgrammingTime = this.DEFAULT_EXPECTED_PROGRAMMING_TIME;
    private int expectedSynthesizingTime = this.DEFAULT_EXPECTED_SYNTHESIZING_TIME;

    private boolean synthesizingMode = false;

    private final Vector<Widget> interactiveWidgets;

    public XilinxExperiment(IConfigurationRetriever configurationRetriever, IBoardBaseController boardController) {
        super(configurationRetriever, boardController);

        this.deviceReady = false;

        this.interactiveWidgets = new Vector<Widget>();

        this.createProvidedWidgets();

        XilinxExperiment.uiBinder.createAndBindUi(this);

        this.webcamPanel.add(this.webcam.getWidget());

        this.findInteractiveWidgets();

        this.disableInteractiveWidgets();

        if (isDemo()) {
            if (isMultiresourceDemo()) {
                this.selectProgram.setText(i18n.thisDemoDemonstratesMultiresourceXilinx());
            } else {
                this.selectProgram.setText(i18n.thisDemoDoesNotAllowUpload());
            }
        }
    }

    private boolean isDemo() {
        return this.configurationRetriever.getBoolProperty(XilinxCreatorFactory.IS_DEMO);
    }

    private boolean isMultiresourceDemo() {
        return this.configurationRetriever.getBoolProperty(XilinxCreatorFactory.IS_MULTIRESOURCE_DEMO);
    }

    /**
     * Will find those interactive widgets that are defined on UiBinder
     * and add them to the interactive widgets list, so that they can
     * be disabled. This isn't too convenient but currently there doesn't 
     * seem to be any other way around. That may change in the future.
     */
    private void findInteractiveWidgets() {

        // Find switches
        for (int i = 0; i < this.switchesRow.getWidgetCount(); ++i) {
            final Widget wid = this.switchesRow.getWidget(i);
            if (wid instanceof WlSwitch) {
                final WlSwitch swi = (WlSwitch) wid;
                this.addInteractiveWidget(swi);
            }
        }

        // Find timed buttons
        for (int i = 0; i < this.buttonsRow.getWidgetCount(); ++i) {
            final Widget wid = this.buttonsRow.getWidget(i);
            if (wid instanceof WlTimedButton) {
                final WlTimedButton swi = (WlTimedButton) wid;
                this.addInteractiveWidget(swi);
            }
        }

    }

    /**
     * Creates those widgets that are specified in the UiBinder xml
     * file but which are marked as provided because they can't be
     * allocated using the default ctor.
     */
    private void createProvidedWidgets() {
        this.webcam = new WlWebcam(XilinxExperiment.DEFAULT_XILINX_WEBCAM_IMAGE_URL);
        this.webcam.setTime(this.configurationRetriever);

        this.timer = new WlTimer(false);

        this.timer.setTimerFinishedCallback(new IWlTimerFinishedCallback() {
            @Override
            public void onFinished() {
                XilinxExperiment.this.boardController.clean();
            }
        });

        if (!isDemo()) {
            this.uploadStructure = new UploadStructure();
            this.uploadStructure.setFileInfo("program");
        }
    }

    @Override
    public void initialize() {

        // Doesn't seem to work from UiBinder.
        if (!isDemo()) {
            this.uploadStructurePanel.add(this.uploadStructure.getFormPanel());
        }

        this.webcam.setVisible(false);
    }

    @Override
    public void queued() {
        this.widget.setVisible(false);
        this.selectProgram.setVisible(false);
    }

    /**
     * Event handler which gets called whenever the upload button is pressed.
     * The upload button is currently only visible when the file could not be
     * uploaded on the reservation stage, generally due to wrong user input.
     * 
     * @param e
     */
    @UiHandler("uploadButton")
    void handleClick(ClickEvent e) {
        final boolean success = this.tryUpload();

        if (success)
            this.uploadButton.setVisible(false);
    }

    /**
     * Helper method to try to upload a file. Currently, we only consider that an upload
     * failed if the filename the user chose is empty.
     * If the upload succeeds we load the standard experiment controls through loadStartControls and
     * hide the upload panel, which is no longer needed.
     * 
     * @return True if the upload succeeds, false otherwise.
     */
    private boolean tryUpload() {
        final boolean didChooseFile = !this.uploadStructure.getFileUpload().getFilename().isEmpty();

        if (didChooseFile) {

            // Extract the file extension.
            final String filename = this.uploadStructure.getFileUpload().getFilename();
            final String[] split = filename.split("\\.");
            String extension;
            if (split.length == 0)
                extension = "bit"; // BIT as default
            extension = split[split.length - 1];

            this.uploadStructure.getFormPanel().setVisible(false);
            this.uploadStructure.setFileInfo(extension.toLowerCase());

            // TODO: Probably it would be more elegant if the server itself would decide whether we are synthesizing
            // and programming or just programming. However, at least for now, this will do fine. The mode is currently
            // used only to decide how to estimate progress bar length.
            if (extension.toLowerCase().equals("vhd"))
                this.synthesizingMode = true;

            this.boardController.sendFile(this.uploadStructure, this.sendFileCallback);
            this.loadStartControls();
        } else {
            GWT.log("The user did not really choose a file");
        }

        return didChooseFile;
    }

    /**
     * Called when the experiment starts. 
     * @param time Time available for the experiment
     * @param initialConfiguration JSON-encoded server-provided configuration parameters. 
     * This feature is part of the API version 2. Parameters expected by this experiment
     * are "webcam", "expected_programming_time", "expected_synthesizing_time".
     */
    @Override
    public void start(int time, String initialConfiguration) {

        final JSONValue parsedInitialConfiguration = JSONParser.parseStrict(initialConfiguration);

        this.currentState = STATE_NOT_READY;

        try {
            final String webcamUrl = parsedInitialConfiguration.isObject().get("webcam").isString().stringValue();
            this.webcam.setUrl(webcamUrl);
        } catch (Exception e) {
            this.messages.setText("[Xilinx] Did not receive the webcam parameter.");
            GWT.log("[Xilinx] Did not receive the webcam parameter.", null);
            return;
        }

        try {
            double expectedProgrammingTime = parsedInitialConfiguration.isObject().get("expected_programming_time")
                    .isNumber().doubleValue();
            XilinxExperiment.this.expectedProgrammingTime = (int) (expectedProgrammingTime * 1000);
        } catch (Exception e) {
            this.messages.setText("[Xilinx] Did not receive the expected_programming_time parameter.");
            GWT.log("[Xilinx] Did not receive the expected_programming_time parameter.", null);
            return;
        }

        // TODO: Consider whether this parameter should or should not be mandatory. For now, it isn't. 
        // If the parameter is not provided by the server then the default will be used.
        try {
            double expectedSynthesizingTime = parsedInitialConfiguration.isObject()
                    .get("expected_synthesizing_time").isNumber().doubleValue();
            XilinxExperiment.this.expectedSynthesizingTime = (int) (expectedSynthesizingTime * 1000);
        } catch (Exception e) {
            //this.messages.setText("[Xilinx] Did not receive the expected_synthesizing_time parameter.");
            GWT.log("[Xilinx] Did not receive the expected_synthesizing_time parameter. Using the default. ", null);
            XilinxExperiment.this.expectedSynthesizingTime = this.DEFAULT_EXPECTED_SYNTHESIZING_TIME;
            //return;
        }

        // If it's not a demo, the user will have been prompted a file uploading form.
        // He might have indeed chosen a file to upload, or he might not.
        if (!isDemo()) {

            boolean success = this.tryUpload();

            // If the file upload attempt on the reserve stage failed, then we will have to display 
            // a button during the experiment itself so that the user can request the file he chose
            // be uploaded to the server.
            if (!success)
                this.uploadButton.setVisible(true);

        } else {
            this.loadStartControls();
        }

        // The experiment started, so we should start the timer.
        this.timer.start();

        // Start polling to know when the board has been programmed and the server is ready
        // to receive our requests.
        setupReadyTimer();
    }

    /**
     * Loads those controls that are meant to be displayed
     * when the experiment starts.
     */
    private void loadStartControls() {
        this.loadProgressBar();

        this.widget.setVisible(true);
        this.selectProgram.setVisible(false);

        this.loadWidgets();
        this.disableInteractiveWidgets();
    }

    /**
     * Will setup the timer that will poll the experiment server for its state, to
     * know when the board programming process ends and how.
     */
    private void setupReadyTimer() {

        this.readyTimer = new Timer() {
            @Override
            public void run() {

                // Build the command to query the state.
                final Command command = new Command() {
                    @Override
                    public String getCommandString() {
                        return "STATE";
                    }
                }; //! new Command

                // Send the command and react to the response
                XilinxExperiment.this.boardController.sendCommand(command, new IResponseCommandCallback() {
                    @Override
                    public void onFailure(CommException e) {
                        XilinxExperiment.this.messages.setText(
                                "There was an error while trying to find out whether the experiment is ready");
                    }

                    @Override
                    public void onSuccess(ResponseCommand responseCommand) {

                        // Read the full message returned by the exp server and ensure it's not empty
                        final String resp = responseCommand.getCommandString();
                        if (resp.length() == 0)
                            XilinxExperiment.this.messages.setText("The STATE query returned an empty result");

                        // The command follows the format STATE=ready
                        // Extract both parts
                        final String[] tokens = resp.split("=", 2);
                        if (tokens.length != 2 || !tokens[0].equals("STATE")) {
                            XilinxExperiment.this.messages
                                    .setText("Unexpected response ot the STATE query: " + resp);
                            return;
                        }

                        final String state = tokens[1];

                        // Update the current state. This is needed in other places within this experiment.
                        XilinxExperiment.this.currentState = state;

                        System.out.println("[DBG]: Current state is: " + state);

                        if (state.equals(STATE_NOT_READY)) {
                            XilinxExperiment.this.readyTimer.schedule(IS_READY_QUERY_TIMER);
                        } else if (state.equals(STATE_READY)) {
                            // Ready
                            XilinxExperiment.this.onDeviceReady();
                        } else if (state.equals(STATE_SYNTHESIZING)) {
                            // Check in a few seconds whether the state changed.
                            XilinxExperiment.this.readyTimer.schedule(IS_READY_QUERY_TIMER);
                        } else if (state.equals(STATE_PROGRAMMING)) {
                            // Check in a few seconds whether the state changed.
                            XilinxExperiment.this.readyTimer.schedule(IS_READY_QUERY_TIMER);
                        } else if (state.equals(STATE_FAILED)) {
                            // Something failed in the programming.
                            XilinxExperiment.this.onDeviceProgrammingFailed();
                        } else if (state.equals(STATE_SYNTHESIZING_ERROR)) {
                            // Compiling failed. 
                            XilinxExperiment.this.onDeviceSynthesizingError();
                        } else if (state.equals(STATE_NOT_ALLOWED)) {
                            // That filetype is not allowed.
                            XilinxExperiment.this.onFileNotAllowed();
                        } else if (state.equals(STATE_AWAITING_CODE)) {
                            // Awaiting for VHDL code.
                            // TODO: Implement this. THIS IS NOT YET SUPPORTED.
                            XilinxExperiment.this.messages.setText("STATE_AWAITING_CODE is not yet supported");
                        } else {
                            XilinxExperiment.this.messages
                                    .setText("Received unexpected response to the STATE query");
                        }
                    } //! onSuccess
                }); //! new IResponseCommandCallback for the STATE command.
            } //! run() of the Timer
        }; //! new Timer

        this.readyTimer.schedule(1000);

    } //! setupReadyTimer

    final IResponseCommandCallback sendFileCallback = new IResponseCommandCallback() {

        @Override
        public void onSuccess(ResponseCommand response) {
            XilinxExperiment.this.messages.setText(i18n.fileSent());
        }

        @Override
        public void onFailure(CommException e) {

            GWT.log("It was not possible to send the file");

            if (XilinxExperiment.DEBUG_ENABLED)
                XilinxExperiment.this.enableInteractiveWidgets();

            XilinxExperiment.this.messages.stop();

            XilinxExperiment.this.progressBar.stop();
            XilinxExperiment.this.progressBar.setTextUpdater(new IProgressBarTextUpdater() {
                @Override
                public String generateText(double progress) {
                    return "Error. Could not complete.";
                }
            });

            XilinxExperiment.this.messages.setText("Error sending file: " + e.getMessage());

        }
    };

    /**
     * Called when the STATE query tells us that the experiment is ready.
     */
    private void onDeviceReady() {
        this.deviceReady = true;

        if (XilinxExperiment.this.progressBar.isWaiting()) {
            this.progressBar.stop();
            this.progressBar.setVisible(false);
        } else
            // Make the bar finish in a few seconds, it will make itself
            // invisible once it is full.
            this.progressBar.finish(300);

        this.enableInteractiveWidgets();
        this.messages.setText(i18n.deviceReady());
        this.messages.stop();
    }

    /**
     * Called when the STATE query tells us that the filetype we uploaded is
     * not allowed.
     */
    private void onFileNotAllowed() {
        this.deviceReady = true;

        if (XilinxExperiment.this.progressBar.isWaiting()) {
            this.progressBar.stop();
            this.progressBar.setVisible(false);
        } else
            // Make the bar finish in a few seconds, it will make itself
            // invisible once it is full.
            this.progressBar.finish(300);

        this.messages.setText(i18n.fileNotAllowed());
        this.messages.stop();
    }

    /**
     * Called when the STATE query tells us that the board programming failed.
     */
    private void onDeviceProgrammingFailed() {
        this.deviceReady = true;

        if (XilinxExperiment.this.progressBar.isWaiting()) {
            this.progressBar.stop();
            this.progressBar.setVisible(false);
        } else
            // Make the bar finish in a few seconds, it will make itself
            // invisible once it is full.
            this.progressBar.finish(300);

        this.messages.setText(i18n.deviceProgrammingFailed());
        this.messages.stop();
    }

    // TODO: Implement this properly.
    /**
     * Called when the STATE query tells us that the compiling process failed.
     */
    private void onDeviceSynthesizingError() {
        this.deviceReady = false;

        if (XilinxExperiment.this.progressBar.isWaiting()) {
            this.progressBar.stop();
            this.progressBar.setVisible(false);
        } else
            // Make the bar finish in a few seconds, it will make itself
            // invisible once it is full.
            this.progressBar.finish(300);

        final Command compilingResultCommand = new Command() {
            @Override
            public String getCommandString() {
                return "SYNTHESIZING_RESULT";
            }
        };

        this.messages.setText("Synthesizing failed");
        this.messages.stop();

        // Find out why compiling failed through the COMPILING_RESULT command.
        XilinxExperiment.this.boardController.sendCommand(compilingResultCommand, new IResponseCommandCallback() {
            @Override
            public void onFailure(CommException e) {
                XilinxExperiment.this.messages
                        .setText("There was an error while trying to retrieve the SYNTHESIZING_RESULT");
            }

            @Override
            public void onSuccess(ResponseCommand responseCommand) {
                XilinxExperiment.this.messages.setText("Synthesizing failed: \n" + responseCommand);
            }
        });

    }

    private void loadWidgets() {

        this.webcam.setVisible(true);
        this.webcam.start();

        this.messages.setText(i18n.sendingFile());
        this.messages.start();

        final ClockActivationListener clockActivationListener = new ClockActivationListener(this.boardController,
                this.getResponseCommandCallback());
        this.clockActivator.addClockActivationListener(clockActivationListener);

        this.addInteractiveWidget(this.timer.getWidget());
        this.addInteractiveWidget(this.clockActivator);

        this.prepareSwitchesRow();
        this.prepareButtonsRow();

        this.innerVerticalPanel.setSpacing(20);
    }

    private void loadProgressBar() {
        this.progressBar.setResolution(40);
        this.progressBar.setTextUpdater(new IProgressBarTextUpdater() {
            @Override
            public String generateText(double progress) {
                // Set the current action. Depending on the state, it will
                // be either compiling or programming the device.

                // TODO: Remove this.
                System.out.println("[DBG/GT]: State: " + XilinxExperiment.this.currentState);

                final String currentAction;
                if (XilinxExperiment.this.currentState.equals(STATE_PROGRAMMING))
                    currentAction = "Programming device";
                else if (XilinxExperiment.this.currentState.equals(STATE_SYNTHESIZING))
                    currentAction = "Synthesizing VHDL";
                else
                    currentAction = "Processing";

                return currentAction + " (" + (int) (progress * 100) + "%)";
            }
        });

        // Set up a listener to automatically remove the progress
        // bar whenever it reaches a 100%.
        this.progressBar.setListener(new IProgressBarListener() {
            @Override
            public void onFinished() {
                if (XilinxExperiment.this.deviceReady) {
                    XilinxExperiment.this.progressBar.setVisible(false);
                } else {
                    // This order is important, since setTextUpdater would call onFinished again
                    XilinxExperiment.this.progressBar.keepWaiting();
                    XilinxExperiment.this.progressBar
                            .setTextUpdater(new TextProgressBarTextUpdater(i18n.finishingProgramming() + "..."));
                }
            }
        });

        this.progressBar.setWaitPoint(0.98);
        this.progressBar.setVisible(true);

        if (this.synthesizingMode)
            this.progressBar.setEstimatedTime(this.expectedSynthesizingTime + this.expectedProgrammingTime);
        else
            this.progressBar.setEstimatedTime(this.expectedProgrammingTime);

        this.progressBar.start();
    }

    private void addInteractiveWidget(Widget widget) {
        this.interactiveWidgets.add(widget);
    }

    private void enableInteractiveWidgets() {
        for (int i = 0; i < this.interactiveWidgets.size(); ++i)
            this.interactiveWidgets.get(i).setVisible(true);
    }

    private void disableInteractiveWidgets() {
        for (int i = 0; i < this.interactiveWidgets.size(); ++i)
            this.interactiveWidgets.get(i).setVisible(false);
    }

    /* Iterates through every switch in the switchesRow panel,
     * setting up a listener for each of them. Switches found on it
     * are defined anonymously on UiBinder, along with their title.
     * This title is currently used as an integral identifier.
     */
    private HorizontalPanel prepareSwitchesRow() {

        for (int i = 0; i < this.switchesRow.getWidgetCount(); ++i) {
            final Widget wid = this.switchesRow.getWidget(i);
            if (wid instanceof WlSwitch) {
                final WlSwitch swi = (WlSwitch) wid;

                // Avoid trying to convert non-numerical titles (which serve
                // as identifiers). Not exactly an elegant way to do it.
                if (swi.getTitle().length() != 1)
                    continue;

                final int id = this.switchesRow.getWidgetCount() - Integer.parseInt(swi.getTitle()) - 1;
                final IWlActionListener actionListener = new SwitchListener(id, this.boardController,
                        this.getResponseCommandCallback());
                swi.addActionListener(actionListener);
                this.addInteractiveWidget(swi);
            }
        }

        return this.switchesRow;
    }

    /*
     * Iterates through every timed button in the buttonsRow panel,
     * setting up a listener for each of them. Buttons found on it
     * are defined anonymously on UiBinder, along with their title.
     * This title is currently used as an integral identifier.
     */
    private HorizontalPanel prepareButtonsRow() {

        for (int i = 0; i < this.buttonsRow.getWidgetCount(); ++i) {
            final Widget wid = this.buttonsRow.getWidget(i);
            if (wid instanceof WlTimedButton) {
                final WlTimedButton timedButton = (WlTimedButton) wid;

                if (timedButton.getTitle().length() != 1)
                    continue;

                final int id = Integer.parseInt(timedButton.getTitle());
                final IWlButtonUsed buttonUsed = new ButtonListener(id, this.boardController,
                        this.getResponseCommandCallback());
                timedButton.addButtonListener(buttonUsed);
                this.addInteractiveWidget(timedButton);
            }
        }

        return this.buttonsRow;
    }

    @Override
    public void end() {

        if (this.readyTimer != null) {
            this.readyTimer.cancel();
            this.readyTimer = null;
        }

        if (this.webcam != null) {
            this.webcam.dispose();
            this.webcam = null;
        }

        if (this.timer != null) {
            this.timer.dispose();
            this.timer = null;
        }

        if (this.clockActivator != null) {
            this.clockActivator.dispose();
            this.clockActivator = null;
        }

        for (int i = 0; i < this.switchesRow.getWidgetCount(); ++i) {
            final Widget wid = this.switchesRow.getWidget(i);
            if (wid instanceof WlSwitch)
                ((WlSwitch) wid).dispose();
        }

        for (int i = 0; i < this.buttonsRow.getWidgetCount(); ++i) {
            final Widget wid = this.buttonsRow.getWidget(i);
            if (wid instanceof WlTimedButton)
                ((WlTimedButton) wid).dispose();
        }

        if (this.progressBar != null)
            this.progressBar.stop();

        this.messages.stop();
    }

    @Override
    public void setTime(int time) {
        this.timer.updateTime(time);
    }

    @Override
    public Widget getWidget() {
        return this.widget;
    }

    protected IResponseCommandCallback getResponseCommandCallback() {
        return new IResponseCommandCallback() {
            @Override
            public void onSuccess(ResponseCommand responseCommand) {
                GWT.log("responseCommand: success", null);
            }

            @Override
            public void onFailure(CommException e) {
                GWT.log("responseCommand: failure", null);
                XilinxExperiment.this.messages.stop();
                XilinxExperiment.this.messages.setText("Error sending command: " + e.getMessage());
            }
        };
    }
}