diff visualizer/View/src/com/sun/hotspot/igv/view/scene/DiagramScene.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 diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/visualizer/View/src/com/sun/hotspot/igv/view/scene/DiagramScene.java	Tue Feb 07 22:41:09 2012 +0100
@@ -0,0 +1,964 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+package com.sun.hotspot.igv.view.scene;
+
+import com.oracle.graal.visualizer.editor.DiagramViewModel;
+import com.oracle.graal.visualizer.sharedactions.ExportSVGCookie;
+import com.oracle.graal.visualizer.sharedactions.ZoomCookie;
+import com.sun.hotspot.igv.data.ChangedListener;
+import com.sun.hotspot.igv.data.Pair;
+import com.sun.hotspot.igv.data.Properties;
+import com.sun.hotspot.igv.graph.*;
+import com.sun.hotspot.igv.hierarchicallayout.HierarchicalLayoutManager;
+import com.sun.hotspot.igv.layout.LayoutGraph;
+import com.sun.hotspot.igv.util.ColorIcon;
+import com.sun.hotspot.igv.util.DoubleClickAction;
+import com.sun.hotspot.igv.util.PropertiesSheet;
+import com.sun.hotspot.igv.view.widgets.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.List;
+import java.util.*;
+import javax.swing.*;
+import org.netbeans.api.visual.action.*;
+import org.netbeans.api.visual.animator.SceneAnimator;
+import org.netbeans.api.visual.layout.LayoutFactory;
+import org.netbeans.api.visual.model.*;
+import org.netbeans.api.visual.widget.LayerWidget;
+import org.netbeans.api.visual.widget.Widget;
+import org.openide.nodes.AbstractNode;
+import org.openide.nodes.Children;
+import org.openide.nodes.Sheet;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.AbstractLookup;
+import org.openide.util.lookup.InstanceContent;
+
+public class DiagramScene extends ObjectScene implements ExportSVGCookie, ZoomCookie {
+
+    private CustomizablePanWidgetAction panAction;
+    private WidgetAction hoverAction;
+    private WidgetAction selectAction;
+    private Lookup lookup;
+    private InstanceContent content;
+    private Action[] actions;
+    private LayerWidget connectionLayer;
+    private JScrollPane scrollPane;
+    private LayerWidget mainLayer;
+    private LayerWidget blockLayer;
+    private Widget topLeft;
+    private Widget bottomRight;
+    private DiagramViewModel model;
+    private WidgetAction zoomAction;
+    private boolean rebuilding;
+    /**
+     * The alpha level of partially visible figures.
+     */
+    public static final float ALPHA = 0.4f;
+    /**
+     * The offset of the graph to the border of the window showing it.
+     */
+    public static final int BORDER_SIZE = 20;
+    public static final int UNDOREDO_LIMIT = 100;
+    public static final int SCROLL_UNIT_INCREMENT = 80;
+    public static final int SCROLL_BLOCK_INCREMENT = 400;
+    public static final float ZOOM_MAX_FACTOR = 3.0f;
+    public static final float ZOOM_MIN_FACTOR = 0.0f;//0.15f;
+    public static final float ZOOM_INCREMENT = 1.5f;
+    public static final int SLOT_OFFSET = 6;
+    public static final int ANIMATION_LIMIT = 40;
+    private PopupMenuProvider popupMenuProvider = new PopupMenuProvider() {
+
+        @Override
+        public JPopupMenu getPopupMenu(Widget widget, Point localLocation) {
+            return DiagramScene.this.createPopupMenu();
+        }
+    };
+    private RectangularSelectDecorator rectangularSelectDecorator = new RectangularSelectDecorator() {
+
+        @Override
+        public Widget createSelectionWidget() {
+            Widget widget = new Widget(DiagramScene.this);
+            widget.setBorder(BorderFactory.createLineBorder(Color.black, 2));
+            widget.setForeground(Color.red);
+            return widget;
+        }
+    };
+
+    @SuppressWarnings("unchecked")
+    public <T> T getWidget(Object o) {
+        Widget w = this.findWidget(o);
+        return (T) w;
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T> T getWidget(Object o, Class<T> klass) {
+        Widget w = this.findWidget(o);
+        return (T) w;
+    }
+
+    public void zoomOut() {
+        double zoom = getZoomFactor();
+        Point viewPosition = getScrollPane().getViewport().getViewPosition();
+        double newZoom = zoom / DiagramScene.ZOOM_INCREMENT;
+        if (newZoom > DiagramScene.ZOOM_MIN_FACTOR) {
+            setZoomFactor(newZoom);
+            validate();
+            getScrollPane().getViewport().setViewPosition(new Point((int) (viewPosition.x / DiagramScene.ZOOM_INCREMENT), (int) (viewPosition.y / DiagramScene.ZOOM_INCREMENT)));
+        }
+    }
+
+    public void zoomIn() {
+
+        double zoom = getZoomFactor();
+        Point viewPosition = getScrollPane().getViewport().getViewPosition();
+        double newZoom = zoom * DiagramScene.ZOOM_INCREMENT;
+        if (newZoom < DiagramScene.ZOOM_MAX_FACTOR) {
+            setZoomFactor(newZoom);
+            validate();
+            getScrollPane().getViewport().setViewPosition(new Point((int) (viewPosition.x * DiagramScene.ZOOM_INCREMENT), (int) (viewPosition.y * DiagramScene.ZOOM_INCREMENT)));
+        }
+    }
+
+    private void centerFigures(Collection<Figure> list) {
+        gotoFigures(list);
+    }
+
+    private RectangularSelectProvider rectangularSelectProvider = new RectangularSelectProvider() {
+
+        @Override
+        public void performSelection(Rectangle rectangle) {
+            if (rectangle.width < 0) {
+                rectangle.x += rectangle.width;
+                rectangle.width *= -1;
+            }
+
+            if (rectangle.height < 0) {
+                rectangle.y += rectangle.height;
+                rectangle.height *= -1;
+            }
+
+            Set<Object> selectedObjects = new HashSet<>();
+            for (Figure f : getModel().getDiagramToView().getFigures()) {
+                FigureWidget w = getWidget(f);
+                if (w != null) {
+                    Rectangle r = new Rectangle(w.getBounds());
+                    r.setLocation(w.getLocation());
+
+                    if (r.intersects(rectangle)) {
+                        selectedObjects.add(f);
+                    }
+
+                    for (Slot s : f.getSlots()) {
+                        SlotWidget sw = getWidget(s);
+                        Rectangle r2 = new Rectangle(sw.getBounds());
+                        r2.setLocation(sw.convertLocalToScene(new Point(0, 0)));
+
+                        if (r2.intersects(rectangle)) {
+                            selectedObjects.add(s);
+                        }
+                    }
+                } else {
+                    assert false : "w should not be null here!";
+                }
+            }
+
+            setSelectedObjects(selectedObjects);
+        }
+    };
+    private MouseWheelListener mouseWheelListener = new MouseWheelListener() {
+
+        @Override
+        public void mouseWheelMoved(MouseWheelEvent e) {
+            if (e.isControlDown()) {
+                DiagramScene.this.relayoutWithoutLayout(null);
+            }
+        }
+    };
+
+    public Point getScrollPosition() {
+        return getScrollPane().getViewport().getViewPosition();
+    }
+
+    public void setScrollPosition(Point p) {
+        getScrollPane().getViewport().setViewPosition(p);
+    }
+
+    private JScrollPane createScrollPane() {
+        JComponent comp = this.createView();
+        comp.setDoubleBuffered(true);
+        comp.setBackground(Color.WHITE);
+        comp.setOpaque(true);
+        this.setBackground(Color.WHITE);
+        this.setOpaque(true);
+        JScrollPane result = new JScrollPane(comp);
+        result.setBackground(Color.WHITE);
+        result.getVerticalScrollBar().setUnitIncrement(SCROLL_UNIT_INCREMENT);
+        result.getVerticalScrollBar().setBlockIncrement(SCROLL_BLOCK_INCREMENT);
+        result.getHorizontalScrollBar().setUnitIncrement(SCROLL_UNIT_INCREMENT);
+        result.getHorizontalScrollBar().setBlockIncrement(SCROLL_BLOCK_INCREMENT);
+        return result;
+    }
+    private ObjectSceneListener selectionChangedListener = new ObjectSceneListener() {
+
+        @Override
+        public void objectAdded(ObjectSceneEvent arg0, Object arg1) {
+        }
+
+        @Override
+        public void objectRemoved(ObjectSceneEvent arg0, Object arg1) {
+        }
+
+        @Override
+        public void objectStateChanged(ObjectSceneEvent e, Object o, ObjectState oldState, ObjectState newState) {
+        }
+
+        @Override
+        public void selectionChanged(ObjectSceneEvent e, Set<Object> oldSet, Set<Object> newSet) {
+            DiagramScene scene = (DiagramScene) e.getObjectScene();
+            if (scene.isRebuilding()) {
+                return;
+            }
+
+            content.set(newSet, null);
+
+            Set<Integer> nodeSelection = new HashSet<>();
+            for (Object o : newSet) {
+                if (o instanceof Properties.Provider) {
+                    final Properties.Provider provider = (Properties.Provider) o;
+                    AbstractNode node = new AbstractNode(Children.LEAF) {
+
+                        @Override
+                        protected Sheet createSheet() {
+                            Sheet s = super.createSheet();
+                            PropertiesSheet.initializeSheet(provider.getProperties(), s);
+                            return s;
+                        }
+                    };
+                    node.setDisplayName(provider.getProperties().get("name"));
+                    content.add(node);
+                }
+
+
+                if (o instanceof Figure) {
+                    nodeSelection.addAll(((Figure) o).getSource().getSourceNodesAsSet());
+                } else if (o instanceof Slot) {
+                    nodeSelection.addAll(((Slot) o).getSource().getSourceNodesAsSet());
+                }
+            }
+            getModel().setSelectedNodes(nodeSelection);
+        }
+
+        @Override
+        public void highlightingChanged(ObjectSceneEvent e, Set<Object> oldSet, Set<Object> newSet) {
+            Set<Integer> nodeHighlighting = new HashSet<>();
+            for (Object o : newSet) {
+                if (o instanceof Figure) {
+                    nodeHighlighting.addAll(((Figure) o).getSource().getSourceNodesAsSet());
+                } else if (o instanceof Slot) {
+                    nodeHighlighting.addAll(((Slot) o).getSource().getSourceNodesAsSet());
+                }
+            }
+//            boolean b = highlightedCoordinatorListener.isEnabled();
+//            highlightedCoordinatorListener.setEnabled(false);
+//            SelectionCoordinator.getInstance().setHighlightedObjects(nodeHighlighting);
+//            highlightedCoordinatorListener.setEnabled(b);
+            validate();
+        }
+
+        @Override
+        public void hoverChanged(ObjectSceneEvent e, Object oldObject, Object newObject) {
+            Set<Object> newHighlightedObjects = new HashSet<>(DiagramScene.this.getHighlightedObjects());
+            if (oldObject != null) {
+                newHighlightedObjects.remove(oldObject);
+            }
+            if (newObject != null) {
+                newHighlightedObjects.add(newObject);
+            }
+            DiagramScene.this.setHighlightedObjects(newHighlightedObjects);
+        }
+
+        @Override
+        public void focusChanged(ObjectSceneEvent arg0, Object arg1, Object arg2) {
+        }
+    };
+
+    public void setActions(Action[] actions) {
+        this.actions = actions;
+    }
+    
+    
+
+    public DiagramScene(DiagramViewModel model) {
+
+        this.model = model;
+        content = new InstanceContent();
+        lookup = new AbstractLookup(content);
+        content.add(this);
+
+        this.setCheckClipping(true);
+
+        scrollPane = createScrollPane();
+
+        hoverAction = createObjectHoverAction();
+
+        // This panAction handles the event only when the left mouse button is
+        // pressed without any modifier keys, otherwise it will not consume it
+        // and the selection action (below) will handle the event
+        panAction = new CustomizablePanWidgetAction(~0, MouseEvent.BUTTON1_DOWN_MASK);
+        this.getActions().addAction(panAction);
+
+        selectAction = createSelectAction();
+        this.getActions().addAction(selectAction);
+
+        blockLayer = new LayerWidget(this);
+        this.addChild(blockLayer);
+
+        mainLayer = new LayerWidget(this);
+        this.addChild(mainLayer);
+
+        topLeft = new Widget(this);
+        topLeft.setPreferredLocation(new Point(-BORDER_SIZE, -BORDER_SIZE));
+        this.addChild(topLeft);
+
+        bottomRight = new Widget(this);
+        bottomRight.setPreferredLocation(new Point(-BORDER_SIZE, -BORDER_SIZE));
+        this.addChild(bottomRight);
+
+        connectionLayer = new LayerWidget(this);
+        this.addChild(connectionLayer);
+
+        LayerWidget selectionLayer = new LayerWidget(this);
+        this.addChild(selectionLayer);
+
+        this.setLayout(LayoutFactory.createAbsoluteLayout());
+
+        this.getInputBindings().setZoomActionModifiers(KeyEvent.CTRL_MASK);
+        zoomAction = ActionFactory.createMouseCenteredZoomAction(1.2);
+        this.getActions().addAction(zoomAction);
+        this.getView().addMouseWheelListener(mouseWheelListener);
+        this.getActions().addAction(ActionFactory.createPopupMenuAction(popupMenuProvider));
+
+        this.getActions().addAction(ActionFactory.createWheelPanAction());
+
+        LayerWidget selectLayer = new LayerWidget(this);
+        this.addChild(selectLayer);
+        this.getActions().addAction(ActionFactory.createRectangularSelectAction(rectangularSelectDecorator, selectLayer, rectangularSelectProvider));
+
+        this.addObjectSceneListener(selectionChangedListener, ObjectSceneEventType.OBJECT_SELECTION_CHANGED, ObjectSceneEventType.OBJECT_HIGHLIGHTING_CHANGED, ObjectSceneEventType.OBJECT_HOVER_CHANGED);
+        
+        update();
+    }
+
+    public DiagramViewModel getModel() {
+        return model;
+    }
+
+    public JScrollPane getScrollPane() {
+        return scrollPane;
+    }
+
+    Component getComponent() {
+        return scrollPane;
+    }
+
+    public boolean isAllVisible() {
+        return getModel().getHiddenNodes().isEmpty();
+    }
+
+    public Action createGotoAction(final Figure f) {
+        final DiagramScene diagramScene = this;
+        Action a = new AbstractAction() {
+
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                diagramScene.gotoFigure(f);
+            }
+        };
+
+        a.setEnabled(true);
+        a.putValue(Action.SMALL_ICON, new ColorIcon(f.getColor()));
+        String name = f.getLines()[0];
+
+        name += " (";
+
+        if (!this.getWidget(f, FigureWidget.class).isVisible()) {
+            name += "hidden";
+        }
+        name += ")";
+        a.putValue(Action.NAME, name);
+        return a;
+    }
+
+    private void update() {
+        mainLayer.removeChildren();
+        blockLayer.removeChildren();
+
+        rebuilding = true;
+
+        Collection<Object> objects = new ArrayList<>(this.getObjects());
+        for (Object o : objects) {
+            this.removeObject(o);
+        }
+
+        Diagram d = getModel().getDiagramToView();
+
+        for (Figure f : d.getFigures()) {
+            FigureWidget w = new FigureWidget(f, hoverAction, selectAction, this, mainLayer);
+            w.getActions().addAction(ActionFactory.createPopupMenuAction(w));
+            w.getActions().addAction(selectAction);
+            w.getActions().addAction(hoverAction);
+            w.setVisible(false);
+
+            this.addObject(f, w);
+
+            for (InputSlot s : f.getInputSlots()) {
+                SlotWidget sw = new InputSlotWidget(s, this, w, w);
+                addObject(s, sw);
+                sw.getActions().addAction(new DoubleClickAction(sw));
+                sw.getActions().addAction(hoverAction);
+                sw.getActions().addAction(selectAction);
+            }
+
+            for (OutputSlot s : f.getOutputSlots()) {
+                SlotWidget sw = new OutputSlotWidget(s, this, w, w);
+                addObject(s, sw);
+                sw.getActions().addAction(new DoubleClickAction(sw));
+                sw.getActions().addAction(hoverAction);
+                sw.getActions().addAction(selectAction);
+            }
+        }
+
+        rebuilding = false;
+        this.smallUpdate(true);
+    }
+
+    public boolean isRebuilding() {
+        return rebuilding;
+    }
+
+    private void smallUpdate(boolean relayout) {
+
+        this.updateHiddenNodes(model.getHiddenNodes(), relayout);
+        this.validate();
+    }
+
+    private boolean isVisible(Connection c) {
+        FigureWidget w1 = getWidget(c.getInputSlot().getFigure());
+        FigureWidget w2 = getWidget(c.getOutputSlot().getFigure());
+
+        if (w1.isVisible() && w2.isVisible()) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private void relayout(Set<Widget> oldVisibleWidgets) {
+        System.out.println("relayout called with old visible widgets: " + oldVisibleWidgets);
+
+        Diagram diagram = getModel().getDiagramToView();
+
+        HashSet<Figure> figures = new HashSet<>();
+
+        for (Figure f : diagram.getFigures()) {
+            FigureWidget w = getWidget(f);
+            if (w.isVisible()) {
+                figures.add(f);
+            }
+        }
+
+        HashSet<Connection> edges = new HashSet<>();
+
+        for (Connection c : diagram.getConnections()) {
+            if (isVisible(c)) {
+                edges.add(c);
+            }
+        }
+
+        HierarchicalLayoutManager manager = new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS);
+        manager.setMaxLayerLength(10);
+        manager.doLayout(new LayoutGraph(edges, figures));
+        relayoutWithoutLayout(oldVisibleWidgets);
+    }
+    private Set<Pair<Point, Point>> lineCache = new HashSet<>();
+
+    private void relayoutWithoutLayout(Set<Widget> oldVisibleWidgets) {
+
+        System.out.println("relayout without layout with visible widgets: " + oldVisibleWidgets);
+
+        Diagram diagram = getModel().getDiagramToView();
+
+        int maxX = -BORDER_SIZE;
+        int maxY = -BORDER_SIZE;
+        for (Figure f : diagram.getFigures()) {
+            FigureWidget w = getWidget(f);
+            if (w.isVisible()) {
+                Point p = f.getPosition();
+                Dimension d = f.getSize();
+                maxX = Math.max(maxX, p.x + d.width);
+                maxY = Math.max(maxY, p.y + d.height);
+            }
+        }
+
+        for (Connection c : diagram.getConnections()) {
+            List<Point> points = c.getControlPoints();
+            FigureWidget w1 = getWidget((Figure) c.getTo().getVertex());
+            FigureWidget w2 = getWidget((Figure) c.getFrom().getVertex());
+            if (w1.isVisible() && w2.isVisible()) {
+                for (Point p : points) {
+                    if (p != null) {
+                        maxX = Math.max(maxX, p.x);
+                        maxY = Math.max(maxY, p.y);
+                    }
+                }
+            }
+        }
+
+        bottomRight.setPreferredLocation(new Point(maxX + BORDER_SIZE, maxY + BORDER_SIZE));
+        int offx = 0;
+        int offy = 0;
+        int curWidth = maxX + 2 * BORDER_SIZE;
+        int curHeight = maxY + 2 * BORDER_SIZE;
+
+        Rectangle bounds = this.getScrollPane().getBounds();
+        bounds.width /= getZoomFactor();
+        bounds.height /= getZoomFactor();
+        if (curWidth < bounds.width) {
+            offx = (bounds.width - curWidth) / 2;
+        }
+
+        if (curHeight < bounds.height) {
+            offy = (bounds.height - curHeight) / 2;
+        }
+
+        final int offx2 = offx;
+        final int offy2 = offy;
+
+        SceneAnimator animator = this.getSceneAnimator();
+        connectionLayer.removeChildren();
+        int visibleFigureCount = 0;
+        for (Figure f : diagram.getFigures()) {
+            if (getWidget(f, FigureWidget.class).isVisible()) {
+                visibleFigureCount++;
+            }
+        }
+
+
+        Set<Pair<Point, Point>> lastLineCache = lineCache;
+        lineCache = new HashSet<>();
+        for (Figure f : diagram.getFigures()) {
+            for (OutputSlot s : f.getOutputSlots()) {
+                SceneAnimator anim = animator;
+                if (visibleFigureCount > ANIMATION_LIMIT || oldVisibleWidgets == null) {
+                    anim = null;
+                }
+                processOutputSlot(lastLineCache, s, s.getConnections(), 0, null, null, offx2, offy2, anim);
+            }
+        }
+
+        for (Figure f : diagram.getFigures()) {
+            FigureWidget w = getWidget(f);
+            if (w.isVisible()) {
+                Point p = f.getPosition();
+                Point p2 = new Point(p.x + offx2, p.y + offy2);
+                if ((visibleFigureCount <= ANIMATION_LIMIT && oldVisibleWidgets != null && oldVisibleWidgets.contains(w))) {
+                    animator.animatePreferredLocation(w, p2);
+                } else {
+                    w.setPreferredLocation(p2);
+                    animator.animatePreferredLocation(w, p2);
+                }
+            }
+        }
+
+        this.validate();
+    }
+    private final Point specialNullPoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
+
+    private void processOutputSlot(Set<Pair<Point, Point>> lastLineCache, OutputSlot s, List<Connection> connections, int controlPointIndex, Point lastPoint, LineWidget predecessor, int offx, int offy, SceneAnimator animator) {
+        Map<Point, List<Connection>> pointMap = new HashMap<>(connections.size());
+
+        for (Connection c : connections) {
+
+            if (!isVisible(c)) {
+                continue;
+            }
+
+            List<Point> controlPoints = c.getControlPoints();
+            if (controlPointIndex >= controlPoints.size()) {
+                continue;
+            }
+
+            Point cur = controlPoints.get(controlPointIndex);
+            if (cur == null) {
+                cur = specialNullPoint;
+            } else if (controlPointIndex == 0 && !s.shouldShowName()) {
+                cur = new Point(cur.x, cur.y - SLOT_OFFSET);
+            } else if (controlPointIndex == controlPoints.size() - 1 && !c.getInputSlot().shouldShowName()) {
+                cur = new Point(cur.x, cur.y + SLOT_OFFSET);
+            }
+
+            if (pointMap.containsKey(cur)) {
+                pointMap.get(cur).add(c);
+            } else {
+                List<Connection> newList = new ArrayList<>(2);
+                newList.add(c);
+                pointMap.put(cur, newList);
+            }
+
+        }
+
+        for (Point p : pointMap.keySet()) {
+            List<Connection> connectionList = pointMap.get(p);
+
+            boolean isBold = false;
+            boolean isDashed = true;
+
+            for (Connection c : connectionList) {
+
+                if (c.getStyle() == Connection.ConnectionStyle.BOLD) {
+                    isBold = true;
+                }
+
+                if (c.getStyle() != Connection.ConnectionStyle.DASHED) {
+                    isDashed = false;
+                }
+            }
+
+            LineWidget newPredecessor = predecessor;
+            if (p == specialNullPoint) {
+            } else if (lastPoint == specialNullPoint) {
+            } else if (lastPoint != null) {
+                Point p1 = new Point(lastPoint.x + offx, lastPoint.y + offy);
+                Point p2 = new Point(p.x + offx, p.y + offy);
+
+                Pair<Point, Point> curPair = new Pair<>(p1, p2);
+                SceneAnimator curAnimator = animator;
+                if (lastLineCache.contains(curPair)) {
+                    curAnimator = null;
+                }
+                LineWidget w = new LineWidget(this, s, connectionList, p1, p2, predecessor, curAnimator, isBold, isDashed);
+                lineCache.add(curPair);
+
+                newPredecessor = w;
+                connectionLayer.addChild(w);
+                w.getActions().addAction(hoverAction);
+            }
+
+            processOutputSlot(lastLineCache, s, connectionList, controlPointIndex + 1, p, newPredecessor, offx, offy, animator);
+        }
+    }
+
+    @Override
+    public Lookup getLookup() {
+        return lookup;
+    }
+
+    private void gotoFigures(final Collection<Figure> figures) {
+        Rectangle overall = null;
+        getModel().showFigures(figures);
+        for (Figure f : figures) {
+
+            FigureWidget fw = getWidget(f);
+            if (fw != null) {
+                Rectangle r = fw.getBounds();
+                Point p = fw.getLocation();
+                Rectangle r2 = new Rectangle(p.x, p.y, r.width, r.height);
+
+                if (overall == null) {
+                    overall = r2;
+                } else {
+                    overall = overall.union(r2);
+                }
+            }
+        }
+        if (overall != null) {
+            centerRectangle(overall);
+        }
+    }
+
+    private Set<Object> idSetToObjectSet(Set<Object> ids) {
+
+        Set<Object> result = new HashSet<>();
+        for (Figure f : getModel().getDiagramToView().getFigures()) {
+            if (DiagramScene.doesIntersect(f.getSource().getSourceNodesAsSet(), ids)) {
+                result.add(f);
+            }
+
+            for (Slot s : f.getSlots()) {
+                if (DiagramScene.doesIntersect(s.getSource().getSourceNodesAsSet(), ids)) {
+                    result.add(s);
+                }
+            }
+        }
+        return result;
+    }
+
+    public void gotoSelection(Set<Object> ids) {
+
+        Rectangle overall = null;
+        Set<Integer> hiddenNodes = new HashSet<>(this.getModel().getHiddenNodes());
+        hiddenNodes.removeAll(ids);
+        this.getModel().showNot(hiddenNodes);
+
+        Set<Object> objects = idSetToObjectSet(ids);
+        for (Object o : objects) {
+
+            Widget w = getWidget(o);
+            if (w != null) {
+                Rectangle r = w.getBounds();
+                Point p = w.convertLocalToScene(new Point(0, 0));
+
+                Rectangle r2 = new Rectangle(p.x, p.y, r.width, r.height);
+
+                if (overall == null) {
+                    overall = r2;
+                } else {
+                    overall = overall.union(r2);
+                }
+            }
+        }
+        if (overall != null) {
+            centerRectangle(overall);
+        }
+
+        setSelectedObjects(objects);
+    }
+
+    private Point calcCenter(Rectangle r) {
+
+        Point center = new Point((int) r.getCenterX(), (int) r.getCenterY());
+        center.x -= getScrollPane().getViewport().getViewRect().width / 2;
+        center.y -= getScrollPane().getViewport().getViewRect().height / 2;
+
+        // Ensure to be within area
+        center.x = Math.max(0, center.x);
+        center.x = Math.min(getScrollPane().getViewport().getViewSize().width - getScrollPane().getViewport().getViewRect().width, center.x);
+        center.y = Math.max(0, center.y);
+        center.y = Math.min(getScrollPane().getViewport().getViewSize().height - getScrollPane().getViewport().getViewRect().height, center.y);
+
+        return center;
+    }
+
+    private void centerRectangle(Rectangle r) {
+
+        if (getScrollPane().getViewport().getViewRect().width == 0 || getScrollPane().getViewport().getViewRect().height == 0) {
+            return;
+        }
+
+        Rectangle r2 = new Rectangle(r.x, r.y, r.width, r.height);
+        r2 = convertSceneToView(r2);
+
+        double factorX = (double) r2.width / (double) getScrollPane().getViewport().getViewRect().width;
+        double factorY = (double) r2.height / (double) getScrollPane().getViewport().getViewRect().height;
+        double factor = Math.max(factorX, factorY);
+        if (factor >= 1.0) {
+            Point p = getScrollPane().getViewport().getViewPosition();
+            setZoomFactor(getZoomFactor() / factor);
+            r2.x /= factor;
+            r2.y /= factor;
+            r2.width /= factor;
+            r2.height /= factor;
+            getScrollPane().getViewport().setViewPosition(calcCenter(r2));
+        } else {
+            getScrollPane().getViewport().setViewPosition(calcCenter(r2));
+        }
+    }
+
+    void setSelection(Collection<Figure> list) {
+        super.setSelectedObjects(new HashSet<>(list));
+        centerFigures(list);
+    }
+
+    private boolean isVisible(Figure f) {
+        for (Integer n : f.getSource().getSourceNodesAsSet()) {
+            if (getModel().getHiddenNodes().contains(n)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static boolean doesIntersect(Set<?> s1, Set<?> s2) {
+        if (s1.size() > s2.size()) {
+            Set<?> tmp = s1;
+            s1 = s2;
+            s2 = tmp;
+        }
+
+        for (Object o : s1) {
+            if (s2.contains(o)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private void updateHiddenNodes(Set<Integer> newHiddenNodes, boolean doRelayout) {
+
+        System.out.println("newHiddenNodes: " + newHiddenNodes);
+
+        Diagram diagram = getModel().getDiagramToView();
+        assert diagram != null;
+
+        Set<Widget> oldVisibleWidgets = new HashSet<>();
+
+        for (Figure f : diagram.getFigures()) {
+            FigureWidget w = getWidget(f);
+            if (w != null && w.isVisible()) {
+                oldVisibleWidgets.add(w);
+            }
+        }
+
+        for (Figure f : diagram.getFigures()) {
+            boolean hiddenAfter = doesIntersect(f.getSource().getSourceNodesAsSet(), newHiddenNodes);
+
+            FigureWidget w = getWidget(f);
+            w.setBoundary(false);
+            if (!hiddenAfter) {
+                // Figure is shown
+                w.setVisible(true);
+            } else {
+                // Figure is hidden
+                w.setVisible(false);
+            }
+        }
+
+        if (getModel().getShowNodeHull()) {
+            List<FigureWidget> boundaries = new ArrayList<>();
+            for (Figure f : diagram.getFigures()) {
+                FigureWidget w = getWidget(f);
+                if (!w.isVisible()) {
+                    Set<Figure> set = new HashSet<>(f.getPredecessorSet());
+                    set.addAll(f.getSuccessorSet());
+
+                    boolean b = false;
+                    for (Figure neighbor : set) {
+                        FigureWidget neighborWidget = getWidget(neighbor);
+                        if (neighborWidget.isVisible()) {
+                            b = true;
+                            break;
+                        }
+                    }
+
+                    if (b) {
+                        w.setBoundary(true);
+                        boundaries.add(w);
+                    }
+                }
+            }
+
+            for (FigureWidget w : boundaries) {
+                if (w.isBoundary()) {
+                    w.setVisible(true);
+                }
+            }
+        }
+
+        if (doRelayout) {
+            relayout(oldVisibleWidgets);
+        }
+        this.validate();
+    }
+
+    private void showFigure(Figure f) {
+        HashSet<Integer> newHiddenNodes = new HashSet<>(getModel().getHiddenNodes());
+        newHiddenNodes.removeAll(f.getSource().getSourceNodesAsSet());
+        updateHiddenNodes(newHiddenNodes, true);
+    }
+
+    public void show(final Figure f) {
+        showFigure(f);
+    }
+
+    public void setSelectedObjects(Object... args) {
+        Set<Object> set = new HashSet<>();
+        for (Object o : args) {
+            set.add(o);
+        }
+        super.setSelectedObjects(set);
+    }
+
+    private void centerWidget(Widget w) {
+        Rectangle r = w.getBounds();
+        Point p = w.getLocation();
+        centerRectangle(new Rectangle(p.x, p.y, r.width, r.height));
+    }
+
+    public void gotoFigure(final Figure f) {
+        if (!isVisible(f)) {
+            showFigure(f);
+        }
+
+        FigureWidget fw = getWidget(f);
+        if (fw != null) {
+            setSelection(new HashSet<>(Arrays.asList(f)));
+        }
+    }
+
+    public JPopupMenu createPopupMenu() {
+        JPopupMenu menu = new JPopupMenu();
+        for (Action a : actions) {
+            if (a == null) {
+                menu.addSeparator();
+            } else {
+                menu.add(a);
+            }
+        }
+        return menu;
+    }
+    
+    private final ChangedListener<DiagramViewModel> fullChange = new ChangedListener<DiagramViewModel>() {
+
+        @Override
+        public void changed(DiagramViewModel source) {
+            assert source == model : "Receive only changed event from current model!";
+            assert source != null;
+            update();
+        }
+    };
+    private final ChangedListener<DiagramViewModel> hiddenNodesChange = new ChangedListener<DiagramViewModel>() {
+
+        @Override
+        public void changed(DiagramViewModel source) {
+            assert source == model : "Receive only changed event from current model!";
+            assert source != null;
+            smallUpdate(true);
+        }
+    };
+    private final ChangedListener<DiagramViewModel> selectionChange = new ChangedListener<DiagramViewModel>() {
+
+        @Override
+        public void changed(DiagramViewModel source) {
+            assert source == model : "Receive only changed event from current model!";
+            assert source != null;
+            smallUpdate(false);
+        }
+    };
+
+    @Override
+    public void showAll() {
+        // TODO(tw): Implement.
+    }
+}