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.List; 008 009import javax.swing.AbstractAction; 010import javax.swing.JPopupMenu; 011 012import jmri.InstanceManager; 013import jmri.NamedBeanHandle; 014import jmri.Sensor; 015import jmri.jmrit.catalog.NamedIcon; 016import jmri.jmrit.display.palette.MultiSensorItemPanel; 017import jmri.jmrit.picker.PickListModel; 018import jmri.util.swing.JmriMouseEvent; 019 020import org.slf4j.Logger; 021import org.slf4j.LoggerFactory; 022 023/** 024 * An icon to display a status of set of Sensors. 025 * <p> 026 * Each sensor has an associated image. Normally, only one sensor will be active 027 * at a time, and in that case the associated image will be shown. If more than 028 * one is active, one of the corresponding images will be shown, but which one 029 * is not guaranteed. 030 * 031 * @author Bob Jacobsen Copyright (C) 2001, 2007 032 */ 033public class MultiSensorIcon extends PositionableLabel implements java.beans.PropertyChangeListener { 034 035 String _iconFamily; 036 037 public MultiSensorIcon(Editor editor) { 038 // super ctor call to make sure this is an icon label 039 super(new NamedIcon("resources/icons/smallschematics/tracksegments/circuit-error.gif", 040 "resources/icons/smallschematics/tracksegments/circuit-error.gif"), editor); 041 _control = true; 042 displayState(); 043 setPopupUtility(null); 044 } 045 046 boolean updown = false; 047 048 // if not updown, is rightleft 049 public void setUpDown(boolean b) { 050 updown = b; 051 } 052 053 public boolean getUpDown() { 054 return updown; 055 } 056 057 ArrayList<Entry> entries = new ArrayList<>(); 058 059 @Override 060 public Positionable deepClone() { 061 MultiSensorIcon pos = new MultiSensorIcon(_editor); 062 return finishClone(pos); 063 } 064 065 protected Positionable finishClone(MultiSensorIcon pos) { 066 pos.setInactiveIcon(cloneIcon(getInactiveIcon(), pos)); 067 pos.setInconsistentIcon(cloneIcon(getInconsistentIcon(), pos)); 068 pos.setUnknownIcon(cloneIcon(getUnknownIcon(), pos)); 069 for (int i = 0; i < entries.size(); i++) { 070 pos.addEntry(getSensorName(i), cloneIcon(getSensorIcon(i), pos)); 071 } 072 return super.finishClone(pos); 073 } 074 075 public void addEntry(NamedBeanHandle<Sensor> sensor, NamedIcon icon) { 076 if (sensor != null) { 077 if (log.isDebugEnabled()) { 078 log.debug("addEntry: sensor= {}", sensor.getName()); 079 } 080 Entry e = new Entry(); 081 sensor.getBean().addPropertyChangeListener(this, sensor.getName(), "MultiSensor Icon"); 082 e.namedSensor = sensor; 083 e.icon = icon; 084 entries.add(e); 085 displayState(); 086 } else { 087 log.error("Sensor not available, icon won't see changes"); 088 } 089 } 090 091 public void addEntry(String pName, NamedIcon icon) { 092 NamedBeanHandle<Sensor> sensor; 093 if (InstanceManager.getNullableDefault(jmri.SensorManager.class) != null) { 094 sensor = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class) 095 .getNamedBeanHandle(pName, InstanceManager.sensorManagerInstance().provideSensor(pName)); 096 addEntry(sensor, icon); 097 } else { 098 log.error("No SensorManager for this protocol, icon won't see changes"); 099 } 100 } 101 102 public int getNumEntries() { 103 return entries.size(); 104 } 105 106 public List<Sensor> getSensors() { 107 ArrayList<Sensor> list = new ArrayList<>(getNumEntries()); 108 for (Entry handle : entries) { 109 list.add(handle.namedSensor.getBean()); 110 } 111 return list; 112 } 113 114 public String getSensorName(int i) { 115 return entries.get(i).namedSensor.getName(); 116 } 117 118 public NamedIcon getSensorIcon(int i) { 119 return entries.get(i).icon; 120 } 121 122 public String getFamily() { 123 return _iconFamily; 124 } 125 126 public void setFamily(String family) { 127 _iconFamily = family; 128 } 129 130 // display icons 131 String inactiveName = "resources/icons/USS/plate/levers/l-inactive.gif"; 132 NamedIcon inactive = new NamedIcon(inactiveName, inactiveName); 133 134 String inconsistentName = "resources/icons/USS/plate/levers/l-inconsistent.gif"; 135 NamedIcon inconsistent = new NamedIcon(inconsistentName, inconsistentName); 136 137 String unknownName = "resources/icons/USS/plate/levers/l-unknown.gif"; 138 NamedIcon unknown = new NamedIcon(unknownName, unknownName); 139 140 public NamedIcon getInactiveIcon() { 141 return inactive; 142 } 143 144 public void setInactiveIcon(NamedIcon i) { 145 inactive = i; 146 } 147 148 public NamedIcon getInconsistentIcon() { 149 return inconsistent; 150 } 151 152 public void setInconsistentIcon(NamedIcon i) { 153 inconsistent = i; 154 } 155 156 public NamedIcon getUnknownIcon() { 157 return unknown; 158 } 159 160 public void setUnknownIcon(NamedIcon i) { 161 unknown = i; 162 } 163 164 // update icon as state of turnout changes 165 @Override 166 public void propertyChange(java.beans.PropertyChangeEvent e) { 167 if (log.isDebugEnabled()) { 168 String prop = e.getPropertyName(); 169 Sensor sen = (Sensor) e.getSource(); 170 log.debug("property change({}) Sensor state= {} - old= {}, new= {}", 171 prop, sen.getKnownState(), e.getOldValue(), e.getNewValue()); 172 } 173 if (e.getPropertyName().equals("KnownState")) { 174 displayState(); 175 _editor.repaint(); 176 } 177 } 178 179 @Override 180 public String getNameString() { 181 StringBuilder name = new StringBuilder(); 182 if ((entries == null) || (entries.size() < 1)) { 183 name.append(Bundle.getMessage("NotConnected")); 184 } else { 185 name.append(entries.get(0).namedSensor.getName()); 186 entries.forEach((entry) -> name.append(",").append(entry.namedSensor.getName())); 187 } 188 return name.toString(); 189 } 190 191 /** 192 * ****** popup AbstractAction.actionPerformed method overrides ******** 193 */ 194 @Override 195 protected void rotateOrthogonal() { 196 for (Entry entry : entries) { 197 NamedIcon icon = entry.icon; 198 icon.setRotation(icon.getRotation() + 1, this); 199 } 200 inactive.setRotation(inactive.getRotation() + 1, this); 201 unknown.setRotation(unknown.getRotation() + 1, this); 202 inconsistent.setRotation(inconsistent.getRotation() + 1, this); 203 displayState(); 204 // bug fix, must repaint icons that have same width and height 205 repaint(); 206 } 207 208 @Override 209 public void setScale(double s) { 210 for (Entry entry : entries) { 211 NamedIcon icon = entry.icon; 212 icon.scale(s, this); 213 } 214 inactive.scale(s, this); 215 unknown.scale(s, this); 216 inconsistent.scale(s, this); 217 displayState(); 218 } 219 220 @Override 221 public void rotate(int deg) { 222 for (Entry entry : entries) { 223 NamedIcon icon = entry.icon; 224 icon.rotate(deg, this); 225 } 226 inactive.rotate(deg, this); 227 unknown.rotate(deg, this); 228 inconsistent.rotate(deg, this); 229 displayState(); 230 } 231 232 @Override 233 public boolean setEditItemMenu(JPopupMenu popup) { 234 String txt = Bundle.getMessage("EditItem", Bundle.getMessage("MultiSensor")); 235 popup.add(new javax.swing.AbstractAction(txt) { 236 @Override 237 public void actionPerformed(ActionEvent e) { 238 editItem(); 239 } 240 }); 241 return true; 242 } 243 244 MultiSensorItemPanel _itemPanel; 245 246 protected void editItem() { 247 _paletteFrame = makePaletteFrame(Bundle.getMessage("EditItem", Bundle.getMessage("MultiSensor"))); 248 _itemPanel = new MultiSensorItemPanel(_paletteFrame, "MultiSensor", _iconFamily, 249 PickListModel.multiSensorPickModelInstance()); 250 ActionListener updateAction = (ActionEvent a) -> updateItem(); 251 // duplicate _iconMap map with unscaled and unrotated icons 252 HashMap<String, NamedIcon> map = new HashMap<>(); 253 map.put("SensorStateInactive", inactive); 254 map.put("BeanStateInconsistent", inconsistent); 255 map.put("BeanStateUnknown", unknown); 256 for (int i = 0; i < entries.size(); i++) { 257 map.put(MultiSensorItemPanel.getPositionName(i), entries.get(i).icon); 258 } 259 _itemPanel.init(updateAction, map); 260 for (Entry entry : entries) { 261 _itemPanel.setSelection(entry.namedSensor.getBean()); 262 } 263 _itemPanel.setUpDown(getUpDown()); 264 initPaletteFrame(_paletteFrame, _itemPanel); 265 } 266 267 void updateItem() { 268 if (!_itemPanel.oktoUpdate()) { 269 return; 270 } 271 HashMap<String, NamedIcon> iconMap = _itemPanel.getIconMap(); 272 ArrayList<Sensor> selections = _itemPanel.getTableSelections(); 273 setInactiveIcon(new NamedIcon(iconMap.get("SensorStateInactive"))); 274 setInconsistentIcon(new NamedIcon(iconMap.get("BeanStateInconsistent"))); 275 setUnknownIcon(new NamedIcon(iconMap.get("BeanStateUnknown"))); 276 entries = new ArrayList<>(selections.size()); 277 for (int i = 0; i < selections.size(); i++) { 278 addEntry(selections.get(i).getDisplayName(), new NamedIcon(iconMap.get(MultiSensorItemPanel.getPositionName(i)))); 279 } 280 _iconFamily = _itemPanel.getFamilyName(); 281 _itemPanel.clearSelections(); 282 setUpDown(_itemPanel.getUpDown()); 283 finishItemUpdate(_paletteFrame, _itemPanel); 284 } 285 286 @Override 287 public boolean setEditIconMenu(JPopupMenu popup) { 288 String txt = Bundle.getMessage("EditItem", Bundle.getMessage("MultiSensor")); 289 popup.add(new AbstractAction(txt) { 290 @Override 291 public void actionPerformed(ActionEvent e) { 292 edit(); 293 } 294 }); 295 return true; 296 } 297 298 @Override 299 protected void edit() { 300 MultiSensorIconAdder iconEditor = new MultiSensorIconAdder("MultiSensor"); 301 makeIconEditorFrame(this, "MultiSensor", false, iconEditor); 302 _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.sensorPickModelInstance()); 303 _iconEditor.setIcon(2, "SensorStateInactive", inactive); 304 _iconEditor.setIcon(0, "BeanStateInconsistent", inconsistent); 305 _iconEditor.setIcon(1, "BeanStateUnknown", unknown); 306 if (_iconEditor instanceof MultiSensorIconAdder) { 307 ((MultiSensorIconAdder) _iconEditor).setMultiIcon(entries); 308 _iconEditor.makeIconPanel(false); 309 310 ActionListener addIconAction = (ActionEvent a) -> updateSensor(); 311 iconEditor.complete(addIconAction, true, true, true); 312 } 313 } 314 315 void updateSensor() { 316 if (_iconEditor instanceof MultiSensorIconAdder) { 317 MultiSensorIconAdder iconEditor = (MultiSensorIconAdder) _iconEditor; 318 setInactiveIcon(iconEditor.getIcon("SensorStateInactive")); 319 setInconsistentIcon(iconEditor.getIcon("BeanStateInconsistent")); 320 setUnknownIcon(iconEditor.getIcon("BeanStateUnknown")); 321 for (Entry entry : entries) { 322 entry.namedSensor.getBean().removePropertyChangeListener(this); 323 } 324 int numPositions = iconEditor.getNumIcons(); 325 entries = new ArrayList<>(numPositions); 326 for (int i = 3; i < numPositions; i++) { 327 NamedIcon icon = iconEditor.getIcon(i); 328 NamedBeanHandle<Sensor> namedSensor = iconEditor.getSensor(i); 329 addEntry(namedSensor, icon); 330 } 331 setUpDown(iconEditor.getUpDown()); 332 } 333 _iconEditorFrame.dispose(); 334 _iconEditorFrame = null; 335 _iconEditor = null; 336 invalidate(); 337 } 338 /** 339 * *********** end popup action methods *************** 340 */ 341 342 int displaying = -1; 343 344 /** 345 * Drive the current state of the display from the state of the turnout. 346 */ 347 public void displayState() { 348 349 updateSize(); 350 351 // run the entries 352 boolean foundActive = false; 353 354 for (int i = 0; i < entries.size(); i++) { 355 Entry e = entries.get(i); 356 357 int state = e.namedSensor.getBean().getKnownState(); 358 359 switch (state) { 360 case Sensor.ACTIVE: 361 if (isText()) { 362 super.setText(Bundle.getMessage("SensorStateActive")); 363 } 364 if (isIcon()) { 365 super.setIcon(e.icon); 366 } 367 foundActive = true; 368 displaying = i; 369 break; // look at the next ones too 370 case Sensor.UNKNOWN: 371 if (isText()) { 372 super.setText(Bundle.getMessage("BeanStateUnknown")); 373 } 374 if (isIcon()) { 375 super.setIcon(unknown); 376 } 377 return; // this trumps all others 378 case Sensor.INCONSISTENT: 379 if (isText()) { 380 super.setText(Bundle.getMessage("BeanStateInconsistent")); 381 } 382 if (isIcon()) { 383 super.setIcon(inconsistent); 384 } 385 break; 386 default: 387 break; 388 } 389 } 390 // loop has gotten to here 391 if (foundActive) { 392 return; // set active 393 } // only case left is all inactive 394 if (isText()) { 395 super.setText(Bundle.getMessage("SensorStateInactive")); 396 } 397 if (isIcon()) { 398 super.setIcon(inactive); 399 } 400 } 401 402 // Use largest size. If icons are not same size, 403 // this can result in drawing artifacts. 404 @Override 405 public int maxHeight() { 406 int size = Math.max( 407 ((inactive != null) ? inactive.getIconHeight() : 0), 408 Math.max((unknown != null) ? unknown.getIconHeight() : 0, 409 (inconsistent != null) ? inconsistent.getIconHeight() : 0) 410 ); 411 if (entries != null) { 412 for (Entry entry : entries) { 413 size = Math.max(size, entry.icon.getIconHeight()); 414 } 415 } 416 return size; 417 } 418 419 // Use largest size. If icons are not same size, 420 // this can result in drawing artifacts. 421 @Override 422 public int maxWidth() { 423 int size = Math.max( 424 ((inactive != null) ? inactive.getIconWidth() : 0), 425 Math.max((unknown != null) ? unknown.getIconWidth() : 0, 426 (inconsistent != null) ? inconsistent.getIconWidth() : 0) 427 ); 428 if (entries != null) { 429 for (Entry entry : entries) { 430 size = Math.max(size, entry.icon.getIconWidth()); 431 } 432 } 433 return size; 434 } 435 436 public void performMouseClicked(JmriMouseEvent e, int xx, int yy) { 437 if (log.isDebugEnabled()) { 438 log.debug("performMouseClicked: location ({}, {}), click from ({}, {}) displaying={}", 439 getX(), getY(), xx, yy, displaying); 440 } 441 if (!buttonLive() || (entries == null || entries.size() < 1)) { 442 if (log.isDebugEnabled()) { 443 log.debug("performMouseClicked: buttonLive={}, entries={}", buttonLive(), entries.size()); 444 } 445 return; 446 } 447 448 // find if we want to increment or decrement 449 // regardless of the zooming scale, (getX(), getY()) is the un-zoomed position in _editor._contents 450 // but the click is at the zoomed position 451 double ratio = _editor.getPaintScale(); 452 boolean dec = false; 453 if (updown) { 454 if ((yy/ratio - getY()) > (double)(maxHeight()) / 2) { 455 dec = true; 456 } 457 } else { 458 if ((xx/ratio - getX()) < (double)(maxWidth()) / 2) { 459 dec = true; 460 } 461 } 462 463 // get new index 464 int next; 465 if (dec) { 466 next = displaying - 1; 467 } else { 468 next = displaying + 1; 469 } 470 if (next < 0) { 471 next = 0; 472 } 473 if (next >= entries.size()) { 474 next = entries.size() - 1; 475 } 476 477 int drop = displaying; 478 if (log.isDebugEnabled()) { 479 log.debug("dec= {} displaying={} next= {}", dec, displaying, next); 480 } 481 try { 482 entries.get(next).namedSensor.getBean().setKnownState(Sensor.ACTIVE); 483 if (drop >= 0 && drop != next) { 484 entries.get(drop).namedSensor.getBean().setKnownState(Sensor.INACTIVE); 485 } 486 } catch (jmri.JmriException ex) { 487 log.error("Click failed to set sensor: ", ex); 488 } 489 } 490 491 boolean buttonLive() { 492 return _editor.getFlag(Editor.OPTION_CONTROLS, isControlling()); 493 } 494 495 @Override 496 public void doMouseClicked(JmriMouseEvent e) { 497 if (!e.isAltDown() && !e.isMetaDown()) { 498 performMouseClicked(e, e.getX(), e.getY()); 499 } 500 } 501 502 @Override 503 public void dispose() { 504 // remove listeners 505 for (Entry entry : entries) { 506 entry.namedSensor.getBean().removePropertyChangeListener(this); 507 } 508 super.dispose(); 509 } 510 511 static class Entry { 512 513 NamedBeanHandle<Sensor> namedSensor; 514 NamedIcon icon; 515 } 516 517 private final static Logger log = LoggerFactory.getLogger(MultiSensorIcon.class); 518 519}