1/* 2 * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 * 23 */ 24package com.sun.hotspot.igv.view; 25 26import com.sun.hotspot.igv.data.ChangedListener; 27import com.sun.hotspot.igv.data.ControllableChangedListener; 28import com.sun.hotspot.igv.data.InputBlock; 29import com.sun.hotspot.igv.data.InputNode; 30import com.sun.hotspot.igv.data.Pair; 31import com.sun.hotspot.igv.data.Properties; 32import com.sun.hotspot.igv.data.services.Scheduler; 33import com.sun.hotspot.igv.graph.*; 34import com.sun.hotspot.igv.hierarchicallayout.HierarchicalClusterLayoutManager; 35import com.sun.hotspot.igv.hierarchicallayout.HierarchicalLayoutManager; 36import com.sun.hotspot.igv.layout.LayoutGraph; 37import com.sun.hotspot.igv.selectioncoordinator.SelectionCoordinator; 38import com.sun.hotspot.igv.util.ColorIcon; 39import com.sun.hotspot.igv.util.DoubleClickAction; 40import com.sun.hotspot.igv.util.PropertiesSheet; 41import com.sun.hotspot.igv.view.actions.CustomizablePanAction; 42import com.sun.hotspot.igv.view.widgets.*; 43import java.awt.*; 44import java.awt.event.*; 45import java.util.List; 46import java.util.*; 47import javax.swing.*; 48import javax.swing.event.UndoableEditEvent; 49import javax.swing.undo.AbstractUndoableEdit; 50import javax.swing.undo.CannotRedoException; 51import javax.swing.undo.CannotUndoException; 52import org.netbeans.api.visual.action.*; 53import org.netbeans.api.visual.animator.SceneAnimator; 54import org.netbeans.api.visual.layout.LayoutFactory; 55import org.netbeans.api.visual.model.*; 56import org.netbeans.api.visual.widget.LayerWidget; 57import org.netbeans.api.visual.widget.Widget; 58import org.openide.awt.UndoRedo; 59import org.openide.nodes.AbstractNode; 60import org.openide.nodes.Children; 61import org.openide.nodes.Sheet; 62import org.openide.util.Lookup; 63import org.openide.util.lookup.AbstractLookup; 64import org.openide.util.lookup.InstanceContent; 65 66/** 67 * 68 * @author Thomas Wuerthinger 69 */ 70public class DiagramScene extends ObjectScene implements DiagramViewer { 71 72 private CustomizablePanAction panAction; 73 private WidgetAction hoverAction; 74 private WidgetAction selectAction; 75 private Lookup lookup; 76 private InstanceContent content; 77 private Action[] actions; 78 private Action[] actionsWithSelection; 79 private LayerWidget connectionLayer; 80 private JScrollPane scrollPane; 81 private UndoRedo.Manager undoRedoManager; 82 private LayerWidget mainLayer; 83 private LayerWidget blockLayer; 84 private Widget topLeft; 85 private Widget bottomRight; 86 private DiagramViewModel model; 87 private DiagramViewModel modelCopy; 88 private WidgetAction zoomAction; 89 private boolean rebuilding; 90 91 /** 92 * The alpha level of partially visible figures. 93 */ 94 public static final float ALPHA = 0.4f; 95 96 /** 97 * The offset of the graph to the border of the window showing it. 98 */ 99 public static final int BORDER_SIZE = 20; 100 101 102 public static final int UNDOREDO_LIMIT = 100; 103 public static final int SCROLL_UNIT_INCREMENT = 80; 104 public static final int SCROLL_BLOCK_INCREMENT = 400; 105 public static final float ZOOM_MAX_FACTOR = 3.0f; 106 public static final float ZOOM_MIN_FACTOR = 0.0f;//0.15f; 107 public static final float ZOOM_INCREMENT = 1.5f; 108 public static final int SLOT_OFFSET = 8; 109 public static final int ANIMATION_LIMIT = 40; 110 111 private PopupMenuProvider popupMenuProvider = new PopupMenuProvider() { 112 113 @Override 114 public JPopupMenu getPopupMenu(Widget widget, Point localLocation) { 115 return DiagramScene.this.createPopupMenu(); 116 } 117 }; 118 119 private RectangularSelectDecorator rectangularSelectDecorator = new RectangularSelectDecorator() { 120 121 @Override 122 public Widget createSelectionWidget() { 123 Widget widget = new Widget(DiagramScene.this); 124 widget.setBorder(BorderFactory.createLineBorder(Color.black, 2)); 125 widget.setForeground(Color.red); 126 return widget; 127 } 128 }; 129 130 @SuppressWarnings("unchecked") 131 public <T> T getWidget(Object o) { 132 Widget w = this.findWidget(o); 133 return (T) w; 134 } 135 136 @SuppressWarnings("unchecked") 137 public <T> T getWidget(Object o, Class<T> klass) { 138 Widget w = this.findWidget(o); 139 return (T) w; 140 } 141 142 private static boolean intersects(Set<? extends Object> s1, Set<? extends Object> s2) { 143 for (Object o : s1) { 144 if (s2.contains(o)) { 145 return true; 146 } 147 } 148 return false; 149 } 150 151 @Override 152 public void zoomOut() { 153 double zoom = getZoomFactor(); 154 Point viewPosition = getScrollPane().getViewport().getViewPosition(); 155 double newZoom = zoom / DiagramScene.ZOOM_INCREMENT; 156 if (newZoom > DiagramScene.ZOOM_MIN_FACTOR) { 157 setZoomFactor(newZoom); 158 validate(); 159 getScrollPane().getViewport().setViewPosition(new Point((int) (viewPosition.x / DiagramScene.ZOOM_INCREMENT), (int) (viewPosition.y / DiagramScene.ZOOM_INCREMENT))); 160 } 161 } 162 163 @Override 164 public void zoomIn() { 165 166 double zoom = getZoomFactor(); 167 Point viewPosition = getScrollPane().getViewport().getViewPosition(); 168 double newZoom = zoom * DiagramScene.ZOOM_INCREMENT; 169 if (newZoom < DiagramScene.ZOOM_MAX_FACTOR) { 170 setZoomFactor(newZoom); 171 validate(); 172 getScrollPane().getViewport().setViewPosition(new Point((int) (viewPosition.x * DiagramScene.ZOOM_INCREMENT), (int) (viewPosition.y * DiagramScene.ZOOM_INCREMENT))); 173 } 174 } 175 176 177 @Override 178 public void centerFigures(List<Figure> list) { 179 180 boolean b = getUndoRedoEnabled(); 181 setUndoRedoEnabled(false); 182 gotoFigures(list); 183 setUndoRedoEnabled(b); 184 } 185 186 private Set<Object> getObjectsFromIdSet(Set<Object> set) { 187 Set<Object> selectedObjects = new HashSet<>(); 188 for (Figure f : getModel().getDiagramToView().getFigures()) { 189 if (intersects(f.getSource().getSourceNodesAsSet(), set)) { 190 selectedObjects.add(f); 191 } 192 193 for (Slot s : f.getSlots()) { 194 if (intersects(s.getSource().getSourceNodesAsSet(), set)) { 195 selectedObjects.add(s); 196 } 197 } 198 } 199 return selectedObjects; 200 } 201 private ControllableChangedListener<SelectionCoordinator> highlightedCoordinatorListener = new ControllableChangedListener<SelectionCoordinator>() { 202 203 @Override 204 public void filteredChanged(SelectionCoordinator source) { 205 DiagramScene.this.setHighlightedObjects(getObjectsFromIdSet(source.getHighlightedObjects())); 206 DiagramScene.this.validate(); 207 } 208 }; 209 private ControllableChangedListener<SelectionCoordinator> selectedCoordinatorListener = new ControllableChangedListener<SelectionCoordinator>() { 210 211 @Override 212 public void filteredChanged(SelectionCoordinator source) { 213 DiagramScene.this.gotoSelection(source.getSelectedObjects()); 214 DiagramScene.this.validate(); 215 } 216 }; 217 218 private RectangularSelectProvider rectangularSelectProvider = new RectangularSelectProvider() { 219 220 @Override 221 public void performSelection(Rectangle rectangle) { 222 if (rectangle.width < 0) { 223 rectangle.x += rectangle.width; 224 rectangle.width *= -1; 225 } 226 227 if (rectangle.height < 0) { 228 rectangle.y += rectangle.height; 229 rectangle.height *= -1; 230 } 231 232 Set<Object> selectedObjects = new HashSet<>(); 233 for (Figure f : getModel().getDiagramToView().getFigures()) { 234 FigureWidget w = getWidget(f); 235 if (w != null) { 236 Rectangle r = new Rectangle(w.getBounds()); 237 r.setLocation(w.getLocation()); 238 239 if (r.intersects(rectangle)) { 240 selectedObjects.add(f); 241 } 242 243 for (Slot s : f.getSlots()) { 244 SlotWidget sw = getWidget(s); 245 Rectangle r2 = new Rectangle(sw.getBounds()); 246 r2.setLocation(sw.convertLocalToScene(new Point(0, 0))); 247 248 if (r2.intersects(rectangle)) { 249 selectedObjects.add(s); 250 } 251 } 252 } else { 253 assert false : "w should not be null here!"; 254 } 255 } 256 257 setSelectedObjects(selectedObjects); 258 } 259 }; 260 261 private MouseWheelListener mouseWheelListener = new MouseWheelListener() { 262 263 @Override 264 public void mouseWheelMoved(MouseWheelEvent e) { 265 if (e.isControlDown()) { 266 DiagramScene.this.relayoutWithoutLayout(null); 267 } 268 } 269 }; 270 271 public Point getScrollPosition() { 272 return getScrollPane().getViewport().getViewPosition(); 273 } 274 275 public void setScrollPosition(Point p) { 276 getScrollPane().getViewport().setViewPosition(p); 277 } 278 279 private JScrollPane createScrollPane() { 280 JComponent comp = this.createView(); 281 comp.setDoubleBuffered(true); 282 comp.setBackground(Color.WHITE); 283 comp.setOpaque(true); 284 this.setBackground(Color.WHITE); 285 this.setOpaque(true); 286 JScrollPane result = new JScrollPane(comp); 287 result.setBackground(Color.WHITE); 288 result.getVerticalScrollBar().setUnitIncrement(SCROLL_UNIT_INCREMENT); 289 result.getVerticalScrollBar().setBlockIncrement(SCROLL_BLOCK_INCREMENT); 290 result.getHorizontalScrollBar().setUnitIncrement(SCROLL_UNIT_INCREMENT); 291 result.getHorizontalScrollBar().setBlockIncrement(SCROLL_BLOCK_INCREMENT); 292 return result; 293 } 294 private ObjectSceneListener selectionChangedListener = new ObjectSceneListener() { 295 296 @Override 297 public void objectAdded(ObjectSceneEvent arg0, Object arg1) { 298 } 299 300 @Override 301 public void objectRemoved(ObjectSceneEvent arg0, Object arg1) { 302 } 303 304 @Override 305 public void objectStateChanged(ObjectSceneEvent e, Object o, ObjectState oldState, ObjectState newState) { 306 } 307 308 @Override 309 public void selectionChanged(ObjectSceneEvent e, Set<Object> oldSet, Set<Object> newSet) { 310 DiagramScene scene = (DiagramScene) e.getObjectScene(); 311 if (scene.isRebuilding()) { 312 return; 313 } 314 315 content.set(newSet, null); 316 317 Set<Integer> nodeSelection = new HashSet<>(); 318 for (Object o : newSet) { 319 if (o instanceof Properties.Provider) { 320 final Properties.Provider provider = (Properties.Provider) o; 321 AbstractNode node = new AbstractNode(Children.LEAF) { 322 323 @Override 324 protected Sheet createSheet() { 325 Sheet s = super.createSheet(); 326 PropertiesSheet.initializeSheet(provider.getProperties(), s); 327 return s; 328 } 329 }; 330 node.setDisplayName(provider.getProperties().get("name")); 331 content.add(node); 332 } 333 334 335 if (o instanceof Figure) { 336 nodeSelection.addAll(((Figure) o).getSource().getSourceNodesAsSet()); 337 } else if (o instanceof Slot) { 338 nodeSelection.addAll(((Slot) o).getSource().getSourceNodesAsSet()); 339 } 340 } 341 getModel().setSelectedNodes(nodeSelection); 342 343 boolean b = selectedCoordinatorListener.isEnabled(); 344 selectedCoordinatorListener.setEnabled(false); 345 SelectionCoordinator.getInstance().setSelectedObjects(nodeSelection); 346 selectedCoordinatorListener.setEnabled(b); 347 348 } 349 350 @Override 351 public void highlightingChanged(ObjectSceneEvent e, Set<Object> oldSet, Set<Object> newSet) { 352 Set<Integer> nodeHighlighting = new HashSet<>(); 353 for (Object o : newSet) { 354 if (o instanceof Figure) { 355 nodeHighlighting.addAll(((Figure) o).getSource().getSourceNodesAsSet()); 356 } else if (o instanceof Slot) { 357 nodeHighlighting.addAll(((Slot) o).getSource().getSourceNodesAsSet()); 358 } 359 } 360 boolean b = highlightedCoordinatorListener.isEnabled(); 361 highlightedCoordinatorListener.setEnabled(false); 362 SelectionCoordinator.getInstance().setHighlightedObjects(nodeHighlighting); 363 highlightedCoordinatorListener.setEnabled(true); 364 } 365 366 @Override 367 public void hoverChanged(ObjectSceneEvent e, Object oldObject, Object newObject) { 368 Set<Object> newHighlightedObjects = new HashSet<>(DiagramScene.this.getHighlightedObjects()); 369 if (oldObject != null) { 370 newHighlightedObjects.remove(oldObject); 371 } 372 if (newObject != null) { 373 newHighlightedObjects.add(newObject); 374 } 375 DiagramScene.this.setHighlightedObjects(newHighlightedObjects); 376 } 377 378 @Override 379 public void focusChanged(ObjectSceneEvent arg0, Object arg1, Object arg2) { 380 } 381 }; 382 383 public DiagramScene(Action[] actions, Action[] actionsWithSelection, DiagramViewModel model) { 384 385 this.actions = actions; 386 this.actionsWithSelection = actionsWithSelection; 387 388 content = new InstanceContent(); 389 lookup = new AbstractLookup(content); 390 391 this.setCheckClipping(true); 392 393 scrollPane = createScrollPane(); 394 395 hoverAction = createObjectHoverAction(); 396 397 // This panAction handles the event only when the left mouse button is 398 // pressed without any modifier keys, otherwise it will not consume it 399 // and the selection action (below) will handle the event 400 panAction = new CustomizablePanAction(~0, MouseEvent.BUTTON1_DOWN_MASK); 401 this.getActions().addAction(panAction); 402 403 selectAction = createSelectAction(); 404 this.getActions().addAction(selectAction); 405 406 blockLayer = new LayerWidget(this); 407 this.addChild(blockLayer); 408 409 connectionLayer = new LayerWidget(this); 410 this.addChild(connectionLayer); 411 412 mainLayer = new LayerWidget(this); 413 this.addChild(mainLayer); 414 415 topLeft = new Widget(this); 416 topLeft.setPreferredLocation(new Point(-BORDER_SIZE, -BORDER_SIZE)); 417 this.addChild(topLeft); 418 419 bottomRight = new Widget(this); 420 bottomRight.setPreferredLocation(new Point(-BORDER_SIZE, -BORDER_SIZE)); 421 this.addChild(bottomRight); 422 423 LayerWidget selectionLayer = new LayerWidget(this); 424 this.addChild(selectionLayer); 425 426 this.setLayout(LayoutFactory.createAbsoluteLayout()); 427 428 this.getInputBindings().setZoomActionModifiers(KeyEvent.CTRL_MASK); 429 zoomAction = ActionFactory.createMouseCenteredZoomAction(1.2); 430 this.getActions().addAction(zoomAction); 431 this.getView().addMouseWheelListener(mouseWheelListener); 432 this.getActions().addAction(ActionFactory.createPopupMenuAction(popupMenuProvider)); 433 434 this.getActions().addAction(ActionFactory.createWheelPanAction()); 435 436 LayerWidget selectLayer = new LayerWidget(this); 437 this.addChild(selectLayer); 438 this.getActions().addAction(ActionFactory.createRectangularSelectAction(rectangularSelectDecorator, selectLayer, rectangularSelectProvider)); 439 440 boolean b = this.getUndoRedoEnabled(); 441 this.setUndoRedoEnabled(false); 442 this.setNewModel(model); 443 this.setUndoRedoEnabled(b); 444 this.addObjectSceneListener(selectionChangedListener, ObjectSceneEventType.OBJECT_SELECTION_CHANGED, ObjectSceneEventType.OBJECT_HIGHLIGHTING_CHANGED, ObjectSceneEventType.OBJECT_HOVER_CHANGED); 445 } 446 447 public DiagramViewModel getModel() { 448 return model; 449 } 450 451 public JScrollPane getScrollPane() { 452 return scrollPane; 453 } 454 455 @Override 456 public Component getComponent() { 457 return scrollPane; 458 } 459 460 public boolean isAllVisible() { 461 return getModel().getHiddenNodes().isEmpty(); 462 } 463 464 public Action createGotoAction(final Figure f) { 465 final DiagramScene diagramScene = this; 466 String name = f.getLines()[0]; 467 468 name += " ("; 469 470 if (f.getCluster() != null) { 471 name += "B" + f.getCluster().toString(); 472 } 473 final boolean hidden = !this.getWidget(f, FigureWidget.class).isVisible(); 474 if (hidden) { 475 if (f.getCluster() != null) { 476 name += ", "; 477 } 478 name += "hidden"; 479 } 480 name += ")"; 481 Action a = new AbstractAction(name, new ColorIcon(f.getColor())) { 482 483 @Override 484 public void actionPerformed(ActionEvent e) { 485 diagramScene.gotoFigure(f); 486 } 487 }; 488 489 a.setEnabled(true); 490 return a; 491 } 492 493 public void setNewModel(DiagramViewModel model) { 494 assert this.model == null : "can set model only once!"; 495 this.model = model; 496 this.modelCopy = null; 497 498 model.getDiagramChangedEvent().addListener(fullChange); 499 model.getViewPropertiesChangedEvent().addListener(fullChange); 500 model.getViewChangedEvent().addListener(selectionChange); 501 model.getHiddenNodesChangedEvent().addListener(hiddenNodesChange); 502 update(); 503 } 504 505 private void update() { 506 mainLayer.removeChildren(); 507 blockLayer.removeChildren(); 508 509 rebuilding = true; 510 511 Collection<Object> objects = new ArrayList<>(this.getObjects()); 512 for (Object o : objects) { 513 this.removeObject(o); 514 } 515 516 Diagram d = getModel().getDiagramToView(); 517 518 if (d.getGraph().getBlocks().isEmpty()) { 519 Scheduler s = Lookup.getDefault().lookup(Scheduler.class); 520 d.getGraph().clearBlocks(); 521 s.schedule(d.getGraph()); 522 d.getGraph().ensureNodesInBlocks(); 523 d.updateBlocks(); 524 } 525 526 for (Figure f : d.getFigures()) { 527 FigureWidget w = new FigureWidget(f, hoverAction, selectAction, this, mainLayer); 528 w.getActions().addAction(ActionFactory.createPopupMenuAction(w)); 529 w.getActions().addAction(selectAction); 530 w.getActions().addAction(hoverAction); 531 w.setVisible(false); 532 533 this.addObject(f, w); 534 535 for (InputSlot s : f.getInputSlots()) { 536 SlotWidget sw = new InputSlotWidget(s, this, w, w); 537 addObject(s, sw); 538 sw.getActions().addAction(new DoubleClickAction(sw)); 539 sw.getActions().addAction(hoverAction); 540 sw.getActions().addAction(selectAction); 541 } 542 543 for (OutputSlot s : f.getOutputSlots()) { 544 SlotWidget sw = new OutputSlotWidget(s, this, w, w); 545 addObject(s, sw); 546 sw.getActions().addAction(new DoubleClickAction(sw)); 547 sw.getActions().addAction(hoverAction); 548 sw.getActions().addAction(selectAction); 549 } 550 } 551 552 if (getModel().getShowBlocks()) { 553 for (InputBlock bn : d.getGraph().getBlocks()) { 554 BlockWidget w = new BlockWidget(this, d, bn); 555 w.setVisible(false); 556 this.addObject(bn, w); 557 blockLayer.addChild(w); 558 } 559 } 560 561 rebuilding = false; 562 this.smallUpdate(true); 563 } 564 565 public boolean isRebuilding() { 566 return rebuilding; 567 } 568 569 private void smallUpdate(boolean relayout) { 570 this.updateHiddenNodes(model.getHiddenNodes(), relayout); 571 boolean b = this.getUndoRedoEnabled(); 572 this.setUndoRedoEnabled(false); 573 this.setUndoRedoEnabled(b); 574 this.validate(); 575 } 576 577 private boolean isVisible(Connection c) { 578 FigureWidget w1 = getWidget(c.getInputSlot().getFigure()); 579 FigureWidget w2 = getWidget(c.getOutputSlot().getFigure()); 580 581 if (w1.isVisible() && w2.isVisible()) { 582 return true; 583 } 584 585 return false; 586 } 587 588 private void relayout(Set<Widget> oldVisibleWidgets) { 589 Diagram diagram = getModel().getDiagramToView(); 590 591 HashSet<Figure> figures = new HashSet<>(); 592 593 for (Figure f : diagram.getFigures()) { 594 FigureWidget w = getWidget(f); 595 if (w.isVisible()) { 596 figures.add(f); 597 } 598 } 599 600 HashSet<Connection> edges = new HashSet<>(); 601 602 for (Connection c : diagram.getConnections()) { 603 if (isVisible(c)) { 604 edges.add(c); 605 } 606 } 607 608 if (getModel().getShowBlocks()) { 609 HierarchicalClusterLayoutManager m = new HierarchicalClusterLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS); 610 HierarchicalLayoutManager manager = new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS); 611 manager.setMaxLayerLength(9); 612 manager.setMinLayerDifference(3); 613 m.setManager(manager); 614 m.setSubManager(new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS)); 615 m.doLayout(new LayoutGraph(edges, figures)); 616 } else { 617 HierarchicalLayoutManager manager = new HierarchicalLayoutManager(HierarchicalLayoutManager.Combine.SAME_OUTPUTS); 618 manager.setMaxLayerLength(10); 619 manager.doLayout(new LayoutGraph(edges, figures)); 620 } 621 622 relayoutWithoutLayout(oldVisibleWidgets); 623 } 624 private Set<Pair<Point, Point>> lineCache = new HashSet<>(); 625 626 private void relayoutWithoutLayout(Set<Widget> oldVisibleWidgets) { 627 628 Diagram diagram = getModel().getDiagramToView(); 629 630 int maxX = -BORDER_SIZE; 631 int maxY = -BORDER_SIZE; 632 for (Figure f : diagram.getFigures()) { 633 FigureWidget w = getWidget(f); 634 if (w.isVisible()) { 635 Point p = f.getPosition(); 636 Dimension d = f.getSize(); 637 maxX = Math.max(maxX, p.x + d.width); 638 maxY = Math.max(maxY, p.y + d.height); 639 } 640 } 641 642 for (Connection c : diagram.getConnections()) { 643 List<Point> points = c.getControlPoints(); 644 FigureWidget w1 = getWidget((Figure) c.getTo().getVertex()); 645 FigureWidget w2 = getWidget((Figure) c.getFrom().getVertex()); 646 if (w1.isVisible() && w2.isVisible()) { 647 for (Point p : points) { 648 if (p != null) { 649 maxX = Math.max(maxX, p.x); 650 maxY = Math.max(maxY, p.y); 651 } 652 } 653 } 654 } 655 656 if (getModel().getShowBlocks()) { 657 for (Block b : diagram.getBlocks()) { 658 BlockWidget w = getWidget(b.getInputBlock()); 659 if (w != null && w.isVisible()) { 660 Rectangle r = b.getBounds(); 661 maxX = Math.max(maxX, r.x + r.width); 662 maxY = Math.max(maxY, r.y + r.height); 663 } 664 } 665 } 666 667 bottomRight.setPreferredLocation(new Point(maxX + BORDER_SIZE, maxY + BORDER_SIZE)); 668 int offx = 0; 669 int offy = 0; 670 int curWidth = maxX + 2 * BORDER_SIZE; 671 int curHeight = maxY + 2 * BORDER_SIZE; 672 673 Rectangle bounds = this.getScrollPane().getBounds(); 674 bounds.width /= getZoomFactor(); 675 bounds.height /= getZoomFactor(); 676 if (curWidth < bounds.width) { 677 offx = (bounds.width - curWidth) / 2; 678 } 679 680 if (curHeight < bounds.height) { 681 offy = (bounds.height - curHeight) / 2; 682 } 683 684 final int offx2 = offx; 685 final int offy2 = offy; 686 687 SceneAnimator animator = this.getSceneAnimator(); 688 connectionLayer.removeChildren(); 689 int visibleFigureCount = 0; 690 for (Figure f : diagram.getFigures()) { 691 if (getWidget(f, FigureWidget.class).isVisible()) { 692 visibleFigureCount++; 693 } 694 } 695 696 697 Set<Pair<Point, Point>> lastLineCache = lineCache; 698 lineCache = new HashSet<>(); 699 for (Figure f : diagram.getFigures()) { 700 for (OutputSlot s : f.getOutputSlots()) { 701 SceneAnimator anim = animator; 702 if (visibleFigureCount > ANIMATION_LIMIT || oldVisibleWidgets == null) { 703 anim = null; 704 } 705 processOutputSlot(lastLineCache, s, s.getConnections(), 0, null, null, offx2, offy2, anim); 706 } 707 } 708 709 for (Figure f : diagram.getFigures()) { 710 FigureWidget w = getWidget(f); 711 if (w.isVisible()) { 712 Point p = f.getPosition(); 713 Point p2 = new Point(p.x + offx2, p.y + offy2); 714 if ((visibleFigureCount <= ANIMATION_LIMIT && oldVisibleWidgets != null && oldVisibleWidgets.contains(w))) { 715 animator.animatePreferredLocation(w, p2); 716 } else { 717 w.setPreferredLocation(p2); 718 animator.animatePreferredLocation(w, p2); 719 } 720 } 721 } 722 723 if (getModel().getShowBlocks()) { 724 for (Block b : diagram.getBlocks()) { 725 BlockWidget w = getWidget(b.getInputBlock()); 726 if (w != null && w.isVisible()) { 727 Point location = new Point(b.getBounds().x + offx2, b.getBounds().y + offy2); 728 Rectangle r = new Rectangle(location.x, location.y, b.getBounds().width, b.getBounds().height); 729 730 if ((visibleFigureCount <= ANIMATION_LIMIT && oldVisibleWidgets != null && oldVisibleWidgets.contains(w))) { 731 animator.animatePreferredBounds(w, r); 732 } else { 733 w.setPreferredBounds(r); 734 animator.animatePreferredBounds(w, r); 735 } 736 } 737 } 738 } 739 740 this.validate(); 741 } 742 private final Point specialNullPoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE); 743 744 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) { 745 Map<Point, List<Connection>> pointMap = new HashMap<>(connections.size()); 746 747 for (Connection c : connections) { 748 749 if (!isVisible(c)) { 750 continue; 751 } 752 753 List<Point> controlPoints = c.getControlPoints(); 754 if (controlPointIndex >= controlPoints.size()) { 755 continue; 756 } 757 758 Point cur = controlPoints.get(controlPointIndex); 759 if (cur == null) { 760 cur = specialNullPoint; 761 } else if (controlPointIndex == 0 && !s.shouldShowName()) { 762 cur = new Point(cur.x, cur.y - SLOT_OFFSET); 763 } else if (controlPointIndex == controlPoints.size() - 1 && !c.getInputSlot().shouldShowName()) { 764 cur = new Point(cur.x, cur.y + SLOT_OFFSET); 765 } 766 767 if (pointMap.containsKey(cur)) { 768 pointMap.get(cur).add(c); 769 } else { 770 List<Connection> newList = new ArrayList<>(2); 771 newList.add(c); 772 pointMap.put(cur, newList); 773 } 774 775 } 776 777 for (Point p : pointMap.keySet()) { 778 List<Connection> connectionList = pointMap.get(p); 779 780 boolean isBold = false; 781 boolean isDashed = true; 782 783 for (Connection c : connectionList) { 784 785 if (c.getStyle() == Connection.ConnectionStyle.BOLD) { 786 isBold = true; 787 } 788 789 if (c.getStyle() != Connection.ConnectionStyle.DASHED) { 790 isDashed = false; 791 } 792 } 793 794 LineWidget newPredecessor = predecessor; 795 if (p == specialNullPoint) { 796 } else if (lastPoint == specialNullPoint) { 797 } else if (lastPoint != null) { 798 Point p1 = new Point(lastPoint.x + offx, lastPoint.y + offy); 799 Point p2 = new Point(p.x + offx, p.y + offy); 800 801 Pair<Point, Point> curPair = new Pair<>(p1, p2); 802 SceneAnimator curAnimator = animator; 803 if (lastLineCache.contains(curPair)) { 804 curAnimator = null; 805 } 806 LineWidget w = new LineWidget(this, s, connectionList, p1, p2, predecessor, curAnimator, isBold, isDashed); 807 lineCache.add(curPair); 808 809 newPredecessor = w; 810 connectionLayer.addChild(w); 811 this.addObject(new ConnectionSet(connectionList), w); 812 w.getActions().addAction(hoverAction); 813 } 814 815 processOutputSlot(lastLineCache, s, connectionList, controlPointIndex + 1, p, newPredecessor, offx, offy, animator); 816 } 817 } 818 819 @Override 820 public void setInteractionMode(InteractionMode mode) { 821 panAction.setEnabled(mode == InteractionMode.PANNING); 822 // When panAction is not enabled, it does not consume the event 823 // and the selection action handles it instead 824 } 825 826 private class ConnectionSet { 827 828 private Set<Connection> connections; 829 830 public ConnectionSet(Collection<Connection> connections) { 831 connections = new HashSet<>(connections); 832 } 833 834 public Set<Connection> getConnectionSet() { 835 return Collections.unmodifiableSet(connections); 836 } 837 } 838 839 @Override 840 public Lookup getLookup() { 841 return lookup; 842 } 843 844 @Override 845 public void initialize() { 846 Figure f = getModel().getDiagramToView().getRootFigure(); 847 if (f != null) { 848 setUndoRedoEnabled(false); 849 gotoFigure(f); 850 setUndoRedoEnabled(true); 851 } 852 } 853 854 public void gotoFigures(final List<Figure> figures) { 855 Rectangle overall = null; 856 getModel().showFigures(figures); 857 for (Figure f : figures) { 858 859 FigureWidget fw = getWidget(f); 860 if (fw != null) { 861 Rectangle r = fw.getBounds(); 862 Point p = fw.getLocation(); 863 Rectangle r2 = new Rectangle(p.x, p.y, r.width, r.height); 864 865 if (overall == null) { 866 overall = r2; 867 } else { 868 overall = overall.union(r2); 869 } 870 } 871 } 872 if (overall != null) { 873 centerRectangle(overall); 874 } 875 } 876 877 private Set<Object> idSetToObjectSet(Set<Object> ids) { 878 879 Set<Object> result = new HashSet<>(); 880 for (Figure f : getModel().getDiagramToView().getFigures()) { 881 if (DiagramScene.doesIntersect(f.getSource().getSourceNodesAsSet(), ids)) { 882 result.add(f); 883 } 884 885 for (Slot s : f.getSlots()) { 886 if (DiagramScene.doesIntersect(s.getSource().getSourceNodesAsSet(), ids)) { 887 result.add(s); 888 } 889 } 890 } 891 return result; 892 } 893 894 public void gotoSelection(Set<Object> ids) { 895 896 Rectangle overall = null; 897 Set<Integer> hiddenNodes = new HashSet<>(this.getModel().getHiddenNodes()); 898 hiddenNodes.removeAll(ids); 899 this.getModel().showNot(hiddenNodes); 900 901 Set<Object> objects = idSetToObjectSet(ids); 902 for (Object o : objects) { 903 904 Widget w = getWidget(o); 905 if (w != null) { 906 Rectangle r = w.getBounds(); 907 Point p = w.convertLocalToScene(new Point(0, 0)); 908 909 Rectangle r2 = new Rectangle(p.x, p.y, r.width, r.height); 910 911 if (overall == null) { 912 overall = r2; 913 } else { 914 overall = overall.union(r2); 915 } 916 } 917 } 918 if (overall != null) { 919 centerRectangle(overall); 920 } 921 922 setSelectedObjects(objects); 923 } 924 925 private Point calcCenter(Rectangle r) { 926 927 Point center = new Point((int) r.getCenterX(), (int) r.getCenterY()); 928 center.x -= getScrollPane().getViewport().getViewRect().width / 2; 929 center.y -= getScrollPane().getViewport().getViewRect().height / 2; 930 931 // Ensure to be within area 932 center.x = Math.max(0, center.x); 933 center.x = Math.min(getScrollPane().getViewport().getViewSize().width - getScrollPane().getViewport().getViewRect().width, center.x); 934 center.y = Math.max(0, center.y); 935 center.y = Math.min(getScrollPane().getViewport().getViewSize().height - getScrollPane().getViewport().getViewRect().height, center.y); 936 937 return center; 938 } 939 940 private void centerRectangle(Rectangle r) { 941 942 if (getScrollPane().getViewport().getViewRect().width == 0 || getScrollPane().getViewport().getViewRect().height == 0) { 943 return; 944 } 945 946 Rectangle r2 = new Rectangle(r.x, r.y, r.width, r.height); 947 r2 = convertSceneToView(r2); 948 949 double factorX = (double) r2.width / (double) getScrollPane().getViewport().getViewRect().width; 950 double factorY = (double) r2.height / (double) getScrollPane().getViewport().getViewRect().height; 951 double factor = Math.max(factorX, factorY); 952 if (factor >= 1.0) { 953 Point p = getScrollPane().getViewport().getViewPosition(); 954 setZoomFactor(getZoomFactor() / factor); 955 r2.x /= factor; 956 r2.y /= factor; 957 r2.width /= factor; 958 r2.height /= factor; 959 getScrollPane().getViewport().setViewPosition(calcCenter(r2)); 960 } else { 961 getScrollPane().getViewport().setViewPosition(calcCenter(r2)); 962 } 963 } 964 965 @Override 966 public void setSelection(Collection<Figure> list) { 967 super.setSelectedObjects(new HashSet<>(list)); 968 } 969 970 private UndoRedo.Manager getUndoRedoManager() { 971 if (undoRedoManager == null) { 972 undoRedoManager = new UndoRedo.Manager(); 973 undoRedoManager.setLimit(UNDOREDO_LIMIT); 974 } 975 976 return undoRedoManager; 977 } 978 979 @Override 980 public UndoRedo getUndoRedo() { 981 return getUndoRedoManager(); 982 } 983 984 private boolean isVisible(Figure f) { 985 for (Integer n : f.getSource().getSourceNodesAsSet()) { 986 if (getModel().getHiddenNodes().contains(n)) { 987 return false; 988 } 989 } 990 return true; 991 } 992 993 public static boolean doesIntersect(Set<?> s1, Set<?> s2) { 994 if (s1.size() > s2.size()) { 995 Set<?> tmp = s1; 996 s1 = s2; 997 s2 = tmp; 998 } 999 1000 for (Object o : s1) { 1001 if (s2.contains(o)) { 1002 return true; 1003 } 1004 } 1005 1006 return false; 1007 } 1008 1009 @Override 1010 public void componentHidden() { 1011 SelectionCoordinator.getInstance().getHighlightedChangedEvent().removeListener(highlightedCoordinatorListener); 1012 SelectionCoordinator.getInstance().getSelectedChangedEvent().removeListener(selectedCoordinatorListener); 1013 } 1014 1015 @Override 1016 public void componentShowing() { 1017 SelectionCoordinator.getInstance().getHighlightedChangedEvent().addListener(highlightedCoordinatorListener); 1018 SelectionCoordinator.getInstance().getSelectedChangedEvent().addListener(selectedCoordinatorListener); 1019 } 1020 1021 private void updateHiddenNodes(Set<Integer> newHiddenNodes, boolean doRelayout) { 1022 1023 Diagram diagram = getModel().getDiagramToView(); 1024 assert diagram != null; 1025 1026 Set<InputBlock> visibleBlocks = new HashSet<InputBlock>(); 1027 Set<Widget> oldVisibleWidgets = new HashSet<>(); 1028 1029 for (Figure f : diagram.getFigures()) { 1030 FigureWidget w = getWidget(f); 1031 if (w != null && w.isVisible()) { 1032 oldVisibleWidgets.add(w); 1033 } 1034 } 1035 1036 if (getModel().getShowBlocks()) { 1037 for (InputBlock b : diagram.getGraph().getBlocks()) { 1038 BlockWidget w = getWidget(b); 1039 if (w.isVisible()) { 1040 oldVisibleWidgets.add(w); 1041 } 1042 } 1043 } 1044 1045 for (Figure f : diagram.getFigures()) { 1046 boolean hiddenAfter = doesIntersect(f.getSource().getSourceNodesAsSet(), newHiddenNodes); 1047 1048 FigureWidget w = getWidget(f); 1049 w.setBoundary(false); 1050 if (!hiddenAfter) { 1051 // Figure is shown 1052 w.setVisible(true); 1053 for (InputNode n : f.getSource().getSourceNodes()) { 1054 visibleBlocks.add(diagram.getGraph().getBlock(n)); 1055 } 1056 } else { 1057 // Figure is hidden 1058 w.setVisible(false); 1059 } 1060 } 1061 1062 if (getModel().getShowNodeHull()) { 1063 List<FigureWidget> boundaries = new ArrayList<>(); 1064 for (Figure f : diagram.getFigures()) { 1065 FigureWidget w = getWidget(f); 1066 if (!w.isVisible()) { 1067 Set<Figure> set = new HashSet<>(f.getPredecessorSet()); 1068 set.addAll(f.getSuccessorSet()); 1069 1070 boolean b = false; 1071 for (Figure neighbor : set) { 1072 FigureWidget neighborWidget = getWidget(neighbor); 1073 if (neighborWidget.isVisible()) { 1074 b = true; 1075 break; 1076 } 1077 } 1078 1079 if (b) { 1080 w.setBoundary(true); 1081 for (InputNode n : f.getSource().getSourceNodes()) { 1082 visibleBlocks.add(diagram.getGraph().getBlock(n)); 1083 } 1084 boundaries.add(w); 1085 } 1086 } 1087 } 1088 1089 for (FigureWidget w : boundaries) { 1090 if (w.isBoundary()) { 1091 w.setVisible(true); 1092 } 1093 } 1094 } 1095 1096 if (getModel().getShowBlocks()) { 1097 for (InputBlock b : diagram.getGraph().getBlocks()) { 1098 1099 boolean visibleAfter = visibleBlocks.contains(b); 1100 1101 BlockWidget w = getWidget(b); 1102 if (visibleAfter) { 1103 // Block must be shown 1104 w.setVisible(true); 1105 } else { 1106 // Block must be hidden 1107 w.setVisible(false); 1108 } 1109 } 1110 } 1111 1112 if (doRelayout) { 1113 relayout(oldVisibleWidgets); 1114 } 1115 this.validate(); 1116 addUndo(); 1117 } 1118 1119 private void showFigure(Figure f) { 1120 HashSet<Integer> newHiddenNodes = new HashSet<>(getModel().getHiddenNodes()); 1121 newHiddenNodes.removeAll(f.getSource().getSourceNodesAsSet()); 1122 this.model.setHiddenNodes(newHiddenNodes); 1123 } 1124 1125 public void show(final Figure f) { 1126 showFigure(f); 1127 } 1128 1129 public void setSelectedObjects(Object... args) { 1130 Set<Object> set = new HashSet<>(); 1131 for (Object o : args) { 1132 set.add(o); 1133 } 1134 super.setSelectedObjects(set); 1135 } 1136 1137 private void centerWidget(Widget w) { 1138 Rectangle r = w.getBounds(); 1139 Point p = w.getLocation(); 1140 centerRectangle(new Rectangle(p.x, p.y, r.width, r.height)); 1141 } 1142 1143 public void gotoFigure(final Figure f) { 1144 if (!isVisible(f)) { 1145 showFigure(f); 1146 } 1147 1148 FigureWidget fw = getWidget(f); 1149 if (fw != null) { 1150 centerWidget(fw); 1151 setSelection(Arrays.asList(f)); 1152 } 1153 } 1154 1155 public JPopupMenu createPopupMenu() { 1156 JPopupMenu menu = new JPopupMenu(); 1157 1158 Action[] currentActions = actionsWithSelection; 1159 if (this.getSelectedObjects().isEmpty()) { 1160 currentActions = actions; 1161 } 1162 for (Action a : currentActions) { 1163 if (a == null) { 1164 menu.addSeparator(); 1165 } else { 1166 menu.add(a); 1167 } 1168 } 1169 return menu; 1170 } 1171 1172 private static class DiagramUndoRedo extends AbstractUndoableEdit implements ChangedListener<DiagramViewModel> { 1173 1174 private DiagramViewModel oldModel; 1175 private DiagramViewModel newModel; 1176 private Point oldScrollPosition; 1177 private DiagramScene scene; 1178 1179 public DiagramUndoRedo(DiagramScene scene, Point oldScrollPosition, DiagramViewModel oldModel, DiagramViewModel newModel) { 1180 assert oldModel != null; 1181 assert newModel != null; 1182 this.oldModel = oldModel; 1183 this.newModel = newModel; 1184 this.scene = scene; 1185 this.oldScrollPosition = oldScrollPosition; 1186 } 1187 1188 @Override 1189 public void redo() throws CannotRedoException { 1190 super.redo(); 1191 boolean b = scene.getUndoRedoEnabled(); 1192 scene.setUndoRedoEnabled(false); 1193 scene.getModel().getViewChangedEvent().addListener(this); 1194 scene.getModel().setData(newModel); 1195 scene.getModel().getViewChangedEvent().removeListener(this); 1196 scene.setUndoRedoEnabled(b); 1197 } 1198 1199 @Override 1200 public void undo() throws CannotUndoException { 1201 super.undo(); 1202 boolean b = scene.getUndoRedoEnabled(); 1203 scene.setUndoRedoEnabled(false); 1204 scene.getModel().getViewChangedEvent().addListener(this); 1205 scene.getModel().setData(oldModel); 1206 scene.getModel().getViewChangedEvent().removeListener(this); 1207 1208 SwingUtilities.invokeLater(new Runnable() { 1209 1210 @Override 1211 public void run() { 1212 scene.setScrollPosition(oldScrollPosition); 1213 } 1214 }); 1215 1216 scene.setUndoRedoEnabled(b); 1217 } 1218 1219 @Override 1220 public void changed(DiagramViewModel source) { 1221 scene.getModel().getViewChangedEvent().removeListener(this); 1222 if (oldModel.getHiddenNodes().equals(newModel.getHiddenNodes())) { 1223 scene.smallUpdate(false); 1224 } else { 1225 scene.smallUpdate(true); 1226 } 1227 } 1228 } 1229 private boolean undoRedoEnabled = true; 1230 1231 public void setUndoRedoEnabled(boolean b) { 1232 this.undoRedoEnabled = b; 1233 } 1234 1235 public boolean getUndoRedoEnabled() { 1236 return undoRedoEnabled; 1237 } 1238 1239 private final ChangedListener<DiagramViewModel> fullChange = new ChangedListener<DiagramViewModel>() { 1240 @Override 1241 public void changed(DiagramViewModel source) { 1242 assert source == model : "Receive only changed event from current model!"; 1243 assert source != null; 1244 update(); 1245 } 1246 }; 1247 1248 private final ChangedListener<DiagramViewModel> hiddenNodesChange = new ChangedListener<DiagramViewModel>() { 1249 @Override 1250 public void changed(DiagramViewModel source) { 1251 assert source == model : "Receive only changed event from current model!"; 1252 assert source != null; 1253 smallUpdate(true); 1254 } 1255 }; 1256 1257 private final ChangedListener<DiagramViewModel> selectionChange = new ChangedListener<DiagramViewModel>() { 1258 @Override 1259 public void changed(DiagramViewModel source) { 1260 assert source == model : "Receive only changed event from current model!"; 1261 assert source != null; 1262 smallUpdate(false); 1263 } 1264 }; 1265 1266 1267 private void addUndo() { 1268 1269 DiagramViewModel newModelCopy = model.copy(); 1270 1271 if (undoRedoEnabled) { 1272 this.getUndoRedoManager().undoableEditHappened(new UndoableEditEvent(this, new DiagramUndoRedo(this, this.getScrollPosition(), modelCopy, newModelCopy))); 1273 } 1274 1275 this.modelCopy = newModelCopy; 1276 } 1277} 1278