tufts.vue.OutlineViewTree.java Source code

Java tutorial

Introduction

Here is the source code for tufts.vue.OutlineViewTree.java

Source

/*
* Copyright 2003-2010 Tufts University  Licensed under the
* Educational Community 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.osedu.org/licenses/ECL-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 tufts.vue;

import java.awt.*;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.awt.event.FocusListener;
import java.awt.event.FocusEvent;
import javax.swing.AbstractCellEditor;
import javax.swing.BoxLayout;
import javax.swing.UIManager;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.JViewport;
import javax.swing.JScrollPane;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.tree.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.ImageIcon;
import java.util.Iterator;
import java.util.ArrayList;

/**
 *
 * A class that represents a tree structure which holds the outline view model
 *   
 * Todo: render right from the node labels so all we have to do is repaint to refresh.
 * (still need to modify tree for hierarchy changes tho).
 *
 * @version $Revision: 1.52 $ / $Date: 2010-02-03 19:17:40 $ / $Author: mike $
 * @author  Daisuke Fujiwara
 */

public class OutlineViewTree extends JTree
        implements LWComponent.Listener, LWSelection.Listener, ActiveListener<LWMap> {
    private boolean selectionFromVUE = false;
    private boolean valueChangedState = false;

    //a variable that controls the label change of nodes
    private boolean labelChangeState = false;

    private LWContainer currentContainer = null;
    private tufts.oki.hierarchy.OutlineViewHierarchyModel hierarchyModel = null;

    private ImageIcon nodeIcon = VueResources.getImageIcon("outlineIcon.node");
    private ImageIcon linkIcon = VueResources.getImageIcon("outlineIcon.link");
    private ImageIcon mapIcon = VueResources.getImageIcon("outlineIcon.map");
    private ImageIcon textIcon = VueResources.getImageIcon("outlineIcon.text");

    /** Creates a new instance of OverviewTree */
    public OutlineViewTree() {
        setModel(null);
        setEditable(true);
        setFocusable(true);
        getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
        setCellRenderer(new OutlineViewTreeRenderer());
        //setCellRenderer(new DefaultTreeCellRenderer());
        setCellEditor(new OutlineViewTreeEditor());
        setInvokesStopCellEditing(true); // so focus-loss during edit triggers a save instead of abort edit

        setRowHeight(-1); // ahah: must do this to force variable row height

        //tree selection listener to keep track of the selected node 
        addTreeSelectionListener(new TreeSelectionListener() {
            public void valueChanged(TreeSelectionEvent e) {
                ArrayList selectedComponents = new ArrayList();
                TreePath[] paths = getSelectionPaths();

                //if there is no selected nodes
                if (paths == null) {
                    valueChangedState = false;
                    selectionFromVUE = false;

                    return;
                }

                for (int i = 0; i < paths.length; i++) {
                    DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) paths[i].getLastPathComponent();
                    Object o = treeNode.getUserObject();
                    if (DEBUG.FOCUS)
                        System.out.println(this + " valueChanged in treeNode[" + treeNode + "] userObject="
                                + o.getClass() + "[" + o + "]");
                    tufts.oki.hierarchy.HierarchyNode hierarchyNode = (tufts.oki.hierarchy.HierarchyNode) o;

                    LWComponent component = hierarchyNode.getLWComponent();

                    //if it is not LWMap, add to the selected components list
                    if (!(component instanceof LWMap)) {
                        selectedComponents.add(component);
                    }
                }

                if (!selectionFromVUE) {
                    valueChangedState = true;

                    //                         if(selectedComponents.size() != 0)
                    //                           VUE.getSelection().setTo(selectedComponents.iterator());
                    //                         else
                    //                           VUE.getSelection().clear();
                }

                valueChangedState = false;
                selectionFromVUE = false;
            }
        });
    }

    public OutlineViewTree(LWContainer container) {
        this();
        switchContainer(container);
    }

    public void activeChanged(ActiveEvent<LWMap> e) {
        switchContainer(e.active);
    }

    /**A method which switches the displayed container*/
    public void switchContainer(LWContainer newContainer) {
        //removes itself from the old container's listener list
        if (currentContainer != null) {
            currentContainer.removeLWCListener(this);
            currentContainer.removeLWCListener(hierarchyModel);
        }

        //adds itself to the new container's listener list
        if (newContainer != null) {
            currentContainer = newContainer;

            //creates the new model for the tree with the given new LWContainer
            hierarchyModel = new tufts.oki.hierarchy.OutlineViewHierarchyModel(newContainer);
            DefaultTreeModel model = hierarchyModel.getTreeModel();

            setModel(model);

            currentContainer.addLWCListener(this, LWKey.Label);
            currentContainer.addLWCListener(hierarchyModel, new Object[] { LWKey.ChildrenAdded,
                    LWKey.ChildrenRemoved, LWKey.HierarchyChanged, LWKey.LinkAdded, LWKey.LinkRemoved });
        } else
            setModel(null);
    }

    /**A method that sets the current tree path to the one designated by the given LWComponent*/
    public void setSelectionPath(LWComponent component) {
        //in case the node inspector's outline tree is not initalized

        TreePath path = hierarchyModel.getTreePath(component);
        super.setSelectionPath(path);
        super.expandPath(path);
        super.scrollPathToVisible(path);

    }

    /**A method that sets the current tree paths to the ones designated by the given list of components*/
    public void setSelectionPaths(ArrayList list) {
        TreePath[] paths = new TreePath[list.size()];
        int counter = 0;

        TreePath path;

        for (Iterator i = list.iterator(); i.hasNext();) {
            if ((path = hierarchyModel.getTreePath((LWComponent) i.next())) != null) {
                paths[counter] = path;
                counter++;
            }
        }

        super.setSelectionPaths(paths);
    }

    /**A wrapper method which determines whether the underlying model contains a node with the given component*/
    public boolean contains(LWComponent component) {
        return hierarchyModel.contains(component);
    }

    /**A method which returns whether the model has been intialized or not*/
    public boolean isInitialized() {
        if (hierarchyModel != null)
            return true;

        else
            return false;
    }

    /**A method for handling a LWC event*/
    public void LWCChanged(LWCEvent e) {
        if (e.getComponent() instanceof LWPathway)
            return;

        //when a label on a node was changed
        //Already label filtered. 

        if (!labelChangeState) {
            labelChangeState = true;
            hierarchyModel.updateHierarchyNodeLabel(e.getComponent().getLabel(), e.getComponent().getID());
            repaint();
            labelChangeState = false;
        }
    }

    /** A method for handling LWSelection event **/
    public void selectionChanged(LWSelection selection) {
        if (!isShowing()) // TODO: now we should do an auto-update when made visible
            return;

        if (!valueChangedState) {
            selectionFromVUE = true;

            if (!selection.isEmpty())
                setSelectionPaths(selection);

            //else deselect
            else
                super.setSelectionPath(null);

            //hacking
            selectionFromVUE = false;
        }
    }

    /**A class that specifies the rendering method of the outline view tree*/
    private class OutlineViewTreeRenderer extends OutlineViewRenderElement implements TreeCellRenderer {
        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded,
                boolean leaf, int row, boolean hasFocus) {
            String label = null;

            if (((DefaultMutableTreeNode) value).getUserObject() instanceof tufts.oki.hierarchy.HierarchyNode) {
                tufts.oki.hierarchy.HierarchyNode hierarchyNode = (tufts.oki.hierarchy.HierarchyNode) (((DefaultMutableTreeNode) value)
                        .getUserObject());
                LWComponent component = hierarchyNode.getLWComponent();

                if (component instanceof LWMap)
                    setIcon(mapIcon);

                else if (component instanceof LWNode)
                    setIcon(nodeIcon);

                else if (component instanceof LWText)
                    setIcon(textIcon);

                else if (component instanceof LWLink)
                    setIcon(linkIcon);

                //setText(component.getDisplayLabel());
                // need to update size (but only if label has changed)
                //setPreferredSize(getPreferredSize());
                // doesn't appear to get right size if there's a '.' in the name!
                label = tree.convertValueToText(value, sel, expanded, leaf, row, hasFocus);
                //System.out.println("render text[" + label + "]");
                if (component instanceof LWText) {
                    //System.out.println("NODE");
                    label = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(label);
                    //label = java.net.URLDecoder.decode();
                }
            }
            //else
            //label = tree.convertValueToText(value, sel, expanded, leaf, row, hasFocus);

            if (sel)
                setIsSelected(true);

            else
                setIsSelected(false);

            if (hasFocus)
                setIsFocused(true);

            else
                setIsFocused(false);

            setText(label);

            return this;
        }
    }

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    /**A class that specifies the editing method of the outline view tree*/
    private class OutlineViewTreeEditor extends AbstractCellEditor
            implements TreeCellEditor, KeyListener, FocusListener {
        // This is the component that will handle the editing of the cell value
        private OutlineViewEditorElement editorElement = null;
        private tufts.oki.hierarchy.HierarchyNode hierarchyNode = null;

        private boolean modified = false;
        private final int clickToStartEditing = 2;

        public OutlineViewTreeEditor() {
            editorElement = new OutlineViewEditorElement(this);
        }

        // This method is called when a cell value is edited by the user.
        public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded,
                boolean leaf, int row) {
            //editorElement.setFont(VueConstants.FONT_MEDIUM);
            editorElement.setBackground(Color.white);
            editorElement.setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));

            // Configure the component with the specified value
            String label = tree.convertValueToText(value, isSelected, expanded, leaf, row, true);
            editorElement.setText(label);

            DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
            hierarchyNode = (tufts.oki.hierarchy.HierarchyNode) node.getUserObject();
            LWComponent selectedLWComponent = hierarchyNode.getLWComponent();

            if (selectedLWComponent instanceof LWText)
                editorElement.setIcon(nodeIcon);

            else if (selectedLWComponent instanceof LWText)
                editorElement.setIcon(textIcon);

            else if (selectedLWComponent instanceof LWLink)
                editorElement.setIcon(linkIcon);

            else
                editorElement.setIcon(mapIcon);

            // Return the configured component
            return editorElement;
        }

        // This method is called when editing is completed.
        // It must return the new value to be stored in the cell.
        public Object getCellEditorValue() {
            try {
                String text = editorElement.getText();
                if (DEBUG.FOCUS)
                    System.out.println(this + " getCellEditorValue returns [" + text + "]");

                hierarchyNode.changeLWComponentLabel(text);
            }

            catch (osid.hierarchy.HierarchyException he) {
            }

            return hierarchyNode;
        }

        /** When any key is pressed on the text area, then it sets the flag that the value needs to be modified,
        and when a certain combination of keys are pressed then the tree node value is modified */
        public void keyPressed(KeyEvent e) {
            // if we hit return key either on numpad ("enter" key), or
            // with any modifier down except a shift alone (in case of
            // caps lock) complete the edit.
            if (e.getKeyCode() == KeyEvent.VK_ENTER && (e.getKeyLocation() == KeyEvent.KEY_LOCATION_NUMPAD
                    || (e.getModifiersEx() != 0 && !e.isShiftDown()))) {
                e.consume();
                this.stopCellEditing();
                modified = false;
            } else
                modified = true;
        }

        /** notused */
        public void keyReleased(KeyEvent e) {
        }

        /** not used **/
        public void keyTyped(KeyEvent e) {
        }

        /** The tree node is only editable when it's clicked on more than a specified value */
        public boolean isCellEditable(java.util.EventObject anEvent) {
            if (anEvent instanceof java.awt.event.MouseEvent) {
                return ((java.awt.event.MouseEvent) anEvent).getClickCount() >= clickToStartEditing;
            }

            return true;
        }

        public void focusGained(FocusEvent e) {
            if (DEBUG.FOCUS)
                System.out.println(this + " focusGained from " + e.getOppositeComponent());
        }

        // When the focus is lost and if the text area has been modified, it changes the tree node value
        public void focusLost(FocusEvent e) {
            if (DEBUG.FOCUS)
                System.out.println(this + " focusLost to " + e.getOppositeComponent() + " modified=" + modified);

            if (modified) {
                this.stopCellEditing();
                modified = false;
            }
        }
    }

    /** A class which displays the specified icon*/
    private static class IconPanel extends JPanel {
        private ImageIcon icon = null;

        public IconPanel() {
            setBackground(Color.white);
        }

        public void setIcon(ImageIcon icon) {
            this.icon = icon;

            if (icon != null)
                setPreferredSize(new Dimension(icon.getIconWidth() + 4, icon.getIconHeight() + 4));
        }

        public ImageIcon getIcon() {
            return icon;
        }

        protected void paintComponent(Graphics g) {
            if (icon != null)
                icon.paintIcon(this, g, 0, 0);
        }
    }

    /**A class which is used to display the rednerer for the outline view tree*/
    private static class OutlineViewRenderElement extends JPanel {
        private final JTextArea label;
        private final IconPanel iconPanel;

        private static final Color selectedColor = VueResources.getColor("outlineTreeSelectionColor");
        private static final Color nonSelectedColor = Color.white;

        public OutlineViewRenderElement() {
            //super(new FlowLayout(FlowLayout.LEFT, 5, 2));
            super();
            BoxLayout box = new BoxLayout(OutlineViewRenderElement.this, BoxLayout.X_AXIS);
            setLayout(box);
            setBorder(new EmptyBorder(new Insets(2, 2, 2, 2)));
            //super(new BorderLayout());
            setBackground(Color.white);

            label = new JTextArea();

            label.setFont(VueResources.getFont("toolbar.font"));
            label.setEditable(false);
            iconPanel = new IconPanel();

            add(iconPanel);
            add(label);
            //add(iconPanel, BorderLayout.WEST);
            //add(label, BorderLayout.CENTER);
        }

        /**A method which gets called with the value of whether the renderer is focused */
        public void setIsFocused(boolean value) {
        }

        /**A method which gets called with the value of whether the renderer is selected */
        public void setIsSelected(boolean value) {
            if (value)
                label.setBackground(selectedColor);
            else
                label.setBackground(nonSelectedColor);
        }

        public void setText(String text) {
            label.setText(text);
        }

        public String getText() {
            return label.getText();
        }

        public void setIcon(ImageIcon icon) {
            iconPanel.setIcon(icon);
        }
    }

    /**A class which is used to display the editor for the outline view tree*/
    private class OutlineViewEditorElement extends JPanel {
        private IconPanel iconPanel = null;
        private JTextArea label = null;
        private JViewport viewPort = null;
        private JScrollPane scrollPane = null;
        private OutlineViewTreeEditor editor = null;

        public OutlineViewEditorElement(OutlineViewTreeEditor editor) {
            BoxLayout box = new BoxLayout(OutlineViewEditorElement.this, BoxLayout.X_AXIS);
            setLayout(box);
            setBorder(new EmptyBorder(new Insets(2, 2, 2, 2)));

            label = new JTextArea();
            label.setFont(VueResources.getFont("toolbar.font"));
            label.setEditable(true);
            label.setLineWrap(true);
            label.setColumns(30);

            this.editor = editor;
            addKeyListener(editor);
            //addFocusListener(editor);
            label.addFocusListener(this.editor);

            iconPanel = new IconPanel();

            scrollPane = new JScrollPane(label);
            //viewPort = new JViewport();
            //viewPort.setView(label);

            add(iconPanel);
            add(scrollPane);
            //add(viewPort);     
        }

        public JViewport getViewPort() {
            return viewPort;
        }

        public void addKeyListener(KeyListener l) {
            label.addKeyListener(l);
        }

        public void removeKeyListener(KeyListener l) {
            label.removeKeyListener(l);
        }

        public void setText(String text) {
            label.setText(text);
        }

        public String getText() {
            return label.getText();
        }

        public void setIcon(ImageIcon icon) {
            iconPanel.setIcon(icon);
        }
    }
}