001/*
002 * Copyright (c) 2004-2007 by Michael Connor. All Rights Reserved.
003 *
004 * Redistribution and use in source and binary forms, with or without
005 * modification, are permitted provided that the following conditions are met:
006 *
007 *  o Redistributions of source code must retain the above copyright notice,
008 *    this list of conditions and the following disclaimer.
009 *
010 *  o Redistributions in binary form must reproduce the above copyright notice,
011 *    this list of conditions and the following disclaimer in the documentation
012 *    and/or other materials provided with the distribution.
013 *
014 *  o Neither the name of FormLayoutBuilder or Michael Connor nor the names of
015 *    its contributors may be used to endorse or promote products derived
016 *    from this software without specific prior written permission.
017 *
018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.mlc.swing.layout;
031
032import java.awt.BorderLayout;
033import java.awt.Color;
034import java.awt.Component;
035import java.awt.Container;
036import java.awt.Font;
037import java.awt.Frame;
038import java.awt.Insets;
039import java.awt.Point;
040import java.awt.Rectangle;
041import java.awt.event.ActionEvent;
042import java.awt.event.ActionListener;
043import java.awt.event.InputEvent;
044import java.awt.event.KeyEvent;
045import java.awt.event.MouseAdapter;
046import java.awt.event.MouseEvent;
047import java.util.ArrayList;
048import java.util.HashMap;
049import java.util.HashSet;
050import java.util.List;
051import java.util.Locale;
052import java.util.Map;
053import java.util.Set;
054
055import javax.swing.AbstractAction;
056import javax.swing.AbstractListModel;
057import javax.swing.AbstractSpinnerModel;
058import javax.swing.Action;
059import javax.swing.ImageIcon;
060import javax.swing.JButton;
061import javax.swing.JComboBox;
062import javax.swing.JDialog;
063import javax.swing.JFrame;
064import javax.swing.JLabel;
065import javax.swing.JList;
066import javax.swing.JOptionPane;
067import javax.swing.JPanel;
068import javax.swing.JScrollPane;
069import javax.swing.JSpinner;
070import javax.swing.JSplitPane;
071import javax.swing.JTable;
072import javax.swing.JTextField;
073import javax.swing.JToolBar;
074import javax.swing.JViewport;
075import javax.swing.KeyStroke;
076import javax.swing.ListCellRenderer;
077import javax.swing.ListModel;
078import javax.swing.ListSelectionModel;
079import javax.swing.SpinnerNumberModel;
080import javax.swing.SwingUtilities;
081import javax.swing.UIManager;
082import javax.swing.border.Border;
083import javax.swing.border.EmptyBorder;
084import javax.swing.event.ChangeEvent;
085import javax.swing.event.ChangeListener;
086import javax.swing.event.ListSelectionEvent;
087import javax.swing.event.ListSelectionListener;
088import javax.swing.table.DefaultTableCellRenderer;
089
090import com.jgoodies.forms.factories.Borders;
091import com.jgoodies.forms.factories.DefaultComponentFactory;
092import com.jgoodies.forms.layout.CellConstraints;
093
094/**
095 * This is the main panel that is used in LayoutFrame serving as the user
096 * interface for the builder. This is a pretty juicy file because there is a lot
097 * going on here. Better docs to come...
098 *
099 * @author Michael Connor mlconnor@yahoo.com
100@version $Id$
101@since Ptolemy II 8.0
102 */
103@SuppressWarnings("serial")
104public class FormEditor extends JPanel {
105    String[] verticalAlignmentList = { LayoutConstraintsManager.DEFAULT,
106            LayoutConstraintsManager.FILL, LayoutConstraintsManager.CENTER,
107            LayoutConstraintsManager.TOP, LayoutConstraintsManager.BOTTOM };
108
109    String[] horizontalAlignmentList = { LayoutConstraintsManager.DEFAULT,
110            LayoutConstraintsManager.FILL, LayoutConstraintsManager.CENTER,
111            LayoutConstraintsManager.LEFT, LayoutConstraintsManager.RIGHT };
112
113    ColSpanSpinnerModel colSpinnerModel = new ColSpanSpinnerModel();
114
115    RowSpanSpinnerModel rowSpinnerModel = new RowSpanSpinnerModel();
116
117    Action newComponentAction = new NewComponentAction();
118
119    Action removeComponentAction = new RemoveComponentAction();
120
121    Action insertRowBeforeAction = new InsertRowBeforeAction();
122
123    Action insertRowAfterAction = new InsertRowAfterAction();
124
125    Action deleteRowAction = new DeleteRowAction();
126
127    Action insertColumnBeforeAction = new InsertColumnBeforeAction();
128
129    Action insertColumnAfterAction = new InsertColumnAfterAction();
130
131    Action deleteColumnAction = new DeleteColumnAction();
132
133    JComboBox verticalAlignmentCombo = new JComboBox(verticalAlignmentList);
134
135    JComboBox horizontalAlignmentCombo = new JComboBox(horizontalAlignmentList);
136
137    JSpinner rowSpanSpinner = new JSpinner(rowSpinnerModel);
138
139    JSpinner columnSpanSpinner = new JSpinner(colSpinnerModel);
140
141    JLabel columnSpanLabel = new JLabel("Column Span");
142
143    JLabel horizontalAlignmentLabel = new JLabel("Horizontal Alignment");
144
145    JLabel rowSpanLabel = new JLabel("Row Span");
146
147    JLabel verticalAlignmentLabel = new JLabel("Vertical Alignment");
148
149    JPanel contentPanel = new JPanel();
150
151    JPanel insetsPanel = new JPanel();
152
153    SpinnerNumberModel rightInsetSpinnerModel = new SpinnerNumberModel(0, 0,
154            Integer.MAX_VALUE, 1);
155
156    SpinnerNumberModel topInsetSpinnerModel = new SpinnerNumberModel(0, 0,
157            Integer.MAX_VALUE, 1);
158
159    SpinnerNumberModel bottomInsetSpinnerModel = new SpinnerNumberModel(0, 0,
160            Integer.MAX_VALUE, 1);
161
162    SpinnerNumberModel leftInsetSpinnerModel = new SpinnerNumberModel(0, 0,
163            Integer.MAX_VALUE, 1);
164
165    JSpinner rightInsetSpinner = new JSpinner(rightInsetSpinnerModel);
166
167    JSpinner bottomInsetSpinner = new JSpinner(bottomInsetSpinnerModel);
168
169    JSpinner leftInsetSpinner = new JSpinner(leftInsetSpinnerModel);
170
171    JSpinner topInsetSpinner = new JSpinner(topInsetSpinnerModel);
172
173    GridTableModel tableModel = new GridTableModel();
174
175    JLabel insetsLabel = new JLabel("Insets");
176
177    JLabel componentsLabel = new JLabel("Components (Drag n Drop)");
178
179    JLabel componentPaletteLabel = new JLabel("Palette (Drag n Drop)");
180
181    ComponentPaletteListModel componentPaletteListModel = new ComponentPaletteListModel();
182
183    // KBR JList componentPalette = new JList(componentPaletteListModel);
184    DndList componentPalette = new DndList(this, componentPaletteListModel);
185
186    JScrollPane componentPaletteScrollPane = new JScrollPane(componentPalette);
187
188    ComponentSelectionListModel componentSelectionListModel = new ComponentSelectionListModel();
189
190    DndList componentList = new DndList(this, componentSelectionListModel);
191
192    JScrollPane componentListScrollPane = new JScrollPane(componentList);
193
194    ComponentListCellRenderer componentListCellRenderer = new ComponentListCellRenderer();
195
196    Component constraintsSeparator = DefaultComponentFactory.getInstance()
197            .createSeparator("Component Constraints");
198
199    Component positionsSeparator = DefaultComponentFactory.getInstance()
200            .createSeparator("Component Positions (Drag n Drop)");
201
202    JPanel componentsPanel = new JPanel();
203
204    JPanel propertiesPanel = new JPanel();
205
206    JSplitPane componentsSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
207            componentsPanel, propertiesPanel);
208
209    JTextField colSpecField = new JTextField();
210
211    JTextField rowSpecField = new JTextField();
212
213    Set<Component> newComponents = new HashSet<Component>();
214
215    LayoutConstraintsManager layoutConstraintsManager;
216
217    JToolBar toolbar = new JToolBar();
218
219    JButton newComponentButton = new JButton(newComponentAction);
220
221    JButton removeComponentButton = new JButton(removeComponentAction);
222
223    JButton columnDeleteButton = new JButton(deleteColumnAction);
224
225    JButton columnInsertAfterButton = new JButton(insertColumnAfterAction);
226
227    JButton columnInsertBeforeButton = new JButton(insertColumnBeforeAction);
228
229    JButton rowDeleteButton = new JButton(deleteRowAction);
230
231    JButton rowInsertBeforeButton = new JButton(insertRowBeforeAction);
232
233    JButton rowInsertAfterButton = new JButton(insertRowAfterAction);
234
235    Container container;
236
237    ContainerLayout containerLayout;
238
239    MultiContainerFrame layoutFrame;
240    DnDTable table = null;
241    JScrollPane tableScrollPane = null;
242    JSplitPane constraintsSplitPane = null;
243
244    Component topComponent = null;
245
246    boolean suspendConstraintControlUpdates = false;
247
248    void setContainer(Container container) {
249        java.awt.LayoutManager layoutManager = container.getLayout();
250        if (!(layoutManager instanceof ContainerLayout)) {
251            throw new RuntimeException(
252                    "Container layout must be of type ContainerLayout");
253        }
254        this.container = container;
255    }
256
257    public FormEditor(MultiContainerFrame layoutFrame, ContainerLayout layout,
258            Container container) {
259        super();
260
261        this.layoutFrame = layoutFrame;
262        table = new DnDTable(layoutFrame, this);
263        tableScrollPane = new JScrollPane(table);
264        constraintsSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
265                tableScrollPane, componentsSplitPane);
266
267        setContainer(container);
268        containerLayout = layout;
269
270        table.setBackground(java.awt.Color.white);
271        table.setSelectionBackground(new Color(220, 220, 255));
272        table.setSelectionForeground(Color.black);
273
274        table.setDefaultRenderer(Object.class,
275                new ConstraintTableCellRenderer());
276        table.setRowHeight(20);
277        table.setModel(tableModel);
278        table.setCellSelectionEnabled(true);
279        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
280
281        // let's put the cursor in the table so the create component
282        // icon is enabled. at least then the user knows what to do...
283        if (tableModel.getRowCount() > 1 && tableModel.getColumnCount() > 1) {
284            // KBR do NOT force visible so constraints row can still be seen
285            setSelectedCell(1, 1, false);
286        }
287
288        componentList.setCellRenderer(componentListCellRenderer);
289
290        // let's setup all of the usability stuff...
291        componentsLabel.setLabelFor(componentListScrollPane);
292        // componentsLabel.setDisplayedMnemonic(KeyEvent.VK_S);
293        verticalAlignmentLabel.setLabelFor(verticalAlignmentCombo);
294        verticalAlignmentLabel.setDisplayedMnemonic(KeyEvent.VK_V);
295        horizontalAlignmentLabel.setLabelFor(horizontalAlignmentCombo);
296        horizontalAlignmentLabel.setDisplayedMnemonic(KeyEvent.VK_H);
297        columnSpanLabel.setLabelFor(columnSpanSpinner);
298        columnSpanLabel.setDisplayedMnemonic(KeyEvent.VK_C);
299        rowSpanLabel.setLabelFor(rowSpanSpinner);
300        rowSpanLabel.setDisplayedMnemonic(KeyEvent.VK_R);
301
302        columnInsertAfterButton
303                .setToolTipText("Insert a column after this column");
304        columnInsertBeforeButton
305                .setToolTipText("Insert a column before this column");
306        columnDeleteButton.setToolTipText("Delete this column");
307        rowInsertBeforeButton.setToolTipText("Insert a row before this row");
308        rowInsertAfterButton.setToolTipText("Insert a row after this row");
309
310        // let's setup the table toolbar
311        toolbar.add(newComponentButton);
312        toolbar.add(removeComponentButton);
313        toolbar.addSeparator();
314        toolbar.add(columnDeleteButton);
315        toolbar.add(columnInsertBeforeButton);
316        toolbar.add(columnInsertAfterButton);
317        toolbar.addSeparator();
318        toolbar.add(rowDeleteButton);
319        toolbar.add(rowInsertBeforeButton);
320        toolbar.add(rowInsertAfterButton);
321
322        setFormComponent(null);
323
324        layoutConstraintsManager = LayoutConstraintsManager
325                .getLayoutConstraintsManager(this.getClass()
326                        .getResourceAsStream("editableLayoutConstraints.xml"));
327
328        layoutConstraintsManager.setLayout("mainLayout", contentPanel);
329        layoutConstraintsManager.setLayout("insetsLayout", insetsPanel);
330        layoutConstraintsManager.setLayout("componentsLayout", componentsPanel);
331        layoutConstraintsManager.setLayout("propertiesLayout", propertiesPanel);
332
333        insetsPanel.add(rightInsetSpinner, "rightInsetSpinner");
334        insetsPanel.add(leftInsetSpinner, "leftInsetSpinner");
335        insetsPanel.add(topInsetSpinner, "topInsetSpinner");
336        insetsPanel.add(bottomInsetSpinner, "bottomInsetSpinner");
337
338        componentsPanel.add(componentListScrollPane, "componentListScrollPane");
339        componentsPanel.add(componentsLabel, "componentsLabel");
340        propertiesPanel.add(componentPaletteScrollPane,
341                "componentPaletteScrollPane");
342        componentPalette.setCellRenderer(new ComponentPaletteListRenderer());
343        propertiesPanel.add(componentPaletteLabel, "componentPaletteLabel");
344
345        contentPanel.add(rowSpanLabel, "rowSpanLabel");
346        contentPanel.add(horizontalAlignmentCombo, "horizontalAlignmentCombo");
347        contentPanel.add(horizontalAlignmentLabel, "horizontalAlignmentLabel");
348        contentPanel.add(rowSpanSpinner, "rowSpanSpinner");
349        contentPanel.add(verticalAlignmentCombo, "verticalAlignmentCombo");
350        contentPanel.add(columnSpanLabel, "columnSpanLabel");
351        contentPanel.add(verticalAlignmentLabel, "verticalAlignmentLabel");
352        contentPanel.add(columnSpanSpinner, "columnSpanSpinner");
353        contentPanel.add(insetsPanel, "insetsPanel");
354        contentPanel.add(insetsLabel, "insetsLabel");
355        contentPanel.add(constraintsSeparator, "constraintsSeparator");
356        contentPanel.add(positionsSeparator, "positionsSeparator");
357        contentPanel.add(toolbar, "toolbar");
358        contentPanel.add(constraintsSplitPane, "constraintsSplitPane");
359
360        constraintsSplitPane.setDividerLocation(605);
361
362        contentPanel.setBorder(Borders.DIALOG_BORDER);
363
364        setLayout(new BorderLayout());
365        add(contentPanel, BorderLayout.CENTER);
366
367        setupListeners();
368    }
369
370    private void setupListeners() {
371
372        verticalAlignmentCombo.addActionListener(new ActionListener() {
373            @Override
374            public void actionPerformed(ActionEvent e) {
375                Component component = table.getSelectedControl();
376                if (component != null) {
377                    CellConstraints cellConstraints = getComponentConstraints(
378                            component);
379                    cellConstraints.vAlign = LayoutConstraintsManager
380                            .getAlignment((String) verticalAlignmentCombo
381                                    .getSelectedItem());
382                    updateLayout(component);
383                }
384            }
385        });
386
387        horizontalAlignmentCombo.addActionListener(new ActionListener() {
388            @Override
389            public void actionPerformed(ActionEvent e) {
390                Component component = table.getSelectedControl();
391                if (component != null) {
392                    CellConstraints cellConstraints = getComponentConstraints(
393                            component);
394                    cellConstraints.hAlign = LayoutConstraintsManager
395                            .getAlignment((String) horizontalAlignmentCombo
396                                    .getSelectedItem());
397                    updateLayout(component);
398                }
399            }
400        });
401
402        topInsetSpinnerModel.addChangeListener(new ChangeListener() {
403            @Override
404            public void stateChanged(ChangeEvent e) {
405                if (!suspendConstraintControlUpdates) {
406                    Component component = table.getSelectedControl();
407                    CellConstraints constraints = getComponentConstraints(
408                            component);
409                    Insets insets = new Insets(
410                            topInsetSpinnerModel.getNumber().intValue(),
411                            constraints.insets.left, constraints.insets.bottom,
412                            constraints.insets.right);
413                    constraints.insets = insets;
414                    updateLayout(component);
415                }
416            }
417        });
418
419        leftInsetSpinnerModel.addChangeListener(new ChangeListener() {
420            @Override
421            public void stateChanged(ChangeEvent e) {
422                if (!suspendConstraintControlUpdates) {
423                    Component component = table.getSelectedControl();
424                    CellConstraints constraints = getComponentConstraints(
425                            component);
426                    Insets insets = new Insets(constraints.insets.top,
427                            leftInsetSpinnerModel.getNumber().intValue(),
428                            constraints.insets.bottom,
429                            constraints.insets.right);
430                    constraints.insets = insets;
431                    updateLayout(component);
432                }
433            }
434        });
435
436        rightInsetSpinnerModel.addChangeListener(new ChangeListener() {
437            @Override
438            public void stateChanged(ChangeEvent e) {
439                if (!suspendConstraintControlUpdates) {
440                    Component component = table.getSelectedControl();
441                    CellConstraints constraints = getComponentConstraints(
442                            component);
443                    Insets insets = new Insets(constraints.insets.top,
444                            constraints.insets.left, constraints.insets.bottom,
445                            rightInsetSpinnerModel.getNumber().intValue());
446                    constraints.insets = insets;
447                    updateLayout(component);
448                }
449            }
450        });
451
452        bottomInsetSpinnerModel.addChangeListener(new ChangeListener() {
453            @Override
454            public void stateChanged(ChangeEvent e) {
455                if (!suspendConstraintControlUpdates) {
456                    Component component = table.getSelectedControl();
457                    CellConstraints constraints = getComponentConstraints(
458                            component);
459                    Insets insets = new Insets(constraints.insets.top,
460                            constraints.insets.left,
461                            bottomInsetSpinnerModel.getNumber().intValue(),
462                            constraints.insets.right);
463                    constraints.insets = insets;
464                    updateLayout(component);
465                }
466            }
467        });
468
469        table.addMouseListener(new MouseAdapter() {
470            @Override
471            public void mouseClicked(MouseEvent e) {
472                if (e.getClickCount() == 2) {
473                    Point p = e.getPoint();
474                    int row = table.rowAtPoint(p);
475                    int col = table.columnAtPoint(p);
476                    // support double-click:
477                    Component component = table.getSelectedControl();
478                    if (component == null) {
479                        return;
480                    }
481
482                    /* invoke componentDef editor on double-clicked control */
483                    String name = getComponentName(component);
484                    ComponentDef componentDef = containerLayout
485                            .getComponentDef(name);
486                    if (componentDef != null) {
487                        editComponent(componentDef, component,
488                                new CellConstraints(col, row));
489                    }
490                }
491            }
492        });
493        componentList.addListSelectionListener(new ListSelectionListener() {
494            @Override
495            public void valueChanged(ListSelectionEvent e) {
496                if (!e.getValueIsAdjusting()) {
497                    /* set selected component as selected, which causes a
498                     * scroll-to-visible in the table */
499                    Component thisComponent = (Component) componentList
500                            .getSelectedValue();
501
502                    CellConstraints constraints = getComponentConstraints(
503                            thisComponent);
504                    if (constraints == null) {
505                        throw new RuntimeException(
506                                "Unable to find constraints for component "
507                                        + thisComponent + " in layout "
508                                        + containerLayout.getName());
509                    }
510                    int col = constraints.gridX;
511                    int row = constraints.gridY;
512
513                    table.changeSelection(row, col, false, false);
514                    topComponent = thisComponent;
515                }
516            }
517        });
518
519        componentList.addMouseListener(new MouseAdapter() {
520            @Override
521            public void mouseClicked(MouseEvent e) {
522                if (e.getClickCount() == 2) {
523                    int index = componentList.locationToIndex(e.getPoint());
524                    if (index == -1) {
525                        return;
526                    }
527
528                    // Get item
529                    ListModel lm = ((DndList) e.getSource()).getModel();
530                    Component thisComponent = (Component) lm
531                            .getElementAt(index);
532
533                    String name = getComponentName(thisComponent);
534                    ComponentDef compDef = containerLayout
535                            .getComponentDef(name);
536                    CellConstraints constraints = getComponentConstraints(
537                            thisComponent);
538                    if (constraints == null) {
539                        throw new RuntimeException(
540                                "Unable to find constraints for component "
541                                        + thisComponent + " in layout "
542                                        + containerLayout.getName());
543                    }
544                    editComponent(compDef, thisComponent, constraints);
545                }
546            }
547        });
548    }
549
550    String getComponentName(Component control) {
551        return containerLayout.getComponentName(control);
552    }
553
554    CellConstraints getComponentConstraints(Component component) {
555        return containerLayout.getComponentConstraints(component);
556    }
557
558    private void specsChanged() {
559        updateLayouts();
560
561        // lets go down the tree
562        Component[] children = container.getComponents();
563        for (Component component : children) {
564            if (component instanceof Container) {
565                ((Container) component).doLayout();
566            }
567        }
568    }
569
570    void updateLayouts() {
571        container.validate();
572        container.doLayout();
573
574        Container parent = container;
575
576        while (parent != null) {
577            parent.validate();
578            parent = parent.getParent();
579        }
580    }
581
582    private void setSelectedCell(int columnIndex, int rowIndex,
583            boolean forceVisible) {
584        // we don't want to update the selection interval if nothing changed...
585        table.getSelectionModel().setSelectionInterval(rowIndex, rowIndex);
586        table.getColumnModel().getSelectionModel()
587                .setSelectionInterval(columnIndex, columnIndex);
588
589        if (forceVisible) {
590            // let's make sure the cell is in the visible range...
591            JViewport viewport = (JViewport) table.getParent();
592            Rectangle rect = table.getCellRect(rowIndex, columnIndex, true);
593            Point pt = viewport.getViewPosition();
594            rect.setLocation(rect.x - pt.x, rect.y - pt.y);
595            viewport.scrollRectToVisible(rect);
596        }
597    }
598
599    private class ComponentSelectionListModel
600            extends javax.swing.AbstractListModel {
601        //    private String selectedName = null;
602
603        List<Component> sortedComponents = new ArrayList<Component>();
604
605        public ComponentSelectionListModel() {
606            super();
607        }
608
609        // Bug: when the user does the following:
610        // 1) drags a new control into place
611        // 2) drags a 2nd new control into place
612        // 3) deletes the 2nd control
613        // 4) drags a new 2nd control into place
614        // we get an array out of bounds exception in getElementAt. The
615        // delete probably failed to update our list.
616        @Override
617        public Object getElementAt(int index) {
618            Component component = sortedComponents.get(index);
619            return component;
620        }
621
622        @Override
623        public int getSize() {
624            sortedComponents = new ArrayList<Component>();
625
626            if (container != null) {
627                Component[] containerComponents = container.getComponents();
628                for (Component insertComponent : containerComponents) {
629                    String insertComponentName = getComponentName(
630                            insertComponent);
631
632                    int insertIndex = 0;
633                    while (insertIndex < sortedComponents.size()
634                            && insertComponentName != null) {
635                        Component testComponent = sortedComponents
636                                .get(insertIndex);
637                        String testName = getComponentName(testComponent);
638                        if (testName != null) {
639                            testName = testName
640                                    .toUpperCase(Locale.getDefault());
641                        }
642                        if (insertComponentName.toUpperCase(Locale.getDefault())
643                                .compareTo(testName) <= 0) {
644                            break;
645                        } else {
646                            insertIndex++;
647                        }
648                    }
649                    sortedComponents.add(insertIndex, insertComponent);
650                }
651            }
652
653            return container != null ? container.getComponentCount() : 0;
654        }
655
656        public void fireDelete() {
657            super.fireContentsChanged(this, 0,
658                    Math.max(0, container.getComponents().length - 1));
659        }
660
661        public void fireInsert() {
662            super.fireContentsChanged(this, 0,
663                    Math.max(0, container.getComponents().length - 1));
664        }
665    }
666
667    class ComponentListCellRenderer extends JLabel implements ListCellRenderer {
668        private static final long serialVersionUID = 1L;
669        Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
670
671        public ComponentListCellRenderer() {
672            super();
673            setOpaque(true);
674        }
675
676        @Override
677        public Component getListCellRendererComponent(JList list, Object value,
678                int index, boolean isSelected, boolean cellHasFocus) {
679            Component component = (Component) value;
680            String name = getComponentName(component);
681
682            setComponentOrientation(list.getComponentOrientation());
683            if (isSelected) {
684                setBackground(list.getSelectionBackground());
685                setForeground(list.getSelectionForeground());
686            } else {
687                setBackground(list.getBackground());
688                setForeground(list.getForeground());
689            }
690
691            setText(name != null ? name : "(Untitled)");
692
693            setEnabled(list.isEnabled());
694            Font font = list.getFont();
695            setFont(font.deriveFont(
696                    component.isVisible() ? Font.PLAIN : Font.BOLD));
697            setBorder(cellHasFocus
698                    ? UIManager.getBorder("List.focusCellHighlightBorder")
699                    : noFocusBorder);
700
701            return this;
702        }
703    }
704
705    private void insertColumn(int column) {
706        for (int index = 0; index < container.getComponentCount(); index++) {
707            Component component = container.getComponent(index);
708            CellConstraints constraints = getComponentConstraints(component);
709            if (constraints.gridX > column) {
710                constraints.gridX++;
711            }
712        }
713
714        try {
715            containerLayout.addColumnSpec(column, "pref");
716            tableModel.fireTableStructureChanged();
717            setSelectedCell(column + 1, 0, true);
718            specsChanged();
719        } catch (IllegalArgumentException iae) {
720            JOptionPane.showMessageDialog(FormEditor.this, iae.getMessage(),
721                    "Invalid Layout", JOptionPane.ERROR_MESSAGE);
722        }
723    }
724
725    private void insertRow(int rowIndex) {
726        for (int index = 0; index < container.getComponentCount(); index++) {
727            Component component = container.getComponent(index);
728            CellConstraints constraints = getComponentConstraints(component);
729            if (constraints.gridY > rowIndex) {
730                constraints.gridY++;
731            }
732        }
733
734        try {
735            containerLayout.addRowSpec(rowIndex, "pref");
736            tableModel.fireTableStructureChanged();
737            setSelectedCell(0, rowIndex + 1, true);
738            specsChanged();
739        } catch (IllegalArgumentException iae) {
740            JOptionPane.showMessageDialog(FormEditor.this, iae.getMessage(),
741                    "Invalid Layout", JOptionPane.ERROR_MESSAGE);
742        }
743    }
744
745    Component formComponent = null;
746
747    // this method will act as the controller for the buttons
748    // and the cell constraints form
749    public void setFormComponent(Component component) {
750        // KBR with this, selecting header row then table body row, doesn't enable
751        // 'add'
752        // if ( component == formComponent )
753        // return;
754        formComponent = component;
755
756        CellConstraints constraints = formComponent != null
757                ? getComponentConstraints(formComponent)
758                : null;
759
760        suspendConstraintControlUpdates = true;
761
762        if (formComponent != null) {
763            rowSpinnerModel.setComponent(formComponent);
764            colSpinnerModel.setComponent(formComponent);
765            verticalAlignmentCombo.setSelectedItem(
766                    LayoutConstraintsManager.getAlignment(constraints.vAlign));
767            horizontalAlignmentCombo.setSelectedItem(
768                    LayoutConstraintsManager.getAlignment(constraints.hAlign));
769            topInsetSpinnerModel
770                    .setValue(Integer.valueOf(constraints.insets.top));
771            bottomInsetSpinnerModel
772                    .setValue(Integer.valueOf(constraints.insets.bottom));
773            rightInsetSpinnerModel
774                    .setValue(Integer.valueOf(constraints.insets.right));
775            leftInsetSpinnerModel
776                    .setValue(Integer.valueOf(constraints.insets.left));
777        }
778
779        verticalAlignmentCombo.setEnabled(constraints != null);
780        horizontalAlignmentCombo.setEnabled(constraints != null);
781        rightInsetSpinner.setEnabled(constraints != null);
782        leftInsetSpinner.setEnabled(constraints != null);
783        topInsetSpinner.setEnabled(constraints != null);
784        bottomInsetSpinner.setEnabled(constraints != null);
785        rowSpanSpinner.setEnabled(constraints != null);
786        columnSpanSpinner.setEnabled(constraints != null);
787
788        int col = table.getSelectedColumn();
789        int row = table.getSelectedRow();
790
791        // Don't allow 'add' on top of existing component
792        newComponentAction
793                .setEnabled(col > 0 && row > 0 && formComponent == null);
794        removeComponentAction.setEnabled(constraints != null);
795        columnDeleteButton.setEnabled(
796                row == 0 && col > 0 && containerLayout.getColumnCount() > 1);
797        columnInsertAfterButton.setEnabled(col > -1);
798        columnInsertBeforeButton.setEnabled(col > 0);
799        rowDeleteButton.setEnabled(
800                col == 0 && row > 0 && containerLayout.getRowCount() > 1);
801        rowInsertBeforeButton.setEnabled(row > 0);
802        rowInsertAfterButton.setEnabled(row > -1);
803
804        suspendConstraintControlUpdates = false;
805    }
806
807    public void updateLayout(Component component) {
808        if (suspendConstraintControlUpdates) {
809            return;
810        }
811
812        CellConstraints constraints = getComponentConstraints(component);
813
814        // we have to update the containerLayout which is the keeper of all
815        // constraints. if we didn't do this then we wouldn't notice any changes
816        // when we went to print everything out.
817        String name = getComponentName(component);
818        // i don't like this direct access thing. this should be changed...
819        containerLayout.setCellConstraints(name, constraints);
820        // updateForm();
821
822        // be careful when modifying the next few lines of code. this
823        // is tricky to get right. this seems to work.
824        container.invalidate();
825        container.doLayout();
826
827        // KBR list (for example) doesn't seem to re-layout properly on drag&drop
828        // without these
829        container.validate();
830        container.repaint();
831
832        if (component instanceof Container) {
833            Container cContainer = (Container) component;
834            cContainer.invalidate();
835            cContainer.doLayout();
836        }
837    }
838
839    private class ColSpanSpinnerModel extends AbstractSpinnerModel {
840        CellConstraints constraints;
841
842        Component component;
843
844        public ColSpanSpinnerModel() {
845        }
846
847        public void setComponent(Component component) {
848            this.component = component;
849            if (component != null) {
850                constraints = getComponentConstraints(component);
851                fireStateChanged();
852            } else {
853                constraints = null;
854            }
855        }
856
857        @Override
858        public Object getNextValue() {
859            if (constraints == null) {
860                return null;
861            }
862            Integer next = constraints.gridX + constraints.gridWidth
863                    - 1 < containerLayout.getColumnCount()
864                            ? Integer.valueOf(constraints.gridWidth + 1)
865                            : null;
866            return next;
867        }
868
869        @Override
870        public Object getPreviousValue() {
871            if (constraints == null) {
872                return null;
873            } else {
874                Integer previous = constraints.gridWidth > 1
875                        ? Integer.valueOf(constraints.gridWidth - 1)
876                        : null;
877                return previous;
878            }
879        }
880
881        @Override
882        public Object getValue() {
883            if (constraints == null) {
884                return "";
885            } else {
886                return Integer.valueOf(constraints.gridWidth);
887            }
888        }
889
890        @Override
891        public void setValue(Object value) {
892            if (constraints == null || value == null) {
893                return;
894            }
895
896            //      Number val = (Number) value;
897            constraints.gridWidth = ((Number) value).intValue();
898            super.fireStateChanged();
899            updateLayout(component);
900
901            // firing table data changed messes up the
902            // selection so we'll get it and then restore it...
903            int col = table.getSelectedColumn();
904            int row = table.getSelectedRow();
905            tableModel.fireTableDataChanged();
906            setSelectedCell(col, row, true);
907        }
908    }
909
910    private class RowSpanSpinnerModel extends AbstractSpinnerModel {
911        CellConstraints constraints;
912
913        Component component;
914
915        public RowSpanSpinnerModel() {
916        }
917
918        public void setComponent(Component component) {
919            this.component = component;
920            if (component != null) {
921                constraints = getComponentConstraints(component);
922                fireStateChanged();
923            } else {
924                constraints = null;
925            }
926        }
927
928        @Override
929        public Object getNextValue() {
930            if (constraints == null) {
931                return null;
932            } else {
933                Integer next = constraints.gridY + constraints.gridHeight
934                        - 1 < containerLayout.getRowCount()
935                                ? Integer.valueOf(constraints.gridHeight + 1)
936                                : null;
937                return next;
938            }
939        }
940
941        @Override
942        public Object getPreviousValue() {
943            if (constraints == null) {
944                return null;
945            } else {
946                Integer previous = constraints.gridHeight > 1
947                        ? Integer.valueOf(constraints.gridHeight - 1)
948                        : null;
949                return previous;
950            }
951        }
952
953        @Override
954        public Object getValue() {
955            if (constraints == null) {
956                return "";
957            } else {
958                return Integer.valueOf(constraints.gridHeight);
959            }
960        }
961
962        @Override
963        public void setValue(Object value) {
964            if (constraints == null || value == null) {
965                return;
966            }
967            //      Number val = (Number) value;
968            constraints.gridHeight = ((Number) value).intValue();
969            super.fireStateChanged();
970            updateLayout(component);
971
972            // firing table data changed messes up the
973            // selection so we'll get it and then restore it...
974            int col = table.getSelectedColumn();
975            int row = table.getSelectedRow();
976            tableModel.fireTableDataChanged();
977            setSelectedCell(col, row, true);
978        }
979    }
980
981    /**
982     * Returns true if the named component was created by hand in this session
983     */
984    public boolean isNewComponent(Component component) {
985        return newComponents.contains(component);
986    }
987
988    private class NewComponentDialog0 extends JDialog {
989        JTextField nameField = new JTextField();
990
991        JLabel nameLabel = new JLabel("Name");
992
993        JLabel typeLabel = new JLabel("Type");
994
995        JButton okButton = new JButton("OK");
996
997        JButton cancelButton = new JButton("Cancel");
998
999        JComboBox typeCombo = new JComboBox();
1000
1001        PropertyTableModel propertyTableModel = new PropertyTableModel();
1002
1003        JTable propertyTable = new JTable();
1004
1005        JScrollPane propertyScrollPane = new JScrollPane(propertyTable);
1006
1007        boolean wasSuccessful = false;
1008
1009        Component component = null;
1010
1011        Map<String, Object> controlProperties = new HashMap<String, Object>();
1012
1013        public NewComponentDialog0(Frame owner) throws Exception {
1014            super(owner, "Add Component", true);
1015
1016            okButton.setMnemonic(KeyEvent.VK_O);
1017            cancelButton.setMnemonic(KeyEvent.VK_C);
1018            nameLabel.setDisplayedMnemonic(KeyEvent.VK_N);
1019            typeLabel.setDisplayedMnemonic(KeyEvent.VK_T);
1020            nameLabel.setLabelFor(nameField);
1021            typeLabel.setLabelFor(typeCombo);
1022
1023            //      FormLayout layout = new FormLayout("right:max(40dlu;pref), 3dlu, 130dlu",
1024            //          "");
1025            JPanel content = new JPanel();
1026            content.setBorder(Borders.DIALOG_BORDER);
1027            layoutConstraintsManager.setLayout("newComponentContent", content);
1028            getContentPane().setLayout(new BorderLayout());
1029            getContentPane().add(content, BorderLayout.CENTER);
1030
1031            content.add(typeCombo, "typeCombo");
1032            content.add(nameField, "nameField");
1033            content.add(typeLabel, "typeLabel");
1034            content.add(nameLabel, "nameLabel");
1035            content.add(propertyScrollPane, "propertyScrollPane");
1036            content.add(
1037                    com.jgoodies.forms.factories.ButtonBarFactory
1038                            .buildRightAlignedBar(
1039                                    new JButton[] { okButton, cancelButton }),
1040                    "buttonPanel");
1041
1042            propertyTable.putClientProperty("terminateEditOnFocusLost",
1043                    Boolean.TRUE);
1044            propertyTable.setModel(propertyTableModel);
1045
1046            pack();
1047
1048            typeCombo.addItem(new DefaultComponentBuilder(
1049                    javax.swing.JButton.class, new String[] { "text" }));
1050            typeCombo.addItem(new DefaultComponentBuilder(
1051                    javax.swing.JCheckBox.class, new String[] { "text" }));
1052            typeCombo.addItem(
1053                    new DefaultComponentBuilder(javax.swing.JComboBox.class));
1054            typeCombo.addItem(new DefaultComponentBuilder(
1055                    javax.swing.JLabel.class, new String[] { "text" }));
1056            typeCombo.addItem(new JListComponentBuilder());
1057            typeCombo.addItem(
1058                    new DefaultComponentBuilder(javax.swing.JPanel.class));
1059            typeCombo.addItem(new DefaultComponentBuilder(
1060                    javax.swing.JPasswordField.class));
1061            typeCombo.addItem(new DefaultComponentBuilder(
1062                    javax.swing.JRadioButton.class, new String[] { "text" }));
1063            typeCombo.addItem(
1064                    new DefaultComponentBuilder(javax.swing.JScrollPane.class));
1065            typeCombo.addItem(
1066                    new DefaultComponentBuilder(javax.swing.JSpinner.class));
1067            typeCombo.addItem(new JTableComponentBuilder());
1068            typeCombo.addItem(
1069                    new DefaultComponentBuilder(javax.swing.JTextArea.class));
1070            typeCombo.addItem(
1071                    new DefaultComponentBuilder(javax.swing.JTextField.class));
1072            typeCombo.addItem(new JToolBarComponentBuilder());
1073            typeCombo.addItem(new JTreeComponentBuilder());
1074            typeCombo.addItem(new SeparatorComponentBuilder());
1075            typeCombo.addItem(new ButtonBarComponentBuilder());
1076
1077            typeCombo.addActionListener(new ActionListener() {
1078                @Override
1079                public void actionPerformed(ActionEvent e) {
1080                    controlProperties = new HashMap<String, Object>();
1081                    propertyTableModel.fireTableDataChanged();
1082                }
1083            });
1084
1085            okButton.addActionListener(new ActionListener() {
1086                @Override
1087                public void actionPerformed(ActionEvent e) {
1088
1089                    try {
1090                        if (nameField.getText().trim().length() == 0) {
1091                            throw new Exception("The name field is required");
1092                        }
1093
1094                        //            int currentCol = propertyTable.getSelectedColumn();
1095                        //            int currentRow = propertyTable.getSelectedRow();
1096
1097                        ComponentBuilder builder = (ComponentBuilder) typeCombo
1098                                .getSelectedItem();
1099                        component = builder.getInstance(controlProperties);
1100
1101                        wasSuccessful = true;
1102                        dispose();
1103
1104                    } catch (Exception exception) {
1105                        exception.printStackTrace();
1106                        wasSuccessful = false;
1107                        JOptionPane.showMessageDialog(null,
1108                                exception.getMessage(),
1109                                "Error Creating Component",
1110                                JOptionPane.ERROR_MESSAGE);
1111                    }
1112                }
1113            });
1114
1115            cancelButton.addActionListener(new ActionListener() {
1116                @Override
1117                public void actionPerformed(ActionEvent e) {
1118                    wasSuccessful = false;
1119                    dispose();
1120                }
1121            });
1122        }
1123
1124        public boolean wasSuccessful() {
1125            return wasSuccessful;
1126        }
1127
1128        public String getComponentName() {
1129            return nameField.getText();
1130        }
1131
1132        //        public String getComponentDeclaration() {
1133        //            ComponentBuilder builder = (ComponentBuilder) typeCombo
1134        //                    .getSelectedItem();
1135        //            return builder
1136        //                    .getDeclaration(getComponentName(), controlProperties);
1137        //        }
1138
1139        public boolean isUsingLayoutComponent() {
1140            ComponentBuilder builder = (ComponentBuilder) typeCombo
1141                    .getSelectedItem();
1142            return builder.isComponentALayoutContainer();
1143        }
1144
1145        public Component getComponent() {
1146            return component;
1147        }
1148
1149        public ComponentDef getComponentDef() {
1150            ComponentBuilder builder = (ComponentBuilder) typeCombo
1151                    .getSelectedItem();
1152            return builder.getComponentDef(getComponentName(),
1153                    controlProperties);
1154        }
1155
1156        private class PropertyTableModel
1157                extends javax.swing.table.AbstractTableModel {
1158            @Override
1159            public int getColumnCount() {
1160                return 2;
1161            }
1162
1163            @Override
1164            public int getRowCount() {
1165                ComponentBuilder builder = (ComponentBuilder) typeCombo
1166                        .getSelectedItem();
1167                return builder != null ? builder.getProperties().size() : 0;
1168            }
1169
1170            @Override
1171            public boolean isCellEditable(int row, int col) {
1172                return col == 1;
1173            }
1174
1175            @Override
1176            public String getColumnName(int col) {
1177                return col == 0 ? "Property" : "Value";
1178            }
1179
1180            @Override
1181            public void setValueAt(Object aValue, int row, int col) {
1182                ComponentBuilder builder = (ComponentBuilder) typeCombo
1183                        .getSelectedItem();
1184                List<BeanProperty> properties = builder.getProperties();
1185                BeanProperty property = properties.get(row);
1186                controlProperties.put(property.getName(), aValue);
1187            }
1188
1189            @Override
1190            public Object getValueAt(int rowIndex, int columnIndex) {
1191                ComponentBuilder builder = (ComponentBuilder) typeCombo
1192                        .getSelectedItem();
1193                List<BeanProperty> properties = builder.getProperties();
1194                BeanProperty property = properties.get(rowIndex);
1195
1196                return columnIndex == 0 ? property.getName()
1197                        : controlProperties.get(property.getName());
1198            }
1199
1200            //            public Component getComponent() throws Exception {
1201            //                ComponentBuilder builder = (ComponentBuilder) typeCombo
1202            //                        .getSelectedItem();
1203            //                Component instance = builder.getInstance(controlProperties);
1204            //                return instance;
1205            //            }
1206        }
1207    }
1208
1209    private class ConstraintTableCellRenderer extends DefaultTableCellRenderer {
1210        @Override
1211        public Component getTableCellRendererComponent(JTable table,
1212                Object value, boolean isSelected, boolean hasFocus, int row,
1213                int column) {
1214            String stringValue = null;
1215            if (value != null) {
1216                if (value instanceof Component) {
1217                    String name = getComponentName((Component) value);
1218                    stringValue = name == null ? "(Untitled)" : name;
1219                } else {
1220                    // in this case it's a row or col header
1221                    stringValue = (String) value;
1222                }
1223            }
1224
1225            return super.getTableCellRendererComponent(table, stringValue,
1226                    isSelected, hasFocus, row, column);
1227        }
1228    }
1229
1230    private class GridTableModel extends javax.swing.table.AbstractTableModel {
1231
1232        @Override
1233        public int getColumnCount() {
1234            return containerLayout != null
1235                    ? containerLayout.getColumnCount() + 1
1236                    : 1;
1237        }
1238
1239        @Override
1240        public int getRowCount() {
1241            return containerLayout != null ? containerLayout.getRowCount() + 1
1242                    : 1;
1243        }
1244
1245        @Override
1246        public boolean isCellEditable(int row, int col) {
1247            return (row == 0 || col == 0) && !(row == 0 && col == 0);
1248        }
1249
1250        @Override
1251        public String getColumnName(int col) {
1252            return col == 0 ? "*" : "" + col;
1253        }
1254
1255        @Override
1256        public void setValueAt(Object aValue, int row, int col) {
1257            String value = (String) aValue;
1258            if (row == 0) // a column was changed
1259            {
1260                try {
1261                    containerLayout.setColumnSpec(col - 1, value);
1262                    specsChanged();
1263                } catch (IllegalArgumentException iae) {
1264                    JOptionPane.showMessageDialog(FormEditor.this,
1265                            iae.getMessage(), "Invalid Layout",
1266                            JOptionPane.ERROR_MESSAGE);
1267                }
1268            } else if (col == 0) {
1269                try {
1270                    containerLayout.setRowSpec(row - 1, value);
1271                    specsChanged();
1272                } catch (Exception e) {
1273                    JOptionPane.showMessageDialog(null, e.getMessage(),
1274                            "Invalid row specification",
1275                            JOptionPane.ERROR_MESSAGE);
1276                }
1277            }
1278        }
1279
1280        @Override
1281        public Object getValueAt(int rowIndex, int columnIndex) {
1282            if (rowIndex == 0 && columnIndex == 0) {
1283                return null;
1284            }
1285            if (rowIndex == 0) {
1286                return containerLayout.getColumnSpec(columnIndex - 1);
1287            }
1288            if (columnIndex == 0) {
1289                return containerLayout.getRowSpec(rowIndex - 1);
1290            }
1291
1292            Component component = null;
1293
1294            for (int index = 0; index < container
1295                    .getComponentCount(); index++) {
1296                Component thisComponent = container.getComponent(index);
1297                // we don't want to show invisible components. we
1298                // have decided to make components that are added without
1299                // constraints invisible until they are dropped onto the form.
1300                // this is our way of hiding them.
1301                if (thisComponent.isVisible()) {
1302                    CellConstraints constraints = getComponentConstraints(
1303                            thisComponent);
1304                    if (constraints == null) {
1305                        throw new RuntimeException(
1306                                "Unable to find constraints for component "
1307                                        + thisComponent + " in layout "
1308                                        + containerLayout.getName());
1309                    }
1310                    if (columnIndex >= constraints.gridX
1311                            && columnIndex < constraints.gridX
1312                                    + constraints.gridWidth
1313                            && rowIndex >= constraints.gridY
1314                            && rowIndex < constraints.gridY
1315                                    + constraints.gridHeight) {
1316                        component = thisComponent;
1317                        if (component == topComponent) {
1318                            break;
1319                        }
1320                    }
1321                }
1322            }
1323
1324            return component;
1325        }
1326    }
1327
1328    private class RemoveComponentAction extends AbstractAction {
1329        public RemoveComponentAction() {
1330            super();
1331            putValue(Action.SHORT_DESCRIPTION, "Remove the component (Alt+D)");
1332            putValue(Action.LONG_DESCRIPTION, "Remove the component (Alt+D)");
1333            putValue(Action.SMALL_ICON, new ImageIcon(
1334                    FormEditor.class.getResource("Remove24.gif")));
1335            putValue(Action.ACCELERATOR_KEY, KeyStroke
1336                    .getKeyStroke(KeyEvent.VK_D, InputEvent.CTRL_MASK));
1337        }
1338
1339        @Override
1340        public void actionPerformed(ActionEvent e) {
1341            Component selectedControl = table.getSelectedControl();
1342            String controlName = getComponentName(selectedControl);
1343            container.remove(selectedControl);
1344            tableModel.fireTableDataChanged();
1345
1346            if (selectedControl instanceof Container
1347                    && layoutFrame.hasContainer(controlName)) {
1348                layoutFrame.removeContainer(controlName);
1349            }
1350            container.doLayout();
1351            container.repaint();
1352            componentSelectionListModel.fireDelete();
1353
1354            setFormComponent(null);
1355            table.requestFocus();
1356        }
1357    }
1358
1359    private class NewComponentAction extends AbstractAction {
1360        public NewComponentAction() {
1361            super();
1362            putValue(Action.SHORT_DESCRIPTION,
1363                    "Create a new component (Alt+N)");
1364            putValue(Action.LONG_DESCRIPTION, "Create a new component (Alt+N)");
1365            putValue(Action.SMALL_ICON,
1366                    new ImageIcon(FormEditor.class.getResource("New24.gif")));
1367            putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_N));
1368        }
1369
1370        @Override
1371        public void actionPerformed(ActionEvent e) {
1372            Frame frame = (Frame) SwingUtilities.getAncestorOfClass(Frame.class,
1373                    FormEditor.this);
1374
1375            NewComponentDialog0 newComponentDialog = null;
1376            int columnIndex = table.getSelectedColumn();
1377            int rowIndex = table.getSelectedRow();
1378
1379            try {
1380                newComponentDialog = new NewComponentDialog0(frame);
1381                newComponentDialog.setVisible(true);
1382            } catch (Exception ex) {
1383                ex.printStackTrace();
1384            }
1385
1386            if (newComponentDialog.wasSuccessful()) {
1387                String controlName = newComponentDialog.getComponentName();
1388                Component newControl = newComponentDialog.getComponent();
1389                ComponentDef newCD = newComponentDialog.getComponentDef();
1390
1391                if (containerLayout.getCellConstraints(controlName) != null) {
1392                    JOptionPane.showMessageDialog(FormEditor.this,
1393                            "A component named '" + controlName
1394                                    + "' already exists",
1395                            "Error", JOptionPane.ERROR_MESSAGE);
1396                } else {
1397                    // the best way to add this control is to setup the constraints
1398                    // in the map of name->constraints and then add it to the container.
1399                    // this layout manager will intercept it, find the constraints and
1400                    // then
1401                    // set it up properly in the table and assign the maps.
1402                    CellConstraints newConstraints = new CellConstraints(
1403                            columnIndex, rowIndex);
1404                    if (newCD != null) {
1405                        containerLayout.addComponent(controlName, newCD,
1406                                newConstraints);
1407                    }
1408                    containerLayout.getCellConstraints().put(controlName,
1409                            newConstraints);
1410                    container.add(newControl, controlName);
1411
1412                    // we need to keep track of the new components added so we can present
1413                    // some code to the user to add back into their module.
1414                    // newComponents.put(controlName,
1415                    // newComponentDialog.getComponentDeclaration());
1416
1417                    // if it's a panel, let's add it to the LayoutFrame so it can be
1418                    // manipulated too...
1419                    if (newComponentDialog.isUsingLayoutComponent()) {
1420                        Container newContainer = (Container) newControl;
1421                        layoutFrame.addContainer(controlName, newContainer);
1422                    }
1423
1424                    componentSelectionListModel.fireInsert();
1425
1426                    table.changeSelection(newConstraints.gridY,
1427                            newConstraints.gridX, false, false);
1428                    updateLayout(newControl); // relayout preview
1429                    updateLayouts(); // relayout all panels
1430
1431                    newComponents.add(newControl); // we're not generating code unless this happens
1432                }
1433            }
1434
1435            table.requestFocus();
1436        }
1437    }
1438
1439    private class InsertRowBeforeAction extends AbstractAction {
1440        public InsertRowBeforeAction() {
1441            super();
1442            putValue(Action.SHORT_DESCRIPTION,
1443                    "Inserts a row before the selected row");
1444            putValue(Action.LONG_DESCRIPTION,
1445                    "Inserts a row before the selected row");
1446            putValue(Action.SMALL_ICON, new ImageIcon(
1447                    FormEditor.class.getResource("RowInsertBefore24.gif")));
1448            putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_I));
1449        }
1450
1451        @Override
1452        public void actionPerformed(ActionEvent e) {
1453            int row = table.getSelectedRow();
1454            insertRow(row - 1);
1455            table.requestFocus();
1456        }
1457    }
1458
1459    private class InsertRowAfterAction extends AbstractAction {
1460        public InsertRowAfterAction() {
1461            super();
1462            putValue(Action.SHORT_DESCRIPTION,
1463                    "Inserts a row after the selected row");
1464            putValue(Action.LONG_DESCRIPTION,
1465                    "Inserts a row after the selected row");
1466            putValue(Action.SMALL_ICON, new ImageIcon(
1467                    FormEditor.class.getResource("RowInsertAfter24.gif")));
1468            putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_O));
1469        }
1470
1471        @Override
1472        public void actionPerformed(ActionEvent e) {
1473            int row = table.getSelectedRow();
1474            insertRow(row);
1475            table.requestFocus();
1476        }
1477    }
1478
1479    private class InsertColumnBeforeAction extends AbstractAction {
1480        public InsertColumnBeforeAction() {
1481            super();
1482            putValue(Action.SHORT_DESCRIPTION,
1483                    "Inserts a column before the selected column");
1484            putValue(Action.LONG_DESCRIPTION,
1485                    "Inserts a column before the selected column");
1486            putValue(Action.SMALL_ICON, new ImageIcon(
1487                    FormEditor.class.getResource("ColumnInsertBefore24.gif")));
1488            putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_K));
1489        }
1490
1491        @Override
1492        public void actionPerformed(ActionEvent e) {
1493            int column = table.getSelectedColumn();
1494            insertColumn(column - 1);
1495            table.requestFocus();
1496        }
1497    }
1498
1499    private class InsertColumnAfterAction extends AbstractAction {
1500        public InsertColumnAfterAction() {
1501            super();
1502            putValue(Action.SHORT_DESCRIPTION,
1503                    "Inserts a column after the selected column");
1504            putValue(Action.LONG_DESCRIPTION,
1505                    "Inserts a column after the selected column");
1506            putValue(Action.SMALL_ICON, new ImageIcon(
1507                    FormEditor.class.getResource("ColumnInsertAfter24.gif")));
1508            putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_L));
1509        }
1510
1511        @Override
1512        public void actionPerformed(ActionEvent e) {
1513            int column = table.getSelectedColumn();
1514            insertColumn(column);
1515            table.requestFocus();
1516        }
1517    }
1518
1519    private class DeleteRowAction extends AbstractAction {
1520        public DeleteRowAction() {
1521            super();
1522            putValue(Action.SHORT_DESCRIPTION, "Deletes the selected row");
1523            putValue(Action.LONG_DESCRIPTION, "Deletes the selected row");
1524            putValue(Action.SMALL_ICON, new ImageIcon(
1525                    FormEditor.class.getResource("RowDelete24.gif")));
1526            putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_D));
1527        }
1528
1529        @Override
1530        public void actionPerformed(ActionEvent e) {
1531            int rowIndex = table.getSelectedRow();
1532
1533            // move any components that are on the deleted row or
1534            // above it down one
1535            for (int index = 0; index < container
1536                    .getComponentCount(); index++) {
1537                Component component = container.getComponent(index);
1538                CellConstraints constraints = getComponentConstraints(
1539                        component);
1540
1541                if (constraints.gridY >= rowIndex && constraints.gridY > 1) {
1542                    constraints.gridY--;
1543                } else {
1544                    // if the row deleted was within the span of the component and the
1545                    // component
1546                    // is bigger than one cell...
1547                    if (constraints.gridY + constraints.gridHeight
1548                            - 1 >= rowIndex && constraints.gridHeight > 1) {
1549                        constraints.gridHeight--;
1550                    }
1551                }
1552            }
1553
1554            containerLayout.removeRowSpec(rowIndex - 1);
1555
1556            tableModel.fireTableRowsDeleted(rowIndex, rowIndex);
1557            table.changeSelection(
1558                    Math.min(rowIndex, containerLayout.getRowCount()), 0, false,
1559                    false);
1560            specsChanged();
1561            table.requestFocus();
1562        }
1563    }
1564
1565    private class DeleteColumnAction extends AbstractAction {
1566        public DeleteColumnAction() {
1567            super();
1568            putValue(Action.SHORT_DESCRIPTION, "Deletes the selected column");
1569            putValue(Action.LONG_DESCRIPTION, "Deletes the selected column");
1570            putValue(Action.SMALL_ICON, new ImageIcon(
1571                    FormEditor.class.getResource("ColumnDelete24.gif")));
1572            putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_C));
1573        }
1574
1575        @Override
1576        public void actionPerformed(ActionEvent e) {
1577            int columnIndex = table.getSelectedColumn();
1578
1579            for (int index = 0; index < container
1580                    .getComponentCount(); index++) {
1581                Component component = container.getComponent(index);
1582                CellConstraints constraints = getComponentConstraints(
1583                        component);
1584
1585                if (constraints.gridX >= columnIndex && constraints.gridX > 1) {
1586                    constraints.gridX--;
1587                } else {
1588                    // if the col deleted was within the span of the component and the
1589                    // component
1590                    // is bigger than one cell...
1591                    if (constraints.gridX + constraints.gridWidth
1592                            - 1 >= columnIndex && constraints.gridWidth > 1) {
1593                        constraints.gridWidth--;
1594                    }
1595                }
1596            }
1597
1598            containerLayout.removeColumnSpec(columnIndex - 1);
1599            tableModel.fireTableStructureChanged();
1600            table.changeSelection(0,
1601                    Math.min(columnIndex, containerLayout.getColumnCount()),
1602                    false, false);
1603            specsChanged();
1604            table.requestFocus();
1605        }
1606    }
1607
1608    class ComponentPaletteListModel extends AbstractListModel {
1609        List<ComponentDef> componentDefs = ComponentDef.createComponentDefs();
1610
1611        @Override
1612        public int getSize() {
1613            return componentDefs.size();
1614        }
1615
1616        @Override
1617        public Object getElementAt(int index) {
1618            return componentDefs.get(index);
1619        }
1620    }
1621
1622    class ComponentPaletteListRenderer extends JLabel
1623            implements ListCellRenderer {
1624        public ComponentPaletteListRenderer() {
1625            setOpaque(true);
1626        }
1627
1628        /*
1629         * This method finds the image and text corresponding to the selected value
1630         * and returns the label, set up to display the text and image.
1631         */
1632        @Override
1633        public Component getListCellRendererComponent(JList list, Object value,
1634                int index, boolean isSelected, boolean cellHasFocus) {
1635            ComponentDef componentDef = (ComponentDef) value;
1636            setIcon(componentDef.icon != null ? componentDef.icon : null);
1637            setText(componentDef.name);
1638            if (isSelected) {
1639                setBackground(list.getSelectionBackground());
1640                setForeground(list.getSelectionForeground());
1641            } else {
1642                setBackground(list.getBackground());
1643                setForeground(list.getForeground());
1644            }
1645            return this;
1646        }
1647    }
1648
1649    public void updateList() {
1650        componentSelectionListModel.fireInsert();
1651    }
1652
1653    public String uniqueName(String name, Component comp) {
1654        // insure the component name doesn't collide
1655
1656        String newname = name;
1657        int suffix = 1;
1658        for (;;) {
1659            Component temp = containerLayout.getComponentByName(newname);
1660            // no such component, or found component was ourself, stop the search
1661            if (temp == null || temp == comp) {
1662                break; // exitloop
1663            }
1664
1665            newname = name + "_" + suffix;
1666            suffix++;
1667        }
1668        return newname;
1669    }
1670
1671    public boolean editComponent(ComponentDef componentDef, Component component,
1672            CellConstraints cellConstraints) {
1673        if (componentDef.isContainer) {
1674            return false; //punt!
1675        }
1676
1677        // get original name for remove
1678        String name = getComponentName(component);
1679
1680        NewComponentDialog dlg = NewComponentDialog
1681                .editDialog((JFrame) layoutFrame, componentDef);
1682        if (!dlg.succeeded()) {
1683            return false;
1684        }
1685
1686        componentDef.name = uniqueName(dlg.getComponentName(), component);
1687        String newname = componentDef.name;
1688
1689        Component newcomponent = dlg.getInstance();
1690        containerLayout.removeLayoutComponent(component);
1691        containerLayout.addComponent(newname, componentDef, cellConstraints);
1692
1693        container.remove(component);
1694        container.add(newcomponent, newname);
1695
1696        newComponents.remove(component);
1697        newComponents.add(newcomponent);
1698
1699        if (componentDef.isContainer) {
1700            /** @todo losing components INSIDE the container!! */
1701            //        layoutFrame.replaceContainer(name, newName, (Container) newcomponent);
1702            layoutFrame.removeContainer(name);
1703            layoutFrame.addContainer(newname, (Container) newcomponent);
1704        }
1705
1706        updateLayout(newcomponent);
1707        updateList();
1708        updateLayouts();
1709        repaint();
1710        return true;
1711    }
1712}