# TreeGraphDisplay.java

    /*
     * Copyright (c) 2009, Tony Kohar. All Rights Reserved.
     *
     * 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 org.qi4j.envisage.graph;

    import java.awt.Color;
    import java.awt.event.MouseEvent;
    import java.awt.geom.Point2D;
    import java.awt.geom.Rectangle2D;
    import java.util.Iterator;
    import org.qi4j.envisage.event.LinkEvent;
    import prefuse.Constants;
    import prefuse.Visualization;
    import prefuse.action.Action;
    import prefuse.action.ActionList;
    import prefuse.action.ItemAction;
    import prefuse.action.RepaintAction;
    import prefuse.action.animate.ColorAnimator;
    import prefuse.action.animate.LocationAnimator;
    import prefuse.action.animate.QualityControlAnimator;
    import prefuse.action.animate.VisibilityAnimator;
    import prefuse.action.assignment.ColorAction;
    import prefuse.action.assignment.FontAction;
    import prefuse.action.filter.FisheyeTreeFilter;
    import prefuse.action.layout.CollapsedSubtreeLayout;
    import prefuse.action.layout.graph.NodeLinkTreeLayout;
    import prefuse.activity.SlowInSlowOutPacer;
    import prefuse.controls.ControlAdapter;
    import prefuse.controls.FocusControl;
    import prefuse.controls.PanControl;
    import prefuse.controls.WheelZoomControl;
    import prefuse.controls.ZoomControl;
    import prefuse.controls.ZoomToFitControl;
    import prefuse.data.Graph;
    import prefuse.data.expression.Predicate;
    import prefuse.data.expression.parser.ExpressionParser;
    import prefuse.data.tuple.TupleSet;
    import prefuse.render.AbstractShapeRenderer;
    import prefuse.render.DefaultRendererFactory;
    import prefuse.render.EdgeRenderer;
    import prefuse.render.LabelRenderer;
    import prefuse.util.ColorLib;
    import prefuse.util.FontLib;
    import prefuse.util.GraphicsLib;
    import prefuse.util.PrefuseLib;
    import prefuse.util.display.DisplayLib;
    import prefuse.visual.VisualItem;
    import prefuse.visual.sort.TreeDepthItemSorter;

    public final class TreeGraphDisplay
        extends GraphDisplay
    {

        static final String FILTER_ACTION = "filter";
        static final String REPAINT_ACTION = "repaint";
        static final String FULL_PAINT_ACTION = "fullPaint";
        static final String ANIMATE_PAINT_ACTION = "animatePaint";
        static final String ANIMATE_ACTION = "animate";
        static final String LAYOUT_ACTION = "layout";
        static final String SUB_LAYOUT_ACTION = "subLayout";
        static final String AUTO_ZOOM_ACTION = "autoZoom";

        private final LabelRenderer nodeRenderer;
        private final EdgeRenderer edgeRenderer;
        private final EdgeRenderer usesRenderer;

        private final int orientation = Constants.ORIENT_LEFT_RIGHT;

        public TreeGraphDisplay()
        {
            super( new Visualization() );

            Color BACKGROUND = Color.WHITE;
            Color FOREGROUND = Color.BLACK;

            setBackground( BACKGROUND );
            setForeground( FOREGROUND );

            nodeRenderer = new LabelRenderer( NAME_LABEL );
            nodeRenderer.setRenderType( AbstractShapeRenderer.RENDER_TYPE_FILL );
            nodeRenderer.setHorizontalAlignment( Constants.LEFT );
            nodeRenderer.setRoundedCorner( 8, 8 );
            edgeRenderer = new EdgeRenderer( Constants.EDGE_TYPE_CURVE );
            usesRenderer = new EdgeRenderer( Constants.EDGE_TYPE_CURVE, Constants.EDGE_ARROW_FORWARD );

            Predicate edgesPredicate = (Predicate) ExpressionParser.parse( "ingroup('graph.edges') AND [" + USES_EDGES + "]==false", true );
            Predicate usesPredicate = (Predicate) ExpressionParser.parse( "ingroup('graph.edges') AND [" + USES_EDGES + "]==true", true );

            DefaultRendererFactory rf = new DefaultRendererFactory( nodeRenderer );
            rf.add( edgesPredicate, edgeRenderer );
            rf.add( usesPredicate, usesRenderer );
            m_vis.setRendererFactory( rf );

            // colors
            ItemAction nodeColor = new NodeColorAction( GRAPH_NODES );
            ItemAction textColor = new ColorAction( GRAPH_NODES, VisualItem.TEXTCOLOR, ColorLib.rgb( 0, 0, 0 ) );
            m_vis.putAction( "textColor", textColor );

            ItemAction edgeColor = new ColorAction( GRAPH_EDGES, edgesPredicate, VisualItem.STROKECOLOR, ColorLib.rgb( 200, 200, 200 ) );
            ItemAction usesColor = new ColorAction( GRAPH_EDGES, usesPredicate, VisualItem.STROKECOLOR, ColorLib.rgb( 255, 100, 100 ) );
            ItemAction usesArrow = new ColorAction( GRAPH_EDGES, usesPredicate, VisualItem.FILLCOLOR, ColorLib.rgb( 255, 100, 100 ) );

            // quick repaint
            ActionList repaint = new ActionList();
            repaint.add( nodeColor );
            repaint.add( new RepaintAction() );
            m_vis.putAction( REPAINT_ACTION, repaint );

            // full paint
            ActionList fullPaint = new ActionList();
            fullPaint.add( nodeColor );
            m_vis.putAction( FULL_PAINT_ACTION, fullPaint );

            // animate paint change
            //ActionList animatePaint = new ActionList( 0 );
            ActionList animatePaint = new ActionList();
            animatePaint.add( new ColorAnimator( GRAPH_NODES ) );
            animatePaint.add( new RepaintAction() );
            m_vis.putAction( ANIMATE_PAINT_ACTION, animatePaint );

            // create the GRAPH layout action
            NodeLinkTreeLayout treeLayout = new NodeLinkTreeLayout( GRAPH, orientation, 40, 0, 8 );
            treeLayout.setLayoutAnchor( new Point2D.Double( 25, 300 ) );
            m_vis.putAction( LAYOUT_ACTION, treeLayout );

            CollapsedSubtreeLayout subLayout = new CollapsedSubtreeLayout( GRAPH, orientation );
            m_vis.putAction( SUB_LAYOUT_ACTION, subLayout );

            // create the filtering and layout
            ActionList filter = new ActionList();
            filter.add( new ExtendedFisheyeTreeFilter( GRAPH, 2 ) );
            filter.add( new FontAction( GRAPH_NODES, FontLib.getFont( "Tahoma", 14 ) ) );
            filter.add( treeLayout );
            filter.add( subLayout );
            filter.add( textColor );
            filter.add( nodeColor );
            filter.add( edgeColor );
            filter.add( usesColor );
            filter.add( usesArrow );
            m_vis.putAction( FILTER_ACTION, filter );

            // animated transition
            AutoPanAction autoPan = new AutoPanAction();
            ActionList animate = new ActionList( 750 );
            animate.setPacingFunction( new SlowInSlowOutPacer() );
            animate.add( autoPan );
            animate.add( new QualityControlAnimator() );
            animate.add( new VisibilityAnimator( GRAPH ) );
            animate.add( new LocationAnimator( GRAPH_NODES ) );
            animate.add( new ColorAnimator( GRAPH_NODES ) );
            animate.add( new RepaintAction() );
            m_vis.putAction( ANIMATE_ACTION, animate );
            m_vis.alwaysRunAfter( FILTER_ACTION, ANIMATE_ACTION );

            m_vis.putAction( AUTO_ZOOM_ACTION, new AutoZoomAction() );

            // initialize the display
            setItemSorter( new TreeDepthItemSorter() );
            addControlListener( new ZoomToFitControl() );
            addControlListener( new ZoomControl() );
            addControlListener( new WheelZoomControl() );
            addControlListener( new PanControl() );
            addControlListener( new FocusControl( 1, FILTER_ACTION ) );
            addControlListener( new ItemSelectionControl() );

            // set orientation
            nodeRenderer.setHorizontalAlignment( Constants.LEFT );
            edgeRenderer.setHorizontalAlignment1( Constants.RIGHT );
            edgeRenderer.setHorizontalAlignment2( Constants.LEFT );
            edgeRenderer.setVerticalAlignment1( Constants.CENTER );
            edgeRenderer.setVerticalAlignment2( Constants.CENTER );
            usesRenderer.setHorizontalAlignment1( Constants.CENTER );
            usesRenderer.setHorizontalAlignment2( Constants.CENTER );
            usesRenderer.setVerticalAlignment1( Constants.BOTTOM );
            usesRenderer.setVerticalAlignment2( Constants.CENTER );
            NodeLinkTreeLayout rtl = (NodeLinkTreeLayout) m_vis.getAction( LAYOUT_ACTION );
            CollapsedSubtreeLayout stl = (CollapsedSubtreeLayout) m_vis.getAction( SUB_LAYOUT_ACTION );
            rtl.setOrientation( orientation );
            stl.setOrientation( orientation );
        }

        @Override
        public void run( Graph graph )
        {
            m_vis.add( GRAPH, graph );
            run();
            m_vis.run( AUTO_ZOOM_ACTION );

            // disable edges interactive
            m_vis.setInteractive( GRAPH_EDGES, null, false );
        }

        @Override
        public void run()
        {
            m_vis.run( FILTER_ACTION );
        }

        /**
         * select the specified object
         *
         * @param object the object to select eg: Descriptor
         */
        @Override
        public void setSelectedValue( Object object )
        {
            if( object == null )
            {
                return;
            }

            VisualItem item = null;

            Iterator iter = m_vis.items( GRAPH_NODES );
            while( iter.hasNext() )
            {
                VisualItem tItem = (VisualItem) iter.next();
                Object tObj = tItem.get( USER_OBJECT );
                if( tObj.equals( object ) )
                {
                    item = tItem;
                    break;
                }
            }

            if( item != null )
            {
                TupleSet ts = m_vis.getFocusGroup( Visualization.FOCUS_ITEMS );
                ts.setTuple( item );
                m_vis.run( FILTER_ACTION );
            }
        }

        private class AutoZoomAction
            extends Action
        {
            @Override
            public void run( double frac )
            {
                int duration = 20;
                int margin = 50;
                Visualization vis = getVisualization();
                Rectangle2D bounds = vis.getBounds( Visualization.ALL_ITEMS );
                GraphicsLib.expand( bounds, margin + (int) ( 1 / getScale() ) );
                DisplayLib.fitViewToBounds( TreeGraphDisplay.this, bounds, duration );
            }
        }

        private class AutoPanAction
            extends Action
        {
            private final Point2D m_start = new Point2D.Double();
            private final Point2D m_end = new Point2D.Double();
            private final Point2D m_cur = new Point2D.Double();
            private final int m_bias = 150;

            @Override
            public void run( double frac )
            {
                TupleSet ts = m_vis.getFocusGroup( Visualization.FOCUS_ITEMS );
                if( ts.getTupleCount() == 0 )
                {
                    return;
                }

                if( frac == 0.0 )
                {
                    int xbias, ybias = 0;

                    xbias = m_bias;
                    switch( orientation )
                    {
                        case Constants.ORIENT_LEFT_RIGHT:

                            break;
                        case Constants.ORIENT_RIGHT_LEFT:
                            xbias = -m_bias;
                            break;
                        case Constants.ORIENT_TOP_BOTTOM:
                            ybias = m_bias;
                            break;
                        case Constants.ORIENT_BOTTOM_TOP:
                            ybias = -m_bias;
                            break;
                    }

                    VisualItem vi = (VisualItem) ts.tuples().next();
                    m_cur.setLocation( getWidth() / 2, getHeight() / 2 );
                    getAbsoluteCoordinate( m_cur, m_start );
                    m_end.setLocation( vi.getX() + xbias, vi.getY() + ybias );
                }
                else
                {
                    m_cur.setLocation( m_start.getX() + frac * ( m_end.getX() - m_start.getX() ),
                                       m_start.getY() + frac * ( m_end.getY() - m_start.getY() ) );
                    panToAbs( m_cur );
                }
            }
        }

        private static class NodeColorAction
            extends ColorAction
        {

            private NodeColorAction( String group )
            {
                super( group, VisualItem.FILLCOLOR );
            }

            @Override
            public int getColor( VisualItem item )
            {
                if( m_vis.isInGroup( item, Visualization.SEARCH_ITEMS ) )
                {
                    return ColorLib.rgb( 255, 190, 190 );
                }
                else if( m_vis.isInGroup( item, Visualization.FOCUS_ITEMS ) )
                {
                    return ColorLib.rgb( 198, 229, 229 );
                }
                else if( item.getDOI() > -1 )
                {
                    return ColorLib.rgb( 164, 193, 193 );
                }
                else
                {
                    return ColorLib.rgba( 255, 255, 255, 0 );
                }
            }
        }

        private class ItemSelectionControl
            extends ControlAdapter
        {
            @Override
            public final void itemClicked( VisualItem anItem, MouseEvent anEvent )
            {
                if( !anItem.canGet( USER_OBJECT, Object.class ) )
                {
                    return;
                }
                Object object = anItem.get( USER_OBJECT );
                LinkEvent evt = new LinkEvent( TreeGraphDisplay.this, object );
                fireLinkActivated( evt );
            }
        }

        private static class ExtendedFisheyeTreeFilter
            extends FisheyeTreeFilter
        {
            private ExtendedFisheyeTreeFilter( String group, int distance )
            {
                super( group, distance );
            }

            @Override
            public void run( double frac )
            {
                super.run( frac );

                // set uses_edges always visible
                Iterator items = m_vis.items( GRAPH_EDGES );
                while( items.hasNext() )
                {
                    VisualItem item = (VisualItem) items.next();
                    if( item.getBoolean( USES_EDGES ) )
                    {
                        PrefuseLib.updateVisible( item, true );
                    }
                }
            }
        }

    }