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