edu.ku.brc.specify.tasks.subpane.wb.FormPane.java Source code

Java tutorial

Introduction

Here is the source code for edu.ku.brc.specify.tasks.subpane.wb.FormPane.java

Source

/* Copyright (C) 2015, University of Kansas Center for Research
 * 
 * Specify Software Project, specify@ku.edu, Biodiversity Institute,
 * 1345 Jayhawk Boulevard, Lawrence, Kansas, 66045, USA
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
package edu.ku.brc.specify.tasks.subpane.wb;

import static edu.ku.brc.ui.UIHelper.createIconBtn;
import static edu.ku.brc.ui.UIRegistry.getResourceString;

import java.awt.AWTKeyStroke;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.datatransfer.DataFlavor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;

import javax.swing.ComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.text.JTextComponent;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import edu.ku.brc.af.core.UsageTracker;
import edu.ku.brc.af.ui.forms.validation.ValCheckBox;
import edu.ku.brc.af.ui.forms.validation.ValTextArea;
import edu.ku.brc.af.ui.forms.validation.ValTextField;
import edu.ku.brc.specify.datamodel.Workbench;
import edu.ku.brc.specify.datamodel.WorkbenchDataItem;
import edu.ku.brc.specify.datamodel.WorkbenchRow;
import edu.ku.brc.specify.datamodel.WorkbenchTemplateMappingItem;
import edu.ku.brc.specify.tasks.SGRTask;
import edu.ku.brc.specify.tasks.WorkbenchTask;
import edu.ku.brc.specify.ui.LengthInputVerifier;
import edu.ku.brc.ui.DocumentAdaptor;
import edu.ku.brc.ui.GetSetValueIFace;
import edu.ku.brc.ui.IconManager;
import edu.ku.brc.ui.UIHelper;
import edu.ku.brc.ui.UIRegistry;
import edu.ku.brc.ui.dnd.GhostActionable;
import edu.ku.brc.ui.dnd.GhostGlassPane;
import edu.ku.brc.ui.dnd.GhostMouseInputAdapter;

/**
 * This Panel holds the form elements for Template definition of a Workbench. NOTE: This assumes that the IUNputPanel is a container and the label
 * and UI input control are direct children. This could makes calls to getParent that assume it is only nested one deep.
 * 
 * @author rod
 *
 * @code_status Beta
 *
 * Mar 8, 2007
 *
 */
@SuppressWarnings("serial")
public class FormPane extends JPanel implements FormPaneWrapper {
    private static final Logger log = Logger.getLogger(FormPane.class);

    protected static final short TEXTFIELD_DATA_LEN_MAX = 64;
    protected static final short MAX_COLS = 64;
    protected static final short DEFAULT_TEXTFIELD_COLS = 15;
    protected static final short DEFAULT_TEXTAREA_COLS = 45;
    protected static final short DEFAULT_TEXTFIELD_ROWS = 1;
    protected static final short DEFAULT_TEXTAREA_ROWS = 5;

    protected WorkbenchPaneSS workbenchPane;
    protected Workbench workbench;
    protected boolean hasChanged = false;
    protected Vector<InputPanel> uiComps = new Vector<InputPanel>();
    protected boolean isInImageMode = false;
    protected JButton controlPropsBtn = null;
    protected Component firstComp = null;

    protected boolean ignoreChanges = false;
    protected boolean changesInForm = false;
    protected int currentIndex = -1;
    protected DocumentAdaptor docListener;
    protected ChangeListener changeListener = null;

    protected InputPanel selectedInputPanel = null;

    protected Vector<WorkbenchTemplateMappingItem> headers = new Vector<WorkbenchTemplateMappingItem>();

    protected List<DataFlavor> dropFlavors = new ArrayList<DataFlavor>();

    protected EditFormControlDlg controlPropsDlg = null;
    protected boolean wasShowing = false;
    protected JScrollPane scrollPane;

    // This is necessary because CardLayout doesn't set visibility
    protected Dimension initialSize = null;

    protected boolean readOnly;

    /**
     * Creates a Pane for editing a Workbench as a form.
     * @param workbenchPane the workbench pane to be parented into
     * @param workbench the workbench
     */
    public FormPane(final WorkbenchPaneSS workbenchPane, final Workbench workbench, final boolean readOnly) {
        setLayout(null);

        this.workbenchPane = workbenchPane;
        this.workbench = workbench;
        this.readOnly = readOnly;
        dropFlavors.add(InputPanel.INPUTPANEL_FLAVOR);

        docListener = new DocumentAdaptor() {
            @Override
            protected void changed(DocumentEvent e) {
                stateChange();
            }

        };

        changeListener = new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                stateChange();
            }
        };

        buildUI();

        scrollPane = new JScrollPane(this, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);

    }

    /**
     * Creates all the UI form the Form. 
     */
    protected void buildUI() {
        headers.addAll(workbench.getWorkbenchTemplate().getWorkbenchTemplateMappingItems());
        Collections.sort(headers);

        MouseAdapter clickable = new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                selectControl(e.getSource());
            }

            @Override
            public void mouseClicked(MouseEvent e) {
                selectedInputPanel = getInputPanel(e.getSource());
                controlPropsBtn.setEnabled(true);

                if (controlPropsDlg != null) {
                    controlPropsDlg.setControl(selectedInputPanel);
                }

                if (e.getClickCount() == 2 && (controlPropsDlg == null || !controlPropsDlg.isVisible())) {
                    UsageTracker.incrUsageCount("WB.FormPropsTool");
                    showControlProps();
                }

            }
        };

        Point topLeftPnt = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);

        final int LAYOUT_SPACING = 4;
        short maxY = 0;
        Vector<InputPanel> delayedLayout = new Vector<InputPanel>();
        int maxWidthOffset = 0;
        for (WorkbenchTemplateMappingItem wbtmi : headers) {
            // Create the InputPanel and make it draggable
            InputPanel panel = new InputPanel(wbtmi, wbtmi.getCaption(), createUIComp(wbtmi), this, clickable);

            Dimension size = panel.getPreferredSize();
            panel.setSize(size);
            panel.setPreferredSize(size);

            // Finds the largest label
            maxWidthOffset = Math.max(panel.getTextFieldOffset(), maxWidthOffset);

            int x = wbtmi.getXCoord();
            int y = wbtmi.getYCoord();
            if (y < topLeftPnt.y || (y == topLeftPnt.y && x < topLeftPnt.x)) {
                firstComp = panel.getComp();
                topLeftPnt.setLocation(x, y);
            }

            // Add it to the Form (drag canvas)
            uiComps.add(panel);
            add(panel);

            // NOTE: that the constructor sets the x,y storage from WorkbenchTemplateMappingItem object
            // so the ones with XCoord and YCoord set do not need to be positioned
            if (wbtmi.getXCoord() == null || wbtmi.getYCoord() == null || wbtmi.getXCoord() == -1
                    || wbtmi.getYCoord() == -1) {
                delayedLayout.add(panel); // remember this for later once we know the Max Y

            } else {
                maxY = (short) Math.max(wbtmi.getYCoord() + size.height, maxY);
            }
        }

        // Now align the control by their text fields and skips the ones that have actual positions defined.
        // NOTE: We set the X,Y into the Mapping so that each item knows where it is, then if the user
        // drags and drop anything or save the template everyone knows where they are suppose to be
        // 
        int inx = 0;
        int maxX = 0;
        int y = maxY + LAYOUT_SPACING;
        for (InputPanel panel : uiComps) {
            WorkbenchTemplateMappingItem wbtmi = headers.get(inx);
            if (delayedLayout.contains(panel)) {
                int x = maxWidthOffset - panel.getTextFieldOffset();
                panel.setLocation(x, y);
                wbtmi.setXCoord((short) x);
                wbtmi.setYCoord((short) y);

                Dimension size = panel.getPreferredSize();
                y += size.height + LAYOUT_SPACING;

            }
            Rectangle r = panel.getBounds();
            maxX = Math.max(maxX, r.x + r.width);
            inx++;
        }

        initialSize = new Dimension(maxX, y);

        controlPropsBtn = createIconBtn("ControlEdit", IconManager.IconSize.NonStd, "WB_EDIT_CONTROL", false,
                new ActionListener() {
                    public void actionPerformed(ActionEvent ae) {
                        UsageTracker.getUsageCount("WBFormPropsTool");
                        showControlProps();
                    }
                });

        addArrowTraversalKeys();
    }

    /**
     * Adds Up/Down Focus Traversal Keys to the form.
     */
    protected void addArrowTraversalKeys() {
        Set<AWTKeyStroke> set = getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS);
        KeyStroke forward = KeyStroke.getKeyStroke("DOWN");
        set = new HashSet<AWTKeyStroke>(set);
        set.add(forward);
        setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, set);

        set = getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS);
        KeyStroke backward = KeyStroke.getKeyStroke("UP");
        set = new HashSet<AWTKeyStroke>(set);
        set.add(backward);
        setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, set);
    }

    /**
     * For a given Object (usually from a MouseEvent) it determines if a selection of a control or label happened.
     * It also requests focus for the COntrol of the label.
     * @param obj the UI Object
     * @return the parent InputPanel
     */
    protected InputPanel getInputPanel(final Object obj) {
        Component comp = ((Component) obj).getParent();
        while (comp != null && !(comp instanceof InputPanel)) {
            comp = comp.getParent();
        }

        InputPanel inputPanel = comp instanceof InputPanel ? (InputPanel) comp : null;
        if (inputPanel != null) {
            if (obj instanceof JLabel) {
                inputPanel.getComp().requestFocus();
            }
        }
        return inputPanel;
    }

    /**
     * Determinaes if a child of the InputPanel was clicked on and selects the InputPanel and enables the Edit Control button.
     * @param uiObj the UI Object usually from an event
     */
    protected void selectControl(final Object uiObj) {
        //System.out.println("!!!!!!!!!FOCUS GAINED!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
        selectedInputPanel = getInputPanel(uiObj); // NOTE: This also requests the focus if it finds one
        controlPropsBtn.setEnabled(true);

        if (controlPropsDlg != null) {
            controlPropsDlg.setControl(selectedInputPanel);
        }
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.wb.FormPaneWrapper#setWorkbench(edu.ku.brc.specify.datamodel.Workbench)
     */
    @Override
    public void setWorkbench(final Workbench workbench) {
        this.workbench = workbench;

        // Make the new Header List
        headers.clear();
        headers.addAll(workbench.getWorkbenchTemplate().getWorkbenchTemplateMappingItems());
        Collections.sort(headers);

        // Now set all the new items into the panels 
        // by checking the Id
        Vector<InputPanel> oldItems = new Vector<InputPanel>(uiComps);
        for (WorkbenchTemplateMappingItem newItem : headers) {
            InputPanel fndItem = null;
            for (InputPanel panel : oldItems) {
                if (newItem.getId().intValue() == panel.getWbtmi().getId().intValue()) {
                    fndItem = panel;
                    break;
                }
            }

            if (fndItem != null) {
                oldItems.remove(fndItem);
                fndItem.setWbtmi(newItem);

            } else {
                log.error("Couldn't find panel by ID [" + newItem.getId() + "]");
            }
        }
    }

    /* (non-Javadoc)
     * @see javax.swing.JComponent#getPreferredSize()
     */
    @Override
    public Dimension getPreferredSize() {
        return initialSize;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.wb.FormPaneWrapper#getScrollPane()
     */
    @Override
    public JScrollPane getPane() {
        return scrollPane;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.wb.FormPaneWrapper#getWorkbenchPane()
     */
    @Override
    public WorkbenchPaneSS getWorkbenchPane() {
        return workbenchPane;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.wb.FormPaneWrapper#getControlPropsBtn()
     */
    @Override
    public JButton getControlPropsBtn() {
        return controlPropsBtn;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.wb.FormPaneWrapper#cleanup()
     */
    @Override
    public void cleanup() {
        removeAll();

        scrollPane.removeAll();

        for (InputPanel panel : uiComps) {
            Component comp = panel.getComp();
            if (comp instanceof ValTextField) {
                ((ValTextField) comp).getDocument().removeDocumentListener(docListener);
            } else if (comp instanceof ValTextArea) {
                ((ValTextArea) comp).getDocument().removeDocumentListener(docListener);

            } else if (comp instanceof ValCheckBox) {
                ((ValCheckBox) comp).removeChangeListener(changeListener);
            }
            panel.cleanUp();
        }
        uiComps.clear();
        headers.clear();
        dropFlavors.clear();

        workbenchPane = null;
        workbench = null;
        controlPropsBtn = null;
        firstComp = null;
        selectedInputPanel = null;
        controlPropsDlg = null;
        scrollPane = null;
        initialSize = null;
        uiComps = null;
        headers = null;
        dropFlavors = null;
        docListener = null;
        changeListener = null;
    }

    /**
     * Creates a JTextArea in a ScrollPane.
     * @return the scollpane
     */
    protected JScrollPane createTextArea(final short len, final short rows) {
        ValTextArea textArea = new ValTextArea("", rows, len);
        textArea.setLineWrap(true);
        textArea.setWrapStyleWord(true);
        textArea.getDocument().addDocumentListener(docListener);
        JScrollPane taScrollPane = new JScrollPane(textArea);
        taScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        taScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        return taScrollPane;
    }

    /**
     * Returns a UI component for editing.
     * @param wbtmi the mapping item
     * @return a UI component for editing
     */
    protected JComponent createUIComp(final WorkbenchTemplateMappingItem wbtmi) {
        return createUIComp(WorkbenchTask.getDataType(wbtmi), wbtmi.getCaption(), wbtmi.getFieldName(),
                wbtmi.getFieldType(), wbtmi.getDataFieldLength(), getColumns(wbtmi), getRows(wbtmi), wbtmi);
    }

    /**
     * Determines whether it is a TextField or whether it SHOULD be a TextField
     * @param fieldName the name of the field
     * @param fieldType the type of field
     * @param fieldLen the length of the data 
      * @return true to use TextField, false to use TextArea
     */
    protected static boolean useTextField(final String fieldName, final Short fieldType, final Short fieldLen) {
        // Check to see if the length of the data field is past threshold TEXTFIELD_DATA_LEN_MAX
        // if so, then only create a TextArea if the field name contains the word "remarks"
        // the user can switch other field fro TextField to TextArea if the wish.
        if (fieldType != null) {
            return fieldType == WorkbenchTemplateMappingItem.TEXTFIELD
                    || fieldType == WorkbenchTemplateMappingItem.TEXTFIELD_DATE
                    || fieldType == WorkbenchTemplateMappingItem.CHECKBOX; //check-boxes aren't actually in table view and allowance of 0/1/Yes/No/True/False 
            //makes using checkboxes in form view tricky (bug  #8796)

        }

        if (fieldLen > TEXTFIELD_DATA_LEN_MAX) {
            return fieldName.toLowerCase().indexOf("remarks") == -1;
        }

        return true;
    }

    protected static boolean useTextField(final WorkbenchTemplateMappingItem wbtmi) {
        return useTextField(wbtmi.getFieldName(), wbtmi.getFieldType(), wbtmi.getDataFieldLength());
    }

    /**
     * Returns the number of columns it should use for the TextComponent.
     * @param wbtmi the mapping item
     * @return the number of columns for the UI component
     */
    public static short getColumns(final WorkbenchTemplateMappingItem wbtmi) {
        // Check to see if the length of the data field is past threshold TEXTFIELD_DATA_LEN_MAX
        // if so, then only create a TextArea if the field name contains the word "remarks"
        // the user can switch other field fro TextField to TextArea if the wish.
        short columns = useTextField(wbtmi) ? DEFAULT_TEXTFIELD_COLS : DEFAULT_TEXTAREA_COLS;

        String metaData = wbtmi.getMetaData();
        if (StringUtils.isNotEmpty(metaData)) {
            Properties props = UIHelper.parseProperties(metaData);
            if (props != null) {
                columns = (short) UIHelper.getProperty(props, "columns",
                        useTextField(wbtmi) ? DEFAULT_TEXTFIELD_COLS : DEFAULT_TEXTAREA_COLS);
            }
        }
        return columns;
    }

    /**
     * Returns the number of rows for the tmeplate column
     * @param wbtmi the col def
     * @return the number rows
     */
    public static short getRows(final WorkbenchTemplateMappingItem wbtmi) {
        // Check to see if the length of the data field is past threshold TEXTFIELD_DATA_LEN_MAX
        // if so, then only create a TextArea if the field name contains the word "remarks"
        // the user can switch other field fro TextField to TextArea if the wish.
        short rows = useTextField(wbtmi) ? DEFAULT_TEXTFIELD_ROWS : DEFAULT_TEXTAREA_ROWS;

        String metaData = wbtmi.getMetaData();
        if (StringUtils.isNotEmpty(metaData)) {
            Properties props = UIHelper.parseProperties(metaData);
            if (props != null) {
                rows = (short) UIHelper.getProperty(props, "rows",
                        useTextField(wbtmi) ? DEFAULT_TEXTFIELD_ROWS : DEFAULT_TEXTAREA_ROWS);
            }
        }
        return rows;
    }

    /**
     * Creates the proper UI component for the Mapping item.
     * @param dbFieldType the field type
     * @param caption the caption
     * @param fieldName the name of the field
     * @param dataFieldLength the length of the definition for that field
     * @param fieldType the field type
     * @return a UI component for editing
     */
    protected JComponent createUIComp(final Class<?> dbFieldTypeArg, final String caption, final String fieldName,
            final Short fieldType, final Short fieldLength, final short columns, final short rows,
            final WorkbenchTemplateMappingItem wbtmi) {
        short uiType = WorkbenchTemplateMappingItem.UNKNOWN;
        //System.out.println(wbtmi.getCaption()+" "+wbtmi.getDataType()+" "+wbtmi.getFieldLength());
        Class<?> dbFieldType = dbFieldTypeArg;
        if (dbFieldType == null) {
            // if we can't find a class for the field (i.e. Genus Species, or other 'fake' fields), we say it's a string
            dbFieldType = String.class;
        }

        JComponent comp;
        Component focusComp;

        // handle dates
        if (dbFieldType.equals(Calendar.class) || dbFieldType.equals(Date.class)) {
            //ValFormattedTextField txt = new ValFormattedTextField("Date"); 
            //txt.setColumns(columns == -1 ? DEFAULT_TEXTFIELD_COLS : columns);
            ValTextField txt = new ValTextField(columns);
            txt.getDocument().addDocumentListener(docListener);
            comp = txt;
            focusComp = comp;
            uiType = WorkbenchTemplateMappingItem.TEXTFIELD_DATE;
        }
        //        else if (dbFieldType.equals(Boolean.class)) // strings
        //        {
        //            ValCheckBox checkBox = new ValCheckBox(caption, false, false);
        //            checkBox.addChangeListener(changeListener);
        //            comp      = checkBox;
        //            focusComp = comp;
        //            uiType = WorkbenchTemplateMappingItem.CHECKBOX;
        //        }
        else if (useComboBox(wbtmi)) {
            //ValComboBox comboBox = new ValComboBox(getValues(wbtmi), true);

            final JComboBox comboBox = new JComboBox(getValues(wbtmi));
            comboBox.setName("Form Combo");
            comboBox.setEditable(true);
            comboBox.addActionListener(new ActionListener() {

                /* (non-Javadoc)
                 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
                 */
                @Override
                public void actionPerformed(ActionEvent arg0) {
                    if (arg0.getSource() == comboBox) {
                        //System.out.println("ComboBox Action!" + ((JComboBox )arg0.getSource()).getName());
                        stateChange();
                    }
                }

            });
            //           comboBox.getEditor().getEditorComponent().addFocusListener(new FocusListener() {
            //
            //            /* (non-Javadoc)
            //             * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent)
            //             */
            //            @Override
            //            public void focusGained(FocusEvent arg0) {
            //               System.out.println("FOCUS GAINED");
            //               
            //            }
            //
            //            /* (non-Javadoc)
            //             * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent)
            //             */
            //            @Override
            //            public void focusLost(FocusEvent arg0) {
            //               System.out.println("FOCUS LOST");
            //               
            //            }
            //              
            //           });
            comp = comboBox;
            focusComp = comp;
            uiType = WorkbenchTemplateMappingItem.COMBOBOX;
        } else if (useTextField(fieldName, fieldType, fieldLength)) {
            ValTextField txt = new ValTextField(columns);
            txt.getDocument().addDocumentListener(docListener);
            txt.setInputVerifier(new LengthInputVerifier(caption, fieldLength));
            comp = txt;
            focusComp = comp;
            uiType = WorkbenchTemplateMappingItem.TEXTFIELD;

        } else {
            JScrollPane taScrollPane = createTextArea(columns, rows);
            ((JTextArea) taScrollPane.getViewport().getView())
                    .setInputVerifier(new LengthInputVerifier(caption, fieldLength));
            comp = taScrollPane;
            focusComp = taScrollPane.getViewport().getView();
            uiType = WorkbenchTemplateMappingItem.TEXTAREA;
        }

        wbtmi.setFieldType(uiType);

        focusComp.addFocusListener(new FocusListener() {
            public void focusGained(FocusEvent e) {
                selectControl(e.getSource());
            }

            public void focusLost(FocusEvent e) {
                //stateChange();
            }
        });

        comp.setEnabled(!readOnly);
        focusComp.setEnabled(!readOnly);

        comp.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (!readOnly) {
                    if ((e.isControlDown() || e.isMetaDown()) && e.getKeyCode() == KeyEvent.VK_N) {
                        workbenchPane.addRowAfter();
                    }
                }
                super.keyTyped(e);
            }

        });
        return comp;
    }

    protected Vector<Object> getValues(final WorkbenchTemplateMappingItem wbtmi) {
        Vector<WorkbenchTemplateMappingItem> wbtmis = new Vector<WorkbenchTemplateMappingItem>();
        wbtmis.addAll(workbench.getWorkbenchTemplate().getWorkbenchTemplateMappingItems());
        Collections.sort(wbtmis);
        int wbCol = -1;
        for (int c = 0; c < wbtmis.size(); c++) {
            if (wbtmis.get(c) == wbtmi) {
                wbCol = c;
                break;
            }
        }
        if (wbCol != -1) {
            if (workbenchPane.getSpreadSheet().getColumnModel().getColumn(wbCol)
                    .getCellEditor() instanceof WorkbenchPaneSS.GridCellListEditor) {
                ComboBoxModel model = ((WorkbenchPaneSS.GridCellListEditor) workbenchPane.getSpreadSheet()
                        .getColumnModel().getColumn(wbCol).getCellEditor()).getList();
                Vector<Object> result = new Vector<Object>();
                for (int i = 0; i < model.getSize(); i++) {
                    result.add(model.getElementAt(i));
                }
                return result;
            }
        }
        return null;
    }

    protected boolean useComboBox(final WorkbenchTemplateMappingItem wbtmi) {
        return getValues(wbtmi) != null;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.wb.FormPaneWrapper#swapTextFieldType(edu.ku.brc.specify.tasks.subpane.wb.InputPanel, short)
     */
    @Override
    public void swapTextFieldType(final InputPanel inputPanel, final short fieldLen) {
        JTextComponent oldComp;
        short fieldType;
        short rows;
        if (inputPanel.getComp() instanceof JTextField) {
            JTextField tf = (JTextField) inputPanel.getComp();
            tf.getDocument().removeDocumentListener(docListener);
            fieldType = WorkbenchTemplateMappingItem.TEXTAREA;
            oldComp = tf;
            rows = DEFAULT_TEXTAREA_ROWS;
        } else {
            JTextArea ta = (JTextArea) inputPanel.getComp();
            ta.getDocument().removeDocumentListener(docListener);
            fieldType = WorkbenchTemplateMappingItem.TEXTFIELD;
            oldComp = ta;
            rows = DEFAULT_TEXTFIELD_ROWS;
        }

        WorkbenchTemplateMappingItem wbtmi = inputPanel.getWbtmi();
        inputPanel.setComp(createUIComp(WorkbenchTask.getDataType(wbtmi), wbtmi.getCaption(), wbtmi.getFieldName(),
                fieldType, wbtmi.getDataFieldLength(), fieldLen, rows, wbtmi));

        ignoreChanges = true;
        ((JTextComponent) inputPanel.getComp()).setText(oldComp.getText());
        ignoreChanges = false;

        hasChanged = true;
        workbenchPane.setChanged(true);
    }

    /**
     *  Show a properties dialog for the control.
     */
    protected void showControlProps() {
        if (selectedInputPanel != null) // this shouldn't happen
        {
            if (controlPropsDlg == null) {
                controlPropsDlg = new EditFormControlDlg((Frame) UIRegistry.get(UIRegistry.FRAME), "",
                        selectedInputPanel, this); // I18N
            }

            controlPropsDlg.setTitle(String.format(getResourceString("WB_EDIT_DLG_TITLE"),
                    new Object[] { selectedInputPanel.getControlTitle() }));

            controlPropsDlg.setVisible(true);
            if (!controlPropsDlg.isCancelled()) {
                workbenchPane.gridColumnsUpdated();

                adjustPanelSize();
            }
            controlPropsDlg.dispose();
            controlPropsDlg = null;
        }
    }

    /**
     * Adjust the size of the FormPane to encompass the the controls. 
     */
    protected void adjustPanelSize() {
        int maxX = 0;
        int maxY = 0;

        for (InputPanel panel : uiComps) {
            Rectangle r = panel.getBounds();
            maxX = Math.max(r.x + r.width, maxX);
            maxY = Math.max(r.y + r.height, maxY);
        }

        Dimension size = getSize();
        if (maxX != size.width || maxY != size.height) {
            initialSize.width = maxX;
            initialSize.height = maxY;
            scrollPane.validate();
            scrollPane.repaint();
        }
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.wb.FormPaneWrapper#aboutToShowHide(boolean)
     */
    @Override
    public void aboutToShowHide(final boolean show) {
        if (!show) {
            if (currentIndex > -1 && changesInForm) {
                copyDataFromForm(currentIndex);
            }
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    if (firstComp != null) {
                        firstComp.requestFocus();
                    }
                }
            });
        }
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.wb.FormPaneWrapper#showingPane(boolean)
     */
    @Override
    public void showingPane(final boolean show) {
        if (show) {
            if (wasShowing) {
                controlPropsDlg.setVisible(true);
            }
        } else if (controlPropsDlg != null) {
            controlPropsDlg.setVisible(false);
            wasShowing = true;

        } else {
            wasShowing = false;
        }
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.wb.FormPaneWrapper#copyDataFromForm()
     */
    @Override
    public void copyDataFromForm() {
        copyDataFromForm(currentIndex);
    }

    /**
     * @param p
     * @param wbRow
     */
    protected void copyDataFromForm(final InputPanel p, final WorkbenchRow wbRow) {
        short col = p.getWbtmi().getViewOrder();
        WorkbenchDataItem result = null;

        if (p.getComp() instanceof JTextComponent) {
            String data = ((JTextComponent) p.getComp()).getText();
            String cellData = wbRow.getData(col);
            if (StringUtils.isNotEmpty(cellData) || data != null) {
                result = wbRow.setData(data == null ? "" : data, col, true);
            }

        } else if (p.getComp() instanceof GetSetValueIFace) {
            Object data = ((GetSetValueIFace) p.getComp()).getValue();
            String cellData = wbRow.getData(col);
            if (StringUtils.isNotEmpty(cellData) || data != null) {
                result = wbRow.setData(data == null ? "" : data.toString(), col, true);
            }

        } else if (p.getComp() instanceof JScrollPane) {
            JScrollPane sc = (JScrollPane) p.getComp();
            Component comp = sc.getViewport().getView();
            if (comp instanceof JTextArea) {
                result = wbRow.setData(((JTextArea) comp).getText(), col, true);
            }
        } else if (p.getComp() instanceof JComboBox) {
            JComboBox cb = (JComboBox) p.getComp();
            result = wbRow.setData(cb.getSelectedItem().toString(), col, true);
        } else {
            log.error("Can't get data from control[" + p.getLabelText() + "]");
        }

        if (result != null && workbenchPane.getTask() instanceof SGRTask) {
            wbRow.setSgrStatus((byte) 1);
        }
    }

    /**
     * Copies the data from the form into the row.
     * @param index the index of the row
     */
    protected void copyDataFromForm(final int index) {
        if (!changesInForm) {
            return;
        }

        ignoreChanges = true;

        changesInForm = false;

        int modelIndex = workbenchPane.getSpreadSheet().convertRowIndexToModel(index);
        WorkbenchRow wbRow = workbench.getWorkbenchRowsAsList().get(modelIndex);
        for (InputPanel p : uiComps) {
            copyDataFromForm(p, wbRow);
        }
        ignoreChanges = false;
    }

    /* (non-Javadoc)
     * @see javax.swing.JComponent#setVisible(boolean)
     */
    @Override
    public void setVisible(boolean aFlag) {
        if (!aFlag && controlPropsDlg != null) {
            controlPropsDlg.setVisible(false);
        }
        super.setVisible(aFlag);
    }

    /**
     * Sets the data into the form for a given index.
     * @param index the index of the record to be set
     */
    protected void setDataIntoForm(final int index) {
        if (index < 0) {
            ignoreChanges = true;
            for (InputPanel panel : uiComps) {
                panel.getLabel().setEnabled(false);
                Component comp = panel.getComp();
                comp.setEnabled(false);
                if (comp instanceof JTextComponent) {
                    ((JTextComponent) comp).setText("");
                } else if (comp instanceof JCheckBox) {
                    ((JCheckBox) comp).setSelected(false);
                }
            }
            ignoreChanges = false;

            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    workbenchPane.getResultSetController().getNewRecBtn().requestFocus();
                }
            });

            return;

        } else if (index == 0 && workbench.getWorkbenchRowsAsList().size() == 1) {
            for (InputPanel panel : uiComps) {
                panel.getLabel().setEnabled(true);
                panel.getComp().setEnabled(true);
            }
        }

        ignoreChanges = true; // turn off change notification

        int modelIndex = workbenchPane.getSpreadSheet().convertRowIndexToModel(index);
        WorkbenchRow wbRow = workbench.getWorkbenchRowsAsList().get(modelIndex);
        for (InputPanel p : uiComps) {
            int col = p.getWbtmi().getViewOrder();

            if (p.getComp() instanceof GetSetValueIFace) {
                ((GetSetValueIFace) p.getComp()).setValue(wbRow.getData(col), wbRow.getData(col));

            } else if (p.getComp() instanceof JComboBox) {
                ((JComboBox) p.getComp()).setSelectedItem(wbRow.getData(col));
            } else if (p.getComp() instanceof JScrollPane) {
                JScrollPane sc = (JScrollPane) p.getComp();
                Component comp = sc.getViewport().getView();
                if (comp instanceof JTextArea) {
                    ((JTextArea) comp).setText(wbRow.getData(col));
                }
            } else {
                ((JTextComponent) p.getComp()).setText(wbRow.getData(col));
            }
        }
        ignoreChanges = false;
    }

    //-----------------------------------------------
    // ResultSetControllerListener Interface
    //-----------------------------------------------

    /* (non-Javadoc)
     * @see edu.ku.brc.ui.forms.ResultSetControllerListener#indexAboutToChange(int, int)
     */
    public boolean indexAboutToChange(int oldIndex, int newIndex) {
        if (newIndex != currentIndex && currentIndex > -1 && scrollPane.isVisible()) {
            copyDataFromForm(currentIndex);
            currentIndex = newIndex;
        }
        return true;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.ui.forms.ResultSetControllerListener#indexChanged(int)
     */
    public void indexChanged(int newIndex) {
        if (scrollPane.isVisible()) {
            currentIndex = newIndex;
            setDataIntoForm(currentIndex);
            if (workbenchPane.isDoIncremental()) {
                updateValidationUI();
            } else {
                for (InputPanel ip : uiComps) {
                    ip.getLabel().setToolTipText(null);
                    ip.getLabel().setBorder(null);
                }
            }
            if (firstComp != null) {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        firstComp.requestFocus();
                    }
                });
            }
        }
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.ui.forms.ResultSetControllerListener#newRecordAdded()
     */
    public void newRecordAdded() {
        if (currentIndex > -1) {
            copyDataFromForm(currentIndex);
        }
        workbenchPane.addRowAfter();
    }

    //-----------------------------------------------
    // GhostActionable Interface
    //-----------------------------------------------

    /* (non-Javadoc)
     * @see edu.ku.brc.ui.dnd.GhostActionable#doAction(edu.ku.brc.ui.dnd.GhostActionable)
     */
    public void doAction(GhostActionable src) {
        if (src instanceof InputPanel) {
            InputPanel inputPanel = (InputPanel) src;
            Point offSet = src.getMouseInputAdapter().getOffsetFromStartPnt();
            Point location = inputPanel.getLocation();
            Point offset = ((GhostGlassPane) UIRegistry.get(UIRegistry.GLASSPANE)).getOffset();
            location.translate(offSet.x - offset.x, offSet.y - offset.y);
            location.x = Math.max(0, location.x);
            location.y = Math.max(0, location.y);
            //System.out.println("***************************************");
            inputPanel.setLocation(location);
            remove(inputPanel);
            add(inputPanel);
            repaint();
            validate();
            doLayout();

            adjustPanelSize();

            // Update the template
            WorkbenchTemplateMappingItem wbtmi = inputPanel.getWbtmi();
            wbtmi.setXCoord((short) location.x);
            wbtmi.setYCoord((short) location.y);
            workbenchPane.setChanged(true);

            UsageTracker.getUsageCount("WBLayoutFormDrop");
        }
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.ui.dnd.GhostActionable#setData(java.lang.Object)
     */
    public void setData(final Object data) {
        //this.data = data;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.ui.dnd.GhostActionable#getData()
     */
    public Object getData() {
        return null;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.ui.dnd.GhostActionable#getDataForClass(java.lang.Class)
     */
    public Object getDataForClass(Class<?> classObj) {
        return UIHelper.getDataForClass(this, classObj);
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.ui.dnd.GhostActionable#createMouseInputAdapter()
     */
    public void createMouseInputAdapter() {
        // nothing
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.ui.dnd.GhostActionable#setActive(boolean)
     */
    public void setActive(boolean isActive) {
        // Auto-generated method stub
    }

    /**
     * Returns the adaptor for tracking mouse drop gestures
     * @return Returns the adaptor for tracking mouse drop gestures
     */
    public GhostMouseInputAdapter getMouseInputAdapter() {
        return null;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.ui.dnd.GhostActionable#getBufferedImage()
     */
    public BufferedImage getBufferedImage() {
        return null;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.ui.dnd.GhostActionable#getDataFlavor()
     */
    public List<DataFlavor> getDropDataFlavors() {
        return dropFlavors;
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.ui.dnd.GhostActionable#getDragDataFlavors()
     */
    public List<DataFlavor> getDragDataFlavors() {
        return null; // this is not draggable
    }

    /**
     * Helper method for setting state that changes were made.
     */
    protected void stateChange() {
        //System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!STATE CHANGE!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
        if (!ignoreChanges) {
            changesInForm = true;
            workbenchPane.setChanged(true);
            if (workbenchPane.isDoIncremental()) {
                ignoreChanges = true;
                WorkbenchRow wbRow = workbench.getWorkbenchRowsAsList().get(currentIndex);
                copyDataFromForm();
                workbenchPane.updateRowValidationStatus(currentIndex, -1, null);
                SwingUtilities.invokeLater(new Runnable() {

                    @Override
                    public void run() {
                        workbenchPane.updateBtnUI();
                    }

                });
                updateValidationUI(wbRow);
                ignoreChanges = false;
            }
        }
    }

    /* (non-Javadoc)
     * @see edu.ku.brc.specify.tasks.subpane.wb.FormPaneWrapper#updateValidationUI()
     */
    @Override
    public void updateValidationUI() {
        updateValidationUI(workbench.getWorkbenchRowsAsList().get(currentIndex));
    }

    /**
     * @param wbRow
     * 
     * Updates validation status display for all controls.
     */
    protected void updateValidationUI(final WorkbenchRow wbRow) {
        for (InputPanel ip : uiComps) {
            updateValidationUI(ip, wbRow);
        }
    }

    /**
     * @param ip
     */
    protected void updateValidationUI(InputPanel ip, WorkbenchRow wbRow) {
        //for some reason addAttributes() is not working on the form pane.
        //      workbenchPane.getCellDecorator().addAttributes(ip.getLabel(), wbCell, 
        //            workbenchPane.isDoIncrementalValidation(),
        //            workbenchPane.isDoIncrementalMatching());         
        //System.out.println("Updating validation UI for " + ip.getLabelText());
        String toolTip = null;
        LineBorder border = null;
        WorkbenchDataItem wbCell = wbRow.getItems().get(ip.getWbtmi().getViewOrder());
        if (wbCell != null) {
            toolTip = wbCell.getStatusText();
            if (wbCell.getEditorValidationStatus() == WorkbenchDataItem.VAL_ERROR
                    && workbenchPane.isDoIncrementalValidation()) {
                border = new LineBorder(workbenchPane.getCellDecorator().errorBorder);
            } else if (wbCell.getEditorValidationStatus() == WorkbenchDataItem.VAL_NEW_DATA
                    && workbenchPane.isDoIncrementalMatching()) {
                border = new LineBorder(workbenchPane.getCellDecorator().newDataBorder);
            } else {
                toolTip = null;
            }
        }
        ip.getLabel().setToolTipText(toolTip);
        ip.getLabel().setBorder(border);
    }
}