gt:             FigureWidget w = figureWidgets.get(f);
context: /*
   * Copyright 2008 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
   * CA 95054 USA or visit www.sun.com if you need additional information or
   * have any questions.
   *
   */
  package com.sun.hotspot.igv.view;
  
  import com.sun.hotspot.igv.view.widgets.BlockWidget;
  import com.sun.hotspot.igv.view.widgets.LineWidget;
  import com.sun.hotspot.igv.util.DoubleClickAction;
  import com.sun.hotspot.igv.data.InputBlock;
  import com.sun.hotspot.igv.data.InputNode;
  import com.sun.hotspot.igv.graph.Connection;
  import com.sun.hotspot.igv.graph.Diagram;
  import com.sun.hotspot.igv.graph.Figure;
  import com.sun.hotspot.igv.graph.InputSlot;
  import com.sun.hotspot.igv.graph.OutputSlot;
  import com.sun.hotspot.igv.graph.Slot;
  import com.sun.hotspot.igv.hierarchicallayout.HierarchicalClusterLayoutManager;
  import com.sun.hotspot.igv.hierarchicallayout.OldHierarchicalLayoutManager;
  import com.sun.hotspot.igv.hierarchicallayout.HierarchicalLayoutManager;
  import com.sun.hotspot.igv.view.widgets.FigureWidget;
  import com.sun.hotspot.igv.view.widgets.InputSlotWidget;
  import com.sun.hotspot.igv.view.widgets.OutputSlotWidget;
  import com.sun.hotspot.igv.view.widgets.SlotWidget;
  import com.sun.hotspot.igv.layout.LayoutGraph;
  import com.sun.hotspot.igv.data.services.Scheduler;
  import com.sun.hotspot.igv.data.ChangedListener;
  import com.sun.hotspot.igv.graph.Block;
  import com.sun.hotspot.igv.util.ColorIcon;
  import com.sun.hotspot.igv.util.ExtendedSelectAction;
  import java.awt.Color;
  import java.awt.Dimension;
  import java.awt.Point;
  import java.awt.Rectangle;
  import java.awt.event.ActionEvent;
  import java.awt.event.FocusEvent;
  import java.awt.event.FocusListener;
  import java.awt.event.MouseEvent;
  import java.awt.event.MouseListener;
  import java.awt.event.MouseMotionListener;
  import java.awt.event.MouseWheelEvent;
  import java.awt.event.MouseWheelListener;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import javax.swing.AbstractAction;
  import javax.swing.Action;
  import javax.swing.BorderFactory;
  import javax.swing.JComponent;
  import javax.swing.JPopupMenu;
  import javax.swing.JScrollPane;
  import javax.swing.SwingUtilities;
  import javax.swing.event.ChangeEvent;
  import javax.swing.event.ChangeListener;
  import javax.swing.event.UndoableEditEvent;
  import javax.swing.undo.AbstractUndoableEdit;
  import javax.swing.undo.CannotRedoException;
  import javax.swing.undo.CannotUndoException;
  import org.netbeans.api.visual.action.ActionFactory;
  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.SelectProvider;
  import org.netbeans.api.visual.action.WidgetAction;
  import org.netbeans.api.visual.animator.SceneAnimator;
  import org.netbeans.api.visual.layout.LayoutFactory;
  import org.netbeans.api.visual.widget.ConnectionWidget;
  import org.netbeans.api.visual.widget.LayerWidget;
  import org.netbeans.api.visual.widget.Scene;
  import org.netbeans.api.visual.widget.Widget;
  import org.netbeans.api.visual.widget.LabelWidget;
  import org.openide.awt.UndoRedo;
  import org.openide.util.Lookup;
  import org.openide.util.lookup.AbstractLookup;
  import org.openide.util.lookup.InstanceContent;
  
  /**
   *
   * @author Thomas Wuerthinger
   */
  public class DiagramScene extends Scene implements ChangedListener<DiagramViewModel> {
  
      private HashMap<Figure, FigureWidget> figureWidgets;
      private HashMap<Slot, SlotWidget> slotWidgets;
      private HashMap<Connection, ConnectionWidget> connectionWidgets;
      private HashMap<InputBlock, BlockWidget> blockWidgets;
      private Widget hoverWidget;
      private WidgetAction hoverAction;
      private List<FigureWidget> selectedWidgets;
      private Lookup lookup;
      private InstanceContent content;
      private Action[] actions;
      private LayerWidget connectionLayer;
      private JScrollPane scrollPane;
      private UndoRedo.Manager undoRedoManager;
      private LayerWidget mainLayer;
      private LayerWidget slotLayer;
      private LayerWidget blockLayer;
      private double realZoomFactor;
      private BoundedZoomAction zoomAction;
      private WidgetAction panAction;
      private Widget topLeft;
      private Widget bottomRight;
      private LayerWidget startLayer;
      private LabelWidget startLabel;
      private DiagramViewModel model;
      private DiagramViewModel modelCopy;
      public static final int AFTER = 1;
      public static final int BEFORE = 1;
      public static final float ALPHA = 0.4f;
      public static final int GRID_SIZE = 30;
      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() {
  
          public JPopupMenu getPopupMenu(Widget widget, Point localLocation) {
              return DiagramScene.this.createPopupMenu();
          }
      };
      private RectangularSelectDecorator rectangularSelectDecorator = new RectangularSelectDecorator() {
  
          public Widget createSelectionWidget() {
              Widget widget = new Widget(DiagramScene.this);
              widget.setBorder(BorderFactory.createLineBorder(Color.black, 2));
              widget.setForeground(Color.red);
              return widget;
          }
      };
      private RectangularSelectProvider rectangularSelectProvider = new RectangularSelectProvider() {
  
          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;
              }
  
              boolean updated = false;
              for (Figure f : getModel().getDiagramToView().getFigures()) {
                  FigureWidget w = figureWidgets.get(f);
                  Rectangle r = new Rectangle(w.getBounds());
                  r.setLocation(w.getLocation());
                  if (r.intersects(rectangle)) {
                      if (!selectedWidgets.contains(w)) {
                          addToSelection(w);
                          updated = true;
                      }
                  } else {
                      if (selectedWidgets.contains(w)) {
                          selectedWidgets.remove(w);
                          content.remove(w.getNode());
                          w.setState(w.getState().deriveSelected(false));
                          updated = true;
                      }
                  }
              }
  
              if (updated) {
                  selectionUpdated();
              }
          }
      };
      private SelectProvider selectProvider = new SelectProvider() {
  
          public boolean isAimingAllowed(Widget widget, Point point, boolean b) {
              return false;
          }
  
          public boolean isSelectionAllowed(Widget widget, Point point, boolean b) {
              return widget instanceof FigureWidget || widget == DiagramScene.this;
          }
  
          public void select(Widget w, Point point, boolean change) {
  
              boolean updated = false;
  
              if (w == DiagramScene.this) {
                  if (DiagramScene.this.selectedWidgets.size() != 0) {
                      clearSelection();
                      selectionUpdated();
                  }
                  return;
              }
  
              FigureWidget widget = (FigureWidget) w;
  
  
              if (change) {
                  if (widget.getState().isSelected()) {
                      assert selectedWidgets.contains(widget);
                      widget.setState(widget.getState().deriveSelected(false));
                      selectedWidgets.remove(widget);
                      content.remove(widget.getNode());
                      updated = true;
                  } else {
                      assert !selectedWidgets.contains(widget);
                      addToSelection(widget);
                      updated = true;
                      assert widget.getState().isSelected();
                  }
              } else {
  
                  if (widget.getState().isSelected()) {
                      assert selectedWidgets.contains(widget);
                  } else {
  
                      assert !selectedWidgets.contains(widget);
                      clearSelection();
                      addToSelection(widget);
                      updated = true;
                      assert widget.getState().isSelected();
                  }
              }
  
              if (updated) {
                  selectionUpdated();
              }
  
          }
      };
  
      private FigureWidget getFigureWidget(Figure f) {
          return figureWidgets.get(f);
      }
      private FocusListener focusListener = new FocusListener() {
  
          public void focusGained(FocusEvent e) {
              DiagramScene.this.getView().requestFocus();
          }
  
          public void focusLost(FocusEvent e) {
          }
      };
      private MouseWheelListener mouseWheelListener = new MouseWheelListener() {
  
          public void mouseWheelMoved(MouseWheelEvent e) {
              DiagramScene.this.zoomAction.mouseWheelMoved(DiagramScene.this, new WidgetAction.WidgetMouseWheelEvent(0, e));
              DiagramScene.this.validate();
          }
      };
      private MouseListener mouseListener = new MouseListener() {
  
          public void mouseClicked(MouseEvent e) {
              DiagramScene.this.panAction.mouseClicked(DiagramScene.this, new WidgetAction.WidgetMouseEvent(0, e));
          }
  
          public void mousePressed(MouseEvent e) {
              DiagramScene.this.panAction.mousePressed(DiagramScene.this, new WidgetAction.WidgetMouseEvent(0, e));
          }
  
          public void mouseReleased(MouseEvent e) {
              DiagramScene.this.panAction.mouseReleased(DiagramScene.this, new WidgetAction.WidgetMouseEvent(0, e));
          }
  
          public void mouseEntered(MouseEvent e) {
              DiagramScene.this.panAction.mouseEntered(DiagramScene.this, new WidgetAction.WidgetMouseEvent(0, e));
          }
  
          public void mouseExited(MouseEvent e) {
              DiagramScene.this.panAction.mouseExited(DiagramScene.this, new WidgetAction.WidgetMouseEvent(0, e));
          }
      };
      private MouseMotionListener mouseMotionListener = new MouseMotionListener() {
  
          public void mouseDragged(MouseEvent e) {
              DiagramScene.this.panAction.mouseDragged(DiagramScene.this, new WidgetAction.WidgetMouseEvent(0, e));
          }
  
          public void mouseMoved(MouseEvent e) {
          }
      };
      private ScrollChangeListener scrollChangeListener = new ScrollChangeListener();
  
      private class ScrollChangeListener implements ChangeListener {
  
          private Map<Widget, Point> relativePositions = new HashMap<Widget, Point>();
          private Point oldPosition;
  
          public void register(Widget w, Point p) {
              relativePositions.put(w, p);
          }
  
          public void unregister(Widget w) {
              relativePositions.remove(w);
          }
  
          public void stateChanged(ChangeEvent e) {
              Point p = DiagramScene.this.getScrollPane().getViewport().getViewPosition();
              if (oldPosition == null || !p.equals(oldPosition)) {
                  for (Widget w : relativePositions.keySet()) {
                      Point curPoint = relativePositions.get(w);
                      Point newPoint = new Point(p.x + curPoint.x, p.y + curPoint.y);
                      w.setPreferredLocation(newPoint);
                      DiagramScene.this.validate();
                  }
                  oldPosition = p;
              }
          }
      }
  
      public Point getScrollPosition() {
          return getScrollPane().getViewport().getViewPosition();
      }
  
      public void setScrollPosition(Point p) {
          getScrollPane().getViewport().setViewPosition(p);
      }
  
      public DiagramScene(Action[] actions, DiagramViewModel model) {
          this.actions = actions;
          selectedWidgets = new ArrayList<FigureWidget>();
          content = new InstanceContent();
          lookup = new AbstractLookup(content);
          this.setCheckClipping(true);
          this.getInputBindings().setZoomActionModifiers(0);
  
          JComponent comp = this.createView();
          comp.setDoubleBuffered(true);
          comp.setBackground(Color.WHITE);
          comp.setOpaque(true);
  
          this.setBackground(Color.WHITE);
          this.setOpaque(true);
          scrollPane = new JScrollPane(comp);
          scrollPane.setBackground(Color.WHITE);
          scrollPane.getVerticalScrollBar().setUnitIncrement(SCROLL_UNIT_INCREMENT);
          scrollPane.getVerticalScrollBar().setBlockIncrement(SCROLL_BLOCK_INCREMENT);
          scrollPane.getHorizontalScrollBar().setUnitIncrement(SCROLL_UNIT_INCREMENT);
          scrollPane.getHorizontalScrollBar().setBlockIncrement(SCROLL_BLOCK_INCREMENT);
          scrollPane.getViewport().addChangeListener(scrollChangeListener);
          hoverAction = this.createWidgetHoverAction();
  
          blockLayer = new LayerWidget(this);
          this.addChild(blockLayer);
  
          startLayer = new LayerWidget(this);
          this.addChild(startLayer);
          // TODO: String startLabelString = "Loading graph with " + originalDiagram.getFigures().size() + " figures and " + originalDiagram.getConnections().size() + " connections...";
          String startLabelString = "";
          LabelWidget w = new LabelWidget(this, startLabelString);
          scrollChangeListener.register(w, new Point(10, 10));
          w.setAlignment(LabelWidget.Alignment.CENTER);
          startLabel = w;
          startLayer.addChild(w);
  
          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);
  
          slotLayer = new LayerWidget(this);
          this.addChild(slotLayer);
  
          connectionLayer = new LayerWidget(this);
          this.addChild(connectionLayer);
  
          LayerWidget selectionLayer = new LayerWidget(this);
          this.addChild(selectionLayer);
  
          this.setLayout(LayoutFactory.createAbsoluteLayout());
  
          this.getActions().addAction(hoverAction);
          zoomAction = new BoundedZoomAction(1.1, false);
          zoomAction.setMaxFactor(ZOOM_MAX_FACTOR);
          zoomAction.setMinFactor(ZOOM_MIN_FACTOR);
          this.getActions().addAction(ActionFactory.createMouseCenteredZoomAction(1.1));
          panAction = new ExtendedPanAction();
          this.getActions().addAction(panAction);
          this.getActions().addAction(ActionFactory.createPopupMenuAction(popupMenuProvider));
  
          LayerWidget selectLayer = new LayerWidget(this);
          this.addChild(selectLayer);
          this.getActions().addAction(ActionFactory.createRectangularSelectAction(rectangularSelectDecorator, selectLayer, rectangularSelectProvider));
  
          blockWidgets = new HashMap<InputBlock, BlockWidget>();
  
          boolean b = this.getUndoRedoEnabled();
          this.setUndoRedoEnabled(false);
          this.setNewModel(model);
          this.setUndoRedoEnabled(b);
      }
  
      private void selectionUpdated() {
          getModel().setSelectedNodes(this.getSelectedNodes());
          addUndo();
      }
  
      public DiagramViewModel getModel() {
          return model;
      }
  
      public void setRealZoomFactor(double d) {
          this.realZoomFactor = d;
      }
  
      public double getRealZoomFactor() {
          if (realZoomFactor == 0.0) {
              return getZoomFactor();
          } else {
              return realZoomFactor;
          }
      }
  
      public JScrollPane getScrollPane() {
          return scrollPane;
      }
  
      public boolean isAllVisible() {
          return getModel().getHiddenNodes().size() == 0;
      }
  
      public Action createGotoAction(final Figure f) {
          final DiagramScene diagramScene = this;
          Action a = new AbstractAction() {
  
              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 (f.getCluster() != null) {
              name += "B" + f.getCluster().toString();
          }
          if (!this.getFigureWidget(f).isVisible()) {
              if (f.getCluster() != null) {
                  name += ", ";
              }
              name += "hidden";
          }
          name += ")";
          a.putValue(Action.NAME, name);
          return a;
      }
  
      public void setNewModel(DiagramViewModel model) {
          if (this.model != null) {
              this.model.getDiagramChangedEvent().removeListener(this);
              this.model.getViewPropertiesChangedEvent().removeListener(this);
          }
          this.model = model;
  
          if (this.model == null) {
              this.modelCopy = null;
          } else {
              this.modelCopy = this.model.copy();
          }
  
          model.getDiagramChangedEvent().addListener(this);
          model.getViewPropertiesChangedEvent().addListener(this);
  
          update();
      }
  
      private void update() {
  
          /*if (startLabel != null) {
          // Animate fade-out
          final LabelWidget labelWidget = this.startLabel;
          labelWidget.setVisible(true);
          RequestProcessor.getDefault().post(new Runnable() {
          public void run() {
          final int Sleep = 200;
          final int Progress = 10;
          for (int i = 0; i < 255 / Progress + 1; i++) {
          try {
          SwingUtilities.invokeAndWait(new Runnable() {
          public void run() {
          Color c = labelWidget.getForeground();
          int v = c.getRed();
          v += Progress;
          if (v > 255) {
          v = 255;
          }
          labelWidget.setForeground(new Color(v, v, v, 255 - v));
          labelWidget.getScene().validate();
          }
          });
          } catch (InterruptedException ex) {
          } catch (InvocationTargetException ex) {
          }
          try {
          Thread.sleep(Sleep);
          } catch (InterruptedException ex) {
          }
          }
          labelWidget.setVisible(false);
          DiagramScene.this.scrollChangeListener.unregister(labelWidget);
          }
          }, 1000);
          startLabel = null;
          }*/
  
          slotLayer.removeChildren();
          mainLayer.removeChildren();
          blockLayer.removeChildren();
  
          blockWidgets.clear();
          figureWidgets = new HashMap<Figure, FigureWidget>();
          slotWidgets = new HashMap<Slot, SlotWidget>();
          connectionWidgets = new HashMap<Connection, ConnectionWidget>();
  
          WidgetAction selectAction = new ExtendedSelectAction(selectProvider);
          Diagram d = getModel().getDiagramToView();
  
          if (getModel().getShowBlocks()) {
              Scheduler s = Lookup.getDefault().lookup(Scheduler.class);
              Collection<InputBlock> newBlocks = new ArrayList<InputBlock>(s.schedule(d.getGraph()));
              d.schedule(newBlocks);
          }
  
          for (Figure f : d.getFigures()) {
              FigureWidget w = new FigureWidget(f, this, mainLayer);
              w.getActions().addAction(selectAction);
              w.getActions().addAction(hoverAction);
              w.getActions().addAction(ActionFactory.createPopupMenuAction(w));
              w.getActions().addAction(new DoubleClickAction(w));
              w.setVisible(false);
  
              figureWidgets.put(f, w);
  
              for (InputSlot s : f.getInputSlots()) {
                  SlotWidget sw = new InputSlotWidget(s, this, slotLayer, w);
                  slotWidgets.put(s, sw);
                  sw.getActions().addAction(selectAction);
              }
  
              for (OutputSlot s : f.getOutputSlots()) {
                  SlotWidget sw = new OutputSlotWidget(s, this, slotLayer, w);
                  slotWidgets.put(s, sw);
                  sw.getActions().addAction(selectAction);
              }
          }
  
          if (getModel().getShowBlocks()) {
              for (InputBlock bn : d.getGraph().getBlocks()) {
                  BlockWidget w = new BlockWidget(this, d, bn);
                  w.setVisible(false);
                  blockWidgets.put(bn, w);
                  blockLayer.addChild(w);
              }
          }
  
          this.smallUpdate(true);
  
      }
  
      private void smallUpdate(boolean relayout) {
  
          this.updateHiddenNodes(model.getHiddenNodes(), relayout);
          boolean b = this.getUndoRedoEnabled();
          this.setUndoRedoEnabled(false);
          this.setSelection(getModel().getSelectedNodes());
          this.setUndoRedoEnabled(b);
          this.validate();
      }
  
      private boolean isVisible(Connection c) {
          FigureWidget w1 = figureWidgets.get(c.getInputSlot().getFigure());
          FigureWidget w2 = figureWidgets.get(c.getOutputSlot().getFigure());
  
          if (w1.isVisible() && w2.isVisible()) {
              return true;
          }
  
          return false;
      }
  
      private void relayout(Set<Widget> oldVisibleWidgets) {
  
          Diagram diagram = getModel().getDiagramToView();
  
          HashSet<Figure> figures = new HashSet<Figure>();
  
          for (Figure f : diagram.getFigures()) {
              FigureWidget w = figureWidgets.get(f);
              if (w.isVisible()) {
                  figures.add(f);
              }
          }
  
          HashSet<Connection> edges = new HashSet<Connection>();
  
          for (Connection c : diagram.getConnections()) {
              if (isVisible(c)) {
                  edges.add(c);
              }
          }
  
          if (getModel().getShowBlocks()) {
              HierarchicalClusterLayoutManager m = new HierarchicalClusterLayoutManager(OldHierarchicalLayoutManager.Combine.SAME_OUTPUTS);
              HierarchicalLayoutManager manager = new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS);
              manager.setMaxLayerLength(9);
              manager.setMinLayerDifference(3);
              m.setManager(manager);
              m.setSubManager(new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS));
              m.doLayout(new LayoutGraph(edges, figures));
  
          } else {
              HierarchicalLayoutManager manager = new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS);
              manager.setMaxLayerLength(10);
              manager.doLayout(new LayoutGraph(edges, figures));
          }
  
          int maxX = -BORDER_SIZE;
          int maxY = -BORDER_SIZE;
          for (Figure f : diagram.getFigures()) {
              FigureWidget w = figureWidgets.get(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 = figureWidgets.get((Figure) c.getTo().getVertex());
              FigureWidget w2 = figureWidgets.get((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);
                      }
                  }
              }
          }
  
          if (getModel().getShowBlocks()) {
              for (Block b : diagram.getBlocks()) {
                  BlockWidget w = blockWidgets.get(b.getInputBlock());
                  if (w != null && w.isVisible()) {
                      Rectangle r = b.getBounds();
                      maxX = Math.max(maxX, r.x + r.width);
                      maxY = Math.max(maxY, r.y + r.height);
                  }
              }
          }
  
          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();
          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 (figureWidgets.get(f).isVisible()) {
                  visibleFigureCount++;
              }
          }
  
          for (Figure f : diagram.getFigures()) {
              for (OutputSlot s : f.getOutputSlots()) {
                  SceneAnimator anim = animator;
                  if (visibleFigureCount > ANIMATION_LIMIT) {
                      anim = null;
                  }
                  processOutputSlot(s, s.getConnections(), 0, null, null, offx2, offy2, anim);
              }
          }
  
          for (Figure f : diagram.getFigures()) {
              FigureWidget w = figureWidgets.get(f);
              if (w.isVisible()) {
                  Point p = f.getPosition();
                  Point p2 = new Point(p.x + offx2, p.y + offy2);
                  Rectangle r = new Rectangle(p.x + offx2, p.y + offy2, f.getSize().width, f.getSize().height);
                  if (oldVisibleWidgets.contains(w)) {
                      if (visibleFigureCount > ANIMATION_LIMIT) {
                          w.setPreferredLocation(p2);
                      } else {
                          animator.animatePreferredLocation(w, p2);
                      }
                  } else {
                      w.setPreferredLocation(p2);
                  }
              }
          }
  
          if (getModel().getShowBlocks()) {
              for (Block b : diagram.getBlocks()) {
                  BlockWidget w = blockWidgets.get(b.getInputBlock());
                  if (w != null && w.isVisible()) {
                      Point location = new Point(b.getBounds().x + offx2, b.getBounds().y + offy2);
                      Rectangle r = new Rectangle(location.x, location.y, b.getBounds().width, b.getBounds().height);
                      if (oldVisibleWidgets.contains(w)) {
                          if (visibleFigureCount > ANIMATION_LIMIT) {
                              w.setPreferredBounds(r);
                          } else {
                              animator.animatePreferredBounds(w, r);
                          }
                      } else {
                          w.setPreferredBounds(r);
                      }
                  }
              }
          }
      }
      private final Point specialNullPoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
  
      private void processOutputSlot(OutputSlot s, List<Connection> connections, int controlPointIndex, Point lastPoint, LineWidget predecessor, int offx, int offy, SceneAnimator animator) {
          Map<Point, List<Connection>> pointMap = new HashMap<Point, List<Connection>>();
  
          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.getShowName()) {
                  cur = new Point(cur.x, cur.y - SLOT_OFFSET);
              } else if (controlPointIndex == controlPoints.size() - 1 && !c.getInputSlot().getShowName()) {
                  cur = new Point(cur.x, cur.y + SLOT_OFFSET);
              }
  
              if (pointMap.containsKey(cur)) {
                  pointMap.get(cur).add(c);
              } else {
                  List<Connection> newList = new ArrayList<Connection>(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);
                  LineWidget w = new LineWidget(this, s, connectionList, p1, p2, predecessor, animator, isBold, isDashed);
                  newPredecessor = w;
                  connectionLayer.addChild(w);
                  w.getActions().addAction(hoverAction);
              }
  
              processOutputSlot(s, connectionList, controlPointIndex + 1, p, newPredecessor, offx, offy, animator);
          }
      }
  
      private void clearSelection() {
          if (selectedWidgets.size() == 0) {
              return;
          }
          for (FigureWidget w : selectedWidgets) {
              assert w.getState().isSelected();
              w.setState(w.getState().deriveSelected(false));
              content.remove(w.getNode());
          }
          selectedWidgets.clear();
      }
  
      public Lookup getLookup() {
          return lookup;
      }
  
      public void gotoFigures(final List<Figure> figures) {
          Rectangle overall = null;
          showFigures(figures);
          for (Figure f : figures) {
  
              FigureWidget fw = getFigureWidget(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 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));
          }
      }
  
      private void addToSelection(Figure f) {
          FigureWidget w = getFigureWidget(f);
          addToSelection(w);
      }
  
      private void addToSelection(FigureWidget w) {
          assert !selectedWidgets.contains(w);
          selectedWidgets.add(w);
          content.add(w.getNode());
          w.setState(w.getState().deriveSelected(true));
      }
  
      private void setSelection(Set<Integer> nodes) {
          clearSelection();
          for (Figure f : getModel().getDiagramToView().getFigures()) {
              if (doesIntersect(f.getSource().getSourceNodesAsSet(), nodes)) {
                  addToSelection(f);
              }
          }
          selectionUpdated();
          this.validate();
      }
  
      public void setSelection(Collection<Figure> list) {
          clearSelection();
          for (Figure f : list) {
              addToSelection(f);
          }
  
          selectionUpdated();
          this.validate();
      }
  
      public Set<Figure> getSelectedFigures() {
          Set<Figure> result = new HashSet<Figure>();
          for (Widget w : selectedWidgets) {
              if (w instanceof FigureWidget) {
                  FigureWidget fw = (FigureWidget) w;
                  if (fw.getState().isSelected()) {
                      result.add(fw.getFigure());
                  }
              }
          }
          return result;
      }
  
      public Set<Integer> getSelectedNodes() {
          Set<Integer> result = new HashSet<Integer>();
          for (Widget w : selectedWidgets) {
              if (w instanceof FigureWidget) {
                  FigureWidget fw = (FigureWidget) w;
                  if (fw.getState().isSelected()) {
                      result.addAll(fw.getFigure().getSource().getSourceNodesAsSet());
                  }
              }
          }
          return result;
      }
  
      private UndoRedo.Manager getUndoRedoManager() {
          if (undoRedoManager == null) {
              undoRedoManager = new UndoRedo.Manager();
              undoRedoManager.setLimit(UNDOREDO_LIMIT);
          }
  
          return undoRedoManager;
      }
  
      public UndoRedo getUndoRedo() {
          return getUndoRedoManager();
      }
  
      private boolean isVisible(Figure f) {
          for (Integer n : f.getSource().getSourceNodesAsSet()) {
              if (getModel().getHiddenNodes().contains(n)) {
                  return false;
              }
          }
          return true;
      }
  
      private 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;
      }
  
      public void showNot(final Set<Integer> nodes) {
          updateHiddenNodes(nodes, true);
      }
  
      public void showOnly(final Set<Integer> nodes) {
          HashSet<Integer> allNodes = new HashSet<Integer>(getModel().getGraphToView().getGroup().getAllNodes());
          allNodes.removeAll(nodes);
          updateHiddenNodes(allNodes, true);
      }
  
      private void updateHiddenNodes(Set<Integer> newHiddenNodes, boolean doRelayout) {
  
          Set<InputBlock> visibleBlocks = new HashSet<InputBlock>();
  
          Diagram diagram = getModel().getDiagramToView();
          assert diagram != null;
  
          Set<Widget> oldVisibleWidgets = new HashSet<Widget>();
  
          for (Figure f : diagram.getFigures()) {
  