//$HeadURL$ /*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001-2012 by: Department of Geography, University of Bonn http://www.giub.uni-bonn.de/deegree/ lat/lon GmbH http://www.lat-lon.de 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: lat/lon GmbH Aennchenstr. 19 53177 Bonn Germany http://www.lat-lon.de Prof. Dr. Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: greve@giub.uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.igeo.views.swing.layerlist; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Font; import java.awt.Graphics; import java.awt.Point; import java.awt.Rectangle; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.DnDConstants; import java.awt.dnd.DragGestureEvent; import java.awt.dnd.DragGestureListener; 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.io.IOException; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JToolBar; import javax.swing.JTree; import javax.swing.UIManager; import javax.swing.plaf.ColorUIResource; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.MutableTreeNode; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.igeo.ApplicationContainer; import org.deegree.igeo.ChangeListener; import org.deegree.igeo.ValueChangedEvent; import org.deegree.igeo.commands.model.MoveLayerCommand; import org.deegree.igeo.dataadapter.AdapterEvent; import org.deegree.igeo.dataadapter.AdapterEvent.ADAPTER_EVENT_TYPE; import org.deegree.igeo.mapmodel.Layer; import org.deegree.igeo.mapmodel.LayerChangedEvent; import org.deegree.igeo.mapmodel.LayerGroup; import org.deegree.igeo.mapmodel.MapModel; import org.deegree.igeo.mapmodel.MapModelChangedEvent; import org.deegree.igeo.mapmodel.MapModelEntry; import org.deegree.igeo.views.swing.util.IconRegistry; import org.deegree.kernel.Command; /** * TODO add class documentation * * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> * @author last edited by: $Author$ * * @version. $Revision$, $Date$ */ public class DnDJTree extends JTree implements DragSourceListener, DropTargetListener, DragGestureListener, ChangeListener { private static final long serialVersionUID = 867560018228692980L; private static final ILogger LOG = LoggerFactory.getLogger( DnDJTree.class ); private static final Color borderColor = Color.decode( "0x65a360" ); private static DataFlavor localObjectFlavor; static { try { localObjectFlavor = new DataFlavor( DataFlavor.javaJVMLocalObjectMimeType ); } catch ( ClassNotFoundException ex ) { ex.printStackTrace( System.out ); } } private static DataFlavor[] supportedFlavors = { localObjectFlavor }; private DragSource dragSource; private TreeNode dropTargetNode = null; private TreeNode draggedNode = null; private MapModel mapModel; private Point clickPoint; private ApplicationContainer<Container> appContainer; private boolean top; /** * * @param root * @param appContainer * @param mapModel */ DnDJTree( TreeNode root, ApplicationContainer<Container> appContainer, MapModel mapModel ) { super( root ); this.appContainer = appContainer; this.mapModel = mapModel; this.mapModel.addChangeListener( this ); _init(); } private void _init() { DnDJTreeCellRenderer tcr = new DnDJTreeCellRenderer(); setCellRenderer( tcr ); dragSource = new DragSource(); dragSource.createDefaultDragGestureRecognizer( this, DnDConstants.ACTION_MOVE, this ); new DropTarget( this, this ); } /** * @param dge */ public void dragGestureRecognized( DragGestureEvent dge ) { clickPoint = dge.getDragOrigin(); TreePath path = getPathForLocation( clickPoint.x, clickPoint.y ); if ( path == null ) { LOG.logWarning( "NOT A NODE" ); return; } draggedNode = (DefaultMutableTreeNode) path.getLastPathComponent(); Transferable trans = new RJLTransferable( draggedNode ); dragSource.startDrag( dge, Cursor.getPredefinedCursor( Cursor.MOVE_CURSOR ), trans, this ); } /* * (non-Javadoc) * * @see java.awt.dnd.DragSourceListener#dragEnter(java.awt.dnd.DragSourceDragEvent) */ public void dragEnter( DragSourceDragEvent event ) { } /* * (non-Javadoc) * * @see java.awt.dnd.DragSourceListener#dragOver(java.awt.dnd.DragSourceDragEvent) */ public void dragOver( DragSourceDragEvent event ) { } /* * (non-Javadoc) * * @see java.awt.dnd.DragSourceListener#dropActionChanged(java.awt.dnd.DragSourceDragEvent) */ public void dropActionChanged( DragSourceDragEvent event ) { } /* * (non-Javadoc) * * @see java.awt.dnd.DragSourceListener#dragDropEnd(java.awt.dnd.DragSourceDropEvent) */ public void dragDropEnd( DragSourceDropEvent event ) { dropTargetNode = null; draggedNode = null; repaint(); } /* * (non-Javadoc) * * @see java.awt.dnd.DragSourceListener#dragExit(java.awt.dnd.DragSourceEvent) */ public void dragExit( DragSourceEvent event ) { } /* * (non-Javadoc) * * @see java.awt.dnd.DropTargetListener#dragEnter(java.awt.dnd.DropTargetDragEvent) */ public void dragEnter( DropTargetDragEvent dtde ) { dtde.acceptDrag( DnDConstants.ACTION_COPY_OR_MOVE ); } /* * (non-Javadoc) * * @see java.awt.dnd.DropTargetListener#dragOver(java.awt.dnd.DropTargetDragEvent) */ public void dragOver( DropTargetDragEvent dtde ) { Point dragPoint = dtde.getLocation(); TreePath path = getPathForLocation( dragPoint.x, dragPoint.y ); if ( path == null ) { dropTargetNode = null; } else { dropTargetNode = (TreeNode) path.getLastPathComponent(); } Rectangle pathBounds = getPathBounds( path ); top = dragPoint.y < ( pathBounds.y + ( pathBounds.height / 2 ) ); repaint(); } /* * (non-Javadoc) * * @see java.awt.dnd.DropTargetListener#dropActionChanged(java.awt.dnd.DropTargetDragEvent) */ public void dropActionChanged( DropTargetDragEvent event ) { } /* * (non-Javadoc) * * @see java.awt.dnd.DropTargetListener#dragExit(java.awt.dnd.DropTargetEvent) */ public void dragExit( DropTargetEvent event ) { } /* * (non-Javadoc) * * @see java.awt.dnd.DropTargetListener#drop(java.awt.dnd.DropTargetDropEvent) */ public void drop( DropTargetDropEvent dtde ) { Point dropPoint = dtde.getLocation(); TreePath path = getPathForLocation( dropPoint.x, dropPoint.y ); Rectangle pathBounds = getPathBounds( path ); boolean before = dropPoint.y < ( pathBounds.y + ( pathBounds.height / 2 ) ); DefaultMutableTreeNode droppedNode = null; try { if ( path != null ) { dtde.acceptDrop( DnDConstants.ACTION_MOVE ); Object droppedObject = dtde.getTransferable().getTransferData( localObjectFlavor ); if ( droppedObject instanceof MutableTreeNode ) { droppedNode = (DefaultMutableTreeNode) droppedObject; } else { droppedNode = new DefaultMutableTreeNode( droppedObject ); } // node where the mouse button has been released DefaultMutableTreeNode targetNode = (DefaultMutableTreeNode) path.getLastPathComponent(); if ( droppedNode.getUserObject().equals( targetNode.getUserObject() ) ) { // not a useful operation return; } // check if target node is a direct or indirect child of dropped node if ( !isChild( droppedNode, targetNode ) ) { // actualize JTree model if ( targetNode.getUserObject() instanceof Layer ) { // if the target node is a leaf (it is a layer that contains data) the // dropped node will be inserted after it DefaultMutableTreeNode parent = (DefaultMutableTreeNode) targetNode.getParent(); if ( parent == null ) { parent = (DefaultMutableTreeNode) path.getParentPath().getLastPathComponent(); } if ( droppedNode.getParent() != null ) { ( (DefaultTreeModel) getModel() ).removeNodeFromParent( droppedNode ); int index = parent.getIndex( targetNode ) + 1; if ( before ) { index--; } if ( index >= 0 ) { ( (DefaultTreeModel) getModel() ).insertNodeInto( droppedNode, parent, index ); } } } else { // if the target node is not a leaf (it is a layergroup) the // dropped node will be inserted within it if ( !droppedNode.equals( targetNode ) ) { ( (DefaultTreeModel) getModel() ).removeNodeFromParent( droppedNode ); ( (DefaultTreeModel) getModel() ).insertNodeInto( droppedNode, targetNode, targetNode.getChildCount() ); } } // actualize mapmodel if ( droppedNode.getUserObject() instanceof MapModelEntry ) { LayerGroup parent = null; MapModelEntry antecessor = (MapModelEntry) targetNode.getUserObject(); if ( antecessor instanceof LayerGroup ) { // if target is a LayerGroup insert dropped node into it parent = (LayerGroup) antecessor; antecessor = null; } else { if ( antecessor != null && before ) { antecessor = ( (Layer) antecessor ).getAntecessor(); } if ( targetNode.getParent() != null ) { parent = (LayerGroup) ( (CheckNode) targetNode.getParent() ).getUserObject(); } } MapModelEntry la = (MapModelEntry) ( (CheckNode) droppedNode ).getUserObject(); if ( droppedNode.getUserObject() instanceof LayerGroup ) { before = false; } Command command = new MoveLayerCommand( la, parent, antecessor, mapModel, before ); appContainer.getCommandProcessor().executeSychronously( command, true ); } } } } catch ( Exception ex ) { ex.printStackTrace(); // this probably is not a reason to forward an exception or to create a new one. // In case of an exception it just will be logged LOG.logWarning( "none critical exception during droping: " + ex.getMessage() ); } dtde.dropComplete( true ); } private boolean isChild( DefaultMutableTreeNode droppedNode, DefaultMutableTreeNode targetNode ) { DefaultMutableTreeNode node = targetNode; while ( !node.equals( getModel().getRoot() ) ) { if ( node.getParent().equals( droppedNode ) ) { return true; } node = (DefaultMutableTreeNode) node.getParent(); } return false; } /** * updates the statuts (visible - not visible) of a node assigned to the passed layer adapter * * @param layer */ void updateNodeStatus( MapModelEntry entry ) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) treeModel.getRoot(); int cnt = node.getChildCount(); for ( int i = 0; i < cnt; i++ ) { traverseTree( (DefaultMutableTreeNode) node.getChildAt( i ), entry ); } } private void traverseTree( DefaultMutableTreeNode node, MapModelEntry thatEntry ) { CheckNode cn = (CheckNode) node; MapModelEntry entry = (MapModelEntry) cn.getUserObject(); if ( entry.getIdentifier().equals( thatEntry.getIdentifier() ) ) { cn.setSelected( entry.isVisible() ); invalidate(); getParent().repaint(); } int cnt = node.getChildCount(); for ( int i = 0; i < cnt; i++ ) { traverseTree( (DefaultMutableTreeNode) node.getChildAt( i ), thatEntry ); } } /* * (non-Javadoc) * * @see org.deegree.igeo.ChangeListener#valueChanged(org.deegree.igeo.ValueChangedEvent) */ public void valueChanged( ValueChangedEvent event ) { if ( ( (MapModelChangedEvent) event ).getChangeType() == MapModelChangedEvent.CHANGE_TYPE.extentChanged ) { // repaint is required because tree node label color depends on map scale invalidate(); getParent().repaint(); } } // //////////////////////////////////////////////////////////////////////////// // inner classes // // //////////////////////////////////////////////////////////////////////////// /** * * * * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> * @author last edited by: $Author$ * * @version. $Revision$, $Date$ */ class RJLTransferable implements Transferable { private Object object; /** * * @param o */ RJLTransferable( Object o ) { this.object = o; } /* * (non-Javadoc) * * @see java.awt.datatransfer.Transferable#getTransferDataFlavors() */ public DataFlavor[] getTransferDataFlavors() { return supportedFlavors; } /* * (non-Javadoc) * * @see java.awt.datatransfer.Transferable#isDataFlavorSupported(java.awt.datatransfer.DataFlavor) */ public boolean isDataFlavorSupported( DataFlavor flavor ) { return flavor.equals( localObjectFlavor ); } /* * (non-Javadoc) * * @see java.awt.datatransfer.Transferable#getTransferData(java.awt.datatransfer.DataFlavor) */ public Object getTransferData( DataFlavor flavor ) throws UnsupportedFlavorException, IOException { if ( isDataFlavorSupported( flavor ) ) { return object; } else { throw new UnsupportedFlavorException( flavor ); } } } /** * * * * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> * @author last edited by: $Author$ * * @version. $Revision$, $Date$ */ class DnDJTreeCellRenderer extends DefaultTreeCellRenderer { private static final long serialVersionUID = -6416294779686003390L; private boolean isTargetNode; private boolean isTargetNodeLeaf; private JCheckBox check; private JLabel label; private JToolBar toolbar; /** * * */ DnDJTreeCellRenderer() { setLayout( new FlowLayout( FlowLayout.LEFT, 0, 0 ) ); label = new JLabel(); check = new JCheckBox(); check.setToolTipText( "visibility" ); check.setOpaque( false ); toolbar = new JToolBar(); toolbar.setMinimumSize( new Dimension( 400, 20 ) ); toolbar.setPreferredSize( new Dimension( 400, 20 ) ); toolbar.setBorder( BorderFactory.createEmptyBorder() ); toolbar.setBackground( Color.WHITE ); } @Override public Component getTreeCellRendererComponent( JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row, boolean hasFocus ) { isTargetNode = ( value == dropTargetNode ); isTargetNodeLeaf = ( isTargetNode && ( (TreeNode) value ).isLeaf() ); Component result = this; if ( value instanceof LayerNode || value instanceof LayerGroupNode ) { setEnabled( tree.isEnabled() ); TreeLabel label_ = null; if ( value instanceof LayerNode ) { LayerNode ln = ( (LayerNode) value ); label_ = ( (LayerNode) value ).getTreeLabel(); // select tree node if layer is selected for action label_.setSelected( ( (Layer) ln.getUserObject() ).getSelectedFor().contains( MapModel.SELECTION_ACTION ) ); } else if ( value instanceof LayerGroupNode ) { label_ = ( (LayerGroupNode) value ).getTreeLabel(); label_.setSelected( false ); } else { throw new RuntimeException( "label_ not initialized!" ); } label_.setFocus( hasFocus ); label_.setTree( tree ); if ( isTargetNodeLeaf ) { toolbar.setBackground( Color.LIGHT_GRAY ); if ( top ) { toolbar.setBorder( BorderFactory.createMatteBorder( 2, 0, 0, 0, borderColor ) ); } else { toolbar.setBorder( BorderFactory.createMatteBorder( 0, 0, 2, 0, borderColor ) ); } } else { toolbar.setBackground( Color.WHITE ); toolbar.setBorder( BorderFactory.createEmptyBorder() ); } toolbar.removeAll(); Object o = ( (CheckNode) value ).getUserObject(); check.setSelected( ( (MapModelEntry) o ).isVisible() ); toolbar.add( check ); if ( ( (MapModelEntry) o ).getSelectedFor().contains( MapModel.SELECTION_EDITING ) ) { toolbar.add( new JLabel( IconRegistry.getIcon( "pen.gif" ) ) ); } toolbar.add( label_ ); result = toolbar; } else if ( ( (DefaultMutableTreeNode) value ).isRoot() ) { String stringValue = tree.convertValueToText( value, isSelected, expanded, leaf, row, hasFocus ); label.setIcon( IconRegistry.getIcon( "layers.png" ) ); label.setText( stringValue ); result = label; } else { // default String stringValue = tree.convertValueToText( value, isSelected, expanded, leaf, row, hasFocus ); label.setText( stringValue ); result = label; } return result; } @Override public Dimension getPreferredSize() { Dimension d_check = check.getPreferredSize(); return new Dimension( 400, d_check.height ); } @Override public Dimension getMinimumSize() { return getPreferredSize(); } @Override public void doLayout() { Dimension d_check = check.getPreferredSize(); check.setBounds( 0, 0, d_check.width, d_check.height ); } } /** * * * * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> * @author last edited by: $Author$ * * @version. $Revision$, $Date$ */ public static class TreeLabel extends JLabel implements ChangeListener { private static final long serialVersionUID = -3798337514877725403L; private boolean isSelected; private boolean hasFocus; private MapModelEntry mme; private JTree tree; private static Font EDITFONT = new Font( "ARIAL", Font.BOLD, 12 ); /** * * @param mme */ public TreeLabel( MapModelEntry mme ) { this.mme = mme; if ( mme instanceof Layer ) { setIcon( IconRegistry.getIcon( "layer.png" ) ); } else { setIcon( IconRegistry.getIcon( "layers.png" ) ); } mme.addChangeListener( this ); setText( mme.getTitle() ); } /** * @param */ public void setBackground( Color color ) { if ( color instanceof ColorUIResource ) color = null; super.setBackground( color ); } public void setTree( JTree tree ) { this.tree = tree; } @Override public void paint( Graphics g ) { if ( mme != null ) { setText( mme.getTitle() ); } setForeground( Color.BLACK ); if ( mme instanceof Layer ) { Layer layer = ( (Layer) mme ); if ( layer.getSelectedFor().contains( MapModel.SELECTION_EDITING ) ) { setForeground( Color.RED ); setFont( EDITFONT ); } else { setFont( tree.getFont() ); } double min = layer.getMinScaleDenominator(); double max = layer.getMaxScaleDenominator(); double sc = layer.getOwner().getScaleDenominator(); if ( min > sc || max < sc ) { setForeground( Color.LIGHT_GRAY ); } } String str; if ( ( str = getText() ) != null && 0 < str.length() ) { if ( isSelected ) { g.setColor( Color.ORANGE ); } else { g.setColor( new Color( 0, 0, 0, 0 ) ); } Dimension d = getPreferredSize(); int imageOffset = 0; Icon currentI = getIcon(); if ( currentI != null ) { imageOffset = currentI.getIconWidth() + Math.max( 0, getIconTextGap() - 1 ); } g.fillRect( imageOffset, 0, d.width - 1 - imageOffset, d.height + 3 ); if ( hasFocus ) { g.setColor( UIManager.getColor( "Tree.selectionBorderColor" ) ); g.drawRect( imageOffset, 0, d.width - 1 - imageOffset, d.height - 1 + 3 ); } } super.paint( g ); } /** * * @param isSelected */ public void setSelected( boolean isSelected ) { this.isSelected = isSelected; } /** * * @param hasFocus */ public void setFocus( boolean hasFocus ) { this.hasFocus = hasFocus; } /* * (non-Javadoc) * * @see org.deegree.igeo.ChangeListener#valueChanged(org.deegree.igeo.ValueChangedEvent) */ public void valueChanged( ValueChangedEvent event ) { if ( event instanceof LayerChangedEvent ) { LayerChangedEvent e = (LayerChangedEvent) event; if ( e.getSource().equals( mme ) && e.getEmbeddedEvent() instanceof AdapterEvent ) { AdapterEvent ae = (AdapterEvent) e.getEmbeddedEvent(); if ( ae.getType() == ADAPTER_EVENT_TYPE.startedLoading ) { setIcon( IconRegistry.getIcon( "clock.gif" ) ); } else if ( ae.getType() == ADAPTER_EVENT_TYPE.finishedLoading ) { setIcon( IconRegistry.getIcon( "layer.png" ) ); } else { setIcon( IconRegistry.getIcon( "cancel.png" ) ); } if ( tree != null ) { tree.repaint(); } } } } } }