/* ===========================================================
* TradeManager : a application to trade strategies for the Java(tm) platform
* ===========================================================
*
* (C) Copyright 2011-2011, by Simon Allen and Contributors.
*
* Project Info: org.trade
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* [Java is a trademark or registered trademark of Oracle, Inc.
* in the United States and other countries.]
*
* (C) Copyright 2011-2011, by Simon Allen and Contributors.
*
* Original Author: Simon Allen;
* Contributor(s): -;
*
* Changes
* -------
*
*/
package org.trade.ui.base;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.SystemColor;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.Autoscroll;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.Serializable;
import java.util.Hashtable;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellEditor;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
*/
public class DOMTree extends JTree implements DragSourceListener, DragGestureListener, Autoscroll, TreeModelListener,
TreeSelectionListener, MouseListener {
private static final long serialVersionUID = -8742998183708844989L;
private final static Logger _log = LoggerFactory.getLogger(DOMTree.class);
/** Stores the selected node info */
protected TreePath m_selectedTreePath = null;
protected DefaultMutableTreeNode m_selectedNode = null;
/** Variables needed for DnD */
private DragSource m_dragSource = null;
private static final int AUTOSCROLL_MARGIN = 12;
private BufferedImage m_imgGhost; // The 'drag image'
private Point m_ptOffset = new Point(); // Where, in the drag image, the
private DOMTreeModel m_model = null;
private JPopupMenu popup = null;
private boolean m_dirty = false;
private BasePanel m_basePanel = null;
/**
* Constructor for DOMTree.
*
* @param basePanel
* BasePanel
*/
public DOMTree(BasePanel basePanel) {
this(null, basePanel);
this.addMouseListener(this);
}
/**
* Constructs a tree with the specified document. * @param document Document
*
* @param basePanel
* BasePanel
*/
public DOMTree(Document document, BasePanel basePanel) {
super();
m_basePanel = basePanel;
m_model = new DOMTreeModel(document);
this.setModel(m_model);
m_model.addTreeModelListener(this);
// set tree properties
setRootVisible(false);
XMLTreeCellRenderer xMLTreeCellRenderer = new XMLTreeCellRenderer();
setCellRenderer(xMLTreeCellRenderer);
setCellEditor(new XMLTreeCellEditor(this, xMLTreeCellRenderer));
this.setEditable(true);
addTreeSelectionListener(this);
m_dragSource = DragSource.getDefaultDragSource();
DragGestureRecognizer dgr = m_dragSource.createDefaultDragGestureRecognizer(this, // DragSource
DnDConstants.ACTION_COPY_OR_MOVE, // specifies valid
// actions
this // DragGestureListener
);
/*
* Eliminates right mouse clicks as valid actions - useful especially if
* you implement a JPopupMenu for the JTree
*/
dgr.setSourceActions(dgr.getSourceActions() & ~InputEvent.BUTTON3_MASK);
/*
* First argument: Component to associate the target with Second
* argument: DropTargetListener
*/
// Also, make this JTree a drag target
DropTarget dropTarget = new DropTarget(this, new CDropTargetListener());
dropTarget.setDefaultActions(DnDConstants.ACTION_COPY_OR_MOVE);
// unnecessary, but gives FileManager look
putClientProperty("JTree.lineStyle", "Angled");
this.addMouseListener(this);
}
//
// Public methods
//
/**
* Sets the document. * @param document Document
*/
public void setDocument(Document document) {
((DOMTreeModel) getModel()).setDocument(document);
expandRow(0);
}
/**
* Returns the document. * @return Document
*/
public Document getDocument() {
return ((DOMTreeModel) getModel()).getDocument();
}
/**
* get the org.w3c.Node for a MutableTreeNode. * @param treeNode Object
*
* @return Node
*/
public Node getNode(Object treeNode) {
return ((DOMTreeModel) getModel()).getNode(treeNode);
}
/**
* Returns The selected node * @return DefaultMutableTreeNode
*/
public DefaultMutableTreeNode getSelectedNode() {
return m_selectedNode;
}
/**
* Method isDirty.
*
* @return boolean
*/
public boolean isDirty() {
return m_dirty;
}
// Ok, weve been told to scroll because the mouse cursor is in our
// scroll zone.
/**
* Method autoscroll.
*
* @param pt
* Point
* @see java.awt.dnd.Autoscroll#autoscroll(Point)
*/
public void autoscroll(Point pt) {
// Figure out which row were on.
int nRow = getRowForLocation(pt.x, pt.y);
// If we are not on a row then ignore this autoscroll request
if (nRow < 0) {
return;
}
Rectangle raOuter = getBounds();
// Now decide if the row is at the top of the screen or at the
// bottom. We do this to make the previous row (or the next
// row) visible as appropriate. If were at the absolute top or
// bottom, just return the first or last row respectively.
nRow = ((pt.y + raOuter.y) <= AUTOSCROLL_MARGIN) // Is row at top of
// screen?
? (nRow <= 0 ? 0 : nRow - 1) // Yes, scroll up one row
: (nRow < (getRowCount() - 1) ? nRow + 1 : nRow); // No, scroll
// down one row
scrollRowToVisible(nRow);
}
// Calculate the insets for the *JTREE*, not the viewport
// the tree is in. This makes it a bit messy.
/**
* Method getAutoscrollInsets.
*
* @return Insets
* @see java.awt.dnd.Autoscroll#getAutoscrollInsets()
*/
public Insets getAutoscrollInsets() {
Rectangle raOuter = getBounds();
Rectangle raInner = getParent().getBounds();
return new Insets((raInner.y - raOuter.y) + AUTOSCROLL_MARGIN, (raInner.x - raOuter.x) + AUTOSCROLL_MARGIN,
(raOuter.height - raInner.height - raInner.y) + raOuter.y + AUTOSCROLL_MARGIN,
(raOuter.width - raInner.width - raInner.x) + raOuter.x + AUTOSCROLL_MARGIN);
}
// Use this method if you want to see the boundaries of the
// autoscroll active region. Toss it out, otherwise.
/**
* Method paintComponent.
*
* @param g
* Graphics
*/
public void paintComponent(Graphics g) {
super.paintComponent(g);
Rectangle raOuter = getBounds();
Rectangle raInner = getParent().getBounds();
g.setColor(Color.red);
g.drawRect(-raOuter.x + 12, -raOuter.y + 12, raInner.width - 24, raInner.height - 24);
}
// TreeModelListener interface...
/**
* Method treeNodesChanged.
*
* @param e
* TreeModelEvent
* @see javax.swing.event.TreeModelListener#treeNodesChanged(TreeModelEvent)
*/
public void treeNodesChanged(TreeModelEvent e) {
}
/**
* Method treeNodesInserted.
*
* @param e
* TreeModelEvent
* @see javax.swing.event.TreeModelListener#treeNodesInserted(TreeModelEvent)
*/
public void treeNodesInserted(TreeModelEvent e) {
// We need to reset the selection path to the node just inserted
int nChildIndex = e.getChildIndices()[0];
TreePath pathParent = e.getTreePath();
setSelectionPath(getChildPath(pathParent, nChildIndex));
}
/**
* Method treeNodesRemoved.
*
* @param e
* TreeModelEvent
* @see javax.swing.event.TreeModelListener#treeNodesRemoved(TreeModelEvent)
*/
public void treeNodesRemoved(TreeModelEvent e) {
}
/**
* Method treeStructureChanged.
*
* @param e
* TreeModelEvent
* @see javax.swing.event.TreeModelListener#treeStructureChanged(TreeModelEvent)
*/
public void treeStructureChanged(TreeModelEvent e) {
}
// More helpers...
/**
* Method getChildPath.
*
* @param pathParent
* TreePath
* @param nChildIndex
* int
* @return TreePath
*/
private TreePath getChildPath(TreePath pathParent, int nChildIndex) {
TreeModel model = getModel();
return pathParent.pathByAddingChild(model.getChild(pathParent.getLastPathComponent(), nChildIndex));
}
/**
* Method isRootPath.
*
* @param path
* TreePath
* @return boolean
*/
private boolean isRootPath(TreePath path) {
return isRootVisible() && (getRowForPath(path) == 0);
}
/**
* Method mousePressed.
*
* @param evt
* MouseEvent
* @see java.awt.event.MouseListener#mousePressed(MouseEvent)
*/
public void mousePressed(MouseEvent evt) {
if (evt.isPopupTrigger()) {
createPopup(evt.getPoint());
}
}
/**
* Method mouseReleased.
*
* @param evt
* MouseEvent
* @see java.awt.event.MouseListener#mouseReleased(MouseEvent)
*/
public void mouseReleased(MouseEvent evt) {
if (evt.isPopupTrigger()) {
createPopup(evt.getPoint());
}
}
/**
* Method mouseClicked.
*
* @param evt
* MouseEvent
* @see java.awt.event.MouseListener#mouseClicked(MouseEvent)
*/
public void mouseClicked(MouseEvent evt) {
if (evt.isPopupTrigger()) {
if (this.isEnabled()) {
createPopup(evt.getPoint());
}
}
}
/**
* Method mouseEntered.
*
* @param evt
* MouseEvent
* @see java.awt.event.MouseListener#mouseEntered(MouseEvent)
*/
public void mouseEntered(MouseEvent evt) {
}
/**
* Method mouseExited.
*
* @param evt
* MouseEvent
* @see java.awt.event.MouseListener#mouseExited(MouseEvent)
*/
public void mouseExited(MouseEvent evt) {
}
/**
* Method createPopup.
*
* @param point
* Point
*/
protected void createPopup(Point point) {
popup = new JPopupMenu();
BaseMenuItem copy = new BaseMenuItem(m_basePanel, BaseUIPropertyCodes.COPY);
popup.add(copy);
BaseMenuItem cut = new BaseMenuItem(m_basePanel, BaseUIPropertyCodes.CUT);
popup.add(cut);
BaseMenuItem paste = new BaseMenuItem(m_basePanel, BaseUIPropertyCodes.PASTE);
popup.add(paste);
// Try to make the popup lightweight
point = getSuitableLocation(point, popup.getPreferredSize(), this);
popup.show(this, point.x, point.y);
}
/**
* Method getComponentContainer.
*
* @param c
* Component
* @return Component
*/
private Component getComponentContainer(Component c) {
Component topLevel = c;
while ((topLevel != null) && !(topLevel instanceof javax.swing.JFrame)
&& !(topLevel instanceof javax.swing.JDialog)) {
topLevel = topLevel.getParent();
}
return topLevel;
}
/**
* Method getSuitableLocation.
*
* @param point
* Point
* @param d
* Dimension
* @param c
* Component
* @return Point
*/
protected Point getSuitableLocation(Point point, Dimension d, Component c) {
// First locate the parent JFrame or JDialog
Component topLevel = this.getComponentContainer(c);
if (topLevel != null) {
int newX; // New proposed x co-ordinate
int newY; // New proposed y co-ordinate
// We have a top-level parent
Rectangle parentBounds = topLevel.getBounds(); // Parent bounds,
// screen-relative
// Get proposed location, relative to the screen
SwingUtilities.convertPointToScreen(point, c);
if ((point.x + d.width) > (parentBounds.x + parentBounds.width)) {
// Popup overhangs to the right
newX = (parentBounds.x + parentBounds.width) - d.width;
} else {
newX = point.x;
}
if ((point.y + d.height) > (parentBounds.y + parentBounds.height)) {
// Popup ends below frame
newY = (parentBounds.y + parentBounds.height) - d.height;
} else {
newY = point.y;
}
// Change location only if necessary AND if
// we can make the popup fit inside the frame
if ((newX >= 0) && (newY >= 0)) {
point.x = newX;
point.y = newY;
}
// Convert back to relative co-ordinates
SwingUtilities.convertPointFromScreen(point, c);
}
return point;
}
/**
* DragGestureListener interface method * @param e DragGestureEvent
*
* @see java.awt.dnd.DragGestureListener#dragGestureRecognized(DragGestureEvent)
*/
public void dragGestureRecognized(DragGestureEvent e) {
DefaultMutableTreeNode dragNode = getSelectedNode();
Node node = getNode(dragNode);
if ((dragNode != null) && !(node instanceof Element)) {
Point ptDragOrigin = e.getDragOrigin();
TreePath path = getPathForLocation(ptDragOrigin.x, ptDragOrigin.y);
if (path == null) {
return;
}
if (isRootPath(path)) {
return; // Ignore user trying to drag the root node
}
// Work out the offset of the drag point from the TreePath bounding
// rectangle origin
Rectangle raPath = getPathBounds(path);
m_ptOffset.setLocation(ptDragOrigin.x - raPath.x, ptDragOrigin.y - raPath.y);
// Get the cell renderer (which is a XMLTreeCellRenderer) for the
// path being dragged
XMLTreeCellRenderer lbl = (XMLTreeCellRenderer) getCellRenderer().getTreeCellRendererComponent(this, // tree
path.getLastPathComponent(), // value
false, // isSelected (dont want a colored
// background)
isExpanded(path), // isExpanded
getModel().isLeaf(path.getLastPathComponent()), // isLeaf
0, // row (not important for rendering)
false // hasFocus (dont want a focus rectangle)
);
lbl.setSize((int) raPath.getWidth(), (int) raPath.getHeight()); // <--
// Get a buffered image of the selection for dragging a ghost image
m_imgGhost = new BufferedImage((int) raPath.getWidth(), (int) raPath.getHeight(),
BufferedImage.TYPE_INT_ARGB_PRE);
Graphics2D g2 = m_imgGhost.createGraphics();
// Ask the cell renderer to paint itself into the BufferedImage
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0.5f)); // Make
// the
// image
// ghostlike
lbl.paint(g2);
// Now paint a gradient UNDER the ghosted JLabel text (but not under
// the icon if any)
// Note: this will need tweaking if your icon is not positioned to
// the left of the text
Icon icon = lbl.getIcon();
int nStartOfText = (icon == null) ? 0 : icon.getIconWidth() + lbl.getIconTextGap();
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_OVER, 0.5f)); // Make
// the
// gradient
// ghostlike
g2.setPaint(new GradientPaint(nStartOfText, 0, SystemColor.controlShadow, getWidth(), 0,
new Color(255, 255, 255, 0)));
g2.fillRect(nStartOfText, 0, getWidth(), m_imgGhost.getHeight());
g2.dispose();
setSelectionPath(path); // Select this path in the tree
StringData stringData = (StringData) dragNode.getUserObject();
stringData.setPath(path);
dragNode.setUserObject(stringData);
m_selectedTreePath = path;
// Get the Transferable Object
Transferable transferable = (Transferable) dragNode.getUserObject();
e.startDrag(null, m_imgGhost, new Point(5, 5), transferable, this);
}
}
/**
* DragSourceListener interface method * @param dsde DragSourceDropEvent
*
* @see java.awt.dnd.DragSourceListener#dragDropEnd(DragSourceDropEvent)
*/
public void dragDropEnd(DragSourceDropEvent dsde) {
}
/**
* DragSourceListener interface method * @param dsde DragSourceDragEvent
*
* @see java.awt.dnd.DragSourceListener#dragEnter(DragSourceDragEvent)
*/
public void dragEnter(DragSourceDragEvent dsde) {
}
/**
* DragSourceListener interface method * @param dsde DragSourceDragEvent
*
* @see java.awt.dnd.DragSourceListener#dragOver(DragSourceDragEvent)
*/
public void dragOver(DragSourceDragEvent dsde) {
}
/**
* DragSourceListener interface method * @param dsde DragSourceDragEvent
*
* @see java.awt.dnd.DragSourceListener#dropActionChanged(DragSourceDragEvent)
*/
public void dropActionChanged(DragSourceDragEvent dsde) {
}
/**
* DragSourceListener interface method * @param dsde DragSourceEvent
*
* @see java.awt.dnd.DragSourceListener#dragExit(DragSourceEvent)
*/
public void dragExit(DragSourceEvent dsde) {
}
/**
* TreeSelectionListener - sets selected node * @param evt
* TreeSelectionEvent
*
* @see javax.swing.event.TreeSelectionListener#valueChanged(TreeSelectionEvent)
*/
public void valueChanged(TreeSelectionEvent evt) {
m_selectedTreePath = evt.getNewLeadSelectionPath();
if (m_selectedTreePath == null) {
m_selectedNode = null;
return;
}
m_selectedNode = (DefaultMutableTreeNode) m_selectedTreePath.getLastPathComponent();
}
/**
* Method testDropTarget.
*
* @param destination
* TreePath
* @param dropper
* TreePath
* @return String
*/
private String testDropTarget(TreePath destination, TreePath dropper) {
boolean destinationPathIsNull = destination == null;
if (destinationPathIsNull) {
return "Invalid drop location.";
}
DefaultMutableTreeNode node = (DefaultMutableTreeNode) destination.getLastPathComponent();
if (!node.getAllowsChildren()) {
return "This node does not allow children";
}
if (destination.equals(dropper)) {
return "Destination cannot be same as source";
}
if (dropper.isDescendant(destination)) {
return "Destination node cannot be a descendant.";
}
if (dropper.getParentPath().equals(destination)) {
return "Destination node cannot be a parent.";
}
return null;
}
/**
*/
static class DOMTreeModel extends DefaultTreeModel implements Serializable {
private static final long serialVersionUID = 558225099585471370L;
private Document document;
private Hashtable<MutableTreeNode, Node> nodeMap = new Hashtable<MutableTreeNode, Node>();
public DOMTreeModel() {
this(null);
}
/**
* Constructor for DOMTreeModel.
*
* @param document
* Document
*/
public DOMTreeModel(Document document) {
super(new DefaultMutableTreeNode());
setDocument(document);
}
/**
* Method setDocument.
*
* @param document
* Document
*/
public synchronized void setDocument(Document document) {
// save document
this.document = document;
// clear tree and re-populate
((DefaultMutableTreeNode) getRoot()).removeAllChildren();
nodeMap.clear();
buildTree();
fireTreeStructureChanged(this, new Object[] { getRoot() }, new int[0], new Object[0]);
} // setDocument(Document)
/**
* Returns the document. * @return Document
*/
public Document getDocument() {
return document;
}
/**
* get the org.w3c.Node for a MutableTreeNode. * @param treeNode Object
*
* @return Node
*/
public Node getNode(Object treeNode) {
return nodeMap.get(treeNode);
}
private void buildTree() {
// is there anything to do?
if (document == null) {
return;
}
// iterate over children of this node
NodeList nodes = document.getChildNodes();
int len = (nodes != null) ? nodes.getLength() : 0;
MutableTreeNode root = (MutableTreeNode) getRoot();
for (int i = 0; i < len; i++) {
Node node = nodes.item(i);
switch (node.getNodeType()) {
case Node.DOCUMENT_NODE: {
root = insertDocumentNode(node, root);
break;
}
case Node.ELEMENT_NODE: {
insertElementNode(node, root);
break;
}
default:
}
}
}
/**
* Inserts a node and returns a reference to the new node. * @param what
* String
*
* @param where
* MutableTreeNode
* @return MutableTreeNode
*/
private MutableTreeNode insertNode(String what, MutableTreeNode where) {
MutableTreeNode node = new DefaultMutableTreeNode(new StringData(what));
insertNodeInto(node, where, where.getChildCount());
return node;
} // insertNode(Node,MutableTreeNode):MutableTreeNode
/**
* Inserts the document node. * @param what Node
*
* @param where
* MutableTreeNode
* @return MutableTreeNode
*/
private MutableTreeNode insertDocumentNode(Node what, MutableTreeNode where) {
MutableTreeNode treeNode = insertNode("<" + what.getNodeName() + '>', where);
nodeMap.put(treeNode, what);
return treeNode;
}
/**
* Inserts an element node. * @param what Node
*
* @param where
* MutableTreeNode
* @return MutableTreeNode
*/
private MutableTreeNode insertElementNode(Node what, MutableTreeNode where) {
// build up name
StringBuffer name = new StringBuffer();
name.append('<');
name.append(what.getNodeName());
NamedNodeMap attrs = what.getAttributes();
int attrCount = (attrs != null) ? attrs.getLength() : 0;
for (int i = 0; i < attrCount; i++) {
Node attr = attrs.item(i);
name.append(' ');
name.append(attr.getNodeName());
name.append("=\"");
name.append(attr.getNodeValue());
name.append('"');
}
name.append('>');
// insert element node
MutableTreeNode element = insertNode(name.toString(), where);
nodeMap.put(element, what);
// gather up attributes and children nodes
NodeList children = what.getChildNodes();
int len = (children != null) ? children.getLength() : 0;
for (int i = 0; i < len; i++) {
Node node = children.item(i);
switch (node.getNodeType()) {
case Node.CDATA_SECTION_NODE: {
insertCDataSectionNode(node, element); // Add a Section Node
break;
}
case Node.TEXT_NODE: {
insertTextNode(node, element);
break;
}
case Node.ELEMENT_NODE: {
insertElementNode(node, element);
break;
}
}
}
return element;
}
/**
* Inserts a text node. * @param what Node
*
* @param where
* MutableTreeNode
* @return MutableTreeNode
*/
private MutableTreeNode insertTextNode(Node what, MutableTreeNode where) {
String value = what.getNodeValue().trim();
if (value.length() > 0) {
MutableTreeNode treeNode = insertNode(value, where);
nodeMap.put(treeNode, what);
return treeNode;
}
return null;
}
/**
* Inserts a CData Section Node. * @param what Node
*
* @param where
* MutableTreeNode
* @return MutableTreeNode
*/
private MutableTreeNode insertCDataSectionNode(Node what, MutableTreeNode where) {
StringBuffer CSectionBfr = new StringBuffer();
// --- optional --- CSectionBfr.append( "<![CDATA[" );
CSectionBfr.append(what.getNodeValue());
// --- optional --- CSectionBfr.append( "]]>" );
if (CSectionBfr.length() > 0) {
MutableTreeNode treeNode = insertNode(CSectionBfr.toString(), where);
nodeMap.put(treeNode, what);
return treeNode;
}
return null;
}
}
/**
*/
class CDropTargetListener implements DropTargetListener {
// Fields...
private TreePath _pathLast = null;
private Rectangle2D _raCueLine = new Rectangle2D.Float();
private Rectangle2D _raGhost = new Rectangle2D.Float();
private Color _colorCueLine;
private Point _ptLast = new Point();
private Timer _timerHover;
private int _nLeftRight = 0; // Cumulative left/right mouse movement
private BufferedImage _imgRight = new CArrowImage(15, 15, CArrowImage.ARROW_RIGHT);
private BufferedImage _imgLeft = new CArrowImage(15, 15, CArrowImage.ARROW_LEFT);
public int _nShift = 0;
public CDropTargetListener() {
_colorCueLine = new Color(SystemColor.controlShadow.getRed(), SystemColor.controlShadow.getGreen(),
SystemColor.controlShadow.getBlue(), 64);
// Set up a hover timer, so that a node will be automatically
// expanded or collapsed
// if the user lingers on it for more than a short time
_timerHover = new Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
_nLeftRight = 0; // Reset left/right movement trend
if (isRootPath(_pathLast)) {
return; // Do nothing if we are hovering over the root
// node
}
if (isExpanded(_pathLast)) {
collapsePath(_pathLast);
} else {
expandPath(_pathLast);
}
}
});
_timerHover.setRepeats(false); // Set timer to one-shot mode
}
// DropTargetListener interface
/**
* Method dragEnter.
*
* @param e
* DropTargetDragEvent
* @see java.awt.dnd.DropTargetListener#dragEnter(DropTargetDragEvent)
*/
public void dragEnter(DropTargetDragEvent e) {
if (!isDragAcceptable(e)) {
e.rejectDrag();
} else {
e.acceptDrag(e.getDropAction());
}
}
/**
* Method dragExit.
*
* @param e
* DropTargetEvent
* @see java.awt.dnd.DropTargetListener#dragExit(DropTargetEvent)
*/
public void dragExit(DropTargetEvent e) {
if (!DragSource.isDragImageSupported()) {
repaint(_raGhost.getBounds());
}
}
/**
* This is where the ghost image is drawn
*
* @param e
* DropTargetDragEvent
* @see java.awt.dnd.DropTargetListener#dragOver(DropTargetDragEvent)
*/
public void dragOver(DropTargetDragEvent e) {
// Even if the mouse is not moving, this method is still invoked 10
// times per second
Point pt = e.getLocation();
if (pt.equals(_ptLast)) {
return;
}
// Try to determine whether the user is flicking the cursor right or
// left
int nDeltaLeftRight = pt.x - _ptLast.x;
if (((_nLeftRight > 0) && (nDeltaLeftRight < 0)) || ((_nLeftRight < 0) && (nDeltaLeftRight > 0))) {
_nLeftRight = 0;
}
_nLeftRight += nDeltaLeftRight;
_ptLast = pt;
Graphics2D g2 = (Graphics2D) getGraphics();
// If a drag image is not supported by the platform, then draw my
// own drag image
if (!DragSource.isDragImageSupported()) {
paintImmediately(_raGhost.getBounds()); // Rub out the last
// ghost image and cue
// line
// And remember where we are about to draw the new ghost image
_raGhost.setRect(pt.x - m_ptOffset.x, pt.y - m_ptOffset.y, m_imgGhost.getWidth(),
m_imgGhost.getHeight());
g2.drawImage(m_imgGhost, AffineTransform.getTranslateInstance(_raGhost.getX(), _raGhost.getY()), null);
} else
// Just rub out the last cue line
{
paintImmediately(_raCueLine.getBounds());
}
TreePath path = getClosestPathForLocation(pt.x, pt.y);
if (!(path == _pathLast)) {
_nLeftRight = 0; // We've moved up or down, so reset
// left/right movement trend
_pathLast = path;
_timerHover.restart();
}
// In any case draw (over the ghost image if necessary) a cue line
// indicating where a drop will occur
Rectangle raPath = getPathBounds(path);
_raCueLine.setRect(0, raPath.y + (int) raPath.getHeight(), getWidth(), 2);
g2.setColor(_colorCueLine);
g2.fill(_raCueLine);
// Now superimpose the left/right movement indicator if necessary
if (_nLeftRight > 20) {
g2.drawImage(_imgRight, AffineTransform.getTranslateInstance(pt.x - m_ptOffset.x, pt.y - m_ptOffset.y),
null);
_nShift = +1;
} else if (_nLeftRight < -20) {
g2.drawImage(_imgLeft, AffineTransform.getTranslateInstance(pt.x - m_ptOffset.x, pt.y - m_ptOffset.y),
null);
_nShift = -1;
} else {
_nShift = 0;
}
// And include the cue line in the area to be rubbed out next time
_raGhost = _raGhost.createUnion(_raCueLine);
// Do this if you want to prohibit dropping onto the drag source
if (path.equals(m_selectedTreePath)) {
e.rejectDrag();
} else {
e.acceptDrag(e.getDropAction());
}
}
/**
* Method dropActionChanged.
*
* @param e
* DropTargetDragEvent
* @see java.awt.dnd.DropTargetListener#dropActionChanged(DropTargetDragEvent)
*/
public void dropActionChanged(DropTargetDragEvent e) {
if (!isDragAcceptable(e)) {
e.rejectDrag();
} else {
e.acceptDrag(e.getDropAction());
}
}
// DropTargetListener interface method - What we do when drag is
// released
/**
* Method drop.
*
* @param e
* DropTargetDropEvent
* @see java.awt.dnd.DropTargetListener#drop(DropTargetDropEvent)
*/
public void drop(DropTargetDropEvent e) {
try {
_timerHover.stop(); // Prevent hover timer from doing an
// unwanted expandPath or collapsePath
Transferable tr = e.getTransferable();
// flavor not supported, reject drop
if (!tr.isDataFlavorSupported(DataFlavor.stringFlavor)) {
e.rejectDrop();
}
// cast into appropriate data type
StringData childInfo = (StringData) tr.getTransferData(DataFlavor.stringFlavor);
_log.debug("String node value " + childInfo);
// get new parent node
Point loc = e.getLocation();
TreePath destinationPath = getPathForLocation(loc.x, loc.y);
final String msg = testDropTarget(destinationPath, m_selectedTreePath);
if (msg != null) {
e.rejectDrop();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// JOptionPane.showMessageDialog(this.Parent, msg,
// "Error Dialog", JOptionPane.ERROR_MESSAGE);
_log.error(msg);
}
});
return;
}
DefaultMutableTreeNode newParent = (DefaultMutableTreeNode) destinationPath.getLastPathComponent();
// get old parent node
DefaultMutableTreeNode oldParent = (DefaultMutableTreeNode) getSelectedNode().getParent();
// DefaultMutableTreeNode child = new DefaultMutableTreeNode(
// childInfo);
int action = e.getDropAction();
boolean copyAction = (action == DnDConstants.ACTION_COPY);
// make new child node
try {
MutableTreeNode selectedTreeNode = getSelectedNode();
if (null != m_model.getNode(selectedTreeNode)) {
m_model.insertTextNode(m_model.getNode(selectedTreeNode), newParent);
m_model.removeNodeFromParent(selectedTreeNode);
}
if (copyAction) {
e.acceptDrop(DnDConstants.ACTION_COPY);
} else {
oldParent.remove(getSelectedNode());
e.acceptDrop(DnDConstants.ACTION_MOVE);
}
} catch (java.lang.IllegalStateException ils) {
e.rejectDrop();
}
e.getDropTargetContext().dropComplete(true);
// expand nodes appropriately - this probably isnt the best
// way...
DefaultTreeModel model = (DefaultTreeModel) getModel();
model.reload(oldParent);
model.reload(newParent);
TreePath parentPath = new TreePath(newParent.getPath());
expandPath(parentPath);
} catch (IOException io) {
e.rejectDrop();
} catch (UnsupportedFlavorException ufe) {
e.rejectDrop();
}
} // end of method
// Helpers...
/**
* Method isDragAcceptable.
*
* @param e
* DropTargetDragEvent
* @return boolean
*/
public boolean isDragAcceptable(DropTargetDragEvent e) {
// Only accept COPY or MOVE gestures (ie LINK is not supported)
if ((e.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE) == 0) {
return false;
}
// Only accept this particular flavor
if (!e.isDataFlavorSupported(StringData.TREEPATH_FLAVOR)) {
return false;
}
// Do this if you want to prohibit dropping onto the drag source...
Point pt = e.getLocation();
TreePath path = getClosestPathForLocation(pt.x, pt.y);
if (path.equals(m_selectedTreePath)) {
return false;
}
// Do this if you want to select the best flavor on offer...
DataFlavor[] flavors = e.getCurrentDataFlavors();
for (DataFlavor flavor : flavors) {
if (flavor.isMimeTypeEqual(DataFlavor.javaJVMLocalObjectMimeType)) {
return true;
}
}
return true;
}
/**
* Method isDropAcceptable.
*
* @param e
* DropTargetDropEvent
* @return boolean
*/
public boolean isDropAcceptable(DropTargetDropEvent e) {
// Only accept COPY or MOVE gestures (ie LINK is not supported)
if ((e.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE) == 0) {
return false;
}
// Only accept this particular flavor
if (!e.isDataFlavorSupported(StringData.TREEPATH_FLAVOR)) {
return false;
}
// Do this if you want to prohibit dropping onto the drag source...
Point pt = e.getLocation();
TreePath path = getClosestPathForLocation(pt.x, pt.y);
if (path.equals(m_selectedTreePath)) {
return false;
}
// Do this if you want to select the best flavor on offer...
DataFlavor[] flavors = e.getCurrentDataFlavors();
for (DataFlavor flavor : flavors) {
if (flavor.isMimeTypeEqual(DataFlavor.javaJVMLocalObjectMimeType)) {
return true;
}
}
return true;
}
}
/*
* The XMLTreeCellRenderer is an inner class which enables the highlighting
* of errors in the tree and shows the gender values as different icons.
*/
/**
*/
class XMLTreeCellEditor extends DefaultTreeCellEditor {
Image openFolder = DefaultImages.createOpenFolderImage();
Image closedFolder = DefaultImages.createClosedFolderImage();
Image leafImage = DefaultImages.createLeafImage();
/**
* Constructor for XMLTreeCellEditor.
*
* @param tree
* JTree
* @param renderer
* DefaultTreeCellRenderer
*/
public XMLTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer) {
super(tree, renderer);
}
/**
* Method getTreeCellEditorComponent.
*
* @param tree
* JTree
* @param value
* Object
* @param selected
* boolean
* @param expanded
* boolean
* @param leaf
* boolean
* @param row
* int
* @return Component
* @see javax.swing.tree.TreeCellEditor#getTreeCellEditorComponent(JTree,
* Object, boolean, boolean, boolean, int)
*/
public Component getTreeCellEditorComponent(JTree tree, Object value, boolean selected, boolean expanded,
boolean leaf, int row) {
Node node = ((DOMTree) tree).getNode(value);
Component comp = super.getTreeCellEditorComponent(tree, value, selected, expanded, leaf, row);
_log.debug("something to edit :" + node);
return comp;
}
}
/*
* The XMLTreeCellRenderer is an inner class which enables the highlighting
* of errors in the tree and shows the gender values as different icons.
*/
/**
*/
class XMLTreeCellRenderer extends DefaultTreeCellRenderer {
private static final long serialVersionUID = 7664391812385841364L;
Image openFolder = DefaultImages.createOpenFolderImage();
Image closedFolder = DefaultImages.createClosedFolderImage();
Image leafImage = DefaultImages.createLeafImage();
/**
* Method getTreeCellRendererComponent.
*
* @param tree
* JTree
* @param value
* Object
* @param selected
* boolean
* @param expanded
* boolean
* @param leaf
* boolean
* @param row
* int
* @param hasFocus
* boolean
* @return Component
* @see javax.swing.tree.TreeCellRenderer#getTreeCellRendererComponent(JTree,
* Object, boolean, boolean, boolean, int, boolean)
*/
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded,
boolean leaf, int row, boolean hasFocus) {
Node node = ((DOMTree) tree).getNode(value);
Component comp = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
if (selected) {
comp.setBackground(Color.blue);
}
if (node != null) {
_log.debug("something to render :" + node + " class: " + node.getClass().getName());
if (!(node instanceof Element) /* leaf */) {
setIcon(new ImageIcon(leafImage));
} else if (expanded) {
setIcon(new ImageIcon(openFolder));
} else {
setIcon(new ImageIcon(closedFolder));
}
}
if ((node != null) && (node instanceof Element)) {
Element txNode = (Element) node;
Attr txAtt = txNode.getAttributeNode("gender");
if (txAtt != null) {
if (txAtt.getValue().equals("male")) {
setIcon(new ImageIcon("male.gif"));
} else if (txAtt.getValue().equals("female")) {
setIcon(new ImageIcon("female.gif"));
}
}
}
return comp;
}
}
}