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}