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