Package org.jdesktop.wonderland.modules.celleditor.client

Source Code of org.jdesktop.wonderland.modules.celleditor.client.CellPropertiesJFrame$CellDropTargetListener

/**
* Copyright (c) 2014, WonderBuilders, Inc., All Rights Reserved
*/

/**
* Open Wonderland
*
* Copyright (c) 2010 - 2012, Open Wonderland Foundation, All Rights Reserved
*
* Redistributions in source code form must reproduce the above
* copyright and this condition.
*
* The contents of this file are subject to the GNU General Public
* License, Version 2 (the "License"); you may not use this file
* except in compliance with the License. A copy of the License is
* available at http://www.opensource.org/licenses/gpl-license.php.
*
* The Open Wonderland Foundation designates this particular file as
* subject to the "Classpath" exception as provided by the Open Wonderland
* Foundation in the License file that accompanied this code.
*/

/**
* Project Wonderland
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., All Rights Reserved
*
* Redistributions in source code form must reproduce the above
* copyright and this condition.
*
* The contents of this file are subject to the GNU General Public
* License, Version 2 (the "License"); you may not use this file
* except in compliance with the License. A copy of the License is
* available at http://www.opensource.org/licenses/gpl-license.php.
*
* Sun designates this particular file as subject to the "Classpath"
* exception as provided by Sun in the License file that accompanied
* this code.
*/
package org.jdesktop.wonderland.modules.celleditor.client;

import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingSphere;
import com.jme.bounding.BoundingVolume;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import java.awt.Color;
import java.awt.Component;
import java.awt.Point;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TooManyListenersException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import org.jdesktop.wonderland.client.ClientContext;
import org.jdesktop.wonderland.client.cell.Cell;
import org.jdesktop.wonderland.client.cell.CellCache;
import org.jdesktop.wonderland.client.cell.CellEditChannelConnection;
import org.jdesktop.wonderland.client.cell.CellManager;
import org.jdesktop.wonderland.client.cell.CellStatusChangeListener;
import org.jdesktop.wonderland.client.cell.EnvironmentCell;
import org.jdesktop.wonderland.client.cell.properties.CellPropertiesEditor;
import org.jdesktop.wonderland.client.cell.properties.PropertiesManager;
import org.jdesktop.wonderland.client.cell.properties.spi.PropertiesFactorySPI;
import org.jdesktop.wonderland.client.cell.registry.CellComponentRegistry;
import org.jdesktop.wonderland.client.cell.registry.spi.CellComponentFactorySPI;
import org.jdesktop.wonderland.client.cell.utils.CellPlacementUtils;
import org.jdesktop.wonderland.client.cell.utils.CellUtils;
import org.jdesktop.wonderland.client.cell.view.AvatarCell;
import org.jdesktop.wonderland.client.comms.WonderlandSession;
import org.jdesktop.wonderland.client.content.ContentExportManager;
import org.jdesktop.wonderland.client.content.spi.ContentExporterSPI;
import org.jdesktop.wonderland.client.jme.ClientContextJME;
import org.jdesktop.wonderland.client.jme.ViewManager;
import org.jdesktop.wonderland.client.jme.ViewProperties;
import org.jdesktop.wonderland.client.jme.utils.ScenegraphUtils;
import org.jdesktop.wonderland.client.login.LoginManager;
import org.jdesktop.wonderland.common.cell.CellEditConnectionType;
import org.jdesktop.wonderland.common.cell.CellID;
import org.jdesktop.wonderland.common.cell.CellStatus;
import org.jdesktop.wonderland.common.cell.CellTransform;
import org.jdesktop.wonderland.common.cell.messages.CellReparentMessage;
import org.jdesktop.wonderland.common.cell.messages.CellServerComponentMessage;
import org.jdesktop.wonderland.common.cell.messages.CellServerComponentResponseMessage;
import org.jdesktop.wonderland.common.cell.messages.CellServerStateRequestMessage;
import org.jdesktop.wonderland.common.cell.messages.CellServerStateResponseMessage;
import org.jdesktop.wonderland.common.cell.messages.CellServerStateUpdateMessage;
import org.jdesktop.wonderland.common.cell.state.CellComponentServerState;
import org.jdesktop.wonderland.common.cell.state.CellServerState;
import org.jdesktop.wonderland.common.cell.state.PositionComponentServerState;
import org.jdesktop.wonderland.common.messages.ErrorMessage;
import org.jdesktop.wonderland.common.messages.OKMessage;
import org.jdesktop.wonderland.common.messages.ResponseMessage;

/**
* A frame to allow the editing of properties for the cell.
*
* @author Jordan Slott <jslott@dev.java.net>
* @author Ronny Standtke <ronny.standtke@fhnw.ch>
* @author Abhishek Upadhyay
*/
public class CellPropertiesJFrame extends JFrame implements CellPropertiesEditor {

    private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(
            "org/jdesktop/wonderland/modules/celleditor/client/resources/Bundle");

    private static final Logger LOGGER =
            Logger.getLogger(CellPropertiesJFrame.class.getName());

    private List<PropertiesFactorySPI> factoryList = null;
    private Cell selectedCell = null;
    private CellServerState selectedCellServerState = null;
    private PropertiesFactorySPI cellProperties = null;
    private DefaultListModel listModel = null;
    private SortedTreeNode treeRoot = null;
    private CellStatusChangeListener cellListener = null;
    private TreeSelectionListener treeListener = null;
    private SortedTreeNode dragOverTreeNode = null;
    private Set<Class> dirtyPanelSet = new HashSet();
    private StateUpdates stateUpdates = null;

    // A Map from the Cell to its node in the tree. All access to this Map must
    // happen in the AWT Event Thread to insure synchronized access.
    private Map<Cell, SortedTreeNode> cellNodes = null;

    // The two standard panels for all Cells: Basic and Position
    private PropertiesFactorySPI basicPropertiesFactory = null;
    private PropertiesFactorySPI positionPropertiesFactory = null;

    /** Constructor */
    public CellPropertiesJFrame() {
        factoryList = new LinkedList();
        stateUpdates = new StateUpdates();

        // Initialize the GUI components
        initComponents();

        // Add a list model for the list of capabilities. Also, listen for
        // selections on the list to display the appropriate panel
        listModel = new DefaultListModel();
        capabilityList.setModel(listModel);
        capabilityList.addListSelectionListener(new CapabilityListSelectionListener());

        // Create and add a basic panel for all cells as a special case.
        basicPropertiesFactory = new BasicJPanel();
        basicPropertiesFactory.setCellPropertiesEditor(this);

        // Create the position panel for all cells as a special case.
        positionPropertiesFactory = new PositionJPanel();
        positionPropertiesFactory.setCellPropertiesEditor(this);

        // Set up all of the stuff we need to the tree to display Cells
        treeRoot = new SortedTreeNode(BUNDLE.getString("World_Root"));
        cellNodes = new HashMap();
        DefaultTreeModel treeModel = new DefaultTreeModel(treeRoot);
        cellHierarchyTree.setModel(treeModel);
        cellHierarchyTree.setCellRenderer(new CellTreeRenderer());

        // Create a listener that will listen to the status of Cells. This
        // listener gets added when the dialog is made visible. We need to do
        // all of this in the AWT even thread.
        cellListener = new CellStatusChangeListener() {
            public void cellStatusChanged(final Cell cell, final CellStatus status) {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        DefaultMutableTreeNode node = cellNodes.get(cell);
                        if (status == CellStatus.DISK) {
                            // If there is a Node that corresponds to the Cell,
                            // then remove it from the tree.
                            if (node != null) {

                                // We need to handle a special case here: if the
                                // node is currently selected and we have made
                                // edits, the GUI will think the panel is in
                                // the "dirty" state so we need to pretend the
                                // edits have not happened and just delete the
                                // Cell.
                                if (selectedCell == cell) {
                                    dirtyPanelSet.clear();
                                }

                                // Now just go ahead and remove the Cell from
                                // the tree.
                                TreeModel m = cellHierarchyTree.getModel();
                                ((DefaultTreeModel) m).removeNodeFromParent(node);
                                cellNodes.remove(cell);
                            }
                        }
                        else if (status == CellStatus.RENDERING) {
                            // If the node does not exist, then create it and
                            // tell the tree that a node has been inserted. We
                            // fetch the parent node of the newly created node
                            // and tell its parent that its structure has
                            // changed.
                            if (node == null) {
                                createJTreeNode(cell);
                            }
                        }
                    }
                });
            }
        };

        // Listen to selections on the tree and change the selected Cell.
        treeListener = new TreeSelectionListener() {
            public void valueChanged(TreeSelectionEvent e) {
                SortedTreeNode selectedNode =
                        (SortedTreeNode) cellHierarchyTree.getLastSelectedPathComponent();
                if (selectedNode != null) {
                    Object userObject = selectedNode.getUserObject();
                    if (userObject instanceof Cell) {
                        setSelectedCell((Cell) userObject);
                    }
                    else {
                        setSelectedCell(null);
                    }
                }
                else {
                    setSelectedCell(null);
                }
            }
        };

        // Install drag and drop on the tree. This will handle when a tree node
        // is dropped on top of another node.
        cellHierarchyTree.setDragEnabled(true);
        DropTarget dt = new DropTarget();
        try {
            dt.addDropTargetListener(new CellDropTargetListener());
        } catch (TooManyListenersException ex) {
            LOGGER.log(Level.SEVERE, null, ex);
        }
        cellHierarchyTree.setDropTarget(dt);

        // Listen for when the window is closing. If so, see if there are any
        // changes to the properties and ask if the user wants to apply
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                if (dirtyPanelSet.isEmpty() == false) {
                    int result = JOptionPane.showConfirmDialog(
                            CellPropertiesJFrame.this,
                            BUNDLE.getString("Apply_Close_Message"),
                            BUNDLE.getString("Apply_Title"),
                            JOptionPane.YES_NO_OPTION,
                            JOptionPane.QUESTION_MESSAGE);

                    if (result == JOptionPane.YES_OPTION) {
                        applyValues();
                    }
                    else {
                        restoreValues();
                    }
                }

                // Regardless, tell the panels to close themselves
                for (PropertiesFactorySPI factory : factoryList) {
                    factory.close();
                }
            }
        });
    }

    /**
     * Overrides setVisible() to refect the GUI if being made visible.
     */
    @Override
    public void setVisible(boolean visible) {
        super.setVisible(visible);
        if (visible == true) {
            // Add the listener for the JTree for Cell status changes
            CellManager.getCellManager().addCellStatusChangeListener(cellListener);
            updateTreeGUI();
            updateGUI();

            // issue #687: force a repaint
            invalidate();
            repaint();
        }
        else {
            // Remove the listener for the JTree for Cell status changes
            CellManager.getCellManager().removeCellStatusChangeListener(cellListener);
        }
    }

    /**
     * Sets the currently selected Cell. Update the GUI of the Cell Properties
     * frame to reflect the newly-selected Cell's state.
     * @param cell the currently selected Cell
     */
    public void setSelectedCell(Cell cell) throws IllegalStateException {

        // Check to see if there have been changes to the values in the Cell
        // Properties. If so, then prompt the user whether these should be
        // applies first. If so, then apply, otherwise restore the values so
        // the GUI is in a clean state.
        if (dirtyPanelSet.isEmpty() == false) {
            int result = JOptionPane.showConfirmDialog(this,
                    BUNDLE.getString("Apply_Switch_Message"),
                    BUNDLE.getString("Apply_Title"),
                    JOptionPane.YES_NO_OPTION,
                    JOptionPane.QUESTION_MESSAGE);
            if (result == JOptionPane.YES_OPTION) {
                applyValues();
            }
            else {
                restoreValues();
            }
        }


        // Remember the selected index before we clear the capabilities panel.
        // We do this to try to keep the same capability selected if at all
        // possible.
        int oldSelectedIndex = capabilityList.getSelectedIndex();

        // Remove any existing panels from the GUI. We first need to tell them
        // to close themselves. We do this before we set to the new selected
        // Cell.
        clearPanelSet();

        // Now, set the currnent Cell. If it is null, we simply return. We
        // also turn off the "add" capability button and "remove" Cell button.
        selectedCell = cell;
        if (selectedCell == null) {
            addCapabilityButton.setEnabled(false);
            removeCellButton.setEnabled(false);
            goToButton.setEnabled(false);
            exportButton.setEnabled(false);
            return;
        }

        // Next, fetch the server-state of the Cell. This will tell us which
        // panels we will need to add to the frame.
        selectedCellServerState = fetchCellServerState();
        if (selectedCellServerState == null) {
            String warning = "Unable to fetch cell server state for " + cell.getName();
            LOGGER.warning(warning);
            throw new IllegalStateException(warning);
        }

        // Turn on the "add" capability button and the "remove" Cell button
        addCapabilityButton.setEnabled(true);

        // Disable the remove button for the environment cell, enable it
        // otherwise
        boolean isEnvironment = cell.getCellID().equals(CellID.getEnvironmentCellID());
        removeCellButton.setEnabled(!isEnvironment);
        goToButton.setEnabled(!isEnvironment);
       
        // see if we have a valid exporter
        ContentExporterSPI exporter =
                ContentExportManager.INSTANCE.getContentExporter(selectedCell.getClass());
        exportButton.setEnabled(!isEnvironment && exporter != null);
        if (exporter == null) {
            exportButton.setToolTipText(BUNDLE.getString("No_Exporter"));
        } else {
            exportButton.setToolTipText(null);
        }
       
       
        // Update the panel set based upon the elements in the server state
        updatePanelSet();
        if (isVisible() == true) {
            updateGUI();
        }

        // Try to set the selected index intelligently. A great example is if
        // you select "Position" in one Cell and want to compare with the
        // "Position" of another Cell. It's a pain to (1) select "Position",
        // (2) select another Cell, (3) select "Position" again. So we try to
        // keep the same tab selected.
        if (listModel.getSize() > oldSelectedIndex && oldSelectedIndex != -1) {
            capabilityList.setSelectedIndex(oldSelectedIndex);
        }
        else {
            // Set the initial selected capability to "Basic"
            capabilityList.setSelectedIndex(0);
        }

        // Make sure the GUI redraws itself
        invalidate();
        repaint();
    }

    /**
     * @inheritDoc()
     */
    public void addToUpdateList(CellServerState cellServerState) {
        stateUpdates.cellServerState = cellServerState;
    }

    /**
     * @inheritDoc()
     */
    public void addToUpdateList(CellComponentServerState cellComponentServerState) {
        stateUpdates.cellComponentServerStateSet.add(cellComponentServerState);
    }

    /**
     * @inheritDoc()
     */
    public Cell getCell() {
        return selectedCell;
    }

    /**
     * @inheritDoc()
     */
    public CellServerState getCellServerState() {
        return selectedCellServerState;
    }

    /**
     * @inheritDoc()
     */
    public void setPanelDirty(Class clazz, boolean isDirty) {
       
        // Either add or remove the Class depending upon whether it is dirty
        if (isDirty == true) {
            dirtyPanelSet.add(clazz);
        }
        else {
            dirtyPanelSet.remove(clazz);
        }

        // Enable/disable the Ok/Apply buttons depending upon whether the set
        // of dirty panels is empty or not
        applyButton.setEnabled(dirtyPanelSet.isEmpty() == false);
        restoreButton.setEnabled(dirtyPanelSet.isEmpty() == false);
    }

    /** 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.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        mainPanel = new javax.swing.JPanel();
        topLevelSplitPane = new javax.swing.JSplitPane();
        jPanel4 = new javax.swing.JPanel();
        propertyButtonPanel = new javax.swing.JPanel();
        refreshButton = new javax.swing.JButton();
        restoreButton = new javax.swing.JButton();
        applyButton = new javax.swing.JButton();
        jScrollPane1 = new javax.swing.JScrollPane();
        propertyPanel = new javax.swing.JPanel();
        leftSplitPanePanel = new javax.swing.JSplitPane();
        capabilityPanel = new javax.swing.JPanel();
        capabilityListPanel = new javax.swing.JPanel();
        capabilityListScrollPane = new javax.swing.JScrollPane();
        capabilityList = new javax.swing.JList();
        capabilityButtonPanel = new javax.swing.JPanel();
        addCapabilityButton = new javax.swing.JButton();
        removeCapabilityButton = new javax.swing.JButton();
        cellHierarchyPanel = new javax.swing.JPanel();
        treePanel = new javax.swing.JPanel();
        treeScrollPane = new javax.swing.JScrollPane();
        cellHierarchyTree = new javax.swing.JTree();
        removeCellButton = new javax.swing.JButton();
        goToButton = new javax.swing.JButton();
        exportButton = new javax.swing.JButton();

        java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("org/jdesktop/wonderland/modules/celleditor/client/resources/Bundle"); // NOI18N
        setTitle(bundle.getString("CellPropertiesJFrame.title")); // NOI18N
        getContentPane().setLayout(new java.awt.GridLayout(1, 1));

        mainPanel.setBorder(new javax.swing.border.LineBorder(new java.awt.Color(0, 0, 0), 1, true));
        mainPanel.setLayout(new java.awt.GridLayout(1, 1));

        topLevelSplitPane.setOneTouchExpandable(true);

        jPanel4.setBorder(javax.swing.BorderFactory.createTitledBorder(bundle.getString("CellPropertiesJFrame.jPanel4.border.title"))); // NOI18N
        jPanel4.setLayout(new java.awt.GridBagLayout());

        propertyButtonPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(5, 0, 5, 0));
        propertyButtonPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.RIGHT, 5, 0));

        refreshButton.setText(bundle.getString("CellPropertiesJFrame.refreshButton.text")); // NOI18N
        refreshButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                refreshButtonActionPerformed(evt);
            }
        });
        propertyButtonPanel.add(refreshButton);

        restoreButton.setText(bundle.getString("CellPropertiesJFrame.restoreButton.text")); // NOI18N
        restoreButton.setEnabled(false);
        restoreButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                restoreButtonActionPerformed(evt);
            }
        });
        propertyButtonPanel.add(restoreButton);

        applyButton.setText(bundle.getString("CellPropertiesJFrame.applyButton.text")); // NOI18N
        applyButton.setEnabled(false);
        applyButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                applyButtonActionPerformed(evt);
            }
        });
        propertyButtonPanel.add(applyButton);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
        gridBagConstraints.weightx = 1.0;
        jPanel4.add(propertyButtonPanel, gridBagConstraints);

        propertyPanel.setBackground(new java.awt.Color(255, 255, 255));
        propertyPanel.setLayout(new java.awt.GridLayout(1, 0));
        jScrollPane1.setViewportView(propertyPanel);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.gridheight = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        jPanel4.add(jScrollPane1, gridBagConstraints);

        topLevelSplitPane.setRightComponent(jPanel4);

        leftSplitPanePanel.setDividerLocation(250);
        leftSplitPanePanel.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
        leftSplitPanePanel.setOneTouchExpandable(true);

        capabilityPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(bundle.getString("CellPropertiesJFrame.capabilityPanel.border.title"))); // NOI18N
        capabilityPanel.setLayout(new java.awt.GridBagLayout());

        capabilityListPanel.setLayout(new java.awt.GridLayout(1, 0));

        capabilityListScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        capabilityListScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);

        capabilityList.setBackground(new java.awt.Color(204, 204, 255));
        capabilityList.setBorder(javax.swing.BorderFactory.createEmptyBorder(4, 4, 4, 4));
        capabilityList.setFont(new java.awt.Font("Lucida Grande", 1, 12));
        capabilityList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
        capabilityListScrollPane.setViewportView(capabilityList);

        capabilityListPanel.add(capabilityListScrollPane);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridheight = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(0, 3, 0, 3);
        capabilityPanel.add(capabilityListPanel, gridBagConstraints);

        capabilityButtonPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(5, 0, 5, 0));
        capabilityButtonPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT, 5, 0));

        addCapabilityButton.setFont(new java.awt.Font("Lucida Grande", 1, 14));
        addCapabilityButton.setText(bundle.getString("CellPropertiesJFrame.addCapabilityButton.text")); // NOI18N
        addCapabilityButton.setEnabled(false);
        addCapabilityButton.setMargin(new java.awt.Insets(2, 2, 2, 2));
        addCapabilityButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                addCapabilityButtonActionPerformed(evt);
            }
        });
        capabilityButtonPanel.add(addCapabilityButton);

        removeCapabilityButton.setFont(new java.awt.Font("Lucida Grande", 1, 14));
        removeCapabilityButton.setText(bundle.getString("CellPropertiesJFrame.removeCapabilityButton.text")); // NOI18N
        removeCapabilityButton.setEnabled(false);
        removeCapabilityButton.setMargin(new java.awt.Insets(2, 4, 2, 4));
        removeCapabilityButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                removeCapabilityButtonActionPerformed(evt);
            }
        });
        capabilityButtonPanel.add(removeCapabilityButton);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        capabilityPanel.add(capabilityButtonPanel, gridBagConstraints);

        leftSplitPanePanel.setBottomComponent(capabilityPanel);

        cellHierarchyPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(bundle.getString("CellPropertiesJFrame.cellHierarchyPanel.border.title"))); // NOI18N
        cellHierarchyPanel.setLayout(new java.awt.GridBagLayout());

        treePanel.setMinimumSize(new java.awt.Dimension(250, 23));
        treePanel.setLayout(new java.awt.GridLayout(1, 0));

        treeScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        treeScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);

        javax.swing.tree.DefaultMutableTreeNode treeNode1 = new javax.swing.tree.DefaultMutableTreeNode("root");
        cellHierarchyTree.setModel(new javax.swing.tree.DefaultTreeModel(treeNode1));
        cellHierarchyTree.setDragEnabled(true);
        treeScrollPane.setViewportView(cellHierarchyTree);

        treePanel.add(treeScrollPane);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridwidth = java.awt.GridBagConstraints.REMAINDER;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(0, 3, 3, 3);
        cellHierarchyPanel.add(treePanel, gridBagConstraints);

        removeCellButton.setFont(new java.awt.Font("Lucida Grande", 1, 14));
        removeCellButton.setText(bundle.getString("CellPropertiesJFrame.removeCellButton.text")); // NOI18N
        removeCellButton.setEnabled(false);
        removeCellButton.setMargin(new java.awt.Insets(2, 4, 2, 4));
        removeCellButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                removeCellButtonActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.weightx = 0.3;
        cellHierarchyPanel.add(removeCellButton, gridBagConstraints);

        goToButton.setText(bundle.getString("CellPropertiesJFrame.goToButton.text")); // NOI18N
        goToButton.setEnabled(false);
        goToButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                goToButtonActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.weightx = 0.3;
        cellHierarchyPanel.add(goToButton, gridBagConstraints);

        exportButton.setText(bundle.getString("CellPropertiesJFrame.exportButton.text")); // NOI18N
        exportButton.setEnabled(false);
        exportButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                exportButtonActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.weightx = 0.3;
        cellHierarchyPanel.add(exportButton, gridBagConstraints);

        leftSplitPanePanel.setTopComponent(cellHierarchyPanel);

        topLevelSplitPane.setLeftComponent(leftSplitPanePanel);

        mainPanel.add(topLevelSplitPane);

        getContentPane().add(mainPanel);

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void addCapabilityButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addCapabilityButtonActionPerformed
        // Create a new AddComponentDialog and display. Wait for the dialog
        // to close
        AddComponentDialog dialog = new AddComponentDialog(this, true, selectedCell);
        dialog.setLocationRelativeTo(this);
        dialog.setVisible(true);

        // If the OK button was pressed on the dialog and we can fetch a valid
        // cell component factory, then try to add it on the server.
        CellComponentFactorySPI spi = dialog.getCellComponentFactorySPI();
        if (dialog.getReturnStatus() == AddComponentDialog.RET_OK && spi != null) {
            addComponent(spi);
        }
    }//GEN-LAST:event_addCapabilityButtonActionPerformed

    private void removeCapabilityButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeCapabilityButtonActionPerformed
        // Find out which component is selected and remove it
        int index = capabilityList.getSelectedIndex();
        PropertiesFactorySPI spi = factoryList.get(index);
        removeComponent(spi);
    }//GEN-LAST:event_removeCapabilityButtonActionPerformed

    private void applyButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_applyButtonActionPerformed
        // Simply apply all of the values in the GUI
        applyValues();
    }//GEN-LAST:event_applyButtonActionPerformed

    private void restoreButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_restoreButtonActionPerformed
        // Show a confirmation dialog before the restore takes place.
        int result = JOptionPane.showConfirmDialog(this,
                BUNDLE.getString("Restore_Message"),
                BUNDLE.getString("Restore_Title"),
                JOptionPane.YES_NO_OPTION,
                JOptionPane.QUESTION_MESSAGE);
        if (result == JOptionPane.YES_OPTION) {
            restoreValues();
        }
    }//GEN-LAST:event_restoreButtonActionPerformed

    private void removeCellButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeCellButtonActionPerformed
        // Post a dialog asking if the user really wants to delete the Cell.
        // Since the button is only enabled when a Cell is selected, we are
        // guaranteed to have a Cell.
        String message = BUNDLE.getString("Confirm_Delete_Message");
        String title = BUNDLE.getString("Confirm_Delete");
        message = MessageFormat.format(message, selectedCell.getName());
        int result = JOptionPane.showConfirmDialog(this, message, title, JOptionPane.YES_NO_OPTION);

        // Note that pressing the Esc key results in -1, so we must check
        // for that too (CLOSED_OPTION)
        if (result == JOptionPane.NO_OPTION || result == JOptionPane.CLOSED_OPTION) {
            return;
        }

        CellUtils.deleteCell(selectedCell);
    }//GEN-LAST:event_removeCellButtonActionPerformed

    private void refreshButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshButtonActionPerformed
        // Show a confirmation dialog before the refresh takes place
         int result = JOptionPane.showConfirmDialog(this,
                BUNDLE.getString("Refresh_Message"),
                BUNDLE.getString("Refresh_Title"),
                JOptionPane.YES_NO_OPTION,
                JOptionPane.QUESTION_MESSAGE);
        if (result == JOptionPane.YES_OPTION) {
            refreshValues();
        }
    }//GEN-LAST:event_refreshButtonActionPerformed

    private void goToButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_goToButtonActionPerformed
        try {
            CellTransform gotoTransform = getGotoLocation(selectedCell);
            ClientContextJME.getClientMain().gotoLocation(null,
                    gotoTransform.getTranslation(null),
                    gotoTransform.getRotation(null));
        } catch (IOException ex) {
            LOGGER.log(Level.WARNING, "Error in goto " + selectedCell, ex);
        }
    }//GEN-LAST:event_goToButtonActionPerformed

    private CellTransform getGotoLocation(Cell cell) {
        // get the bounds of the cell we are going to
        BoundingVolume bv = cell.getWorldBounds();
        if (isLarge(bv)) {
            // if the cell is big, go to the center rather than very
            // far away
            return cell.getWorldTransform();
        }
       
        // use the view properties to calculate the idea distance away
        ViewProperties vp = ViewManager.getViewManager().getViewProperties();
        float fov = vp.getFieldOfView();
        float min = vp.getFrontClip();
        float max = 30f;
        float distance = CellPlacementUtils.getDistance(bv, fov, min, max);
       
        // calculate the look vector to this cell -- we only care about the y axis
        // rotation
        Quaternion rotation = cell.getWorldTransform().getRotation(null);
        Vector3f lookVec = CellPlacementUtils.getLookDirection(rotation, null);

        // translate into a quaternion using lookAt
        Quaternion look = new Quaternion();
        look.lookAt(lookVec.negate(), Vector3f.UNIT_Y);

        // find the origin by translating the look vector
        Vector3f origin = lookVec.mult(distance);
        origin.addLocal(cell.getWorldTransform().getTranslation(null));
        return new CellTransform(look, origin);
    }
   
    private boolean isLarge(BoundingVolume bounds) {
        if (bounds instanceof BoundingBox) {
            BoundingBox box = (BoundingBox) bounds;
            return (box.xExtent > 20 || box.zExtent > 20);
        } else if (bounds instanceof BoundingSphere) {
            BoundingSphere sphere = (BoundingSphere) bounds;
            return (sphere.radius > 20);
        } else {
            // unknown bounds type
            return true;
        }
    }
   
    /**
     * Inner class to deal with selection on the capability list.
     */
    class CapabilityListSelectionListener implements ListSelectionListener {

        public void valueChanged(ListSelectionEvent e) {
            // Check to see if the value is still changing and if so, ignore
            if (e.getValueIsAdjusting() == true) {
                return;
            }

            // Handles when an item has been selected in the list of
            // capabilities Display the proper panel in such an instance. We
            // also enable or disable the '-' sign to remove components
            // depending upon what is selected
            int index = capabilityList.getSelectedIndex();
            if (index == -1) {
                propertyPanel.removeAll();
                removeCapabilityButton.setEnabled(false);
                propertyPanel.revalidate();
                propertyPanel.repaint();
                return;
            }

            // For all items, look up the panel in the ordered list of panels
            propertyPanel.removeAll();
            PropertiesFactorySPI factory = factoryList.get(index);
            propertyPanel.add(factory.getPropertiesJPanel());

            // We want to enable/disable the "remove" button only for property
            // sheets other than the first two. (The first three if the Cell
            // property sheet is non-null.
            if (cellProperties == null) {
                removeCapabilityButton.setEnabled(index >= 2);
            }
            else {
                removeCapabilityButton.setEnabled(index >= 3);
            }

            // Invalidate the layout and repaint
            propertyPanel.revalidate();
            propertyPanel.repaint();
        }
    }

    private void exportButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportButtonActionPerformed
        // find an exporter for the selected cell
        ContentExporterSPI exporter =
                ContentExportManager.INSTANCE.getContentExporter(selectedCell.getClass());
       
        // find the origin, which is the same as the location we would use to
        // go to the given cell
        CellTransform origin = getGotoLocation(selectedCell);
       
        // adjust the height so the export is relative to the avatar's current
        // height
        Vector3f translation = origin.getTranslation(null);
        CellTransform avatarTransform = ViewManager.getViewManager().getPrimaryViewCell().getWorldTransform();
        translation.y = avatarTransform.getTranslation(null).getY();
        origin.setTranslation(translation);
       
        exporter.exportCells(new Cell[] { selectedCell }, origin);      
    }//GEN-LAST:event_exportButtonActionPerformed

    /**
     * Applies the values stored in the GUI to the cell. Loops through each
     * of the panels and tells them to apply().
     */
    private void applyValues() {
        // Loop through all of the properties in the list and tell them to
        // apply if they have been marked as dirty.
        for (PropertiesFactorySPI factory : factoryList) {
            // Fetch the Class for the factory and see if it is either in the
            // cell or cell component dirty list
            Class clazz = factory.getClass();
            if (dirtyPanelSet.contains(clazz) == true) {
                factory.apply();
            }
        }

        // As a first step, remove all of the cell component server states from
        // the cell server state. These cell component server states to update
        // are kept in a separate list.
        CellServerState updateState = stateUpdates.cellServerState;
        if (updateState != null) {
            updateState.removeAllComponentServerStates();
        }

        // Form a new CellUpdateServerState message with the appropriate
        // information and send it
        CellServerStateUpdateMessage msg = new CellServerStateUpdateMessage(
                selectedCell.getCellID(),
                updateState,
                stateUpdates.cellComponentServerStateSet);
        ResponseMessage response = selectedCell.sendCellMessageAndWait(msg);
        if (response instanceof ErrorMessage) {
            // XXX Probably should get a success/failed here!
            ErrorMessage em = (ErrorMessage) response;
            LOGGER.log(Level.WARNING, "Error applugin values: " +
                    em.getErrorMessage(), em.getErrorCause());

            JOptionPane.showMessageDialog(this, em.getErrorMessage(),
                    "Error applying values", JOptionPane.ERROR_MESSAGE);
        }

        // Clear any existing updates store by this class
        stateUpdates.clear();

        // Re-fetch the server state of the Cell here. This is so we have a
        // fetch copy of the state.
        selectedCellServerState = fetchCellServerState();
        if (selectedCellServerState == null) {
            LOGGER.warning("Unable to fetch cell server state for " +
                    selectedCell.getName());
            return;
        }

        // Clear out the set of "dirty" panels and disable the apply/restore
        // buttons
        dirtyPanelSet.clear();
        applyButton.setEnabled(false);
        restoreButton.setEnabled(false);

        // Finally, with the new server state just fetched, re-open the panels
        // to cause their GUIs to be refreshed
        updateGUI();

        // Also tell the tree node of the select Cell to repaint itself. This
        // is necessary, for example, if the name of the Cell has changed and
        // the label on the tree node needs to resize itself.
        SortedTreeNode node = cellNodes.get(selectedCell);
        node.nameChanged();
    }

    /**
     * Restores all of the values in the GUI to the original values. Loops
     * through each of the panels and tells them to refresh()
     */
    private void restoreValues() {
        // First clear out any existing updates stored by this class
        stateUpdates.clear();

        // Next iteratate through all factories and tell them to refresh
        for (PropertiesFactorySPI factory : factoryList) {
            factory.restore();
        }

        // Clear out the set of "dirty" panels and disable the apply/restore
        // buttons
        dirtyPanelSet.clear();
        applyButton.setEnabled(false);
        restoreButton.setEnabled(false);
    }

    /**
     * Refreshes all of the values in the GUI to the values stored by the
     * server.
     */
    private void refreshValues() {
        // First clear out any existing updates stored by this class and the
        // "dirty" state for the panel, to ignore any changes made by the
        // user.
        stateUpdates.clear();
        dirtyPanelSet.clear();

        // Then tell the dialog to simply reset the selected Cell
        setSelectedCell(selectedCell);
    }

    /**
     * Iterates through all of the properties panels and tell them they are
     * about to be closed. They should (perhaps) reset some values. Then this
     * method removes the panels from the Cell Properties dialog.
     */
    private void clearPanelSet() {
        // First, loop through all of the factories and tell them they are about
        // to close. It is up to them to decide what to do.
        for (PropertiesFactorySPI factory : factoryList) {
            factory.close();
        }

        // We simply clear out the list model and the list of factories
        listModel.clear();
        factoryList.clear();
    }

    /**
     * Asks the server for the server state of the cell; returns null upon
     * error
     */
    private CellServerState fetchCellServerState() {
        // Fetch the setup object from the Cell object. We send a message on
        // the cell channel, so we must fetch that first.
        ResponseMessage response = selectedCell.sendCellMessageAndWait(
                new CellServerStateRequestMessage(selectedCell.getCellID()));
        if (response == null) {
            return null;
        }

        // We need to remove the position component first as a special case
        // since we do not want to update it after the cell is created.
        CellServerStateResponseMessage cssrm = (CellServerStateResponseMessage) response;
        CellServerState state = cssrm.getCellServerState();
        if (state != null) {
            state.removeComponentServerState(PositionComponentServerState.class);
        }
        return state;
    }

    /**
     * Update the node in the tree with the current Cell hierarchy.
     */
    private void updateTreeGUI() {
        // Stop listening to selections on the tree while we update the GUI
        cellHierarchyTree.removeTreeSelectionListener(treeListener);

        // Refresh the Cell hierarchy tree. Expand the tree path around the
        // selected cell and select it in the tree, if there is one.
        refreshCells(LoginManager.getPrimary().getPrimarySession());
        if (selectedCell != null) {
            SortedTreeNode node = cellNodes.get(selectedCell);
            if (node == null) {
                LOGGER.warning("Unable to find tree node for selected Cell " +
                        selectedCell);
                return;
            }
            TreePath path = new TreePath(node.getPath());
            cellHierarchyTree.expandPath(path);
            cellHierarchyTree.setSelectionPath(path);
           
            //scroll down so that the item is visible
            cellHierarchyTree.scrollPathToVisible(path);
        }

        // Start listening to the tree once again
        cellHierarchyTree.addTreeSelectionListener(treeListener);
    }

    /**
     * Updates the GUI with values currently set in the cell
     */
    private void updateGUI() {
        // Loop through all of the panels and tell them to refresh their values
        for (PropertiesFactorySPI factory : factoryList) {
            factory.open();
        }
    }

    /**
     * For the current Cell object, fetch which CellComponents are currently
     * associated with the cell and creates/deletes any panels on the GUI
     * edit frame as necessary.
     */
    private void updatePanelSet() {
        // Look through the registry of cell property objects and check to see
        // if a panel exists for the cell. Add it if so
        Class clazz = selectedCellServerState.getClass();
        PropertiesManager manager = PropertiesManager.getPropertiesManager();
        cellProperties = manager.getPropertiesByClass(clazz);

        // For all cells, add the "Basic" and "Position" panels
        listModel.addElement(basicPropertiesFactory.getDisplayName());
        factoryList.add(basicPropertiesFactory);
        listModel.addElement(positionPropertiesFactory.getDisplayName());
        factoryList.add(positionPropertiesFactory);

        // If the cell properties panel exists, add an entry for it
        if (cellProperties != null && cellProperties.getPropertiesJPanel() != null) {
            listModel.addElement(cellProperties.getDisplayName());
            factoryList.add(cellProperties);
            cellProperties.setCellPropertiesEditor(this);
        }

        // Loop through all of the cell components in the server state and for
        // each see if there is a properties sheet registered for it. If so,
        // then add it.
        for (Map.Entry<Class, CellComponentServerState> e :
                selectedCellServerState.getComponentServerStates().entrySet()) {

            CellComponentServerState state = e.getValue();
            PropertiesFactorySPI spi = manager.getPropertiesByClass(state.getClass());
            if (spi != null) {
                JPanel panel = spi.getPropertiesJPanel();
                if (panel != null) {
                    String displayName = spi.getDisplayName();
                    spi.setCellPropertiesEditor(this);
                    listModel.addElement(displayName);
                    factoryList.add(spi);
                }
            }
        }
    }

    /**
     * Given a component factory, adds the component to the server and upates
     * the GUI to indicate its presence
     */
    private void addComponent(CellComponentFactorySPI spi) {
        // Fetch the default server state for the factory, and cell id. Make
        // sure we make it dynamically added
        CellComponentServerState state = spi.getDefaultCellComponentServerState();
        CellID cellID = selectedCell.getCellID();
       
        // Send a ADD component message on the cell channel. Wait for a
        // response. If OK, then update the GUI with the new component.
        // Otherwise, display an error dialog box.
        CellServerComponentMessage message =
                CellServerComponentMessage.newAddMessage(cellID, state);
        ResponseMessage response = selectedCell.sendCellMessageAndWait(message);
        if (response == null) {
            // log and error and post a dialog box
            LOGGER.warning("Received a null reply from cell with id " +
                    selectedCell.getCellID() + " with name " +
                    selectedCell.getName() + " adding component.");
            return;
        }

        if (response instanceof CellServerComponentResponseMessage) {
            // If successful, add the component to the GUI by refreshing the
            // Cell that is selected.
            setSelectedCell(selectedCell);
            PropertiesManager manager = PropertiesManager.getPropertiesManager();
            PropertiesFactorySPI prop = manager.getPropertiesByClass(state.getClass());
            capabilityList.setSelectedValue(prop.getDisplayName(),true);
        }
        else if (response instanceof ErrorMessage) {
            // Log an error. Eventually we should display a dialog
            LOGGER.log(Level.WARNING, "Unable to add component to the server: " +
                    ((ErrorMessage) response).getErrorMessage(),
                    ((ErrorMessage) response).getErrorCause());
        }
    }

    /**
     * Given the component properties SPI, removes the component from the server
     * and updates the GUI to indicate its absense
     */
    private void removeComponent(PropertiesFactorySPI factory) {
        // Using the given factory, find out the server-side class name that
        // corresponds to the component.
        Class clazz = PropertiesManager.getServerStateClass(factory);
        if (clazz == null) {
            LOGGER.warning("Unable to remove component for " + factory);
            return;
        }

        // Using the registry of Cell components, find a factory to generate
        // a default cell component server state class.
        CellComponentRegistry r = CellComponentRegistry.getCellComponentRegistry();
        CellComponentFactorySPI spi = r.getCellFactoryByStateClass(clazz);
        if (spi == null) {
            LOGGER.warning("Could not find cell component factory for " +
                    clazz.getName());
            return;
        }

        // Create a new (default) instance of the server-side cell component
        // state class. We use this to find out what the class name is for
        // the cell component on the server side.
        CellComponentServerState s = spi.getDefaultCellComponentServerState();
        String className = s.getServerComponentClassName();

        // Send a message to remove the component giving the class name. Wait
        // for a response.
        CellID cellID = selectedCell.getCellID();

        // Send a message to the server with the cell id and class name and
        // wait for a response
        CellServerComponentMessage cscm =
                CellServerComponentMessage.newRemoveMessage(cellID, className);
        ResponseMessage response = selectedCell.sendCellMessageAndWait(cscm);
        if (response == null) {
            LOGGER.warning("Received a null reply from cell with id " +
                    cellID + " with name " + selectedCell.getName() +
                    " removing component.");
            return;
        }

        // Send the message to the server. Wait for a response. If OK, then
        // update the GUI with the new component. Otherwise, display an error
        // dialog box.
        if (response instanceof OKMessage) {
            // If successful, then remove the component from the GUI by
            // refreshing the Cell that is selected.
            setSelectedCell(selectedCell);
        }
        else if (response instanceof ErrorMessage) {
            // Log an error. Eventually we should display a dialog
            LOGGER.log(Level.WARNING, "Unable to add component to the server: " +
                    ((ErrorMessage) response).getErrorMessage(),
                    ((ErrorMessage) response).getErrorCause());
        }
    }

    /**
     * Holds the collection of updates to the server state to be sent to the
     * server
     */
    private class StateUpdates {

        public CellServerState cellServerState = null;
        public Set<CellComponentServerState> cellComponentServerStateSet = null;

        /** Default constructor */
        public StateUpdates() {
            cellComponentServerStateSet = new HashSet();
        }

        /**
         * Clears out any existing updates of state
         */
        public void clear() {
            cellServerState = null;
            cellComponentServerStateSet.clear();
        }
    }

    /**
     * An inner class that extends DefaultMutableTreeNode to make sure all of
     * the nodes are sorted.
     */
    class SortedTreeNode extends DefaultMutableTreeNode implements Comparable {
        private String displayName;

        /**
         * Constructor, takes a display name, use for the root of the world.
         * @param name The visual name of the node
         */
        public SortedTreeNode(String name) {
            super(name);
        }


        /**
         * Constructor, takes a display name and a cell, use for cells with
         * a different display name than the cell name.
         * @param cell the cell to represent
         * @param name The visual name of the node
         */
        public SortedTreeNode(Cell cell, String name) {
            super((cell == null) ? name : cell);

            this.displayName = name;
        }
       
        /**
         * Constructor, takes the Cell to use as the 'user object'
         * @param cell The Cell as the 'user object'
         */
        public SortedTreeNode(Cell cell) {
            super(cell);
        }

        /**
         * Get a user-visible name to use. If null, use the cell's name
         * @return the user-visible name
         */
        public String getDisplayName() {
            return displayName;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void insert(MutableTreeNode child, int childIndex) {
            // Insert the child and then sort all of the nodes.
            super.insert(child, childIndex);
            Collections.sort(this.children);
        }

         /**
         * {@inheritDoc}
         */
        public int compareTo(Object obj) {
            // Fetch the Cell names from this node and from obj and compare.
            Cell thisCell = (Cell)getUserObject();
            String thisName = thisCell.getName() + " (" +
                    thisCell.getCellID().toString() + ")";

            DefaultMutableTreeNode objNode = (DefaultMutableTreeNode) obj;
            Cell objCell = (Cell) objNode.getUserObject();
            String objName = objCell.getName() + " (" +
                    objCell.getCellID().toString() + ")";

            return thisName.compareToIgnoreCase(objName);
        }

        /**
         * Notifies the node that its name has changed, causing a redraw of
         * the node and a re-sort of the parent's children
         */
        public void nameChanged() {
            // Tell the parent to restore its children
            SortedTreeNode parentNode = (SortedTreeNode)getParent();
            if (parentNode != null) {
                Collections.sort(parentNode.children);
            }

            // Redraw this node, so that the entire name appears, necessary if
            // the name is made longer and to avoid the "..." that would be
            // drawn as a result.
            DefaultTreeModel model = (DefaultTreeModel)cellHierarchyTree.getModel();
            if (parentNode != null) {
                model.nodeStructureChanged(parentNode);
            }
            model.nodeChanged(this);

            // Make sure selected node is still selected
            TreePath treePath = new TreePath(getPath());
            cellHierarchyTree.setSelectionPath(treePath);
           
            //scroll down so that the item is visible
            cellHierarchyTree.scrollPathToVisible(treePath);
        }
    }

    /**
     * Render for Cells in the JTree. This uses the "default" tree cell renderer
     * which is a subclass of JLabel. Each tree node has a "user object" which
     * is a Cell. Draw the Cell name, and a border around it if it is currently
     * dragged-over in a drag-and-drop operation. The "root" node is just a
     * default mutable tree node with a user object of a String name.
     */
    private class CellTreeRenderer extends DefaultTreeCellRenderer {

        /**
         * @inheritDoc()
         */
        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value,
                boolean selected, boolean expanded, boolean leaf, int row,
                boolean hasFocus) {

            // Call the super class method to render the tree node properly.
            super.getTreeCellRendererComponent(tree, value, selected, expanded,
                    leaf, row, hasFocus);

            // Assume the node is a default mutable tree node. If the node is
            // currently being dragged-over in a drag-and-drop operation, then
            // set a black line border around the tree node, otherwise clear
            // the border.
            SortedTreeNode treeNode = (SortedTreeNode) value;
            if (treeNode == dragOverTreeNode) {
                setBorder(BorderFactory.createLineBorder(Color.BLACK));
            }
            else {
                setBorder(null);
            }

            // Using the name of the Cell to set the name of the tree node.
            if (treeNode.getDisplayName() != null) {
                setText(treeNode.getDisplayName());
            } else if (treeNode.getUserObject() instanceof Cell) {
                Cell cell = (Cell) treeNode.getUserObject();
                setText(cell.getName() + " (" + cell.getCellID().toString() + ")");
            }
            return this;
        }
    }

    /**
     * Listener for drop target events. Makes sure the node in the tree for the
     * drop is properly highlighted and performs the drop by reparenting the
     * Cell.
     */
    private class CellDropTargetListener extends DropTargetAdapter {

        /**
         * @inheritDoc()
         */
        @Override
        public void dragOver(DropTargetDragEvent dtde) {
            // Fetch the location over which we are dragging and find
            // the tree node corresponding to that position.
            Point location = dtde.getLocation();
            TreePath path = cellHierarchyTree.getPathForLocation(location.x, location.y);
            if (path == null) {
                dragOverTreeNode = null;
            }
            else {
                dragOverTreeNode = (SortedTreeNode) path.getLastPathComponent();
            }
            cellHierarchyTree.repaint();
        }

        /**
         * @inheritDoc()
         */
        @Override
        public void dragExit(DropTargetEvent dte) {
            dragOverTreeNode = null;
            cellHierarchyTree.repaint();
        }

        /**
         * @inheritDoc()
         */
        @Override
        public void drop(DropTargetDropEvent dtde) {
            // Fetch the location over which we are dropping and find
            // the tree node corresponding to that position.
            Point location = dtde.getLocation();
            TreePath path = cellHierarchyTree.getPathForLocation(location.x, location.y);
            if (path == null) {
                dtde.rejectDrop();
                return;
            }

            // Accept the drop and fetch the transferable from the drop event.
            // We are given the value of toString() from the dropped object.
            // Since this is a default mutable tree node, we defined the
            // toString() method below to return CellID@<CellID>.
            dtde.acceptDrop(DnDConstants.ACTION_MOVE);
            Transferable transferable = dtde.getTransferable();
            String cellIDString = null;
            try {
                DataFlavor df = new DataFlavor(
                        "application/x-java-jvm-local-objectref; " +
                        "class=java.lang.String");
                cellIDString = (String) transferable.getTransferData(df);
            } catch (Exception excp) {
                LOGGER.log(Level.WARNING, "Unable to fetch Cell ID string " +
                        "from the drop target", excp);
                return;
            }

            // Check to make sure the cellID String is properly formed. If not,
            // then log an error and return
            if (cellIDString == null || cellIDString.startsWith("CellID@") == false) {
                LOGGER.warning("Invalid Cell ID from drag and drop " + cellIDString);
                return;
            }

            // Parse out the Cell ID from the String
            int cellIDInt = -1;
            try {
                cellIDInt = Integer.parseInt(cellIDString.substring(7));
            } catch (Exception excp) {
                LOGGER.log(Level.WARNING, "Unable to fetch Cell ID integer " +
                        "from the drop target", excp);
                return;
            }
            CellID cellID = new CellID(cellIDInt);

            // Fetch the client-side Cell cache and find the Cell with the
            // dropped CellID
            WonderlandSession session = LoginManager.getPrimary().getPrimarySession();
            CellCache cache = ClientContext.getCellCache(session);
            if (cache == null) {
                LOGGER.warning("Unable to find Cell cache for session " + session);
                return;
            }
            Cell draggedCell = cache.getCell(cellID);
            if (draggedCell == null) {
                LOGGER.warning("Unable to find dragged Cell with ID " + cellID);
                return;
            }

            // Find out what Cell ID this was dropped over. This will form the
            // new parent. If the Cell is dropped over the world root, then set
            // the CellID to InvalidCellID
            CellID parentCellID = CellID.getInvalidCellID();
            SortedTreeNode treeNode = (SortedTreeNode) path.getLastPathComponent();
            Object userObject = treeNode.getUserObject();
            Cell newParent = null;
            if (userObject instanceof Cell && !(userObject instanceof EnvironmentCell)) {
                parentCellID = ((Cell) userObject).getCellID();
                newParent = (Cell) userObject;
                if (draggedCell.equals(newParent) == true) {
                    // User dropped cell on itself, return !
                    return;
                }
            }

            // Find the world transform of the new parent. If there is no new
            // parent (e.g. if the Cell is to be placed at the world root), then
            // use a null transform.
            CellTransform newParentWorld = new CellTransform(null, null);
            if (newParent != null) {
                newParentWorld = newParent.getWorldTransform();
            }

            CellTransform newChildLocal = ScenegraphUtils.computeChildTransform(
                    newParentWorld, draggedCell.getWorldTransform());

            // Send a message to the server indicating the change in the
            // parent. We need to send this over the cell edit connection,
            // rather than the cell connection.
            CellEditChannelConnection connection =
                    (CellEditChannelConnection) session.getConnection(
                    CellEditConnectionType.CLIENT_TYPE);
            connection.send(new CellReparentMessage(cellID, parentCellID, newChildLocal));

            // Turn off the selected node border and repaint the tree.
            dragOverTreeNode = null;
            cellHierarchyTree.repaint();
        }
    }

    /**
     * Get the  cells from the cache and update the nodes in tree
     */
    private void refreshCells(WonderlandSession session) {
        // Fetch the client-side Cell cache, log an error if not found and
        // return
        CellCache cache = ClientContext.getCellCache(session);
        if (cache == null) {
            LOGGER.warning("Unable to find Cell cache for session " + session);
            return;
        }

        // Clear out any existing Cells from the tree. We do this by creating a
        // new tree model
        treeRoot = new SortedTreeNode(cache.getEnvironmentCell(),
                                      BUNDLE.getString("World_Root"));
        DefaultTreeModel treeModel = new DefaultTreeModel(treeRoot);
        cellHierarchyTree.setModel(treeModel);
        cellNodes.clear();
        cellNodes.put(cache.getEnvironmentCell(), treeRoot);

        // Loop through all of the root cells and add into the world
        Collection<Cell> rootCells = cache.getRootCells();
        for (Cell rootCell : rootCells) {
            createJTreeNode(rootCell);
        }
        cellHierarchyTree.repaint();
    }

    /**
     * Creates a new tree node for the given Cell and inserts it into the tree.
     */
    private void createJTreeNode(Cell cell) {
        // As a special case, if the Cell is an AvatarCell, then simply ignore
        // and return, since Avatar Cells are returned by the Cell cache.
        if (cell instanceof AvatarCell) {
            return;
        }

        // Create the tree node and put into the map of all nodes. We override
        // the toString() method to return a string containing the Cell ID.
        // This is used in the drag and drop mechanism to figure out which
        // Cell is being dragged.
        SortedTreeNode ret = new SortedTreeNode(cell) {
            @Override
            public String toString() {
                Cell cell = (Cell) getUserObject();
                return "CellID@" + cell.getCellID().toString();
            }
        };
        cellNodes.put(cell, ret);

        // Find the parent node of the new node, and insert it into the tree
        SortedTreeNode parentNode = cellNodes.get(cell.getParent());
        if (parentNode == null) {
            parentNode = treeRoot;
        }
        parentNode.add(ret);

        // Tell the model that a new node has been inserted
        DefaultTreeModel model = (DefaultTreeModel)cellHierarchyTree.getModel();
        int childIndex = parentNode.getIndex(ret);
        model.nodesWereInserted(parentNode, new int[] { childIndex });

        // Recursively iterate through all of the Cell's children and add to
        // the tree.
        List<Cell> children = cell.getChildren();
        for (Cell child : children) {
            createJTreeNode(child);
        }
    }
   
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton addCapabilityButton;
    private javax.swing.JButton applyButton;
    private javax.swing.JPanel capabilityButtonPanel;
    private javax.swing.JList capabilityList;
    private javax.swing.JPanel capabilityListPanel;
    private javax.swing.JScrollPane capabilityListScrollPane;
    private javax.swing.JPanel capabilityPanel;
    private javax.swing.JPanel cellHierarchyPanel;
    private javax.swing.JTree cellHierarchyTree;
    private javax.swing.JButton exportButton;
    private javax.swing.JButton goToButton;
    private javax.swing.JPanel jPanel4;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JSplitPane leftSplitPanePanel;
    private javax.swing.JPanel mainPanel;
    private javax.swing.JPanel propertyButtonPanel;
    private javax.swing.JPanel propertyPanel;
    private javax.swing.JButton refreshButton;
    private javax.swing.JButton removeCapabilityButton;
    private javax.swing.JButton removeCellButton;
    private javax.swing.JButton restoreButton;
    private javax.swing.JSplitPane topLevelSplitPane;
    private javax.swing.JPanel treePanel;
    private javax.swing.JScrollPane treeScrollPane;
    // End of variables declaration//GEN-END:variables
}
TOP

Related Classes of org.jdesktop.wonderland.modules.celleditor.client.CellPropertiesJFrame$CellDropTargetListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.