org.shaman.rpg.editor.dialog.DialogVisualElement.java Source code

Java tutorial

Introduction

Here is the source code for org.shaman.rpg.editor.dialog.DialogVisualElement.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package org.shaman.rpg.editor.dialog;

import java.awt.*;
import java.awt.dnd.DropTarget;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.event.*;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import org.apache.commons.lang3.ArrayUtils;
import org.netbeans.core.spi.multiview.CloseOperationState;
import org.netbeans.core.spi.multiview.MultiViewElement;
import org.netbeans.core.spi.multiview.MultiViewElementCallback;
import org.netbeans.core.spi.multiview.MultiViewFactory;
import org.openide.awt.UndoRedo;
import org.openide.filesystems.FileChangeAdapter;
import org.openide.filesystems.FileEvent;
import org.openide.loaders.DataObject;
import org.openide.util.EditableProperties;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.TopComponent;
import org.shaman.rpg.editor.dialog.panels.*;
import org.shaman.rpg.engine.dialog.model.*;

//TODO: add localization of text (TextTypePanel and QuestionTypePanel) over Property Bundle
@MultiViewElement.Registration(displayName = "#LBL_Dialog_VISUAL", iconBase = "org/shaman/rpg/editor/dialog/speech-bubble.png", mimeType = "text/rpg-dialog+xml", persistenceType = TopComponent.PERSISTENCE_NEVER, preferredID = "DialogVisual", position = 0)
@Messages("LBL_Dialog_VISUAL=Visual")
public final class DialogVisualElement extends JPanel implements MultiViewElement {
    private static final Logger LOG = Logger.getLogger(DialogVisualElement.class.getName());
    private static final int GAP_Y = 2;

    private DialogDataObject obj;
    private JToolBar toolbar = new JToolBar();
    private transient MultiViewElementCallback callback;
    private final UndoRedo.Manager undoRedo;
    private final JumpListener jumpListener;
    private final DropListener dropListener;
    private JComponent contentParent;
    private JPanel contentPanel;
    private List<JComponent> overlayComponents = new ArrayList<>();
    private List<CommandPanel> commandPanels = new ArrayList<>();
    private DialogType dialog;
    private boolean isInsideSetupVisuals = false;

    public DialogVisualElement(Lookup lkp) {
        obj = lkp.lookup(DialogDataObject.class);
        assert obj != null;

        undoRedo = new UndoRedo.Manager();
        undoRedo.addChangeListener(new ChangeListener() {

            @Override
            public void stateChanged(ChangeEvent e) {
                obj.setModified(true);
            }
        });
        initComponents();

        contentPanel = new JPanel() {
            @Override
            public void paint(Graphics g) {
                super.paint(g);
                for (JComponent c : overlayComponents) {
                    c.setBounds(this.getBounds());
                    c.paint(g);
                }
            }
        };
        contentPanel.setLayout(new VerticalFlowLayout(GAP_Y * 2));
        contentPanel.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                if (!e.isConsumed()) {
                    requestFocus();
                    callback.requestActive();
                }
            }
        });

        personsList.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                personsListSelectionEvent();
            }
        });
        removeButton.setEnabled(false);
        Action editNames = new EditListAction();
        ListAction ls = new ListAction(personsList, editNames);
        personsList.getModel().addListDataListener(new ListDataListener() {
            @Override
            public void intervalAdded(ListDataEvent e) {
                updatePersons();
            }

            @Override
            public void intervalRemoved(ListDataEvent e) {
                updatePersons();
            }

            @Override
            public void contentsChanged(ListDataEvent e) {
                updatePersons();
            }
        });

        setupVisuals();

        contentParent = new JPanel();
        contentParent.setLayout(new BorderLayout());
        //      contentPanel.setBounds(contentPanel.getBounds());
        //      contentParent.setBounds(contentPanel.getBounds());
        contentParent.add(contentPanel);
        scrollPane.setViewportView(contentParent);
        jumpListener = new JumpListener(this);
        dropListener = new DropListener(this);
        DropTarget dt = new DropTarget(contentParent, dropListener);
        contentParent.setDropTarget(dt);

        obj.getPrimaryFile().addFileChangeListener(new FileChangeAdapter() {

            @Override
            public void fileChanged(FileEvent fe) {
                refresh();
            }

        });
        obj.addPropertyChangeListener(new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if (DataObject.PROP_MODIFIED.equals(evt.getPropertyName()) && callback != null) {
                    if (obj.isModified()) {
                        callback.updateTitle("<html><b>" + obj.getName() + "</b></html>");
                    } else {
                        callback.updateTitle("<html>" + obj.getName() + "</html>");
                    }
                }
            }
        });
    }

    private void refresh() {
        setupVisuals();
    }

    void preSave() {
        EditableProperties editableProperties;
        try {
            editableProperties = obj.getResourceBundle();
        } catch (IOException ex) {
            LOG.log(Level.WARNING, "unable to load properties file", ex);
            return;
        }
        for (CommandPanel p : commandPanels) {
            p.updateResourceBundle(editableProperties, true);
        }
        try {
            obj.saveResourceBundle(editableProperties);
        } catch (IOException ex) {
            LOG.log(Level.WARNING, "unable to save properties file", ex);
        }
    }

    public String getPropertyFilePrefix() {
        return obj.getPrimaryFile().getName();
    }

    private void setupVisuals() {
        isInsideSetupVisuals = true;
        LOG.info("setup visuals");
        contentPanel.removeAll();
        commandPanels.clear();
        //load dialog
        dialog = obj.load();
        if (dialog == null) {
            contentPanel.add(new JLabel("unable to load the dialog"));
            isInsideSetupVisuals = false;
            return;
        }
        EditableProperties editableProperties = null;
        try {
            editableProperties = obj.getResourceBundle();
        } catch (IOException ex) {
            LOG.log(Level.WARNING, "unable to load properties file", ex);
        }
        //set header
        nameTextField.getDocument().removeUndoableEditListener(undoRedo);
        nameTextField.setText(dialog.getName());
        nameTextField.getDocument().addUndoableEditListener(undoRedo);
        String[] persons = dialog.getPersonList();
        @SuppressWarnings("unchecked")
        DefaultListModel<String> listModel = (DefaultListModel<String>) personsList.getModel();
        listModel.clear();
        listModel.ensureCapacity(persons.length);
        for (String p : persons) {
            listModel.addElement(p);
        }
        personsList.clearSelection();

        //set commands
        for (Command c : dialog.getCommands()) {
            CommandPanel p = null;
            if (c instanceof TextType) {
                p = new TextTypePanel();
            } else if (c instanceof StopType) {
                p = new StopTypePanel();
            } else if (c instanceof SetvarType) {
                p = new SetVarTypePanel();
            } else if (c instanceof AddvarType) {
                p = new AddVarTypePanel();
            } else if (c instanceof IfType) {
                p = new IfTypePanel();
            } else if (c instanceof LabelType) {
                p = new LabelTypePanel();
            } else if (c instanceof GotoType) {
                p = new GotoTypePanel();
            } else if (c instanceof QuestionType) {
                p = new QuestionTypePanel();
            } else {
                LOG.info("unknown command: " + c);
                continue;
            }
            p.setVisualElement(this);
            p.setDialog(dialog);
            p.setCommand(c);
            if (editableProperties != null) {
                p.updateResourceBundle(editableProperties, false);
            }
            p.setUndoRedo(undoRedo);
            contentPanel.add(p);
            commandPanels.add(p);
        }
        repaint();
        recursiveValidate(contentPanel);
        repaint();
        recursiveValidate(contentPanel);
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                recursiveValidate(contentPanel);
                contentPanel.repaint();
            }
        });
        isInsideSetupVisuals = false;
    }

    private void recursiveValidate(Component p) {
        if (p instanceof Container) {
            for (Component c : ((Container) p).getComponents()) {
                recursiveValidate(c);
            }
        }
        p.invalidate();
        p.validate();
    }

    public void deleteCommand(CommandPanel toDelete) {
        int panelIndex = ArrayUtils.indexOf(contentPanel.getComponents(), toDelete);
        int commandIndex = dialog.getCommands().indexOf(toDelete.getCommand());
        if (panelIndex < 0 || commandIndex < 0) {
            return;
        }
        contentPanel.remove(panelIndex);
        dialog.getCommands().remove(commandIndex);
        commandPanels.remove(panelIndex);
        contentPanel.invalidate();
        contentPanel.validate();
        contentPanel.repaint();
        undoRedo.undoableEditHappened(new UndoableEditEvent(dialog,
                new DeleteCommandUndoableEdit(commandIndex, toDelete.getCommand(), panelIndex, toDelete)));
        LOG.info("delete " + toDelete);
    }

    public JComponent getContentPanel() {
        return contentPanel;
    }

    public List<JComponent> getOverlayComponents() {
        return overlayComponents;
    }

    public List<? extends CommandPanel> getCommandPanels() {
        return commandPanels;
    }

    public Collection<DropLocation> collectDropLocations() {
        ArrayList<DropLocation> locations = new ArrayList<>();
        for (int i = 0; i < commandPanels.size(); ++i) {
            CommandPanel c = commandPanels.get(i);
            locations.add(new DropLocationImpl(c.getY() - GAP_Y, i));
            locations.addAll(c.getDropLocations());
        }
        if (commandPanels.isEmpty()) {
            locations.add(new DropLocationImpl(0, -1));
        } else {
            Component c = commandPanels.get(commandPanels.size() - 1);
            locations.add(new DropLocationImpl(c.getY() + c.getHeight() + GAP_Y, -1));
        }
        return locations;
    }

    public Collection<JumpLocation> collectJumpLocations() {
        ArrayList<JumpLocation> locations = new ArrayList<>();
        for (CommandPanel c : commandPanels) {
            locations.addAll(c.getJumpLocations());
        }
        return locations;
    }

    private void updateDialogPersons() {
        for (CommandPanel p : commandPanels) {
            p.setDialog(dialog);
        }
    }

    private void updatePersons() {
        if (isInsideSetupVisuals) {
            return;
        }
        //Extract persons
        @SuppressWarnings("unchecked")
        final DefaultListModel<String> listModel = (DefaultListModel<String>) personsList.getModel();
        final String[] newPersons = new String[listModel.size()];
        listModel.copyInto(newPersons);
        LOG.info("settings persons to " + Arrays.toString(newPersons));
        final String[] oldPersons = dialog.getPersonList();

        //send to dialog
        dialog.setPersonList(newPersons);
        updateDialogPersons();

        // undo/redo
        undoRedo.undoableEditHappened(new UndoableEditEvent(dialog, new AbstractUndoableEdit() {

            @Override
            public void undo() throws CannotUndoException {
                super.undo();
                isInsideSetupVisuals = true;
                listModel.clear();
                listModel.ensureCapacity(oldPersons.length);
                for (String s : oldPersons) {
                    listModel.addElement(s);
                }
                LOG.info("settings persons to " + Arrays.toString(oldPersons));
                dialog.setPersonList(oldPersons);
                updateDialogPersons();
                isInsideSetupVisuals = false;
            }

            @Override
            public void redo() throws CannotRedoException {
                super.redo();
                isInsideSetupVisuals = true;
                listModel.clear();
                listModel.ensureCapacity(newPersons.length);
                for (String s : newPersons) {
                    listModel.addElement(s);
                }
                LOG.info("settings persons to " + Arrays.toString(newPersons));
                dialog.setPersonList(newPersons);
                updateDialogPersons();
                isInsideSetupVisuals = false;
            }

        }));
    }

    @Override
    public String getName() {
        return "DialogVisualElement";
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        scrollPane = new javax.swing.JScrollPane();
        jLabel1 = new javax.swing.JLabel();
        nameTextField = new javax.swing.JTextField();
        jLabel2 = new javax.swing.JLabel();
        jScrollPane1 = new javax.swing.JScrollPane();
        personsList = new javax.swing.JList();
        addButton = new javax.swing.JButton();
        removeButton = new javax.swing.JButton();

        org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle
                .getMessage(DialogVisualElement.class, "DialogVisualElement.jLabel1.text")); // NOI18N

        nameTextField.setText(org.openide.util.NbBundle.getMessage(DialogVisualElement.class,
                "DialogVisualElement.nameTextField.text")); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle
                .getMessage(DialogVisualElement.class, "DialogVisualElement.jLabel2.text")); // NOI18N

        personsList.setModel(new DefaultListModel());
        personsList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
        personsList.setToolTipText(org.openide.util.NbBundle.getMessage(DialogVisualElement.class,
                "DialogVisualElement.personsList.toolTipText")); // NOI18N
        personsList.setLayoutOrientation(javax.swing.JList.VERTICAL_WRAP);
        personsList.setVisibleRowCount(-1);
        jScrollPane1.setViewportView(personsList);

        org.openide.awt.Mnemonics.setLocalizedText(addButton, org.openide.util.NbBundle
                .getMessage(DialogVisualElement.class, "DialogVisualElement.addButton.text")); // NOI18N
        addButton.setToolTipText(org.openide.util.NbBundle.getMessage(DialogVisualElement.class,
                "DialogVisualElement.addButton.toolTipText")); // NOI18N
        addButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addButtonEvent(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(removeButton, org.openide.util.NbBundle
                .getMessage(DialogVisualElement.class, "DialogVisualElement.removeButton.text")); // NOI18N
        removeButton.setToolTipText(org.openide.util.NbBundle.getMessage(DialogVisualElement.class,
                "DialogVisualElement.removeButton.toolTipText")); // NOI18N
        removeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                removeButtonEvent(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(layout.createSequentialGroup().addComponent(jLabel1)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 100,
                                javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED).addComponent(jLabel2)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 217, Short.MAX_VALUE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                                .addComponent(removeButton, javax.swing.GroupLayout.DEFAULT_SIZE,
                                        javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addComponent(addButton, javax.swing.GroupLayout.DEFAULT_SIZE,
                                        javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
                .addComponent(scrollPane));
        layout.setVerticalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                        .addComponent(jLabel1)
                                        .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE,
                                                javax.swing.GroupLayout.DEFAULT_SIZE,
                                                javax.swing.GroupLayout.PREFERRED_SIZE)
                                        .addComponent(jLabel2))
                                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 61,
                                        javax.swing.GroupLayout.PREFERRED_SIZE)
                                .addGroup(layout.createSequentialGroup().addComponent(addButton)
                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                        .addComponent(removeButton)))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(scrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 296, Short.MAX_VALUE)));
    }// </editor-fold>//GEN-END:initComponents

    private void addButtonEvent(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addButtonEvent
        @SuppressWarnings("unchecked")
        DefaultListModel<String> listModel = (DefaultListModel<String>) personsList.getModel();
        listModel.addElement("NoName");
    }//GEN-LAST:event_addButtonEvent

    private void removeButtonEvent(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeButtonEvent
        int index = personsList.getSelectedIndex();
        if (index < 0) {
            return; //This should not happen
        }
        @SuppressWarnings("unchecked")
        DefaultListModel<String> listModel = (DefaultListModel<String>) personsList.getModel();
        listModel.remove(index);
    }//GEN-LAST:event_removeButtonEvent

    private void personsListSelectionEvent() {
        int index = personsList.getSelectedIndex();
        if (index < 0) {
            removeButton.setEnabled(false);
        } else {
            removeButton.setEnabled(true);
        }
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton addButton;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTextField nameTextField;
    private javax.swing.JList personsList;
    private javax.swing.JButton removeButton;
    private javax.swing.JScrollPane scrollPane;

    // End of variables declaration//GEN-END:variables
    @Override
    public JComponent getVisualRepresentation() {
        return this;
    }

    @Override
    public JComponent getToolbarRepresentation() {
        return toolbar;
    }

    @Override
    public Action[] getActions() {
        return new Action[0];
    }

    @Override
    public Lookup getLookup() {
        return obj.getLookup();
    }

    @Override
    public void componentOpened() {
        obj.visualElement = this;
    }

    @Override
    public void componentClosed() {
        obj.visualElement = null;
    }

    @Override
    public void componentShowing() {
        recursiveValidate(contentPanel);
    }

    @Override
    public void componentHidden() {
    }

    @Override
    public void componentActivated() {
        recursiveValidate(contentPanel);
        callback.updateTitle(obj.getName());
    }

    @Override
    public void componentDeactivated() {
    }

    @Override
    public UndoRedo getUndoRedo() {
        return undoRedo;
    }

    @Override
    public void setMultiViewCallback(MultiViewElementCallback callback) {
        this.callback = callback;
    }

    @Override
    public CloseOperationState canCloseElement() {
        if (obj.isModified()) {
            return MultiViewFactory.createUnsafeCloseState("unsavedChanges", obj.saveAction, null);
        } else {
            return CloseOperationState.STATE_OK;
        }
    }

    private class AddCommandUndoableEdit extends AbstractUndoableEdit {
        private int dialogIndex;
        private Command c;
        private int panelIndex;
        private CommandPanel p;

        public AddCommandUndoableEdit(int dialogIndex, Command c, int panelIndex, CommandPanel p) {
            this.dialogIndex = dialogIndex;
            this.panelIndex = panelIndex;
            this.c = c;
            this.p = p;
        }

        @Override
        public void undo() throws CannotUndoException {
            super.undo();
            contentPanel.remove(p);
            commandPanels.remove(p);
            dialog.getCommands().remove(dialogIndex);
            contentPanel.invalidate();
            contentPanel.validate();
            contentPanel.repaint();
        }

        @Override
        public void redo() throws CannotRedoException {
            super.redo();
            LOG.info("redo, index=" + panelIndex + " component count=" + contentPanel.getComponentCount());
            contentPanel.add(p, panelIndex);
            commandPanels.add(panelIndex, p);
            dialog.getCommands().add(dialogIndex, c);
            contentPanel.invalidate();
            contentPanel.validate();
            contentPanel.repaint();
        }

    }

    private class DeleteCommandUndoableEdit extends AbstractUndoableEdit {
        private int dialogIndex;
        private Command c;
        private int panelIndex;
        private CommandPanel p;

        public DeleteCommandUndoableEdit(int dialogIndex, Command c, int panelIndex, CommandPanel p) {
            this.dialogIndex = dialogIndex;
            this.panelIndex = panelIndex;
            this.c = c;
            this.p = p;
        }

        @Override
        public void undo() throws CannotUndoException {
            super.undo();
            contentPanel.add(p, panelIndex);
            commandPanels.add(panelIndex, p);
            dialog.getCommands().add(dialogIndex, c);
            contentPanel.invalidate();
            contentPanel.validate();
            contentPanel.repaint();
        }

        @Override
        public void redo() throws CannotRedoException {
            super.redo();
            contentPanel.remove(p);
            commandPanels.remove(p);
            dialog.getCommands().remove(dialogIndex);
            contentPanel.invalidate();
            contentPanel.validate();
            contentPanel.repaint();
        }

    }

    private class DropLocationImpl implements DropLocation {
        private final int y;
        private final int index;
        private Point pos;

        public DropLocationImpl(int y, int index) {
            this.y = y;
            this.index = index;
            pos = new Point(100, y);
        }

        @Override
        public Point getPosition() {
            return pos;
        }

        @Override
        public boolean canDrop(Object obj) {
            return (obj instanceof CommandPanel);
        }

        @Override
        public void drop(Object obj) {
            CommandPanel p = (CommandPanel) obj;
            if (commandPanels.contains(p)) {
                //D`n`D within the current panels
                //TODO
            } else {
                //Add a new command
                p.setVisualElement(DialogVisualElement.this);
                p.setDialog(dialog);
                Command c = p.createNewCommand();
                p.setCommand(c);
                p.setUndoRedo(undoRedo);
                int dialogIndex;
                int panelIndex;
                if (index == -1) {
                    dialogIndex = dialog.getCommands().size();
                    panelIndex = commandPanels.size();
                    contentPanel.add(p);
                    commandPanels.add(p);
                    dialog.getCommands().add(c);
                } else {
                    CommandPanel next = commandPanels.get(index);
                    panelIndex = index;
                    dialogIndex = dialog.getCommands().indexOf(next.getCommand());
                    contentPanel.add(p, panelIndex);
                    commandPanels.add(panelIndex, p);
                    dialog.getCommands().add(dialogIndex, c);
                }
                contentPanel.invalidate();
                contentPanel.validate();
                contentPanel.repaint();
                undoRedo.undoableEditHappened(
                        new UndoableEditEvent(dialog, new AddCommandUndoableEdit(dialogIndex, c, panelIndex, p)));
                contentParent.requestFocus();
                callback.requestActive();
            }
        }

    }

    /*
     *   Add an Action to a JList that can be invoked either by using
     *  the keyboard or a mouse.
     *
     *  By default the Enter will will be used to invoke the Action
     *  from the keyboard although you can specify and KeyStroke you wish.
     *
     *  A double click with the mouse will invoke the same Action.
     *
     *  The Action can be reset at any time.
     */
    private static class ListAction implements MouseListener {

        private static final KeyStroke ENTER = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);

        private JList list;
        private KeyStroke keyStroke;

        /*
         *   Add an Action to the JList bound by the default KeyStroke
         */
        public ListAction(JList list, Action action) {
            this(list, action, ENTER);
        }

        /*
         *   Add an Action to the JList bound by the specified KeyStroke
         */
        public ListAction(JList list, Action action, KeyStroke keyStroke) {
            this.list = list;
            this.keyStroke = keyStroke;

            //  Add the KeyStroke to the InputMap
            InputMap im = list.getInputMap();
            im.put(keyStroke, keyStroke);

            //  Add the Action to the ActionMap
            setAction(action);

            //  Handle mouse double click
            list.addMouseListener(this);
        }

        /*
         *  Add the Action to the ActionMap
         */
        public void setAction(Action action) {
            list.getActionMap().put(keyStroke, action);
        }

        //  Implement MouseListener interface
        public void mouseClicked(MouseEvent e) {
            if (e.getClickCount() == 2) {
                Action action = list.getActionMap().get(keyStroke);

                if (action != null) {
                    ActionEvent event = new ActionEvent(list, ActionEvent.ACTION_PERFORMED, "");
                    action.actionPerformed(event);
                }
            }
        }

        public void mouseEntered(MouseEvent e) {
        }

        public void mouseExited(MouseEvent e) {
        }

        public void mousePressed(MouseEvent e) {
        }

        public void mouseReleased(MouseEvent e) {
        }
    }

    /*
     *   A simple popup editor for a JList that allows you to change
     *  the value in the selected row.
     *
     *  The default implementation has a few limitations:
     *
     *  a) the JList must be using the DefaultListModel
     *  b) the data in the model is replaced with a String object
     *
     *  If you which to use a different model or different data then you must
     *  extend this class and:
     *
     *  a) invoke the setModelClass(...) method to specify the ListModel you need
     *  b) override the applyValueToModel(...) method to update the model
     */
    private static class EditListAction extends AbstractAction {

        private JList list;

        private JPopupMenu editPopup;
        private JTextField editTextField;
        private Class<?> modelClass;

        public EditListAction() {
            setModelClass(DefaultListModel.class);
        }

        protected void setModelClass(Class modelClass) {
            this.modelClass = modelClass;
        }

        protected void applyValueToModel(String value, ListModel model, int row) {
            DefaultListModel dlm = (DefaultListModel) model;
            dlm.set(row, value);
        }

        /*
         *   Display the popup editor when requested
         */
        public void actionPerformed(ActionEvent e) {
            list = (JList) e.getSource();
            ListModel model = list.getModel();

            if (!modelClass.isAssignableFrom(model.getClass())) {
                return;
            }

            //  Do a lazy creation of the popup editor
            if (editPopup == null) {
                createEditPopup();
            }

            //  Position the popup editor over top of the selected row
            int row = list.getSelectedIndex();
            Rectangle r = list.getCellBounds(row, row);

            editPopup.setPreferredSize(new Dimension(r.width, r.height));
            editPopup.show(list, r.x, r.y);

            //  Prepare the text field for editing
            editTextField.setText(list.getSelectedValue().toString());
            editTextField.selectAll();
            editTextField.requestFocusInWindow();
        }

        /*
         *  Create the popup editor
         */
        private void createEditPopup() {
            //  Use a text field as the editor

            editTextField = new JTextField();
            Border border = UIManager.getBorder("List.focusCellHighlightBorder");
            editTextField.setBorder(border);

            //  Add an Action to the text field to save the new value to the model
            editTextField.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    String value = editTextField.getText();
                    ListModel model = list.getModel();
                    int row = list.getSelectedIndex();
                    applyValueToModel(value, model, row);
                    editPopup.setVisible(false);
                }
            });

            //  Add the editor to the popup
            editPopup = new JPopupMenu();
            editPopup.setBorder(new EmptyBorder(0, 0, 0, 0));
            editPopup.add(editTextField);
        }
    }
}