/* * Copyright (c) 2008, 2015, 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; import com.sun.hotspot.igv.data.ChangedListener; import com.sun.hotspot.igv.data.ControllableChangedListener; import com.sun.hotspot.igv.data.InputBlock; import com.sun.hotspot.igv.data.InputNode; import com.sun.hotspot.igv.data.Pair; import com.sun.hotspot.igv.data.Properties; import com.sun.hotspot.igv.data.services.Scheduler; import com.sun.hotspot.igv.graph.*; import com.sun.hotspot.igv.hierarchicallayout.HierarchicalClusterLayoutManager; import com.sun.hotspot.igv.hierarchicallayout.HierarchicalLayoutManager; import com.sun.hotspot.igv.layout.LayoutGraph; import com.sun.hotspot.igv.selectioncoordinator.SelectionCoordinator; 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.actions.CustomizablePanAction; 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 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.*; 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.awt.UndoRedo; 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; /** * * @author Thomas Wuerthinger */ public class DiagramScene extends ObjectScene implements DiagramViewer { private CustomizablePanAction panAction; private WidgetAction hoverAction; private WidgetAction selectAction; private Lookup lookup; private InstanceContent content; private Action[] actions; private Action[] actionsWithSelection; private LayerWidget connectionLayer; private JScrollPane scrollPane; private UndoRedo.Manager undoRedoManager; private LayerWidget mainLayer; private LayerWidget blockLayer; private Widget topLeft; private Widget bottomRight; private DiagramViewModel model; private DiagramViewModel modelCopy; 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 = 8; 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; } private static boolean intersects(Set<? extends Object> s1, Set<? extends Object> s2) { for (Object o : s1) { if (s2.contains(o)) { return true; } } return false; } @Override 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))); } } @Override 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))); } } @Override public void centerFigures(List<Figure> list) { boolean b = getUndoRedoEnabled(); setUndoRedoEnabled(false); gotoFigures(list); setUndoRedoEnabled(b); } private Set<Object> getObjectsFromIdSet(Set<Object> set) { Set<Object> selectedObjects = new HashSet<>(); for (Figure f : getModel().getDiagramToView().getFigures()) { if (intersects(f.getSource().getSourceNodesAsSet(), set)) { selectedObjects.add(f); } for (Slot s : f.getSlots()) { if (intersects(s.getSource().getSourceNodesAsSet(), set)) { selectedObjects.add(s); } } } return selectedObjects; } private ControllableChangedListener<SelectionCoordinator> highlightedCoordinatorListener = new ControllableChangedListener<SelectionCoordinator>() { @Override public void filteredChanged(SelectionCoordinator source) { DiagramScene.this.setHighlightedObjects(getObjectsFromIdSet(source.getHighlightedObjects())); DiagramScene.this.validate(); } }; private ControllableChangedListener<SelectionCoordinator> selectedCoordinatorListener = new ControllableChangedListener<SelectionCoordinator>() { @Override public void filteredChanged(SelectionCoordinator source) { DiagramScene.this.gotoSelection(source.getSelectedObjects()); DiagramScene.this.validate(); } }; 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); boolean b = selectedCoordinatorListener.isEnabled(); selectedCoordinatorListener.setEnabled(false); SelectionCoordinator.getInstance().setSelectedObjects(nodeSelection); selectedCoordinatorListener.setEnabled(b); } @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(true); } @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 DiagramScene(Action[] actions, Action[] actionsWithSelection, DiagramViewModel model) { this.actions = actions; this.actionsWithSelection = actionsWithSelection; content = new InstanceContent(); lookup = new AbstractLookup(content); 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 CustomizablePanAction(~0, MouseEvent.BUTTON1_DOWN_MASK); this.getActions().addAction(panAction); selectAction = createSelectAction(); this.getActions().addAction(selectAction); blockLayer = new LayerWidget(this); this.addChild(blockLayer); connectionLayer = new LayerWidget(this); this.addChild(connectionLayer); 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); 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)); boolean b = this.getUndoRedoEnabled(); this.setUndoRedoEnabled(false); this.setNewModel(model); this.setUndoRedoEnabled(b); this.addObjectSceneListener(selectionChangedListener, ObjectSceneEventType.OBJECT_SELECTION_CHANGED, ObjectSceneEventType.OBJECT_HIGHLIGHTING_CHANGED, ObjectSceneEventType.OBJECT_HOVER_CHANGED); } public DiagramViewModel getModel() { return model; } public JScrollPane getScrollPane() { return scrollPane; } @Override public Component getComponent() { return scrollPane; } public boolean isAllVisible() { return getModel().getHiddenNodes().isEmpty(); } public Action createGotoAction(final Figure f) { final DiagramScene diagramScene = this; String name = f.getLines()[0]; name += " ("; if (f.getCluster() != null) { name += "B" + f.getCluster().toString(); } final boolean hidden = !this.getWidget(f, FigureWidget.class).isVisible(); if (hidden) { if (f.getCluster() != null) { name += ", "; } name += "hidden"; } name += ")"; Action a = new AbstractAction(name, new ColorIcon(f.getColor())) { @Override public void actionPerformed(ActionEvent e) { diagramScene.gotoFigure(f); } }; a.setEnabled(true); return a; } public void setNewModel(DiagramViewModel model) { assert this.model == null : "can set model only once!"; this.model = model; this.modelCopy = null; model.getDiagramChangedEvent().addListener(fullChange); model.getViewPropertiesChangedEvent().addListener(fullChange); model.getViewChangedEvent().addListener(selectionChange); model.getHiddenNodesChangedEvent().addListener(hiddenNodesChange); update(); } 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(); if (d.getGraph().getBlocks().isEmpty()) { Scheduler s = Lookup.getDefault().lookup(Scheduler.class); d.getGraph().clearBlocks(); s.schedule(d.getGraph()); d.getGraph().ensureNodesInBlocks(); d.updateBlocks(); } 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); } } if (getModel().getShowBlocks()) { for (InputBlock bn : d.getGraph().getBlocks()) { BlockWidget w = new BlockWidget(this, d, bn); w.setVisible(false); this.addObject(bn, w); blockLayer.addChild(w); } } rebuilding = false; this.smallUpdate(true); } public boolean isRebuilding() { return rebuilding; } private void smallUpdate(boolean relayout) { this.updateHiddenNodes(model.getHiddenNodes(), relayout); boolean b = this.getUndoRedoEnabled(); this.setUndoRedoEnabled(false); this.setUndoRedoEnabled(b); 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) { 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); } } if (getModel().getShowBlocks()) { HierarchicalClusterLayoutManager m = new HierarchicalClusterLayoutManager(HierarchicalLayoutManager.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)); } relayoutWithoutLayout(oldVisibleWidgets); } private Set<Pair<Point, Point>> lineCache = new HashSet<>(); private void relayoutWithoutLayout(Set<Widget> 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); } } } } if (getModel().getShowBlocks()) { for (Block b : diagram.getBlocks()) { BlockWidget w = getWidget(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(); 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); } } } if (getModel().getShowBlocks()) { for (Block b : diagram.getBlocks()) { BlockWidget w = getWidget(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 ((visibleFigureCount <= ANIMATION_LIMIT && oldVisibleWidgets != null && oldVisibleWidgets.contains(w))) { animator.animatePreferredBounds(w, r); } else { w.setPreferredBounds(r); animator.animatePreferredBounds(w, r); } } } } 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); this.addObject(new ConnectionSet(connectionList), w); w.getActions().addAction(hoverAction); } processOutputSlot(lastLineCache, s, connectionList, controlPointIndex + 1, p, newPredecessor, offx, offy, animator); } } @Override public void setInteractionMode(InteractionMode mode) { panAction.setEnabled(mode == InteractionMode.PANNING); // When panAction is not enabled, it does not consume the event // and the selection action handles it instead } private class ConnectionSet { private Set<Connection> connections; public ConnectionSet(Collection<Connection> connections) { connections = new HashSet<>(connections); } public Set<Connection> getConnectionSet() { return Collections.unmodifiableSet(connections); } } @Override public Lookup getLookup() { return lookup; } @Override public void initialize() { Figure f = getModel().getDiagramToView().getRootFigure(); if (f != null) { setUndoRedoEnabled(false); gotoFigure(f); setUndoRedoEnabled(true); } } public void gotoFigures(final List<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)); } } @Override public void setSelection(Collection<Figure> list) { super.setSelectedObjects(new HashSet<>(list)); } private UndoRedo.Manager getUndoRedoManager() { if (undoRedoManager == null) { undoRedoManager = new UndoRedo.Manager(); undoRedoManager.setLimit(UNDOREDO_LIMIT); } return undoRedoManager; } @Override 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; } 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; } @Override public void componentHidden() { SelectionCoordinator.getInstance().getHighlightedChangedEvent().removeListener(highlightedCoordinatorListener); SelectionCoordinator.getInstance().getSelectedChangedEvent().removeListener(selectedCoordinatorListener); } @Override public void componentShowing() { SelectionCoordinator.getInstance().getHighlightedChangedEvent().addListener(highlightedCoordinatorListener); SelectionCoordinator.getInstance().getSelectedChangedEvent().addListener(selectedCoordinatorListener); } private void updateHiddenNodes(Set<Integer> newHiddenNodes, boolean doRelayout) { Diagram diagram = getModel().getDiagramToView(); assert diagram != null; Set<InputBlock> visibleBlocks = new HashSet<InputBlock>(); Set<Widget> oldVisibleWidgets = new HashSet<>(); for (Figure f : diagram.getFigures()) { FigureWidget w = getWidget(f); if (w != null && w.isVisible()) { oldVisibleWidgets.add(w); } } if (getModel().getShowBlocks()) { for (InputBlock b : diagram.getGraph().getBlocks()) { BlockWidget w = getWidget(b); if (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); for (InputNode n : f.getSource().getSourceNodes()) { visibleBlocks.add(diagram.getGraph().getBlock(n)); } } 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); for (InputNode n : f.getSource().getSourceNodes()) { visibleBlocks.add(diagram.getGraph().getBlock(n)); } boundaries.add(w); } } } for (FigureWidget w : boundaries) { if (w.isBoundary()) { w.setVisible(true); } } } if (getModel().getShowBlocks()) { for (InputBlock b : diagram.getGraph().getBlocks()) { boolean visibleAfter = visibleBlocks.contains(b); BlockWidget w = getWidget(b); if (visibleAfter) { // Block must be shown w.setVisible(true); } else { // Block must be hidden w.setVisible(false); } } } if (doRelayout) { relayout(oldVisibleWidgets); } this.validate(); addUndo(); } private void showFigure(Figure f) { HashSet<Integer> newHiddenNodes = new HashSet<>(getModel().getHiddenNodes()); newHiddenNodes.removeAll(f.getSource().getSourceNodesAsSet()); this.model.setHiddenNodes(newHiddenNodes); } 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) { centerWidget(fw); setSelection(Arrays.asList(f)); } } public JPopupMenu createPopupMenu() { JPopupMenu menu = new JPopupMenu(); Action[] currentActions = actionsWithSelection; if (this.getSelectedObjects().isEmpty()) { currentActions = actions; } for (Action a : currentActions) { if (a == null) { menu.addSeparator(); } else { menu.add(a); } } return menu; } private static class DiagramUndoRedo extends AbstractUndoableEdit implements ChangedListener<DiagramViewModel> { private DiagramViewModel oldModel; private DiagramViewModel newModel; private Point oldScrollPosition; private DiagramScene scene; public DiagramUndoRedo(DiagramScene scene, Point oldScrollPosition, DiagramViewModel oldModel, DiagramViewModel newModel) { assert oldModel != null; assert newModel != null; this.oldModel = oldModel; this.newModel = newModel; this.scene = scene; this.oldScrollPosition = oldScrollPosition; } @Override public void redo() throws CannotRedoException { super.redo(); boolean b = scene.getUndoRedoEnabled(); scene.setUndoRedoEnabled(false); scene.getModel().getViewChangedEvent().addListener(this); scene.getModel().setData(newModel); scene.getModel().getViewChangedEvent().removeListener(this); scene.setUndoRedoEnabled(b); } @Override public void undo() throws CannotUndoException { super.undo(); boolean b = scene.getUndoRedoEnabled(); scene.setUndoRedoEnabled(false); scene.getModel().getViewChangedEvent().addListener(this); scene.getModel().setData(oldModel); scene.getModel().getViewChangedEvent().removeListener(this); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { scene.setScrollPosition(oldScrollPosition); } }); scene.setUndoRedoEnabled(b); } @Override public void changed(DiagramViewModel source) { scene.getModel().getViewChangedEvent().removeListener(this); if (oldModel.getHiddenNodes().equals(newModel.getHiddenNodes())) { scene.smallUpdate(false); } else { scene.smallUpdate(true); } } } private boolean undoRedoEnabled = true; public void setUndoRedoEnabled(boolean b) { this.undoRedoEnabled = b; } public boolean getUndoRedoEnabled() { return undoRedoEnabled; } 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); } }; private void addUndo() { DiagramViewModel newModelCopy = model.copy(); if (undoRedoEnabled) { this.getUndoRedoManager().undoableEditHappened(new UndoableEditEvent(this, new DiagramUndoRedo(this, this.getScrollPosition(), modelCopy, newModelCopy))); } this.modelCopy = newModelCopy; } }