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}