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