/*
* 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);
}
}
}