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 */ 24 package com.sun.hotspot.igv.view.widgets; 25 26 import com.sun.hotspot.igv.graph.Connection; 27 import com.sun.hotspot.igv.graph.Figure; 28 import com.sun.hotspot.igv.graph.InputSlot; 29 import com.sun.hotspot.igv.graph.OutputSlot; 30 import com.sun.hotspot.igv.view.DiagramScene; 31 import java.awt.*; 32 import java.awt.geom.Line2D; 33 import java.util.ArrayList; 34 import java.util.HashSet; 35 import java.util.List; 36 import java.util.Set; 37 import javax.swing.JPopupMenu; 38 import javax.swing.event.PopupMenuEvent; 39 import javax.swing.event.PopupMenuListener; 40 import org.netbeans.api.visual.action.ActionFactory; 41 import org.netbeans.api.visual.action.PopupMenuProvider; 42 import org.netbeans.api.visual.action.SelectProvider; 43 import org.netbeans.api.visual.animator.SceneAnimator; 44 import org.netbeans.api.visual.model.ObjectState; 45 import org.netbeans.api.visual.widget.Widget; 46 47 /** 48 * 49 * @author Thomas Wuerthinger 50 */ 51 public class LineWidget extends Widget implements PopupMenuProvider { 52 53 public final int BORDER = 5; 54 public final int ARROW_SIZE = 6; 55 public final int BOLD_ARROW_SIZE = 7; 56 public final int HOVER_ARROW_SIZE = 8; 57 public final int BOLD_STROKE_WIDTH = 2; 58 public final int HOVER_STROKE_WIDTH = 3; 59 private static double ZOOM_FACTOR = 0.1; 60 private OutputSlot outputSlot; 61 private DiagramScene scene; 62 private List<Connection> connections; 63 private Point from; 64 private Point to; 65 private Rectangle clientArea; 66 private Color color = Color.BLACK; 67 private LineWidget predecessor; 68 private List<LineWidget> successors; 69 private boolean highlighted; 70 private boolean popupVisible; 71 private boolean isBold; 72 private boolean isDashed; 73 74 public LineWidget(DiagramScene scene, OutputSlot s, List<Connection> connections, Point from, Point to, LineWidget predecessor, SceneAnimator animator, boolean isBold, boolean isDashed) { 75 super(scene); 76 this.scene = scene; 77 this.outputSlot = s; 78 this.connections = connections; 79 this.from = from; 80 this.to = to; 81 this.predecessor = predecessor; 82 this.successors = new ArrayList<>(); 83 if (predecessor != null) { 84 predecessor.addSuccessor(this); 85 } 86 87 this.isBold = isBold; 88 this.isDashed = isDashed; 89 90 int minX = from.x; 91 int minY = from.y; 92 int maxX = to.x; 93 int maxY = to.y; 94 if (minX > maxX) { 95 int tmp = minX; 96 minX = maxX; 97 maxX = tmp; 98 } 99 100 if (minY > maxY) { 101 int tmp = minY; 102 minY = maxY; 103 maxY = tmp; 104 } 105 106 clientArea = new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1); 107 clientArea.grow(BORDER, BORDER); 108 109 if (connections.size() > 0) { 110 color = connections.get(0).getColor(); 111 } 112 this.setToolTipText("<HTML>" + generateToolTipText(this.connections) + "</HTML>"); 113 114 this.setCheckClipping(true); 115 116 this.getActions().addAction(ActionFactory.createPopupMenuAction(this)); 117 if (animator == null) { 118 this.setBackground(color); 119 } else { 120 this.setBackground(Color.WHITE); 121 animator.animateBackgroundColor(this, color); 122 } 123 124 this.getActions().addAction(ActionFactory.createSelectAction(new SelectProvider() { 125 126 @Override 127 public boolean isAimingAllowed(Widget arg0, Point arg1, boolean arg2) { 128 return true; 129 } 130 131 @Override 132 public boolean isSelectionAllowed(Widget arg0, Point arg1, boolean arg2) { 133 return true; 134 } 135 136 @Override 137 public void select(Widget arg0, Point arg1, boolean arg2) { 138 Set<Figure> set = new HashSet<>(); 139 for (Connection c : LineWidget.this.connections) { 140 set.add(c.getInputSlot().getFigure()); 141 set.add(c.getOutputSlot().getFigure()); 142 } 143 LineWidget.this.scene.setSelectedObjects(set); 144 } 145 })); 146 } 147 148 private String generateToolTipText(List<Connection> conn) { 149 StringBuilder sb = new StringBuilder(); 150 for (Connection c : conn) { 151 sb.append(c.getToolTipText()); 152 sb.append("<br>"); 153 } 154 return sb.toString(); 155 } 156 157 public Point getFrom() { 158 return from; 159 } 160 161 public Point getTo() { 162 return to; 163 } 164 165 private void addSuccessor(LineWidget widget) { 166 this.successors.add(widget); 167 } 168 169 @Override 170 protected Rectangle calculateClientArea() { 171 return clientArea; 172 } 173 174 @Override 175 protected void paintWidget() { 176 if (scene.getZoomFactor() < ZOOM_FACTOR) { 177 return; 178 } 179 180 Graphics2D g = getScene().getGraphics(); 181 g.setPaint(this.getBackground()); 182 float width = 1.0f; 183 184 if (isBold) { 185 width = BOLD_STROKE_WIDTH; 186 } 187 188 if (highlighted || popupVisible) { 189 width = HOVER_STROKE_WIDTH; 190 } 191 192 Stroke oldStroke = g.getStroke(); 193 if (isDashed) { 194 float[] dashPattern = {5, 5, 5, 5}; 195 g.setStroke(new BasicStroke(width, BasicStroke.CAP_BUTT, 196 BasicStroke.JOIN_MITER, 10, 197 dashPattern, 0)); 198 } else { 199 g.setStroke(new BasicStroke(width)); 200 } 201 202 g.drawLine(from.x, from.y, to.x, to.y); 203 204 boolean sameFrom = false; 205 boolean sameTo = successors.isEmpty(); 206 for (LineWidget w : successors) { 207 if (w.getFrom().equals(getTo())) { 208 sameTo = true; 209 } 210 } 211 212 if (predecessor == null || predecessor.getTo().equals(getFrom())) { 213 sameFrom = true; 214 } 215 216 217 int size = ARROW_SIZE; 218 if (isBold) { 219 size = BOLD_ARROW_SIZE; 220 } 221 if (highlighted || popupVisible) { 222 size = HOVER_ARROW_SIZE; 223 } 224 if (!sameFrom) { 225 g.fillPolygon( 226 new int[]{from.x - size / 2, from.x + size / 2, from.x}, 227 new int[]{from.y - size / 2, from.y - size / 2, from.y + size / 2}, 228 3); 229 } 230 if (!sameTo) { 231 g.fillPolygon( 232 new int[]{to.x - size / 2, to.x + size / 2, to.x}, 233 new int[]{to.y - size / 2, to.y - size / 2, to.y + size / 2}, 234 3); 235 } 236 g.setStroke(oldStroke); 237 } 238 239 private void setHighlighted(boolean b) { 240 this.highlighted = b; 241 Set<Object> highlightedObjects = new HashSet<>(scene.getHighlightedObjects()); 242 Set<Object> highlightedObjectsChange = new HashSet<>(); 243 for (Connection c : connections) { 244 highlightedObjectsChange.add(c.getInputSlot().getFigure()); 245 highlightedObjectsChange.add(c.getInputSlot()); 246 highlightedObjectsChange.add(c.getOutputSlot().getFigure()); 247 highlightedObjectsChange.add(c.getOutputSlot()); 248 } 249 if(b) { 250 highlightedObjects.addAll(highlightedObjectsChange); 251 } else { 252 highlightedObjects.removeAll(highlightedObjectsChange); 253 } 254 scene.setHighlightedObjects(highlightedObjects); 255 this.revalidate(true); 256 } 257 258 private void setPopupVisible(boolean b) { 259 this.popupVisible = b; 260 this.revalidate(true); 261 } 262 263 @Override 264 public boolean isHitAt(Point localPoint) { 265 return Line2D.ptLineDistSq(from.x, from.y, to.x, to.y, localPoint.x, localPoint.y) <= BORDER * BORDER; 266 } 267 268 @Override 269 protected void notifyStateChanged(ObjectState previousState, ObjectState state) { 270 if (previousState.isHovered() != state.isHovered()) { 271 setRecursiveHighlighted(state.isHovered()); 272 } 273 } 274 275 private void setRecursiveHighlighted(boolean b) { 276 LineWidget cur = predecessor; 277 while (cur != null) { 278 cur.setHighlighted(b); 279 cur = cur.predecessor; 280 } 281 282 highlightSuccessors(b); 283 this.setHighlighted(b); 284 } 285 286 private void highlightSuccessors(boolean b) { 287 for (LineWidget s : successors) { 288 s.setHighlighted(b); 289 s.highlightSuccessors(b); 290 } 291 } 292 293 private void setRecursivePopupVisible(boolean b) { 294 LineWidget cur = predecessor; 295 while (cur != null) { 296 cur.setPopupVisible(b); 297 cur = cur.predecessor; 298 } 299 300 popupVisibleSuccessors(b); 301 setPopupVisible(b); 302 } 303 304 private void popupVisibleSuccessors(boolean b) { 305 for (LineWidget s : successors) { 306 s.setPopupVisible(b); 307 s.popupVisibleSuccessors(b); 308 } 309 } 310 311 @Override 312 public JPopupMenu getPopupMenu(Widget widget, Point localLocation) { 313 JPopupMenu menu = new JPopupMenu(); 314 menu.add(scene.createGotoAction(outputSlot.getFigure())); 315 menu.addSeparator(); 316 317 for (Connection c : connections) { 318 InputSlot s = c.getInputSlot(); 319 menu.add(scene.createGotoAction(s.getFigure())); 320 } 321 322 final LineWidget w = this; 323 menu.addPopupMenuListener(new PopupMenuListener() { 324 325 @Override 326 public void popupMenuWillBecomeVisible(PopupMenuEvent e) { 327 w.setRecursivePopupVisible(true); 328 } 329 330 @Override 331 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { 332 w.setRecursivePopupVisible(false); 333 } 334 335 @Override 336 public void popupMenuCanceled(PopupMenuEvent e) { 337 } 338 }); 339 340 return menu; 341 } 342 }