com.ibm.xsp.extlib.designer.tooling.panels.AbstractTreeNodePanel.java Source code

Java tutorial

Introduction

Here is the source code for com.ibm.xsp.extlib.designer.tooling.panels.AbstractTreeNodePanel.java

Source

/*
 *  Copyright IBM Corp. 2011
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at:
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
 * implied. See the License for the specific language governing 
 * permissions and limitations under the License.
 */
package com.ibm.xsp.extlib.designer.tooling.panels;

import static com.ibm.xsp.extlib.designer.tooling.constants.IExtLibAttrNames.EXT_LIB_ATTR_CONFIGURATION;
import static com.ibm.xsp.extlib.designer.tooling.constants.IExtLibAttrNames.EXT_LIB_ATTR_LABEL;
import static com.ibm.xsp.extlib.designer.tooling.constants.IExtLibRegistry.EXT_LIB_NAMESPACE_URI;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;

import org.eclipse.gef.commands.CommandStack;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.ibm.commons.iloader.node.DataNode;
import com.ibm.commons.iloader.node.DataNodeAdapter;
import com.ibm.commons.iloader.node.DataNodeListener;
import com.ibm.commons.iloader.node.IClassDef;
import com.ibm.commons.iloader.node.ICollection;
import com.ibm.commons.iloader.node.ILoader;
import com.ibm.commons.iloader.node.IMember;
import com.ibm.commons.iloader.node.IObjectCollection;
import com.ibm.commons.iloader.node.NodeException;
import com.ibm.commons.iloader.node.collections.SingleCollection;
import com.ibm.commons.swt.SWTLayoutUtils;
import com.ibm.commons.swt.SWTUtils;
import com.ibm.commons.swt.controls.custom.CustomButton;
import com.ibm.commons.swt.controls.custom.CustomComposite;
import com.ibm.commons.swt.controls.custom.CustomTree;
import com.ibm.commons.swt.controls.custom.CustomTreeColumn;
import com.ibm.commons.swt.data.controls.DCUtils;
import com.ibm.commons.swt.data.layouts.PropLayout2;
import com.ibm.commons.util.StringUtil;
import com.ibm.designer.domino.xsp.api.panels.IPanelDataReciever;
import com.ibm.designer.domino.xsp.api.panels.IPanelExtraData;
import com.ibm.designer.domino.xsp.api.panels.complex.ComplexPanelComposite;
import com.ibm.designer.domino.xsp.api.util.XPagesDOMUtil;
import com.ibm.designer.domino.xsp.api.util.XPagesEditorUtils;
import com.ibm.designer.domino.xsp.api.util.XPagesPropertiesViewUtils;
import com.ibm.designer.domino.xsp.registry.DesignerExtension;
import com.ibm.designer.domino.xsp.registry.DesignerExtensionUtil;
import com.ibm.xsp.extlib.designer.tooling.ExtLibToolingPlugin;
import com.ibm.xsp.extlib.designer.tooling.commands.RemoveNodeCommand;
import com.ibm.xsp.extlib.designer.tooling.constants.IExtLibAttrNames;
import com.ibm.xsp.extlib.designer.tooling.constants.IExtLibTagNames;
import com.ibm.xsp.extlib.designer.tooling.panels.actions.RedoAction;
import com.ibm.xsp.extlib.designer.tooling.panels.actions.UndoAction;
import com.ibm.xsp.extlib.designer.tooling.panels.model.LinkContentProvider;
import com.ibm.xsp.extlib.designer.tooling.panels.model.NodeLabelProvider;
import com.ibm.xsp.extlib.designer.tooling.utils.ExtLibRegistryUtil;
import com.ibm.xsp.extlib.designer.tooling.utils.ExtLibToolingLogger;
import com.ibm.xsp.extlib.designer.tooling.utils.XSPNodeUtil;
import com.ibm.xsp.extlib.tree.ITreeNode;
import com.ibm.xsp.registry.FacesContainerProperty;
import com.ibm.xsp.registry.FacesDefinition;
import com.ibm.xsp.registry.FacesProperty;
import com.ibm.xsp.registry.FacesRegistry;

/**
 * This abstract panel encapsulates all of the functionality needed to create a "Links" panel for the
 * applicationLayout tag. The panel has two main sections, the left column and right column.
 * The left column will display a TreeViewer. The TreeViewer will display the link node children
 * of the attribute provided via the abstract method {@link #getLinkAttributeName()}.<br/>
 * When items are selected in the Tree the right column will be updated. This works similar
 * to the Data Panel on an XPage. An item is selected in the TreeViewer and as a result a corresponding
 * properties panel is displayed in the right hand column.
 * 
 * 
 * @author doconnor
 *
 */
public abstract class AbstractTreeNodePanel extends PropLayout2 implements IPanelDataReciever {

    final private TreeNodePanelDescriptor _descriptor;

    private Menu _childLinkMenu = null; //the menu that will show "Add Child Link" items
    private ComplexPanelComposite _complexComposite = null; //the composite that will house the UI for the selected node
    private IPanelExtraData _data = null; //data access to the xpage
    private Composite _leftChild = null; //the left composite - needed so we can retrieve the DataNode as needed
    private Menu _linkMenu = null; //the menu that will show "Add Link" items
    private TreeViewer _linkViewer = null; //the viewer that will show all of the links for the current tag
    private CustomButton _addLink = null; // the button for adding link tree nodes to tree nodes 
    private CustomButton _addChildLink = null; // the button for adding link tree nodes to tree nodes that can have children
    private CustomButton _removeLink = null; // the button for deleting link tree nodes 

    private CustomButton _moveUp = null;
    private CustomButton _moveDown = null;

    private DataNodeListener dataNodeListener = new DataNodeAdapter() {
        public void onValueChanged2(DataNode source, int record, Object object, IMember member) {
            AbstractTreeNodePanel.this._linkViewer.refresh();
        }
    };

    private KeyListener delKeyListener = new KeyAdapter() {
        public void keyPressed(KeyEvent event) {
            switch (event.keyCode) {
            case SWT.DEL: {
                AbstractTreeNodePanel.this.removeSelected();
                break;
            }
            }
            ;
        }
    };

    protected abstract String getLinkAttributeDescription();

    /**
     * Constructs a new instance of this class
     * @param parent
     * @param style
     */
    public AbstractTreeNodePanel(Composite parent, TreeNodePanelDescriptor descriptor, int style) {
        super(parent, style);
        _descriptor = descriptor;
    }

    /**
     * Creates a link menu item. Adds a listener to the item, the listener
     * adds a child node to the applicationLayout. If <code>createChild</code> is
     * true then a child will be added to the currently selected node.
     * 
     * @param menu
     *          the parent menu item
     * @param label
     *          the label of the menu item
     * @param def
     *          the FacesDefinition of the TreeNode represented by the menu item
     * @param createChild
     *          a flag that determines whether or not the TreeNode being inserted should be inserted as a
     *          child of another TreeNode or as a child of the applicationLayout tag.
     */
    protected void addLinkMenuItem(Menu menu, final String label, final FacesDefinition def,
            final boolean createChild) {
        MenuItem item = new MenuItem(menu, SWT.PUSH);
        item.setText(label);

        item.addListener(SWT.Selection, new Listener() {
            public void handleEvent(Event event) {
                Object newObject = null;
                Object parentObj = null;
                DataNode dn = DCUtils.findDataNode(_leftChild, true);
                IMember member = dn.getMember(_descriptor.propertyName);
                ILoader loader = dn.getLoader();
                IClassDef classDef;
                try {
                    classDef = loader.loadClass(def.getNamespaceUri(), def.getTagName());
                    parentObj = dn.getCurrentObject();

                    newObject = classDef.newInstance(parentObj);

                    // if we're creating a child node, get the parent from the tree selection
                    if (createChild) {
                        // this DN is set when selection changes in the tree
                        DataNode dnParent = DCUtils.findDataNode(_complexComposite, true);
                        // find the first member of type "ITreeNode"
                        IMember parentMember = AbstractTreeNodePanel.this
                                .findTreeNodeMember(dnParent.getCurrentObject(), loader, null);
                        // and change whose collection we'll add to 
                        if (parentMember instanceof ICollection) {
                            parentObj = dnParent.getCurrentObject();
                            member = parentMember;
                        }
                    }
                    IObjectCollection col = loader.getObjectCollection(parentObj, (ICollection) member);
                    col.add(col.size(), newObject, null);

                    // add a default label if appropriate
                    if (def.getProperty(EXT_LIB_ATTR_LABEL) != null && newObject instanceof Element) {
                        //TODO DAN - Should use data node here.. should avoid instanceof Element if possible.
                        if (def.getProperty(IExtLibAttrNames.EXT_LIB_ATTR_VAR) != null) {
                            String var = getLinkDisplayName(def, false);
                            if (StringUtil.isEmpty(var)) {
                                var = "var"; // $NON-NLS-1$
                            } else {
                                char[] cs = var.toCharArray();
                                if (cs != null) {
                                    StringBuffer buff = new StringBuffer();
                                    for (char c : cs) {
                                        if (!Character.isSpaceChar(c)) {
                                            buff.append(c);
                                        }
                                    }
                                    var = buff.toString();
                                }
                            }
                            String[] vars = XPagesDOMUtil.getVars(((Element) newObject).getOwnerDocument(), null);
                            var = XPagesDOMUtil.generateUniqueVar(Arrays.asList(vars), (Element) newObject, var);
                            XPagesDOMUtil.setAttribute((Element) newObject, IExtLibAttrNames.EXT_LIB_ATTR_VAR, var);
                        }
                        String newLabel = generateNewLabel(def);
                        XPagesDOMUtil.setAttribute((Element) newObject, EXT_LIB_ATTR_LABEL, newLabel);
                    }

                } catch (NodeException e) {
                    if (ExtLibToolingLogger.EXT_LIB_TOOLING_LOGGER.isErrorEnabled()) {
                        ExtLibToolingLogger.EXT_LIB_TOOLING_LOGGER.errorp(this, "addLinkMenuItem", e,
                                "Failed to create new {0}", // $NON-NLS-1$ $NLE-AbstractTreeNodePanel.Failedtocreatenew0-2$
                                _descriptor.propertyName);
                    }
                }
                if (newObject != null) {
                    _linkViewer.refresh();
                    if (null != parentObj) {
                        _linkViewer.setExpandedState(parentObj, true);
                    }
                    _linkViewer.setSelection(new StructuredSelection(newObject));
                }
            }
        });
    }

    /**
     * @param treenodeTypeName
     * @return the names of any propertoes of 'treenodeTypeName' that are required.
     */
    protected List<String> checkRequired(String treenodeTypeName) {

        List<String> propnames = new ArrayList<String>();

        FacesRegistry registry = _data.getDesignerProject().getFacesRegistry();

        FacesDefinition propDef = registry.findDef(EXT_LIB_NAMESPACE_URI, treenodeTypeName);
        for (String propname : propDef.getDefinedPropertyNames()) {
            boolean required = ExtLibRegistryUtil.getIsRequiredProperty(registry, propDef.getId(), propname);
            if (required) {
                propnames.add(propname);
            }
        }
        return propnames;
    }

    /**
     * Creates a new menu and adds a menu item for each TreeNode returned by {@link #findTreeNodes()}.
     * 
     * @param parent
     *          the shell parent of the menu
     * @param createChild
     *          a flag which specifies whether the menu item should deal with children of a TreeNode or add
     *          root TreeNodes
     *          
     * @return
     *          the menu containing menu items returned by {@link #findTreeNodes()}
     */
    private Menu addLinkTypes(Shell parent, boolean createChild) {
        Menu menu = new Menu(parent);
        SWTUtils.setControlId(menu, "menu.links.add"); //$NON-NLS-1$
        List<FacesDefinition> treeNodes = findTreeNodes();
        if (treeNodes != null) {
            for (final FacesDefinition def : treeNodes) {
                // @todo: do we want to look for required properties here?

                String label = getLinkDisplayName(def, true);
                addLinkMenuItem(menu, label, def, createChild);
            }
        }
        return menu;
    }

    /**
     * 
     * @param def
     * @return
     */
    protected String getLinkDisplayName(FacesDefinition def, boolean forMenu) {
        if (!forMenu && StringUtil.equals(def.getTagName(), IExtLibTagNames.EXT_LIB_TAG_BASIC_LEAF_NODE)) {
            return "Link"; // $NLX-AbstractTreeNodePanel.Link-1$
        }
        DesignerExtension ext = DesignerExtensionUtil.getExtension(def);
        if (ext != null) {
            return ext.getDisplayName();
        }
        return def.getTagName();
    }

    /**
     * setup the DataNode for the panel using the member (attribute) name provided by {@link #getLinksContainingTypeName()}  
     * @param leftChild
     * @param listener
     */
    protected void setupLinkPanelDataNode(Composite leftChild, DataNodeListener listener) {
        ExtLibPanelUtil.initDataNode(leftChild, listener, _descriptor.complexName);
    }

    protected void removeLinkPanelDataNodeListener(DataNodeListener listener) {
        if (_leftChild != null) {
            DataNode dataNode = DCUtils.findDataNode(_leftChild, false);
            if (dataNode != null) {
                dataNode.removeDataNodeListener(AbstractTreeNodePanel.this.dataNodeListener);
                AbstractTreeNodePanel.this.dataNodeListener = null;
            }
        }
    }

    /**
     * create new unique text for a "label" attribute, if present, by looking at all existing. 
     * @param displayName
     * @return
     */
    protected String generateNewLabel(FacesDefinition def) {
        if (StringUtil.equals(def.getTagName(), IExtLibTagNames.EXT_LIB_TAG_DOMINO_VIEW_LIST)) {
            return null; //for the dominoViewListTreeNode tag we do not want to set the label!
        }
        String label = "Label"; // $NLX-AbstractTreeNodePanel.Label-1$
        String displayName = getLinkDisplayName(def, false);
        if (null == displayName)
            return label;

        Object input = _linkViewer.getInput();

        ITreeContentProvider cp = (ITreeContentProvider) _linkViewer.getContentProvider();
        Object[] children = cp.getElements(input);

        Stack<Object> stack = new Stack<Object>(); // for non-recursive tree traversal
        stack.push(input);

        Set<String> set = new HashSet<String>(); // collects current names

        while (!stack.isEmpty()) {
            if (null != children) {
                for (int i = 0; i < children.length; i++) {
                    if (children[i] instanceof Element) {
                        String l = XPagesDOMUtil.getAttribute((Element) children[i], EXT_LIB_ATTR_LABEL);
                        if (StringUtil.isNotEmpty(l))
                            set.add(l);
                    }
                    stack.push(children[i]);
                }
            }
            children = cp.getChildren(stack.pop());
        }
        while (!stack.isEmpty())
            ;

        String prefix = displayName;
        String[] splits = displayName.split(" "); //$NON-NLS-1$
        if (null != splits && splits.length > 0) {
            prefix = splits[0];
            for (int i = 1; i < 100; i++) {
                String thistry = prefix + " " + String.valueOf(i); //$NON-NLS-1$
                if (!set.contains(thistry)) {
                    label = thistry;
                    break;
                }
            }
        }
        return label;
    }

    @Override
    protected void createLeftContents(Composite leftChild) {

        _leftChild = leftChild;

        // set up the data node, add listener to refresh tree in case "children" is edited 
        // using right tree.

        setupLinkPanelDataNode(leftChild, this.dataNodeListener);

        _linkMenu = addLinkTypes(leftChild.getShell(), false);
        _childLinkMenu = addLinkTypes(leftChild.getShell(), true);

        CustomTree tree = new CustomTree(leftChild, SWT.SINGLE | SWT.FULL_SELECTION | SWT.BORDER, "tree.id"); // $NON-NLS-1$
        tree.setLinesVisible(true);
        tree.setHeaderVisible(true);
        tree.setCols(55);
        GridData treeData = SWTLayoutUtils.createGDFill();
        treeData.heightHint = 200;
        treeData.horizontalIndent = getControlIndentAmt();
        tree.setLayoutData(treeData);
        CustomTreeColumn links = new CustomTreeColumn(tree, SWT.NONE, "links.id"); // $NON-NLS-1$
        links.setText("Links"); // $NLX-AbstractTreeNodePanel.Links-1$
        links.setWidthUnit(CustomTreeColumn.UNIT_PERCENT);
        links.setColWidth(100);

        tree.addKeyListener(delKeyListener);

        _linkViewer = new TreeViewer(tree);
        NodeLabelProvider labelProvider = new NodeLabelProvider(_data.getDesignerProject().getFacesRegistry());
        _linkViewer.setLabelProvider(labelProvider);
        LinkContentProvider contentPvdr = new LinkContentProvider(_descriptor.propertyName,
                _data.getDesignerProject().getFacesRegistry());

        _linkViewer.setContentProvider(contentPvdr);

        _linkViewer.addSelectionChangedListener(new ISelectionChangedListener() {

            public void selectionChanged(SelectionChangedEvent event) {
                ISelection sel = event.getSelection();

                boolean enableRemove = false;
                boolean enableAddChild = false;

                if (sel instanceof IStructuredSelection) {
                    IStructuredSelection structured = (IStructuredSelection) sel;
                    Object element = structured.getFirstElement();
                    if (element instanceof Element) {
                        Element e = (Element) element;
                        refreshMoveButtons(e);
                        DataNode dn = DCUtils.findDataNode(_complexComposite, true);
                        ILoader loader = dn.getLoader();

                        if (null == loader) {
                            loader = XPagesPropertiesViewUtils
                                    .getXPagesMultiDomLoader(getExtraData().getDesignerProject());
                        }

                        if (loader != null) {
                            dn.setDataProvider(new SingleCollection(e));
                            try {
                                dn.setClassDef(loader.getClassOf(e));
                            } catch (NodeException ne) {
                                if (ExtLibToolingLogger.EXT_LIB_TOOLING_LOGGER.isErrorEnabled()) {
                                    ExtLibToolingLogger.EXT_LIB_TOOLING_LOGGER.error(ne,
                                            "Failed to get class def of element ", e.getTagName()); // $NLE-AbstractTreeNodePanel.Failedtogetclassdefofelement-1$
                                }
                            }
                            // enable "remove"
                            enableRemove = true;
                            // enablement for "Add Child"...
                            IMember mTreeNode = AbstractTreeNodePanel.this.findTreeNodeMember(e, dn.getLoader(),
                                    null);
                            enableAddChild = mTreeNode != null;
                        } else {
                            ExtLibToolingLogger.EXT_LIB_TOOLING_LOGGER.warnp(AbstractTreeNodePanel.class,
                                    "selectionChanged", "No loader for current data node"); //$NON-NLS-1$ // $NLW-AbstractTreeNodePanel.Noloaderforcurrentdatanode-2$
                        }

                        // show the properties on the right
                        _complexComposite.updatePanel(e.getNamespaceURI(), e.getLocalName());
                        // re-layout parents until we get to a scrolled composite
                        getDisplay().asyncExec(new Runnable() {
                            /*
                             * (non-Javadoc)
                             * @see java.lang.Runnable#run()
                             */
                            public void run() {
                                if (AbstractTreeNodePanel.this.isDisposed()) {
                                    return;
                                }
                                Composite parent = _complexComposite;
                                Composite prevParent = parent;
                                while (parent != null && !parent.isDisposed()) {
                                    try {
                                        if (parent instanceof ScrolledComposite) {
                                            ((ScrolledComposite) parent)
                                                    .setMinSize(prevParent.computeSize(SWT.DEFAULT, SWT.DEFAULT));
                                            break;
                                        }
                                        parent.layout();
                                    } catch (Throwable t) {
                                        if (ExtLibToolingLogger.EXT_LIB_TOOLING_LOGGER.isWarnEnabled()) {
                                            ExtLibToolingLogger.EXT_LIB_TOOLING_LOGGER.warnp(
                                                    AbstractTreeNodePanel.this, "selectionChanged", t, //$NON-NLS-1$
                                                    "Error encountered when refreshing applicationLayout properties panel"); // $NLW-AbstractTreeNodePanel.Errorencounteredwhenrefreshingapp-1$
                                        }
                                    }
                                    prevParent = parent;
                                    parent = parent.getParent();
                                }
                            }
                        });
                    } else { // (it's not instanceof Element, must be null
                        if (null == element && null != _complexComposite) {
                            _complexComposite.updatePanel(null, null);
                        }
                    }
                }
                _removeLink.setEnabled(enableRemove);
                _addChildLink.setEnabled(enableAddChild);
            }
        });

        _linkViewer.setInput(getDataNode());
        _leftChild.addDisposeListener(new DisposeListener() {
            /*
             * (non-Javadoc)
             * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
             */
            public void widgetDisposed(DisposeEvent event) {
                removeLinkPanelDataNodeListener(dataNodeListener);
            }
        });

        Composite buttonParent = new Composite(leftChild, SWT.NONE);
        buttonParent.setLayout(SWTLayoutUtils.createLayoutNoMarginDefaultSpacing(1));
        GridData bpGD = SWTLayoutUtils.createGDFillVerticalNoGrab();
        bpGD.verticalAlignment = GridData.BEGINNING;
        buttonParent.setLayoutData(bpGD);
        _moveUp = new CustomButton(buttonParent, SWT.NONE, "move.up.id");//$NON-NLS-1$
        _moveUp.setToolTipText("Moves the currently selected item up"); // $NLX-AbstractTreeNodePanel.Movesthecurrentlyselecteditemup-1$
        _moveUp.setImage(getDisabledImage(true));
        _moveUp.addSelectionListener(new SelectionAdapter() {

            /* (non-Javadoc)
             * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
             */
            @Override
            public void widgetSelected(SelectionEvent event) {
                super.widgetSelected(event);
                ISelection sel = _linkViewer.getSelection();
                if (sel instanceof IStructuredSelection) {
                    Object o = ((IStructuredSelection) sel).getFirstElement();
                    if (o instanceof Element) {
                        moveUp((Element) o);
                    }
                }
            }

        });

        _moveDown = new CustomButton(buttonParent, SWT.NONE, "move.down.id");//$NON-NLS-1$
        _moveDown.setToolTipText("Moves the currently selected item down"); // $NLX-AbstractTreeNodePanel.Movesthecurrentlyselecteditemdown-1$
        _moveDown.setImage(getDisabledImage(false));
        _moveDown.addSelectionListener(new SelectionAdapter() {

            /* (non-Javadoc)
             * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
             */
            @Override
            public void widgetSelected(SelectionEvent event) {
                super.widgetSelected(event);
                ISelection sel = _linkViewer.getSelection();
                if (sel instanceof IStructuredSelection) {
                    Object o = ((IStructuredSelection) sel).getFirstElement();
                    if (o instanceof Element) {
                        moveDown((Element) o);
                    }
                }
            }
        });
        if (_linkViewer.getSelection() instanceof IStructuredSelection) {
            Object o = ((IStructuredSelection) _linkViewer.getSelection()).getFirstElement();
            if (o instanceof Element) {
                refreshMoveButtons((Element) o);
            }
        }
    }

    @Override
    protected void createRightContents(Composite rightChild) {
        ((GridData) rightChild.getLayoutData()).horizontalAlignment = SWT.FILL;
        _complexComposite = new ComplexPanelComposite(rightChild, SWT.NONE);
        initComplexDataNode(_complexComposite);
        _complexComposite.updatePanelData(_data);
        _complexComposite.setLayout(SWTLayoutUtils.createLayoutNoMarginDefaultSpacing(1));
        _complexComposite.setLayoutData(SWTLayoutUtils.createGDFill());

        _complexComposite.setBackground(getShell().getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));

        TreeItem[] items = _linkViewer.getTree().getItems();
        if (items != null && items.length > 0) {
            Object firstItem = items[0].getData();
            _linkViewer.setSelection(new StructuredSelection(firstItem));
        }
    }

    private List<FacesDefinition> findTreeNodes() {
        if (this._data != null && this._data.getDesignerProject() != null) {
            FacesRegistry registry = this._data.getDesignerProject().getFacesRegistry();
            if (registry != null) {
                return ExtLibRegistryUtil.getNodes(registry, _descriptor.complexType, _descriptor.propertyName);
            }
        }
        return null;
    }

    @Override
    protected int getNumLeftColumns() {
        return 2;
    }

    @Override
    protected int getNumRightColumns() {
        return 1;
    }

    private void initComplexDataNode(ComplexPanelComposite parent) {
        DataNode dataNode = DCUtils.findDataNode(parent, false);
        if (dataNode != null) {
            DCUtils.initDataBinding(parent.getParent());
            DataNode newNode = DCUtils.findDataNode(parent, true);
            newNode.setClassDef(dataNode.getClassDef());
        }
    }

    /**
     * 
     */
    private void removeSelected() {

        // save the current table selection
        Tree tree = _linkViewer.getTree();
        TreeItem[] selectedItems = tree.getSelection();
        int selectedIndex = -1;
        if (null != selectedItems && selectedItems.length > 0) {
            TreeItem[] items = tree.getItems();
            if (null != items && items.length > 0) {
                for (int i = 0; i < items.length; i++) {
                    if (selectedItems[0].equals(items[i])) {
                        selectedIndex = i;
                        break;
                    }
                }
                if (selectedIndex == items.length - 1)
                    selectedIndex--;
            }
        } else {
            //play the prompt sound!
            Display.getDefault().beep();
            return;
        }

        // the DN for the complextComposite will be the current selection
        DataNode dnSelected = DCUtils.findDataNode(_complexComposite, true);
        ILoader loader = dnSelected.getLoader();
        Element selected = (Element) dnSelected.getCurrentObject();

        if (null == selected) {
            ExtLibToolingLogger.EXT_LIB_TOOLING_LOGGER
                    .warn("\"Delete\" links menu item pressed with no selected element"); //$NON-NLS-1$
            return;
        }

        boolean doIt = true;

        // need to check if we want to warn that they are deleting a container with child elements 

        NodeList childrenNodes = selected.getChildNodes();

        if (null != childrenNodes && childrenNodes.getLength() > 0) {
            // does it have any non-text children?
            boolean hasChildElement = false;
            for (int i = 0; i < childrenNodes.getLength() && !hasChildElement; i++) {
                Node node = childrenNodes.item(i);
                hasChildElement = (node.getNodeType() == Node.ELEMENT_NODE);
            }

            // GGRD8UAP4U check if this -really- has children, or just child element nodes (like an empty <this.children>)
            if (hasChildElement) {
                ITreeContentProvider cp = (ITreeContentProvider) _linkViewer.getContentProvider();
                Object[] childrenFromProvider = cp.getChildren(selected);
                if (childrenFromProvider == null || childrenFromProvider.length == 0) {
                    hasChildElement = false;
                }
            }

            if (hasChildElement) {

                // can this have children? if so, get the member that contains the children
                IMember childrenMember = findTreeNodeMember(selected, loader, null);

                // if selected has child DOM Nodes and could have child tree nodes 
                // (has a member of type iTreeNode), warn...
                if (childrenMember != null) {
                    doIt = MessageDialog.openConfirm(getShell(), "Application Layout", // $NLX-AbstractTreeNodePanel.ApplicationLayout-1$
                            "This Node has children. Are you sure you want to remove it, and all of its children?"); // $NLX-AbstractTreeNodePanel.ThisNodehaschildrenAreyousureyouw-1$
                }
            }
        }

        Node parent = selected.getParentNode();
        if (doIt && null != parent) {
            String memberName = parent.getLocalName();
            try {
                if (memberName.startsWith(IExtLibTagNames.TAG_THIS_PREFIX)) {
                    memberName = memberName.substring(IExtLibTagNames.TAG_THIS_PREFIX.length());
                }

                Node grandparentNode = selected.getParentNode().getParentNode();
                IMember containerMember = findTreeNodeMember(grandparentNode, loader, memberName);
                if (containerMember instanceof ICollection) {
                    IObjectCollection c = loader.getObjectCollection(grandparentNode,
                            (ICollection) containerMember);
                    if (null != c) {
                        IWorkbenchPart part = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
                                .getActivePart();
                        IAction undo = null;
                        IAction redo = null;
                        CommandStack stack = null;
                        IActionBars bars = null;
                        if (part instanceof IViewPart) {
                            bars = ((IViewPart) part).getViewSite().getActionBars();
                            if (bars != null) {
                                undo = bars.getGlobalActionHandler(ActionFactory.UNDO.getId());
                                redo = bars.getGlobalActionHandler(ActionFactory.REDO.getId());

                                if (redo == null) {
                                    stack = XPagesEditorUtils.getCommandStack(null);
                                    redo = new RedoAction(stack);
                                }
                                if (undo == null) {
                                    if (stack == null) {
                                        stack = XPagesEditorUtils.getCommandStack(null);
                                    }
                                    undo = new UndoAction(stack, redo);
                                    if (redo instanceof RedoAction) {
                                        ((RedoAction) redo).setUndoAction((UndoAction) undo);
                                    }
                                }
                                bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), undo);
                                bars.setGlobalActionHandler(ActionFactory.REDO.getId(), redo);
                            }
                        }
                        for (int i = 0; i < c.size(); i++) {
                            // remove the member of the collection that is selected
                            if (c.get(i).equals(selected)) {
                                Node n = (Node) c.get(i);
                                removeAndUpdateUndoActions(n, undo, redo, stack, bars);
                                break;
                            }
                        }

                        // GGRD8UAP4U get the children again.... if 0, remove the "this.children"
                        IObjectCollection cpost = loader.getObjectCollection(grandparentNode,
                                (ICollection) containerMember);
                        if (null != cpost && cpost.size() == 0 && parent != null) {
                            if (containerMember.getName().endsWith("children")) { //$NON-NLS-1$
                                removeAndUpdateUndoActions(parent, undo, redo, stack, bars);
                            }
                        }
                    }
                }
                // try to select the previous (or next) one
                if (selectedIndex >= 0) {
                    TreeItem[] items = tree.getItems();
                    if (selectedIndex < items.length)
                        tree.select(items[selectedIndex]);
                } else {
                    _complexComposite.updatePanel(null, null);
                }
            } catch (NodeException e) {
                if (ExtLibToolingLogger.EXT_LIB_TOOLING_LOGGER.isErrorEnabled()) {
                    ExtLibToolingLogger.EXT_LIB_TOOLING_LOGGER.error(e, "Failed to get children for Data Node ",
                            memberName); // $NLE-AbstractTreeNodePanel.FailedtogetchildrenforDataNode-1$
                }
            }
        }
    }

    /*
     * 
     */

    private void removeAndUpdateUndoActions(Node n, IAction undo, IAction redo, CommandStack stack,
            IActionBars bars) {
        RemoveNodeCommand cmd = new RemoveNodeCommand(n);
        XPagesEditorUtils.executeCommand(cmd, (IEditorPart) null);
        if (stack != null) {
            if (undo != null) {
                undo.setEnabled(stack.canUndo());
            }
            if (redo != null) {
                redo.setEnabled(stack.canRedo());
            }
            if (bars != null) {
                bars.updateActionBars();
            }
        }
    }

    private IMember findTreeNodeMember(Object element, ILoader loader, String nameToFind) {
        IMember member = null;
        if (null != element) {
            try {
                IClassDef classDef = loader.getClassOf(element);
                if (null != classDef) {
                    Object obj = classDef.getNativeClass(); //@TODO: is getNativeClass() OK here?
                    if (obj instanceof FacesDefinition) {
                        FacesDefinition fdef = (FacesDefinition) obj;
                        Collection<String> names = fdef.getPropertyNames();
                        for (String name : names) {
                            FacesProperty prop = fdef.getProperty(name);
                            if (prop instanceof FacesContainerProperty) {
                                FacesContainerProperty cp = (FacesContainerProperty) prop;
                                if (cp.getItemProperty() != null) {
                                    FacesProperty fp = cp.getItemProperty();
                                    if (fp.getJavaClass().isAssignableFrom(ITreeNode.class)) {
                                        if (null == nameToFind || nameToFind.equals(name)) {
                                            member = classDef.getMember(name);
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (NodeException e) {
                if (ExtLibToolingLogger.EXT_LIB_TOOLING_LOGGER.isErrorEnabled()) {
                    ExtLibToolingLogger.EXT_LIB_TOOLING_LOGGER.error(e,
                            StringUtil.format("Failed to get class definition or native class for {0}",
                                    ((Element) element).getTagName())); // $NLE-AbstractTreeNodePanel.Failedtogetclassdefinitionornativ-1$
                }
            }
        }
        return member;
    }

    /*
     * (non-Javadoc)
     * @see com.ibm.designer.domino.xsp.api.panels.IPanelDataReciever#setExtraData(com.ibm.designer.domino.xsp.api.panels.IPanelExtraData)
     */
    public void setExtraData(IPanelExtraData data) {
        this._data = data;
    }

    protected IPanelExtraData getExtraData() {
        return _data;
    }

    /*
     * (non-Javadoc)
     * @see com.ibm.designer.domino.xsp.api.panels.IPanelDataReciever#setExtraData(com.ibm.designer.domino.xsp.api.panels.IPanelExtraData)
     */
    @Override
    protected void createContents() {
        this.getParent().addDisposeListener(new DisposeListener() {
            /*
             * (non-Javadoc)
             * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
             */
            public void widgetDisposed(DisposeEvent e) {
                if (!isDisposed()) {
                    AbstractTreeNodePanel.this.dispose();
                }
            }
        });
        initLayout();
        setCurrentParent(this);

        ExtLibPanelUtil.initDataNode(this, null, EXT_LIB_ATTR_CONFIGURATION);

        createTopSection();
        createLinkButtons();
        super.createContents();
    }

    protected Composite createPanel(Composite parent, int columns) {

        Composite c = new Composite(this, SWT.NONE);
        GridLayout gl = SWTLayoutUtils.createLayoutNoMarginDefaultSpacing(columns);
        gl.marginWidth = gl.marginLeft = 0;
        c.setLayout(gl);
        GridData data = SWTLayoutUtils.createGDFillHorizontalNoGrab();
        data.horizontalIndent = 0;
        data.horizontalSpan = 2;
        c.setLayoutData(data);
        return c;
    }

    private Composite _desc;

    protected Composite getDescriptionPanel() {
        if (null == _desc) {
            _desc = createPanel(this, 1);
        }
        return _desc;
    }

    protected void createTopSection() {
        String desc = getLinkAttributeDescription();
        if (StringUtil.isNotEmpty(desc)) {
            Composite c = getDescriptionPanel();
            Label l = new Label(c, SWT.NONE);
            GridData gd = new GridData();
            gd.horizontalIndent = getControlIndentAmt();
            l.setText(desc);
            l.setLayoutData(gd);
        }
    }

    protected void clearLinkSelection() {

        if (null != _linkViewer) {
            _linkViewer.setSelection(new StructuredSelection());
        }
    }

    protected void enableButtons(boolean enabled) {
        if (null != _addLink)
            _addLink.setEnabled(enabled);
        if (null != _addChildLink)
            _addChildLink.setEnabled(false); /// selection should enable this when appropriate
    }

    protected void enableDelete(boolean enabled) {
        if (enabled && _linkViewer != null) {
            ISelection selection = _linkViewer.getSelection();
            if (selection.isEmpty()) {
                enabled = false; //can't enable delete if there is nothing selected in the table
            }
        }
        if (null != _removeLink) {
            _removeLink.setEnabled(enabled);
        }
    }

    protected void updateViewers() {
        if (null != _linkViewer) {
            _linkViewer.refresh();
        }
        if (null != _complexComposite) {
            _complexComposite.update();
        }
    }

    protected void createLinkButtons() {
        CustomComposite buttonParent = new CustomComposite(this, SWT.NONE, "button.parent.id"); // $NON-NLS-1$
        GridLayout gl = SWTLayoutUtils.createLayoutDefaultSpacing(3);
        gl.marginWidth = 0;
        buttonParent.setLayout(gl);
        GridData gd = SWTLayoutUtils.createGDFillHorizontal();
        gd.horizontalSpan = 2;
        gd.horizontalIndent = getControlIndentAmt();
        buttonParent.setLayoutData(gd);

        _addLink = new CustomButton(buttonParent, SWT.PUSH, "add.link.id"); // $NON-NLS-1$
        _addLink.setText("Add Item"); // $NLX-AbstractTreeNodePanel.AddItem-1$
        _addLink.addSelectionListener(new SelectionAdapter() {

            @Override
            public void widgetSelected(SelectionEvent event) {
                Button item = (Button) event.widget;
                Rectangle rect = item.getBounds();
                Point pt = item.getParent().toDisplay(new Point(rect.x, rect.y));
                _linkMenu.setLocation(pt.x, pt.y + rect.height);

                _linkMenu.setVisible(true);
                while (!_linkMenu.isDisposed() && _linkMenu.isVisible()) {
                    if (!getDisplay().readAndDispatch()) {
                        getDisplay().sleep();
                    }
                }
            }
        });

        _addChildLink = new CustomButton(buttonParent, SWT.PUSH, "add.child.id"); // $NON-NLS-1$
        _addChildLink.setText("Add Child"); // $NLX-AbstractTreeNodePanel.AddChild-1$
        _addChildLink.setEnabled(false); // will enable on appropriate link selection
        _addChildLink.addSelectionListener(new SelectionAdapter() {

            public void widgetSelected(SelectionEvent event) {
                Button item = (Button) event.widget;
                Rectangle rect = item.getBounds();
                Point pt = item.getParent().toDisplay(new Point(rect.x, rect.y));
                _childLinkMenu.setLocation(pt.x, pt.y + rect.height);

                _childLinkMenu.setVisible(true);
                while (!_childLinkMenu.isDisposed() && _childLinkMenu.isVisible()) {
                    if (!getDisplay().readAndDispatch()) {
                        getDisplay().sleep();
                    }
                }
            }
        });
        _removeLink = new CustomButton(buttonParent, SWT.PUSH, "remove.id"); // $NON-NLS-1$
        _removeLink.setText("Remove"); // $NLX-AbstractTreeNodePanel.Remove-1$
        _removeLink.setEnabled(false); // will enable on appropriate link selection
        _removeLink.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent event) {
                AbstractTreeNodePanel.this.removeSelected();
            }
        });
    }

    protected void refreshMoveButtons(Element e) {
        if (_moveUp != null && !_moveUp.isDisposed()) {
            boolean enabled = canMoveUp(e);
            if (enabled) {
                _moveUp.setImage(getEnabledImage(true));
            } else {
                _moveUp.setImage(getDisabledImage(true));
            }
            _moveUp.setEnabled(enabled);
        }
        if (_moveDown != null && !_moveDown.isDisposed()) {
            boolean enabled = canMoveDown(e);
            if (enabled) {
                _moveDown.setImage(getEnabledImage(false));
            } else {
                _moveDown.setImage(getDisabledImage(false));
            }
            _moveDown.setEnabled(enabled);
        }
    }

    /**
     * Move the element down in the underlying model
     */
    protected void moveDown(Element e) {
        Node nextSibling = XSPNodeUtil.getNextSibling(e);
        if (nextSibling != null) {
            nextSibling = XSPNodeUtil.getNextSibling(nextSibling);
            if (nextSibling != null) {
                e.getParentNode().insertBefore(e, nextSibling);
            } else {
                e.getParentNode().appendChild(e);
            }
            XPagesDOMUtil.formatNode(e.getParentNode(), null);
            _linkViewer.setSelection(new StructuredSelection(e));
        }
    }

    /**
     * Move the element up in the underlying model.
     */
    protected void moveUp(Element e) {
        Node prevSibling = XSPNodeUtil.getPreviousSibling(e);
        if (prevSibling != null) {
            e.getParentNode().insertBefore(e, prevSibling);
            XPagesDOMUtil.formatNode(e.getParentNode(), null);
            _linkViewer.setSelection(new StructuredSelection(e));
        }
    }

    protected boolean canMoveUp(Element e) {
        return XSPNodeUtil.getPreviousSibling(e) != null;
    }

    protected boolean canMoveDown(Element e) {
        return XSPNodeUtil.getNextSibling(e) != null;
    }

    private Image getDisabledImage(boolean upImage) {
        if (upImage) {
            return ExtLibToolingPlugin.getImage("moveUpDisabled.gif"); // $NON-NLS-1$
        } else {
            return ExtLibToolingPlugin.getImage("moveDownDisabled.gif"); // $NON-NLS-1$
        }
    }

    private Image getEnabledImage(boolean upImage) {
        if (upImage) {
            return ExtLibToolingPlugin.getImage("moveUp.gif"); // $NON-NLS-1$
        } else {
            return ExtLibToolingPlugin.getImage("moveDown.gif"); // $NON-NLS-1$
        }
    }

    protected TreeViewer getLinkTreeViewer() {
        return _linkViewer;
    }

    /* (non-Javadoc)
     * @see com.ibm.commons.swt.data.layouts.PropLayout#dispose()
     */
    @Override
    public void dispose() {
        IWorkbenchPart part = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActivePart();
        IActionBars bars = null;
        if (part instanceof IViewPart) {
            bars = ((IViewPart) part).getViewSite().getActionBars();
            if (bars != null) {
                bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), null);
                bars.setGlobalActionHandler(ActionFactory.REDO.getId(), null);
            }
        }
        if (!isDisposed()) {
            super.dispose();
        }
    }
}