org.eclipse.ice.client.widgets.EntryComposite.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.ice.client.widgets.EntryComposite.java

Source

/*******************************************************************************
 * Copyright (c) 2012, 2014- UT-Battelle, LLC.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Initial API and implementation and/or initial documentation - Jay Jay Billings,
 *   Jordan H. Deyton, Dasha Gorin, Alexander J. McCaskey, Taylor Patterson,
 *   Claire Saunders, Matthew Wang, Anna Wojtowicz
 *******************************************************************************/
package org.eclipse.ice.client.widgets;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.ice.client.common.internal.ClientHolder;
import org.eclipse.ice.datastructures.ICEObject.IUpdateable;
import org.eclipse.ice.datastructures.ICEObject.IUpdateableListener;
import org.eclipse.ice.datastructures.form.AllowedValueType;
import org.eclipse.ice.datastructures.form.BasicEntryContentProvider;
import org.eclipse.ice.datastructures.form.Entry;
import org.eclipse.ice.datastructures.form.IEntryContentProvider;
import org.eclipse.ice.iclient.IClient;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.jface.window.Window;
import org.eclipse.remote.ui.dialogs.RemoteResourceBrowser;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.RowData;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.forms.IMessageManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>
 * This is an subclass of SWT's Composite class made specifically to work with
 * ICE Entries.
 * </p>
 * <p>
 * Changes to this class are broadcasted using SWT's event system. Marking the
 * FormEditor as dirty, for example, should be handled by registering an event
 * listener with instances of this class and catching the signal.
 * </p>
 * <p>
 * The EntryComposite can post messages about its work with an Entry to a
 * IMessageManager if it is set by calling setMessageManager().
 * </p>
 * 
 * @author Gregory M. Lyon, Anna Wojtowicz, Alex McCaskey
 */
public class EntryComposite extends Composite implements IUpdateableListener {

    /**
     * Logger for handling event messages and other information.
     */
    private static final Logger logger = LoggerFactory.getLogger(EntryComposite.class);

    /**
     * A label that describes the Entry.
     */
    private Label label;

    /**
     * A text field that is used if the Entry type is unspecified.
     */
    private Text text;

    /**
     * A drop-down menu for the Entry.
     */
    private Combo dropDown;

    /**
     * A set of buttons for the Entry.
     */
    protected final List<Button> buttons;

    /**
     * The Entry that is displayed by the EntryComposite.
     */
    protected Entry entry;

    /**
     * The currently set value of the Entry.
     */
    private String currentSelection;

    /**
     * The message manager to which message about the success or failure of
     * manipulating the Entry should be posted.
     */
    private IMessageManager messageManager;

    /**
     * The name of the message posted to the message manager.
     */
    private String messageName;

    /**
     * Entry map of binary/boolean-type allowed values
     */
    private final List<String> allowedBinaryValues = new ArrayList<String>();

    /**
     * List of allowed values for the entry in lowercase text
     */
    private final List<String> lowercaseAllowedValues = new ArrayList<String>();

    /**
     * This listens to the {@code EntryComposite}'s resize events and adjusts
     * the size of the dropdown if necessary. This is currently only used for
     * file entries.
     */
    private ControlListener resizeListener = null;

    /**
     * A ControlDecoration that can be added to the EntryComposite if desired.
     */
    private ControlDecoration decoration = null;

    /**
     * The Constructor
     * 
     * @param parent
     *            The parent Composite.
     * @param style
     *            The style of the EntryComposite.
     * @param refEntry
     *            An Entry that should be used to create the widget, to update
     *            when changed by the user and to be updated from when changed
     *            internally by ICE.
     */
    public EntryComposite(Composite parent, int style, Entry refEntry) {

        // Call the super constructor
        super(parent, style);

        // Set the Entry reference
        if (refEntry != null) {
            entry = refEntry;
        } else {
            throw new RuntimeException("Entry passed to EntryComposite " + "constructor cannot be null!");
        }
        // Create the Buttons array
        buttons = new ArrayList<Button>();

        // Create the MessageName String
        messageName = new String();

        // Setup the allowedBinaryValues for check boxes
        // Setup the list of values that are equivalent to "ready"
        allowedBinaryValues.add("ready");
        allowedBinaryValues.add("yes");
        allowedBinaryValues.add("y");
        allowedBinaryValues.add("true");
        allowedBinaryValues.add("enabled");
        allowedBinaryValues.add("on");
        // Setup the list of values that are equivalent to "not ready"
        allowedBinaryValues.add("not ready");
        allowedBinaryValues.add("no");
        allowedBinaryValues.add("n");
        allowedBinaryValues.add("false");
        allowedBinaryValues.add("disabled");
        allowedBinaryValues.add("off");

        // Register for updates from the Entry
        entry.register(this);

        // Add a listener to the Entry that unregisters this composite as a
        // listener upon disposal.
        addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(DisposeEvent e) {
                entry.unregister(EntryComposite.this);
            }
        });

        // Get a reference to the current Entry value
        currentSelection = entry.getValue();

        // Render the entry
        render();

        return;
    }

    /**
     * Returns the entry stored on this Composite
     * 
     * @return The Entry rendered by this Composite.
     */
    public Entry getEntry() {
        return entry;
    }

    /**
     * Creates a label for the EntryComposite.
     */
    private void createLabel() {

        // Create the Label
        label = new Label(this, SWT.WRAP);
        label.setText(entry.getName() + ":");
        label.setToolTipText(entry.getDescription());
        label.setBackground(getBackground());

        return;
    }

    /**
     * Creates a checkbox for the EntryComposite.
     */
    private void createCheckbox() {

        // Local Declarations
        Button tmpButton = null;

        // Create the check box
        tmpButton = new Button(this, SWT.CHECK);
        tmpButton.setText(entry.getName());
        tmpButton.setToolTipText(entry.getDescription());

        // Set the box to be checked if the current entry value is one of the
        // "positive" answers from the allowed values
        if (allowedBinaryValues.subList(0, 5).contains(entry.getValue().toLowerCase())) {
            tmpButton.setSelection(true);
        } else {
            // Otherwise unchecked
            tmpButton.setSelection(false);
        }
        // Add the listeners
        tmpButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                // Notify any listeners that the selection has changed
                notifyListeners(SWT.Selection, new Event());
                // Get the index of the value
                int index = lowercaseAllowedValues.indexOf(entry.getValue().toLowerCase());
                // Set the correct value
                String value = null;
                value = (index == 0) ? entry.getAllowedValues().get(1) : entry.getAllowedValues().get(0);
                setEntryValue(value);
                logger.info("EntryComposite Message: Updated Entry " + entry.getName() + " with value = "
                        + entry.getValue());

                return;
            }
        });

        // Add the button to the list
        tmpButton.setBackground(getBackground());
        buttons.add(tmpButton);
    }

    /**
     * This operation creates buttons on the Composite.
     */
    private void createButtons() {

        // Local Declarations
        Button tmpButton = null;

        // Create the radio buttons
        for (String i : entry.getAllowedValues()) {
            // Create the button and set its text fields
            tmpButton = new Button(this, SWT.RADIO);
            tmpButton.setText(i);
            tmpButton.setToolTipText(entry.getDescription());
            // Select the default value if it is this one
            if (i.equals(entry.getValue())) {
                tmpButton.setSelection(true);
            }
            // Add the listeners
            tmpButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    // Notify any listeners that the selection has changed
                    notifyListeners(SWT.Selection, new Event());
                    // Set the value of the Entry
                    setEntryValue(((Button) e.getSource()).getText());
                }
            });
            // Fix the color
            tmpButton.setBackground(getBackground());
            // Add the button to the list
            buttons.add(tmpButton);
        }

        return;
    }

    /**
     * This operation creates a drop-down menu on the Composite.
     */
    private void createDropdown() {

        if (dropDown == null || dropDown.isDisposed()) {
            // Create a drop-down menu
            dropDown = new Combo(this, SWT.DROP_DOWN | SWT.SINGLE | SWT.V_SCROLL | SWT.H_SCROLL | SWT.READ_ONLY);
            dropDown.setBackground(getBackground());

            // Determine the current value of the entry.
            String currentValue = entry.getValue();

            // Add the allowed values to the dropdown menu. If the allowed value
            // matches the current value, select it.
            List<String> allowedValues = entry.getAllowedValues();
            for (int i = 0; i < allowedValues.size(); i++) {
                String allowedValue = allowedValues.get(i);
                dropDown.add(allowedValue);
                if (allowedValue.equals(currentValue)) {
                    dropDown.select(i);
                }
            }

            // Add a selection listener
            dropDown.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    // Set the value of the Entry
                    setEntryValue(((Combo) e.widget).getText());
                    // Notify any listeners that the selection has changed
                    notifyListeners(SWT.Selection, new Event());
                }
            });

        } else {
            // If the dropDown hasn't been disposed, check if a new AllowedValue
            // has been added to the Entry
            List<String> allowedValues = entry.getAllowedValues();
            List<String> comboValues = Arrays.asList(dropDown.getItems());

            for (int i = 0; i < allowedValues.size(); i++) {
                String allowedValue = allowedValues.get(i);
                // Add any new AllowedValues to the dropDown
                if (!comboValues.contains(allowedValue)) {
                    dropDown.add(allowedValue);
                }
            }
        }

        return;
    }

    /**
     * This operation creates a textfield on the Composite.
     */
    private void createTextfield() {

        // Create a textfield
        if (!entry.isSecret()) {
            text = new Text(this, SWT.LEFT | SWT.BORDER);
        } else {
            text = new Text(this, SWT.LEFT | SWT.BORDER | SWT.PASSWORD);
        }
        text.setToolTipText(entry.getDescription());
        text.setText(entry.getValue());
        text.setBackground(getBackground());

        // Add the Focus Listeners
        text.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(FocusEvent e) {
                // Notify any listeners that the selection has changed
                notifyListeners(SWT.Selection, new Event());
                // Set the value of the Entry
                setEntryValue(text.getText());
            };
        });

        // Add a listener for the "DefaultSelection" key (return/enter). It
        // needs to be registered with the EntryComposite and the Text widget,
        // in case it is being used by JFace, which doesn't always post standard
        // SWT events.
        Listener enterListener = new Listener() {
            @Override
            public void handleEvent(Event e) {
                // Notify any listeners that the selection has changed
                notifyListeners(SWT.Selection, new Event());
                // Set the value of the Entry
                setEntryValue(text.getText());
            }
        };
        this.addListener(SWT.DefaultSelection, enterListener);
        text.addListener(SWT.DefaultSelection, enterListener);

        return;
    }

    /**
     * This method creates a browse button on the EntryComposite. Clicking the
     * button opens a file browser, and once a file is selected, the file is
     * imported into the default workspace.
     */
    private void createBrowseButton() {

        boolean redraw = buttons.isEmpty();

        if (redraw) {
            // Create a new button, set the text
            Button browseButton = new Button(this, SWT.PUSH);
            browseButton.setText("Browse...");

            // Add an event listener that displays a Directory Dialog prompt
            browseButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    // Get the Client
                    IClient client = ClientHolder.getClient();
                    // Open up a file browser
                    FileDialog fileDialog = new FileDialog(getShell());
                    fileDialog.setText("Select a file to import into ICE");
                    String filePath = fileDialog.open();
                    if (filePath != null) {
                        // Import the files
                        File importedFile = new File(filePath);
                        client.importFile(importedFile.toURI());
                        // Create a new content provider with the new file
                        // in the allowed values list
                        IEntryContentProvider prov = new BasicEntryContentProvider();
                        ArrayList<String> valueList = entry.getAllowedValues();
                        if (!valueList.contains(importedFile.getName())) {
                            valueList.add(importedFile.getName());
                        }
                        prov.setAllowedValueType(AllowedValueType.File);

                        // Finish setting the allowed values and default
                        // value
                        prov.setAllowedValues(valueList);

                        // Set the new provider
                        entry.setContentProvider(prov);

                        // Set the entry's value to the new file
                        setEntryValue(importedFile.getName());
                    }
                    // Notify any listeners of the selection event
                    notifyListeners(SWT.Selection, new Event());

                    return;
                }

            });

            // Add the browse button
            buttons.add(browseButton);
        }

        return;
    }

    /**
     * This method creates a drop down Combo for an Entry 
     * with the Executable AllowedValueType.
     */
    private void createExecutableDropDown() {
        if (dropDown == null || dropDown.isDisposed()) {
            dropDown = new Combo(this,
                    SWT.BORDER | SWT.LEAD | SWT.DROP_DOWN | SWT.SINGLE | SWT.V_SCROLL | SWT.H_SCROLL);
            dropDown.setFocus();
            dropDown.setLayoutData(new GridData(400, SWT.DEFAULT));

            List<String> allowedValues = entry.getAllowedValues();
            // Add a selection listener
            dropDown.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    // Set the value of the Entry
                    setEntryValue(((Combo) e.widget).getText());
                    // Notify any listeners that the selection has changed
                    notifyListeners(SWT.Selection, new Event());
                }
            });

            // Add a traverse listener to validate the entry
            // when the user hits Enter or Tab
            dropDown.addTraverseListener(new TraverseListener() {
                @Override
                public void keyTraversed(TraverseEvent e) {
                    if (e.detail == SWT.TRAVERSE_RETURN || e.detail == SWT.TRAVERSE_TAB_NEXT) {

                        // Get the entered text and create a File
                        String path = dropDown.getText();
                        File file = new File(path);

                        // If this is an actual executable that exists, then
                        // add it to the File Entry.
                        if (file.exists() && file.isFile()) {
                            // Check if its an executable
                            if (file.canExecute()) {
                                IEntryContentProvider prov = new BasicEntryContentProvider();
                                ArrayList<String> valueList = entry.getAllowedValues();
                                valueList.add(file.toURI().toString());
                                prov.setAllowedValueType(AllowedValueType.Executable);

                                // Finish setting the allowed values and default
                                // value
                                prov.setAllowedValues(valueList);
                                entry.setContentProvider(prov);

                                // If it is executable just add its absolute
                                // path
                                setEntryValue(file.toURI().toString());
                            } else {
                                // If its just a File, import it
                                IClient client = ClientHolder.getClient();
                                client.importFile(file.toURI());
                                // Set the entry's value to the new file
                                setEntryValue(file.getName());
                            }

                            notifyListeners(SWT.Selection, new Event());
                        }
                    }
                }
            });

            // Determine the current value of the entry.
            String currentValue = entry.getValue();
            for (int i = 0; i < allowedValues.size(); i++) {
                String allowedValue = allowedValues.get(i);
                dropDown.add(allowedValue);
                if (allowedValue.equals(currentValue)) {
                    dropDown.select(i);
                }
            }
        } else {
            // If the dropDown hasn't been disposed, check if a new AllowedValue
            // has been added to the Entry
            List<String> allowedValues = entry.getAllowedValues();
            List<String> comboValues = Arrays.asList(dropDown.getItems());

            for (int i = 0; i < allowedValues.size(); i++) {
                String allowedValue = allowedValues.get(i);
                // Add any new AllowedValues to the dropDown
                if (!comboValues.contains(allowedValue)) {
                    dropDown.add(allowedValue);
                }
            }
        }
    }

    /**
     * This method creates a Local/Remote file browser button for 
     * an Entry with the Executable AllowedValueType. 
     * 
     */
    private void createExecutableBrowser() {
        boolean redraw = buttons.isEmpty();

        if (redraw) {
            // Create a new button, set the text
            Button browseButton = new Button(this, SWT.PUSH);
            browseButton.setText("Browse...");

            // RemoteFileWidget widg = new RemoteFileWidget(this, SWT.NONE,
            // SWT.NONE, "TITLE", "/");
            // Add an event listener that displays a Directory Dialog prompt
            browseButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {

                    // Create a variable for the Executable Value
                    String entryValue = null;

                    // Create a MessageDialog to get whether the user 
                    // wants to use a remote or local executable. 
                    MessageDialog dialog = new MessageDialog(getShell(), "Local or Remote Application", null,
                            "Please specify whether your executable is local or remote.",
                            MessageDialog.QUESTION_WITH_CANCEL, new String[] { "Local", "Remote" }, 0);

                    // Launch the dialot and get the result
                    int result = dialog.open();

                    // If the user selected Remote
                    if (result == 1) {

                        // Create a new Remote browser and set its type to File_Browser
                        RemoteResourceBrowser browser = new RemoteResourceBrowser(getShell(), SWT.NONE);
                        browser.setTitle("Select a remote executable.");
                        browser.setType(RemoteResourceBrowser.FILE_BROWSER);

                        // Open and make sure they didn't select Cancel
                        if (browser.open() != Window.OK) {
                            return;
                        }

                        // Get the selected Resource
                        IFileStore fs = browser.getResource();

                        // Set the value as the remote URI string
                        entryValue = fs.toURI().toString();

                    } else {
                        // If Local, just open up a file browser
                        FileDialog fileDialog = new FileDialog(getShell());
                        fileDialog.setText("Select an executable to import into ICE");
                        String filePath = fileDialog.open();
                        if (filePath != null) {
                            // Import the files
                            File importedFile = new File(filePath);
                            entryValue = importedFile.toURI().toString();
                        }
                    }

                    // If we got a valid entryValue, then let's set it. 
                    if (entryValue != null && !entryValue.isEmpty()) {
                        // Create a new content provider with the new file
                        // in the allowed values list
                        IEntryContentProvider prov = new BasicEntryContentProvider();
                        ArrayList<String> valueList = entry.getAllowedValues();
                        if (!valueList.contains(entryValue)) {
                            valueList.add(entryValue);
                        }
                        prov.setAllowedValueType(AllowedValueType.Executable);

                        // Finish setting the allowed values and default
                        // value
                        prov.setAllowedValues(valueList);

                        // Set the new provider
                        entry.setContentProvider(prov);

                        // If it is executable just add its absolute path
                        setEntryValue(entryValue);

                    }

                    // Notify any listeners of the selection event
                    notifyListeners(SWT.Selection, new Event());

                    return;
                }

            });

            // Add the browse button
            buttons.add(browseButton);
        }
    }

    /**
     * This operation renders the SWT widgets for the Entry.
     */
    protected void render() {

        // Local Declarations
        int numAllowedValues = 0, maxValueLength = 12, maxShortValues = 8;
        boolean shortValues = true;
        AllowedValueType valueType = null;

        // Make all of the allowed values lowercase
        for (String value : entry.getAllowedValues()) {
            lowercaseAllowedValues.add(value.toLowerCase());
        }

        // Set the allowed value type
        valueType = entry.getValueType();
        // Get the number of allowed values
        numAllowedValues = entry.getAllowedValues().size();
        // Figure out if the allowed values are small in length. This
        // will let us draw it in a more intuitive way, but it only
        // takes one value that is not short to ruin the view.
        for (String i : entry.getAllowedValues()) {
            if (i.length() > maxValueLength) {
                shortValues = false;
                break;
            }
        }

        // Set the default layout to a vertical FillLayout.
        FillLayout fillLayout = new FillLayout(SWT.VERTICAL);
        fillLayout.marginHeight = 5;
        fillLayout.marginWidth = 3;
        fillLayout.spacing = 5;
        Layout layout = fillLayout;

        // If the valueType is Discrete and there are some allowed values,
        // figure out how to draw it
        if (valueType == AllowedValueType.Discrete && numAllowedValues > 0) {
            // We can use Radio buttons if the allowed values are few
            if (numAllowedValues <= maxShortValues && shortValues) {
                // Check to see if this is something that should use a check box
                if (numAllowedValues == 2 && allowedBinaryValues.containsAll(lowercaseAllowedValues)) {
                    createCheckbox();
                } else {
                    // Otherwise create the regular button set
                    createLabel();
                    createButtons();
                }
            } else {
                // Otherwise we can use a drop-down menu
                createLabel();
                createDropdown();
            }
        } else if (valueType == AllowedValueType.Discrete) {
            // If no values were found, throw an error
            throwMissingValuesError();
        } else if (valueType == AllowedValueType.File) {
            // If this is a File entry, draw dropdown (if applicable)
            // and browse button
            createLabel();
            if (numAllowedValues > 0) {
                createDropdown();
            }
            createBrowseButton();
            layout = setupDropDownLayout(numAllowedValues);

        } else if (valueType == AllowedValueType.Executable) {
            createLabel();
            createExecutableDropDown();
            createExecutableBrowser();
            layout = setupDropDownLayout(numAllowedValues);
        } else {
            // Otherwise create a text field
            createLabel();
            createTextfield();
        }

        // Apply the layout.
        setLayout(layout);

        return;
    }

    /**
     * 
     * @param layout
     * @param numAllowedValues
     */
    private Layout setupDropDownLayout(int numAllowedValues) {
        // Use a RowLayout so we can wrap widgets.
        final RowLayout rowLayout = new RowLayout();
        rowLayout.wrap = true;
        rowLayout.fill = false;
        rowLayout.center = true;
        // Layout layout = rowLayout;

        // If the file list Combo is rendered, we need to give it RowData so
        // it will grab excess horizontal space. Otherwise, the default
        // RowLayout above will suffice.
        if (numAllowedValues > 0) {
            // Use a RowData for the dropdown Combo so it can get excess
            // space.
            final RowData rowData = new RowData();
            dropDown.setLayoutData(rowData);
            // Set a minimum width of 50 for the dropdown.
            final int minWidth = 50;

            // Compute the space taken up by the label and browse button.
            final int unwrappedWidth;
            Button button = buttons.get(0);
            int labelWidth = label.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
            int buttonWidth = button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
            int padding = 2 * rowLayout.spacing + rowLayout.marginLeft + rowLayout.marginWidth * 2
                    + rowLayout.marginRight + 30;
            unwrappedWidth = labelWidth + buttonWidth + padding;

            // Size the dropdown based on the currently available space.
            int availableWidth = getClientArea().width - unwrappedWidth;
            rowData.width = (availableWidth > minWidth ? availableWidth : minWidth);

            // If necessary, remove the old resize listener.
            if (resizeListener != null) {
                removeControlListener(resizeListener);
            }

            // Add a resize listener to the EntryComposite to update the
            // size of the dropdown.
            resizeListener = new ControlAdapter() {
                @Override
                public void controlResized(ControlEvent e) {
                    int availableWidth = getClientArea().width - unwrappedWidth;
                    rowData.width = (availableWidth > minWidth ? availableWidth : minWidth);
                    layout();
                }
            };
            addControlListener(resizeListener);
        }

        return rowLayout;
    }

    /**
     * This operation will post a message to the message manager (if one exists)
     * if the Entry has no value, but requires a value from a discrete set.
     */
    private void throwMissingValuesError() {

        if (messageManager != null) {
            // Get the message
            String errorMessage = entry.getErrorMessage();
            // Post it if it exists
            if (errorMessage != null) {
                // Display the error at the top of the screen
                if (messageManager != null) {
                    messageManager.addMessage(messageName, errorMessage, null, IMessageProvider.ERROR);
                }
                // Highlight the text if it is in a text box
                if (text != null) {
                    Color color = new Color(Display.getCurrent(), 200, 0, 0);
                    text.setForeground(color);
                    FontData fontData = new FontData();
                    fontData.setStyle(SWT.BOLD);
                    Font font = new Font(getDisplay(), fontData);
                    text.setFont(font);
                }
            }
        }

        return;
    }

    /**
     * This operation directs the EntryComposite to refresh its view of the
     * Entry. This should be called in the event that the Entry has changed on
     * the file system and the view needs to be updated.
     */
    public void refresh() {

        // Dispose of the old widgets
        if (dropDown != null) {
            dropDown.dispose();
            dropDown = null;
        }
        if (label != null) {
            label.dispose();
            label = null;
        }
        if (text != null) {
            text.dispose();
            text = null;
        }
        for (Button button : buttons) {
            if (!button.isDisposed()) {
                button.dispose();
            }
        }
        // Remove all of the previous buttons.
        buttons.clear();

        // Print an error if this Entry has been prematurely disposed.
        if (isDisposed()) {
            logger.info("EntryComposite Message: " + "This composite has been prematurely disposed!");
            return;
        }

        // Remove the resize listener.
        if (resizeListener != null) {
            removeControlListener(resizeListener);
            resizeListener = null;
        }

        // Re-render the Composite
        render();

        // Re-draw the Composite
        layout();

        return;
    }

    /**
     * This operation sets the Message Manager that should be used by the
     * EntryComposite to post messages about the Entry. If the Message Manager
     * is not set, the EntryComposite will not attempt to post messages.
     * 
     * @param manager
     *            The Message Manager that the EntryComposite should use.
     */
    public void setMessageManager(IMessageManager manager) {

        // Set the messageManager
        messageManager = manager;

        return;
    }

    /**
     * This operation sets the value of the Entry and, if possible and
     * necessary, reports to the message manager.
     * 
     * @param value
     */
    protected void setEntryValue(String value) {

        // Set the value and post a message if necessary
        if (!entry.setValue(value) && messageManager != null) {
            // Get the message
            String errorMessage = entry.getErrorMessage();
            // Post it if it exists
            if (errorMessage != null) {
                // Display the error at the top of the screen
                if (messageManager != null) {
                    messageManager.addMessage(messageName, errorMessage, null, IMessageProvider.ERROR);
                }
                // Highlight the text if it is in a text box
                if (text != null) {
                    Color color = new Color(Display.getCurrent(), 200, 0, 0);
                    text.setForeground(color);
                    FontData fontData = new FontData();
                    fontData.setStyle(SWT.BOLD);
                    Font font = new Font(getDisplay(), fontData);
                    text.setFont(font);
                }
            }

        } else if (value == null) {

            if (entry.getValueType().equals(AllowedValueType.Discrete)) {
                // Set the Entry to the first AllowedValue if it's Discrete
                if (!entry.getAllowedValues().isEmpty()) {
                    String allowedValue = entry.getAllowedValues().get(0);
                    entry.setValue(allowedValue);
                }
            } else {
                // Otherwise, set the default value
                entry.setValue(entry.getDefaultValue());
            }

        } else {
            // Remove a posted message if necessary
            if (messageManager != null) {
                messageManager.removeMessage(messageName);
            }
            // Remove the text box highlight if it is in a text box
            if (text != null) {
                Color color = new Color(Display.getCurrent(), 0, 0, 0);
                text.setForeground(color);
                FontData fontData = new FontData();
                fontData.setStyle(SWT.NORMAL);
                fontData.setHeight(10);
                Font font = new Font(getDisplay(), fontData);
                text.setFont(font);
            }
        }

        return;
    }

    /**
     * Listen for updates from the Entry and redraw if needed.
     */
    @Override
    public void update(IUpdateable component) {
        // When the Entry has updated, refresh on the Eclipse UI thread.
        if (component == entry) {
            PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
                @Override
                public void run() {
                    if (!EntryComposite.this.isDisposed()) {

                        // Refresh the EntryComposite
                        refresh();

                        // Toggle the "unsaved changes" decoration if the entry
                        // value is different
                        if (!EntryComposite.this.entry.getValue().equals(currentSelection)) {
                            toggleSaveDecoration();
                        }

                        // Update the reference to the Entry's value
                        currentSelection = EntryComposite.this.entry.getValue();

                    } else {
                        entry.unregister(EntryComposite.this);
                    }
                }
            });
        }
        return;
    }

    /**
     * This method is responsible for toggling a ControlDecoration on and off on
     * the EntryComposite. The decoration will toggle on if the editor is dirty
     * and the selection was recently changed (monitored by
     * {@link EntryComposite#currentSelection}). Otherwise, it will toggle off.
     */
    public void toggleSaveDecoration() {

        if (decoration == null) {
            // Create a new decoration and message
            decoration = new ControlDecoration(this, SWT.TOP | SWT.LEFT);
            final String saveMessage = "The form contains unsaved changes";

            // Set a description and image
            decoration.setDescriptionText(saveMessage);
            Image image = FieldDecorationRegistry.getDefault()
                    .getFieldDecoration(FieldDecorationRegistry.DEC_WARNING).getImage();
            decoration.setImage(image);

            // Set a listener to hide/show the decoration according to the
            // editor's state and the current entry value
            final IEditorPart editor = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
                    .getActiveEditor();
            editor.addPropertyListener(new IPropertyListener() {
                @Override
                public void propertyChanged(Object source, int propId) {
                    // Toggle the decoration on if the form is dirty and the
                    // value has changed
                    if (editor != null) {
                        if (editor.isDirty() && !EntryComposite.this.entry.getValue().equals(currentSelection)) {
                            // Show the decoration
                            EntryComposite.this.decoration.show();
                        } else if (!editor.isDirty()) {
                            // Hide the decoration
                            EntryComposite.this.decoration.hide();
                        }
                    }

                    return;
                }
            });
        }

        return;
    }
}