001package jmri.jmrit.display; 002 003import java.awt.event.ActionEvent; 004import java.awt.event.ActionListener; 005import java.awt.event.FocusAdapter; 006import java.awt.event.FocusEvent; 007import java.awt.event.KeyAdapter; 008import java.awt.event.KeyEvent; 009 010import javax.annotation.Nonnull; 011import javax.swing.JComponent; 012import javax.swing.JLabel; 013import javax.swing.JPanel; 014import javax.swing.JSpinner; 015import javax.swing.SpinnerNumberModel; 016 017import jmri.InstanceManager; 018import jmri.Block; 019import jmri.NamedBeanHandle; 020import jmri.NamedBean.DisplayOptions; 021import jmri.util.swing.*; 022 023import org.slf4j.Logger; 024import org.slf4j.LoggerFactory; 025 026/** 027 * An icon to display and input a Block contents value in a TextField. 028 * <p> 029 * Handles the case of either a String or an Integer in the Block, preserving 030 * what it finds. 031 * 032 * Cloned from MemoryInputIcon by Pete Cressman 033 * 034 * @author Dave Sand Copyright (c) 2026 035 * @since 5.15.4 036 */ 037public class BlockContentsInputIcon extends PositionableJPanelWithColumns implements java.beans.PropertyChangeListener { 038 039 // the associated Block object 040 private NamedBeanHandle<Block> namedBlock; 041 042 private final java.awt.event.MouseListener _mouseListener = JmriMouseListener.adapt(this); 043 private final java.awt.event.MouseMotionListener _mouseMotionListener = JmriMouseMotionListener.adapt(this); 044 045 public BlockContentsInputIcon(int nCols, Editor editor) { 046 super(editor); 047 _nCols = nCols; 048 setDisplayLevel(Editor.LABELS); 049 050 setLayout(new java.awt.GridBagLayout()); 051 add(_textBox, new java.awt.GridBagConstraints()); 052 _textBox.addKeyListener(new KeyAdapter() { 053 @Override 054 public void keyReleased(KeyEvent e) { 055 int key = e.getKeyCode(); 056 if (key == KeyEvent.VK_ENTER) { 057 updateBlock(); 058 transferFocus(); // this field should no longer be in edit mode 059 } 060 } 061 }); 062 _textBox.setColumns(_nCols); 063 _textBox.setBorder(null); // drop default border so user can configure entire border appearance 064 _textBox.addFocusListener(new FocusAdapter() { 065 @Override 066 public void focusLost(FocusEvent e) { 067 updateBlock(); 068 } 069 }); 070 _textBox.addMouseMotionListener(_mouseMotionListener); 071 _textBox.addMouseListener(_mouseListener); 072 setPopupUtility(new PositionablePopupUtil(this, _textBox)); 073 } 074 075 @Override 076 public Positionable deepClone() { 077 BlockContentsInputIcon pos = new BlockContentsInputIcon(_nCols, _editor); 078 return finishClone(pos); 079 } 080 081 protected Positionable finishClone(BlockContentsInputIcon pos) { 082 pos.setBlock(namedBlock.getName()); 083 return super.finishClone(pos); 084 } 085 086 @Override 087 public JComponent getTextComponent() { 088 return _textBox; 089 } 090 091 @Override 092 public void mouseExited(JmriMouseEvent e) { 093 updateBlock(); 094 super.mouseExited(e); 095 transferFocus(); // this field should no longer be in edit mode 096 } 097 098 /** 099 * Attached a named Block to this display item 100 * 101 * @param pName Used as a system/user name to lookup the Block object 102 */ 103 public void setBlock(String pName) { 104 log.debug("setBlock for block = {}", pName); 105 if (InstanceManager.getNullableDefault(jmri.BlockManager.class) != null) { 106 try { 107 Block block = InstanceManager.getDefault(jmri.BlockManager.class).provideBlock(pName); 108 setBlock(jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(pName, block)); 109 } catch (IllegalArgumentException e) { 110 log.error("Block '{}' not available, icon won't see changes", pName); 111 } 112 } else { 113 log.error("No BlockManager for this protocol, icon won't see changes"); 114 } 115 updateSize(); 116 } 117 118 /** 119 * Attached a named Block to this display item 120 * 121 * @param b The Block object 122 */ 123 public void setBlock(NamedBeanHandle<Block> b) { 124 if (namedBlock != null) { 125 getBlock().removePropertyChangeListener(this); 126 } 127 namedBlock = b; 128 if (namedBlock != null) { 129 getBlock().addPropertyChangeListener(this, namedBlock.getName(), "Block Input Icon"); 130 displayState(); 131 setName(namedBlock.getName()); 132 } 133 } 134 135 public NamedBeanHandle<Block> getNamedBlock() { 136 return namedBlock; 137 } 138 139 public Block getBlock() { 140 if (namedBlock == null) { 141 return null; 142 } 143 return namedBlock.getBean(); 144 } 145 146 @Override 147 public jmri.NamedBean getNamedBean() { 148 return getBlock(); 149 } 150 151 // update icon as state of Block changes 152 @Override 153 public void propertyChange(java.beans.PropertyChangeEvent e) { 154 if (e.getPropertyName().equals("value")) { 155 displayState(); 156 } 157 } 158 159 @Override 160 @Nonnull 161 public String getTypeString() { 162 return Bundle.getMessage("PositionableType_BlockContentsInputIcon"); 163 } 164 165 @Override 166 public String getNameString() { 167 String name; 168 if (namedBlock == null) { 169 name = Bundle.getMessage("NotConnected"); 170 } else { 171 name = getBlock().getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME); 172 } 173 return name; 174 } 175 176 @Override 177 public void mouseMoved(JmriMouseEvent e) { 178 super.mouseMoved(e); 179 updateBlock(); 180 } 181 182 private void updateBlock() { 183 if (namedBlock == null) { 184 return; 185 } 186 String str = _textBox.getText(); 187 getBlock().setValue(str); 188 } 189 190 @Override 191 public boolean setEditIconMenu(javax.swing.JPopupMenu popup) { 192 String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameBlock")); 193 popup.add(new javax.swing.AbstractAction(txt) { 194 @Override 195 public void actionPerformed(ActionEvent e) { 196 edit(); 197 } 198 }); 199 return true; 200 } 201 202 /** 203 * Popup menu iconEditor's ActionListener 204 */ 205 SpinnerNumberModel _spinModel = new SpinnerNumberModel(3, 1, 100, 1); 206 207 @Override 208 protected void edit() { 209 _iconEditor = new IconAdder("Block") { 210 final JSpinner spinner = new JSpinner(_spinModel); 211 212 @Override 213 protected void addAdditionalButtons(JPanel p) { 214 ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField().setColumns(2); 215 spinner.setMaximumSize(spinner.getPreferredSize()); 216 spinner.setValue(_textBox.getColumns()); 217 JPanel p2 = new JPanel(); 218 //p2.setLayout(new BoxLayout(p2, BoxLayout.X_AXIS)); 219 //p2.setLayout(new FlowLayout(FlowLayout.TRAILING)); 220 p2.add(new JLabel(Bundle.getMessage("NumColsLabel"))); 221 p2.add(spinner); 222 p.add(p2); 223 p.setVisible(true); 224 } 225 }; 226 227 makeIconEditorFrame(this, "Block", true, _iconEditor); 228 _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.blockPickModelInstance()); 229 ActionListener addIconAction = a -> editBlock(); 230 _iconEditor.makeIconPanel(false); 231 _iconEditor.complete(addIconAction, false, true, true); 232 _iconEditor.setSelection(getBlock()); 233 } 234 235 void editBlock() { 236 setBlock(_iconEditor.getTableSelection().getDisplayName()); 237 _nCols = _spinModel.getNumber().intValue(); 238 _textBox.setColumns(_nCols); 239 setSize(getPreferredSize().width + 1, getPreferredSize().height); 240 _iconEditorFrame.dispose(); 241 _iconEditorFrame = null; 242 _iconEditor = null; 243 validate(); 244 } 245 246 /** 247 * Drive the current state of the display from the state of the Block. 248 */ 249 public void displayState() { 250 log.debug("displayState"); 251 if (namedBlock == null) { // leave alone if not connected yet 252 return; 253 } 254 Object show = getBlock().getValue(); 255 if (show != null) { 256 _textBox.setText(show.toString()); 257 } else { 258 _textBox.setText(""); 259 } 260 } 261 262 @Override 263 void cleanup() { 264 if (namedBlock != null) { 265 getBlock().removePropertyChangeListener(this); 266 } 267 if (_textBox != null) { 268 _textBox.removeMouseMotionListener(_mouseMotionListener); 269 _textBox.removeMouseListener(_mouseListener); 270 } 271 namedBlock = null; 272 } 273 274 private static final Logger log = LoggerFactory.getLogger(BlockContentsInputIcon.class); 275}