001package jmri.jmrix.dccpp.swing.virtuallcd;
002
003import java.awt.*;
004import java.util.*;
005
006import static jmri.jmrit.display.Editor.ICONS;
007
008import java.util.List;
009
010import javax.annotation.CheckForNull;
011import javax.swing.*;
012
013import jmri.jmrit.display.*;
014import jmri.jmrit.display.PositionableFactory.DoAfter;
015import jmri.jmrix.dccpp.*;
016import jmri.jmrix.dccpp.swing.virtuallcd.VirtualLCDConfiguration.DisplayConfig;
017import jmri.util.JmriJFrame;
018import jmri.util.swing.JmriJOptionPane;
019
020/**
021 * Configure a VirtualLCD display.
022 *
023 * @author Daniel Bergqvist (C) 2026
024 */
025public class ConfigureVirtualLCD extends JmriJFrame {
026
027    private static ConfigureVirtualLCD editPositionableFrame;
028
029    private final Editor editor;
030    private final VirtualLCDConfiguration virtualLCDConfiguration;
031    private final DoAfter doAfter;
032    private final Map<DCCppSystemConnectionMemo, Integer> highestDisplayNoMap = new HashMap<>();
033
034    private final JComboBox<DCCppConnection> _memoComboBox = new JComboBox<>();
035    private final JTextField _numColumnsTextField = new JTextField();
036    private final JTextField _numRowsTextField = new JTextField();
037    private final JComboBox<DisplayConfig> displayConfigComboBox = new JComboBox<>();
038    private final JPanel selectedDisplays = new JPanel();
039    private final JComboBox<Integer> displayNoComboBox = new JComboBox<>();
040    private final JComboBox<Integer> minDisplayNoComboBox = new JComboBox<>();
041    private final JComboBox<Integer> maxDisplayNoComboBox = new JComboBox<>();
042    private final Map<Integer, JCheckBox> selectDisplayNoCheckBox = new HashMap<>();
043
044    public static void createConfigureVirtualLCD(Editor editor, DoAfter doAfter) {
045        editPositionableFrame = new ConfigureVirtualLCD(editor, null, doAfter);
046        editPositionableFrame.initComponents();
047    }
048
049    public static void editConfigureVirtualLCD(
050            Editor editor, VirtualLCDConfiguration virtualLCDConfiguration) {
051
052        closeDialog(null);
053
054        editPositionableFrame = new ConfigureVirtualLCD(
055                editor, virtualLCDConfiguration, null);
056        editPositionableFrame.initComponents();
057    }
058
059    private static void closeDialog(@CheckForNull Editor editor) {
060        if (editPositionableFrame != null) {
061            editPositionableFrame.setVisible(false);
062            editPositionableFrame.dispose();
063
064            if (editor != null) {
065                editor.setVisible(true);
066            }
067        }
068    }
069
070    private ConfigureVirtualLCD(
071            Editor editor,
072            VirtualLCDConfiguration virtualLCDConfiguration,
073            DoAfter doAfter) {
074
075        super(Bundle.getMessage("AddVirtualLcdPositionable"), false, false);
076        this.editor = editor;
077        this.virtualLCDConfiguration = virtualLCDConfiguration;
078        this.doAfter = doAfter;
079    }
080
081    @Override
082    public void initComponents() {
083        addHelpMenu("package.jmri.jmrix.dccpp.swing.virtuallcd.VirtualLcdPositionableFactory", true);
084        setLocation(50, 30);
085        Container contentPane = getContentPane();
086        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
087
088        List<DCCppSystemConnectionMemo> systemConnections =
089                jmri.InstanceManager.getList(DCCppSystemConnectionMemo.class);
090        for (DCCppSystemConnectionMemo connection : systemConnections) {
091            DCCppConnection c = new DCCppConnection(connection);
092            _memoComboBox.addItem(c);
093            if (virtualLCDConfiguration != null && connection == virtualLCDConfiguration.getMemo()) {
094                _memoComboBox.setSelectedItem(c);
095            }
096        }
097        if (_memoComboBox.getSelectedIndex() == -1) {
098            _memoComboBox.setSelectedIndex(0);
099        }
100        _memoComboBox.setToolTipText(Bundle.getMessage("ConnectionHint"));
101
102        JLabel memoLabel = new JLabel(Bundle.getMessage("Connection"));
103        JLabel displayNoLabel = new JLabel(Bundle.getMessage("DisplayNo"));
104
105        JPanel p = new JPanel();
106        p.setLayout(new java.awt.GridBagLayout());
107        java.awt.GridBagConstraints c = new java.awt.GridBagConstraints();
108        c.gridwidth = 1;
109        c.gridheight = 1;
110        c.gridx = 0;
111        c.gridy = 0;
112        c.anchor = java.awt.GridBagConstraints.EAST;
113        if (virtualLCDConfiguration == null
114                || virtualLCDConfiguration.isMemoEditable()) {
115            p.add(memoLabel, c);
116            memoLabel.setLabelFor(_memoComboBox);
117        }
118        c.gridy = 1;
119        p.add(new JLabel(Bundle.getMessage("ConfigureVirtualLCD_NumColumns")), c);
120        displayNoLabel.setLabelFor(_numColumnsTextField);
121        c.gridy = 2;
122        p.add(new JLabel(Bundle.getMessage("ConfigureVirtualLCD_NumRows")), c);
123        displayNoLabel.setLabelFor(_numRowsTextField);
124        c.gridy = 3;
125        c.gridwidth = 2;
126        c.anchor = java.awt.GridBagConstraints.WEST;
127        p.add(new JLabel(Bundle.getMessage("ConfigureVirtualLCD_ColumsRowsHelp")), c);
128        displayNoLabel.setLabelFor(displayNoComboBox);
129        c.gridwidth = 1;
130        c.gridy = 4;
131        p.add(Box.createVerticalStrut(8), c);
132        c.gridy = 5;
133        c.anchor = java.awt.GridBagConstraints.EAST;
134        p.add(displayNoLabel, c);
135        displayNoLabel.setLabelFor(displayNoComboBox);
136        c.gridx = 1;
137        c.gridy = 0;
138        c.anchor = java.awt.GridBagConstraints.WEST;
139        c.weightx = 1.0;
140        c.fill = java.awt.GridBagConstraints.HORIZONTAL;  // text field will expand
141        if (virtualLCDConfiguration == null
142                || virtualLCDConfiguration.isMemoEditable()) {
143            p.add(_memoComboBox, c);
144        }
145        c.gridy = 1;
146        c.fill = java.awt.GridBagConstraints.NONE;  // text field will expand
147        _numColumnsTextField.setColumns(5);
148        p.add(_numColumnsTextField, c);
149        c.gridy = 2;
150        _numRowsTextField.setColumns(5);
151        p.add(_numRowsTextField, c);
152        c.gridy = 5;
153        c.fill = java.awt.GridBagConstraints.HORIZONTAL;  // text field will expand
154        for (DisplayConfig dc : DisplayConfig.values()) {
155            displayConfigComboBox.addItem(dc);
156            if (virtualLCDConfiguration != null
157                    && dc == virtualLCDConfiguration.getDisplayConfig()) {
158                displayConfigComboBox.setSelectedItem(dc);
159            }
160        }
161        p.add(displayConfigComboBox, c);
162        c.gridx = 2;
163        c.gridy = 1;
164        c.anchor = java.awt.GridBagConstraints.WEST;
165        c.weightx = 1.0;
166        c.fill = java.awt.GridBagConstraints.HORIZONTAL;  // text field will expand
167        c.gridy = 0;
168//        displayNoTextField.setToolTipText(Bundle.getMessage("DisplayNoHint"));
169
170
171        JPanel allDisplays = new JPanel();
172        allDisplays.add(new JLabel(Bundle.getMessage("ConfigureVirtualLCD_AllDisplays")));
173
174        JPanel oneDisplay = new JPanel();
175        oneDisplay.add(new JLabel(Bundle.getMessage("DisplayNo")));
176        oneDisplay.add(displayNoComboBox);
177
178        JPanel intervalDisplays = new JPanel();
179        intervalDisplays.add(new JLabel(Bundle.getMessage("DisplayNo")));
180        intervalDisplays.add(minDisplayNoComboBox);
181        intervalDisplays.add(new JLabel(" - "));
182        intervalDisplays.add(maxDisplayNoComboBox);
183
184        selectedDisplays.setLayout(new BoxLayout(selectedDisplays, BoxLayout.Y_AXIS));
185
186        JPanel cards;   // A panel that uses CardLayout
187        CardLayout cardLayout = new CardLayout();
188        cards = new JPanel(cardLayout);   // Create the panel that contains the "cards".
189        cards.setBorder(BorderFactory.createLineBorder(Color.black));
190        cards.add(allDisplays, DisplayConfig.ConfigureVirtualLCD_AllDisplays.name());
191        cards.add(oneDisplay, DisplayConfig.ConfigureVirtualLCD_OneDisplay.name());
192        cards.add(intervalDisplays, DisplayConfig.ConfigureVirtualLCD_IntervalDisplay.name());
193        cards.add(selectedDisplays, DisplayConfig.ConfigureVirtualLCD_SelectedDisplays.name());
194
195        displayConfigComboBox.addItemListener(evt -> {
196            cardLayout.show(cards, ((DisplayConfig) evt.getItem()).name());
197        });
198
199        c.gridwidth = 2;
200        c.gridheight = 1;
201        c.gridx = 0;
202        c.gridy = 6;
203        c.anchor = java.awt.GridBagConstraints.EAST;
204        p.add(cards, c);
205
206        c.gridy = 7;
207        p.add(new JLabel(Bundle.getMessage("VirtualLcdPositionable_DialogTakesTime")), c);
208
209        contentPane.add(p);
210
211        // set up create and cancel buttons
212        JPanel panel5 = new JPanel();
213        panel5.setLayout(new FlowLayout());
214        // Cancel
215        JButton cancel = new JButton(Bundle.getMessage("ButtonCancel"));
216        panel5.add(cancel);
217        cancel.addActionListener((e) -> closeDialog(editor));
218        cancel.setToolTipText(Bundle.getMessage("CancelButtonHint"));
219
220        if (virtualLCDConfiguration != null) {  // Edit configuration
221
222            Dimension lcdSize = virtualLCDConfiguration.getLCDSize();
223            if (lcdSize != null) {
224                _numColumnsTextField.setText(Integer.toString(lcdSize.width));
225                _numRowsTextField.setText(Integer.toString(lcdSize.height));
226            }
227
228            cardLayout.show(cards, virtualLCDConfiguration.getDisplayConfig().name());
229
230            JButton ok = new JButton(Bundle.getMessage("ButtonOK"));
231            panel5.add(ok);
232            ok.addActionListener((e) -> {
233                var memo = _memoComboBox.getItemAt(_memoComboBox.getSelectedIndex())._memo;
234                try {
235                    updateVirtualLCD(memo);
236                    closeDialog(editor);
237                } catch (NumberFormatException ex) {
238                    JmriJOptionPane.showMessageDialog(
239                            editor,
240                            Bundle.getMessage("ErrorDisplayNoMustBeInteger"),
241                            Bundle.getMessage("ErrorTitle"),
242                            JmriJOptionPane.ERROR_MESSAGE);
243                }
244            });
245            ok.setToolTipText(Bundle.getMessage("CreateButtonHint"));
246
247        } else {    // Create new VirtualLCD icon on a panel
248
249            JButton create = new JButton(Bundle.getMessage("ButtonCreate"));
250            panel5.add(create);
251            create.addActionListener((e) -> {
252                var memo = _memoComboBox.getItemAt(_memoComboBox.getSelectedIndex())._memo;
253                try {
254                    addVirtualLCD(memo);
255                    closeDialog(editor);
256                } catch (NumberFormatException ex) {
257                    JmriJOptionPane.showMessageDialog(
258                            editor,
259                            Bundle.getMessage("ErrorDisplayNoMustBeInteger"),
260                            Bundle.getMessage("ErrorTitle"),
261                            JmriJOptionPane.ERROR_MESSAGE);
262                }
263            });
264            create.setToolTipText(Bundle.getMessage("CreateButtonHint"));
265        }
266
267        addWindowListener(new java.awt.event.WindowAdapter() {
268            @Override
269            public void windowClosing(java.awt.event.WindowEvent e) {
270                closeDialog(editor);
271            }
272        });
273
274        contentPane.add(panel5);
275
276        // Add listeners for each memo
277        for (DCCppSystemConnectionMemo memo : systemConnections) {
278            memo.getDCCppTrafficController().addDCCppListener(DCCppInterface.CS_INFO, new DCCppListener(){
279                @Override
280                public void message(DCCppReply msg) {
281                    if (msg.isLCDTextReply()) { // <@ display# line# "message text">
282                        int displayNumber = msg.getLCDDisplayNumInt();
283                        int highestDisplayNo = highestDisplayNoMap.getOrDefault(memo,0);
284                        if (displayNumber > highestDisplayNo) {
285                            highestDisplayNoMap.put(memo, displayNumber);
286                            if (memo == _memoComboBox.getItemAt(_memoComboBox.getSelectedIndex())._memo) {
287                                configureDisplaySelection(memo);
288                            }
289//                            System.out.format("Higest display: %s:%d%n", memo.getUserName(), displayNumber);
290                        }
291                    }
292                }
293
294                @Override
295                public void message(DCCppMessage msg) {
296                }
297
298                @Override
299                public void notifyTimeout(DCCppMessage msg) {
300                }
301            });
302        }
303
304        pack();
305        setVisible(true);
306    }
307
308    private void configureDisplaySelection(DCCppSystemConnectionMemo memo) {
309        selectedDisplays.removeAll();
310        displayNoComboBox.removeAllItems();
311        minDisplayNoComboBox.removeAllItems();
312        maxDisplayNoComboBox.removeAllItems();
313        for (int i=0; i <= highestDisplayNoMap.getOrDefault(memo,0); i++) {
314            displayNoComboBox.addItem(i);
315            if (virtualLCDConfiguration != null
316                    && i == virtualLCDConfiguration.getDisplayNo()) {
317                displayNoComboBox.setSelectedItem(i);
318            }
319
320            minDisplayNoComboBox.addItem(i);
321            if (virtualLCDConfiguration != null
322                    && i == virtualLCDConfiguration.getMinDisplayNo()) {
323                minDisplayNoComboBox.setSelectedItem(i);
324            }
325
326            maxDisplayNoComboBox.addItem(i);
327            if (virtualLCDConfiguration != null
328                    && i == virtualLCDConfiguration.getMaxDisplayNo()) {
329                maxDisplayNoComboBox.setSelectedItem(i);
330            }
331
332            JCheckBox cb = new JCheckBox(Bundle.getMessage("ConfigureVirtualLCD_SelectedDisplays_CheckBox", i));
333            if (virtualLCDConfiguration != null
334                    && virtualLCDConfiguration.getSelectedDisplays().contains(i)) {
335                cb.setSelected(true);
336            }
337            selectDisplayNoCheckBox.put(i, cb);
338            selectedDisplays.add(cb);
339        }
340        this.pack();
341    }
342
343    private void addVirtualLCD(DCCppSystemConnectionMemo memo) {
344
345        VirtualLcdPositionable virtLcdPositionable = new VirtualLcdPositionable(editor);
346        updateVirtualLCD(memo, virtLcdPositionable.getVirtualLCDPanel());
347        virtLcdPositionable.initComponents();
348        virtLcdPositionable.setDisplayLevel(ICONS);
349        editor.setNextLocation(virtLcdPositionable);
350        try {
351            editor.putItem(virtLcdPositionable, true);
352        } catch (Positionable.DuplicateIdException e) {
353            // This should never happen
354            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
355        }
356        if (doAfter != null) {
357            doAfter.doAfter(virtLcdPositionable);
358        }
359    }
360
361    private void updateVirtualLCD(DCCppSystemConnectionMemo memo) {
362        updateVirtualLCD(memo, virtualLCDConfiguration);
363    }
364
365    private void updateVirtualLCD(DCCppSystemConnectionMemo memo, VirtualLCDConfiguration virtLCDConfig) {
366        virtLCDConfig.setMemo(memo);
367        if (!_numColumnsTextField.getText().isBlank() && !_numRowsTextField.getText().isBlank()) {
368            virtLCDConfig.setLCDSize(new Dimension(
369                    Integer.parseInt(_numColumnsTextField.getText()),
370                    Integer.parseInt(_numRowsTextField.getText())));
371        } else {
372            virtLCDConfig.setLCDSize(null);
373        }
374        virtLCDConfig.setDisplayConfig(displayConfigComboBox.getItemAt(displayConfigComboBox.getSelectedIndex()));
375        if (displayNoComboBox.getSelectedIndex() >= 0) {
376            virtLCDConfig.setDisplayNo(displayNoComboBox.getItemAt(displayNoComboBox.getSelectedIndex()));
377        }
378        if (minDisplayNoComboBox.getSelectedIndex() >= 0) {
379            virtLCDConfig.setMinDisplayNo(minDisplayNoComboBox.getItemAt(minDisplayNoComboBox.getSelectedIndex()));
380        }
381        if (maxDisplayNoComboBox.getSelectedIndex() >= 0) {
382            virtLCDConfig.setMaxDisplayNo(maxDisplayNoComboBox.getItemAt(maxDisplayNoComboBox.getSelectedIndex()));
383        }
384        Set<Integer> selectedDisplaysSet = new HashSet<>();
385        for (var entry : selectDisplayNoCheckBox.entrySet()) {
386            if (entry.getValue().isSelected()) {
387                selectedDisplaysSet.add(entry.getKey());
388            }
389        }
390        virtLCDConfig.setSelectedDisplays(selectedDisplaysSet);
391    }
392
393
394    private static class DCCppConnection {
395
396        private DCCppSystemConnectionMemo _memo;
397
398        public DCCppConnection(DCCppSystemConnectionMemo memo) {
399            _memo = memo;
400        }
401
402        @Override
403        public String toString() {
404            return _memo.getUserName();
405        }
406    }
407
408
409    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConfigureVirtualLCD.class);
410}