001package jmri.jmrit.display; 002 003import java.awt.event.ActionEvent; 004 005import javax.annotation.Nonnull; 006import javax.swing.AbstractAction; 007import javax.swing.JCheckBoxMenuItem; 008import javax.swing.JMenuItem; 009import javax.swing.JPopupMenu; 010 011import jmri.jmrit.catalog.NamedIcon; 012import jmri.jmrix.rps.Distributor; 013import jmri.jmrix.rps.Measurement; 014import jmri.jmrix.rps.MeasurementListener; 015import jmri.util.swing.JmriJOptionPane; 016 017/** 018 * An icon to display the position of an RPS input. 019 * 020 * In this initial version, it ignores the ID, so there's only one icon. 021 * 022 * @author Bob Jacobsen Copyright (C) 2007 023 */ 024public class RpsPositionIcon extends PositionableLabel implements MeasurementListener { 025 026 public RpsPositionIcon(Editor editor) { 027 // super ctor call to make sure this is an icon label 028 super(new NamedIcon("resources/icons/smallschematics/tracksegments/circuit-error.gif", 029 "resources/icons/smallschematics/tracksegments/circuit-error.gif"), editor); 030 _control = true; 031 displayState(); 032 033 // blow up default font 034 setFont(getFont().deriveFont(24.f)); 035 036 // connect 037 Distributor.instance().addMeasurementListener(this); 038 } 039 040 // display icon for a correct reading 041 String activeName = "resources/icons/smallschematics/tracksegments/circuit-occupied.gif"; 042 NamedIcon active = new NamedIcon(activeName, activeName); 043 044 // display icon if the last reading not OK 045 String errorName = "resources/icons/smallschematics/tracksegments/circuit-error.gif"; 046 NamedIcon error = new NamedIcon(errorName, errorName); 047 048 public NamedIcon getActiveIcon() { 049 return active; 050 } 051 052 public void setActiveIcon(NamedIcon i) { 053 active = i; 054 displayState(); 055 } 056 057 public NamedIcon getErrorIcon() { 058 return error; 059 } 060 061 public void setErrorIcon(NamedIcon i) { 062 error = i; 063 displayState(); 064 } 065 066 @Override 067 @Nonnull 068 public String getTypeString() { 069 return Bundle.getMessage("PositionableType_RpsPositionIcon"); 070 } 071 072 @Override 073 public String getNameString() { 074 return "RPS Position Readout"; 075 } 076 077 @Override 078 public boolean setEditIconMenu(JPopupMenu popup) { 079 return false; 080 } 081 082 /** 083 * Pop-up contents 084 */ 085 @Override 086 public boolean showPopUp(JPopupMenu popup) { 087 088 if (showIdItem == null) { 089 showIdItem = new JCheckBoxMenuItem("Show ID"); 090 showIdItem.setSelected(false); 091 showIdItem.addActionListener(e -> toggleID(showIdItem.isSelected())); 092 } 093 popup.add(showIdItem); 094 095 popup.add(new AbstractAction("Set Origin") { 096 @Override 097 public void actionPerformed(ActionEvent e) { 098 setRpsOrigin(); 099 } 100 }); 101 102 popup.add(new AbstractAction("Set Current Location") { 103 @Override 104 public void actionPerformed(ActionEvent e) { 105 setRpsCurrentLocation(); 106 } 107 }); 108 109 notify = new Notifier(); 110 popup.add(notify); 111 112 popup.add(new AbstractAction("Set Filter") { 113 @Override 114 public void actionPerformed(ActionEvent e) { 115 setFilterPopup(); 116 } 117 }); 118 119 // add help item 120 JMenuItem item = new JMenuItem("Help"); 121 jmri.util.HelpUtil.addHelpToComponent(item, "package.jmri.jmrit.display.RpsIcon"); 122 popup.add(item); 123 124 // update position 125 notify.setPosition(getX(), getY()); 126 return false; 127 } 128 129 /** 130 * ****** popup AbstractAction.actionPerformed method overrides ******** 131 */ 132 @Override 133 protected void rotateOrthogonal() { 134 active.setRotation(active.getRotation() + 1, this); 135 error.setRotation(error.getRotation() + 1, this); 136 displayState(); 137 //bug fix, must repaint icons that have same width and height 138 repaint(); 139 } 140 141 @Override 142 public void setScale(double s) { 143 active.scale(s, this); 144 error.scale(s, this); 145 displayState(); 146 } 147 148 @Override 149 public void rotate(int deg) { 150 active.rotate(deg, this); 151 error.rotate(deg, this); 152 displayState(); 153 } 154 155 JCheckBoxMenuItem showIdItem = null; 156 157 /** 158 * Internal class to show position in the popup menu. 159 * <p> 160 * This is updated before the menu is shown, and then appears in the menu. 161 */ 162 class Notifier extends AbstractAction { 163 164 public Notifier() { 165 super(); 166 } 167 168 /** 169 * Does nothing, here to make this work 170 */ 171 @Override 172 public void actionPerformed(ActionEvent e) { 173 } 174 175 /** 176 * 177 * @param x display coordinate 178 * @param y display coordinate 179 */ 180 void setPosition(int x, int y) { 181 // convert to RPS coordinates 182 double epsilon = .00001; 183 if ((sxScale > -epsilon && sxScale < epsilon) 184 || (syScale > -epsilon && syScale < epsilon)) { 185 putValue("Name", "Not Calibrated"); 186 return; 187 } 188 189 double xn = (x - sxOrigin) / sxScale; 190 double yn = (y - syOrigin) / syScale; 191 192 putValue("Name", "At: " + xn + "," + yn); 193 } 194 } 195 Notifier notify; 196 197 JCheckBoxMenuItem momentaryItem; 198 199 // true if valid message received last 200 boolean state = false; 201 202 /** 203 * Drive the current state of the display from whether a valid measurement 204 * has been received 205 */ 206 void displayState() { 207 208 if (state) { 209 if (isIcon()) { 210 super.setIcon(active); 211 } 212 } else { 213 if (isIcon()) { 214 super.setIcon(error); 215 } 216 } 217 218 updateSize(); 219 revalidate(); 220 } 221 222 @Override 223 public int maxHeight() { 224 return getPreferredSize().height; 225 } 226 227 @Override 228 public int maxWidth() { 229 return getPreferredSize().width; 230 } 231 232 boolean momentary = false; 233 234 public boolean getMomentary() { 235 return momentary; 236 } 237 238 public void setMomentary(boolean m) { 239 momentary = m; 240 } 241 242 void toggleID(boolean value) { 243 if (value) { 244 _text = true; 245 } else { 246 247 _text = false; 248 setText(null); 249 } 250 displayState(); 251 } 252 253 public boolean isShowID() { 254 return _text; 255 } 256 257 public void setShowID(boolean mode) { 258 _text = mode; 259 displayState(); 260 } 261 262 /** 263 * Respond to a measurement by moving to new position 264 */ 265 @Override 266 public void notify(Measurement m) { 267 // only honor measurements to this icon if filtered 268 if (filterNumber != null && m.getReading() != null 269 && !filterNumber.equals(m.getReading().getId())) { 270 return; 271 } 272 273 // remember this measurement for last position, e.g. for 274 // alignment 275 lastMeasurement = m; 276 277 // update state based on if valid measurement, fiducial volume 278 if (!m.isOkPoint() || m.getZ() < -20 || m.getZ() > 20) { 279 state = false; 280 } else { 281 state = true; 282 } 283 284 if (_text) { 285 super.setText("" + m.getReading().getId()); 286 } 287 displayState(); 288 289 // if the state is bad, leave icon in last position 290 if (!state) { 291 return; 292 } 293 294 // Do a 2D, no-rotation conversion using the saved constants. 295 // xn, yn are the RPS coordinates; x, y are the display coordinates. 296 double xn = m.getX(); 297 double yn = m.getY(); 298 299 int x = sxOrigin + (int) (sxScale * xn); 300 int y = syOrigin + (int) (syScale * yn); 301 302 // and set position 303 setLocation(x, y); 304 } 305 306 public void setFilterPopup() { 307 // Popup menu has trigger request for filter value 308 String inputValue = JmriJOptionPane.showInputDialog(null, "Please enter a filter value", ""); 309 if (inputValue == null) { 310 return; // cancelled 311 } 312 setFilter(inputValue); 313 } 314 315 public void setFilter(String val) { 316 filterNumber = val; 317 } 318 319 public String getFilter() { 320 return filterNumber; 321 } 322 String filterNumber = null; 323 324 @Override 325 public void dispose() { 326 Distributor.instance().removeMeasurementListener(this); 327 active = null; 328 error = null; 329 330 super.dispose(); 331 } 332 333 /** 334 * Set the current icon position as the origin (0,0) of the RPS space. 335 */ 336 public void setRpsOrigin() { 337 sxOrigin = getX(); 338 syOrigin = getY(); 339 } 340 341 public double getXScale() { 342 return sxScale; 343 } 344 345 public double getYScale() { 346 return syScale; 347 } 348 349 public int getXOrigin() { 350 return sxOrigin; 351 } 352 353 public int getYOrigin() { 354 return syOrigin; 355 } 356 357 public void setTransform(double sxScale, double syScale, int sxOrigin, int syOrigin) { 358 this.sxScale = sxScale; 359 this.syScale = syScale; 360 this.sxOrigin = sxOrigin; 361 this.syOrigin = syOrigin; 362 } 363 364 /** 365 * Matches the icon position on the screen to its position in the RPS 366 * coordinate system. 367 * <p> 368 * Typically invoked from the popup menu, you move the icon (e.g. via drag 369 * and drop) to the correct position on the screen for its current measured 370 * position, and then invoke this method. 371 * <p> 372 * Requires the origin to have been set, and some other measurement to have 373 * been made (and current). 374 */ 375 public void setRpsCurrentLocation() { 376 if (lastMeasurement == null) { 377 return; 378 } 379 380 if (sxOrigin == getX()) { 381 return; 382 } 383 if (syOrigin == getY()) { 384 return; 385 } 386 // if (lastMeasurement.getX()<10. && lastMeasurement.getX()>-10) return; 387 // if (lastMeasurement.getY()<10. && lastMeasurement.getY()>-10) return; 388 389 sxScale = (getX() - sxOrigin) / lastMeasurement.getX(); 390 syScale = (getY() - syOrigin) / lastMeasurement.getY(); 391 } 392 393 // store coordinate system information 394 Measurement lastMeasurement; 395 396 double sxScale, syScale; 397 int sxOrigin, syOrigin; 398}