//Kenneth J. Goldman //March 2000, Modified October 2003 //All rights reserved. package canvas; import java.util.*; import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import javax.swing.*; import javax.swing.Timer; import javax.swing.border.*; import java.lang.reflect.*; public class DataView extends JPanel { static HashMap views = new HashMap(); static DataView lastAdded; static final int SPACING = 35; static final Color DEFAULT_COLOR = new Color(255, 255, 255, 80); Object model; JFrame frame; Field[] fields; JLabel[] fieldNames; JLabel[] fieldValues; Object[] fieldObjects; PointerLine[] pointerLines; public DataView(Object model, JFrame frame) { this.model = model; this.frame = frame; try { synchronized (views) { fields = model.getClass().getDeclaredFields(); // ****** Get the fields setLayout(new GridBagLayout()); setBorder(new BevelBorder(BevelBorder.RAISED)); setBackground(DEFAULT_COLOR); views.put(model, this); createFieldNamesAndViews(); // ****** Fill in the labels by reflection computeLocation(); lastAdded = this; frame.add(this); frame.add(this); createPointers(); frame.repaint(); startUpdateTimer(); addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) { Rectangle r = getBounds(); int deltax = e.getX() - r.width / 2; int deltay = e.getY() - r.height / 2; setLocation(r.x + deltax, r.y + deltay); } }); } } catch (Exception e) { System.out.println( "CS101Canvas showData could not show data of type " + model.getClass().getName() + " due to " + e); e.printStackTrace(); } } public static void reset() { lastAdded = null; } boolean showAsText(Class c) { return c.isPrimitive() || c == String.class || Number.class.isAssignableFrom(c) || Boolean.class.isAssignableFrom(c); } void createFieldNamesAndViews() { //initialization: fieldNames = new JLabel[fields.length]; fieldValues = new JLabel[fields.length]; fieldObjects = new Object[fields.length]; pointerLines = new PointerLine[fields.length]; //layout setup and class name label: GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; gbc.anchor = GridBagConstraints.CENTER; JLabel className = new JLabel(model.getClass().getName()); className.setForeground(Color.black); add(className, gbc); if (showAsText(model.getClass())) { gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 2; gbc.anchor = GridBagConstraints.WEST; add(new JLabel(" value: " + model), gbc); } else // **** Consider each field in the model object for (int i = 0; i < fields.length; i++) { fieldNames[i] = new JLabel(" " + fields[i].getName() + ": "); gbc.gridx = 0; gbc.gridy = i + 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.WEST; add(fieldNames[i], gbc); fields[i].setAccessible(true); // **** Ensure that we can read the field if (showAsText(fields[i].getType())) { try { fieldObjects[i] = fields[i].get(model); fieldValues[i] = new JLabel("" + fieldObjects[i]); } catch (IllegalAccessException iae) { fieldValues[i] = new JLabel("(unavailable)"); } } else { // Show as a pointer (* and arrow) fieldValues[i] = new JLabel("*"); } gbc.gridx = 1; add(fieldValues[i], gbc); // add the label to the right of the name } } void createPointers() { if (!showAsText(model.getClass())) for (int i = 0; i < fields.length; i++) if (!showAsText(fields[i].getType())) pointerLines[i] = new PointerLine(i); } void updateSize() { setSize(getPreferredSize()); validate(); repaint(); } public void paint(Graphics g) { g.setColor(Color.WHITE); g.fillRect(0, 0, getWidth(), getHeight()); super.paint(g); } void startUpdateTimer() { Timer timer = new Timer(200, new ActionListener() { public void actionPerformed(ActionEvent arg0) { for (int i = 0; i < fields.length; i++) { if (showAsText(fields[i].getType())) { try { Object x = fields[i].get(model); Object old = fieldObjects[i]; if ((x == null && old != null) || (x != null && !x.equals(fieldObjects[i]))) { fieldObjects[i] = x; fieldValues[i].setText("" + x); updateSize(); } } catch (IllegalAccessException e) { // ignore exception... already shown } } else if (pointerLines[i] != null) { pointerLines[i].updatePosition(); } } } }); timer.start(); } void computeLocation() { // arrange the views in rows left to right if (lastAdded == null) { setLocation(SPACING, SPACING); } else { Rectangle r = lastAdded.getBounds(); Dimension d = getPreferredSize(); Dimension boundary = frame.getSize(); if (r.x + r.width + SPACING + d.width < boundary.width) setLocation(r.x + r.width + SPACING, r.y); else setLocation(SPACING, r.y + r.height + SPACING); } } // void computeLocation() { // arrange the views in rows right to left // Dimension boundary = screen.getSize(); // Dimension d = getPreferredSize(); // int x = boundary.width - d.width - SPACING; // int y = SPACING; // if (lastAdded != null) { // Rectangle r = lastAdded.getBounds(); // x = r.x - d.width - SPACING; // y = r.y; // if (x < 0) { // go to the next line // x = boundary.width - d.width - SPACING; // y = r.y + r.height + SPACING; // } // } // setLocation(x, y); // } class ArrowComponent extends ShapeComponent { static final int HEAD_LENGTH = 15; static final int HEAD_HALF_WIDTH = 5; int oldX1, oldY1, oldX2, oldY2; Line2D.Double theLine; Polygon head; ArrowComponent(Line2D.Double line) { super(line); theLine = line; } public synchronized boolean setLine(int x1, int y1, int x2, int y2) { if (x1 != oldX1 || x2 != oldX2 || y1 != oldY1 || y2 != oldY2) { oldX1 = x1; oldX2 = x2; oldY1 = y1; oldY2 = y2; theLine.setLine(x1, y1, x2, y2); head = null; if (x1 != x2 || y1 != y2) { head = new Polygon(); double rise = y2 - y1; double run = x2 - x1; double length = Math.sqrt(rise * rise + run * run); double slope = rise / run; double normalSlope = -1 / slope; double alpha = Math.atan(normalSlope); double xint = x2 - run * (HEAD_LENGTH / length); double yint = y2 - rise * (HEAD_LENGTH / length); double deltaX = HEAD_HALF_WIDTH * Math.cos(alpha); double deltaY = HEAD_HALF_WIDTH * Math.sin(alpha); int px = (int) Math.rint(xint + deltaX); int py = (int) Math.rint(yint + deltaY); int qx = (int) Math.rint(xint - deltaX); int qy = (int) Math.rint(yint - deltaY); head.addPoint(x2, y2); head.addPoint(px, py); head.addPoint(qx, qy); } updateBounds(); return true; } return false; } synchronized void updateBounds() { if (head != null) { Rectangle union = theLine.getBounds().union(head.getBounds()); this.setBounds(union); } else super.updateBounds(); } public void paint(Graphics g) { Graphics2D g2 = (Graphics2D) g; // For smoother lines: g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setColor(this.getForeground()); g2.translate(-this.getBounds().x, -this.getBounds().y); g2.draw(getShape()); if (head != null) g2.fill(head); } } class PointerLine extends ArrowComponent { int index; PointerLine(int index) { super(new Line2D.Double(0, 0, 0, 0)); this.index = index; updatePosition(); frame.add(this); } void updatePosition() { Point start, end; start = fieldValues[index].getLocation(); start = new Point( start.x + DataView.this.getLocation().x, start.y + DataView.this.getLocation().y + fieldValues[index].getSize().height / 2); try { synchronized (views) { Object dest = fields[index].get(model); if (dest == null) { end = start; if (!fieldValues[index].getText().equals("null")) { fieldValues[index].setText("null"); updateSize(); } } else { if (!fieldValues[index].getText().equals("*")) { fieldValues[index].setText("*"); updateSize(); } if (!views.containsKey(dest)) new DataView(dest, frame); JComponent destView = (JComponent) views.get(dest); end = closestPointOnBoundary(destView.getBounds(), start); } // System.out.println("start=" + start + " end=" + end); if (setLine(start.x, start.y, end.x, end.y)) frame.add(this); } } catch (IllegalAccessException iae) { fieldValues[index].setText("(not public)"); } catch (Exception e) { System.out.println("Updating position... " + e); } } Point closestPointOnBoundary(Rectangle r, Point p) { Point center = new Point(r.x + r.width / 2, r.y + r.height / 2); int outcode = r.outcode(p.x, p.y); if ((outcode & Rectangle2D.OUT_LEFT) != 0) center.x = r.x; else if ((outcode & Rectangle2D.OUT_RIGHT) != 0) center.x = r.x + r.width; if ((outcode & Rectangle2D.OUT_TOP) != 0) center.y = r.y; else if ((outcode & Rectangle2D.OUT_BOTTOM) != 0) center.y = r.y + r.height; return center; } } }