/* * * This file is part of antro, the line-level profiler for ant build scripts. * * antro is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * antro 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 General Public License * along with antro. If not, see <http://www.gnu.org/licenses/>. */ package ru.jkff.antro.ui; import ru.jkff.antro.OurLocation; import ru.jkff.antro.Trace; import javax.swing.*; import java.awt.*; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Formatter; import java.util.List; /** * Created on 17:33:03 17.03.2008 * * @author jkff */ public class BarView extends JScrollPane { private BarControl bars; private TraceView traceView; // When c.percentOfParent is less than this, bars of its children are not painted private static final double EXPAND_THRESHOLD = 5; private static final double PAINT_THRESHOLD = 3; private static final double TOP_PAINT_THRESHOLD = 1; private static final Color HIGHLIGHTED_BAR_COLOR = Color.BLUE; public BarView() { super(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); } public void focusOn(Trace trace) { if (bars != null) remove(bars); bars = new BarControl(); bars.focusOn(trace); getViewport().add(bars); repaint(); } private void selectInTree(Trace t) { traceView.select(t); } private void highlightBars(OurLocation loc) { bars.highlightLocation(loc); } public void setTraceView(TraceView traceView) { this.traceView = traceView; } private class BarControl extends JPanel { private Trace focus; private OurLocation highlightedLocation; private List<Row> rows; private static final int BAR_HEIGHT = 20; public BarControl() { setToolTipText(""); ToolTipManager.sharedInstance().registerComponent(this); this.addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { rows = buildRows(); repaint(); } }); this.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { Trace trace = getTraceAtPoint(e.getX(), e.getY()); if (trace == null) return; if(e.getClickCount() == 1) { highlightBars(trace.getCall().location); } else { selectInTree(trace); } } }); } public void focusOn(Trace trace) { this.focus = trace; rows = buildRows(); repaint(); } private List<Row> buildRows() { List<Row> res = new ArrayList<Row>(); fillRows(res, focus, 0, 0, getWidth(), true, focus.getTotalTime()); return res; } private void fillRows(List<Row> res, Trace trace, int rowIndex, double x0, double width, boolean recurse, double total) { if (res.size() == rowIndex) { res.add(new Row()); } else if (res.size() < rowIndex) { throw new AssertionError(); } res.get(rowIndex).add((int) x0, (int) width, trace); if (!recurse) return; double cx0 = x0; for (Trace child : trace.getChildren()) { if (trace.getTotalTime() == 0) continue; double dw = width * child.getTotalTime() / trace.getTotalTime(); boolean recIntoChild = child.getPercentOfParent() > EXPAND_THRESHOLD; if (child.getPercentOfParent() > PAINT_THRESHOLD && 100 * child.getTotalTime() / total > TOP_PAINT_THRESHOLD) { fillRows(res, child, rowIndex + 1, cx0, dw, recIntoChild, total); } cx0 += dw; } } public void paint(Graphics g) { g.setColor(Color.WHITE); g.fillRect(0, 0, getWidth(), getHeight()); for (int i = 0; i < rows.size(); i++) { Row row = rows.get(i); for (Row.Bar bar : row.bars) { paintBar(g, bar, BAR_HEIGHT * i); } } } private void paintBar(Graphics g, Row.Bar bar, int y) { int x0 = bar.x0, width = bar.width; Trace trace = bar.t; g.setColor(Color.GRAY); g.drawLine(x0 + 1, y, x0 + width - 1, y); g.setColor(Color.BLACK); g.drawLine(x0, y, x0, y + BAR_HEIGHT - 1); g.setColor(Color.DARK_GRAY); g.drawLine(x0 + 1, y + BAR_HEIGHT - 1, x0 + width, y + BAR_HEIGHT - 1); g.drawLine(x0 + width, y, x0 + width, y + BAR_HEIGHT - 1); Color color; if (trace.getCall().location.equals(highlightedLocation)) { color = HIGHLIGHTED_BAR_COLOR; } else { color = toColor(getPercent(trace)); } g.setColor(color); g.fillRect(x0, y, width, BAR_HEIGHT); String msg = describe(trace); g.setFont(new Font("Times New Roman", Font.PLAIN, 9)); int sw = g.getFontMetrics().stringWidth(msg); if (sw >= width) { // Try to shorten the string msg = new Formatter().format("%.0fs %s", trace.getTotalTime(), trace.getCall().name).toString(); sw = g.getFontMetrics().stringWidth(msg); if (sw >= width) { // Try to shorten again msg = trace.getCall().name; sw = g.getFontMetrics().stringWidth(msg); if (sw < width) { msg = trace.getCall().name; } else { // Everything failed return; } } } int h = g.getFontMetrics().getHeight(); g.setColor(Color.BLACK); g.drawString(msg, x0 + (width - sw) / 2, y + BAR_HEIGHT / 2 + h / 2); } private double getPercent(Trace trace) { if (focus.getTotalTime() == 0) return 0; else return 100 * trace.getTotalTime() / focus.getTotalTime(); } private Color toColor(double percent) { if (percent < 50) { return ColorUtil.blend(Color.GREEN, Color.YELLOW, percent / 50); } else { return ColorUtil.blend(Color.YELLOW, Color.RED, (percent - 50) / 50); } } public Trace getTraceAtPoint(int x, int y) { int row = getRowIndex(y); return row < rows.size() ? rows.get(row).getTraceAt(x) : null; } private int getRowIndex(int y) { return y / BAR_HEIGHT; } public String getToolTipText(MouseEvent event) { Trace t = getTraceAtPoint(event.getX(), event.getY()); if (t == null) return null; return describe(t); } private String describe(Trace trace) { double percent = getPercent(trace); return new Formatter().format("%.0f%% %.0fs %s", percent, trace.getTotalTime(), trace.getCall().name).toString(); } public void highlightLocation(OurLocation loc) { highlightedLocation = loc; repaint(); } private class Row { private class Bar { int x0, width; Trace t; } ArrayList<Bar> bars = new ArrayList<Bar>(); public void add(int x0, int width, Trace t) { Bar bar = new Bar(); bar.x0 = x0; bar.width = width; bar.t = t; bars.add(bar); } public Trace getTraceAt(int x) { for (Bar bar : bars) { if (bar.x0 < x && bar.x0 + bar.width > x) return bar.t; } return null; } } } }