001package jmri.jmrit.display; 002 003import java.awt.event.ActionEvent; 004import java.awt.event.ActionListener; 005import java.util.ArrayList; 006import java.util.HashMap; 007import java.util.Map.Entry; 008import javax.annotation.Nonnull; 009import javax.swing.JPopupMenu; 010import jmri.InstanceManager; 011import jmri.NamedBeanHandle; 012import jmri.NamedBeanHandleManager; 013import jmri.Sensor; 014import jmri.jmrit.catalog.NamedIcon; 015import jmri.jmrit.display.palette.IndicatorItemPanel; 016import jmri.jmrit.logix.OBlock; 017import jmri.util.ThreadingUtil; 018 019/** 020 * An icon to display the status of a track segment in a block. 021 * <p> 022 * This responds to the following conditions: 023 * <ol> 024 * <li>KnownState of an occupancy sensor of the block where the track segment appears 025 * <li>Allocation of a route by a Warrant where the track segment appears 026 * <li>Current position of a train being run under a Warrant where the track segment 027 * appears in a block of the route 028 * <li>Out of Service for a block that cannot or should not be used 029 * <li>An error state of the block where the track segment appears (short/no power 030 * etc.) 031 * </ol> 032 * A click on the icon does not change any of the above conditions. 033 * 034 * @author Pete Cressman Copyright (c) 2010 035 */ 036public class IndicatorTrackIcon extends PositionableIcon 037 implements java.beans.PropertyChangeListener, IndicatorTrack { 038 039 private NamedBeanHandle<Sensor> namedOccSensor = null; 040 private NamedBeanHandle<OBlock> namedOccBlock = null; 041 042 private IndicatorTrackPaths _pathUtil; 043 private IndicatorItemPanel _trackPanel; 044 private String _status; // is a key for _iconMap 045 046 public IndicatorTrackIcon(Editor editor) { 047 // super ctor call to make sure this is an icon label 048 super(editor); 049 _pathUtil = new IndicatorTrackPaths(); 050 _status = "ClearTrack"; 051 _iconMap = new HashMap<>(); 052 } 053 054 @Override 055 @Nonnull 056 public Positionable deepClone() { 057 IndicatorTrackIcon pos = new IndicatorTrackIcon(_editor); 058 return finishClone(pos); 059 } 060 061 protected Positionable finishClone(IndicatorTrackIcon pos) { 062 pos.setOccSensorHandle(namedOccSensor); 063 pos.setOccBlockHandle(namedOccBlock); 064 pos._iconMap = cloneMap(_iconMap, pos); 065 pos._pathUtil = _pathUtil.deepClone(); 066 pos._iconFamily = _iconFamily; 067 pos._namedIcon = null; 068 pos._status = _status; 069 return super.finishClone(pos); 070 } 071 072 /** 073 * Attach a named sensor to display status. 074 * 075 * @param pName Used as a system/user name to lookup the sensor object 076 */ 077 @Override 078 public void setOccSensor(String pName) { 079 if (pName == null || pName.trim().isEmpty()) { 080 setOccSensorHandle(null); 081 return; 082 } 083 if (InstanceManager.getNullableDefault(jmri.SensorManager.class) != null) { 084 try { 085 Sensor sensor = InstanceManager.sensorManagerInstance().provideSensor(pName); 086 setOccSensorHandle(jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(pName, sensor)); 087 } catch (IllegalArgumentException ex) { 088 log.error("Occupancy Sensor '{}' not available, icon won't see changes", pName); 089 } 090 } else { 091 log.error("No SensorManager for this protocol, block icons won't see changes"); 092 } 093 } 094 095 @Override 096 public void setOccSensorHandle(NamedBeanHandle<Sensor> senHandle) { 097 if (namedOccSensor != null) { 098 getOccSensor().removePropertyChangeListener(this); 099 } 100 namedOccSensor = senHandle; 101 if (namedOccSensor != null) { 102 if (_iconMap == null) { 103 _iconMap = new HashMap<>(); 104 } 105 Sensor sensor = getOccSensor(); 106 sensor.addPropertyChangeListener(this, namedOccSensor.getName(), "Indicator Track"); 107 _status = _pathUtil.getStatus(sensor.getKnownState()); 108 displayState(_status); 109 } 110 } 111 112 @Override 113 public Sensor getOccSensor() { 114 if (namedOccSensor == null) { 115 return null; 116 } 117 return namedOccSensor.getBean(); 118 } 119 120 @Override 121 public NamedBeanHandle<Sensor> getNamedOccSensor() { 122 return namedOccSensor; 123 } 124 125 /** 126 * Attach a named OBlock to display status. 127 * 128 * @param pName Used as a system/user name to look up the OBlock object 129 */ 130 @Override 131 public void setOccBlock(String pName) { 132 if (pName == null || pName.trim().isEmpty()) { 133 setOccBlockHandle(null); 134 return; 135 } 136 OBlock block = InstanceManager.getDefault(jmri.jmrit.logix.OBlockManager.class).getOBlock(pName); 137 if (block != null) { 138 setOccBlockHandle(InstanceManager.getDefault(NamedBeanHandleManager.class).getNamedBeanHandle(pName, block)); 139 } else { 140 log.error("Detection OBlock '{}' not available, icon won't see changes", pName); 141 } 142 } 143 144 @Override 145 public void setOccBlockHandle(NamedBeanHandle<OBlock> blockHandle) { 146 if (namedOccBlock != null) { 147 getOccBlock().removePropertyChangeListener(this); 148 } 149 namedOccBlock = blockHandle; 150 if (namedOccBlock != null) { 151 if (_iconMap == null) { 152 _iconMap = new HashMap<>(); 153 } 154 OBlock block = getOccBlock(); 155 block.addPropertyChangeListener(this, namedOccBlock.getName(), "Indicator Track"); 156 setStatus(block, block.getState()); 157 displayState(_status); 158 setToolTip(new ToolTip(block.getDescription(), 0, 0, this)); 159 } else { 160 setToolTip(new ToolTip(null, 0, 0, this)); 161 } 162 } 163 164 @Override 165 public OBlock getOccBlock() { 166 if (namedOccBlock == null) { 167 return null; 168 } 169 return namedOccBlock.getBean(); 170 } 171 172 @Override 173 public NamedBeanHandle<OBlock> getNamedOccBlock() { 174 return namedOccBlock; 175 } 176 177 @Override 178 public void setShowTrain(boolean set) { 179 _pathUtil.setShowTrain(set); 180 } 181 182 @Override 183 public boolean showTrain() { 184 return _pathUtil.showTrain(); 185 } 186 187 @Override 188 public ArrayList<String> getPaths() { 189 return _pathUtil.getPaths(); 190 } 191 192 public void setPaths(ArrayList<String> paths) { 193 _pathUtil.setPaths(paths); 194 } 195 196 @Override 197 public void addPath(String path) { 198 _pathUtil.addPath(path); 199 } 200 201 @Override 202 public void removePath(String path) { 203 _pathUtil.removePath(path); 204 } 205 206 /** 207 * Get track name for known state of occupancy sensor 208 */ 209 @Override 210 public void setStatus(int state) { 211 _status = _pathUtil.getStatus(state); 212 } 213 214 /* 215 * Place icon by its bean state name 216 */ 217 public void setIcon(String name, NamedIcon icon) { 218 log.debug("set \"{}\" icon= {}", name, icon); 219 _iconMap.put(name, icon); 220 if (_status.equals(name)) { 221 setIcon(icon); 222 } 223 } 224 225 public String getStatus() { 226 return _status; 227 } 228 229 @Override 230 public int maxHeight() { 231 if (_iconMap == null) { 232 return 0; 233 } 234 int max = 0; 235 for (NamedIcon namedIcon : _iconMap.values()) { 236 max = Math.max(namedIcon.getIconHeight(), max); 237 } 238 return max; 239 } 240 241 @Override 242 public int maxWidth() { 243 if (_iconMap == null) { 244 return 0; 245 } 246 int max = 0; 247 for (NamedIcon namedIcon : _iconMap.values()) { 248 max = Math.max(namedIcon.getIconWidth(), max); 249 } 250 return max; 251 } 252 253 @Override 254 public void propertyChange(java.beans.PropertyChangeEvent evt) { 255 if (log.isDebugEnabled()) { 256 log.debug("property change: {} property {} is now {} from {}", getNameString(), evt.getPropertyName(), 257 evt.getNewValue(), evt.getSource().getClass().getName()); 258 } 259 260 Object source = evt.getSource(); 261 if (source instanceof OBlock) { 262 String property = evt.getPropertyName(); 263 if ("state".equals(property) || "pathState".equals(property)) { 264 int now = ((Integer) evt.getNewValue()); 265 setStatus((OBlock) source, now); 266 } else if ("pathName".equals(property)) { 267 _pathUtil.removePath((String) evt.getOldValue()); 268 _pathUtil.addPath((String) evt.getNewValue()); 269 } 270 } else if (source instanceof Sensor) { 271 if (evt.getPropertyName().equals("KnownState")) { 272 int now = ((Integer) evt.getNewValue()); 273 if (source.equals(getOccSensor())) { 274 _status = _pathUtil.getStatus(now); 275 } 276 } 277 } 278 displayState(_status); 279 } 280 281 private void setStatus(OBlock block, int state) { 282 _status = _pathUtil.getStatus(block, state); 283 if ((state & (OBlock.OCCUPIED | OBlock.RUNNING)) != 0) { 284 // _pathUtil.setLocoIcon must run on GUI. LocoLabel ctor causes editor to draw a graphic 285 ThreadingUtil.runOnGUIEventually(() -> { 286 _pathUtil.setLocoIcon(block, getLocation(), getSize(), _editor); 287 repaint(); 288 }); 289 } 290 if ((block.getState() & OBlock.OUT_OF_SERVICE) != 0) { 291 setControlling(false); 292 } else { 293 setControlling(true); 294 } 295 } 296 297 @Override 298 @Nonnull 299 public String getTypeString() { 300 return Bundle.getMessage("PositionableType_IndicatorTrackIcon"); 301 } 302 303 @Override 304 @Nonnull 305 public String getNameString() { 306 String str = ""; 307 if (namedOccBlock != null) { 308 str = "in " + namedOccBlock.getBean().getDisplayName(); 309 } else if (namedOccSensor != null) { 310 str = "on " + namedOccSensor.getBean().getDisplayName(); 311 } 312 return "ITrack " + str; 313 } 314 315 /** 316 * Pop-up displays unique attributes. 317 */ 318 @Override 319 public boolean showPopUp(JPopupMenu popup) { 320 return false; 321 } 322 323 /* 324 * Drive the current state of the display from the status. 325 */ 326 public void displayState(String status) { 327 log.debug("{} displayStatus {}", getNameString(), _status); 328 NamedIcon icon = getIcon(status); 329 if (icon != null) { 330 super.setIcon(icon); 331 } 332 updateSize(); 333 } 334 335 @Override 336 public void rotate(int deg) { 337 super.rotate(deg); 338 displayState(_status); 339 } 340 341 @Override 342 public boolean setEditItemMenu(JPopupMenu popup) { 343 String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("IndicatorTrack")); 344 popup.add(new javax.swing.AbstractAction(txt) { 345 @Override 346 public void actionPerformed(ActionEvent e) { 347 editItem(); 348 } 349 }); 350 return true; 351 } 352 353 protected void editItem() { 354 _paletteFrame = makePaletteFrame(java.text.MessageFormat.format(Bundle.getMessage("EditItem"), 355 Bundle.getMessage("IndicatorTrack"))); 356 _trackPanel = new IndicatorItemPanel(_paletteFrame, "IndicatorTrack", _iconFamily); 357 358 ActionListener updateAction = a -> updateItem(); 359 // duplicate _iconMap map with unscaled and unrotated icons 360 HashMap<String, NamedIcon> map = new HashMap<>(); 361 362 for (Entry<String, NamedIcon> entry : _iconMap.entrySet()) { 363 NamedIcon oldIcon = entry.getValue(); 364 NamedIcon newIcon = cloneIcon(oldIcon, this); 365 newIcon.rotate(0, this); 366 newIcon.scale(1.0, this); 367 newIcon.setRotation(4, this); 368 map.put(entry.getKey(), newIcon); 369 } 370 _trackPanel.init(updateAction, map); 371 if (namedOccSensor != null) { 372 _trackPanel.setOccDetector(namedOccSensor.getBean().getDisplayName()); 373 } 374 if (namedOccBlock != null) { 375 _trackPanel.setOccDetector(namedOccBlock.getBean().getDisplayName()); 376 } 377 _trackPanel.setShowTrainName(_pathUtil.showTrain()); 378 _trackPanel.setPaths(_pathUtil.getPaths()); 379 initPaletteFrame(_paletteFrame, _trackPanel); 380 } 381 382 private void updateItem() { 383 setOccSensor(_trackPanel.getOccSensor()); 384 setOccBlock(_trackPanel.getOccBlock()); 385 _pathUtil.setShowTrain(_trackPanel.getShowTrainName()); 386 _iconFamily = _trackPanel.getFamilyName(); 387 _pathUtil.setPaths(_trackPanel.getPaths()); 388 HashMap<String, NamedIcon> iconMap = _trackPanel.getIconMap(); 389 if (iconMap != null) { 390 HashMap<String, NamedIcon> oldMap = cloneMap(_iconMap, this); 391 for (Entry<String, NamedIcon> entry : _iconMap.entrySet()) { 392 if (log.isDebugEnabled()) { 393 log.debug("key= {}", entry.getKey()); 394 } 395 NamedIcon newIcon = entry.getValue(); 396 NamedIcon oldIcon = oldMap.get(entry.getKey()); 397 newIcon.setLoad(oldIcon.getDegrees(), oldIcon.getScale(), this); 398 newIcon.setRotation(oldIcon.getRotation(), this); 399 setIcon(entry.getKey(), newIcon); 400 } 401 } // otherwise retain current map 402 finishItemUpdate(_paletteFrame, _trackPanel); 403 displayState(_status); 404 } 405 406 @Override 407 public void dispose() { 408 if (namedOccSensor != null) { 409 getOccSensor().removePropertyChangeListener(this); 410 } 411 namedOccSensor = null; 412 if (namedOccBlock != null) { 413 getOccBlock().removePropertyChangeListener(this); 414 } 415 namedOccBlock = null; 416 _iconMap = null; 417 super.dispose(); 418 } 419 420 @Override 421 public jmri.NamedBean getNamedBean() { 422 if (namedOccBlock != null) { 423 return namedOccBlock.getBean(); 424 } else if (namedOccSensor != null) { 425 return namedOccSensor.getBean(); 426 } 427 return null; 428 } 429 430 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(IndicatorTrackIcon.class); 431 432}