view visualizer/ControlFlowEditor/src/at/ssw/visualizer/cfg/graph/CfgScene.java @ 4512:015fb895586b

Moved visualizer to new directory.
author Thomas Wuerthinger <thomas.wuerthinger@oracle.com>
date Tue, 07 Feb 2012 22:41:09 +0100
parents
children
line wrap: on
line source

package at.ssw.visualizer.cfg.graph;

import at.ssw.visualizer.cfg.model.CfgEdge;
import at.ssw.visualizer.cfg.CfgEditorContext;
import at.ssw.visualizer.cfg.action.HideEdgesAction;
import at.ssw.visualizer.cfg.action.ShowEdgesAction;
import at.ssw.visualizer.cfg.graph.layout.HierarchicalCompoundLayout;
import at.ssw.visualizer.cfg.graph.layout.HierarchicalNodeLayout;
import at.ssw.visualizer.cfg.model.CfgEnv;
import at.ssw.visualizer.cfg.model.CfgNode;
import at.ssw.visualizer.cfg.model.LoopInfo;
import at.ssw.visualizer.cfg.preferences.CfgPreferences;
import at.ssw.visualizer.cfg.visual.PolylineRouter;
import at.ssw.visualizer.cfg.visual.PolylineRouterV2;
import at.ssw.visualizer.cfg.visual.WidgetCollisionCollector;
import at.ssw.visualizer.model.cfg.BasicBlock;
import at.ssw.visualizer.model.cfg.ControlFlowGraph;
import com.oracle.graal.visualizer.sharedactions.ZoomCookie;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;
import org.netbeans.api.visual.action.ActionFactory;
import org.netbeans.api.visual.action.MoveProvider;
import org.netbeans.api.visual.action.PopupMenuProvider;
import org.netbeans.api.visual.action.RectangularSelectDecorator;
import org.netbeans.api.visual.action.RectangularSelectProvider;
import org.netbeans.api.visual.action.WidgetAction;
import org.netbeans.api.visual.anchor.Anchor;
import org.netbeans.api.visual.anchor.AnchorFactory;
import org.netbeans.api.visual.graph.GraphScene;
import org.netbeans.api.visual.graph.layout.GraphLayout;
import org.netbeans.api.visual.layout.LayoutFactory;
import org.netbeans.api.visual.layout.SceneLayout;
import org.netbeans.api.visual.router.Router;
import org.netbeans.api.visual.router.Router.*;
import org.netbeans.api.visual.router.RouterFactory;
import org.netbeans.api.visual.widget.ConnectionWidget;
import org.netbeans.api.visual.widget.LayerWidget;
import org.netbeans.api.visual.widget.Widget;
import org.openide.util.NbPreferences;
import org.openide.util.actions.SystemAction;

public final class CfgScene extends GraphScene<CfgNode, CfgEdge> implements ChangeListener, ZoomCookie {

    private LayerWidget mainLayer = new LayerWidget(this);
    private LayerWidget connectionLayer = new LayerWidget(this);
    private LayerWidget interractionLayer = new LayerWidget(this);
    private LayerWidget clusterLayer = new LayerWidget(this);
    private Set<CfgNode> selectedNodes = Collections.<CfgNode>emptySet();
    private Map<Integer, LoopClusterWidget> loopidx2clusterwidget = new HashMap<>();
    private Map<CfgNode, EdgeSwitchWidget> inputSwitches = new HashMap<>();
    private Map<CfgNode, EdgeSwitchWidget> outputSwitches = new HashMap<>();
    private WidgetAction moveAction = ActionFactory.createMoveAction(ActionFactory.createFreeMoveStrategy(), this.createMoveProvider());
    private SceneLayout sceneLayout;
    private CfgEnv env;
    private int currentLayout = -1;
    private JScrollPane scrollPane;
    private WidgetAction contextPopupAction = this.createContextMenuAction(this);
    private List<NodeWidget> nodeWidgets = null;
    private boolean loopClustersVisible = true;
    private static String PREFERENCE_LOOP_CLUSTER = "loopClusters";
    private static String PREFERENCE_ROUTER = "router";
    private static String PREFERENCE_LAYOUT = "layout";

    public static Preferences getPreferences() {
        return NbPreferences.forModule(CfgScene.class);
    }
    private final PreferenceChangeListener preferenceChangeListener = new PreferenceChangeListener() {
        @Override
        public void preferenceChange(PreferenceChangeEvent evt) {
            if (evt.getKey().equals(PREFERENCE_ROUTER)) {
                setUseBezierRouter(Boolean.parseBoolean(evt.getNewValue()));
            } else if (evt.getKey().equals(PREFERENCE_LOOP_CLUSTER)) {
                setLoopWidgets(Boolean.parseBoolean(evt.getNewValue()));
            }
        }
    };

    public CfgScene(final JScrollPane scrollPane, final ControlFlowGraph cfg) {
        addChild(clusterLayer);
        addChild(mainLayer);
        addChild(interractionLayer);
        addChild(connectionLayer);
        this.scrollPane = scrollPane;
        this.loadModel(new CfgEnv(cfg));
        this.getInputBindings().setZoomActionModifiers(0);
        this.getActions().addAction(ActionFactory.createMouseCenteredZoomAction(1.1));
        this.getActions().addAction(ActionFactory.createPanAction());
        this.getActions().addAction(ActionFactory.createRectangularSelectAction(
                this.createSelectDecorator(this),
                interractionLayer,
                this.createRectangularSelectProvider()));
        this.getActions().addAction(this.contextPopupAction);
        this.addSceneListener(createSceneListener(this));
        this.validate();

        getPreferences().addPreferenceChangeListener(preferenceChangeListener);
        this.loadDefaults();
    }

    private void loadModel(CfgEnv cfgenv) {
        this.env = cfgenv;
        for (CfgNode n : env.getNodes()) {
            addNode(n);
        }
        for (CfgEdge e : env.getEdges()) {
            addEdge(e);
            setEdgeSource(e, e.getSourceNode());
            setEdgeTarget(e, e.getTargetNode());
        }
        this.stackLoops(cfgenv.getLoopMap());
        this.autoHideEdges();
    }

    public void loadDefaults() {
        CfgPreferences prefs = CfgPreferences.getInstance();
        this.setBackground(prefs.getBackgroundColor());
        setUseBezierRouter(getPreferences().getBoolean(PREFERENCE_ROUTER, true));
        setLoopWidgets(getPreferences().getBoolean(PREFERENCE_LOOP_CLUSTER, true));
        setSceneLayout(getPreferences().getInt(PREFERENCE_LAYOUT, CfgEditorContext.LAYOUT_HIERARCHICALNODELAYOUT));
    }

    //sets the parent Widget of all LoopClusterWidgets
    private void stackLoops(Map<CfgNode, LoopInfo> map) {
        this.clusterLayer.removeChildren();

        Set<LoopInfo> cache = new HashSet<>();
        for (LoopInfo info : map.values()) {
            if (cache.contains(info)) {
                continue;
            }
            LoopClusterWidget widget = this.loopidx2clusterwidget.get(info.getLoopIndex());
            LoopInfo parent = info.getParent();
            while (parent != null) {
                LoopClusterWidget parentWidget = this.loopidx2clusterwidget.get(parent.getLoopIndex());
                assert parentWidget != null;
                if (widget.getParentWidget() != null) {
                    widget.removeFromParent();
                }
                parentWidget.addChild(widget);
                widget = parentWidget;
                parent = parent.getParent();
            }
            widget.removeFromParent();
            this.clusterLayer.addChild(widget);//parent == null => parent is clusterlayer
        }
    }

    //hide in|output edges 
    private void autoHideEdges() {
        for (CfgNode n : this.getNodes()) {
            int fanin = n.getInputEdges().length;
            int fanout = n.getOutputEdges().length;
            if (fanin > CfgEditorContext.MAX_AUTOEDGESVISIBLE) {
                assert (inputSwitches.containsKey(n));
                if (this.inputSwitches.containsKey(n)) {
                    EdgeSwitchWidget esw = this.inputSwitches.get(n);
                    esw.changeEdgeVisibility(false);
                }
            }
            if (fanout > CfgEditorContext.MAX_AUTOEDGESVISIBLE) {
                if (this.outputSwitches.containsKey(n)) {
                    EdgeSwitchWidget esw = this.outputSwitches.get(n);
                    esw.changeEdgeVisibility(false);
                }
            }
        }

    }

    //apply current cfggraphscene layout  
    public void applyLayout() {
        this.sceneLayout.invokeLayoutImmediately();
    }

    //returns a Set with the currently selected Nodes    
    public Set<CfgNode> getSelectedNodes() {
        return Collections.<CfgNode>unmodifiableSet(selectedNodes);
    }

    public Map<Integer, LoopClusterWidget> getLoopidx2clusterwidget() {
        return loopidx2clusterwidget;
    }

    /**
     * Sets the color of the currently selected Nodes If the supplied color is null the default color will be used
     */
    public void setSelectedNodesColor(Color color) {
        if (color == null) { //set default color
            CfgPreferences prefs = CfgPreferences.getInstance();
            boolean customized = false;
            for (CfgNode n : this.selectedNodes) {
                color = null;
                color = prefs.getFlagsSetting().getColor(n.getBasicBlock().getFlags());
                customized = (color != null);
                NodeWidget nw = (NodeWidget) this.findWidget(n);
                nw.setNodeColor((customized) ? color : prefs.getNodeColor(), customized);
            }
        } else {
            for (CfgNode n : this.selectedNodes) {
                NodeWidget nw = (NodeWidget) this.findWidget(n);
                nw.setNodeColor(color, true);
            }
        }
        this.validate();
    }

    public void setSelectedEdgesVisibility(boolean visible) {
        for (CfgNode n : this.selectedNodes) {
            EdgeSwitchWidget in = this.inputSwitches.get(n);
            EdgeSwitchWidget out = this.outputSwitches.get(n);
            if (in != null) {
                in.changeEdgeVisibility(visible);
            }
            if (out != null) {
                out.changeEdgeVisibility(visible);
            }
        }
        this.validate();
    }

    public EdgeSwitchWidget getInputSwitch(CfgNode n) {
        return this.inputSwitches.get(n);
    }

    public EdgeSwitchWidget getOutputSwitch(CfgNode n) {
        return this.outputSwitches.get(n);
    }

    public CfgEnv getCfgEnv() {
        return env;
    }

    public boolean isLoopClusterVisible() {
        return loopClustersVisible;
    }

    public void setLoopWidgets(boolean visible) {
        for (Widget w : this.loopidx2clusterwidget.values()) {
            w.setVisible(visible);
            w.revalidate();
        }
        this.loopClustersVisible = visible;
        this.validate();
        getPreferences().putBoolean(PREFERENCE_LOOP_CLUSTER, visible);
    }
    
    private void setUseBezierRouter(boolean value) {
        Router router;
        if (value) {
            router = new PolylineRouterV2(new WidgetCollisionCollector() {
                @Override
                public void collectCollisions(List<Widget> collisions) {
                    collisions.addAll(getNodeWidgets());
                }
            });
        } else {
            router = RouterFactory.createDirectRouter();
        }

        for (CfgEdge e : this.getEdges()) {
            EdgeWidget ew = (EdgeWidget) this.findWidget(e);
            ew.setRouter(router);
        }
        this.validate();
        getPreferences().putBoolean(PREFERENCE_ROUTER, value);
    }

    public Collection<NodeWidget> getNodeWidgets() {
        if (nodeWidgets != null && nodeWidgets.size() == this.getNodes().size()) {
            return nodeWidgets;
        }

        List<NodeWidget> widgets = new ArrayList<>();
        for (CfgNode n : this.getNodes()) {
            NodeWidget w = (NodeWidget) this.findWidget(n);
            widgets.add(w);
        }

        nodeWidgets = Collections.unmodifiableList(widgets);
        return widgets;
    }

    public void setSceneLayout(int newLayout) {

        GraphLayout<CfgNode, CfgEdge> graphLayout = null;

        switch (newLayout) {
            case CfgEditorContext.LAYOUT_HIERARCHICALNODELAYOUT:
                graphLayout = new HierarchicalNodeLayout(this);
                break;

            case CfgEditorContext.LAYOUT_HIERARCHICALCOMPOUNDLAYOUT:
                graphLayout = new HierarchicalCompoundLayout(this);
                break;
        }

        this.currentLayout = newLayout;
        if (graphLayout != null) {
            this.sceneLayout = LayoutFactory.createSceneGraphLayout(this, graphLayout);
        }
        
        getPreferences().putInt(PREFERENCE_LAYOUT, newLayout);
        sceneLayout.invokeLayoutImmediately();
    }

    @Override
    protected void attachEdgeSourceAnchor(CfgEdge edge, CfgNode oldSourceNode, CfgNode sourceNode) {
        Anchor sourceAnchor;
        EdgeWidget edgeWidget = (EdgeWidget) findWidget(edge);
        Widget sourceWidget = findWidget(sourceNode);

        if (edge.isSymmetric()) {
            sourceAnchor = new SymmetricAnchor(sourceWidget, true, true);
        } else {
            sourceAnchor = AnchorFactory.createRectangularAnchor(sourceWidget);
        }
        edgeWidget.setSourceAnchor(sourceAnchor);
    }

    @Override
    protected void attachEdgeTargetAnchor(CfgEdge edge, CfgNode oldtarget, CfgNode targetNode) {
        Anchor targetAnchor;
        ConnectionWidget edgeWidget = (ConnectionWidget) findWidget(edge);
        Widget targetWidget = findWidget(targetNode);

        if (edge.isSymmetric()) {
            targetAnchor = new SymmetricAnchor(targetWidget, true, false);
        } else {
            targetAnchor = AnchorFactory.createRectangularAnchor(targetWidget);
        }
        edgeWidget.setTargetAnchor(targetAnchor);
    }

    @Override
    protected Widget attachEdgeWidget(CfgEdge edge) {
        EdgeWidget widget = new EdgeWidget(this, edge);
        connectionLayer.addChild(widget);
        attachSourceSwitchWidget(edge);
        attachTargetSwitchWidget(edge);
        return widget;
    }

    @Override
    protected Widget attachNodeWidget(CfgNode node) {
        this.nodeWidgets = null;

        NodeWidget nw = new NodeWidget(this, node);
        WidgetAction.Chain actions = nw.getActions();
        actions.addAction(this.contextPopupAction);
        actions.addAction(this.moveAction);
        actions.addAction(this.createObjectHoverAction());

        if (node.isLoopMember()) {
            LoopClusterWidget loopWidget = this.attachLoopMember(node);
            loopWidget.addMember(nw);
        }
        mainLayer.addChild(nw);
        return nw;
    }

    private LoopClusterWidget attachLoopMember(CfgNode node) {
        LoopClusterWidget lw = this.loopidx2clusterwidget.get(node.getLoopIndex());
        if (lw == null) {
            lw = new LoopClusterWidget(this, node.getLoopDepth(), node.getLoopIndex());
            this.loopidx2clusterwidget.put(node.getLoopIndex(), lw);
            this.clusterLayer.addChild(lw);
        }
        return lw;
    }

    private boolean detachLoopMember(CfgNode node, NodeWidget nodeWidget) {
        LoopClusterWidget rm = this.loopidx2clusterwidget.get(node.getLoopIndex());
        if (rm == null) {
            return false;//not added
        }
        if (rm.removeMember(nodeWidget)) {
            if (rm.getMembers().isEmpty()) {
                this.loopidx2clusterwidget.remove(rm.getLoopIndex());
                List<Widget> childs = new ArrayList<>(rm.getChildren());
                for (Widget w : childs) {//append stacked loopwidgets
                    w.removeFromParent();
                    rm.getParentWidget().addChild(w);
                }
                rm.removeFromParent();
            }
            return true;
        }
        return false;
    }

    //this function is not invoked by any class  of the module
    //however to ensure that the edge switches are treatet corretly
    //when a future version removes nodes it was implemented too.
    @Override
    protected void detachNodeWidget(CfgNode node, Widget nodeWidget) {
        if (node.isLoopMember() && nodeWidget instanceof NodeWidget) {
            this.detachLoopMember(node, (NodeWidget) nodeWidget);
        }
        super.detachNodeWidget(node, nodeWidget);
        assert nodeWidget.getParentWidget() == null;
        if (this.inputSwitches.containsKey(node)) {
            EdgeSwitchWidget esw = this.inputSwitches.remove(node);
            this.connectionLayer.removeChild(esw);
        }
        if (this.outputSwitches.containsKey(node)) {
            EdgeSwitchWidget esw = this.outputSwitches.remove(node);
            this.connectionLayer.removeChild(esw);
        }
    }

    protected EdgeSwitchWidget attachSourceSwitchWidget(CfgEdge e) {
        CfgNode sourceNode = e.getSourceNode();
        NodeWidget sourceWidget = (NodeWidget) this.findWidget(sourceNode);
        EdgeSwitchWidget out = outputSwitches.get(sourceNode);
        if (out == null) {
            out = new EdgeSwitchWidget(this, sourceWidget, true);
            this.connectionLayer.addChild(out);
            outputSwitches.put(sourceNode, out);
        }
        return out;
    }

    protected EdgeSwitchWidget attachTargetSwitchWidget(CfgEdge e) {
        CfgNode targetNode = e.getTargetNode();
        NodeWidget targetWidget = (NodeWidget) this.findWidget(targetNode);
        EdgeSwitchWidget in = inputSwitches.get(targetNode);
        if (in == null) {
            in = new EdgeSwitchWidget(this, targetWidget, false);
            this.connectionLayer.addChild(in);
            inputSwitches.put(targetNode, in);
        }
        return in;
    }

    //resets the selection state of all NodeWidgets
    private void cleanNodeSelection() {
        if (!this.selectedNodes.isEmpty()) {
            this.userSelectionSuggested(Collections.<CfgNode>emptySet(), false);
            this.selectedNodes = Collections.<CfgNode>emptySet();
            this.validate();
        }
    }

    //sets the scene & global node selection
    public void setNodeSelection(Set<CfgNode> newSelection) {
        this.setSceneSelection(newSelection);
        this.updateGlobalSelection();
    }

    //sets the scene selection
    private void setSceneSelection(Set<CfgNode> newSelection) {
        if (newSelection.equals(selectedNodes)) {
            return;
        }

        this.selectedNodes = newSelection;

        Set<Object> selectedObjects = new HashSet<>();

        for (CfgNode n : newSelection) {
            selectedObjects.addAll(this.findNodeEdges(n, true, true));
        }
        selectedObjects.addAll(newSelection);

        //if the selection gets updated from a change in the block view
        //the scene will be centered
        if (selectionUpdating) {
            this.centerSelection();
        }

        this.userSelectionSuggested(selectedObjects, false);
        this.validate();
    }

    //updates selection of Block View
    public void updateGlobalSelection() {
        // TODO(tw): Add selection management.
//        Selection selection = SelectionManager.getDefault().getCurSelection();
//        ArrayList<BasicBlock> newBlocks = new ArrayList<BasicBlock>();
//        for (CfgNode n : this.selectedNodes) {
//            newBlocks.add(n.getBasicBlock());
//        }
//        BasicBlock[] curBlocks = newBlocks.toArray(new BasicBlock[newBlocks.size()]);     
//        selection.put(curBlocks);
    }
    private boolean selectionUpdating = false;

    //change of blockview selection   
    public void stateChanged(ChangeEvent event) {
        if (selectionUpdating) {
            return;
        }

        selectionUpdating = true;

        Object source = event.getSource();
//        if(source instanceof Selection){
//            Selection selection=(Selection) source;
//            Set<CfgNode> newSelection = new HashSet<CfgNode>();
//            BasicBlock[] newBlocks = selection.get(BasicBlock[].class);                  
//            if (newBlocks != null) {
//                for(BasicBlock b : newBlocks){
//                    for(CfgNode n : this.getNodes()){
//                        if(n.getBasicBlock() == b) newSelection.add(n);
//                    }
//                }
//                this.setSceneSelection(newSelection);                               
//            } 
//        }       
        selectionUpdating = false;
        // TODO(tw): Add selection management.
    }

    //centers the viewport on the currently selected nodewidgets
    private void centerSelection() {
        Point sceneCenter = null;
        Collection<CfgNode> nodes = this.selectedNodes;
        if (nodes.size() == 0) {
            nodes = this.getNodes();
        }

        for (CfgNode n : nodes) {
            if (sceneCenter == null) {
                sceneCenter = this.findWidget(n).getLocation();
                continue;
            }
            Point location = this.findWidget(n).getLocation();
            sceneCenter.x = (location.x + sceneCenter.x) / 2;
            sceneCenter.y = (location.y + sceneCenter.y) / 2;
        }

        JComponent view = this.getView();
        if (view != null) {
            Rectangle viewBounds = view.getVisibleRect();

            Point viewCenter = this.convertSceneToView(sceneCenter);

            view.scrollRectToVisible(new Rectangle(
                    viewCenter.x - viewBounds.width / 2,
                    viewCenter.y - viewBounds.height / 2,
                    viewBounds.width,
                    viewBounds.height));
        }
    }

    public void zoomScene() {
        JScrollPane pane = scrollPane;
        Rectangle prefBounds = this.getPreferredBounds();
        Dimension viewDim = pane.getViewportBorderBounds().getSize();

        double realwidth = (double) prefBounds.width * this.getZoomFactor();
        double realheight = (double) prefBounds.height * this.getZoomFactor();

        double zoomX = (double) viewDim.width / realwidth;
        double zoomY = (double) viewDim.height / realheight;
        double zoomFactor = Math.min(zoomX, zoomY);
        this.setZoomFactor(zoomFactor * 0.9);
    }

    //Enables Antialiasing
    @Override
    public void paintChildren() {
        Object anti = getGraphics().getRenderingHint(RenderingHints.KEY_ANTIALIASING);
        Object textAnti = getGraphics().getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);

        getGraphics().setRenderingHint(
                RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        getGraphics().setRenderingHint(
                RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        super.paintChildren();

        getGraphics().setRenderingHint(RenderingHints.KEY_ANTIALIASING, anti);
        getGraphics().setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, textAnti);
    }

    //select provider for node selection
    private RectangularSelectProvider createRectangularSelectProvider() {
        return new RectangularSelectProvider() {

            public void performSelection(Rectangle rectangle) {
                HashSet<CfgNode> set = new HashSet<>();

                //change to a rectangle with (x,offsetY) at the top left
                if (rectangle.width < 0) {
                    rectangle.x = rectangle.x + rectangle.width;
                    rectangle.width *= -1;
                }
                if (rectangle.height < 0) {
                    rectangle.y = rectangle.y + rectangle.height;
                    rectangle.height *= -1;
                }

                for (NodeWidget n : getNodeWidgets()) {
                    Point p = n.getLocation();
                    if (p != null && rectangle.contains(p)) {
                        set.add(n.getNodeModel());
                    }
                }
                setNodeSelection(set);
            }
        };
    }

    //select decorator for node selection
    private RectangularSelectDecorator createSelectDecorator(final CfgScene scene) {
        return new RectangularSelectDecorator() {

            public Widget createSelectionWidget() {
                scene.cleanNodeSelection();//unselected all nodes
                scene.revalidate();
                return new SelectionWidget(getScene());
            }
        };
    }

    private MoveProvider createMoveProvider() {
        return new MoveProvider() {

            private HashMap<Widget, Point> originals = new HashMap<>();
            private Point original = null;

            public void movementStarted(Widget widget) {
                originals.clear();
                NodeWidget nw = (NodeWidget) widget;
                if (selectedNodes.contains(nw.getNodeModel())) {//move current selection
                    for (CfgNode n : selectedNodes) {
                        Widget w = findWidget(n);
                        originals.put(w, w.getLocation());
                    }
                } else {//a newly-selected node will be moved               
                    CfgNode n = nw.getNodeModel();
                    HashSet<CfgNode> selectedNode = new HashSet<>(1);
                    selectedNode.add(n);
                    setNodeSelection(selectedNode);
                    originals.put(widget, widget.getPreferredLocation());
                    widget.revalidate();
                    validate();

                }
            }

            public void movementFinished(Widget widget) {
                NodeWidget nw = (NodeWidget) widget;
                if (selectedNodes.contains(nw.getNodeModel())) {
                    return;//to be able to move the current selection
                }

                HashSet<CfgNode> selectedNode = new HashSet<>(1);
                selectedNode.add(nw.getNodeModel());
                setNodeSelection(selectedNode);
                originals.clear();
                original = null;

            }

            public Point getOriginalLocation(Widget widget) {
                if (original == null) {
                    original = widget.getLocation();
                }

                return original;
            }
            //todo : find a cache algorithm which only routes edges
            //which are intersected by bounds of the moved rectangle

            public void setNewLocation(Widget widget, Point location) {
                Point org = getOriginalLocation(widget);
                int dx = location.x - org.x;
                int dy = location.y - org.y;
                for (Map.Entry<Widget, Point> entry : originals.entrySet()) {
                    Point point = entry.getValue();
                    entry.getKey().setPreferredLocation(new Point(point.x + dx, point.y + dy));
                }
                for (CfgEdge e : getEdges()) {
                    EdgeWidget ew = (EdgeWidget) findWidget(e);
                    if (ew.isVisible()) {
                        ew.reroute();
                    }
                }
            }
        };
    }

    private WidgetAction createContextMenuAction(final CfgScene scene) {
        return ActionFactory.createPopupMenuAction(new PopupMenuProvider() {

            public JPopupMenu getPopupMenu(Widget widget, Point point) {
                JPopupMenu menu = new JPopupMenu();
                NodeWidget nw = null;
                if (widget instanceof NodeWidget) {
                    nw = (NodeWidget) widget;
                    if (!selectedNodes.contains(nw.getNodeModel())) {
                        HashSet<CfgNode> selectedNode = new HashSet<>(1);
                        selectedNode.add(nw.getNodeModel());
                        setNodeSelection(selectedNode);
                    }
                } else if (scene.getSelectedNodes().size() == 1) {
                    nw = (NodeWidget) scene.findWidget(scene.getSelectedNodes().iterator().next());
                }

                if (nw != null) {
                    CfgNode node = nw.getNodeModel();
                    ArrayList<CfgNode> successors = new ArrayList<>();
                    ArrayList<CfgNode> predecessors = new ArrayList<>();
                    for (CfgEdge e : node.getOutputEdges()) {
                        successors.add(e.getTargetNode());
                    }
                    for (CfgEdge e : node.getInputEdges()) {
                        predecessors.add(e.getSourceNode());
                    }

                    if (predecessors.size() > 0) {
                        Collections.sort(predecessors, new NodeNameComparator());
                        JMenu predmenu = new JMenu("Go to predecessor");
                        for (CfgNode n : predecessors) {
                            GotoNodeAction action = new GotoNodeAction(n);
                            predmenu.add(action);
                        }
                        menu.add(predmenu);
                    }
                    if (successors.size() > 0) {
                        Collections.sort(successors, new NodeNameComparator());
                        JMenu succmenu = new JMenu("Go to successor");
                        for (CfgNode n : successors) {
                            GotoNodeAction action = new GotoNodeAction(n);
                            succmenu.add(action);
                        }
                        menu.add(succmenu);
                    }
                    if (successors.size() > 0 || predecessors.size() > 0) {
                        menu.addSeparator();
                    }
                }

                return menu;
            }
        });
    }

    @Override
    public void zoomIn() {
        this.setZoomFactor(this.getZoomFactor() * 1.1);
        this.validate();
    }

    @Override
    public void zoomOut() {
        this.setZoomFactor(this.getZoomFactor() * 0.9);
        this.validate();
    }

    @Override
    public void showAll() {
        this.zoomScene();
        this.validate();
    }

    private class NodeNameComparator implements Comparator<CfgNode> {

        public int compare(CfgNode node1, CfgNode node2) {
            String name1 = node1.getBasicBlock().getName().substring(1);
            String name2 = node2.getBasicBlock().getName().substring(1);
            Integer blocknum1 = Integer.parseInt(name1);
            Integer blocknum2 = Integer.parseInt(name2);
            return blocknum1.compareTo(blocknum2);
        }
    }

    private class GotoNodeAction extends AbstractAction {

        CfgNode node;

        GotoNodeAction(CfgNode node) {
            super(node.getBasicBlock().getName());
            this.node = node;
        }

        public void actionPerformed(ActionEvent e) {
            Set<CfgNode> nodes = new HashSet<>(1);
            nodes.add(node);
            setNodeSelection(nodes);
            centerSelection();
        }
    }

    private SceneListener createSceneListener(final CfgScene scene) {
        return new SceneListener() {

            public void sceneRepaint() {
            }

            public void sceneValidating() {
            }

            public void sceneValidated() {
                if (scene.isLoopClusterVisible()) { //update only if visible
                    for (LoopClusterWidget cw : getLoopidx2clusterwidget().values()) {
                        cw.updateClusterBounds();
                    }
                }
                for (EdgeSwitchWidget esw : inputSwitches.values()) {
                    esw.updatePosition();
                }

                for (EdgeSwitchWidget esw : outputSwitches.values()) {
                    esw.updatePosition();
                }
            }
        };
    }
}