001package jmri.jmrix.can.cbus.swing.nodeconfig;
002
003import java.awt.BorderLayout;
004import java.awt.Color;
005import java.awt.GridLayout;
006import java.awt.event.ActionListener;
007import javax.swing.*;
008import javax.swing.event.ChangeEvent;
009
010import jmri.jmrix.can.cbus.node.CbusNode;
011import jmri.jmrix.can.cbus.node.CbusNodeTimerManager;
012import jmri.util.ThreadingUtil;
013import jmri.util.swing.JmriJOptionPane;
014import jmri.util.swing.JSpinnerUtil;
015
016/**
017 *
018 * @author Steve Young Copyright (C) 2019
019 */
020public class CbusNodeSetupPane extends CbusNodeConfigTab {
021
022    private transient ActionListener setNameListener;
023    private transient ActionListener removeListener;
024    private transient ActionListener setCanIdListener;
025    private transient ActionListener selfEnumerateListener;
026    private transient ActionListener clearAllEventsListener;
027    private jmri.util.swing.BusyDialog busy_dialog;
028
029    private JTextField textFieldName;
030
031    /**
032     * Create a new instance of CbusNodeSetupPane.
033     * @param main the main NodeConfigToolPane this is a pane of.
034     */
035    protected CbusNodeSetupPane( NodeConfigToolPane main ) {
036        super(main);
037        getInitPane();
038    }
039
040    /**
041     * {@inheritDoc}
042     */
043    @Override
044    public String getTitle(){
045        return "Node Setup";
046    }
047
048    /**
049     * {@inheritDoc}
050     */
051    @Override
052    public void changedNode(CbusNode newNode){
053
054        textFieldName.setText( nodeOfInterest.getUserName() );
055        validate();
056        repaint();
057
058    }
059
060    private void getInitPane() {
061
062        JPanel evPane = new JPanel();
063        evPane.setLayout(new BoxLayout(evPane, BoxLayout.Y_AXIS));
064
065        initListeners();
066
067        JPanel nodeEventsPanel = new JPanel();
068        nodeEventsPanel.setBorder(BorderFactory.createTitledBorder(
069                  BorderFactory.createEtchedBorder(), Bundle.getMessage("EventCol")));
070        JButton clearAllEventsButton = new JButton("Clear All Events");
071        clearAllEventsButton.addActionListener(clearAllEventsListener);
072        nodeEventsPanel.add(clearAllEventsButton);
073
074        evPane.add(getNamePanel());
075        evPane.add(getCanIdPanel());
076        evPane.add(nodeEventsPanel);
077        evPane.add(getRemovePanel());
078
079        JScrollPane eventScroll = new JScrollPane(evPane);
080
081        add(eventScroll, BorderLayout.CENTER);
082
083    }
084
085    private JPanel getNamePanel() {
086
087        JPanel namePanel = new JPanel();
088        namePanel.setBorder(BorderFactory.createTitledBorder(
089            BorderFactory.createEtchedBorder(), ("JMRI Node User Name" ) ) );
090        JButton setNameButton = new JButton("Set Module User Name");
091        textFieldName = new JTextField(20);
092        
093        namePanel.add(textFieldName);
094        namePanel.add(setNameButton);
095        setNameButton.addActionListener(setNameListener);
096        return namePanel;
097    }
098
099    private JPanel getCanIdPanel() {
100
101        JPanel canIdPanel = new JPanel();
102        canIdPanel.setBorder(BorderFactory.createTitledBorder(
103            BorderFactory.createEtchedBorder(), ( "CAN ID")));
104        JButton selfCanEnumerateButton = new JButton("CAN ID Self Enumeration");
105        selfCanEnumerateButton.addActionListener(selfEnumerateListener);
106        JButton setCanIdButton = new JButton("Force set CAN ID");
107        setCanIdButton.addActionListener(setCanIdListener);
108        canIdPanel.add(selfCanEnumerateButton);
109        canIdPanel.add(setCanIdButton);
110        
111        return canIdPanel;
112    }
113    
114    private JPanel getRemovePanel() {
115        JPanel removePanel = new JPanel();
116        removePanel.setBorder(BorderFactory.createTitledBorder(
117            BorderFactory.createEtchedBorder(), ("Node Manager")));
118        JButton removeNodeButton = new JButton("Remove from Table");
119        removePanel.add(removeNodeButton);
120        removeNodeButton.addActionListener(removeListener);
121        return removePanel;
122    }
123
124    private void initListeners() {   
125
126        setNameListener = ae -> {
127            nodeOfInterest.setUserName(textFieldName.getText());
128            changedNode(nodeOfInterest);
129        };
130
131        removeListener = ae -> {
132            JCheckBox checkbox = new JCheckBox(("Remove node xml File"));
133            int oldRow = Math.max(0, getNodeRow()-1);
134            int option = JmriJOptionPane.showConfirmDialog(this, 
135                new Object[]{("Remove Node from Manager?"), checkbox}, 
136                "Please Confirm", 
137                JmriJOptionPane.OK_CANCEL_OPTION);
138            if ( option == JmriJOptionPane.OK_OPTION ) {
139                getMainPane().getNodeModel().
140                removeRow( getMainPane().getNodeModel().getNodeRowFromNodeNum(nodeOfInterest.getNodeNumber())
141                ,checkbox.isSelected() );
142                if (getMainPane().nodeTable.getRowCount() > 0 ) {
143                    getMainPane().nodeTable.getSelectionModel().setSelectionInterval(oldRow,oldRow);
144                    getMainPane().tabbedPane.setSelectedIndex(0);
145                }
146            }
147        };
148
149        selfEnumerateListener = ae -> {
150            // start busy
151            busy_dialog = new jmri.util.swing.BusyDialog(null, "CAN ID", false);
152            busy_dialog.start();
153            // CbusNode will pick the outgoing message up, start timer and show dialogue on error / timeout
154            nodeOfInterest.send.eNUM(nodeOfInterest.getNodeNumber());
155            // cancel the busy
156            ThreadingUtil.runOnGUIDelayed(() -> {
157                changedNode(nodeOfInterest); // refresh pane with new CAN ID
158                busy_dialog.finish();
159                busy_dialog=null;
160            },CbusNodeTimerManager.SINGLE_MESSAGE_TIMEOUT_TIME );
161        };
162
163        setCanIdListener = ae -> {
164            newCanIdDialogue();
165        };
166
167        clearAllEventsListener = ae -> {
168            int option = JmriJOptionPane.showConfirmDialog(this, 
169                "Delete All Events from Node?", 
170                "Please Confirm", 
171                JmriJOptionPane.OK_CANCEL_OPTION);
172            if ( option == JmriJOptionPane.OK_OPTION ) {
173
174                // check for existing nodes in learn mode
175                if ( getMainPane().getNodeModel().getAnyNodeInLearnMode() > -1 ) {
176                    log.warn("Cancelling action, node {} is already in learn mode",getMainPane().getNodeModel().getAnyNodeInLearnMode());
177                    return;
178                }
179
180                // start busy
181                busy_dialog = new jmri.util.swing.BusyDialog(null, "Clear All Events", false);
182                busy_dialog.start();
183
184                // node enter learn mode
185                nodeOfInterest.send.nodeEnterLearnEvMode( nodeOfInterest.getNodeNumber() ); // no response expected but we add a mini delay for other traffic
186
187                ThreadingUtil.runOnLayoutDelayed( () -> {
188                    nodeOfInterest.send.nNCLR(nodeOfInterest.getNodeNumber());// no response expected
189                }, 150 );
190
191                ThreadingUtil.runOnLayoutDelayed(() -> {
192                    // node exit learn mode
193                    nodeOfInterest.send.nodeExitLearnEvMode( nodeOfInterest.getNodeNumber() ); // no response expected
194                }, CbusNodeTimerManager.SINGLE_MESSAGE_TIMEOUT_TIME );
195
196                ThreadingUtil.runOnGUIDelayed(() -> {
197
198                    // stop 
199                    busy_dialog.finish();
200                    busy_dialog=null;
201
202                    // query new num events which should be 0
203                    // RQEVN
204                    nodeOfInterest.send.rQEVN( nodeOfInterest.getNodeNumber() );
205
206                }, ( CbusNodeTimerManager.SINGLE_MESSAGE_TIMEOUT_TIME + 150 ) );
207
208            }
209        };
210
211
212    }
213
214    private boolean CANID_DIALOGUE_OPEN = false;
215    private JFormattedTextField rqfield;
216    private JLabel rqNNspinnerlabel;
217
218    private void newCanIdDialogue() {
219
220        if (CANID_DIALOGUE_OPEN) {
221            return;
222        }
223
224        log.debug("allocating new can id");
225
226        CANID_DIALOGUE_OPEN=true;
227
228        JPanel rqNNpane = new JPanel();
229        JPanel bottomrqNNpane = new JPanel();
230        String spinnerlabel="";
231        rqNNspinnerlabel = new JLabel(spinnerlabel);
232
233        bottomrqNNpane.setLayout(new GridLayout(2, 1));
234        rqNNpane.setLayout(new BorderLayout());
235
236        String popuplabel;
237        popuplabel=("Please Select a new CAN ID");
238
239        // forces a value between 1-99
240        JSpinner rqnnSpinner = new JSpinner(
241            new SpinnerNumberModel(Math.min(99,(Math.max(1,nodeOfInterest.getNodeCanId()))), 1, 99, 1));
242        JSpinnerUtil.setCommitsOnValidEdit(rqnnSpinner, true);
243        JComponent rqcomp = rqnnSpinner.getEditor();
244        rqfield = (JFormattedTextField) rqcomp.getComponent(0);
245        rqfield.setBackground(Color.white);
246        rqnnSpinner.addChangeListener((ChangeEvent e) -> {
247            int newval = (Integer) rqnnSpinner.getValue();
248            log.debug("new canid selected value {}",newval);
249            updateSpinnerFeedback(newval);
250        });
251
252        bottomrqNNpane.add(rqNNspinnerlabel);
253        bottomrqNNpane.add(rqnnSpinner);
254
255        rqNNpane.add(bottomrqNNpane, BorderLayout.CENTER);
256
257        // forces a value between 1-99
258        updateSpinnerFeedback( Math.min(99,(Math.max(1,nodeOfInterest.getNodeCanId()))) );
259
260        int option = JmriJOptionPane.showConfirmDialog(this, 
261            rqNNpane, 
262            popuplabel, 
263            JmriJOptionPane.OK_CANCEL_OPTION);
264        if ( option == JmriJOptionPane.CANCEL_OPTION || option == JmriJOptionPane.CLOSED_OPTION ) {
265            CANID_DIALOGUE_OPEN=false;
266        } else if ( option == JmriJOptionPane.OK_OPTION ) {
267            int newval = (Integer) rqnnSpinner.getValue();
268           // baseNodeNum = newval;
269
270            busy_dialog = new jmri.util.swing.BusyDialog(null, "CAN ID", false);
271            busy_dialog.start();
272            // CbusNode will pick the outgoing message up, start timer and show dialogue on error / timeout
273
274            nodeOfInterest.send.cANID(nodeOfInterest.getNodeNumber(), newval);
275
276            // cancel the busy
277            ThreadingUtil.runOnGUIDelayed(() -> {
278                changedNode(nodeOfInterest); // refresh pane with new CAN ID
279                busy_dialog.finish();
280                busy_dialog=null;
281                CANID_DIALOGUE_OPEN=false;
282                
283            },CbusNodeTimerManager.SINGLE_MESSAGE_TIMEOUT_TIME );            
284        }
285    }
286
287    private void updateSpinnerFeedback( int newval ) {
288        if ( getMainPane().getNodeModel().getNodeNameFromCanId(newval).isEmpty() ) {
289            rqfield.setBackground(Color.white);
290            rqNNspinnerlabel.setText("");
291        }
292        else {
293            rqfield.setBackground(Color.yellow);
294            rqNNspinnerlabel.setText("In Use by " + getMainPane().getNodeModel().getNodeNameFromCanId(newval) );
295        }
296    }
297
298    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusNodeSetupPane.class);
299
300}