StackPanel.java

/*
 * Copyright (c) 2001, Zoltan Farkas All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package org.spf4j.ui;

import com.google.common.base.Predicate;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.List;
import java.util.Map;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.ToolTipManager;
import org.spf4j.base.Pair;
import org.spf4j.base.RTree;
import org.spf4j.stackmonitor.Method;
import org.spf4j.stackmonitor.SampleNode;

/**
 *
 * @author zoly
 */
public final class StackPanel extends JPanel
        implements ActionListener, MouseListener {

    private SampleNode samples;
    private RTree<Pair<Method, Integer>> tooltipDetail = new RTree<Pair<Method, Integer>>();
    private JPopupMenu menu;
    private int xx;
    private int yy;

    public StackPanel(final SampleNode samples) {
        this.samples = samples;
        setPreferredSize(new Dimension(400, 20 * samples.height() + 10));
        ToolTipManager.sharedInstance().registerComponent(this);
        menu = buildPopupMenu();
        addMouseListener(this);
    }

    
    
    
    private JPopupMenu buildPopupMenu() {
        JPopupMenu result = new JPopupMenu("Actions");
        JMenuItem filter = new JMenuItem("Filter");
        filter.setActionCommand("FILTER");
        filter.addActionListener(this);
        result.add(filter);
        return result;
    }
    

    @Override
    public String getToolTipText(final MouseEvent event) {
        Point location = event.getPoint();
        List<Pair<Method, Integer>> tips = tooltipDetail.search(new float[]{location.x, location.y}, new float[]{0, 0});
        if (tips.size() >= 1) {
            return tips.get(0).getFirst().toString() + "-" + tips.get(0).getSecond();
        } else {
            return null;
        }
    }

    @Override
    protected void paintComponent(final Graphics g) {
        super.paintComponent(g);
        Dimension size = getSize();
        Insets insets = getInsets();
        Rectangle2D available = new Rectangle2D.Double(insets.left, insets.top,
                size.getWidth() - insets.left - insets.right,
                size.getHeight() - insets.top - insets.bottom);
        Graphics2D g2 = (Graphics2D) g.create();
        try {
            int rowHeight = (int) g2.getFont().getStringBounds("ROOT", g2.getFontRenderContext()).getHeight();

            GraphicsConfiguration gc = g2.getDeviceConfiguration();
            BufferedImage img = gc.createCompatibleImage(
                    (int) available.getWidth(), (int) available.getHeight(),
                    Transparency.TRANSLUCENT);

            int width = (int) available.getWidth();
            tooltipDetail.clear();
            Graphics2D gr = img.createGraphics();
            gr.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            int height = paintNode(Method.ROOT, samples, gr, 0, 0, width, rowHeight, 0);
            g2.drawImage(img, insets.left, insets.top, this);
            setPreferredSize(new Dimension((int) size.getWidth(), height + 10));
        } finally {
            g2.dispose();
        }
    }

    private int paintNode(final Method method, final SampleNode node,
            final Graphics2D g2, final int x, final int py, final int width, final int height, final int depth) {
        int y = py;
        int sampleCount = node.getSampleCount();
        String val = method.toString() + "-" + sampleCount;

        if (depth % 2 == 0) {
            g2.setPaint(Color.YELLOW);
            g2.setBackground(Color.YELLOW);
        } else {
            g2.setPaint(Color.ORANGE);
            g2.setBackground(Color.ORANGE);
        }
        g2.setClip(x, y, width, height);
        g2.fillRect(x, y, width, height);
        tooltipDetail.insert(new float[]{x, y}, new float[]{width, height}, Pair.of(method, sampleCount));
        g2.setPaint(Color.BLACK);
        g2.drawString(val, x, y + height - 1);
        Map<Method, SampleNode> children = node.getSubNodes();
        int result = height;
        if (children != null) {
            y += height;
            int relX = x;
            double scale = (double) width / sampleCount;
            int maxY = 0;
            for (Map.Entry<Method, SampleNode> entry : children.entrySet()) {
                SampleNode cnode = entry.getValue();
                // sampleCount -> width
                // childSampleCount -> childWidth
                int childWidth = (int) (scale * cnode.getSampleCount());
                maxY = Math.max(maxY, paintNode(entry.getKey(), cnode, g2, relX, y, childWidth, height, depth + 1));
                relX += childWidth;
            }
            result += maxY;
        }
        return result;
    }

    @Override
    public void actionPerformed(final ActionEvent e) {
        if (e.getActionCommand().equals("FILTER")) {
            List<Pair<Method, Integer>> tips = tooltipDetail.search(new float[]{xx, yy}, new float[]{0, 0});
            if (tips.size() >= 1) {
                final Method value = tips.get(0).getFirst();
                samples = samples.filteredBy(new Predicate<Method>() {

                    @Override
                    public boolean apply(final Method t) {
                        return t.equals(value);
                    }
                });
                repaint();
            }
        }
    }

    @Override
    public void mouseClicked(final MouseEvent e) {
    }

    @Override
    public void mousePressed(final MouseEvent e) {
        if (e.isPopupTrigger()) {
            xx = e.getX();
            yy = e.getY();
            menu.show(this, e.getX(), e.getY());
        }
    }

    @Override
    public void mouseReleased(final MouseEvent e) {
        if (e.isPopupTrigger()) {
            xx = e.getX();
            yy = e.getY();
            menu.show(this, e.getX(), e.getY());
        }
    }

    @Override
    public void mouseEntered(final MouseEvent e) {
    }

    @Override
    public void mouseExited(final MouseEvent e) {
    }
}