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