001package jmri.jmrit.display; 002 003import java.awt.event.ActionListener; 004import java.util.ArrayList; 005import java.util.HashMap; 006import java.util.Map.Entry; 007 008import javax.annotation.Nonnull; 009 010import jmri.InstanceManager; 011import jmri.NamedBeanHandle; 012import jmri.NamedBeanHandleManager; 013import jmri.Sensor; 014import jmri.Turnout; 015import jmri.jmrit.catalog.NamedIcon; 016import jmri.jmrit.display.palette.IndicatorTOItemPanel; 017import jmri.jmrit.logix.OBlock; 018import jmri.jmrit.picker.PickListModel; 019import jmri.util.ThreadingUtil; 020 021import org.slf4j.Logger; 022import org.slf4j.LoggerFactory; 023 024/** 025 * An icon to display a status and state of a color coded turnout.<p> 026 * This responds to only KnownState, leaving CommandedState to some other 027 * graphic representation later. 028 * <p> 029 * "state" is the state of the underlying turnout ("closed", "thrown", etc.) 030 * <p> 031 * "status" is the operating condition of the track ("clear", "occupied", etc.) 032 * <p> 033 * A click on the icon will command a state change. Specifically, it will set 034 * the CommandedState to the opposite (THROWN vs CLOSED) of the current 035 * KnownState. This will display the setting of the turnout points. 036 * <p> 037 * The status is indicated by color and changes are done only done by the 038 * occupancy sensing - OBlock or other sensor. 039 * <p> 040 * The default icons are for a left-handed turnout, facing point for east-bound 041 * traffic. 042 * 043 * @author Bob Jacobsen Copyright (c) 2002 044 * @author Pete Cressman Copyright (c) 2010 2012 045 */ 046public class IndicatorTurnoutIcon extends TurnoutIcon implements IndicatorTrack { 047 048 HashMap<String, HashMap<Integer, NamedIcon>> _iconMaps; 049 050 private NamedBeanHandle<Sensor> namedOccSensor = null; 051 private NamedBeanHandle<OBlock> namedOccBlock = null; 052 053 private IndicatorTrackPaths _pathUtil; 054 private IndicatorTOItemPanel _itemPanel; 055 private String _status; 056 057 public IndicatorTurnoutIcon(Editor editor) { 058 super(editor); 059 log.debug("IndicatorTurnoutIcon ctor: isIcon()= {}, isText()= {}", isIcon(), isText()); 060 _pathUtil = new IndicatorTrackPaths(); 061 _status = "DontUseTrack"; 062 _iconMaps = initMaps(); 063 064 } 065 066 static HashMap<String, HashMap<Integer, NamedIcon>> initMaps() { 067 HashMap<String, HashMap<Integer, NamedIcon>> iconMaps = new HashMap<>(); 068 iconMaps.put("ClearTrack", new HashMap<>()); 069 iconMaps.put("OccupiedTrack", new HashMap<>()); 070 iconMaps.put("PositionTrack", new HashMap<>()); 071 iconMaps.put("AllocatedTrack", new HashMap<>()); 072 iconMaps.put("DontUseTrack", new HashMap<>()); 073 iconMaps.put("ErrorTrack", new HashMap<>()); 074 return iconMaps; 075 } 076 077 HashMap<String, HashMap<Integer, NamedIcon>> cloneMaps(IndicatorTurnoutIcon pos) { 078 HashMap<String, HashMap<Integer, NamedIcon>> iconMaps = initMaps(); 079 for (Entry<String, HashMap<Integer, NamedIcon>> entry : _iconMaps.entrySet()) { 080 HashMap<Integer, NamedIcon> clone = iconMaps.get(entry.getKey()); 081 for (Entry<Integer, NamedIcon> ent : entry.getValue().entrySet()) { 082 // if (log.isDebugEnabled()) log.debug("key= "+ent.getKey()); 083 clone.put(ent.getKey(), cloneIcon(ent.getValue(), pos)); 084 } 085 } 086 return iconMaps; 087 } 088 089 @Override 090 public Positionable deepClone() { 091 IndicatorTurnoutIcon pos = new IndicatorTurnoutIcon(_editor); 092 return finishClone(pos); 093 } 094 095 protected Positionable finishClone(IndicatorTurnoutIcon pos) { 096 pos.setOccBlockHandle(namedOccBlock); 097 pos.setOccSensorHandle(namedOccSensor); 098 pos._iconMaps = cloneMaps(pos); 099 pos._pathUtil = _pathUtil.deepClone(); 100 pos._iconFamily = _iconFamily; 101 return super.finishClone(pos); 102 } 103 104 public HashMap<String, HashMap<Integer, NamedIcon>> getIconMaps() { 105 return new HashMap<>(_iconMaps); 106 } 107 108 /** 109 * Attached a named sensor to display status from OBlocks 110 * 111 * @param pName Used as a system/user name to lookup the sensor object 112 */ 113 @Override 114 public void setOccSensor(String pName) { 115 if (pName == null || pName.trim().length() == 0) { 116 setOccSensorHandle(null); 117 return; 118 } 119 if (InstanceManager.getNullableDefault(jmri.SensorManager.class) != null) { 120 try { 121 Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(pName); 122 setOccSensorHandle(jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(pName, sensor)); 123 } catch (IllegalArgumentException ex) { 124 log.error("Occupancy Sensor '{}' not available, icon won't see changes", pName); 125 } 126 } else { 127 log.error("No SensorManager for this protocol, block icons won't see changes"); 128 } 129 } 130 131 @Override 132 public void setOccSensorHandle(NamedBeanHandle<Sensor> sen) { 133 if (namedOccSensor != null) { 134 getOccSensor().removePropertyChangeListener(this); 135 } 136 namedOccSensor = sen; 137 if (namedOccSensor != null) { 138 Sensor sensor = getOccSensor(); 139 sensor.addPropertyChangeListener(this, namedOccSensor.getName(), "Indicator Turnout Icon"); 140 _status = _pathUtil.getStatus(sensor.getKnownState()); 141 if (_iconMaps != null) { 142 displayState(turnoutState()); 143 } 144 } 145 } 146 147 @Override 148 public Sensor getOccSensor() { 149 if (namedOccSensor == null) { 150 return null; 151 } 152 return namedOccSensor.getBean(); 153 } 154 155 @Override 156 public NamedBeanHandle<Sensor> getNamedOccSensor() { 157 return namedOccSensor; 158 } 159 160 /** 161 * Attached a named OBlock to display status 162 * 163 * @param pName Used as a system/user name to lookup the OBlock object 164 */ 165 @Override 166 public void setOccBlock(String pName) { 167 if (pName == null || pName.trim().length() == 0) { 168 setOccBlockHandle(null); 169 return; 170 } 171 OBlock block = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getOBlock(pName); 172 if (block != null) { 173 setOccBlockHandle(InstanceManager.getDefault(NamedBeanHandleManager.class) 174 .getNamedBeanHandle(pName, block)); 175 } else { 176 log.error("Detection OBlock '{}' not available, icon won't see changes", pName); 177 } 178 } 179 180 @Override 181 public void setOccBlockHandle(NamedBeanHandle<OBlock> blockHandle) { 182 if (namedOccBlock != null) { 183 getOccBlock().removePropertyChangeListener(this); 184 } 185 namedOccBlock = blockHandle; 186 if (namedOccBlock != null) { 187 OBlock block = getOccBlock(); 188 block.addPropertyChangeListener(this, namedOccBlock.getName(), "Indicator Turnout Icon"); 189 setStatus(block, block.getState()); 190 if (_iconMaps != null) { 191 displayState(turnoutState()); 192 } 193 setToolTip(new ToolTip(block.getDescription(), 0, 0, this)); 194 } else { 195 setToolTip(new ToolTip(null, 0, 0, this)); 196 } 197 } 198 199 @Override 200 public OBlock getOccBlock() { 201 if (namedOccBlock == null) { 202 return null; 203 } 204 return namedOccBlock.getBean(); 205 } 206 207 @Override 208 public NamedBeanHandle<OBlock> getNamedOccBlock() { 209 return namedOccBlock; 210 } 211 212 @Override 213 public void setShowTrain(boolean set) { 214 _pathUtil.setShowTrain(set); 215 } 216 217 @Override 218 public boolean showTrain() { 219 return _pathUtil.showTrain(); 220 } 221 222 @Override 223 public ArrayList<String> getPaths() { 224 return _pathUtil.getPaths(); 225 } 226 227 public void setPaths(ArrayList<String> paths) { 228 _pathUtil.setPaths(paths); 229 } 230 231 @Override 232 public void addPath(String path) { 233 _pathUtil.addPath(path); 234 } 235 236 @Override 237 public void removePath(String path) { 238 _pathUtil.removePath(path); 239 } 240 241 /** 242 * get track name for known state of occupancy sensor 243 */ 244 @Override 245 public void setStatus(int state) { 246 _status = _pathUtil.getStatus(state); 247 } 248 249 /** 250 * Place icon by its localized bean state name 251 * 252 * @param status the track condition of the icon 253 * @param stateName NamedBean name of turnout state 254 * @param icon icon corresponding to status and state 255 */ 256 public void setIcon(String status, String stateName, NamedIcon icon) { 257 if (log.isDebugEnabled()) { 258 log.debug("setIcon for status \"{}\", stateName= \"{} icom= {}", status, stateName, icon.getURL()); 259 } 260// ") state= "+_name2stateMap.get(stateName)+ 261// " icon: w= "+icon.getIconWidth()+" h= "+icon.getIconHeight()); 262 if (_iconMaps == null) { 263 _iconMaps = initMaps(); 264 } 265 _iconMaps.get(status).put(_name2stateMap.get(stateName), icon); 266 setIcon(_iconMaps.get("ClearTrack").get(_name2stateMap.get("BeanStateInconsistent"))); 267 } 268 269 /* 270 * Get icon by its localized bean state name 271 */ 272 public NamedIcon getIcon(String status, int state) { 273 log.debug("getIcon: status= {}, state= {}", status, state); 274 HashMap<Integer, NamedIcon> map = _iconMaps.get(status); 275 if (map == null) { 276 return null; 277 } 278 return map.get(state); 279 } 280 281 public String getStateName(Integer state) { 282 return _state2nameMap.get(state); 283 } 284 285 public String getStatus() { 286 return _status; 287 } 288 289 @Override 290 public int maxHeight() { 291 int max = 0; 292 if (_iconMaps != null) { 293 for (HashMap<Integer, NamedIcon> integerNamedIconHashMap : _iconMaps.values()) { 294 for (NamedIcon namedIcon : integerNamedIconHashMap.values()) { 295 max = Math.max(namedIcon.getIconHeight(), max); 296 } 297 } 298 } 299 return max; 300 } 301 302 @Override 303 public int maxWidth() { 304 int max = 0; 305 if (_iconMaps != null) { 306 for (HashMap<Integer, NamedIcon> integerNamedIconHashMap : _iconMaps.values()) { 307 for (NamedIcon namedIcon : integerNamedIconHashMap.values()) { 308 max = Math.max(namedIcon.getIconWidth(), max); 309 } 310 } 311 } 312 return max; 313 } 314 315 /** 316 * ****** popup AbstractAction.actionPerformed method overrides ******** 317 */ 318 @Override 319 protected void rotateOrthogonal() { 320 if (_iconMaps != null) { 321 for (HashMap<Integer, NamedIcon> integerNamedIconHashMap : _iconMaps.values()) { 322 for (NamedIcon icon : integerNamedIconHashMap.values()) { 323 icon.setRotation(icon.getRotation() + 1, this); 324 } 325 } 326 } 327 displayState(turnoutState()); 328 } 329 330 @Override 331 public void setScale(double s) { 332 _scale = s; 333 if (_iconMaps != null) { 334 for (HashMap<Integer, NamedIcon> integerNamedIconHashMap : _iconMaps.values()) { 335 for (NamedIcon namedIcon : integerNamedIconHashMap.values()) { 336 namedIcon.scale(s, this); 337 } 338 } 339 } 340 displayState(turnoutState()); 341 } 342 343 @Override 344 public void rotate(int deg) { 345 if (_iconMaps != null) { 346 for (HashMap<Integer, NamedIcon> integerNamedIconHashMap : _iconMaps.values()) { 347 for (NamedIcon namedIcon : integerNamedIconHashMap.values()) { 348 namedIcon.rotate(deg, this); 349 } 350 } 351 } 352 setDegrees(deg %360); 353 displayState(turnoutState()); 354 } 355 356 /** 357 * Drive the current state of the display from the state of the turnout and 358 * status of track. 359 */ 360 @Override 361 public void displayState(int state) { 362 if (getNamedTurnout() == null) { 363 log.debug("Display state {}, disconnected", state); 364 } else { 365 if (_status != null && _iconMaps != null) { 366 NamedIcon icon = getIcon(_status, state); 367 if (icon != null) { 368 super.setIcon(icon); 369 } 370 } 371 } 372 super.displayState(state); 373 updateSize(); 374 } 375 376 @Override 377 @Nonnull 378 public String getTypeString() { 379 return Bundle.getMessage("PositionableType_IndicatorTurnoutIcon"); 380 } 381 382 @Override 383 public String getNameString() { 384 String str = ""; 385 if (namedOccBlock != null) { 386 str = " in " + namedOccBlock.getBean().getDisplayName(); 387 } else if (namedOccSensor != null) { 388 str = " on " + namedOccSensor.getBean().getDisplayName(); 389 } 390 return "ITrack " + super.getNameString() + str; 391 } 392 393 // update icon as state of turnout changes and status of track changes 394 // Override 395 @Override 396 public void propertyChange(java.beans.PropertyChangeEvent evt) { 397 if (log.isDebugEnabled()) { 398 log.debug("property change: {} property \"{}\"= {} from {}", getNameString(), evt.getPropertyName(), evt.getNewValue(), evt.getSource().getClass().getName()); 399 } 400 401 Object source = evt.getSource(); 402 if (source instanceof Turnout) { 403 super.propertyChange(evt); 404 } else if (source instanceof OBlock) { 405 String property = evt.getPropertyName(); 406 if ("state".equals(property) || "pathState".equals(property)) { 407 int now = (Integer) evt.getNewValue(); 408 setStatus((OBlock) source, now); 409 } else if ("pathName".equals(property)) { 410 _pathUtil.removePath((String) evt.getOldValue()); 411 _pathUtil.addPath((String) evt.getNewValue()); 412 } 413 } else if (source instanceof Sensor) { 414 if (evt.getPropertyName().equals("KnownState")) { 415 int now = (Integer) evt.getNewValue(); 416 if (source.equals(getOccSensor())) { 417 _status = _pathUtil.getStatus(now); 418 } 419 } 420 } 421 displayState(turnoutState()); 422 } 423 424 private void setStatus(OBlock block, int state) { 425 _status = _pathUtil.getStatus(block, state); 426 log.debug("setStatus _status= {} state= {} block= \"{}\"", _status, state, block.getDisplayName()); 427 if ((state & (OBlock.OCCUPIED | OBlock.RUNNING)) != 0) { 428 ThreadingUtil.runOnLayoutEventually(() -> { 429 _pathUtil.setLocoIcon(block, getLocation(), getSize(), _editor); 430 repaint(); 431 }); 432 } 433 if ((block.getState() & OBlock.OUT_OF_SERVICE) != 0) { 434 setControlling(false); 435 } else { 436 setControlling(true); 437 } 438 } 439 440 @Override 441 protected void editItem() { 442 _paletteFrame = makePaletteFrame(java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("IndicatorTO"))); 443 _itemPanel = new IndicatorTOItemPanel(_paletteFrame, "IndicatorTO", _iconFamily, 444 PickListModel.turnoutPickModelInstance()); 445 ActionListener updateAction = a -> updateItem(); 446 // Convert _iconMaps state (ints) to Palette's bean names 447 HashMap<String, HashMap<String, NamedIcon>> iconMaps 448 = new HashMap<>(); 449 iconMaps.put("ClearTrack", new HashMap<>()); 450 iconMaps.put("OccupiedTrack", new HashMap<>()); 451 iconMaps.put("PositionTrack", new HashMap<>()); 452 iconMaps.put("AllocatedTrack", new HashMap<>()); 453 iconMaps.put("DontUseTrack", new HashMap<>()); 454 iconMaps.put("ErrorTrack", new HashMap<>()); 455 for (Entry<String, HashMap<Integer, NamedIcon>> entry : _iconMaps.entrySet()) { 456 HashMap<String, NamedIcon> clone = iconMaps.get(entry.getKey()); 457 for (Entry<Integer, NamedIcon> ent : entry.getValue().entrySet()) { 458 NamedIcon oldIcon = ent.getValue(); 459 NamedIcon newIcon = cloneIcon(oldIcon, this); 460 newIcon.rotate(0, this); 461 newIcon.scale(1.0, this); 462 newIcon.setRotation(4, this); 463 clone.put(_state2nameMap.get(ent.getKey()), newIcon); 464 } 465 } 466 _itemPanel.initUpdate(updateAction, iconMaps); 467 468 if (namedOccSensor != null) { 469 _itemPanel.setOccDetector(namedOccSensor.getBean().getDisplayName()); 470 } 471 if (namedOccBlock != null) { 472 _itemPanel.setOccDetector(namedOccBlock.getBean().getDisplayName()); 473 } 474 _itemPanel.setShowTrainName(_pathUtil.showTrain()); 475 _itemPanel.setPaths(_pathUtil.getPaths()); 476 _itemPanel.setSelection(getTurnout()); // do after all other params set - calls resize() 477 478 initPaletteFrame(_paletteFrame, _itemPanel); 479 } 480 481 @Override 482 void updateItem() { 483 if (log.isDebugEnabled()) { 484 log.debug("updateItem: {} family= {}", getNameString(), _itemPanel.getFamilyName()); 485 } 486 setTurnout(_itemPanel.getTableSelection().getSystemName()); 487 setOccSensor(_itemPanel.getOccSensor()); 488 setOccBlock(_itemPanel.getOccBlock()); 489 _pathUtil.setShowTrain(_itemPanel.getShowTrainName()); 490 _iconFamily = _itemPanel.getFamilyName(); 491 _pathUtil.setPaths(_itemPanel.getPaths()); 492 HashMap<String, HashMap<String, NamedIcon>> iconMap = _itemPanel.getIconMaps(); 493 if (iconMap != null) { 494 for (Entry<String, HashMap<String, NamedIcon>> entry : iconMap.entrySet()) { 495 String status = entry.getKey(); 496 HashMap<Integer, NamedIcon> oldMap = _iconMaps.get(entry.getKey()); 497 for (Entry<String, NamedIcon> ent : entry.getValue().entrySet()) { 498 if (log.isDebugEnabled()) { 499 log.debug("key= {}", ent.getKey()); 500 } 501 NamedIcon newIcon = cloneIcon(ent.getValue(), this); 502 NamedIcon oldIcon = oldMap.get(_name2stateMap.get(ent.getKey())); 503 newIcon.setLoad(oldIcon.getDegrees(), oldIcon.getScale(), this); 504 newIcon.setRotation(oldIcon.getRotation(), this); 505 setIcon(status, ent.getKey(), newIcon); 506 } 507 } 508 } // otherwise retain current map 509 finishItemUpdate(_paletteFrame, _itemPanel); 510 displayState(turnoutState()); 511 } 512 513 @Override 514 public void dispose() { 515 if (namedOccSensor != null) { 516 getOccSensor().removePropertyChangeListener(this); 517 } 518 if (namedOccBlock != null) { 519 getOccBlock().removePropertyChangeListener(this); 520 } 521 namedOccSensor = null; 522 super.dispose(); 523 } 524 525 private final static Logger log = LoggerFactory.getLogger(IndicatorTurnoutIcon.class); 526}