001package jmri.jmrix.dccpp.swing.messages;
002
003import java.awt.*;
004import java.io.File;
005import java.util.List;
006
007import javax.annotation.CheckForNull;
008import javax.swing.*;
009
010import jmri.jmrit.display.Editor;
011import jmri.jmrit.display.Positionable;
012import jmri.jmrit.display.PositionableFactory.DoAfter;
013import jmri.jmrix.dccpp.DCCppSystemConnectionMemo;
014import jmri.util.FileUtil;
015import jmri.util.JmriJFrame;
016import jmri.util.swing.JmriColorChooser;
017import jmri.util.swing.JmriJOptionPane;
018import jmri.util.swing.WrapLayout;
019
020/** Settings dialog for a DCC-EX Messages window or panel icon. */
021public class ConfigureMessages extends JmriJFrame {
022
023    private static ConfigureMessages editPositionableFrame;
024
025    private final Editor editor;
026    private final MessagesConfiguration messagesConfiguration;
027    private final MessagesPositionable newPositionable;
028    private final DoAfter doAfter;
029    private final Runnable afterApply;
030
031    private final JComboBox<DCCppConnection> _memoComboBox = new JComboBox<>();
032    final JTextField _expiryTextField = new JTextField(5);
033    final JTextField _maxLinesTextField = new JTextField(5);
034    final JCheckBox _autoOpenCheckBox = new JCheckBox();
035    final JCheckBox _playSoundCheckBox = new JCheckBox();
036    final JTextField _soundFileTextField = new JTextField(25);
037    final JTextField _columnsTextField = new JTextField(5);
038    final JTextField _linesTextField = new JTextField(5);
039    Color _foreground;
040    Color _background;
041
042    public static void createConfigureMessages(Editor editor, DoAfter doAfter) {
043        closeDialog(null);
044        MessagesPositionable pos = new MessagesPositionable(editor);
045        editPositionableFrame = new ConfigureMessages(editor, pos.getMessagesPanel(), pos, doAfter, null);
046        editPositionableFrame.initComponents();
047    }
048
049    public static void editConfigureMessages(@CheckForNull Editor editor, MessagesConfiguration config) {
050        editConfigureMessages(editor, config, null);
051    }
052
053    /** Open the settings dialog; afterApply runs once OK has applied the settings. */
054    public static void editConfigureMessages(@CheckForNull Editor editor, MessagesConfiguration config,
055            @CheckForNull Runnable afterApply) {
056        closeDialog(null);
057        editPositionableFrame = new ConfigureMessages(editor, config, null, null, afterApply);
058        editPositionableFrame.initComponents();
059    }
060
061    @CheckForNull
062    static ConfigureMessages getOpenDialog() {
063        if (editPositionableFrame != null && editPositionableFrame.isDisplayable()) {
064            return editPositionableFrame;
065        }
066        return null;
067    }
068
069    private static void closeDialog(@CheckForNull Editor editor) {
070        if (editPositionableFrame != null) {
071            editPositionableFrame.setVisible(false);
072            editPositionableFrame.dispose();
073
074            if (editor != null) {
075                editor.setVisible(true);
076            }
077        }
078    }
079
080    private ConfigureMessages(@CheckForNull Editor editor, MessagesConfiguration config,
081            @CheckForNull MessagesPositionable newPositionable, @CheckForNull DoAfter doAfter,
082            @CheckForNull Runnable afterApply) {
083        super(Bundle.getMessage(newPositionable != null ? "AddMessagesPositionable" : "EditMessages"), false, false);
084        this.editor = editor;
085        this.messagesConfiguration = config;
086        this.newPositionable = newPositionable;
087        this.doAfter = doAfter;
088        this.afterApply = afterApply;
089        _foreground = config.getMessageForeground();
090        _background = config.getMessageBackground();
091    }
092
093    @Override
094    public void initComponents() {
095        addHelpMenu("html.hardware.dcc-ex.index_messages", true);
096        setLocation(50, 30);
097        Container contentPane = getContentPane();
098        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
099
100        List<DCCppSystemConnectionMemo> systemConnections =
101                jmri.InstanceManager.getList(DCCppSystemConnectionMemo.class);
102        for (DCCppSystemConnectionMemo connection : systemConnections) {
103            DCCppConnection c = new DCCppConnection(connection);
104            _memoComboBox.addItem(c);
105            if (connection == messagesConfiguration.getMemo()) {
106                _memoComboBox.setSelectedItem(c);
107            }
108        }
109        if (_memoComboBox.getSelectedIndex() == -1 && _memoComboBox.getItemCount() > 0) {
110            _memoComboBox.setSelectedIndex(0);
111        }
112        _memoComboBox.setToolTipText(Bundle.getMessage("ConnectionHint"));
113
114        JPanel p = new JPanel();
115        p.setLayout(new GridBagLayout());
116        GridBagConstraints c = new GridBagConstraints();
117        c.gridy = 0;
118
119        if (messagesConfiguration.isMemoEditable()) {
120            addRow(p, c, "Connection", _memoComboBox);
121        }
122
123        _expiryTextField.setText(Integer.toString(messagesConfiguration.getExpirySeconds()));
124        addRow(p, c, "ConfigureMessages_Expiry", _expiryTextField);
125        _maxLinesTextField.setText(Integer.toString(messagesConfiguration.getMaxLines()));
126        addRow(p, c, "ConfigureMessages_MaxLines", _maxLinesTextField);
127
128        if (editor == null) {
129            _autoOpenCheckBox.setSelected(messagesConfiguration.isAutoOpen());
130            addRow(p, c, "ConfigureMessages_AutoOpen", _autoOpenCheckBox);
131        }
132
133        _playSoundCheckBox.setSelected(messagesConfiguration.isPlaySound());
134        addRow(p, c, "ConfigureMessages_PlaySound", _playSoundCheckBox);
135
136        _soundFileTextField.setText(messagesConfiguration.getSoundFile());
137        JButton chooseSound = new JButton(Bundle.getMessage("ConfigureMessages_Choose"));
138        chooseSound.addActionListener(e -> chooseSoundFile());
139        JPanel soundPanel = new JPanel();
140        soundPanel.add(_soundFileTextField);
141        soundPanel.add(chooseSound);
142        addRow(p, c, "ConfigureMessages_SoundFile", soundPanel);
143
144        if (editor != null) {
145            addRow(p, c, "ConfigureMessages_Foreground", colorButton(true));
146            addRow(p, c, "ConfigureMessages_Background", colorButton(false));
147            Dimension size = messagesConfiguration.getMessagesSize();
148            if (size != null) {
149                _columnsTextField.setText(Integer.toString(size.width));
150                _linesTextField.setText(Integer.toString(size.height));
151            }
152            addRow(p, c, "ConfigureMessages_Columns", _columnsTextField);
153            addRow(p, c, "ConfigureMessages_Lines", _linesTextField);
154            c.gridx = 0;
155            c.gridwidth = 2;
156            c.anchor = GridBagConstraints.WEST;
157            p.add(new JLabel(Bundle.getMessage("ConfigureMessages_SizeHelp")), c);
158        }
159
160        contentPane.add(p);
161
162        JPanel buttons = new JPanel();
163        buttons.setLayout(new WrapLayout());
164        JButton cancel = new JButton(Bundle.getMessage("ButtonCancel"));
165        buttons.add(cancel);
166        cancel.addActionListener(e -> cancel());
167        cancel.setToolTipText(Bundle.getMessage("CancelButtonHint"));
168
169        JButton ok = new JButton(Bundle.getMessage(newPositionable != null ? "ButtonCreate" : "ButtonOK"));
170        buttons.add(ok);
171        ok.addActionListener(e -> ok());
172        contentPane.add(buttons);
173
174        addWindowListener(new java.awt.event.WindowAdapter() {
175            @Override
176            public void windowClosing(java.awt.event.WindowEvent e) {
177                cancel();
178            }
179        });
180
181        pack();
182        setVisible(true);
183    }
184
185    void ok() {
186        try {
187            applyTo(messagesConfiguration);
188            if (afterApply != null) {
189                afterApply.run();
190            }
191            if (newPositionable != null) {
192                addPositionable();
193            }
194            closeDialog(editor);
195        } catch (NumberFormatException ex) {
196            JmriJOptionPane.showMessageDialog(
197                    this,
198                    Bundle.getMessage("ErrorMessagesMustBeInteger"),
199                    Bundle.getMessage("ErrorTitle"),
200                    JmriJOptionPane.ERROR_MESSAGE);
201        }
202    }
203
204    private void cancel() {
205        if (newPositionable != null) {
206            newPositionable.dispose();
207        }
208        closeDialog(editor);
209    }
210
211    private void addPositionable() {
212        newPositionable.initComponents();
213        newPositionable.setDisplayLevel(Editor.ICONS);
214        editor.setNextLocation(newPositionable);
215        try {
216            editor.putItem(newPositionable, true);
217        } catch (Positionable.DuplicateIdException e) {
218            // putItem with a null id never throws this
219            log.error("Editor.putItem() with null id has thrown DuplicateIdException", e);
220        }
221        if (doAfter != null) {
222            doAfter.doAfter(newPositionable);
223        }
224    }
225
226    private void addRow(JPanel p, GridBagConstraints c, String labelKey, JComponent field) {
227        JLabel label = new JLabel(Bundle.getMessage(labelKey));
228        label.setLabelFor(field);
229        c.gridwidth = 1;
230        c.gridx = 0;
231        c.anchor = GridBagConstraints.EAST;
232        c.weightx = 0;
233        p.add(label, c);
234        c.gridx = 1;
235        c.anchor = GridBagConstraints.WEST;
236        c.weightx = 1.0;
237        p.add(field, c);
238        c.gridy++;
239    }
240
241    private JButton colorButton(boolean foreground) {
242        JButton button = new JButton(Bundle.getMessage("ConfigureMessages_Choose"));
243        button.setOpaque(true);
244        button.setBackground(foreground ? _foreground : _background);
245        button.addActionListener(e -> {
246            String title = Bundle.getMessage(foreground ? "ConfigureMessages_Foreground" : "ConfigureMessages_Background");
247            Color color = JmriColorChooser.showDialog(this, title, foreground ? _foreground : _background);
248            if (color != null) {
249                if (foreground) {
250                    _foreground = color;
251                } else {
252                    _background = color;
253                }
254                button.setBackground(color);
255            }
256        });
257        return button;
258    }
259
260    private void chooseSoundFile() {
261        JFileChooser chooser = new JFileChooser(FileUtil.getExternalFilename("program:resources/sounds"));
262        if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
263            File file = chooser.getSelectedFile();
264            _soundFileTextField.setText(FileUtil.getPortableFilename(file));
265        }
266    }
267
268    /** Write the dialog's values to config; throws NumberFormatException, without changing anything, on bad input. */
269    void applyTo(MessagesConfiguration config) {
270        int expiry = Integer.parseInt(_expiryTextField.getText().trim());
271        int maxLines = Integer.parseInt(_maxLinesTextField.getText().trim());
272        Dimension size = null;
273        if (editor != null && !_columnsTextField.getText().isBlank() && !_linesTextField.getText().isBlank()) {
274            size = new Dimension(
275                    Integer.parseInt(_columnsTextField.getText().trim()),
276                    Integer.parseInt(_linesTextField.getText().trim()));
277        }
278
279        DCCppConnection connection = _memoComboBox.getItemAt(_memoComboBox.getSelectedIndex());
280        if (config.isMemoEditable() && connection != null) {
281            config.setMemo(connection._memo);
282        }
283        config.setExpirySeconds(expiry);
284        config.setMaxLines(maxLines);
285        if (editor == null) {
286            config.setAutoOpen(_autoOpenCheckBox.isSelected());
287        } else {
288            config.setMessageForeground(_foreground);
289            config.setMessageBackground(_background);
290            config.setMessagesSize(size);
291        }
292        config.setPlaySound(_playSoundCheckBox.isSelected());
293        config.setSoundFile(_soundFileTextField.getText().trim());
294    }
295
296    private static class DCCppConnection {
297
298        private final DCCppSystemConnectionMemo _memo;
299
300        public DCCppConnection(DCCppSystemConnectionMemo memo) {
301            _memo = memo;
302        }
303
304        @Override
305        public String toString() {
306            return _memo.getUserName();
307        }
308    }
309
310    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConfigureMessages.class);
311
312}