001package jmri.jmrix.oaktree.nodeconfig;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.awt.Container;
006import java.awt.FlowLayout;
007
008import javax.swing.BorderFactory;
009import javax.swing.BoxLayout;
010import javax.swing.JComboBox;
011import javax.swing.JLabel;
012import javax.swing.JPanel;
013import javax.swing.JSpinner;
014import javax.swing.SpinnerNumberModel;
015import javax.swing.border.Border;
016
017import jmri.jmrix.oaktree.OakTreeSystemConnectionMemo;
018import jmri.jmrix.oaktree.SerialNode;
019import jmri.jmrix.oaktree.SerialSensorManager;
020import jmri.util.swing.JmriJOptionPane;
021
022/**
023 * Frame for user configuration of OakTree serial nodes.
024 *
025 * @author Bob Jacobsen Copyright (C) 2004
026 * @author Dave Duchamp Copyright (C) 2004, 2006
027 */
028public class NodeConfigFrame extends jmri.util.JmriJFrame {
029
030    protected JSpinner nodeAddrSpinner;
031    protected javax.swing.JLabel nodeAddrStatic = new javax.swing.JLabel("000");
032    protected javax.swing.JComboBox<String> nodeTypeBox;
033
034    protected javax.swing.JButton addButton = new javax.swing.JButton(Bundle.getMessage("ButtonAdd"));
035    protected javax.swing.JButton editButton = new javax.swing.JButton(Bundle.getMessage("ButtonEdit"));
036    protected javax.swing.JButton deleteButton = new javax.swing.JButton(Bundle.getMessage("ButtonDelete"));
037    protected javax.swing.JButton doneButton = new javax.swing.JButton(Bundle.getMessage("ButtonDone"));
038    protected javax.swing.JButton updateButton = new javax.swing.JButton(Bundle.getMessage("ButtonUpdate"));
039    protected javax.swing.JButton cancelButton = new javax.swing.JButton(Bundle.getMessage("ButtonCancel"));
040
041    protected javax.swing.JLabel statusText1 = new javax.swing.JLabel();
042    protected javax.swing.JLabel statusText2 = new javax.swing.JLabel();
043    protected javax.swing.JLabel statusText3 = new javax.swing.JLabel();
044
045    protected boolean changedNode = false;  // true if a node was changed, deleted, or added
046    protected boolean editMode = false;     // true if in edit mode
047    private boolean checkEnabled = jmri.InstanceManager.getDefault(jmri.configurexml.ShutdownPreferences.class).isStoreCheckEnabled();
048
049    protected SerialNode curNode = null;    // Serial Node being editted
050    protected int nodeAddress = 0;          // Node address
051    protected int nodeType = SerialNode.IO48; // Node type
052
053    protected boolean errorInStatus1 = false;
054    protected boolean errorInStatus2 = false;
055    protected String stdStatus1 = Bundle.getMessage("NotesStd1");
056    protected String stdStatus2 = Bundle.getMessage("NotesStd2");
057    protected String stdStatus3 = Bundle.getMessage("NotesStd3");
058    protected String editStatus1 = Bundle.getMessage("NotesEdit1");
059    protected String editStatus2 = Bundle.getMessage("NotesEdit2");
060    protected String editStatus3 = Bundle.getMessage("NotesEdit3");
061
062    private OakTreeSystemConnectionMemo _memo = null;
063
064    /**
065     * Constructor method.
066     * @param memo system connection.
067     */
068    public NodeConfigFrame(OakTreeSystemConnectionMemo memo) {
069        super();
070        _memo = memo;
071
072        addHelpMenu("package.jmri.jmrix.oaktree.nodeconfig.NodeConfigFrame", true); // please write this help file
073    }
074
075    /**
076     * Initialize the config window.
077     */
078    @Override
079    public void initComponents() {
080        setTitle(Bundle.getMessage("ConfigNodesTitle"));
081
082        Container contentPane = getContentPane();
083        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
084
085        // Set up node address and node type
086        JPanel panel1 = new JPanel();
087        panel1.setLayout(new BoxLayout(panel1, BoxLayout.Y_AXIS));
088
089        // panel11 is the node address and type
090        JPanel panel11 = new JPanel();
091        panel11.setLayout(new FlowLayout());
092        panel11.add(new JLabel(Bundle.getMessage("LabelNodeAddress") + " "));
093        nodeAddrSpinner = new JSpinner(new SpinnerNumberModel(1, 0, 255, 1)); // start value 1, as 0 causes NPE upan [Add Node]
094        panel11.add(nodeAddrSpinner);
095        nodeAddrSpinner.setToolTipText(Bundle.getMessage("TipNodeAddress"));
096        panel11.add(nodeAddrStatic);
097        nodeAddrStatic.setVisible(false);
098        panel11.add(new JLabel("   " + Bundle.getMessage("LabelNodeType") + " "));
099        nodeTypeBox = new JComboBox<String>(SerialNode.getBoardNames());
100        panel11.add(nodeTypeBox);
101        nodeTypeBox.setToolTipText(Bundle.getMessage("TipNodeType"));
102        contentPane.add(panel11);
103
104        // Set up the notes panel
105        JPanel panel3 = new JPanel();
106        panel3.setLayout(new BoxLayout(panel3, BoxLayout.Y_AXIS));
107        JPanel panel31 = new JPanel();
108        panel31.setLayout(new FlowLayout());
109        statusText1.setText(stdStatus1);
110        statusText1.setVisible(true);
111        panel31.add(statusText1);
112        JPanel panel32 = new JPanel();
113        panel32.setLayout(new FlowLayout());
114        statusText2.setText(stdStatus2);
115        statusText2.setVisible(true);
116        panel32.add(statusText2);
117        JPanel panel33 = new JPanel();
118        panel33.setLayout(new FlowLayout());
119        statusText3.setText(stdStatus3);
120        statusText3.setVisible(true);
121        panel33.add(statusText3);
122        panel3.add(panel31);
123        panel3.add(panel32);
124        panel3.add(panel33);
125        Border panel3Border = BorderFactory.createEtchedBorder();
126        Border panel3Titled = BorderFactory.createTitledBorder(panel3Border,
127                Bundle.getMessage("BoxLabelNotes"));
128        panel3.setBorder(panel3Titled);
129        contentPane.add(panel3);
130
131        // Set up buttons
132        JPanel panel4 = new JPanel();
133        panel4.setLayout(new FlowLayout());
134        addButton.setText(Bundle.getMessage("ButtonAdd"));
135        addButton.setVisible(true);
136        addButton.setToolTipText(Bundle.getMessage("TipAddButton"));
137        addButton.addActionListener(new java.awt.event.ActionListener() {
138            @Override
139            public void actionPerformed(java.awt.event.ActionEvent e) {
140                addButtonActionPerformed();
141            }
142        });
143        panel4.add(addButton);
144        editButton.setText(Bundle.getMessage("ButtonEdit"));
145        editButton.setVisible(true);
146        editButton.setToolTipText(Bundle.getMessage("TipEditButton"));
147        panel4.add(editButton);
148        editButton.addActionListener(new java.awt.event.ActionListener() {
149            @Override
150            public void actionPerformed(java.awt.event.ActionEvent e) {
151                editButtonActionPerformed();
152            }
153        });
154        panel4.add(deleteButton);
155        deleteButton.setText(Bundle.getMessage("ButtonDelete"));
156        deleteButton.setVisible(true);
157        deleteButton.setToolTipText(Bundle.getMessage("TipDeleteButton"));
158        panel4.add(deleteButton);
159        deleteButton.addActionListener(new java.awt.event.ActionListener() {
160            @Override
161            public void actionPerformed(java.awt.event.ActionEvent e) {
162                deleteButtonActionPerformed();
163            }
164        });
165        panel4.add(doneButton);
166        doneButton.setText(Bundle.getMessage("ButtonDone"));
167        doneButton.setVisible(true);
168        doneButton.setToolTipText(Bundle.getMessage("TipDoneButton"));
169        panel4.add(doneButton);
170        doneButton.addActionListener(new java.awt.event.ActionListener() {
171            @Override
172            public void actionPerformed(java.awt.event.ActionEvent e) {
173                doneButtonActionPerformed();
174            }
175        });
176        panel4.add(updateButton);
177        updateButton.setText(Bundle.getMessage("ButtonUpdate"));
178        updateButton.setVisible(true);
179        updateButton.setToolTipText(Bundle.getMessage("TipUpdateButton"));
180        panel4.add(updateButton);
181        updateButton.addActionListener(new java.awt.event.ActionListener() {
182            @Override
183            public void actionPerformed(java.awt.event.ActionEvent e) {
184                updateButtonActionPerformed();
185            }
186        });
187        updateButton.setVisible(false);
188        panel4.add(cancelButton);
189        cancelButton.setText(Bundle.getMessage("ButtonCancel"));
190        cancelButton.setVisible(true);
191        cancelButton.setToolTipText(Bundle.getMessage("TipCancelButton"));
192        panel4.add(cancelButton);
193        cancelButton.addActionListener(new java.awt.event.ActionListener() {
194            @Override
195            public void actionPerformed(java.awt.event.ActionEvent e) {
196                cancelButtonActionPerformed();
197            }
198        });
199        cancelButton.setVisible(false);
200        contentPane.add(panel4);
201
202        // pack for display
203        pack();
204    }
205
206    /**
207     * Handle Add button
208     */
209    public void addButtonActionPerformed() {
210        // Check that a node with this address does not exist
211        int nodeAddress = readNodeAddress();
212        if (nodeAddress < 0) {
213            return;
214        }
215        // get a SerialNode corresponding to this node address if one exists
216        curNode = (SerialNode) _memo.getTrafficController().getNodeFromAddress(nodeAddress);
217        if (curNode != null) {
218            statusText1.setText(Bundle.getMessage("Error1", Integer.toString(nodeAddress)));
219            statusText1.setVisible(true);
220            errorInStatus1 = true;
221            resetNotes2();
222            return;
223        }
224        nodeType = nodeTypeBox.getSelectedIndex();
225
226        // all ready, create the new node
227        curNode = new SerialNode(nodeAddress, nodeType, _memo);
228        // configure the new node
229        setNodeParameters();
230        log.debug("config node {} ready", nodeAddress);
231        // register any orphan sensors that this node may have
232        ((SerialSensorManager)_memo.getSensorManager()).registerSensorsForNode(curNode);
233        // reset after succesfully adding node
234        resetNotes();
235        changedNode = true;
236        // provide user feedback
237        statusText1.setText(Bundle.getMessage("FeedBackAdd") + " "
238                + Integer.toString(nodeAddress));
239        errorInStatus1 = true;
240    }
241
242    /**
243     * Handle Edit button
244     */
245    public void editButtonActionPerformed() {
246        // Find Serial Node address
247        nodeAddress = readNodeAddress();
248        if (nodeAddress < 0) {
249            return;
250        }
251        // get the SerialNode corresponding to this node address
252        curNode = (SerialNode) _memo.getTrafficController().getNodeFromAddress(nodeAddress);
253        if (curNode == null) {
254            statusText1.setText(Bundle.getMessage("Error4"));
255            statusText1.setVisible(true);
256            errorInStatus1 = true;
257            resetNotes2();
258            return;
259        }
260        // Set up static node address
261        nodeAddrStatic.setText(Integer.toString(nodeAddress));
262        nodeAddrSpinner.setVisible(false);
263        nodeAddrStatic.setVisible(true);
264        // get information for this node and set up combo box
265        nodeType = curNode.getNodeType();
266        nodeTypeBox.setSelectedIndex(nodeType);
267        // Switch buttons
268        editMode = true;
269        addButton.setVisible(false);
270        editButton.setVisible(false);
271        deleteButton.setVisible(false);
272        doneButton.setVisible(false);
273        updateButton.setVisible(true);
274        cancelButton.setVisible(true);
275        // Switch to edit notes
276        statusText1.setText(editStatus1);
277        statusText2.setText(editStatus2);
278        statusText3.setText(editStatus3);
279    }
280
281    /**
282     * Handle Delete button
283     */
284    public void deleteButtonActionPerformed() {
285        // Find Serial Node address
286        int nodeAddress = readNodeAddress();
287        if (nodeAddress < 0) {
288            return;
289        }
290        // get the SerialNode corresponding to this node address
291        curNode = (SerialNode) _memo.getTrafficController().getNodeFromAddress(nodeAddress);
292        if (curNode == null) {
293            statusText1.setText(Bundle.getMessage("Error4"));
294            statusText1.setVisible(true);
295            errorInStatus1 = true;
296            resetNotes2();
297            return;
298        }
299        // confirm deletion with the user
300        if (JmriJOptionPane.OK_OPTION == JmriJOptionPane.showConfirmDialog(
301                this, Bundle.getMessage("ConfirmDelete1") + "\n"
302                + Bundle.getMessage("ConfirmDelete2"), Bundle.getMessage("ConfirmDeleteTitle"),
303                JmriJOptionPane.OK_CANCEL_OPTION,
304                JmriJOptionPane.WARNING_MESSAGE)) {
305            // delete this node
306            _memo.getTrafficController().deleteNode(nodeAddress);
307            // provide user feedback
308            resetNotes();
309            statusText1.setText(Bundle.getMessage("FeedBackDelete") + " "
310                    + Integer.toString(nodeAddress));
311            errorInStatus1 = true;
312            changedNode = true;
313        } else {
314            // reset as needed
315            resetNotes();
316        }
317    }
318
319    /**
320     * Handle Done button
321     */
322    public void doneButtonActionPerformed() {
323        if (editMode) {
324            // Reset
325            editMode = false;
326            curNode = null;
327            // Switch buttons
328            addButton.setVisible(true);
329            editButton.setVisible(true);
330            deleteButton.setVisible(true);
331            doneButton.setVisible(true);
332            updateButton.setVisible(false);
333            cancelButton.setVisible(false);
334            nodeAddrSpinner.setVisible(true);
335            nodeAddrStatic.setVisible(false);
336        }
337        if (changedNode && !checkEnabled) {
338            // Remind user to Save new configuration
339            JmriJOptionPane.showMessageDialog(this,
340                    Bundle.getMessage("ReminderNode1") + "\n" + Bundle.getMessage("Reminder2"),
341                    Bundle.getMessage("ReminderTitle"),
342                    JmriJOptionPane.INFORMATION_MESSAGE);
343        }
344        setVisible(false);
345        dispose();
346    }
347
348    /**
349     * Handle Update button
350     */
351    public void updateButtonActionPerformed() {
352        // update node information
353        nodeType = nodeTypeBox.getSelectedIndex();
354        log.debug("update performed: was {} request {}", curNode.getNodeType(), nodeType);
355        if (curNode.getNodeType() != nodeType) {
356            // node type has changed
357            curNode.setNodeType(nodeType);
358        }
359        setNodeParameters();
360        changedNode = true;
361        // Reset Edit Mode
362        editMode = false;
363        curNode = null;
364        // Switch buttons
365        addButton.setVisible(true);
366        editButton.setVisible(true);
367        deleteButton.setVisible(true);
368        doneButton.setVisible(true);
369        updateButton.setVisible(false);
370        cancelButton.setVisible(false);
371        // make node address editable again
372        nodeAddrSpinner.setVisible(true);
373        nodeAddrStatic.setVisible(false);
374        // refresh notes panel
375        statusText2.setText(stdStatus2);
376        statusText3.setText(stdStatus3);
377        // provide user feedback
378        statusText1.setText(Bundle.getMessage("FeedBackUpdate") + " "
379                + Integer.toString(nodeAddress));
380        errorInStatus1 = true;
381    }
382
383    /**
384     * Handle Cancel button
385     */
386    public void cancelButtonActionPerformed() {
387        // Reset
388        editMode = false;
389        curNode = null;
390        // Switch buttons
391        addButton.setVisible(true);
392        editButton.setVisible(true);
393        deleteButton.setVisible(true);
394        doneButton.setVisible(true);
395        updateButton.setVisible(false);
396        cancelButton.setVisible(false);
397        // make node address editable again
398        nodeAddrSpinner.setVisible(true);
399        nodeAddrStatic.setVisible(false);
400        // refresh notes panel
401        statusText1.setText(stdStatus1);
402        statusText2.setText(stdStatus2);
403        statusText3.setText(stdStatus3);
404    }
405
406    /**
407     * Do the done action if the window is closed early.
408     */
409    @Override
410    @SuppressFBWarnings(value = "OVERRIDING_METHODS_MUST_INVOKE_SUPER",
411            justification = "This calls doneButtonActionPerformed which handles window closing")
412    public void windowClosing(java.awt.event.WindowEvent e) {
413        doneButtonActionPerformed();
414    }
415
416    /**
417     * Set node parameters. The node must exist, and be in 'curNode'
418     * Also, the node type must be set and in 'nodeType'
419     */
420    void setNodeParameters() {
421        // set curNode type
422        curNode.setNodeType(nodeType);
423        // Cause reinitialization of this Node to reflect these parameters
424        _memo.getTrafficController().initializeSerialNode(curNode);
425    }
426
427    /**
428     * Method to reset the notes error after error display
429     */
430    private void resetNotes() {
431        if (errorInStatus1) {
432            if (editMode) {
433                statusText1.setText(editStatus1);
434            } else {
435                statusText1.setText(stdStatus1);
436            }
437            errorInStatus1 = false;
438        }
439        resetNotes2();
440    }
441
442    /**
443     * Reset the second line of Notes area
444     */
445    private void resetNotes2() {
446        if (errorInStatus2) {
447            if (editMode) {
448                statusText1.setText(editStatus2);
449            } else {
450                statusText2.setText(stdStatus2);
451            }
452            errorInStatus2 = false;
453        }
454    }
455
456    /**
457     * Read node address and check for legal range, If successful, a node address
458     * in the range 0-255 is returned. Note address 0 causes an NPE when clicking Add!
459     */
460    private int readNodeAddress() {
461        int addr = -1;
462        addr = (Integer) nodeAddrSpinner.getValue();
463        log.debug("addr = {}", addr);
464        return (addr);
465    }
466
467    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NodeConfigFrame.class);
468
469}