package lcm.spy;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.Map.Entry;
import java.lang.reflect.*;
import info.monitorenter.gui.chart.Chart2D;
import info.monitorenter.gui.chart.ITrace2D;
import info.monitorenter.gui.chart.ITracePoint2D;
import info.monitorenter.gui.chart.axis.AxisLinear;
import info.monitorenter.gui.chart.traces.Trace2DLtd;
import info.monitorenter.gui.chart.traces.painters.TracePainterDisc;
/**
* Panel that displays general data for lcm types. Viewed by double-clicking
* or right-clicking and selecting Structure Viewer on the channel list.
*
*/
public class ObjectPanel extends JPanel
{
String name;
Object o;
long utime; // time of this message's arrival
int lastwidth = 500;
int lastheight = 100;
JViewport scrollViewport;
final int sparklineWidth = 150; // width in pixels of all sparklines
// margin around the viewport area in which we will draw graphs
// (in pixels)
final int sparklineDrawMargin = 500;
Section currentlyHoveringSection; // section the mouse is hovering over
String currentlyHoveringName; // name of the section the mouse is hovering over
ChartData chartData; // global data about all charts being displayed by lcm-spy
// array of all sparklines that are visible
// or near visible to the user right now
ArrayList<SparklineData> visibleSparklines = new ArrayList<SparklineData>();
boolean visibleSparklinesInitialized = false;
// array of all sparklines being graphed
ArrayList<SparklineData> graphingSparklines = new ArrayList<SparklineData>();
// we keep track of each drawing iteration to know if the row we clicked
// on was displayed. See SparklineData.lastDrawNumber.
int currentDrawNumber = 0;
class Section
{
int x0, y0, x1, y1; // bounding coordinates for sensitive area
boolean collapsed;
HashMap<String, SparklineData> sparklines;
public Section()
{
sparklines = new HashMap<String, SparklineData>();
}
}
/**
* Data about an individual sparkline.
*
*/
class SparklineData
{
int xmin, xmax;
int ymin, ymax;
boolean isHovering;
// all sparklines have a chart associated with them, even though
// we do not use it for display. This allows us to use the data-collection
// and management features
Chart2D chart;
String name;
Section section;
// we keep track of the drawing iteration number for each line
// to let us figure out if the line is currently being drawn
// when the user clicks it. This is needed to fix a bug where the
// user clicks in a place a line used to be, but is no longer
//there since the array it was in got shorter.
int lastDrawNumber = 0;
}
ArrayList<Section> sections = new ArrayList<Section>();
/**
* Constructor for an object panel, call when the user clicks to see more
* data about a message.
*
* @param name name of the channel
* @param chartData global data about all charts displayed by lcm-spy
*/
public ObjectPanel(String name, ChartData chartData)
{
this.name = name;
this.setLayout(null); // not using a layout manager, drawing everything ourselves
this.chartData = chartData;
addMouseListener(new MyMouseAdapter());
addMouseMotionListener(new MyMouseMotionListener());
repaint();
}
/**
* If given a viewport, the object panel can make smart decisions to
* not draw graphs that are currently outside of the user's view
*
* @param viewport viewport from the JScrollPane that contains this ObjectPanel.
*/
public void setViewport(JViewport viewport) {
scrollViewport = viewport;
scrollViewport.addChangeListener(new MyViewportChangeListener());
}
/**
* Called on mouse movement to determine if we need to
* highlight a line or open a chart.
*
* @param e MouseEvent to process
*
* @return returns true if a mouse click was consumed
*/
public boolean doSparklineInteraction(MouseEvent e)
{
int y = e.getY();
currentlyHoveringName = "";
currentlyHoveringSection = null;
for (SparklineData data : visibleSparklines)
{
if (data.ymin <= y && data.ymax >= y && data.lastDrawNumber == currentDrawNumber)
{
// the mouse is above this sparkline
currentlyHoveringName = data.name;
currentlyHoveringSection = data.section;
if (e.getButton() == MouseEvent.BUTTON1)
{
displayDetailedChart(data, false, false);
graphingSparklines.add(data);
} else if (e.getButton() == MouseEvent.BUTTON2)
{
// middle click means open a new chart
displayDetailedChart(data, true, true);
graphingSparklines.add(data);
} else if (e.getButton() == MouseEvent.BUTTON3)
{
// right click means same chart, new axis
displayDetailedChart(data, false, true);
graphingSparklines.add(data);
}
return true;
}
}
return false;
}
/**
* Opens a detailed, interactive chart for a data stream. If the data is already
* displayed in a chart, brings that chart to the front instead.
*
*
* @param data data channel to display
* @param openNewChart set to true to force opening of a new chart window, false to add
* to an already-open chart (if one exists)
* @param newAxis true if we should add a new Y-axis to display this data
*/
public void displayDetailedChart(SparklineData data, boolean openNewChart, boolean newAxis)
{
if (data.chart == null)
{
// this should not happen, but catch it if it does because we can at least safely ignore it
System.out.println("Warning: detailed chart display requested on uninitialized chart " + data.name);
return;
}
// check to see if we are already displaying this trace
Trace2DLtd trace = (Trace2DLtd) data.chart.getTraces().first();
for (ZoomableChartScrollWheel chart : chartData.getCharts())
{
if (chart.getTraces().contains(trace))
{
chart.toFront();
return;
}
}
if (openNewChart || chartData.getCharts().size() < 1)
{
trace.setMaxSize(chartData.detailedSparklineChartSize);
ZoomableChartScrollWheel.newChartFrame(chartData, trace);
} else
{
// find the most recently interacted with chart
long bestFocusTime = -1;
ZoomableChartScrollWheel bestChart = null;
for (ZoomableChartScrollWheel chart : chartData.getCharts())
{
if (chart.getLastFocusTime() > bestFocusTime)
{
bestFocusTime = chart.getLastFocusTime();
bestChart = chart;
}
}
if (bestChart != null)
{
// add this trace to the winning chart
if (!bestChart.getTraces().contains(trace))
{
trace.setMaxSize(chartData.detailedSparklineChartSize);
trace.setColor(bestChart.popColor());
if (newAxis)
{
// add an axis
AxisLinear axis = new AxisLinear();
bestChart.addAxisYRight(axis);
bestChart.addTrace(trace, bestChart.getAxisX(), axis);
} else
{
bestChart.addTrace(trace);
}
}
bestChart.updateRightClickMenu();
bestChart.toFront();
}
}
}
class PaintState
{
Color indentColors[] = new Color[] {new Color(255,255,255), new Color(230,230,255), new Color(200,200,255)};
Graphics g;
FontMetrics fm;
JPanel panel;
int indent_level;
int color_level;
int y;
int textheight;
int x[] = new int[4]; // tab stops
int indentpx = 20; // pixels per indent level
int maxwidth;
int nextsection = 0;
int collapse_depth = 0;
public int beginSection(String type, String name, String value)
{
// allocate a new section number and make sure there's
// an entry for us to use in the sections array.
int section = nextsection++;
Section cs;
if (section == sections.size()) {
cs = new Section();
sections.add(cs);
}
cs = sections.get(section);
// Some enclosing section is collapsed, exit before drawing anything.
if (collapse_depth == 0)
{
// we're not currently collapsed. Draw the header (at least.)
beginColorBlock();
spacer();
Font of = g.getFont();
g.setFont(of.deriveFont(Font.BOLD));
FontMetrics fm = g.getFontMetrics();
String tok = cs.collapsed ? "+" : "-";
g.setColor(Color.white);
g.fillRect(x[0] + indent_level*indentpx, y, 1, 1);
g.setColor(Color.black);
String type_split[] = type.split("\\.");
String drawtype = type_split[type_split.length - 1];
int type_len = fm.stringWidth(drawtype);
int name_len = fm.stringWidth(name);
int tok_pixidx = x[0] + indent_level*indentpx;
int type_pixidx = x[0] + indent_level*indentpx + 10;
g.drawString(tok, tok_pixidx, y);
g.drawString(drawtype, type_pixidx, y);
// set top of clicking area before
// we might do any text wrapping
cs.y0 = y - textheight;
// check if type field is too long. put name on new line if yes
if (type_pixidx + type_len > x[1])
y+= textheight;
g.drawString(name, x[1], y);
// check if name field is too long. put value on new line if yes
// No need to put it on a new line if value is NULL
if (x[1] + name_len > x[2] && value.length() > 0)
y+= textheight;
g.drawString(value, x[2], y);
g.setFont(of);
final int extra_click_margin = 10; // in pixels
// set up the coordinates where clicking will toggle whether
// we are collapsed.
cs.x0 = x[0];
// only have section minimization out to the edge of the text
if (name_len > 0)
cs.x1 = x[1] + name_len + extra_click_margin;
else {
cs.x1 = type_pixidx + type_len + extra_click_margin;
}
cs.y1 = y;
y += textheight;
}
else
{
// no clicking area.
cs.x0 = 0; cs.x1 = 0; cs.y0 = 0; cs.y1 = 0;
}
// if this section is collapsed, stop drawing.
if (sections.get(section).collapsed) {
collapse_depth ++;
} else if (collapse_depth == 0) {
// Only indent if this section isn't collapsed.
indent();
}
return section;
}
public void endSection(int section)
{
Section cs = sections.get(section);
// if this section is collapsed, resume drawing.
if (sections.get(section).collapsed) {
collapse_depth --;
}
if (collapse_depth == 0) {
unindent();
}
spacer();
endColorBlock();
spacer();
}
public void drawStrings(String type, String name, String value, boolean isstatic)
{
if (collapse_depth > 0)
return;
Font of = g.getFont();
if (isstatic)
g.setFont(of.deriveFont(Font.ITALIC));
g.drawString(type, x[0] + indent_level*indentpx, y);
g.drawString(name, x[1], y);
g.drawString(value, x[2], y);
y+= textheight;
g.setFont(of);
}
/**
* Draws a row for a piece of data in the message and also a sparkline
* for that data.
*
* @param cls type of the data
* @param name name of the entry in the message
* @param o the data itself
* @param isstatic true if the data is static
* @param sec index of section this row is in, used to determine if this
* row should be highlighted because it is under the mouse cursor.
*/
public void drawStringsAndGraph(Class cls, String name, Object o, boolean isstatic,
int sec)
{
Section cs = sections.get(sec);
double value = Double.NaN;
if (o instanceof Double)
value = (Double) o;
else if (o instanceof Float)
value = (Float) o;
else if (o instanceof Integer)
value = (Integer) o;
else if (o instanceof Long)
value = (Long) o;
else if (o instanceof Short)
value = (Short) o;
else if (o instanceof Byte)
value = (Byte) o;
if (collapse_depth > 0)
{
// even if we are collapsed, we need to update the data in
// graphs being displayed
SparklineData data = cs.sparklines.get(name);
if (data.chart != null)
{
ITrace2D trace = data.chart.getTraces().first();
if (trace.getMaxX() < utime/1000000.0d) {
// this is a new point, add it
trace.addPoint(utime/1000000.0d, value);
}
}
return;
}
if (isstatic)
{
drawStrings(cls.getName(), name, o.toString(), isstatic);
return;
}
Color oldColor = g.getColor();
boolean isHovering = false;
if (currentlyHoveringSection != null && cs == currentlyHoveringSection
&& currentlyHoveringName.equals(name))
{
isHovering = true;
g.setColor(Color.RED);
}
Font of = g.getFont();
g.drawString(cls.getName(), x[0] + indent_level*indentpx, y);
g.drawString(name, x[1], y);
if (cls.equals(Byte.TYPE)) {
g.drawString(String.format("0x%02X %03d %+04d %c",
(o),((Byte)o).intValue()&0x00FF,(o), ((Byte)o)&0xff), x[2], y);
} else {
g.drawString(o.toString(), x[2], y);
}
g.setColor(oldColor);
// draw the graph
if (!Double.isNaN(value))
{
SparklineData data = cs.sparklines.get(name);
if (data.chart == null)
{
data.chart = InitChart(name);
}
Chart2D chart = data.chart;
ITrace2D trace = chart.getTraces().first();
// update the positions every loop in case another section
// was collapsed
data.xmin = x[3];
data.xmax = x[3]+sparklineWidth;
// add the data to our trace
if (trace.getMaxX() < utime/1000000.0d) {
// this is a new point, add it
trace.addPoint(utime/1000000.0d, value);
}
data.lastDrawNumber = currentDrawNumber;
// draw the graph
DrawSparkline(x[3], y, trace, isHovering);
}
y+= textheight;
g.setFont(of);
g.setColor(oldColor);
}
/**
* Draws a sparkline.
*
* @param x x-coordinate of the left side of the line
* @param y y-coordinate of the top of the line
* @param trace data for the sparkline
* @param isHovering true if the mouse cursor is hovering over this row
*/
public void DrawSparkline(int x, int y, ITrace2D trace, boolean isHovering)
{
if (trace.getSize() < 2)
{
return;
}
Graphics2D g2 = (Graphics2D) g;
Iterator<ITracePoint2D> iter = trace.iterator();
final int circleSize = 3;
final int height = textheight;
double numSecondsDisplayed = 5.0;
final double width = sparklineWidth;
//width = width * ((double)trace.getSize() / (double) trace.getMaxSize());
if (trace.getMaxX() == trace.getMinX())
{
// no time series, don't draw anything
return;
}
Color pointColor = Color.RED;
Color lineColor = Color.BLACK;
if (isHovering) {
Color temp = pointColor;
pointColor = lineColor;
lineColor = temp;
}
double earliestTimeDisplayed = (utime/1000000.0 - numSecondsDisplayed);
// decide on the main axis scale
double xscale = width / (numSecondsDisplayed);
if (trace.getMaxY() == trace.getMinY())
{
// divide by zero error coming up!
// bail and draw a straight line down the center of the graph
g2.setColor(lineColor);
ITracePoint2D firstPoint = iter.next();
int leftLineX = (int)((firstPoint.getX() - earliestTimeDisplayed) * xscale) + x;
if (leftLineX < x)
{
leftLineX = x;
}
g2.drawLine(leftLineX, y-(int)((double)height/(double)2), x+(int)width, y-(int)((double)height/(double)2));
g2.setColor(pointColor);
g2.fillOval(x + (int) width - 1, y-(int)((double)height/(double)2) - 1, circleSize, circleSize);
return;
}
double yscale = height / (trace.getMaxY() - trace.getMinY());
g2.setColor(lineColor);
boolean first = true;
double lastX = 0, lastY = 0, thisX, thisY;
while (iter.hasNext())
{
ITracePoint2D point = iter.next();
if (first)
{
first = false;
lastX = (point.getX() - earliestTimeDisplayed) * xscale + x;
lastY = y - (point.getY() - trace.getMinY()) * yscale;
} else {
thisX = (point.getX() - earliestTimeDisplayed) * xscale + x;
thisY = y - (point.getY() - trace.getMinY()) * yscale;
if (thisX >= x && lastX >= x)
{
g2.drawLine((int)lastX, (int)lastY, (int)thisX, (int)thisY);
}
lastX = thisX;
lastY = thisY;
}
if (!iter.hasNext())
{
// this is the last point, bold it
g2.setColor(pointColor);
g2.fillOval((int)lastX - 1, (int)lastY - 1, 3, 3);
g2.setColor(lineColor);
}
}
}
public void spacer()
{
if (collapse_depth > 0)
return;
y+= textheight/2;
}
public void beginColorBlock()
{
if (collapse_depth > 0)
return;
color_level++;
g.setColor(indentColors[color_level%indentColors.length]);
g.fillRect(x[0] + indent_level*indentpx - indentpx/2, y - fm.getMaxAscent(), getWidth(), getHeight());
g.setColor(Color.black);
}
public void endColorBlock()
{
if (collapse_depth > 0)
return;
color_level--;
g.setColor(indentColors[color_level%indentColors.length]);
g.fillRect(x[0] + indent_level*indentpx -indentpx/2, y - fm.getMaxAscent(), getWidth(), getHeight());
g.setColor(Color.black);
}
public void indent()
{
indent_level++;
}
public void unindent()
{
indent_level--;
}
public void finish()
{
g.setColor(Color.white);
g.fillRect(0, y, getWidth(), getHeight());
}
}
public void setObject(Object o, long utime)
{
this.o = o;
this.utime = utime - chartData.getStartTime();
JFrame topFrame = (JFrame) SwingUtilities.getWindowAncestor(this);
if (topFrame.getExtendedState() == Frame.ICONIFIED) {
UpdateGraphDataWithoutPaint();
} else {
repaint();
}
}
public Dimension getPreferredSize()
{
return new Dimension(lastwidth, lastheight);
}
public Dimension getMinimumSize()
{
return getPreferredSize();
}
public Dimension getMaximumSize()
{
return getPreferredSize();
}
/**
* Updates visibleSparklines to reflect the data that is near the user's view
* at the current time.
*
* @param viewport viewport the user is looking at. Usually from an event: e.getSource()
*/
void updateVisibleSparklines(JViewport viewport)
{
Rectangle view_rect = viewport.getViewRect();
visibleSparklines.clear();
for (int i = sections.size() -1; i > -1; i--)
{
Section section = sections.get(i);
if (section.collapsed == false)
{
Iterator<Entry<String, SparklineData>> it = section.sparklines.entrySet().iterator();
while (it.hasNext())
{
Entry<String, SparklineData> pair = it.next();
SparklineData data = pair.getValue();
if (data.ymin > view_rect.y - sparklineDrawMargin
&& data.ymax < view_rect.y + view_rect.height + sparklineDrawMargin)
{
visibleSparklines.add(data);
}
}
}
}
}
public void paint(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int width = getWidth(), height = getHeight();
g.setColor(Color.white);
g.fillRect(0, 0, width, height);
g.setColor(Color.black);
FontMetrics fm = g.getFontMetrics();
PaintState ps = new PaintState();
ps.panel = this;
ps.g = g;
ps.fm = fm;
ps.textheight = 15;
ps.y = ps.textheight;
ps.indent_level=1;
ps.x[0] = 0;
ps.x[1] = Math.min(200, width/4);
ps.x[2] = Math.min(ps.x[1]+200, 2*width/4);
ps.x[3] = ps.x[2]+150;
currentDrawNumber ++;
int previousNumSections = sections.size();
// check to make sure that visibleSparklines has been
// initialized otherwise we can end up not displaying
// items if the viewport is never changed
if (!visibleSparklinesInitialized
&& visibleSparklines.isEmpty()
&& (previousNumSections > 0)
&& (scrollViewport != null)) {
visibleSparklinesInitialized = true;
updateVisibleSparklines(scrollViewport);
}
if (o != null)
paintRecurse(g, ps, "", o.getClass(), o, false, -1);
ps.finish();
if (ps.y != lastheight) {
lastheight = ps.y;
invalidate();
getParent().validate();
}
if (previousNumSections != sections.size()) {
// if the number of sections has changed, the system that figures out
// what to draw based on user view needs to rerun to update
repaint();
}
}
void paintRecurse(Graphics g, PaintState ps, String name, Class cls, Object o, boolean isstatic, int section)
{
if (o == null) {
ps.drawStrings(cls==null ? "(null)" : cls.getName(), name, "(null)", isstatic);
return;
}
if (cls.isPrimitive() || cls.equals(Byte.TYPE)) {
// This is our common case...
Section cs = sections.get(section);
SparklineData data = cs.sparklines.get(name); // if data == null, this graph doesn't exist yet
if (data == null)
{
// we may or may not draw this depending on if it is near the view but we need to keep track of it
// so the user can click on it
data = new SparklineData();
data.name = name;
data.section = cs;
data.isHovering = false;
data.chart = null;
cs.sparklines.put(name, data);
}
// text can drop below the expected height for letters like
// "g", which makes it possible to click on a letter and get
// the wrong graph. Add a small correction factor to deal with that
final int text_below_line_height = 2; // in px
data.ymin = ps.y - ps.textheight + text_below_line_height;
data.ymax = ps.y + text_below_line_height;
if (visibleSparklines.contains(data) || graphingSparklines.contains(data))
{
ps.drawStringsAndGraph(cls, name, o, isstatic, section);
} else {
// don't bother drawing the strings or graph for it.
// just update the text height to pretend we drew it
// (on huge messages, this is a large CPU savings)
if (ps.collapse_depth > 0)
return;
ps.y+= ps.textheight;
}
} else if (o instanceof Enum) {
ps.drawStrings(cls.getName(), name, ((Enum) o).name(), isstatic);
} else if (cls.equals(String.class)) {
ps.drawStrings("String", name, o.toString(), isstatic);
} else if (cls.isArray()) {
int sz = Array.getLength(o);
int sec = ps.beginSection(cls.getComponentType()+"[]", name+"["+sz+"]", "");
for (int i = 0; i < sz; i++)
paintRecurse(g, ps, name+"["+i+"]", cls.getComponentType(), Array.get(o, i), isstatic, sec);
ps.endSection(sec);
} else {
// it's a compound type. recurse.
int sec = ps.beginSection(cls.getName(), name, "");
// it's a class
Field fs[] = cls.getFields();
for (Field f : fs) {
try {
paintRecurse(g, ps, f.getName(), f.getType(), f.get(o), isstatic || ((f.getModifiers()&Modifier.STATIC) != 0), sec);
} catch (Exception ex) {
System.out.println(ex.getMessage());
ex.printStackTrace(System.out);
}
}
ps.endSection(sec);
}
}
/**
* Usually the paintRecurse method deals with searching through the incoming data
* but when the window is minimized, we do not paint, causing the child graphs
* to stop updating. This method performs that search and adds data to the graphs
* without actually drawing anything
*/
void UpdateGraphDataWithoutPaint() {
for (SparklineData data : graphingSparklines) {
UpdateGraphDataWithoutPaintRecurse(data, "", o.getClass(), o);
}
}
/**
* Recursive call for the search through the data object
*
* @param sparklineToUpdate data to update
* @param name
* @param cls
* @param o
*/
void UpdateGraphDataWithoutPaintRecurse(SparklineData sparklineToUpdate, String name, Class cls, Object o) {
if (sparklineToUpdate.chart == null) {
// don't have a big chart for this, no point in updating it
return;
}
if (o instanceof Enum || cls.equals(String.class)) {
// no further objects to recurse into and no
// data to store
return;
}
if (cls.isPrimitive() || cls.equals(Byte.TYPE)) {
if (sparklineToUpdate.name.equals(name))
{
// we found the part of the data that this chart is using
// update it!
double value = Double.NaN;
if (o instanceof Double)
value = (Double) o;
else if (o instanceof Float)
value = (Float) o;
else if (o instanceof Integer)
value = (Integer) o;
else if (o instanceof Long)
value = (Long) o;
else if (o instanceof Short)
value = (Short) o;
else if (o instanceof Byte)
value = (Byte) o;
ITrace2D trace = sparklineToUpdate.chart.getTraces().first();
if (trace.getMaxX() < utime/1000000.0d) {
// this is a new point, add it
trace.addPoint(utime/1000000.0d, value);
}
return;
}
} else if (cls.isArray()) {
int sz = Array.getLength(o);
for (int i = 0; i < sz; i++)
UpdateGraphDataWithoutPaintRecurse(sparklineToUpdate, name+"["+i+"]", cls.getComponentType(), Array.get(o, i));
} else {
// it's a compound type. recurse.
// it's a class
Field fs[] = cls.getFields();
for (Field f : fs) {
try {
UpdateGraphDataWithoutPaintRecurse(sparklineToUpdate, f.getName(), f.getType(), f.get(o));
} catch (Exception ex) {
System.out.println(ex.getMessage());
ex.printStackTrace(System.out);
}
}
}
}
public boolean isOptimizedDrawingEnabled()
{
return false;
}
/**
* Initialize a chart. This should happen before you want to use
* the chart, either for storing data or displaying data. It's best
* to delay the inits until you need them, because making a lot of charts
* seems to slow down chart interactions.
*
* @param name Name of the trace you want to init
* @return the created chart object
*/
public Chart2D InitChart(String name)
{
Chart2D chart = new Chart2D();
ITrace2D trace = new Trace2DLtd(chartData.sparklineChartSize, name);
chart.addTrace(trace);
// add marker lines to the trace
TracePainterDisc markerPainter = new TracePainterDisc();
markerPainter.setDiscSize(2);
trace.addTracePainter(markerPainter);
return chart;
}
class MyMouseAdapter extends MouseAdapter
{
/**
* Handle mouse clicks. Either opens graphs if the user
* clicked on a row or toggles sections.
*
* @param e MouseEvent that fired this click
*/
public void mouseClicked(MouseEvent e)
{
int x = e.getX(), y = e.getY();
// check to see if we have clicked on a row in the inspector
// and should open a graph of the data
if (doSparklineInteraction(e) == true)
{
return;
}
int bestsection = -1;
// find the bottom-most section that contains the mouse click.
for (int i = 0; i < sections.size(); i++)
{
Section cs = sections.get(i);
if (x>=cs.x0 && x<=cs.x1 && y>=cs.y0 && y<=cs.y1) {
bestsection = i;
}
}
if (bestsection >= 0)
sections.get(bestsection).collapsed ^= true;
// when changing sections, need to recompute visibility of sparklines
// or you can end up not displaying a section until the viewport changes
updateVisibleSparklines(scrollViewport);
// call repaint here so the UI will update immediately instead of
// waiting for the next piece of data
repaint();
}
}
class MyMouseMotionListener extends MouseMotionAdapter
{
/**
* Check to see if we need to update the highlight
* on a row.
*
* @param e MouseEvent from the mouse move
*/
public void mouseMoved(MouseEvent e)
{
// check to see if we are hovering over any rows of data
doSparklineInteraction(e);
// repaint in case the hovering changed
repaint();
}
}
class MyViewportChangeListener implements ChangeListener
{
/**
* Here we build a list of the items that are visible
* or are close to visible to the user. That way, we can
* only update sparkline charts that are close to what the
* user is looking at, reducing CPU load with huge messages
*
* @param e change event that fired this update
*/
public void stateChanged(ChangeEvent e)
{
JViewport viewport = (JViewport) e.getSource();
updateVisibleSparklines(viewport);
}
}
}