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.data.InputGraph;
27 import com.sun.hotspot.igv.data.Properties;
28 import com.sun.hotspot.igv.data.services.GraphViewer;
29 import com.sun.hotspot.igv.graph.Figure;
30 import com.sun.hotspot.igv.util.DoubleClickAction;
31 import com.sun.hotspot.igv.util.DoubleClickHandler;
32 import com.sun.hotspot.igv.util.PropertiesSheet;
33 import com.sun.hotspot.igv.view.DiagramScene;
34 import java.awt.*;
35 import java.awt.event.ActionEvent;
36 import java.util.ArrayList;
37 import java.util.HashSet;
38 import java.util.Set;
39 import javax.swing.AbstractAction;
40 import javax.swing.Action;
41 import javax.swing.BorderFactory;
42 import javax.swing.JMenu;
43 import javax.swing.JPopupMenu;
44 import javax.swing.event.MenuEvent;
45 import javax.swing.event.MenuListener;
46 import org.netbeans.api.visual.action.PopupMenuProvider;
47 import org.netbeans.api.visual.action.WidgetAction;
48 import org.netbeans.api.visual.layout.LayoutFactory;
49 import org.netbeans.api.visual.model.ObjectState;
50 import org.netbeans.api.visual.widget.LabelWidget;
51 import org.netbeans.api.visual.widget.Widget;
52 import org.openide.nodes.AbstractNode;
53 import org.openide.nodes.Children;
54 import org.openide.nodes.Node;
55 import org.openide.nodes.Sheet;
56 import org.openide.util.Lookup;
57 
58 /**
59  *
60  * @author Thomas Wuerthinger
61  */
62 public class FigureWidget extends Widget implements Properties.Provider, PopupMenuProvider, DoubleClickHandler {
63 
64     public static final boolean VERTICAL_LAYOUT = true;
65     private static final double LABEL_ZOOM_FACTOR = 0.3;
66     private Figure figure;
67     private Widget leftWidget;
68     private Widget rightWidget;
69     private Widget middleWidget;
70     private ArrayList<LabelWidget> labelWidgets;
71     private DiagramScene diagramScene;
72     private boolean boundary;
73     private final Node node;
74     private Widget dummyTop;
75 
setBoundary(boolean b)76     public void setBoundary(boolean b) {
77         boundary = b;
78     }
79 
isBoundary()80     public boolean isBoundary() {
81         return boundary;
82     }
83 
getNode()84     public Node getNode() {
85         return node;
86     }
87 
88     @Override
isHitAt(Point localLocation)89     public boolean isHitAt(Point localLocation) {
90         return middleWidget.isHitAt(localLocation);
91     }
92 
FigureWidget(final Figure f, WidgetAction hoverAction, WidgetAction selectAction, DiagramScene scene, Widget parent)93     public FigureWidget(final Figure f, WidgetAction hoverAction, WidgetAction selectAction, DiagramScene scene, Widget parent) {
94         super(scene);
95 
96         assert this.getScene() != null;
97         assert this.getScene().getView() != null;
98 
99         this.figure = f;
100         this.setCheckClipping(true);
101         this.diagramScene = scene;
102         parent.addChild(this);
103 
104         Widget outer = new Widget(scene);
105         outer.setBackground(f.getColor());
106         outer.setLayout(LayoutFactory.createOverlayLayout());
107 
108         middleWidget = new Widget(scene);
109         middleWidget.setLayout(LayoutFactory.createVerticalFlowLayout(LayoutFactory.SerialAlignment.CENTER, 0));
110         middleWidget.setBackground(f.getColor());
111         middleWidget.setOpaque(true);
112         middleWidget.getActions().addAction(new DoubleClickAction(this));
113         middleWidget.setCheckClipping(true);
114 
115         dummyTop = new Widget(scene);
116         dummyTop.setMinimumSize(new Dimension(Figure.INSET / 2, 1));
117         middleWidget.addChild(dummyTop);
118 
119         String[] strings = figure.getLines();
120         labelWidgets = new ArrayList<>(strings.length);
121 
122         for (String displayString : strings) {
123             LabelWidget lw = new LabelWidget(scene);
124             labelWidgets.add(lw);
125             middleWidget.addChild(lw);
126             lw.setLabel(displayString);
127             lw.setFont(figure.getDiagram().getFont());
128             lw.setForeground(getTextColor());
129             lw.setAlignment(LabelWidget.Alignment.CENTER);
130             lw.setVerticalAlignment(LabelWidget.VerticalAlignment.CENTER);
131             lw.setBorder(BorderFactory.createEmptyBorder());
132         }
133 
134         Widget dummyBottom = new Widget(scene);
135         dummyBottom.setMinimumSize(new Dimension(Figure.INSET / 2, 1));
136         middleWidget.addChild(dummyBottom);
137 
138         middleWidget.setPreferredBounds(new Rectangle(0, Figure.SLOT_WIDTH - Figure.OVERLAPPING, f.getWidth(), f.getHeight()));
139         this.addChild(middleWidget);
140 
141         // Initialize node for property sheet
142         node = new AbstractNode(Children.LEAF) {
143 
144             @Override
145             protected Sheet createSheet() {
146                 Sheet s = super.createSheet();
147                 PropertiesSheet.initializeSheet(f.getProperties(), s);
148                 return s;
149             }
150         };
151         node.setDisplayName(getName());
152     }
153 
getLeftWidget()154     public Widget getLeftWidget() {
155         return leftWidget;
156     }
157 
getRightWidget()158     public Widget getRightWidget() {
159         return rightWidget;
160     }
161 
162     @Override
notifyStateChanged(ObjectState previousState, ObjectState state)163     protected void notifyStateChanged(ObjectState previousState, ObjectState state) {
164         super.notifyStateChanged(previousState, state);
165 
166         Font font = this.figure.getDiagram().getFont();
167         if (state.isSelected()) {
168             font = this.figure.getDiagram().getBoldFont();
169         }
170 
171         Color borderColor = Color.BLACK;
172         Color innerBorderColor = getFigure().getColor();
173         if (state.isHighlighted()) {
174             innerBorderColor = borderColor = Color.BLUE;
175         }
176 
177         middleWidget.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(borderColor, 1), BorderFactory.createLineBorder(innerBorderColor, 1)));
178         for (LabelWidget labelWidget : labelWidgets) {
179             labelWidget.setFont(font);
180         }
181         repaint();
182     }
183 
getName()184     public String getName() {
185         return getProperties().get("name");
186     }
187 
188     @Override
getProperties()189     public Properties getProperties() {
190         return figure.getProperties();
191     }
192 
getFigure()193     public Figure getFigure() {
194         return figure;
195     }
196 
getTextColor()197     private Color getTextColor() {
198         Color bg = figure.getColor();
199         double brightness = bg.getRed() * 0.21 + bg.getGreen() * 0.72 + bg.getBlue() * 0.07;
200         if (brightness < 150) {
201             return Color.WHITE;
202         } else {
203             return Color.BLACK;
204         }
205     }
206 
207     @Override
paintChildren()208     protected void paintChildren() {
209         Composite oldComposite = null;
210         if (boundary) {
211             oldComposite = getScene().getGraphics().getComposite();
212             float alpha = DiagramScene.ALPHA;
213             this.getScene().getGraphics().setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
214         }
215 
216         if (diagramScene.getZoomFactor() < LABEL_ZOOM_FACTOR) {
217             for (LabelWidget labelWidget : labelWidgets) {
218                 labelWidget.setVisible(false);
219             }
220             super.paintChildren();
221             for (LabelWidget labelWidget : labelWidgets) {
222                 labelWidget.setVisible(true);
223             }
224         } else {
225             Color oldColor = null;
226             if (boundary) {
227                 for (LabelWidget labelWidget : labelWidgets) {
228                     oldColor = labelWidget.getForeground();
229                     labelWidget.setForeground(Color.BLACK);
230                 }
231             }
232             super.paintChildren();
233             if (boundary) {
234                 for (LabelWidget labelWidget : labelWidgets) {
235                     labelWidget.setForeground(oldColor);
236                 }
237             }
238         }
239 
240         if (boundary) {
241             getScene().getGraphics().setComposite(oldComposite);
242         }
243     }
244 
245     @Override
getPopupMenu(Widget widget, Point point)246     public JPopupMenu getPopupMenu(Widget widget, Point point) {
247         JPopupMenu menu = diagramScene.createPopupMenu();
248         menu.addSeparator();
249 
250         build(menu, getFigure(), this, false, diagramScene);
251         menu.addSeparator();
252         build(menu, getFigure(), this, true, diagramScene);
253 
254         if (getFigure().getSubgraphs() != null) {
255             menu.addSeparator();
256             JMenu subgraphs = new JMenu("Subgraphs");
257             menu.add(subgraphs);
258 
259             final GraphViewer viewer = Lookup.getDefault().lookup(GraphViewer.class);
260             for (final InputGraph subgraph : getFigure().getSubgraphs()) {
261                 Action a = new AbstractAction() {
262 
263                     @Override
264                     public void actionPerformed(ActionEvent e) {
265                         viewer.view(subgraph, true);
266                     }
267                 };
268 
269                 a.setEnabled(true);
270                 a.putValue(Action.NAME, subgraph.getName());
271                 subgraphs.add(a);
272             }
273         }
274 
275         return menu;
276     }
277 
build(JPopupMenu menu, Figure figure, FigureWidget figureWidget, boolean successors, DiagramScene diagramScene)278     public static void build(JPopupMenu menu, Figure figure, FigureWidget figureWidget, boolean successors, DiagramScene diagramScene) {
279         Set<Figure> set = figure.getPredecessorSet();
280         if (successors) {
281             set = figure.getSuccessorSet();
282         }
283 
284         boolean first = true;
285         for (Figure f : set) {
286             if (f == figure) {
287                 continue;
288             }
289 
290             if (first) {
291                 first = false;
292             } else {
293                 menu.addSeparator();
294             }
295 
296             Action go = diagramScene.createGotoAction(f);
297             menu.add(go);
298 
299             JMenu preds = new JMenu("Nodes Above");
300             preds.addMenuListener(figureWidget.new NeighborMenuListener(preds, f, false));
301             menu.add(preds);
302 
303             JMenu succs = new JMenu("Nodes Below");
304             succs.addMenuListener(figureWidget.new NeighborMenuListener(succs, f, true));
305             menu.add(succs);
306         }
307 
308         if (figure.getPredecessorSet().isEmpty() && figure.getSuccessorSet().isEmpty()) {
309             menu.add("(none)");
310         }
311     }
312 
313     /**
314      * Builds the submenu for a figure's neighbors on demand.
315      */
316     public class NeighborMenuListener implements MenuListener {
317 
318         private final JMenu menu;
319         private final Figure figure;
320         private final boolean successors;
321 
NeighborMenuListener(JMenu menu, Figure figure, boolean successors)322         public NeighborMenuListener(JMenu menu, Figure figure, boolean successors) {
323             this.menu = menu;
324             this.figure = figure;
325             this.successors = successors;
326         }
327 
328         @Override
menuSelected(MenuEvent e)329         public void menuSelected(MenuEvent e) {
330             if (menu.getItemCount() > 0) {
331                 // already built before
332                 return;
333             }
334 
335             build(menu.getPopupMenu(), figure, FigureWidget.this, successors, diagramScene);
336         }
337 
338         @Override
menuDeselected(MenuEvent e)339         public void menuDeselected(MenuEvent e) {
340             // ignore
341         }
342 
343         @Override
menuCanceled(MenuEvent e)344         public void menuCanceled(MenuEvent e) {
345             // ignore
346         }
347     }
348 
349     @Override
handleDoubleClick(Widget w, WidgetAction.WidgetMouseEvent e)350     public void handleDoubleClick(Widget w, WidgetAction.WidgetMouseEvent e) {
351 
352         if (diagramScene.isAllVisible()) {
353             final Set<Integer> hiddenNodes = new HashSet<>(diagramScene.getModel().getGraphToView().getGroup().getAllNodes());
354             hiddenNodes.removeAll(this.getFigure().getSource().getSourceNodesAsSet());
355             this.diagramScene.getModel().showNot(hiddenNodes);
356         } else if (isBoundary()) {
357 
358             final Set<Integer> hiddenNodes = new HashSet<>(diagramScene.getModel().getHiddenNodes());
359             hiddenNodes.removeAll(this.getFigure().getSource().getSourceNodesAsSet());
360             this.diagramScene.getModel().showNot(hiddenNodes);
361         } else {
362             final Set<Integer> hiddenNodes = new HashSet<>(diagramScene.getModel().getHiddenNodes());
363             hiddenNodes.addAll(this.getFigure().getSource().getSourceNodesAsSet());
364             this.diagramScene.getModel().showNot(hiddenNodes);
365         }
366     }
367 }
368