001package jmri.jmrix.rps.trackingpanel; 002 003import java.awt.BasicStroke; 004import java.awt.Color; 005import java.awt.Graphics; 006import java.awt.Graphics2D; 007import java.awt.Point; 008import java.awt.Shape; 009import java.awt.Stroke; 010import java.awt.event.MouseEvent; 011import java.awt.geom.AffineTransform; 012import java.awt.geom.Ellipse2D; 013import java.awt.geom.Line2D; 014import java.awt.geom.Point2D; 015import java.util.ArrayList; 016import java.util.List; 017import javax.vecmath.Point3d; 018import jmri.jmrix.rps.Distributor; 019import jmri.jmrix.rps.Engine; 020import jmri.jmrix.rps.Measurement; 021import jmri.jmrix.rps.MeasurementListener; 022import jmri.jmrix.rps.Model; 023import jmri.jmrix.rps.Receiver; 024import jmri.jmrix.rps.Region; 025import jmri.jmrix.rps.RpsSystemConnectionMemo; 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029/** 030 * 031 * Pane to show a 2D representation of the RPS Model and Measurements. 032 * 033 * @see jmri.jmrix.rps.Model 034 * @see jmri.jmrix.rps.Measurement 035 * 036 * @author Bob Jacobsen Copyright (C) 2006, 2008 037 */ 038public class RpsTrackingPanel extends javax.swing.JPanel 039 implements MeasurementListener { 040 041 RpsSystemConnectionMemo memo = null; 042 043 public RpsTrackingPanel(RpsSystemConnectionMemo _memo) { 044 super(); 045 memo = _memo; 046 Distributor.instance().addMeasurementListener(this); 047 setToolTipText("<no item>"); // activates ToolTip, sets default 048 } 049 050 public void dispose() { 051 Distributor.instance().removeMeasurementListener(this); 052 } 053 054 /** 055 * Provide tool tip text that depends on what's under the cursor. 056 * <p> 057 * Names either a measurement point or a region. 058 * 059 * @return null if no object under mouse; this suppresses ToolTip 060 */ 061 @Override 062 public String getToolTipText(MouseEvent e) { 063 // get mouse coordinates 064 try { 065 Point mouse = e.getPoint(); 066 Point2D userPt = currentAT.inverseTransform(new Point2D.Double(mouse.x, mouse.y), null); 067 // find the path object containing it, if any 068 for (int i = measurementRepList.size() - 1; i >= 0; i--) { 069 MeasurementRep r = measurementRepList.get(i); 070 if (r.contains(userPt)) { 071 Measurement m = r.measurement; 072 return "ID " + m.getId() + " at " + m.getX() + "," + m.getY(); 073 } 074 } 075 076 // find the region containing it, if any 077 // Go through backwards to find the top if overlaps 078 List<Region> l = Model.instance().getRegions(); 079 for (int i = l.size() - 1; i >= 0; i--) { 080 Shape s = l.get(i).getPath(); 081 if (s.contains(userPt)) { 082 return "Region: " + l.get(i).toString() + ", at " + userPt.getX() + "," + userPt.getY(); 083 } 084 } 085 // found nothing, just display location 086 return "" + userPt.getX() + "," + userPt.getY(); 087 } catch (Exception ex) { 088 } // just skip to default 089 // or return default 090 return null; 091 } 092 093 /** 094 * Sets the coordinates of the lower left corner of the screen/paper. 095 * Note this is different from the usual Swing coordinate system! 096 * @param x distance from left. 097 * @param y distance from bottom. 098 */ 099 public void setOrigin(double x, double y) { 100 xorigin = x; 101 yorigin = y; 102 } 103 104 void setShowErrors(boolean show) { 105 this.showErrors = show; 106 } 107 108 void setShowReceivers(boolean show) { 109 this.showReceivers = show; 110 } 111 112 void setShowRegions(boolean show) { 113 this.showRegions = show; 114 } 115 116 boolean showErrors = false; 117 boolean showReceivers = false; 118 boolean showRegions = false; 119 120 /** 121 * Sets the coordinates of the upper-right corner of the screen/paper. 122 * Note this is different from the usual Swing coordinate system! 123 * @param x distance from right. 124 * @param y distance from top. 125 */ 126 public void setCoordMax(double x, double y) { 127 xmax = x; 128 ymax = y; 129 } 130 131 double xorigin, yorigin; 132 double xmax, ymax; 133 134 static final double MEASUREMENT_ACCURACY = 0.2; // in user units 135 static final double RECEIVER_SIZE = 0.75; // in user units 136 static final Color regionFillColor = Color.GRAY.brighter(); 137 static final Color regionOutlineColor = Color.GRAY.darker(); 138 // static final Color measurementColor = new Color(0,0,0); 139 int measurementColor = 0; 140 141 // current transform to graphics coordinates 142 AffineTransform currentAT; 143 144 @Override 145 public void paint(Graphics g) { 146 // draw everything else 147 super.paint(g); 148 log.debug("paint invoked"); 149 150 // Now show regions 151 // First, Graphics2D setup 152 Graphics2D g2 = (Graphics2D) g; 153 double xscale = this.getWidth() / (xmax - xorigin); 154 double yscale = this.getHeight() / (ymax - yorigin); 155 Stroke stroke = new BasicStroke((float) (2. / xscale), 156 BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); 157 g2.setStroke(stroke); 158 // Save the current transform 159 AffineTransform saveAT = g2.getTransform(); 160 // Install the new one 161 currentAT = new AffineTransform(); 162 currentAT.translate(0, this.getHeight()); 163 currentAT.scale(xscale, -yscale); 164 currentAT.translate(-xorigin, -yorigin); // put origin in bottom corner 165 g2.setTransform(currentAT); 166 167 if (showRegions) { 168 // Draw the regions 169 List<Region> l = Model.instance().getRegions(); 170 for (int i = 0; i < l.size(); i++) { 171 g2.setPaint(regionOutlineColor); 172 g2.draw(l.get(i).getPath()); // border (same color) 173 g2.setPaint(regionFillColor); 174 g2.fill(l.get(i).getPath()); 175 } 176 } 177 178 // Draw the measurements; changes graphics 179 for (int i = 0; i < measurementRepList.size(); i++) { 180 measurementRepList.get(i).draw(g2); 181 } 182 if (showReceivers) { // draw receivers 183 for (int i = 1; i < Engine.instance().getMaxReceiverNumber() + 1; i++) { // indexed from 1 184 Receiver r = Engine.instance().getReceiver(i); 185 Point3d p = Engine.instance().getReceiverPosition(i); 186 if (p != null && r != null) { 187 if (r.isActive()) { 188 g2.setPaint(Color.BLACK); 189 } else { 190 g2.setPaint(Color.GRAY); 191 } 192 193 Shape s = new Ellipse2D.Double(p.x - RECEIVER_SIZE / 2, 194 p.y - RECEIVER_SIZE / 2, 195 RECEIVER_SIZE, RECEIVER_SIZE); 196 g2.draw(s); 197 g2.fill(s); 198 } 199 } 200 } 201 // restore original transform 202 g2.setTransform(saveAT); 203 } 204 205 ArrayList<MeasurementRep> measurementRepList = new ArrayList<MeasurementRep>(); 206 java.util.HashMap<String, TransmitterStatus> transmitters = new java.util.HashMap<String, TransmitterStatus>(); // TransmitterStatus, keyed by Integer(measurement id) 207 208 /** 209 * Pick a color for the next set of measurement lines to draw 210 * @return Color for next line chosen via algorithm 211 */ 212 Color nextColor() { 213 int red = Math.min(255, ((measurementColor >> 2) & 0x1) * 255 / 1); 214 int green = Math.min(255, ((measurementColor >> 1) & 0x1) * 255 / 1); 215 int blue = Math.min(255, ((measurementColor >> 0) & 0x1) * 255 / 1); 216 measurementColor++; 217 return new Color(red, green, blue); 218 } 219 220 @Override 221 public void notify(Measurement m) { 222 String id = m.getId(); 223 TransmitterStatus transmitter = transmitters.get(id); 224 double xend = m.getX(); 225 double yend = m.getY(); 226 log.debug("notify {},{}", xend, yend); 227 if (transmitter == null) { 228 // create Transmitter status with current measurement 229 // so we can draw line next time 230 log.debug("create new TransmitterStatus for {}", m.getId()); 231 transmitter = new TransmitterStatus(); 232 transmitter.measurement = m; 233 transmitter.color = nextColor(); 234 235 transmitters.put(id, transmitter); 236 237 // display just the point 238 MeasurementRep r = new MeasurementRep(); 239 r.color = transmitter.color; 240 r.rep1 = new Ellipse2D.Double(xend - MEASUREMENT_ACCURACY / 2, 241 yend - MEASUREMENT_ACCURACY / 2, 242 MEASUREMENT_ACCURACY, MEASUREMENT_ACCURACY); 243 r.measurement = m; 244 measurementRepList.add(r); 245 pruneMeasurementRepList(); 246 247 return; 248 } 249 Measurement lastMessage = transmitter.measurement; 250 251 MeasurementRep r = new MeasurementRep(); 252 r.color = transmitter.color; 253 r.rep2 = new Ellipse2D.Double(xend - MEASUREMENT_ACCURACY / 2, 254 yend - MEASUREMENT_ACCURACY / 2, 255 MEASUREMENT_ACCURACY, MEASUREMENT_ACCURACY); 256 257 if (showErrors || (lastMessage.isOkPoint() && m.isOkPoint())) { 258 // also draw line 259 double xinit = lastMessage.getX(); 260 double yinit = lastMessage.getY(); 261 r.rep1 = new Line2D.Double(xinit, yinit, xend, yend); 262 r.measurement = m; 263 measurementRepList.add(r); 264 pruneMeasurementRepList(); 265 // cause repaint of whole thing for now 266 repaint(getBounds()); 267 } 268 // remember where now 269 transmitter.measurement = m; 270 } 271 272 static final int MAXREPLISTSIZE = 1000; 273 274 void pruneMeasurementRepList() { 275 while (measurementRepList.size() > MAXREPLISTSIZE) { 276 measurementRepList.remove(0); 277 } 278 } 279 280 /** 281 * Clear the measurement history 282 */ 283 void clear() { 284 measurementRepList = new ArrayList<MeasurementRep>(); 285 repaint(getBounds()); 286 } 287 288 /** 289 * Simple tuple class for storing information about a single transmitter 290 * being tracked 291 */ 292 static class TransmitterStatus { 293 294 Color color; 295 Measurement measurement; // last seen location 296 } 297 298 /** 299 * Store draw representation of a measurement (set) 300 */ 301 static class MeasurementRep { 302 303 void draw(Graphics2D g2) { 304 g2.setPaint(color); 305 g2.draw(rep1); 306 if (rep2 != null) { 307 g2.draw(rep2); 308 } 309 } 310 311 boolean contains(Point2D pt) { 312 if (rep1.contains(pt)) { 313 return true; 314 } 315 if (rep2 != null && rep2.contains(pt)) { 316 return true; 317 } 318 return false; 319 } 320 Color color; 321 Shape rep1; 322 Shape rep2; 323 Measurement measurement; 324 } 325 326 private final static Logger log = LoggerFactory.getLogger(RpsTrackingPanel.class); 327 328}