package com.rle.monitor; import java.awt.*; import java.awt.event.*; import java.io.*; import javax.swing.*; import javax.swing.border.*; import javax.swing.event.*; /* * RLE Surface Crack Detector Graphing Software * * Copyright (C) 2002 RLE Technologies * For more information visit http://www.rletech.com/ * * This program 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 2 * of the License, or (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */ /** * @author Andy Goth * @author Eric Peterson * @author Matt Lane * @version 0.1 */ /* * TODO: * Make fps configurable */ /** Panel where charting occurs. */ public class Chart extends JPanel { private Buffer buffer; private Timer timer; private Bounds bounds; private Selection selection; private EventListenerList selectionListeners = new EventListenerList(); private MouseListener mouseListener; private int fps = 60; /** Constructor. */ public Chart(Buffer buffer) { super(); this.buffer = buffer; bounds = new Bounds(buffer); mouseListener = new MouseListener(); selection = new Selection(buffer); setBorder(BorderFactory.createLoweredBevelBorder()); addMouseListener(mouseListener); addMouseMotionListener(mouseListener); timer = new Timer(1000 / fps, new ActionListener() { public void actionPerformed(ActionEvent e) {repaint();} }); timer.start(); } /** Gets the graphical position for the given data index. */ public int getPositionForIndex(int i) { return (int)((i - bounds.getLeft()) * (getWidth() - 4f) / (bounds.getWidth() - 1)); } /** Gets the data index for the given graphical position. */ public int getIndexForPosition(int x) { return (int)(x * (bounds.getWidth() - 1) / (getWidth() - 4f) + bounds.getLeft()); } /** Gets the graphical position for the given value. */ public int getPositionForValue(int v) { return (int)((bounds.getCeil() - v) * (getHeight() - 4f) / (bounds.getHeight() - 1)); } /** Gets the value for the given graphical position. */ public int getValueForPosition(int y) { return (int)(bounds.getCeil() - y * (bounds.getHeight() - 1) / (getHeight() - 4f)); } /** Returns the chart's selection. */ public Selection getSelection() {return selection;} /** Returns the chart's bounds. */ public Bounds getBounds2() {return bounds;} /** Sets the chart's bounds. */ public void setBounds(Bounds bounds) { this.bounds = bounds; repaint(); } /** Ensures that the given index is visible. */ public void show(int idx) { int margin = getIndexForPosition(5) - getIndexForPosition(0); if (idx < bounds.getLeft() + margin) { idx -= margin; if (idx < bounds.getMinLeft()) idx = bounds.getMinLeft(); bounds.setRange(idx, idx + bounds.getWidth()); } else if (idx > bounds.getRight() - margin) { idx += margin; if (idx > bounds.getMaxRight()) idx = bounds.getMaxRight(); bounds.setRange(idx - bounds.getWidth(), idx); } repaint(); } /** Ensure that the given range is visible. */ public void show(int left, int right) { if (left > right) { int t = left; left = right; right = t; } if (left < bounds.getMinLeft()) left = bounds.getMinLeft(); if (right > bounds.getMaxRight()) right = bounds.getMaxRight(); if (right < left + bounds.getMinWidth()) { int overhang = (bounds.getMinWidth() - (right - left)) / 2; left -= overhang; right += overhang; if (right < left + bounds.getMinWidth()) right++; if (left < bounds.getMinLeft()) { left = bounds.getMinLeft(); right = left + bounds.getMinWidth(); } else if (right > bounds.getMaxRight()) { right = bounds.getMaxRight(); left = right - bounds.getMinWidth(); } } bounds.setRange(left, right); repaint(); } /** Registers a new selection listener. */ public void addSelectionListener(SelectionListener l) { listenerList.add(SelectionListener.class, l); } /** Unregisters a selection listener. */ public void removeSelectionListener(SelectionListener l) { listenerList.remove(SelectionListener.class, l); } /** Sends out notifications that the selection range changed. */ protected void fireRangeChange() { Object[] listeners = listenerList.getListenerList(); SelectionEvent event = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] != SelectionListener.class) continue; if (event == null) event = new SelectionEvent(this); ((SelectionListener)listeners[i + 1]). rangeChange(event); } } /** Sends out notifications that the selection state changed. */ protected void fireStateChange() { Object[] listeners = listenerList.getListenerList(); SelectionEvent event = null; for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] != SelectionListener.class) continue; if (event == null) event = new SelectionEvent(this); ((SelectionListener)listeners[i + 1]). stateChange(event); } } /** Sets whether or not regular graphic updates are taking place. */ public void setRunning(boolean running) { if (running == timer.isRunning()) return; if (running) timer.start(); else timer.stop(); } /** Zooms out and/or clears the selection. */ public void clearSelection() {mouseListener.clearSelection();} /** Render the chart. */ public void paint(Graphics g) { if (g == null) return; super.paint(g); /* Adjust for two-pixel border drawn by the call to super */ g.translate(2, 2); int width = getWidth() - 4; int height = getHeight() - 4; /* Don't allow the border to be drawn over (until later...) */ Shape clip = g.getClip(); g.setClip(0, 0, width, height); /* Calculate some stuff */ float horzScale = (getWidth() - 4f) / (bounds.getWidth() - 1); float vertScale = (getHeight() - 4f) / (bounds.getHeight() - 1); int leftI = bounds.getLeft(); int ceilV = bounds.getCeil(); int rightI = bounds.getRight(); int floorV = bounds.getFloor(); int startI = buffer.getStart(); int threshV = buffer.getThreshhold(); int startX = (int)((startI - leftI) * horzScale); int leftSelI = 0; int rightSelI = 0; int leftSelX = 0; int rightSelX = 0; int threshY = (int)((ceilV - threshV) * vertScale); Color highlight = ((BevelBorder)getBorder()). getHighlightInnerColor(this); Color shadow = ((BevelBorder)getBorder()). getShadowInnerColor(this); /* Draw background */ g.setColor(Color.black); g.fillRect(0, 0, width, height); /* Draw horizontal lines */ g.setColor(new Color(0x005500)); for (int i = floorV & ~15; i <= (ceilV & ~15); i += 16) { int y = (int)((ceilV - i) * vertScale); g.drawLine(startX, y, width - 1, y); } if (startX >= 0) { g.drawLine(startX, 0, startX, height - 1); g.setColor(new Color(0x555555)); g.drawLine(startX - 2, 0, startX - 2, height - 1); } if (startX >= -3) { for (int i = floorV & ~15; i <= (ceilV & ~15); i += 16){ int y = (int)((ceilV - i) * vertScale); g.drawLine(0, y, startX - 3, y); } } /* Draw selection, part one */ if (selection.getState() > Selection.BEGIN && bounds.contains(selection)) { leftSelI = selection.getLeftmost(); rightSelI = selection.getRightmost(); leftSelX = (int)((leftSelI - leftI) * horzScale); rightSelX = (int)((rightSelI - leftI) * horzScale); boolean zoomed = selection.getState() == Selection.ZOOM; g.setColor(new Color(zoomed ? 0x550055 : 0x000055)); g.fillRect(leftSelX + 1, 0, rightSelX - leftSelX - 1, height); g.setColor(new Color(zoomed ? 0xaa00aa : 0x0000aa)); for (int i = floorV & ~15; i <= (ceilV & ~15); i += 16){ int y = (int)((ceilV - i) * vertScale); g.drawLine(leftSelX + 1, y, rightSelX - 1, y); } } /* Render baseline */ /*int baseY = (int)((ceilV - buffer.getBaseline()) * vertScale); g.setColor(Color.orange); g.drawLine(0, baseY, width - 1, baseY); */ /* Plot sample data */ int x0 = 0; int y0 = 0; int y1 = 0; float x1 = ((startI - leftI) * horzScale); for (int i = startI; i < rightI; i++, x0 = (int)x1, y0 = y1, x1 += horzScale) { int sample = buffer.getSample(i); y1 = (int)((ceilV - sample) * vertScale); if (i <= startI) continue; if ((y0 == threshY || y0 == threshY + 1) && (y1 == threshY || y1 == threshY + 1)) { /* Covered entirely by threshhold line */ } else if ((y0 >= threshY && y1 <= threshY) || (y1 >= threshY && y0 <= threshY)) { /* Crosses threshhold */ int xi = (int)(x0 - (y0 - threshY) * (x0 - x1) / (y0 - y1)); boolean up = y1 > y0; g.setColor(up ? Color.green : Color.yellow); g.drawLine(x0, y0, xi, threshY); g.setColor(up ? Color.yellow : Color.green); g.drawLine(xi, threshY, (int)x1, y1); } else if (y1 < threshY) { /* Entirely above */ g.setColor(Color.green); g.drawLine(x0, y0, (int)x1, y1); } else { /* Entirely below */ g.setColor(Color.yellow); g.drawLine(x0, y0, (int)x1, y1); } if (sample > ceilV) { /* Above screen */ g.setColor(Color.red); g.fillRect(x0, 0, (int)x1 - x0 + 1, 3); } else if (sample < floorV) { /* Below screen */ g.setColor(Color.red); g.fillRect(x0, height - 3, (int)x1 - x0 + 1, 3); } } g.setClip(-1, -1, width + 2, height + 2); /* Draw selection, part two; also draw threshhold */ if (selection.getState() > Selection.BEGIN && bounds.contains(selection)) { g.setColor(shadow); g.drawLine(leftSelX, -1, leftSelX, height + 3); g.drawLine(rightSelX + 1, -1, rightSelX + 1, height +3); g.drawLine(-1, threshY + 1, width + 3, threshY + 1); g.setColor(highlight); g.drawLine(leftSelX - 1, -1, leftSelX - 1, height + 3); g.drawLine(rightSelX, -1, rightSelX, height + 3); g.drawLine(-1, threshY, width + 3, threshY); } else { g.setColor(shadow); g.drawLine(-1, threshY + 1, width + 3, threshY + 1); g.setColor(highlight); g.drawLine(-1, threshY, width + 3, threshY); } g.setClip(0, 0, width, height); /* Draw floor and ceiling labels */ g.setColor(Color.gray); g.drawString(String.valueOf(ceilV), 0, 11); g.drawString(String.valueOf(ceilV), 1, 10); g.drawString(String.valueOf(floorV), 0, height - 2); g.drawString(String.valueOf(floorV), 1, height - 3); g.setColor(Color.darkGray); g.drawString(String.valueOf(ceilV), 1, 12); g.drawString(String.valueOf(ceilV), 2, 11); g.drawString(String.valueOf(floorV), 1, height - 1); g.drawString(String.valueOf(floorV), 2, height - 2); g.setColor(Color.white); g.drawString(String.valueOf(ceilV), 1, 11); g.drawString(String.valueOf(floorV), 1, height - 2); g.setClip(clip); g.translate(-2, -2); } /** Handler for mouse events. */ protected class MouseListener extends MouseInputAdapter { private int virgin; /* Is this a click or a drag? */ private int x0; /* Position of initial click */ private int y0; /* Position of initial click */ private Bounds oldBounds; /* Bounds before selection */ private boolean threshhold; /* Dragging the threshhold? */ private boolean threshholdDone; /* Threshhold handled? */ private int location; /* General location of click: */ /* Nowhere at all: */ private final int NA = 0; /* Relative to selection: */ private final int OUTSIDE = 1; private final int INSIDE = 2; private final int LEFT = 3; private final int RIGHT = 4; /* The threshhold line: */ private final int THRESHHOLD = 5; /** Starts the selection process. */ public void mousePressed(MouseEvent e) { requestFocus(); x0 = e.getX(); y0 = e.getY(); virgin = 3; int y = getPositionForValue(buffer.getThreshhold()); if (y0 > y - 8 && y0 < y + 8) { threshhold = true; threshholdDone = false; /* Maybe: make clicking transparent */ return; } switch (selection.getState()) { case Selection.NONE: /* * DUR. | ACTION * Click | (Un)pause * Drag | Select */ location = NA; selection.setState(Selection.BEGIN); fireStateChange(); break; case Selection.BEGIN: /* Shouldn't happen */ throw new UnsupportedOperationException( "Invalid selection state"); case Selection.SELECT: /* * DUR. | LOCATION | ACTION * Click | Outside | Cancel * Drag | Outside | Replace * Click | Edge | Zoom * Drag | Edge | Extend * Click | Inside | Zoom * Drag | Inside | Replace */ int left = getPositionForIndex( selection.getLeft()); int right = getPositionForIndex( selection.getRight()); if (x0 > left - 8 && x0 < left + 8) location = LEFT; else if (x0 > right - 8 && x0 < right + 8) location = RIGHT; else { if (left > right) { int t = left; left = right; right = t; } location = x0 <= left - 8 || x0 >= right + 8 ? OUTSIDE : INSIDE; } break; case Selection.ZOOM: /* Cancel zoom */ location = NA; } } /** Creates, replaces, or extends the selection. */ public void mouseDragged(MouseEvent e) { if (!threshhold && selection.getState() == Selection.ZOOM) return; if (virgin > 1) { virgin--; return; } if (threshhold) { threshholdDone = true; int t = getValueForPosition(e.getY()); if (t < 0) t = 0; else if (t > 255) t = 255; buffer.setThreshhold(t); repaint(); return; } int idx = getIndexForPosition(e.getX()); int oldIdx = idx; if (idx < selection.getMin()) idx = selection.getMin(); if (idx > selection.getMax()) idx = selection.getMax(); if (virgin > 0) { if (oldIdx != idx) return; /* First drag message */ if (location == NA) { /* Create selection */ oldBounds = new Bounds(bounds); selection.setState(Selection.SELECT); } if (location == NA || location == INSIDE || location == OUTSIDE) { /* Replace selection */ int idx0 = getIndexForPosition(x0); selection.setRange(idx0, idx); } if (location == NA) fireStateChange(); } /* LEFT and RIGHT: extend selection (virgin or not) */ if (location == LEFT) selection.setLeft(idx); else if (location == RIGHT) selection.setRight(idx); /* Anywhere else, not virgin: extend selection */ else if (virgin == 0) selection.extend(idx); if (virgin > 0) virgin--; fireRangeChange(); show(idx); } /** Finalizes selection. */ public void mouseReleased(MouseEvent e) { if (threshhold) { threshhold = false; /* if (threshholdDone) { threshholdDone = false; selection.setState(Selection.NONE); fireStateChange(); return; } */ return; } switch (selection.getState()) { case Selection.NONE: /* Shouldn't happen */ throw new UnsupportedOperationException( "Invalid selection state"); case Selection.BEGIN: /* Presumably toggle running */ selection.setState(Selection.NONE); fireStateChange(); break; case Selection.SELECT: if (virgin == 0) break; /* Already handled... */ switch (location) { case LEFT: case INSIDE: case RIGHT: /* Zoom */ selection.setState(Selection.ZOOM); show(selection.getLeftmost(), selection.getRightmost()); fireStateChange(); break; case OUTSIDE: /* Cancel selection */ clearSelection(); } break; case Selection.ZOOM: /* Cancel zoom */ clearSelection(); } } /** Clear the selection and/or zoom out. */ public void clearSelection() { selection.setState(Selection.NONE); if (oldBounds != null) { bounds = oldBounds; oldBounds = null; } repaint(); fireStateChange(); } } }