001/* A panel for editing the layout of a customizable run control panel.
002
003 Copyright (c) 2007-2018 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 This class is based on FormEditor by Michael Connor, which
028 bears the following copyright:
029
030 * Copyright (c) 2004-2018 by Michael Connor. All Rights Reserved.
031 *
032 * Redistribution and use in source and binary forms, with or without
033 * modification, are permitted provided that the following conditions are met:
034 *
035 *  o Redistributions of source code must retain the above copyright notice,
036 *    this list of conditions and the following disclaimer.
037 *
038 *  o Redistributions in binary form must reproduce the above copyright notice,
039 *    this list of conditions and the following disclaimer in the documentation
040 *    and/or other materials provided with the distribution.
041 *
042 *  o Neither the name of FormLayoutBuilder or Michael Connor nor the names of
043 *    its contributors may be used to endorse or promote products derived
044 *    from this software without specific prior written permission.
045 *
046 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
047 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
048 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
049 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
050 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
051 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
052 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
053 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
054 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
055 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
056 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
057 */
058package ptolemy.actor.gui.run;
059
060import java.awt.BorderLayout;
061import java.awt.Color;
062import java.awt.Component;
063import java.awt.Container;
064import java.awt.Insets;
065import java.awt.Point;
066import java.awt.Rectangle;
067import java.awt.Window;
068import java.awt.event.ActionEvent;
069import java.awt.event.ActionListener;
070import java.awt.event.KeyEvent;
071import java.awt.event.MouseAdapter;
072import java.awt.event.MouseEvent;
073import java.beans.BeanInfo;
074import java.beans.Introspector;
075import java.beans.PropertyDescriptor;
076import java.io.IOException;
077import java.lang.reflect.Method;
078import java.util.HashMap;
079import java.util.HashSet;
080import java.util.Iterator;
081import java.util.LinkedList;
082import java.util.List;
083import java.util.Map;
084import java.util.Set;
085
086import javax.swing.AbstractAction;
087import javax.swing.AbstractListModel;
088import javax.swing.AbstractSpinnerModel;
089import javax.swing.Action;
090import javax.swing.ImageIcon;
091import javax.swing.JButton;
092import javax.swing.JComboBox;
093import javax.swing.JLabel;
094import javax.swing.JList;
095import javax.swing.JOptionPane;
096import javax.swing.JPanel;
097import javax.swing.JScrollPane;
098import javax.swing.JSpinner;
099import javax.swing.JSplitPane;
100import javax.swing.JTable;
101import javax.swing.JToolBar;
102import javax.swing.JViewport;
103import javax.swing.KeyStroke;
104import javax.swing.ListCellRenderer;
105import javax.swing.ListSelectionModel;
106import javax.swing.SpinnerNumberModel;
107import javax.swing.event.ChangeEvent;
108import javax.swing.event.ChangeListener;
109import javax.swing.table.DefaultTableCellRenderer;
110
111import org.mlc.swing.layout.ContainerLayout;
112import org.mlc.swing.layout.FormEditor;
113import org.mlc.swing.layout.LayoutConstraintsManager;
114
115import com.jgoodies.forms.factories.Borders;
116import com.jgoodies.forms.factories.DefaultComponentFactory;
117import com.jgoodies.forms.layout.CellConstraints;
118
119import ptolemy.actor.gui.Placeable;
120import ptolemy.actor.injection.PortablePlaceable;
121import ptolemy.gui.ComponentDialog;
122import ptolemy.gui.Query;
123import ptolemy.kernel.util.NamedObj;
124import ptolemy.util.FileUtilities;
125
126///////////////////////////////////////////////////////////////////
127//// PtolemyFormEditor
128
129/**
130A customized version of the FormEditor class by
131Michael Connor (mlconnor@yahoo.com).
132
133@see FormEditor
134@author Michael Connor and Edward A. Lee
135@version $Id$
136@since Ptolemy II 8.0
137@Pt.ProposedRating Yellow (eal)
138@Pt.AcceptedRating Red (cxh)
139 */
140@SuppressWarnings("serial")
141public class PtolemyFormEditor extends JPanel {
142
143    /** Construct a new form editor.
144     *  @param layoutFrame The frame within which this editor will be added.
145     *  @param layout The layout manager.
146     *  @param container The container.
147     */
148    public PtolemyFormEditor(RunLayoutFrame layoutFrame, ContainerLayout layout,
149            Container container) {
150        super();
151        _layoutFrame = layoutFrame;
152
153        // Create the layout table.
154        _table = new LayoutTable(_layoutFrame, this);
155        JScrollPane tableScrollPane = new JScrollPane(_table);
156
157        // Create the component palette for this Ptolemy model.
158        ComponentPaletteListModel componentPaletteListModel = new ComponentPaletteListModel();
159        PaletteList componentPalette = new PaletteList(this,
160                componentPaletteListModel);
161        JScrollPane componentPaletteScrollPane = new JScrollPane(
162                componentPalette);
163
164        JPanel propertiesPanel = new JPanel();
165
166        _container = container;
167        _containerLayout = layout;
168
169        _table.setBackground(java.awt.Color.white);
170        _table.setSelectionBackground(new Color(220, 220, 255));
171        _table.setSelectionForeground(Color.black);
172
173        _table.setDefaultRenderer(Object.class,
174                new ConstraintTableCellRenderer());
175        _table.setRowHeight(20);
176        _table.setModel(_tableModel);
177        _table.setCellSelectionEnabled(true);
178        _table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
179
180        JLabel verticalAlignmentLabel = new JLabel("Vertical Alignment");
181        verticalAlignmentLabel.setLabelFor(_verticalAlignmentCombo);
182        verticalAlignmentLabel.setDisplayedMnemonic(KeyEvent.VK_V);
183
184        JLabel horizontalAlignmentLabel = new JLabel("Horizontal Alignment");
185        horizontalAlignmentLabel.setLabelFor(_horizontalAlignmentCombo);
186        horizontalAlignmentLabel.setDisplayedMnemonic(KeyEvent.VK_H);
187
188        JLabel columnSpanLabel = new JLabel("Column Span");
189        columnSpanLabel.setLabelFor(_columnSpanSpinner);
190        columnSpanLabel.setDisplayedMnemonic(KeyEvent.VK_C);
191
192        JLabel rowSpanLabel = new JLabel("Row Span");
193        rowSpanLabel.setLabelFor(_rowSpanSpinner);
194        rowSpanLabel.setDisplayedMnemonic(KeyEvent.VK_R);
195
196        _columnInsertAfterButton
197                .setToolTipText("Insert a column after this column");
198        _columnInsertBeforeButton
199                .setToolTipText("Insert a column before this column");
200        _columnDeleteButton.setToolTipText("Delete this column");
201        _rowInsertBeforeButton.setToolTipText("Insert a row before this row");
202        _rowInsertAfterButton.setToolTipText("Insert a row after this row");
203
204        JToolBar toolbar = new JToolBar();
205        toolbar.add(_removeComponentButton);
206        toolbar.addSeparator();
207        toolbar.add(_columnDeleteButton);
208        toolbar.add(_columnInsertBeforeButton);
209        toolbar.add(_columnInsertAfterButton);
210        toolbar.addSeparator();
211        toolbar.add(_rowDeleteButton);
212        toolbar.add(_rowInsertBeforeButton);
213        toolbar.add(_rowInsertAfterButton);
214        toolbar.addSeparator();
215        toolbar.add(_packAction);
216
217        // Specify that no component is selected.
218        setFormComponent(null);
219
220        LayoutConstraintsManager layoutConstraintsManager = LayoutConstraintsManager
221                .getLayoutConstraintsManager(
222                        this.getClass().getClassLoader().getResourceAsStream(
223                                "/ptolemy/actor/gui/run/editableLayoutConstraints.xml"));
224
225        JPanel insetsPanel = new JPanel();
226        JPanel contentPanel = new JPanel();
227
228        layoutConstraintsManager.setLayout("mainLayout", contentPanel);
229        layoutConstraintsManager.setLayout("insetsLayout", insetsPanel);
230        layoutConstraintsManager.setLayout("propertiesLayout", propertiesPanel);
231
232        insetsPanel.add(_rightInsetSpinner, "rightInsetSpinner");
233        insetsPanel.add(_leftInsetSpinner, "leftInsetSpinner");
234        insetsPanel.add(_topInsetSpinner, "topInsetSpinner");
235        insetsPanel.add(_bottomInsetSpinner, "bottomInsetSpinner");
236
237        propertiesPanel.add(componentPaletteScrollPane,
238                "componentPaletteScrollPane");
239        componentPalette.setCellRenderer(new ComponentPaletteListRenderer());
240        JLabel componentPaletteLabel = new JLabel("Palette");
241        propertiesPanel.add(componentPaletteLabel, "componentPaletteLabel");
242
243        contentPanel.add(rowSpanLabel, "rowSpanLabel");
244        contentPanel.add(_horizontalAlignmentCombo, "horizontalAlignmentCombo");
245        contentPanel.add(horizontalAlignmentLabel, "horizontalAlignmentLabel");
246        contentPanel.add(_rowSpanSpinner, "rowSpanSpinner");
247        contentPanel.add(_verticalAlignmentCombo, "verticalAlignmentCombo");
248        contentPanel.add(columnSpanLabel, "columnSpanLabel");
249        contentPanel.add(verticalAlignmentLabel, "verticalAlignmentLabel");
250        contentPanel.add(_columnSpanSpinner, "columnSpanSpinner");
251        contentPanel.add(insetsPanel, "insetsPanel");
252        JLabel insetsLabel = new JLabel("Insets");
253        contentPanel.add(insetsLabel, "insetsLabel");
254        Component constraintsSeparator = DefaultComponentFactory.getInstance()
255                .createSeparator("Component Constraints");
256        contentPanel.add(constraintsSeparator, "constraintsSeparator");
257        Component positionsSeparator = DefaultComponentFactory.getInstance()
258                .createSeparator("Component Positions");
259        contentPanel.add(positionsSeparator, "positionsSeparator");
260        contentPanel.add(toolbar, "toolbar");
261
262        // Put the layout table and palette side-by-side.
263        JSplitPane constraintsSplitPane = new JSplitPane(
264                JSplitPane.HORIZONTAL_SPLIT, tableScrollPane, propertiesPanel);
265        contentPanel.add(constraintsSplitPane, "constraintsSplitPane");
266        // The following would make the palette invisible!
267        // constraintsSplitPane.setDividerLocation(605);
268
269        contentPanel.setBorder(Borders.DIALOG_BORDER);
270
271        setLayout(new BorderLayout());
272        add(contentPanel, BorderLayout.CENTER);
273
274        _setupListeners();
275    }
276
277    ///////////////////////////////////////////////////////////////////
278    ////                         public methods                    ////
279
280    /** Edit the component by presenting a dialog that infers the
281     *  settable properties of the components.
282     *  @param component The component.
283     *  @return true upon successful completion.
284     */
285    public boolean editComponent(Component component) {
286        String name = _containerLayout.getComponentName(component);
287        Query query = new Query();
288        try {
289            BeanInfo beanInfo = Introspector.getBeanInfo(component.getClass());
290            PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
291            Map<String, String> previousValues = new HashMap<String, String>();
292            for (PropertyDescriptor propertyDescriptor : props) {
293                // Present String and Color-valued properties.
294                if (propertyDescriptor.getPropertyType() == String.class) {
295                    String propertyName = propertyDescriptor.getName();
296                    if (_propertiesToIgnore.contains(propertyName)) {
297                        continue;
298                    }
299                    Method readMethod = propertyDescriptor.getReadMethod();
300                    String value = (String) readMethod.invoke(component,
301                            new Object[] {});
302                    query.addLine(propertyName, propertyName, value);
303                    previousValues.put(propertyName, value);
304                } else if (propertyDescriptor
305                        .getPropertyType() == Color.class) {
306                    String propertyName = propertyDescriptor.getName();
307                    Method readMethod = propertyDescriptor.getReadMethod();
308                    Color value = (Color) readMethod.invoke(component,
309                            new Object[] {});
310                    float[] components = value.getRGBComponents(null);
311                    StringBuffer string = new StringBuffer("{");
312                    // Use the syntax of arrays to present the color.
313                    for (int j = 0; j < components.length; j++) {
314                        string.append(components[j]);
315                        if (j < components.length - 1) {
316                            string.append(",");
317                        } else {
318                            string.append("}");
319                        }
320                    }
321                    query.addColorChooser(propertyName, propertyName,
322                            string.toString());
323                    previousValues.put(propertyName, string.toString());
324                }
325            }
326            ComponentDialog dialog = new ComponentDialog(_layoutFrame, name,
327                    query);
328            if (dialog.buttonPressed().equals("OK")) {
329                // Set each property that has changed.
330                for (PropertyDescriptor propertyDescriptor : props) {
331                    // Present String and Color-valued properties.
332                    if (propertyDescriptor.getPropertyType() == String.class) {
333                        String propertyName = propertyDescriptor.getName();
334                        if (_propertiesToIgnore.contains(propertyName)) {
335                            continue;
336                        }
337                        String newValue = query.getStringValue(propertyName);
338                        if (!newValue
339                                .equals(previousValues.get(propertyName))) {
340                            Method writeMethod = propertyDescriptor
341                                    .getWriteMethod();
342                            writeMethod.invoke(component,
343                                    new Object[] { newValue });
344                            _containerLayout.setProperty(name, propertyName,
345                                    newValue);
346                        }
347                    } else if (propertyDescriptor
348                            .getPropertyType() == Color.class) {
349                        String propertyName = propertyDescriptor.getName();
350                        String newValue = query.getStringValue(propertyName);
351                        Color newColor = Query.stringToColor(newValue);
352                        if (!newValue
353                                .equals(previousValues.get(propertyName))) {
354                            Method writeMethod = propertyDescriptor
355                                    .getWriteMethod();
356                            writeMethod.invoke(component,
357                                    new Object[] { newColor });
358                            _containerLayout.setProperty(name, propertyName,
359                                    newColor);
360                        }
361                    }
362                }
363            }
364        } catch (Throwable throwable) {
365            // FIXME Auto-generated catch block
366            throwable.printStackTrace();
367        }
368
369        /* FIXME
370         *
371
372        NewComponentDialog dlg = NewComponentDialog.editDialog(
373                (JFrame) _layoutFrame, componentDef);
374        if (!dlg.succeeded())
375            return false;
376
377        componentDef.name = uniqueName(dlg.getComponentName(), component);
378        String newname = componentDef.name;
379
380        Component newcomponent = dlg.getInstance();
381        containerLayout.removeLayoutComponent(component);
382        containerLayout.addComponent(newname, componentDef, cellConstraints);
383
384        container.remove(component);
385        container.add(newcomponent, newname);
386
387        newComponents.remove(component);
388        newComponents.add(newcomponent);
389
390        if (componentDef.isContainer) {
391            // @todo losing components INSIDE the container!!
392            //layoutFrame.replaceContainer(name, newName, (Container) newcomponent);
393            layoutFrame.removeContainer(name);
394            layoutFrame.addContainer(newname, (Container) newcomponent);
395        }
396
397        updateLayout(newcomponent);
398        _updateList();
399         */
400        _updateLayouts();
401        repaint();
402        return true;
403    }
404
405    /** Specify the selected component. This causes the various
406     *  constraints controls for the component to be enabled and
407     *  to show the current values for the component.
408     *  @param component The selected component.
409     */
410    public void setFormComponent(Component component) {
411        CellConstraints constraints = component != null
412                ? _getComponentConstraints(component)
413                : null;
414
415        _suspendConstraintControlUpdates = true;
416
417        if (component != null) {
418            _rowSpanSpinnerModel.setComponent(component);
419            _columnSpanSpinnerModel.setComponent(component);
420            _verticalAlignmentCombo.setSelectedItem(
421                    LayoutConstraintsManager.getAlignment(constraints.vAlign));
422            _horizontalAlignmentCombo.setSelectedItem(
423                    LayoutConstraintsManager.getAlignment(constraints.hAlign));
424            _topInsetSpinnerModel
425                    .setValue(Integer.valueOf(constraints.insets.top));
426            _bottomInsetSpinnerModel
427                    .setValue(Integer.valueOf(constraints.insets.bottom));
428            _rightInsetSpinnerModel
429                    .setValue(Integer.valueOf(constraints.insets.right));
430            _leftInsetSpinnerModel
431                    .setValue(Integer.valueOf(constraints.insets.left));
432        }
433
434        _verticalAlignmentCombo.setEnabled(constraints != null);
435        _horizontalAlignmentCombo.setEnabled(constraints != null);
436        _rightInsetSpinner.setEnabled(constraints != null);
437        _leftInsetSpinner.setEnabled(constraints != null);
438        _topInsetSpinner.setEnabled(constraints != null);
439        _bottomInsetSpinner.setEnabled(constraints != null);
440        _rowSpanSpinner.setEnabled(constraints != null);
441        _columnSpanSpinner.setEnabled(constraints != null);
442
443        int col = _table.getSelectedColumn();
444        int row = _table.getSelectedRow();
445
446        _removeComponentAction.setEnabled(constraints != null);
447        _columnDeleteButton.setEnabled(
448                row == 0 && col > 0 && _containerLayout.getColumnCount() > 1);
449        _columnInsertAfterButton.setEnabled(col > -1);
450        _columnInsertBeforeButton.setEnabled(col > 0);
451        _rowDeleteButton.setEnabled(
452                col == 0 && row > 0 && _containerLayout.getRowCount() > 1);
453        _rowInsertBeforeButton.setEnabled(row > 0);
454        _rowInsertAfterButton.setEnabled(row > -1);
455
456        _suspendConstraintControlUpdates = false;
457    }
458
459    /** Update the layout for the specified component.
460     *  @param component  The component to have its layout updated.
461     */
462    public void updateLayout(Component component) {
463        if (_suspendConstraintControlUpdates) {
464            return;
465        }
466        CellConstraints constraints = _getComponentConstraints(component);
467
468        // we have to update the _containerLayout which is the keeper of all
469        // constraints. if we didn't do this then we wouldn't notice any changes
470        // when we went to print everything out.
471        String name = _getComponentName(component);
472        // i don't like this direct access thing. this should be changed...
473        _containerLayout.setCellConstraints(name, constraints);
474        // updateForm();
475
476        // be careful when modifying the next few lines of code. this
477        // is tricky to get right. this seems to work.
478        _container.invalidate();
479        _container.doLayout();
480
481        // KBR list (for example) doesn't seem to re-layout properly on drag&drop
482        // without these
483        _container.validate();
484        _container.repaint();
485
486        if (component instanceof Container) {
487            Container cContainer = (Container) component;
488            cContainer.invalidate();
489            cContainer.doLayout();
490        }
491    }
492
493    ///////////////////////////////////////////////////////////////////
494    ////                         protected methods                 ////
495
496    /** Update the layouts. */
497    protected void _updateLayouts() {
498        _container.validate();
499        _container.doLayout();
500
501        Container parent = _container;
502
503        while (parent != null) {
504            parent.validate();
505            if (parent instanceof Window) {
506                // Packing has the unfortunate
507                // side effect of centering on the
508                // screen, which is annoying...
509                // ((Window)parent).pack();
510                parent.setVisible(true);
511            }
512            parent = parent.getParent();
513        }
514    }
515
516    ///////////////////////////////////////////////////////////////////
517    ////                         private methods                   ////
518
519    /** Return the component constraints from the layout manager.
520     *  @param component The component.
521     *  @return The constraints.
522     */
523    private CellConstraints _getComponentConstraints(Component component) {
524        return _containerLayout.getComponentConstraints(component);
525    }
526
527    /** Return the name of the specified component as known by the layout
528     *  controller.
529     *  @param control The component.
530     *  @return The name of the component in the layout.
531     */
532    private String _getComponentName(Component control) {
533        return _containerLayout.getComponentName(control);
534    }
535
536    /** Insert a column.
537     *  @param column The column index.
538     */
539    private void _insertColumn(int column) {
540        for (int index = 0; index < _container.getComponentCount(); index++) {
541            Component component = _container.getComponent(index);
542            CellConstraints constraints = _getComponentConstraints(component);
543            if (constraints.gridX > column) {
544                constraints.gridX++;
545            }
546        }
547
548        try {
549            _containerLayout.addColumnSpec(column, "pref");
550            _tableModel.fireTableStructureChanged();
551            _setSelectedCell(column + 1, 0, true);
552            _specsChanged();
553        } catch (IllegalArgumentException iae) {
554            // FIXME: Use our error reporting.
555            JOptionPane.showMessageDialog(PtolemyFormEditor.this,
556                    iae.getMessage(), "Invalid Layout",
557                    JOptionPane.ERROR_MESSAGE);
558        }
559    }
560
561    /** Insert a row.
562     *  @param rowIndex The row index.
563     */
564    private void _insertRow(int rowIndex) {
565        for (int index = 0; index < _container.getComponentCount(); index++) {
566            Component component = _container.getComponent(index);
567            CellConstraints constraints = _getComponentConstraints(component);
568            if (constraints.gridY > rowIndex) {
569                constraints.gridY++;
570            }
571        }
572
573        try {
574            _containerLayout.addRowSpec(rowIndex, "pref");
575            _tableModel.fireTableStructureChanged();
576            _setSelectedCell(0, rowIndex + 1, true);
577            _specsChanged();
578        } catch (IllegalArgumentException iae) {
579            // FIXME: Use our error reporting.
580            JOptionPane.showMessageDialog(PtolemyFormEditor.this,
581                    iae.getMessage(), "Invalid Layout",
582                    JOptionPane.ERROR_MESSAGE);
583        }
584    }
585
586    /** Put an icon onto an AbstractAction.  */
587    private void _putValue(AbstractAction abstractAction, String iconName) {
588        String iconDirectory = "$CLASSPATH/ptolemy/actor/gui/run/";
589        try {
590            // We use nameToURL() here so that this code will work
591            // in webstart.
592            // FIXME: we should use rollover icons like what is in
593            // vergil.basic.BasicGraphFrame, where we call
594            // diva.util.GUIUtilities.
595            abstractAction.putValue(Action.SMALL_ICON,
596                    new ImageIcon(FileUtilities
597                            .nameToURL(iconDirectory + iconName, null, null)));
598        } catch (IOException ex) {
599            System.out.println("Failed to open " + iconDirectory + iconName);
600            ex.printStackTrace();
601        }
602    }
603
604    /** Specify the cell in the table that is selected. */
605    private void _setSelectedCell(int columnIndex, int rowIndex,
606            boolean forceVisible) {
607        // we don't want to update the selection interval if nothing changed...
608        _table.getSelectionModel().setSelectionInterval(rowIndex, rowIndex);
609        _table.getColumnModel().getSelectionModel()
610                .setSelectionInterval(columnIndex, columnIndex);
611
612        if (forceVisible) {
613            // let's make sure the cell is in the visible range...
614            JViewport viewport = (JViewport) _table.getParent();
615            Rectangle rect = _table.getCellRect(rowIndex, columnIndex, true);
616            Point pt = viewport.getViewPosition();
617            rect.setLocation(rect.x - pt.x, rect.y - pt.y);
618            viewport.scrollRectToVisible(rect);
619        }
620    }
621
622    /** Set up the listeners. */
623    private void _setupListeners() {
624        _verticalAlignmentCombo.addActionListener(new ActionListener() {
625            @Override
626            public void actionPerformed(ActionEvent e) {
627                Component component = _table.getSelectedControl();
628                if (component != null) {
629                    CellConstraints cellConstraints = _getComponentConstraints(
630                            component);
631                    cellConstraints.vAlign = LayoutConstraintsManager
632                            .getAlignment((String) _verticalAlignmentCombo
633                                    .getSelectedItem());
634                    updateLayout(component);
635                }
636            }
637        });
638        _horizontalAlignmentCombo.addActionListener(new ActionListener() {
639            @Override
640            public void actionPerformed(ActionEvent e) {
641                Component component = _table.getSelectedControl();
642                if (component != null) {
643                    CellConstraints cellConstraints = _getComponentConstraints(
644                            component);
645                    cellConstraints.hAlign = LayoutConstraintsManager
646                            .getAlignment((String) _horizontalAlignmentCombo
647                                    .getSelectedItem());
648                    updateLayout(component);
649                }
650            }
651        });
652        _topInsetSpinnerModel.addChangeListener(new ChangeListener() {
653            @Override
654            public void stateChanged(ChangeEvent e) {
655                if (!_suspendConstraintControlUpdates) {
656                    Component component = _table.getSelectedControl();
657                    CellConstraints constraints = _getComponentConstraints(
658                            component);
659                    Insets insets = new Insets(
660                            _topInsetSpinnerModel.getNumber().intValue(),
661                            constraints.insets.left, constraints.insets.bottom,
662                            constraints.insets.right);
663                    constraints.insets = insets;
664                    updateLayout(component);
665                }
666            }
667        });
668        _leftInsetSpinnerModel.addChangeListener(new ChangeListener() {
669            @Override
670            public void stateChanged(ChangeEvent e) {
671                if (!_suspendConstraintControlUpdates) {
672                    Component component = _table.getSelectedControl();
673                    CellConstraints constraints = _getComponentConstraints(
674                            component);
675                    Insets insets = new Insets(constraints.insets.top,
676                            _leftInsetSpinnerModel.getNumber().intValue(),
677                            constraints.insets.bottom,
678                            constraints.insets.right);
679                    constraints.insets = insets;
680                    updateLayout(component);
681                }
682            }
683        });
684        _rightInsetSpinnerModel.addChangeListener(new ChangeListener() {
685            @Override
686            public void stateChanged(ChangeEvent e) {
687                if (!_suspendConstraintControlUpdates) {
688                    Component component = _table.getSelectedControl();
689                    CellConstraints constraints = _getComponentConstraints(
690                            component);
691                    Insets insets = new Insets(constraints.insets.top,
692                            constraints.insets.left, constraints.insets.bottom,
693                            _rightInsetSpinnerModel.getNumber().intValue());
694                    constraints.insets = insets;
695                    updateLayout(component);
696                }
697            }
698        });
699        _bottomInsetSpinnerModel.addChangeListener(new ChangeListener() {
700            @Override
701            public void stateChanged(ChangeEvent e) {
702                if (!_suspendConstraintControlUpdates) {
703                    Component component = _table.getSelectedControl();
704                    CellConstraints constraints = _getComponentConstraints(
705                            component);
706                    Insets insets = new Insets(constraints.insets.top,
707                            constraints.insets.left,
708                            _bottomInsetSpinnerModel.getNumber().intValue(),
709                            constraints.insets.right);
710                    constraints.insets = insets;
711                    updateLayout(component);
712                }
713            }
714        });
715        _table.addMouseListener(new MouseAdapter() {
716            @Override
717            public void mouseClicked(MouseEvent e) {
718                if (e.getClickCount() == 2) {
719                    //Point p = e.getPoint();
720                    //int row = _table.rowAtPoint(p);
721                    //int col = _table.columnAtPoint(p);
722                    // support double-click:
723                    Component component = _table.getSelectedControl();
724                    if (component == null) {
725                        return;
726                    }
727
728                    /* invoke componentDef editor on double-clicked control */
729                    //String name = _getComponentName(component);
730                    editComponent(component);
731                }
732            }
733        });
734    }
735
736    /** Specify that the specifications have changed. */
737    private void _specsChanged() {
738        _updateLayouts();
739
740        // lets go down the tree
741        Component[] children = _container.getComponents();
742        for (Component component : children) {
743            if (component instanceof Container) {
744                ((Container) component).doLayout();
745            }
746        }
747    }
748
749    ///////////////////////////////////////////////////////////////////
750    ////                         inner classes                     ////
751
752    /** Model for the column span control. */
753    private class ColSpanSpinnerModel extends AbstractSpinnerModel {
754        CellConstraints constraints;
755
756        Component component;
757
758        public ColSpanSpinnerModel() {
759        }
760
761        public void setComponent(Component component) {
762            this.component = component;
763            if (component != null) {
764                constraints = _getComponentConstraints(component);
765                fireStateChanged();
766            } else {
767                constraints = null;
768            }
769        }
770
771        @Override
772        public Object getNextValue() {
773            if (constraints == null) {
774                return null;
775            }
776            Integer next = constraints.gridX + constraints.gridWidth
777                    - 1 < _containerLayout.getColumnCount()
778                            ? Integer.valueOf(constraints.gridWidth + 1)
779                            : null;
780            return next;
781        }
782
783        @Override
784        public Object getPreviousValue() {
785            if (constraints == null) {
786                return null;
787            } else {
788                Integer previous = constraints.gridWidth > 1
789                        ? Integer.valueOf(constraints.gridWidth - 1)
790                        : null;
791                return previous;
792            }
793        }
794
795        @Override
796        public Object getValue() {
797            if (constraints == null) {
798                return "";
799            } else {
800                return Integer.valueOf(constraints.gridWidth);
801            }
802        }
803
804        @Override
805        public void setValue(Object value) {
806            if (constraints == null || value == null) {
807                return;
808            }
809
810            constraints.gridWidth = ((Number) value).intValue();
811            super.fireStateChanged();
812            updateLayout(component);
813
814            // firing _table data changed messes up the
815            // selection so we'll get it and then restore it...
816            int col = _table.getSelectedColumn();
817            int row = _table.getSelectedRow();
818            _tableModel.fireTableDataChanged();
819            _setSelectedCell(col, row, true);
820        }
821    }
822
823    /** Model for the palette. */
824    private class ComponentPaletteListModel extends AbstractListModel {
825        List<String> components = new LinkedList<String>();
826
827        public ComponentPaletteListModel() {
828            // FIXME: The text of the following buttons can in theory be customized.
829            // How to provide the interface for that?
830            components.add("GoButton");
831            components.add("PauseButton");
832            components.add("ResumeButton");
833            components.add("StopButton");
834
835            components.add("ConfigureTopLevel");
836            components.add("ConfigureDirector");
837
838            components.add("Label");
839
840            // Iterate over all the components that implement Placeable.
841            if (_layoutFrame._pane._model != null) {
842                Iterator atomicEntities = _layoutFrame._pane._model
843                        .allAtomicEntityList().iterator();
844                while (atomicEntities.hasNext()) {
845                    NamedObj object = (NamedObj) atomicEntities.next();
846                    if (object instanceof Placeable
847                            || object instanceof PortablePlaceable) {
848                        components.add("Placeable:"
849                                + object.getName(_layoutFrame._pane._model));
850                    }
851                }
852            }
853
854            // FIXME: Do we need to be able to customize the name?
855            components.add("Subpanel:Subpanel");
856
857            // FIXME: This isn't useful without being able to specify the entity.
858            components.add("Configure:Entity");
859        }
860
861        @Override
862        public int getSize() {
863            return components.size();
864        }
865
866        @Override
867        public Object getElementAt(int index) {
868            return components.get(index);
869        }
870    }
871
872    /** Renderer for the palette items. */
873    private static class ComponentPaletteListRenderer extends JLabel
874            implements ListCellRenderer {
875        // FindBugs suggests making this class static so as to decrease
876        // the size of instances and avoid dangling references.
877
878        public ComponentPaletteListRenderer() {
879            setOpaque(true);
880        }
881
882        @Override
883        public Component getListCellRendererComponent(JList list, Object value,
884                int index, boolean isSelected, boolean cellHasFocus) {
885            setText(value.toString());
886            if (isSelected) {
887                setBackground(list.getSelectionBackground());
888                setForeground(list.getSelectionForeground());
889            } else {
890                setBackground(list.getBackground());
891                setForeground(list.getForeground());
892            }
893            return this;
894        }
895    }
896
897    /** Renderer for table cells. */
898    private class ConstraintTableCellRenderer extends DefaultTableCellRenderer {
899        @Override
900        public Component getTableCellRendererComponent(JTable table,
901                Object value, boolean isSelected, boolean hasFocus, int row,
902                int column) {
903            String stringValue = null;
904            if (value != null) {
905                if (value instanceof Component) {
906                    String name = _getComponentName((Component) value);
907                    stringValue = name == null ? "(Untitled)" : name;
908                } else {
909                    // in this case it's a row or col header
910                    stringValue = (String) value;
911                }
912            }
913
914            return super.getTableCellRendererComponent(table, stringValue,
915                    isSelected, hasFocus, row, column);
916        }
917    }
918
919    /** Action to delete a column. */
920    private class DeleteColumnAction extends AbstractAction {
921        public DeleteColumnAction() {
922            super();
923            putValue(Action.SHORT_DESCRIPTION, "Deletes the selected column");
924            putValue(Action.LONG_DESCRIPTION, "Deletes the selected column");
925            _putValue(this, "ColumnDelete24.gif");
926            putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_C));
927        }
928
929        @Override
930        public void actionPerformed(ActionEvent e) {
931            int columnIndex = _table.getSelectedColumn();
932            for (int index = 0; index < _container
933                    .getComponentCount(); index++) {
934                Component component = _container.getComponent(index);
935                CellConstraints constraints = _getComponentConstraints(
936                        component);
937                if (constraints.gridX >= columnIndex && constraints.gridX > 1) {
938                    constraints.gridX--;
939                } else {
940                    // if the col deleted was within the span of the component and the
941                    // component
942                    // is bigger than one cell...
943                    if (constraints.gridX + constraints.gridWidth
944                            - 1 >= columnIndex && constraints.gridWidth > 1) {
945                        constraints.gridWidth--;
946                    }
947                }
948            }
949            _containerLayout.removeColumnSpec(columnIndex - 1);
950            _tableModel.fireTableStructureChanged();
951            _table.changeSelection(0,
952                    Math.min(columnIndex, _containerLayout.getColumnCount()),
953                    false, false);
954            _specsChanged();
955            _table.requestFocus();
956        }
957    }
958
959    /** Action to delete a row. */
960    private class DeleteRowAction extends AbstractAction {
961        public DeleteRowAction() {
962            super();
963            putValue(Action.SHORT_DESCRIPTION, "Deletes the selected row");
964            putValue(Action.LONG_DESCRIPTION, "Deletes the selected row");
965            _putValue(this, "RowDelete24.gif");
966            putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_D));
967        }
968
969        @Override
970        public void actionPerformed(ActionEvent e) {
971            int rowIndex = _table.getSelectedRow();
972
973            // move any components that are on the deleted row or
974            // above it down one
975            for (int index = 0; index < _container
976                    .getComponentCount(); index++) {
977                Component component = _container.getComponent(index);
978                CellConstraints constraints = _getComponentConstraints(
979                        component);
980
981                if (constraints.gridY >= rowIndex && constraints.gridY > 1) {
982                    constraints.gridY--;
983                } else {
984                    // if the row deleted was within the span of the component and the
985                    // component
986                    // is bigger than one cell...
987                    if (constraints.gridY + constraints.gridHeight
988                            - 1 >= rowIndex && constraints.gridHeight > 1) {
989                        constraints.gridHeight--;
990                    }
991                }
992            }
993            _containerLayout.removeRowSpec(rowIndex - 1);
994            _tableModel.fireTableRowsDeleted(rowIndex, rowIndex);
995            _table.changeSelection(
996                    Math.min(rowIndex, _containerLayout.getRowCount()), 0,
997                    false, false);
998            _specsChanged();
999            _table.requestFocus();
1000        }
1001    }
1002
1003    /** The data model for the table. */
1004    private class GridTableModel extends javax.swing.table.AbstractTableModel {
1005        @Override
1006        public int getColumnCount() {
1007            return _containerLayout != null
1008                    ? _containerLayout.getColumnCount() + 1
1009                    : 1;
1010        }
1011
1012        @Override
1013        public int getRowCount() {
1014            return _containerLayout != null ? _containerLayout.getRowCount() + 1
1015                    : 1;
1016        }
1017
1018        @Override
1019        public boolean isCellEditable(int row, int col) {
1020            return (row == 0 || col == 0) && !(row == 0 && col == 0);
1021        }
1022
1023        @Override
1024        public String getColumnName(int col) {
1025            return col == 0 ? "*" : "" + col;
1026        }
1027
1028        @Override
1029        public void setValueAt(Object aValue, int row, int col) {
1030            String value = (String) aValue;
1031            if (row == 0) {
1032                // a column was changed
1033                try {
1034                    _containerLayout.setColumnSpec(col - 1, value);
1035                    _specsChanged();
1036                } catch (IllegalArgumentException iae) {
1037                    JOptionPane.showMessageDialog(PtolemyFormEditor.this,
1038                            iae.getMessage(), "Invalid Layout",
1039                            JOptionPane.ERROR_MESSAGE);
1040                }
1041            } else if (col == 0) {
1042                try {
1043                    _containerLayout.setRowSpec(row - 1, value);
1044                    _specsChanged();
1045                } catch (Exception e) {
1046                    JOptionPane.showMessageDialog(null, e.getMessage(),
1047                            "Invalid row specification",
1048                            JOptionPane.ERROR_MESSAGE);
1049                }
1050            }
1051        }
1052
1053        @Override
1054        public Object getValueAt(int rowIndex, int columnIndex) {
1055            if (rowIndex == 0 && columnIndex == 0) {
1056                return null;
1057            }
1058            if (rowIndex == 0) {
1059                return _containerLayout.getColumnSpec(columnIndex - 1);
1060            }
1061            if (columnIndex == 0) {
1062                return _containerLayout.getRowSpec(rowIndex - 1);
1063            }
1064            Component component = null;
1065            for (int index = 0; index < _container
1066                    .getComponentCount(); index++) {
1067                Component thisComponent = _container.getComponent(index);
1068                // we don't want to show invisible components. we
1069                // have decided to make components that are added without
1070                // constraints invisible until they are dropped onto the form.
1071                // this is our way of hiding them.
1072                if (thisComponent.isVisible()) {
1073                    CellConstraints constraints = _getComponentConstraints(
1074                            thisComponent);
1075                    if (constraints == null) {
1076                        throw new RuntimeException(
1077                                "Unable to find constraints for component "
1078                                        + thisComponent + " in layout "
1079                                        + _containerLayout.getName());
1080                    }
1081                    if (columnIndex >= constraints.gridX
1082                            && columnIndex < constraints.gridX
1083                                    + constraints.gridWidth
1084                            && rowIndex >= constraints.gridY
1085                            && rowIndex < constraints.gridY
1086                                    + constraints.gridHeight) {
1087                        component = thisComponent;
1088                        if (component == topComponent) {
1089                            break;
1090                        }
1091                    }
1092                }
1093            }
1094            return component;
1095        }
1096    }
1097
1098    /** Action to insert a column after the selected column. */
1099    private class InsertColumnAfterAction extends AbstractAction {
1100        public InsertColumnAfterAction() {
1101            super();
1102            putValue(Action.SHORT_DESCRIPTION,
1103                    "Inserts a column after the selected column");
1104            putValue(Action.LONG_DESCRIPTION,
1105                    "Inserts a column after the selected column");
1106            _putValue(this, "ColumnInsertAfter24.gif");
1107            putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_L));
1108        }
1109
1110        @Override
1111        public void actionPerformed(ActionEvent e) {
1112            int column = _table.getSelectedColumn();
1113            _insertColumn(column);
1114            _table.requestFocus();
1115        }
1116    }
1117
1118    /** Action to insert a column before the selected column. */
1119    private class InsertColumnBeforeAction extends AbstractAction {
1120        public InsertColumnBeforeAction() {
1121            super();
1122            putValue(Action.SHORT_DESCRIPTION,
1123                    "Inserts a column before the selected column");
1124            putValue(Action.LONG_DESCRIPTION,
1125                    "Inserts a column before the selected column");
1126            _putValue(this, "ColumnInsertBefore24.gif");
1127            putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_K));
1128        }
1129
1130        @Override
1131        public void actionPerformed(ActionEvent e) {
1132            int column = _table.getSelectedColumn();
1133            _insertColumn(column - 1);
1134            _table.requestFocus();
1135        }
1136    }
1137
1138    /** Action to insert a row after the selected one. */
1139    private class InsertRowAfterAction extends AbstractAction {
1140        public InsertRowAfterAction() {
1141            super();
1142            putValue(Action.SHORT_DESCRIPTION,
1143                    "Inserts a row after the selected row");
1144            putValue(Action.LONG_DESCRIPTION,
1145                    "Inserts a row after the selected row");
1146            _putValue(this, "RowInsertAfter24.gif");
1147            putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_O));
1148        }
1149
1150        @Override
1151        public void actionPerformed(ActionEvent e) {
1152            int row = _table.getSelectedRow();
1153            _insertRow(row);
1154            _table.requestFocus();
1155        }
1156    }
1157
1158    /** Action to insert a row before the selected one. */
1159    private class InsertRowBeforeAction extends AbstractAction {
1160        public InsertRowBeforeAction() {
1161            super();
1162            putValue(Action.SHORT_DESCRIPTION,
1163                    "Inserts a row before the selected row");
1164            putValue(Action.LONG_DESCRIPTION,
1165                    "Inserts a row before the selected row");
1166            _putValue(this, "RowInsertBefore24.gif");
1167            putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_I));
1168        }
1169
1170        @Override
1171        public void actionPerformed(ActionEvent e) {
1172            int row = _table.getSelectedRow();
1173            _insertRow(row - 1);
1174            _table.requestFocus();
1175        }
1176    }
1177
1178    /** Action to pack the run control panel. */
1179    private class PackAction extends AbstractAction {
1180        public PackAction() {
1181            super();
1182            putValue(Action.SHORT_DESCRIPTION, "Pack the run control panel");
1183            putValue(Action.LONG_DESCRIPTION, "Pack the run control panel");
1184            _putValue(this, "Pack.gif");
1185            putValue(Action.ACCELERATOR_KEY,
1186                    KeyStroke.getKeyStroke(KeyEvent.VK_P, 0));
1187            putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_P));
1188        }
1189
1190        @Override
1191        public void actionPerformed(ActionEvent e) {
1192            _container.validate();
1193            _container.doLayout();
1194
1195            Container parent = _container;
1196
1197            while (parent != null) {
1198                parent.validate();
1199                if (parent instanceof Window) {
1200                    ((Window) parent).pack();
1201                    parent.setVisible(true);
1202                }
1203                parent = parent.getParent();
1204            }
1205        }
1206    }
1207
1208    /** Action to remove a component. */
1209    private class RemoveComponentAction extends AbstractAction {
1210        public RemoveComponentAction() {
1211            super();
1212            putValue(Action.SHORT_DESCRIPTION, "Delete the component");
1213            putValue(Action.LONG_DESCRIPTION, "Delete the selected component. "
1214                    + "A component must be selected for this to be enabled.");
1215            _putValue(this, "Remove24.gif");
1216            putValue(Action.ACCELERATOR_KEY,
1217                    KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
1218            putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_D));
1219        }
1220
1221        @Override
1222        public void actionPerformed(ActionEvent e) {
1223            Component selectedControl = _table.getSelectedControl();
1224            String controlName = _getComponentName(selectedControl);
1225            _container.remove(selectedControl);
1226            _tableModel.fireTableDataChanged();
1227
1228            if (selectedControl instanceof Container
1229                    && _layoutFrame.hasContainer(controlName)) {
1230                _layoutFrame.removeContainer(controlName);
1231            }
1232            _container.doLayout();
1233            _container.repaint();
1234            setFormComponent(null);
1235            _table.requestFocus();
1236        }
1237    }
1238
1239    /** Model for the row span control. */
1240    private class RowSpanSpinnerModel extends AbstractSpinnerModel {
1241        CellConstraints constraints;
1242
1243        Component component;
1244
1245        public RowSpanSpinnerModel() {
1246        }
1247
1248        public void setComponent(Component component) {
1249            this.component = component;
1250            if (component != null) {
1251                constraints = _getComponentConstraints(component);
1252                fireStateChanged();
1253            } else {
1254                constraints = null;
1255            }
1256        }
1257
1258        @Override
1259        public Object getNextValue() {
1260            if (constraints == null) {
1261                return null;
1262            } else {
1263                Integer next = constraints.gridY + constraints.gridHeight
1264                        - 1 < _containerLayout.getRowCount()
1265                                ? Integer.valueOf(constraints.gridHeight + 1)
1266                                : null;
1267                return next;
1268            }
1269        }
1270
1271        @Override
1272        public Object getPreviousValue() {
1273            if (constraints == null) {
1274                return null;
1275            } else {
1276                Integer previous = constraints.gridHeight > 1
1277                        ? Integer.valueOf(constraints.gridHeight - 1)
1278                        : null;
1279                return previous;
1280            }
1281        }
1282
1283        @Override
1284        public Object getValue() {
1285            if (constraints == null) {
1286                return "";
1287            } else {
1288                return Integer.valueOf(constraints.gridHeight);
1289            }
1290        }
1291
1292        @Override
1293        public void setValue(Object value) {
1294            if (constraints == null || value == null) {
1295                return;
1296            }
1297            //        Number val = (Number) value;
1298            constraints.gridHeight = ((Number) value).intValue();
1299            super.fireStateChanged();
1300            updateLayout(component);
1301
1302            // firing _table data changed messes up the
1303            // selection so we'll get it and then restore it...
1304            int col = _table.getSelectedColumn();
1305            int row = _table.getSelectedRow();
1306            _tableModel.fireTableDataChanged();
1307            _setSelectedCell(col, row, true);
1308        }
1309    }
1310
1311    ///////////////////////////////////////////////////////////////////
1312    ////                         protected variables               ////
1313
1314    /** The layout manager. */
1315    protected ContainerLayout _containerLayout;
1316
1317    /** The container. */
1318    protected Container _container;
1319
1320    /** The set of new components. */
1321    protected Set<Component> newComponents = new HashSet<Component>();
1322
1323    /** The top level component. */
1324    protected Component topComponent = null;
1325
1326    ///////////////////////////////////////////////////////////////////
1327    ////                         private variables                 ////
1328
1329    /** Model for the bottom inset control. */
1330    private SpinnerNumberModel _bottomInsetSpinnerModel = new SpinnerNumberModel(
1331            0, 0, Integer.MAX_VALUE, 1);
1332
1333    /** The bottom inset control. */
1334    private JSpinner _bottomInsetSpinner = new JSpinner(
1335            _bottomInsetSpinnerModel);
1336
1337    /** The button to delete a column. */
1338    private JButton _columnDeleteButton = new JButton(new DeleteColumnAction());
1339
1340    /** The button to insert after the selected column. */
1341    private JButton _columnInsertAfterButton = new JButton(
1342            new InsertColumnAfterAction());
1343
1344    /** The button to insert before the selected column. */
1345    private JButton _columnInsertBeforeButton = new JButton(
1346            new InsertColumnBeforeAction());
1347
1348    /** The model for the column span control. */
1349    private ColSpanSpinnerModel _columnSpanSpinnerModel = new ColSpanSpinnerModel();
1350
1351    /** The column span control. */
1352    private JSpinner _columnSpanSpinner = new JSpinner(_columnSpanSpinnerModel);
1353
1354    /** The list of horizontal alignment options. */
1355    private String[] _horizontalAlignmentList = {
1356            LayoutConstraintsManager.DEFAULT, LayoutConstraintsManager.FILL,
1357            LayoutConstraintsManager.CENTER, LayoutConstraintsManager.LEFT,
1358            LayoutConstraintsManager.RIGHT };
1359
1360    /** The horizontal alignment control. */
1361    private JComboBox _horizontalAlignmentCombo = new JComboBox(
1362            _horizontalAlignmentList);
1363
1364    /** The layout frame. */
1365    private RunLayoutFrame _layoutFrame;
1366
1367    /** Model for the left inset control. */
1368    private SpinnerNumberModel _leftInsetSpinnerModel = new SpinnerNumberModel(
1369            0, 0, Integer.MAX_VALUE, 1);
1370
1371    /** The left inset control. */
1372    private JSpinner _leftInsetSpinner = new JSpinner(_leftInsetSpinnerModel);
1373
1374    /** Action to pack the run control window. */
1375    private Action _packAction = new PackAction();
1376
1377    /** Button to pack the run control window. */
1378    //private JButton _packButton = new JButton(_packAction);
1379    /** Properties to ignore and not present to the user. */
1380    private static Set<String> _propertiesToIgnore = new HashSet<String>();
1381    static {
1382        _propertiesToIgnore.add("actionCommand");
1383        _propertiesToIgnore.add("name");
1384        _propertiesToIgnore.add("UIClassID");
1385    }
1386
1387    /** Action to remove a component. */
1388    private Action _removeComponentAction = new RemoveComponentAction();
1389
1390    /** Button to remove a component. */
1391    private JButton _removeComponentButton = new JButton(
1392            _removeComponentAction);
1393
1394    /** Model for the right inset control. */
1395    private SpinnerNumberModel _rightInsetSpinnerModel = new SpinnerNumberModel(
1396            0, 0, Integer.MAX_VALUE, 1);
1397
1398    /** The right inset control. */
1399    private JSpinner _rightInsetSpinner = new JSpinner(_rightInsetSpinnerModel);
1400
1401    /** The button to delete a row. */
1402    private JButton _rowDeleteButton = new JButton(new DeleteRowAction());
1403
1404    /** The button to insert a row after the selected one. */
1405    private JButton _rowInsertAfterButton = new JButton(
1406            new InsertRowAfterAction());
1407
1408    /** The button to insert a row before the selected one. */
1409    private JButton _rowInsertBeforeButton = new JButton(
1410            new InsertRowBeforeAction());
1411
1412    /** The model for the row span control. */
1413    private RowSpanSpinnerModel _rowSpanSpinnerModel = new RowSpanSpinnerModel();
1414
1415    /** The row span control. */
1416    private JSpinner _rowSpanSpinner = new JSpinner(_rowSpanSpinnerModel);
1417
1418    /** Flag to suspend updates. */
1419    private boolean _suspendConstraintControlUpdates = false;
1420
1421    /** The layout table, built in the constructor. */
1422    private LayoutTable _table = null;
1423
1424    /** The data model for the table. */
1425    private GridTableModel _tableModel = new GridTableModel();
1426
1427    /** Model for the top inset control. */
1428    private SpinnerNumberModel _topInsetSpinnerModel = new SpinnerNumberModel(0,
1429            0, Integer.MAX_VALUE, 1);
1430
1431    /** The top inset control. */
1432    private JSpinner _topInsetSpinner = new JSpinner(_topInsetSpinnerModel);
1433
1434    /** The list of vertical alignment options. */
1435    private String[] _verticalAlignmentList = {
1436            LayoutConstraintsManager.DEFAULT, LayoutConstraintsManager.FILL,
1437            LayoutConstraintsManager.CENTER, LayoutConstraintsManager.TOP,
1438            LayoutConstraintsManager.BOTTOM };
1439
1440    /** The vertical alignment control. */
1441    private JComboBox _verticalAlignmentCombo = new JComboBox(
1442            _verticalAlignmentList);
1443}