001package jmri.jmrit.display; 002 003import java.awt.Color; 004import java.awt.datatransfer.DataFlavor; 005import java.awt.datatransfer.Transferable; 006import java.awt.event.ActionEvent; 007import java.awt.event.ActionListener; 008import java.util.ArrayList; 009import java.util.Map; 010 011import javax.swing.AbstractAction; 012import javax.swing.JComponent; 013import javax.swing.JPopupMenu; 014import javax.swing.JSeparator; 015 016import jmri.InstanceManager; 017import jmri.Memory; 018import jmri.NamedBeanHandle; 019import jmri.Reportable; 020import jmri.NamedBean.DisplayOptions; 021import jmri.jmrit.catalog.NamedIcon; 022import jmri.jmrit.roster.RosterEntry; 023import jmri.jmrit.roster.RosterIconFactory; 024import jmri.jmrit.throttle.ThrottleFrame; 025import jmri.jmrit.throttle.ThrottleFrameManager; 026import jmri.util.datatransfer.RosterEntrySelection; 027import jmri.util.swing.JmriJOptionPane; 028import jmri.util.swing.JmriMouseEvent; 029 030/** 031 * An icon to display a status of a Memory. 032 * <p> 033 * The value of the memory can't be changed with this icon. 034 * 035 * @author Bob Jacobsen Copyright (c) 2004 036 */ 037public class MemoryIcon extends MemoryOrGVIcon implements java.beans.PropertyChangeListener/*, DropTargetListener*/ { 038 039 NamedIcon defaultIcon = null; 040 // the map of icons 041 java.util.HashMap<String, NamedIcon> map = null; 042 private NamedBeanHandle<Memory> namedMemory; 043 044 public MemoryIcon(String s, Editor editor) { 045 super(s, editor); 046 resetDefaultIcon(); 047 _namedIcon = defaultIcon; 048 //By default all memory is left justified 049 _popupUtil.setJustification(LEFT); 050 this.setTransferHandler(new TransferHandler()); 051 } 052 053 public MemoryIcon(NamedIcon s, Editor editor) { 054 super(s, editor); 055 setDisplayLevel(Editor.LABELS); 056 defaultIcon = s; 057 _popupUtil.setJustification(LEFT); 058 log.debug("MemoryIcon ctor= {}", MemoryIcon.class.getName()); 059 this.setTransferHandler(new TransferHandler()); 060 } 061 062 @Override 063 public Positionable deepClone() { 064 MemoryIcon pos = new MemoryIcon("", _editor); 065 return finishClone(pos); 066 } 067 068 protected Positionable finishClone(MemoryIcon pos) { 069 pos.setMemory(namedMemory.getName()); 070 pos.setOriginalLocation(getOriginalX(), getOriginalY()); 071 if (map != null) { 072 for (Map.Entry<String, NamedIcon> entry : map.entrySet()) { 073 String url = entry.getValue().getName(); 074 pos.addKeyAndIcon(NamedIcon.getIconByName(url), entry.getKey()); 075 } 076 } 077 return super.finishClone(pos); 078 } 079 080 public void resetDefaultIcon() { 081 defaultIcon = new NamedIcon("resources/icons/misc/X-red.gif", 082 "resources/icons/misc/X-red.gif"); 083 } 084 085 public void setDefaultIcon(NamedIcon n) { 086 defaultIcon = n; 087 } 088 089 public NamedIcon getDefaultIcon() { 090 return defaultIcon; 091 } 092 093 private void setMap() { 094 if (map == null) { 095 map = new java.util.HashMap<>(); 096 } 097 } 098 099 /** 100 * Attach a named Memory to this display item. 101 * 102 * @param pName Used as a system/user name to lookup the Memory object 103 */ 104 public void setMemory(String pName) { 105 if (InstanceManager.getNullableDefault(jmri.MemoryManager.class) != null) { 106 try { 107 Memory memory = InstanceManager.memoryManagerInstance().provideMemory(pName); 108 setMemory(jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(pName, memory)); 109 } catch (IllegalArgumentException e) { 110 log.error("Memory '{}' not available, icon won't see changes", pName); 111 } 112 } else { 113 log.error("No MemoryManager for this protocol, icon won't see changes"); 114 } 115 updateSize(); 116 } 117 118 /** 119 * Attach a named Memory to this display item. 120 * 121 * @param m The Memory object 122 */ 123 public void setMemory(NamedBeanHandle<Memory> m) { 124 if (namedMemory != null) { 125 getMemory().removePropertyChangeListener(this); 126 } 127 namedMemory = m; 128 if (namedMemory != null) { 129 getMemory().addPropertyChangeListener(this, namedMemory.getName(), "Memory Icon"); 130 displayState(); 131 setName(namedMemory.getName()); 132 } 133 } 134 135 public NamedBeanHandle<Memory> getNamedMemory() { 136 return namedMemory; 137 } 138 139 public Memory getMemory() { 140 if (namedMemory == null) { 141 return null; 142 } 143 return namedMemory.getBean(); 144 } 145 146 @Override 147 public jmri.NamedBean getNamedBean() { 148 return getMemory(); 149 } 150 151 public java.util.HashMap<String, NamedIcon> getMap() { 152 return map; 153 } 154 155 // display icons 156 public void addKeyAndIcon(NamedIcon icon, String keyValue) { 157 if (map == null) { 158 setMap(); // initialize if needed 159 } 160 map.put(keyValue, icon); 161 // drop size cache 162 //height = -1; 163 //width = -1; 164 displayState(); // in case changed 165 } 166 167 // update icon as state of Memory changes 168 @Override 169 public void propertyChange(java.beans.PropertyChangeEvent e) { 170 if (log.isDebugEnabled()) { 171 log.debug("property change: {} is now {}", 172 e.getPropertyName(), e.getNewValue()); 173 } 174 if (e.getPropertyName().equals("value")) { 175 displayState(); 176 } 177 if (e.getSource() instanceof jmri.Throttle) { 178 if (e.getPropertyName().equals(jmri.Throttle.ISFORWARD)) { 179 Boolean boo = (Boolean) e.getNewValue(); 180 if (boo) { 181 flipIcon(NamedIcon.NOFLIP); 182 } else { 183 flipIcon(NamedIcon.HORIZONTALFLIP); 184 } 185 } 186 } 187 } 188 189 @Override 190 public String getNameString() { 191 String name; 192 if (namedMemory == null) { 193 name = Bundle.getMessage("NotConnected"); 194 } else { 195 name = getMemory().getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME); 196 } 197 return name; 198 } 199 200 public void setSelectable(boolean b) { 201 selectable = b; 202 } 203 204 public boolean isSelectable() { 205 return selectable; 206 } 207 boolean selectable = false; 208 209 @Override 210 public boolean showPopUp(JPopupMenu popup) { 211 if (isEditable() && selectable) { 212 popup.add(new JSeparator()); 213 214 for (String key : map.keySet()) { 215 //String value = ((NamedIcon)map.get(key)).getName(); 216 popup.add(new AbstractAction(key) { 217 218 @Override 219 public void actionPerformed(ActionEvent e) { 220 String key = e.getActionCommand(); 221 setValue(key); 222 } 223 }); 224 } 225 return true; 226 } // end of selectable 227 if (re != null) { 228 popup.add(new AbstractAction(Bundle.getMessage("OpenThrottle")) { 229 230 @Override 231 public void actionPerformed(ActionEvent e) { 232 ThrottleFrame tf = InstanceManager.getDefault(ThrottleFrameManager.class).createThrottleFrame(); 233 tf.toFront(); 234 tf.getAddressPanel().setRosterEntry(re); 235 } 236 }); 237 //don't like the idea of refering specifically to the layout block manager for this, but it has to be done if we are to allow the panel editor to also assign trains to block, when used with a layouteditor 238 if ((InstanceManager.getDefault(jmri.SectionManager.class).getNamedBeanSet().size()) > 0 && jmri.InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class).getBlockWithMemoryAssigned(getMemory()) != null) { 239 final jmri.jmrit.dispatcher.DispatcherFrame df = jmri.InstanceManager.getNullableDefault(jmri.jmrit.dispatcher.DispatcherFrame.class); 240 if (df != null) { 241 final jmri.jmrit.dispatcher.ActiveTrain at = df.getActiveTrainForRoster(re); 242 if (at != null) { 243 popup.add(new AbstractAction(Bundle.getMessage("MenuTerminateTrain")) { 244 245 @Override 246 public void actionPerformed(ActionEvent e) { 247 df.terminateActiveTrain(at,true,false); 248 } 249 }); 250 popup.add(new AbstractAction(Bundle.getMessage("MenuAllocateExtra")) { 251 252 @Override 253 public void actionPerformed(ActionEvent e) { 254 //Just brings up the standard allocate extra frame, this could be expanded in the future 255 //As a point and click operation. 256 df.allocateExtraSection(e, at); 257 } 258 }); 259 if (at.getStatus() == jmri.jmrit.dispatcher.ActiveTrain.DONE) { 260 popup.add(new AbstractAction(Bundle.getMessage("MenuRestartTrain")) { 261 262 @Override 263 public void actionPerformed(ActionEvent e) { 264 at.allocateAFresh(); 265 } 266 }); 267 } 268 } else { 269 popup.add(new AbstractAction(Bundle.getMessage("MenuNewTrain")) { 270 271 @Override 272 public void actionPerformed(ActionEvent e) { 273 jmri.jmrit.display.layoutEditor.LayoutBlock lBlock = jmri.InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager.class).getBlockWithMemoryAssigned(getMemory()); 274 if (!df.getNewTrainActive() && lBlock!=null) { 275 df.getActiveTrainFrame().initiateTrain(e, re, lBlock.getBlock()); 276 df.setNewTrainActive(true); 277 } else { 278 df.getActiveTrainFrame().showActivateFrame(re); 279 } 280 } 281 282 }); 283 } 284 } 285 } 286 return true; 287 } 288 return false; 289 } 290 291 /** 292 * Text edits cannot be done to Memory text - override 293 */ 294 @Override 295 public boolean setTextEditMenu(JPopupMenu popup) { 296 popup.add(new AbstractAction(Bundle.getMessage("EditMemoryValue")) { 297 298 @Override 299 public void actionPerformed(ActionEvent e) { 300 editMemoryValue(); 301 } 302 }); 303 return true; 304 } 305 306 protected void flipIcon(int flip) { 307 if (_namedIcon != null) { 308 _namedIcon.flip(flip, this); 309 } 310 updateSize(); 311 repaint(); 312 } 313 Color _saveColor; 314 315 /** 316 * Drive the current state of the display from the state of the Memory. 317 */ 318 @Override 319 public void displayState() { 320 log.debug("displayState()"); 321 322 if (namedMemory == null) { // use default if not connected yet 323 setIcon(defaultIcon); 324 updateSize(); 325 return; 326 } 327 if (re != null) { 328 jmri.InstanceManager.throttleManagerInstance().removeListener(re.getDccLocoAddress(), this); 329 re = null; 330 } 331 Object key = getMemory().getValue(); 332 displayState(key); 333 } 334 335 /** 336 * Special method to transfer a setAttributes call from the LE version of 337 * MemoryIcon. This eliminates the need to change references to public. 338 * 339 * @since 4.11.6 340 * @param util The LE popup util object. 341 * @param that The current positional object (this). 342 */ 343 public void setAttributes(PositionablePopupUtil util, Positionable that) { 344 _editor.setAttributes(util, that); 345 } 346 347 protected void displayState(Object key) { 348 log.debug("displayState({})", key); 349 if (key != null) { 350 if (map == null) { 351 Object val = key; 352 // no map, attempt to show object directly 353 if (val instanceof jmri.jmrit.roster.RosterEntry) { 354 jmri.jmrit.roster.RosterEntry roster = (jmri.jmrit.roster.RosterEntry) val; 355 val = updateIconFromRosterVal(roster); 356 flipRosterIcon = false; 357 if (val == null) { 358 return; 359 } 360 } 361 if (val instanceof String) { 362 String str = (String) val; 363 _icon = false; 364 _text = true; 365 setText(str); 366 updateIcon(null); 367 if (log.isDebugEnabled()) { 368 log.debug("String str= \"{}\" str.trim().length()= {}", str, str.trim().length()); 369 log.debug(" maxWidth()= {}, maxHeight()= {}", maxWidth(), maxHeight()); 370 log.debug(" getBackground(): {}", getBackground()); 371 log.debug(" _editor.getTargetPanel().getBackground(): {}", _editor.getTargetPanel().getBackground()); 372 log.debug(" setAttributes to getPopupUtility({}) with", getPopupUtility()); 373 log.debug(" hasBackground() {}", getPopupUtility().hasBackground()); 374 log.debug(" getBackground() {}", getPopupUtility().getBackground()); 375 log.debug(" on editor {}", _editor); 376 } 377 _editor.setAttributes(getPopupUtility(), this); 378 } else if (val instanceof javax.swing.ImageIcon) { 379 _icon = true; 380 _text = false; 381 setIcon((javax.swing.ImageIcon) val); 382 setText(null); 383 } else if (val instanceof Number) { 384 _icon = false; 385 _text = true; 386 setText(val.toString()); 387 setIcon(null); 388 } else if (val instanceof jmri.IdTag){ 389 // most IdTags are Reportable objects, so 390 // this needs to be before Reportable 391 _icon = false; 392 _text = true; 393 setIcon(null); 394 setText(((jmri.IdTag)val).getDisplayName()); 395 } else if (val instanceof Reportable) { 396 _icon = false; 397 _text = true; 398 setText(((Reportable)val).toReportString()); 399 setIcon(null); 400 } else { 401 // don't recognize the type, do our best with toString 402 log.debug("display current value of {} as String, val= {} of Class {}", 403 getNameString(), val, val.getClass().getName()); 404 _icon = false; 405 _text = true; 406 setText(val.toString()); 407 setIcon(null); 408 } 409 } else { 410 // map exists, use it 411 NamedIcon newicon = map.get(key.toString()); 412 if (newicon != null) { 413 414 setText(null); 415 super.setIcon(newicon); 416 } else { 417 // no match, use default 418 _icon = true; 419 _text = false; 420 setIcon(defaultIcon); 421 setText(null); 422 } 423 } 424 } else { 425 log.debug("object null"); 426 _icon = true; 427 _text = false; 428 setIcon(defaultIcon); 429 setText(null); 430 } 431 updateSize(); 432 } 433 434 protected Object updateIconFromRosterVal(RosterEntry roster) { 435 re = roster; 436 javax.swing.ImageIcon icon = jmri.InstanceManager.getDefault(RosterIconFactory.class).getIcon(roster); 437 if (icon == null || icon.getIconWidth() == -1 || icon.getIconHeight() == -1) { 438 //the IconPath is still at default so no icon set 439 return roster.titleString(); 440 } else { 441 NamedIcon rosterIcon = new NamedIcon(roster.getIconPath(), roster.getIconPath()); 442 _text = false; 443 _icon = true; 444 updateIcon(rosterIcon); 445 446 if (flipRosterIcon) { 447 flipIcon(NamedIcon.HORIZONTALFLIP); 448 } 449 jmri.InstanceManager.throttleManagerInstance().attachListener(re.getDccLocoAddress(), this); 450 Object isForward = jmri.InstanceManager.throttleManagerInstance().getThrottleInfo(re.getDccLocoAddress(), jmri.Throttle.ISFORWARD); 451 if (isForward != null) { 452 if (!(Boolean) isForward) { 453 flipIcon(NamedIcon.HORIZONTALFLIP); 454 } 455 } 456 return null; 457 } 458 } 459 460 protected jmri.jmrit.roster.RosterEntry re = null; 461 462 /*As the size of a memory label can change we want to adjust the position of the x,y 463 if the width is fixed*/ 464 static final int LEFT = 0x00; 465 static final int RIGHT = 0x02; 466 static final int CENTRE = 0x04; 467 468 @Override 469 public void updateSize() { 470 if (_popupUtil.getFixedWidth() == 0) { 471 //setSize(maxWidth(), maxHeight()); 472 switch (_popupUtil.getJustification()) { 473 case LEFT: 474 super.setLocation(getOriginalX(), getOriginalY()); 475 break; 476 case RIGHT: 477 super.setLocation(getOriginalX() - maxWidth(), getOriginalY()); 478 break; 479 case CENTRE: 480 super.setLocation(getOriginalX() - (maxWidth() / 2), getOriginalY()); 481 break; 482 default: 483 log.warn("Unhandled justification code: {}", _popupUtil.getJustification()); 484 break; 485 } 486 setSize(maxWidth(), maxHeight()); 487 } else { 488 super.updateSize(); 489 if (_icon && _namedIcon != null) { 490 _namedIcon.reduceTo(maxWidthTrue(), maxHeightTrue(), 0.2); 491 } 492 } 493 } 494 495 /*Stores the original location of the memory, this is then used to calculate 496 the position of the text dependant upon the justification*/ 497 private int originalX = 0; 498 private int originalY = 0; 499 500 public void setOriginalLocation(int x, int y) { 501 originalX = x; 502 originalY = y; 503 updateSize(); 504 } 505 506 @Override 507 public int getOriginalX() { 508 return originalX; 509 } 510 511 @Override 512 public int getOriginalY() { 513 return originalY; 514 } 515 516 @Override 517 public void setLocation(int x, int y) { 518 if (_popupUtil.getFixedWidth() == 0) { 519 setOriginalLocation(x, y); 520 } else { 521 super.setLocation(x, y); 522 } 523 } 524 525 @Override 526 public boolean setEditIconMenu(JPopupMenu popup) { 527 String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameMemory")); 528 popup.add(new AbstractAction(txt) { 529 @Override 530 public void actionPerformed(ActionEvent e) { 531 edit(); 532 } 533 }); 534 return true; 535 } 536 537 @Override 538 protected void edit() { 539 makeIconEditorFrame(this, "Memory", true, null); 540 _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.memoryPickModelInstance()); 541 ActionListener addIconAction = (ActionEvent a) -> editMemory(); 542 _iconEditor.complete(addIconAction, false, true, true); 543 _iconEditor.setSelection(getMemory()); 544 } 545 546 void editMemory() { 547 setMemory(_iconEditor.getTableSelection().getDisplayName()); 548 updateSize(); 549 _iconEditorFrame.dispose(); 550 _iconEditorFrame = null; 551 _iconEditor = null; 552 invalidate(); 553 } 554 555 @Override 556 public void dispose() { 557 if (getMemory() != null) { 558 getMemory().removePropertyChangeListener(this); 559 } 560 namedMemory = null; 561 if (re != null) { 562 jmri.InstanceManager.throttleManagerInstance().removeListener(re.getDccLocoAddress(), this); 563 re = null; 564 } 565 super.dispose(); 566 } 567 568 @Override 569 public void doMouseClicked(JmriMouseEvent e) { 570 if (e.getClickCount() == 2) { // double click? 571 editMemoryValue(); 572 } 573 } 574 575 protected void editMemoryValue() { 576 577 String reval = (String)JmriJOptionPane.showInputDialog(this, 578 Bundle.getMessage("EditCurrentMemoryValue", namedMemory.getName()), 579 getMemory().getValue()); 580 581 setValue(reval); 582 updateSize(); 583 } 584 585 //This is used by the LayoutEditor 586 protected boolean updateBlockValue = false; 587 588 public void updateBlockValueOnChange(boolean boo) { 589 updateBlockValue = boo; 590 } 591 592 public boolean updateBlockValueOnChange() { 593 return updateBlockValue; 594 } 595 596 protected boolean flipRosterIcon = false; 597 598 protected void addRosterToIcon(RosterEntry roster) { 599 Object[] options = {"Facing West", 600 "Facing East", 601 "Do Not Add"}; 602 int n = JmriJOptionPane.showOptionDialog(this, // TODO I18N 603 "Would you like to assign loco " 604 + roster.titleString() + " to this location", 605 "Assign Loco", 606 JmriJOptionPane.DEFAULT_OPTION, 607 JmriJOptionPane.QUESTION_MESSAGE, 608 null, 609 options, 610 options[2]); 611 if ( n == 2 || n==JmriJOptionPane.CLOSED_OPTION ) { // option array 2 Do Not Add, or Dialog closed 612 return; 613 } 614 flipRosterIcon = (n == 0); // true if option array position 0, Facing West 615 if (getValue() == roster) { 616 //No change in the loco but a change in direction facing might have occurred 617 updateIconFromRosterVal(roster); 618 } else { 619 setValue(roster); 620 } 621 } 622 623 protected Object getValue() { 624 if (getMemory() == null) { 625 return null; 626 } 627 return getMemory().getValue(); 628 } 629 630 protected void setValue(Object val) { 631 getMemory().setValue(val); 632 } 633 634 class TransferHandler extends javax.swing.TransferHandler { 635 @Override 636 public boolean canImport(JComponent c, DataFlavor[] transferFlavors) { 637 for (DataFlavor flavor : transferFlavors) { 638 if (RosterEntrySelection.rosterEntryFlavor.equals(flavor)) { 639 return true; 640 } 641 } 642 return false; 643 } 644 645 @Override 646 public boolean importData(JComponent c, Transferable t) { 647 try { 648 ArrayList<RosterEntry> REs = RosterEntrySelection.getRosterEntries(t); 649 for (RosterEntry roster : REs) { 650 addRosterToIcon(roster); 651 } 652 } catch (java.awt.datatransfer.UnsupportedFlavorException | java.io.IOException e) { 653 log.error("Could not add a RosterEntry to Icon.", e); 654 } 655 return true; 656 } 657 658 } 659 660 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MemoryIcon.class); 661 662}