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