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.widgets;
25
26import com.sun.hotspot.igv.data.InputGraph;
27import com.sun.hotspot.igv.data.Properties;
28import com.sun.hotspot.igv.data.services.GraphViewer;
29import com.sun.hotspot.igv.graph.Figure;
30import com.sun.hotspot.igv.util.DoubleClickAction;
31import com.sun.hotspot.igv.util.DoubleClickHandler;
32import com.sun.hotspot.igv.util.PropertiesSheet;
33import com.sun.hotspot.igv.view.DiagramScene;
34import java.awt.*;
35import java.awt.event.ActionEvent;
36import java.util.ArrayList;
37import java.util.HashSet;
38import java.util.Set;
39import javax.swing.AbstractAction;
40import javax.swing.Action;
41import javax.swing.BorderFactory;
42import javax.swing.JMenu;
43import javax.swing.JPopupMenu;
44import javax.swing.event.MenuEvent;
45import javax.swing.event.MenuListener;
46import org.netbeans.api.visual.action.PopupMenuProvider;
47import org.netbeans.api.visual.action.WidgetAction;
48import org.netbeans.api.visual.layout.LayoutFactory;
49import org.netbeans.api.visual.model.ObjectState;
50import org.netbeans.api.visual.widget.LabelWidget;
51import org.netbeans.api.visual.widget.Widget;
52import org.openide.nodes.AbstractNode;
53import org.openide.nodes.Children;
54import org.openide.nodes.Node;
55import org.openide.nodes.Sheet;
56import org.openide.util.Lookup;
57
58/**
59 *
60 * @author Thomas Wuerthinger
61 */
62public 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
76    public void setBoundary(boolean b) {
77        boundary = b;
78    }
79
80    public boolean isBoundary() {
81        return boundary;
82    }
83
84    public Node getNode() {
85        return node;
86    }
87
88    @Override
89    public boolean isHitAt(Point localLocation) {
90        return middleWidget.isHitAt(localLocation);
91    }
92
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
154    public Widget getLeftWidget() {
155        return leftWidget;
156    }
157
158    public Widget getRightWidget() {
159        return rightWidget;
160    }
161
162    @Override
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
184    public String getName() {
185        return getProperties().get("name");
186    }
187
188    @Override
189    public Properties getProperties() {
190        return figure.getProperties();
191    }
192
193    public Figure getFigure() {
194        return figure;
195    }
196
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
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
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
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
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
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
339        public void menuDeselected(MenuEvent e) {
340            // ignore
341        }
342
343        @Override
344        public void menuCanceled(MenuEvent e) {
345            // ignore
346        }
347    }
348
349    @Override
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